From 4e421ca186162af42d7e546795057763dcfcc1ab Mon Sep 17 00:00:00 2001 From: Weblate Date: Tue, 3 Aug 2021 09:52:00 -0400 Subject: [PATCH 001/232] Translated using Weblate (Finnish) Currently translated at 100.0% (493 of 493 strings) Co-authored-by: -- Translate-URL: https://weblate.nheko.im/projects/nheko/nheko-master/fi/ Translation: Nheko/nheko --- resources/langs/nheko_fi.ts | 535 +++++++++++++++++++----------------- 1 file changed, 276 insertions(+), 259 deletions(-) diff --git a/resources/langs/nheko_fi.ts b/resources/langs/nheko_fi.ts index 9c48e98f..20428bff 100644 --- a/resources/langs/nheko_fi.ts +++ b/resources/langs/nheko_fi.ts @@ -22,7 +22,7 @@ Hide/Show Picture-in-Picture - + Piilota/Näytä kuva kuvassa @@ -45,7 +45,7 @@ Waiting for other side to complete verification. - + Odotetaan toista puolta saamaan vahvistus loppuun. @@ -119,7 +119,7 @@ Entire screen - + Koko näyttö @@ -127,7 +127,7 @@ Failed to invite user: %1 - + Käyttäjää %1 ei onnistuttu kutsumaan @@ -138,7 +138,7 @@ Migrating the cache to the current version failed. This can have different reasons. Please open an issue and try to use an older version in the mean time. Alternatively you can try deleting the cache manually. - + Välimuistin tuominen nykyiseen versioon epäonnistui. Tällä voi olla eri syitä. Luo vikailmoitus ja yritä sillä aikaa käyttää vanhempaa versiota. Voit myös vaihtoehtoisesti koettaa tyhjentää välimuistin käsin. @@ -153,98 +153,98 @@ Room %1 created. - + Huone %1 luotu. Confirm invite - + Vahvista kutsu Do you really want to invite %1 (%2)? - + Haluatko kutsua %1 (%2)? Failed to invite %1 to %2: %3 - + Epäonnistuttiin kutsuminen %1 huoneeseen %2:%3 Confirm kick - + Vahvista potkut Do you really want to kick %1 (%2)? - + Haluatko potkia %1 (%2)? Kicked user: %1 - + Potkittiin käyttäjä: %1 Confirm ban - + Vahvista porttikielto Do you really want to ban %1 (%2)? - + Haluatko antaa porttikiellon käyttäjälle %1 (%2)? Failed to ban %1 in %2: %3 - + Ei onnistuttu antamaan porttikieltoa käyttäjälle %1 huoneessa %2:%3 Banned user: %1 - + Annettiin porttikielto käyttäjälle: %1 Confirm unban - + Vahvista porttikiellon purku Do you really want to unban %1 (%2)? - + Haluatko purkaa porttikiellon käyttäjältä %1 (%2)? Failed to unban %1 in %2: %3 - + Ei onnistuttu purkamaan porttikieltoa käyttäjältä %1 huoneessa %2: %3 Unbanned user: %1 - + Purettiin porttikielto käyttäjältä %1 Do you really want to start a private chat with %1? - + Haluatko luoda yksityisen keskustelun käyttäjän %1 kanssa? Cache migration failed! - + Välimuistin siirto epäonnistui! Incompatible cache version - + Yhteensopimaton välimuistin versio The cache on your disk is newer than this version of Nheko supports. Please update or clear your cache. - + Levylläsi oleva välimuisti on uudempaa kuin mitä tämä Nhekon versio tukee. Päivitä tai poista välimuistisi. @@ -280,7 +280,7 @@ Failed to remove invite: %1 - + Kutsua ei onnistuttu poistamaan: %1 @@ -295,7 +295,7 @@ Failed to kick %1 from %2: %3 - + Ei onnistuttu potkimaan käyttäjää %1 huoneesta %2: %3 @@ -303,7 +303,7 @@ Hide rooms with this tag or from this space by default. - + Piilota huoneet tällä tagilla tai tästä tilasta oletuksena. @@ -316,37 +316,37 @@ Shows all rooms without filtering. - + Näytä kaikki huoneet ilman suodattamista. Favourites - + Suosikit Rooms you have favourited. - + Suosikkihuoneesi Low Priority - + Matala tärkeysjärjestys Rooms with low priority. - + Huoneet matalalla tärkeysjärjestyksellä. Server Notices - + Palvelimen ilmoitukset Messages from your server or administrator. - + Viestit palvelimeltasi tai ylläpitäjältä. @@ -354,27 +354,27 @@ Decrypt secrets - + Salaisuuksien salauksen purku Enter your recovery key or passphrase to decrypt your secrets: - + Anna palauttamisavain tai salasana purkaaksesi salaisuuksiesi salaus: Enter your recovery key or passphrase called %1 to decrypt your secrets: - + Anna palautusavaimesi tai salasanasi nimeltä %1 purkaaksesi salaisuuksien salauksen: Decryption failed - + Salauksen purku epäonnistui Failed to decrypt secrets with the provided recovery key or passphrase - + Salaisuuksien salauksen purkaminen ei onnistunut annetulla palautusavaimella tai salasanalla @@ -382,22 +382,22 @@ Verification Code - + Vahvistuskoodi Please verify the following digits. You should see the same numbers on both sides. If they differ, please press 'They do not match!' to abort verification! - + Vahvista seuraavat numerot. Sinun tulisi nähdä samat numerot molemmilla puolilla. Jos niissä on eroa, paina "Ne eivät vastaa toisiaan" peruaksesi vahvistuksen! They do not match! - + Ne eivät vastaa toisiaan! They match! - + Ne vastaavat toisiaan! @@ -476,22 +476,22 @@ Verification Code - + Vahvistuskoodi Please verify the following emoji. You should see the same emoji on both sides. If they differ, please press 'They do not match!' to abort verification! - + Vahvista seuraava emoji. Sinun tulisi nähdä sama emoji molemmilla puolilla. Jos ne eroavat toisistaan, paina "Ne eivät vastaa toisiaan" peruaksesi vahvistuksen! They do not match! - + Ne eivät vastaa toisiaan! They match! - + Ne vastaavat toisiaan! @@ -504,17 +504,17 @@ Encrypted by a verified device - + Vahvistetun laitteen salaama Encrypted by an unverified device, but you have trusted that user so far. - + Vahvistamattoman laitteen salama, mutta olet luottanut tähän asti tuohon käyttäjään. Encrypted by an unverified device - + Vahvistamattoman laitteen salaama @@ -529,7 +529,7 @@ -- Encrypted Event (Key not valid for this index) -- Placeholder, when the message can't be decrypted with this key since it is not valid for this index - + -- Salattu tapahtuma (Avain ei ole kelvollinen tälle indeksille) -- @@ -554,12 +554,12 @@ -- Replay attack! This message index was reused! -- - + -- Toistohyökkäys! Tätä viestin indeksiä on käytetty uudelleen! -- -- Message by unverified device! -- - + -- Viesti vahvistamattomalta laitteelta! -- @@ -567,28 +567,28 @@ Verification failed - + Vahvistus epäonnistui Other client does not support our verification protocol. - + Toinen asiakasohjelma ei tue vahvistusprotokollaamme. Key mismatch detected! - + Tunnistettiin virheellinen avain! Device verification timed out. - + Aikakatkaisu laitteen vahvistuksessa. Other party canceled the verification. - + Toinen osapuoli perui vahvistuksen. @@ -619,7 +619,7 @@ Failed to upload media. Please try again. - + Mediaa ei onnistuttu lataamaan. Yritä uudelleen. @@ -648,7 +648,10 @@ You can also put your homeserver address there, if your server doesn't support .well-known lookup. Example: @user:server.my If Nheko fails to discover your homeserver, it will show you a field to enter the server manually. - + Kirjautumisnimesi. MXID:n pitäisi alkaa @ -merkillä, jota seuraa käyttäjätunnus. Käyttäjätunnuksen jälkeen sinun pitää antaa palvelimen nimi kaksoispisteen (:) jälkeen. +Voit myös laittaa tähän kotipalvelimesi osoitteen, jos palvelimesi ei tunne etsintää. +Esimerkki: @user:server.my +Jos Nheko ei onnistu löytämään kotipalvelintasi, se näyttää sinulle kentän, johon laittaa palvelin käsin. @@ -668,7 +671,7 @@ If Nheko fails to discover your homeserver, it will show you a field to enter th A name for this device, which will be shown to others, when verifying your devices. If none is provided a default is used. - + Tämän laitteen nimi, joka näytetään muille kun laitteitasi vahvistetaan. Oletusta käytetään jos mitään ei anneta. @@ -678,13 +681,14 @@ If Nheko fails to discover your homeserver, it will show you a field to enter th server.my:8787 - + server.my:8787 The address that can be used to contact you homeservers client API. Example: https://server.my:8787 - + Osoite, jota voidaan käyttää ottamaan yhteyttä kotipalvelimesi asiakasrajapintaan. +Esimerkki: https://server.my:8787 @@ -697,7 +701,7 @@ Example: https://server.my:8787 You have entered an invalid Matrix ID e.g @joe:matrix.org - + Väärä Matrix-tunnus. Esim. @joe:matrix.org @@ -727,7 +731,7 @@ Example: https://server.my:8787 SSO LOGIN - + SSO-kirjautuminen @@ -737,7 +741,7 @@ Example: https://server.my:8787 SSO login failed - + SSO-kirjautuminen epäonnistui @@ -759,7 +763,7 @@ Example: https://server.my:8787 removed - + poistettu @@ -774,37 +778,37 @@ Example: https://server.my:8787 removed room name - + poistettu huoneen nimi topic changed to: %1 - + aihe vaihdettiin: %1 removed topic - + poistettu aihe %1 changed the room avatar - + %1 muutti huoneen avataria %1 created and configured room: %2 - + %1 loi ja sääti huoneen: %2 %1 placed a voice call. - + %1 asetti äänipuhelun. %1 placed a video call. - + %1 laittoi videopuhelun. @@ -819,12 +823,12 @@ Example: https://server.my:8787 %1 ended the call. - + %1 päätti puhelun. Negotiating call... - + Neuvotellaan puhelua.… @@ -832,12 +836,12 @@ Example: https://server.my:8787 Hang up - + Punainen luuri Place a call - + Soita puhelu @@ -857,12 +861,12 @@ Example: https://server.my:8787 Send - + Lähetä You don't have permission to send messages in this room - + Sinulla ei ole lupaa lähettää viestejä tässä huoneessa @@ -890,72 +894,72 @@ Example: https://server.my:8787 &Copy - + &Kopioi Copy &link location - + Kopioi &linkki sijainti Re&act - + Re&act Repl&y - + Vast&aa &Edit - + &Muokkaa Read receip&ts - + Lue kuitt&eja &Forward - + &Lähetä eteenpäin &Mark as read - + &Merkitse luetuksi View raw message - + Näytä sisältö raakamuodossa View decrypted raw message - + Näytä salaukseltaan purettu raaka viesti Remo&ve message - + Poist&a viesti &Save as - + &Tallenna nimellä &Open in external program - + &Avaa ulkoisessa sovelluksessa Copy link to eve&nt - + Kopioi linkki tapaht&umaan @@ -963,37 +967,37 @@ Example: https://server.my:8787 Send Verification Request - + Lähetä vahvistuspyyntö Received Verification Request - + Otettiin vastaan vahvistuspyyntö To allow other users to see, which of your devices actually belong to you, you can verify them. This also allows key backup to work automatically. Verify %1 now? - + Voit vahvistaa laitteesi, jotta sallit muiden nähdä, mitkä niistä oikeasti kuuluvat sinulle. Tämä myös mahdollistaa avaimen varmuuskopioinnin toiminnnan automaattisesti. Vahvistetaanko %1 nyt? To ensure that no malicious user can eavesdrop on your encrypted communications you can verify the other party. - + Varmistaaksesi, ettei kukaan pahantahtoinen käyttäjä voi salakuunnella salattuja keskustelujanne, voit vahvistaa toisen osapuolen. %1 has requested to verify their device %2. - + %1 on pyytänyt vahvistamaan hänen laitteeensa %2. %1 using the device %2 has requested to be verified. - + %1 käyttää laitetta, jonka %2 on pyytänyt vahvistamaan. Your device (%1) has requested to be verified. - + Laitteesi (%1) on pyytänyt vahvistetuksi tulemista. @@ -1003,12 +1007,12 @@ Example: https://server.my:8787 Deny - + Kiellä Start verification - + Aloita vahvistus @@ -1029,7 +1033,7 @@ Example: https://server.my:8787 * %1 %2 Format an emote message in a notification, %1 is the sender, %2 the message - + * %1 %2 @@ -1065,7 +1069,7 @@ Example: https://server.my:8787 Place a call to %1? - + Soita henkilölle %1? @@ -1075,17 +1079,17 @@ Example: https://server.my:8787 Voice - + Ääni Video - + Video Screen - + Näyttö @@ -1098,7 +1102,7 @@ Example: https://server.my:8787 unimplemented event: - + toistaseksi toteuttamaton tapahtuma: @@ -1106,17 +1110,17 @@ Example: https://server.my:8787 Create a unique profile, which allows you to log into several accounts at the same time and start multiple instances of nheko. - + Luo uniikki profili, joka mahdollistaa kirjautumisen usealle tilille samanaikaisesti ja useamman nheko-instanssin aloittamisen. profile - + profiili profile name - + profiilin nimi @@ -1140,7 +1144,7 @@ Example: https://server.my:8787 Please choose a secure password. The exact requirements for password strength may depend on your server. - + Valitse turvallinen salasana. Tarkat vaatimukset salasanan vahvuudelle voivat riippua palvelimestasi. @@ -1155,7 +1159,7 @@ Example: https://server.my:8787 A server that allows registration. Since matrix is decentralized, you need to first find a server you can register on or host your own. - + Palvelin, joka sallii rekisteröinnin. Koska matrix on hajautettu, sinun pitää ensin löytää palvelin jolle rekisteröityä tai ylläpitää omaasi. @@ -1165,12 +1169,12 @@ Example: https://server.my:8787 No supported registration flows! - + Ei tuettuja rekisteröintejä! One or more fields have invalid inputs. Please correct those issues and try again. - + Yhdessä tai useammassa kentässä on vääriä syötteitä. Korjaa nuo ongelmat ja yritä uudelleen. @@ -1239,12 +1243,12 @@ Example: https://server.my:8787 New tag - + Uusi tagi Enter the tag you want to use: - + Kirjoita tagi jota haluat käyttää: @@ -1254,27 +1258,27 @@ Example: https://server.my:8787 Tag room as: - + Laita huoneelle tagi: Favourite - + Suosikki Low priority - + Matala tärkeysjärjestys Server notice - + Palvelimen ilmoitus Create new tag... - + Luo uusi tagi... @@ -1289,22 +1293,22 @@ Example: https://server.my:8787 Status Message - + Tilapäivitys Enter your status message: - + Kirjoita tilapäivityksesi: Profile settings - + Profiilin asetukset Set status message - + Aseta tilapäivitys @@ -1324,7 +1328,7 @@ Example: https://server.my:8787 Create a new room - + Luo uusi huone @@ -1342,52 +1346,52 @@ Example: https://server.my:8787 Room Settings - + Huoneen asetukset %1 member(s) - + %1 jäsentä SETTINGS - + ASETUKSET Notifications - + Ilmoitukset Muted - + Mykistetty Mentions only - + Vain maininnat All messages - + Kaikki viestit Anyone and guests - + Kaikki ja vieraat Anyone - + Kuka tahansa Invited users - + Kutsutut käyttäjät @@ -1403,29 +1407,31 @@ Example: https://server.my:8787 Encryption is currently experimental and things might break unexpectedly. <br> Please take note that it can't be disabled afterwards. - + Salaus on tällä hetkellä kokeellinen ja asiat voivat mennä rikki odottamattomasti.<br>Huomaa että sitä ei voi poistaa jälkikäteen. Respond to key requests - + Vastaa avainpyyntöihin Whether or not the client should respond automatically with the session keys upon request. Use with caution, this is a temporary measure to test the E2E implementation until device verification is completed. - + Riippumatta siitä, vastaako asiakas automaattisesti istunnon avaimilla +pyynnön myötä. Käytä varoen, tämä on väliaikainen toimi päästä päähän +-salauksen toteutukseen kunnes laitteen varmistus on saatu toimivaksi. INFO - + TIETOA Internal ID - + Sisäinen ID @@ -1474,33 +1480,33 @@ Example: https://server.my:8787 Share desktop with %1? - + Jaa työpöytä käyttäjän %1 kanssa? Window: - + Ikkuna: Frame rate: - + Ruudunpäivitys: Include your camera picture-in-picture - + Sisällytä kamerasi kuva kuvassa -tilaan Request remote camera - + Pyydä etäkameraa View your callee's camera like a regular video call - + Näytä puhelun vastaanottajan kamera tavallisen videopuhelun tapaan @@ -1510,12 +1516,12 @@ Example: https://server.my:8787 Share - + Jaa Preview - + Esikatsele @@ -1528,22 +1534,22 @@ Example: https://server.my:8787 Failed - + Epäonnnistui Sent - + Lähetetyt Received - + Vastaanotetut Read - + Lue @@ -1551,12 +1557,12 @@ Example: https://server.my:8787 Successful Verification - + Onnistunut varmistus Verification successful! Both sides verified their devices! - + Varmistus onnistui! Molemmat osapuolet vahvistivat laitteensa! @@ -1575,7 +1581,7 @@ Example: https://server.my:8787 Failed to encrypt event, sending aborted! - + Tapahtuman salaus epäonnistui, lähetys keskeytetään! @@ -1609,62 +1615,62 @@ Example: https://server.my:8787 %1 opened the room to the public. - + %1 avasi huoneen julkiseksi. %1 made this room require and invitation to join. - + %1 teki tästä huoneesta liittymiskutsun vaativan. %1 made the room open to guests. - + %1 teki huoneesta avoimen vieraille. %1 has closed the room to guest access. - + %1 on sulkenut huoneen vierailta %1 made the room history world readable. Events may be now read by non-joined people. - + %1 teki huoneen historian luettavaksi kaikille. Tapahtumia voivat nyt lukea myös huoneeseen liittymättömät ihmiset. %1 set the room history visible to members from this point on. - + %1 asetti huoneen historian näkyväksi jäsenille tästä lähtien. %1 set the room history visible to members since they were invited. - + %1 asetti huoneen historian näkyväksi jäsenille kutsumisesta lähtien. %1 set the room history visible to members since they joined the room. - + %1 asetti huoneen historian näkyväksi jäsenille huoneeseen liittymisen jälkeen. %1 has changed the room's permissions. - + %1 on muuttanut huoneen lupia. %1 was invited. - + &1 kutsuttiin. %1 changed their avatar. - + %1 muutti avatariaan. %1 changed some profile info. - + %1 muutti joitain tietoja profiilistaan. @@ -1674,12 +1680,12 @@ Example: https://server.my:8787 %1 rejected their invite. - + %1 hylkäsi kutsunsa. Revoked the invite to %1. - + Peruttiin kutsu käyttäjälle %1. @@ -1689,27 +1695,27 @@ Example: https://server.my:8787 Kicked %1. - + Potkittiin %1. Unbanned %1. - + Poistettiin käyttäjän %1 porttikielto. %1 was banned. - + Käyttäjälle %1 annettiin porttikielto. Reason: %1 - + Syy: %1 %1 redacted their knock. - + %1 perui koputuksensa. @@ -1719,28 +1725,28 @@ Example: https://server.my:8787 %1 has changed their avatar and changed their display name to %2. - + %1 vaihtoi avatariaan ja vaihtoi näyttönimekseen %2. %1 has changed their display name to %2. - + %1 vaihtoi näyttönimekseen %2. Rejected the knock from %1. - + Hylättiin koputus käyttäjältä %1. %1 left after having already left! This is a leave event after the user already left and shouldn't happen apart from state resets - + %1 lähti vaikka lähti jo aiemmin! %1 knocked. - + %1 koputti. @@ -1756,17 +1762,17 @@ Example: https://server.my:8787 No room open - + Ei avointa huonetta %1 member(s) - + %1 jäsentä Back to room list - + Takaisin huonelistaan @@ -1774,7 +1780,7 @@ Example: https://server.my:8787 No encrypted private chat found with this user. Create an encrypted private chat with this user and try again. - + Salattua keskustelua ei löydetty tälle käyttäjälle. Luo salattu yksityiskeskustelu tämän käyttäjän kanssa ja yritä uudestaan. @@ -1782,13 +1788,13 @@ Example: https://server.my:8787 Back to room list - + Takaisin huonelistaan No room selected - + Ei valittua huonetta @@ -1834,38 +1840,38 @@ Example: https://server.my:8787 Global User Profile - + Yleinen käyttäjäprofiili Room User Profile - + Huoneen käyttäjäprofiili Verify - + Vahvista Ban the user - + Anna käyttäjälle porttikielto Start a private chat - + Aloita yksityinen keskustelu Kick the user - + Potki käyttäjä Unverify - + Peru vahvistus @@ -1894,7 +1900,7 @@ Example: https://server.my:8787 Default - + Oletus @@ -1917,112 +1923,117 @@ Example: https://server.my:8787 Circular Avatars - + Pyöreät avatarit profile: %1 - + profiili: %1 Default - + Oletus CALLS - + PUHELUT Cross Signing Keys - + Ristiin allekirjoitetut avaimet REQUEST - + PYYNTÖ DOWNLOAD - + LATAA Keep the application running in the background after closing the client window. - + Anna sovelluksen pyöriä taustalla asiakasohjelman ikkunan sulkemisen jälkeen. Start the application in the background without showing the client window. - + Aloita sovellus taustalla näyttämättä asiakasohjelman ikkunaa. Change the appearance of user avatars in chats. OFF - square, ON - Circle. - + Muuta käyttäjien avatarien ulkonäköä keskusteluissa. +POIS PÄÄLTÄ - neliö, PÄÄLLÄ - pyöreä Show a column containing groups and tags next to the room list. - + Näytä huonelistan vieressä tagit ja ryhmät sisältävä sarake. Decrypt messages in sidebar - + Pura viestien salaus sivupalkissa Decrypt the messages shown in the sidebar. Only affects messages in encrypted chats. - + Pura sivupalkissa näkyvien viestien salaus +Vaikuttaa vain salattujen keskustelujen viesteihin. Privacy Screen - + Yksityisyysnäkymä When the window loses focus, the timeline will be blurred. - + Kun ikkuna ei ole kohdistettuna, tämä aikajana +sumennetaan. Privacy screen timeout (in seconds [0 - 3600]) - + Yksityisyysnäkymän aikakatkaisu (sekunneissa [0-3600]) Set timeout (in seconds) for how long after window loses focus before the screen will be blurred. Set to 0 to blur immediately after focus loss. Max value of 1 hour (3600 seconds) - + Aseta aikakatkaisu (sekunneissa) ikkunan kohdistuksen kadottamiselle +ennen kuin näkymä sumennetaan. +Aseta nollaan, jotta sumennetaan heti kohdistus kadotetaan. Suurin arvo 1 tunti (3600 sekuntia) Show buttons in timeline - + Näytä painikkeet aikajanalla Show buttons to quickly reply, react or access additional options next to each message. - + Näytä painikkeet vastataksesi nopeasti, reagoidaksesi tai päästäksesi lisätoimintoihin joka viestin vieressä. Limit width of timeline - + Rajoita aikajanan leveyttä Set the max width of messages in the timeline (in pixels). This can help readability on wide screen, when Nheko is maximised - + Aseta viestien suurin leveys aikajanalla (pikseleissä). Tämä voi auttaa luettavuutta laajakuvassa, kun Nheko on täyden ruudun tilassa. @@ -2033,19 +2044,22 @@ Set to 0 to blur immediately after focus loss. Max value of 1 hour (3600 seconds Show who is typing in a room. This will also enable or disable sending typing notifications to others. - + Näytä kuka kirjoittaa huoneessa. +Tämä myös sallii tai evää kirjoitusilmoitusten lähettämisen muille. Sort rooms by unreads - + Lajittele huoneet lukemattomien mukaan Display rooms with new messages first. If this is off, the list of rooms will only be sorted by the timestamp of the last message in a room. If this is on, rooms which have active notifications (the small circle with a number in it) will be sorted on top. Rooms, that you have muted, will still be sorted by timestamp, since you don't seem to consider them as important as the other rooms. - + Näytä ensiksi huoneet, joissa on uusia viestejä. +Jos tämä on poissa päältä, lista huoneista lajitellaan vain huoneen viimeisimmän viestin aikaleiman mukaan. +Jos tämä on päällä, huoneet, joissa ilmoitukset ovat päällä (pieni ympyrä, jonka sisässä on numero), lajitellaan päällimmäisiksi. Mykistämäsi huoneet lajitellaan aikaleiman mukaan, koska et nähtävästi pidä niitä yhtä tärkeinä kuin muita huoneita. @@ -2056,18 +2070,20 @@ If this is on, rooms which have active notifications (the small circle with a nu Show if your message was read. Status is displayed next to timestamps. - + Näytä jos viestisi oli luettu. +Tila näytetään aikaleimojen vieressä. Send messages as Markdown - + Lähetä viestit Markdownina Allow using markdown in messages. When disabled, all messages are sent as a plain text. - + Salli Markdownin käyttö viesteissä. +Kun poissa päältä, kaikki viestit lähetetään tavallisena tekstinä. @@ -2077,53 +2093,54 @@ When disabled, all messages are sent as a plain text. Notify about received message when the client is not currently focused. - + Ilmoita vastaanotetusta viestistä kun ohjelma ei ole kohdistettuna. Alert on notification - + Hälytä ilmoituksesta Show an alert when a message is received. This usually causes the application icon in the task bar to animate in some fashion. - + Näytä hälytys kun viesti on vastaanotettu. +Tämä yleensä saa sovelluksen kuvakkeen liikkumaan jollain tapaa tehtäväpalkissa. Highlight message on hover - + Korosta viestiä kun kohdistin on päällä Change the background color of messages when you hover over them. - + Muuta viestien taustaväriä kun kohdistimesi liikkuu niiden yli. Large Emoji in timeline - + Iso emoji aikajanalla Make font size larger if messages with only a few emojis are displayed. - + Suurenna fonttikokoa jos näytetään viestit vain muutamalla emojilla. Share keys with verified users and devices - + Jaa avaimet vahvistettujen käyttäjien ja laitteiden kanssa CACHED - + VÄLIMUISTISSA NOT CACHED - + EI VÄLIMUISTISSA @@ -2133,7 +2150,7 @@ This usually causes the application icon in the task bar to animate in some fash Change the scale factor of the whole user interface. - + Muuta koko käyttöliittymän kokoa. @@ -2158,7 +2175,7 @@ This usually causes the application icon in the task bar to animate in some fash Set the notification sound to play when a call invite arrives - + Aseta ilmoitusääni soimaan kun kutsu puheluun tulee. @@ -2173,22 +2190,22 @@ This usually causes the application icon in the task bar to animate in some fash Camera resolution - + Kameran resoluutio Camera frame rate - + Kameran ruudunpäivitys Allow fallback call assist server - + Salli varajärjestelynä toimiva puhelua avustava palvelin Will use turn.matrix.org as assist when your home server does not offer one. - + Käyttää apuna palvelinta turn.matrix.org silloin kun kotipalvelimesi ei sellaista tarjoa. @@ -2228,67 +2245,67 @@ This usually causes the application icon in the task bar to animate in some fash INTERFACE - + KÄYTTÖLIITTYMÄ Touchscreen mode - + Kosketusnäyttötila Will prevent text selection in the timeline to make touch scrolling easier. - + Estää tekstin valitsemisen aikajanalla, jotta koskettamalla vierittäminen on helpompaa. Emoji Font Family - + Emojien fonttiperhe Automatically replies to key requests from other users, if they are verified. - + Vastaa automaattisesti muiden käyttäjien avainpyyntöihin, jos he ovat vahvistettuja. Master signing key - + Päätason allekirjoittava avain Your most important key. You don't need to have it cached, since not caching it makes it less likely it can be stolen and it is only needed to rotate your other signing keys. - + Kaikkein tärkein avaimesi. Sinun ei tarvitse laittaa sitä välimuistiin, koska silloin sen varastaminen on epätodennäköistä ja sitä vaaditaan vain kierrättämään muita allekirjoittavia avaimiasi. User signing key - + Käyttäjän allekirjoittava avain The key to verify other users. If it is cached, verifying a user will verify all their devices. - + Avain vahvistamaan muita käyttäjiä. Jos se on välimuistissa, käyttäjän varmistaminen varmistaa hänen kaikki laitteensa. Self signing key - + Itsensä allekirjoittava avain The key to verify your own devices. If it is cached, verifying one of your devices will mark it verified for all your other devices and for users, that have verified you. - + Avain vahvistamaan omat avaimesi. Jos se on välimuistisas, yhden laitteesi vahvistaminen laittaa sen vahvistetuksi kaikille muille laitteillesi ja käyttäjille, jotka ovat vahvistaneet sinut. Backup key - + Varmuuskopioavain The key to decrypt online key backups. If it is cached, you can enable online key backup to store encryption keys securely encrypted on the server. - + Avain purkamaan avainten varmuuskopioita verkossa. Jos se laitetaan välimuistiin, voit sallia avainten varmuuskopioinnin verkossa säilöäksesi salausavaimet, jotka ovat turvallisesti salattuja palvelimella. @@ -2348,22 +2365,22 @@ This usually causes the application icon in the task bar to animate in some fash Waiting for other party… - + Odotetaan toista osapuolta… Waiting for other side to accept the verification request. - + Odotetaan toista osapuolta hyväksymään vahvistuspyyntö. Waiting for other side to continue the verification process. - + Odotetaan toista puolta jatkamaan vahvistusta. Waiting for other side to complete the verification process. - + Odotetaan toista puolta saamaan vahvistus valmiiksi. @@ -2381,7 +2398,7 @@ This usually causes the application icon in the task bar to animate in some fash Enjoy your stay! - + Nauti vierailustasi! @@ -2450,7 +2467,7 @@ This usually causes the application icon in the task bar to animate in some fash Open Fallback in Browser - + Avaa varajärjestely selaimessa @@ -2465,7 +2482,7 @@ This usually causes the application icon in the task bar to animate in some fash Open the fallback, follow the steps and confirm after completing them. - + Avaa varajärjestely, seuraa ohjeita ja vahvista kun olet saanut ne valmiiksi. From 0d700d99339ff4c921a7d242427fdd1f3c1d4c0b Mon Sep 17 00:00:00 2001 From: kamathmanu Date: Wed, 28 Jul 2021 22:29:57 -0400 Subject: [PATCH 002/232] Implemented Room Directory model to store and provide QML view with public room data from mtxclient --- CMakeLists.txt | 2 + src/RoomDirectoryModel.cpp | 171 +++++++++++++++++++++++++++ src/RoomDirectoryModel.h | 85 +++++++++++++ src/timeline/TimelineViewManager.cpp | 8 ++ 4 files changed, 266 insertions(+) create mode 100644 src/RoomDirectoryModel.cpp create mode 100644 src/RoomDirectoryModel.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 7b26602c..587059fe 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -361,6 +361,7 @@ set(SRC_FILES src/UserSettingsPage.cpp src/UsersModel.cpp src/RoomsModel.cpp + src/RoomDirectoryModel.cpp src/Utils.cpp src/WebRTCSession.cpp src/WelcomePage.cpp @@ -567,6 +568,7 @@ qt5_wrap_cpp(MOC_HEADERS src/UserSettingsPage.h src/UsersModel.h src/RoomsModel.h + src/RoomDirectoryModel.h src/WebRTCSession.h src/WelcomePage.h ) diff --git a/src/RoomDirectoryModel.cpp b/src/RoomDirectoryModel.cpp new file mode 100644 index 00000000..397871eb --- /dev/null +++ b/src/RoomDirectoryModel.cpp @@ -0,0 +1,171 @@ +// SPDX-FileCopyrightText: 2021 Nheko Contributors +// +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "RoomDirectoryModel.h" +#include "ChatPage.h" + +#include + +RoomDirectoryModel::RoomDirectoryModel(QObject *parent, const std::string &s) + : QAbstractListModel(parent) + , server_(s) + , canFetchMore_(true) +{ + connect(this, &RoomDirectoryModel::fetchedRoomsBatch, this, &RoomDirectoryModel::displayRooms, Qt::QueuedConnection); +} + +QHash +RoomDirectoryModel::roleNames() const +{ + return { + {Roles::Name, "name"}, + {Roles::Id, "roomid"}, + {Roles::AvatarUrl, "avatarUrl"}, + {Roles::Topic, "topic"}, + {Roles::MemberCount, "numMembers"}, + {Roles::Previewable, "canPreview"},}; +} + +void +RoomDirectoryModel::resetDisplayedData() +{ + beginResetModel(); + + prevBatch_ = ""; + nextBatch_ = ""; + canFetchMore_ = true; + + beginRemoveRows(QModelIndex(), 0 , static_cast (publicRoomsData_.size())); + publicRoomsData_.clear(); + endRemoveRows(); + + endResetModel(); +} + +void +RoomDirectoryModel::setMatrixServer(const QString &s) +{ + server_ = s.toStdString(); + + nhlog::ui()->debug("Received matrix server: {}", server_); + + resetDisplayedData(); +} + +void +RoomDirectoryModel::setSearchTerm(const QString &f) +{ + userSearchString_ = f.toStdString(); + + nhlog::ui()->debug("Received user query: {}", userSearchString_); + + resetDisplayedData(); +} + +std::vector +RoomDirectoryModel::getViasForRoom(const std::vector &aliases) +{ + std::vector vias; + + vias.reserve(aliases.size()); + + std::transform(aliases.begin(), aliases.end(), + std::back_inserter(vias), [](const auto &alias) { + const auto roomAliasDelimiter = ":"; + return alias.substr(alias.find(roomAliasDelimiter) + 1); + }); + + return vias; +} + +void +RoomDirectoryModel::joinRoom(const int &index) +{ + if (index >= 0 && static_cast (index) < publicRoomsData_.size()) + { + const auto &chunk = publicRoomsData_[index]; + nhlog::ui()->debug("'Joining room {}", chunk.room_id); + ChatPage::instance()->joinRoomVia(chunk.room_id, getViasForRoom(chunk.aliases)); + } +} + +QVariant +RoomDirectoryModel::data(const QModelIndex &index, int role) const +{ + if (hasIndex(index.row(), index.column(), index.parent())) + { + const auto &room_chunk = publicRoomsData_[index.row()]; + switch (role) + { + case Roles::Name: + return QString::fromStdString(room_chunk.name); + case Roles::Id: + return QString::fromStdString(room_chunk.room_id); + case Roles::AvatarUrl: + return QString::fromStdString(room_chunk.avatar_url); + case Roles::Topic: + return QString::fromStdString(room_chunk.topic); + case Roles::MemberCount: + return QVariant::fromValue(room_chunk.num_joined_members); + case Roles::Previewable: + return QVariant::fromValue(room_chunk.world_readable); + } + } + return {}; +} + +void +RoomDirectoryModel::fetchMore(const QModelIndex &) +{ + nhlog::net()->debug("Fetching more rooms from mtxclient..."); + nhlog::net()->debug("Prev batch: {} | Next batch: {}", prevBatch_, nextBatch_); + + mtx::requests::PublicRooms req; + req.limit = limit_; + req.since = prevBatch_; + req.filter.generic_search_term = userSearchString_; + // req.third_party_instance_id = third_party_instance_id; + auto requested_server = server_; + + http::client()->post_public_rooms(req, [requested_server, this, req] + (const mtx::responses::PublicRooms &res, + mtx::http::RequestErr err) + { + if (err) { + nhlog::net()->error + ("Failed to retrieve rooms from mtxclient - {} - {} - {}", + mtx::errors::to_string(err->matrix_error.errcode), + err->matrix_error.error, + err->parse_error); + } else if ( req.filter.generic_search_term == this->userSearchString_ + && req.since == this->prevBatch_ + && requested_server == this->server_) { + nhlog::net()->debug("signalling chunk to GUI thread"); + emit fetchedRoomsBatch(res.chunk, res.prev_batch, res.next_batch); + } + }, requested_server); +} + +void +RoomDirectoryModel::displayRooms(std::vector fetched_rooms, + const std::string &prev_batch, const std::string &next_batch) +{ + nhlog::net()->debug("Prev batch: {} | Next batch: {}", prevBatch_, nextBatch_); + nhlog::net()->debug("NP batch: {} | NN batch: {}", prev_batch, next_batch); + + if (fetched_rooms.empty()) { + nhlog::net()->error("mtxclient helper thread yielded empty chunk!"); + return; + } + + beginInsertRows(QModelIndex(), static_cast (publicRoomsData_.size()), static_cast (publicRoomsData_.size() + fetched_rooms.size()) - 1); + this->publicRoomsData_.insert(this->publicRoomsData_.end(), fetched_rooms.begin(), fetched_rooms.end()); + endInsertRows(); + + if (next_batch.empty()) { + canFetchMore_ = false; + } + + prevBatch_ = std::exchange(nextBatch_, next_batch); +} \ No newline at end of file diff --git a/src/RoomDirectoryModel.h b/src/RoomDirectoryModel.h new file mode 100644 index 00000000..125813da --- /dev/null +++ b/src/RoomDirectoryModel.h @@ -0,0 +1,85 @@ +// SPDX-FileCopyrightText: 2021 Nheko Contributors +// +// SPDX-License-Identifier: GPL-3.0-or-later + +#pragma once + +#include +#include +#include +#include +#include + +#include "MatrixClient.h" +#include +#include + +#include "Logging.h" + +namespace mtx::http { +using RequestErr = const std::optional &; +} +namespace mtx::responses { +struct PublicRooms; +} + +class RoomDirectoryModel : public QAbstractListModel +{ + Q_OBJECT + +public: + explicit RoomDirectoryModel + (QObject *parent = nullptr, const std::string &s = ""); + + enum Roles { + Name = Qt::UserRole, + Id, + AvatarUrl, + Topic, + MemberCount, + Previewable + }; + QHash roleNames() const override; + + QVariant data(const QModelIndex &index, int role) const override; + + inline int rowCount(const QModelIndex &parent = QModelIndex()) const override + { + (void) parent; + return static_cast (publicRoomsData_.size()); + } + + inline bool canFetchMore(const QModelIndex &) const override + { + nhlog::net()->debug("determining if can fetch more"); + return canFetchMore_; + } + void fetchMore(const QModelIndex &) override; + + Q_INVOKABLE void joinRoom(const int &index = -1); + +signals: + void fetchedRoomsBatch(std::vector rooms, + const std::string &prev_batch, const std::string &next_batch); + void serverChanged(); + void searchTermEntered(); + +public slots: + void displayRooms(std::vector rooms, + const std::string &prev, const std::string &next); + void setMatrixServer(const QString &s = ""); + void setSearchTerm(const QString &f); + +private: + static constexpr size_t limit_ = 50; + + std::string server_; + std::string userSearchString_; + std::string prevBatch_; + std::string nextBatch_; + bool canFetchMore_; + std::vector publicRoomsData_; + + std::vector getViasForRoom(const std::vector &room); + void resetDisplayedData(); +}; \ No newline at end of file diff --git a/src/timeline/TimelineViewManager.cpp b/src/timeline/TimelineViewManager.cpp index a6922be7..2e6c45ca 100644 --- a/src/timeline/TimelineViewManager.cpp +++ b/src/timeline/TimelineViewManager.cpp @@ -26,6 +26,8 @@ #include "MainWindow.h" #include "MatrixClient.h" #include "MxcImageProvider.h" +#include "ReadReceiptsModel.h" +#include "RoomDirectoryModel.h" #include "RoomsModel.h" #include "SingleImagePackModel.h" #include "UserSettingsPage.h" @@ -39,6 +41,7 @@ Q_DECLARE_METATYPE(mtx::events::collections::TimelineEvents) Q_DECLARE_METATYPE(std::vector) +Q_DECLARE_METATYPE(std::vector) namespace msgs = mtx::events::msg; @@ -150,6 +153,8 @@ TimelineViewManager::TimelineViewManager(CallManager *callManager, ChatPage *par qRegisterMetaType(); qRegisterMetaType(); + qRegisterMetaType>(); + qmlRegisterUncreatableMetaObject(qml_mtx_events::staticMetaObject, "im.nheko", 1, @@ -273,6 +278,9 @@ TimelineViewManager::TimelineViewManager(CallManager *callManager, ChatPage *par "EmojiCategory", "Error: Only enums"); + qmlRegisterType( + "im.nheko.RoomDirectoryModel", 1, 0, "RoomDirectoryModel"); + #ifdef USE_QUICK_VIEW view = new QQuickView(parent); container = QWidget::createWindowContainer(view, parent); From 93f8c24fc56013bc696323eac71a09119889541f Mon Sep 17 00:00:00 2001 From: kamathmanu Date: Wed, 28 Jul 2021 22:31:31 -0400 Subject: [PATCH 003/232] Room Directory UI for exploring and joining public rooms. V1: simplistic server + network facilities --- resources/qml/RoomDirectory.qml | 155 ++++++++++++++++++++++++++++++++ resources/qml/RoomList.qml | 12 +++ resources/res.qrc | 3 +- 3 files changed, 169 insertions(+), 1 deletion(-) create mode 100644 resources/qml/RoomDirectory.qml diff --git a/resources/qml/RoomDirectory.qml b/resources/qml/RoomDirectory.qml new file mode 100644 index 00000000..e6fc2b84 --- /dev/null +++ b/resources/qml/RoomDirectory.qml @@ -0,0 +1,155 @@ +// SPDX-FileCopyrightText: 2021 Nheko Contributors +// +// SPDX-License-Identifier: GPL-3.0-or-later + +import QtQuick 2.9 +import QtQuick.Controls 2.3 +import QtQuick.Layouts 1.3 +import im.nheko 1.0 +import im.nheko.RoomDirectoryModel 1.0 + +ApplicationWindow { + id: roomDirectoryWindow + visible: true + + x: MainWindow.x + (MainWindow.width / 2) - (width / 2) + y: MainWindow.y + (MainWindow.height / 2) - (height / 2) + minimumWidth: 650 + minimumHeight: 420 + palette: Nheko.colors + color: Nheko.colors.window + modality: Qt.WindowModal + flags: Qt.Dialog + title: qsTr("Explore Public Rooms") + + header: RowLayout { + id: searchBarLayout + spacing: Nheko.paddingMedium + width: parent.width + + implicitHeight: roomTextInput.height + + MatrixTextField { + id: roomSearch + + Layout.fillWidth: true + + font.pixelSize: fontMetrics.font.pixelSize + padding: Nheko.paddingSmall + color: Nheko.colors.text + placeholderText: qsTr("Search for public rooms") + onTextChanged: searchTimer.restart() + } + + Timer { + id: searchTimer + + interval: 350 + onTriggered: roomDirView.model.setSearchTerm(roomSearch.text) + } + } + + ListView { + id: roomDirView + anchors.fill: parent + height: parent.height - searchBarLayout.height + model: RoomDirectoryModel { + id: roomDir + } + delegate: Rectangle { + id: roomDirDelegate + + property color background: Nheko.colors.window + property color importantText: Nheko.colors.text + property color unimportantText: Nheko.colors.buttonText + property int avatarSize: Math.ceil(fontMetrics.lineSpacing * 2.5) + + color: background + + height: avatarSize + 2.5 * Nheko.paddingMedium + width: ListView.view.width + + RowLayout { + + spacing: Nheko.paddingMedium + anchors.fill: parent + anchors.margins: Nheko.paddingMedium + implicitHeight: textContent.height + + Avatar { + id: roomAvatar + + Layout.alignment: Qt.AlignVCenter + width: avatarSize + height: avatarSize + url: model.avatarUrl.replace("mxc://", "image://MxcImage/") + displayName: model.name + } + + ColumnLayout { + id: textContent + + Layout.alignment: Qt.AlignLeft + Layout.fillWidth: true + width: parent.width - avatar.width + Layout.preferredWidth: parent.width - avatar.width + Layout.preferredHeight: roomNameRow.height + roomDescriptionRow.height + spacing: Nheko.paddingSmall + + RowLayout { + id: roomNameRow + Layout.fillWidth: true + spacing: 0 + + ElidedLabel { + Layout.alignment: Qt.AlignBottom + color: roomDirDelegate.importantText + elideWidth: textContent.width * 0.5 - Nheko.paddingMedium + font.pixelSize: fontMetrics.font.pixelSize * 1.1 + fullText: model.name + } + } + + RowLayout { + id: roomDescriptionRow + Layout.fillWidth: true + Layout.preferredWidth: parent.width + spacing: Nheko.paddingSmall + Layout.alignment: Qt.AlignLeft + Layout.preferredHeight: Math.max(roomTopic.height, roomCount.height, joinRoomButton.height) + + Label { + id: roomTopic + color: roomDirDelegate.unimportantText + font.weight: Font.Thin + font.pixelSize: fontMetrics.font.pixelSize + elide: Text.ElideRight + maximumLineCount: 2 + Layout.fillWidth: true + text: model.topic + verticalAlignment: Text.AlignVCenter + wrapMode: Text.WordWrap + } + + Label { + id: roomCount + color: roomDirDelegate.unimportantText + Layout.fillWidth: false + font.weight: Font.Thin + font.pixelSize: fontMetrics.font.pixelSize + text: model.numMembers.toString() + } + + Button { + id: joinRoomButton + Layout.fillWidth: false + text: "Join" + Layout.margins: Nheko.paddingSmall + onClicked: roomDir.joinRoom(model.index) + } + } + } + } + } + } +} diff --git a/resources/qml/RoomList.qml b/resources/qml/RoomList.qml index a2e50fab..31c9d3cf 100644 --- a/resources/qml/RoomList.qml +++ b/resources/qml/RoomList.qml @@ -16,6 +16,13 @@ Page { property int avatarSize: Math.ceil(fontMetrics.lineSpacing * 2.3) property bool collapsed: false +Component { + id: roomDirectoryComponent + + RoomDirectory { + } + } + ListView { id: roomlist @@ -549,6 +556,11 @@ Page { ToolTip.visible: hovered ToolTip.text: qsTr("Room directory") Layout.margins: Nheko.paddingMedium + onClicked: { + console.debug("Roomdir clicked"); + var win = roomDirectoryComponent.createObject(timelineRoot); + win.show(); + } } ImageButton { diff --git a/resources/res.qrc b/resources/res.qrc index 5d37c397..407a0a98 100644 --- a/resources/res.qrc +++ b/resources/res.qrc @@ -143,7 +143,8 @@ qml/emoji/EmojiPicker.qml qml/emoji/StickerPicker.qml qml/UserProfile.qml - qml/delegates/MessageDelegate.qml + qml/RoomDirectory.qml + qml/delegates/MessageDelegate.qml qml/delegates/TextMessage.qml qml/delegates/NoticeMessage.qml qml/delegates/ImageMessage.qml From 04d0d413e321f72f19ab443a2a3894254b7fb0aa Mon Sep 17 00:00:00 2001 From: kamathmanu Date: Wed, 28 Jul 2021 22:33:23 -0400 Subject: [PATCH 004/232] Linted code --- src/RoomDirectoryModel.cpp | 208 ++++++++++++++------------- src/RoomDirectoryModel.h | 94 ++++++------ src/timeline/TimelineViewManager.cpp | 4 +- 3 files changed, 157 insertions(+), 149 deletions(-) diff --git a/src/RoomDirectoryModel.cpp b/src/RoomDirectoryModel.cpp index 397871eb..2a06e4d4 100644 --- a/src/RoomDirectoryModel.cpp +++ b/src/RoomDirectoryModel.cpp @@ -8,164 +8,170 @@ #include RoomDirectoryModel::RoomDirectoryModel(QObject *parent, const std::string &s) - : QAbstractListModel(parent) - , server_(s) - , canFetchMore_(true) + : QAbstractListModel(parent) + , server_(s) + , canFetchMore_(true) { - connect(this, &RoomDirectoryModel::fetchedRoomsBatch, this, &RoomDirectoryModel::displayRooms, Qt::QueuedConnection); + connect(this, + &RoomDirectoryModel::fetchedRoomsBatch, + this, + &RoomDirectoryModel::displayRooms, + Qt::QueuedConnection); } -QHash +QHash RoomDirectoryModel::roleNames() const { - return { - {Roles::Name, "name"}, - {Roles::Id, "roomid"}, - {Roles::AvatarUrl, "avatarUrl"}, - {Roles::Topic, "topic"}, - {Roles::MemberCount, "numMembers"}, - {Roles::Previewable, "canPreview"},}; + return { + {Roles::Name, "name"}, + {Roles::Id, "roomid"}, + {Roles::AvatarUrl, "avatarUrl"}, + {Roles::Topic, "topic"}, + {Roles::MemberCount, "numMembers"}, + {Roles::Previewable, "canPreview"}, + }; } void RoomDirectoryModel::resetDisplayedData() { - beginResetModel(); + beginResetModel(); - prevBatch_ = ""; - nextBatch_ = ""; - canFetchMore_ = true; + prevBatch_ = ""; + nextBatch_ = ""; + canFetchMore_ = true; - beginRemoveRows(QModelIndex(), 0 , static_cast (publicRoomsData_.size())); - publicRoomsData_.clear(); - endRemoveRows(); + beginRemoveRows(QModelIndex(), 0, static_cast(publicRoomsData_.size())); + publicRoomsData_.clear(); + endRemoveRows(); - endResetModel(); + endResetModel(); } void RoomDirectoryModel::setMatrixServer(const QString &s) { - server_ = s.toStdString(); + server_ = s.toStdString(); - nhlog::ui()->debug("Received matrix server: {}", server_); + nhlog::ui()->debug("Received matrix server: {}", server_); - resetDisplayedData(); + resetDisplayedData(); } void RoomDirectoryModel::setSearchTerm(const QString &f) { - userSearchString_ = f.toStdString(); + userSearchString_ = f.toStdString(); - nhlog::ui()->debug("Received user query: {}", userSearchString_); + nhlog::ui()->debug("Received user query: {}", userSearchString_); - resetDisplayedData(); + resetDisplayedData(); } std::vector RoomDirectoryModel::getViasForRoom(const std::vector &aliases) { - std::vector vias; - - vias.reserve(aliases.size()); + std::vector vias; - std::transform(aliases.begin(), aliases.end(), - std::back_inserter(vias), [](const auto &alias) { - const auto roomAliasDelimiter = ":"; - return alias.substr(alias.find(roomAliasDelimiter) + 1); - }); + vias.reserve(aliases.size()); - return vias; + std::transform( + aliases.begin(), aliases.end(), std::back_inserter(vias), [](const auto &alias) { + const auto roomAliasDelimiter = ":"; + return alias.substr(alias.find(roomAliasDelimiter) + 1); + }); + + return vias; } void RoomDirectoryModel::joinRoom(const int &index) { - if (index >= 0 && static_cast (index) < publicRoomsData_.size()) - { - const auto &chunk = publicRoomsData_[index]; - nhlog::ui()->debug("'Joining room {}", chunk.room_id); - ChatPage::instance()->joinRoomVia(chunk.room_id, getViasForRoom(chunk.aliases)); - } + if (index >= 0 && static_cast(index) < publicRoomsData_.size()) { + const auto &chunk = publicRoomsData_[index]; + nhlog::ui()->debug("'Joining room {}", chunk.room_id); + ChatPage::instance()->joinRoomVia(chunk.room_id, getViasForRoom(chunk.aliases)); + } } QVariant RoomDirectoryModel::data(const QModelIndex &index, int role) const { - if (hasIndex(index.row(), index.column(), index.parent())) - { - const auto &room_chunk = publicRoomsData_[index.row()]; - switch (role) - { - case Roles::Name: - return QString::fromStdString(room_chunk.name); - case Roles::Id: - return QString::fromStdString(room_chunk.room_id); - case Roles::AvatarUrl: - return QString::fromStdString(room_chunk.avatar_url); - case Roles::Topic: - return QString::fromStdString(room_chunk.topic); - case Roles::MemberCount: - return QVariant::fromValue(room_chunk.num_joined_members); - case Roles::Previewable: - return QVariant::fromValue(room_chunk.world_readable); + if (hasIndex(index.row(), index.column(), index.parent())) { + const auto &room_chunk = publicRoomsData_[index.row()]; + switch (role) { + case Roles::Name: + return QString::fromStdString(room_chunk.name); + case Roles::Id: + return QString::fromStdString(room_chunk.room_id); + case Roles::AvatarUrl: + return QString::fromStdString(room_chunk.avatar_url); + case Roles::Topic: + return QString::fromStdString(room_chunk.topic); + case Roles::MemberCount: + return QVariant::fromValue(room_chunk.num_joined_members); + case Roles::Previewable: + return QVariant::fromValue(room_chunk.world_readable); + } } - } - return {}; + return {}; } void -RoomDirectoryModel::fetchMore(const QModelIndex &) +RoomDirectoryModel::fetchMore(const QModelIndex &) { - nhlog::net()->debug("Fetching more rooms from mtxclient..."); - nhlog::net()->debug("Prev batch: {} | Next batch: {}", prevBatch_, nextBatch_); + nhlog::net()->debug("Fetching more rooms from mtxclient..."); + nhlog::net()->debug("Prev batch: {} | Next batch: {}", prevBatch_, nextBatch_); - mtx::requests::PublicRooms req; - req.limit = limit_; - req.since = prevBatch_; - req.filter.generic_search_term = userSearchString_; - // req.third_party_instance_id = third_party_instance_id; - auto requested_server = server_; + mtx::requests::PublicRooms req; + req.limit = limit_; + req.since = prevBatch_; + req.filter.generic_search_term = userSearchString_; + // req.third_party_instance_id = third_party_instance_id; + auto requested_server = server_; - http::client()->post_public_rooms(req, [requested_server, this, req] - (const mtx::responses::PublicRooms &res, - mtx::http::RequestErr err) - { - if (err) { - nhlog::net()->error - ("Failed to retrieve rooms from mtxclient - {} - {} - {}", - mtx::errors::to_string(err->matrix_error.errcode), - err->matrix_error.error, - err->parse_error); - } else if ( req.filter.generic_search_term == this->userSearchString_ - && req.since == this->prevBatch_ - && requested_server == this->server_) { - nhlog::net()->debug("signalling chunk to GUI thread"); - emit fetchedRoomsBatch(res.chunk, res.prev_batch, res.next_batch); - } - }, requested_server); + http::client()->post_public_rooms( + req, + [requested_server, this, req](const mtx::responses::PublicRooms &res, + mtx::http::RequestErr err) { + if (err) { + nhlog::net()->error( + "Failed to retrieve rooms from mtxclient - {} - {} - {}", + mtx::errors::to_string(err->matrix_error.errcode), + err->matrix_error.error, + err->parse_error); + } else if (req.filter.generic_search_term == this->userSearchString_ && + req.since == this->prevBatch_ && requested_server == this->server_) { + nhlog::net()->debug("signalling chunk to GUI thread"); + emit fetchedRoomsBatch(res.chunk, res.prev_batch, res.next_batch); + } + }, + requested_server); } void RoomDirectoryModel::displayRooms(std::vector fetched_rooms, - const std::string &prev_batch, const std::string &next_batch) + const std::string &prev_batch, + const std::string &next_batch) { - nhlog::net()->debug("Prev batch: {} | Next batch: {}", prevBatch_, nextBatch_); - nhlog::net()->debug("NP batch: {} | NN batch: {}", prev_batch, next_batch); + nhlog::net()->debug("Prev batch: {} | Next batch: {}", prevBatch_, nextBatch_); + nhlog::net()->debug("NP batch: {} | NN batch: {}", prev_batch, next_batch); - if (fetched_rooms.empty()) { - nhlog::net()->error("mtxclient helper thread yielded empty chunk!"); - return; - } + if (fetched_rooms.empty()) { + nhlog::net()->error("mtxclient helper thread yielded empty chunk!"); + return; + } - beginInsertRows(QModelIndex(), static_cast (publicRoomsData_.size()), static_cast (publicRoomsData_.size() + fetched_rooms.size()) - 1); - this->publicRoomsData_.insert(this->publicRoomsData_.end(), fetched_rooms.begin(), fetched_rooms.end()); - endInsertRows(); + beginInsertRows(QModelIndex(), + static_cast(publicRoomsData_.size()), + static_cast(publicRoomsData_.size() + fetched_rooms.size()) - 1); + this->publicRoomsData_.insert( + this->publicRoomsData_.end(), fetched_rooms.begin(), fetched_rooms.end()); + endInsertRows(); - if (next_batch.empty()) { - canFetchMore_ = false; - } - - prevBatch_ = std::exchange(nextBatch_, next_batch); + if (next_batch.empty()) { + canFetchMore_ = false; + } + + prevBatch_ = std::exchange(nextBatch_, next_batch); } \ No newline at end of file diff --git a/src/RoomDirectoryModel.h b/src/RoomDirectoryModel.h index 125813da..ff571d93 100644 --- a/src/RoomDirectoryModel.h +++ b/src/RoomDirectoryModel.h @@ -7,12 +7,12 @@ #include #include #include -#include #include +#include #include "MatrixClient.h" -#include #include +#include #include "Logging.h" @@ -25,61 +25,63 @@ struct PublicRooms; class RoomDirectoryModel : public QAbstractListModel { - Q_OBJECT + Q_OBJECT -public: - explicit RoomDirectoryModel - (QObject *parent = nullptr, const std::string &s = ""); +public: + explicit RoomDirectoryModel(QObject *parent = nullptr, const std::string &s = ""); - enum Roles { - Name = Qt::UserRole, - Id, - AvatarUrl, - Topic, - MemberCount, - Previewable - }; - QHash roleNames() const override; + enum Roles + { + Name = Qt::UserRole, + Id, + AvatarUrl, + Topic, + MemberCount, + Previewable + }; + QHash roleNames() const override; - QVariant data(const QModelIndex &index, int role) const override; + QVariant data(const QModelIndex &index, int role) const override; - inline int rowCount(const QModelIndex &parent = QModelIndex()) const override - { - (void) parent; - return static_cast (publicRoomsData_.size()); - } + inline int rowCount(const QModelIndex &parent = QModelIndex()) const override + { + (void)parent; + return static_cast(publicRoomsData_.size()); + } - inline bool canFetchMore(const QModelIndex &) const override - { - nhlog::net()->debug("determining if can fetch more"); - return canFetchMore_; - } - void fetchMore(const QModelIndex &) override; + inline bool canFetchMore(const QModelIndex &) const override + { + nhlog::net()->debug("determining if can fetch more"); + return canFetchMore_; + } + void fetchMore(const QModelIndex &) override; - Q_INVOKABLE void joinRoom(const int &index = -1); + Q_INVOKABLE void joinRoom(const int &index = -1); signals: - void fetchedRoomsBatch(std::vector rooms, - const std::string &prev_batch, const std::string &next_batch); - void serverChanged(); - void searchTermEntered(); + void fetchedRoomsBatch(std::vector rooms, + const std::string &prev_batch, + const std::string &next_batch); + void serverChanged(); + void searchTermEntered(); public slots: - void displayRooms(std::vector rooms, - const std::string &prev, const std::string &next); - void setMatrixServer(const QString &s = ""); - void setSearchTerm(const QString &f); + void displayRooms(std::vector rooms, + const std::string &prev, + const std::string &next); + void setMatrixServer(const QString &s = ""); + void setSearchTerm(const QString &f); private: - static constexpr size_t limit_ = 50; - - std::string server_; - std::string userSearchString_; - std::string prevBatch_; - std::string nextBatch_; - bool canFetchMore_; - std::vector publicRoomsData_; + static constexpr size_t limit_ = 50; - std::vector getViasForRoom(const std::vector &room); - void resetDisplayedData(); + std::string server_; + std::string userSearchString_; + std::string prevBatch_; + std::string nextBatch_; + bool canFetchMore_; + std::vector publicRoomsData_; + + std::vector getViasForRoom(const std::vector &room); + void resetDisplayedData(); }; \ No newline at end of file diff --git a/src/timeline/TimelineViewManager.cpp b/src/timeline/TimelineViewManager.cpp index 2e6c45ca..fd5528d8 100644 --- a/src/timeline/TimelineViewManager.cpp +++ b/src/timeline/TimelineViewManager.cpp @@ -153,7 +153,7 @@ TimelineViewManager::TimelineViewManager(CallManager *callManager, ChatPage *par qRegisterMetaType(); qRegisterMetaType(); - qRegisterMetaType>(); + qRegisterMetaType>(); qmlRegisterUncreatableMetaObject(qml_mtx_events::staticMetaObject, "im.nheko", @@ -278,7 +278,7 @@ TimelineViewManager::TimelineViewManager(CallManager *callManager, ChatPage *par "EmojiCategory", "Error: Only enums"); - qmlRegisterType( + qmlRegisterType( "im.nheko.RoomDirectoryModel", 1, 0, "RoomDirectoryModel"); #ifdef USE_QUICK_VIEW From f5ee1e84b5e813bb7a98e4c92cf6648da2eb6fac Mon Sep 17 00:00:00 2001 From: kamathmanu Date: Sun, 1 Aug 2021 19:14:54 -0400 Subject: [PATCH 005/232] Padding for search bar --- resources/qml/RoomDirectory.qml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/resources/qml/RoomDirectory.qml b/resources/qml/RoomDirectory.qml index e6fc2b84..7298a7cd 100644 --- a/resources/qml/RoomDirectory.qml +++ b/resources/qml/RoomDirectory.qml @@ -27,7 +27,7 @@ ApplicationWindow { spacing: Nheko.paddingMedium width: parent.width - implicitHeight: roomTextInput.height + implicitHeight: roomSearch.height MatrixTextField { id: roomSearch @@ -35,7 +35,7 @@ ApplicationWindow { Layout.fillWidth: true font.pixelSize: fontMetrics.font.pixelSize - padding: Nheko.paddingSmall + padding: Math.ceil(1.5 * Nheko.paddingSmall) color: Nheko.colors.text placeholderText: qsTr("Search for public rooms") onTextChanged: searchTimer.restart() From d3d7844106a422bd54f899f210e54dcb43a26a4c Mon Sep 17 00:00:00 2001 From: kamathmanu Date: Tue, 3 Aug 2021 18:25:11 -0400 Subject: [PATCH 006/232] Made only unjoined rooms joinable --- src/RoomDirectoryModel.cpp | 13 ++++++++++++- src/RoomDirectoryModel.h | 3 ++- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/src/RoomDirectoryModel.cpp b/src/RoomDirectoryModel.cpp index 2a06e4d4..b206bf9e 100644 --- a/src/RoomDirectoryModel.cpp +++ b/src/RoomDirectoryModel.cpp @@ -4,6 +4,7 @@ #include "RoomDirectoryModel.h" #include "ChatPage.h" +#include "Cache.h" #include @@ -68,6 +69,16 @@ RoomDirectoryModel::setSearchTerm(const QString &f) resetDisplayedData(); } + +bool +RoomDirectoryModel::canJoinRoom(const QByteArray &room) +{ + const auto &cache = cache::roomInfo(); + const QString room_id (room); + const bool validRoom = !room_id.isNull() && !room_id.isEmpty(); + return validRoom && !cache.contains(room_id); +} + std::vector RoomDirectoryModel::getViasForRoom(const std::vector &aliases) { @@ -174,4 +185,4 @@ RoomDirectoryModel::displayRooms(std::vector f } prevBatch_ = std::exchange(nextBatch_, next_batch); -} \ No newline at end of file +} diff --git a/src/RoomDirectoryModel.h b/src/RoomDirectoryModel.h index ff571d93..7099ff00 100644 --- a/src/RoomDirectoryModel.h +++ b/src/RoomDirectoryModel.h @@ -56,6 +56,7 @@ public: } void fetchMore(const QModelIndex &) override; + Q_INVOKABLE bool canJoinRoom(const QByteArray &room); Q_INVOKABLE void joinRoom(const int &index = -1); signals: @@ -84,4 +85,4 @@ private: std::vector getViasForRoom(const std::vector &room); void resetDisplayedData(); -}; \ No newline at end of file +}; From 98b733ad26e9cb6a82e8592ac0d40959a0c1a1f5 Mon Sep 17 00:00:00 2001 From: kamathmanu Date: Tue, 3 Aug 2021 18:26:11 -0400 Subject: [PATCH 007/232] Fixed anchoring/positioning of delegate items and join room display --- resources/qml/RoomDirectory.qml | 35 +++++++++++++++++++++++---------- 1 file changed, 25 insertions(+), 10 deletions(-) diff --git a/resources/qml/RoomDirectory.qml b/resources/qml/RoomDirectory.qml index 7298a7cd..94bfceb2 100644 --- a/resources/qml/RoomDirectory.qml +++ b/resources/qml/RoomDirectory.qml @@ -62,7 +62,7 @@ ApplicationWindow { property color background: Nheko.colors.window property color importantText: Nheko.colors.text property color unimportantText: Nheko.colors.buttonText - property int avatarSize: Math.ceil(fontMetrics.lineSpacing * 2.5) + property int avatarSize: Math.ceil(fontMetrics.lineSpacing * 4) color: background @@ -115,13 +115,14 @@ ApplicationWindow { Layout.fillWidth: true Layout.preferredWidth: parent.width spacing: Nheko.paddingSmall - Layout.alignment: Qt.AlignLeft - Layout.preferredHeight: Math.max(roomTopic.height, roomCount.height, joinRoomButton.height) + Layout.alignment: Qt.AlignVCenter | Qt.AlignLeft + Layout.preferredHeight: Math.ceil(fontMetrics.lineSpacing * 4) Label { id: roomTopic color: roomDirDelegate.unimportantText font.weight: Font.Thin + Layout.alignment: Qt.AlignVCenter | Qt.AlignLeft font.pixelSize: fontMetrics.font.pixelSize elide: Text.ElideRight maximumLineCount: 2 @@ -130,23 +131,37 @@ ApplicationWindow { verticalAlignment: Text.AlignVCenter wrapMode: Text.WordWrap } + Item { + id: numMembersRectangle + Layout.fillWidth: false + Layout.margins: Nheko.paddingSmall + width: roomCount.width Label { id: roomCount color: roomDirDelegate.unimportantText + anchors.centerIn: parent Layout.fillWidth: false font.weight: Font.Thin font.pixelSize: fontMetrics.font.pixelSize text: model.numMembers.toString() } + } - Button { - id: joinRoomButton - Layout.fillWidth: false - text: "Join" - Layout.margins: Nheko.paddingSmall - onClicked: roomDir.joinRoom(model.index) - } + Item { + id: buttonRectangle + Layout.fillWidth: false + Layout.margins: Nheko.paddingSmall + width: joinRoomButton.width + Button { + id: joinRoomButton + visible: roomDir.canJoinRoom(model.roomid) + anchors.centerIn: parent + width: Math.ceil(0.1 * roomDirectoryWindow.width) + text: "Join" + onClicked: roomDir.joinRoom(model.index) + } + } } } } From 4ec0c8c9bb384dcaeb9c18bb359cbd4e1e95e9c1 Mon Sep 17 00:00:00 2001 From: kamathmanu Date: Wed, 4 Aug 2021 14:12:10 -0400 Subject: [PATCH 008/232] make lint --- src/RoomDirectoryModel.cpp | 11 +++++------ src/RoomDirectoryModel.h | 2 +- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/src/RoomDirectoryModel.cpp b/src/RoomDirectoryModel.cpp index b206bf9e..6cb7a4fb 100644 --- a/src/RoomDirectoryModel.cpp +++ b/src/RoomDirectoryModel.cpp @@ -3,8 +3,8 @@ // SPDX-License-Identifier: GPL-3.0-or-later #include "RoomDirectoryModel.h" -#include "ChatPage.h" #include "Cache.h" +#include "ChatPage.h" #include @@ -69,14 +69,13 @@ RoomDirectoryModel::setSearchTerm(const QString &f) resetDisplayedData(); } - bool RoomDirectoryModel::canJoinRoom(const QByteArray &room) { - const auto &cache = cache::roomInfo(); - const QString room_id (room); - const bool validRoom = !room_id.isNull() && !room_id.isEmpty(); - return validRoom && !cache.contains(room_id); + const auto &cache = cache::roomInfo(); + const QString room_id(room); + const bool validRoom = !room_id.isNull() && !room_id.isEmpty(); + return validRoom && !cache.contains(room_id); } std::vector diff --git a/src/RoomDirectoryModel.h b/src/RoomDirectoryModel.h index 7099ff00..c7089a1e 100644 --- a/src/RoomDirectoryModel.h +++ b/src/RoomDirectoryModel.h @@ -56,7 +56,7 @@ public: } void fetchMore(const QModelIndex &) override; - Q_INVOKABLE bool canJoinRoom(const QByteArray &room); + Q_INVOKABLE bool canJoinRoom(const QByteArray &room); Q_INVOKABLE void joinRoom(const int &index = -1); signals: From 34ffe054cf767a15f03d30b4839f08144648080f Mon Sep 17 00:00:00 2001 From: kamathmanu Date: Sat, 7 Aug 2021 15:08:38 -0400 Subject: [PATCH 009/232] Improve window closing --- resources/qml/RoomDirectory.qml | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/resources/qml/RoomDirectory.qml b/resources/qml/RoomDirectory.qml index 94bfceb2..f31df64d 100644 --- a/resources/qml/RoomDirectory.qml +++ b/resources/qml/RoomDirectory.qml @@ -19,9 +19,14 @@ ApplicationWindow { palette: Nheko.colors color: Nheko.colors.window modality: Qt.WindowModal - flags: Qt.Dialog + flags: Qt.Dialog | Qt.WindowCloseButtonHint title: qsTr("Explore Public Rooms") + Shortcut { + sequence: StandardKey.Cancel + onActivated: roomDirectoryWindow.close() + } + header: RowLayout { id: searchBarLayout spacing: Nheko.paddingMedium From 14f8f4d61b45f0f2fd304a87f291a83781142615 Mon Sep 17 00:00:00 2001 From: kamathmanu Date: Sat, 7 Aug 2021 17:13:18 -0400 Subject: [PATCH 010/232] Fix Duplicate fetched chunk --- src/RoomDirectoryModel.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/RoomDirectoryModel.cpp b/src/RoomDirectoryModel.cpp index 6cb7a4fb..06bd9d8a 100644 --- a/src/RoomDirectoryModel.cpp +++ b/src/RoomDirectoryModel.cpp @@ -131,7 +131,6 @@ void RoomDirectoryModel::fetchMore(const QModelIndex &) { nhlog::net()->debug("Fetching more rooms from mtxclient..."); - nhlog::net()->debug("Prev batch: {} | Next batch: {}", prevBatch_, nextBatch_); mtx::requests::PublicRooms req; req.limit = limit_; @@ -183,5 +182,5 @@ RoomDirectoryModel::displayRooms(std::vector f canFetchMore_ = false; } - prevBatch_ = std::exchange(nextBatch_, next_batch); + prevBatch_ = next_batch; } From 6a75e5270cd12aebc6130f0bc3ee1d9999620601 Mon Sep 17 00:00:00 2001 From: kamathmanu Date: Mon, 9 Aug 2021 09:18:08 -0400 Subject: [PATCH 011/232] Fix nits from code review --- CMakeLists.txt | 4 ++-- resources/qml/RoomDirectory.qml | 8 +++----- resources/qml/RoomList.qml | 1 - resources/res.qrc | 5 ----- src/RoomDirectoryModel.cpp | 5 +---- src/RoomDirectoryModel.h | 1 - src/timeline/TimelineViewManager.cpp | 2 +- 7 files changed, 7 insertions(+), 19 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index bcf31b41..cafe7948 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -359,8 +359,8 @@ set(SRC_FILES src/TrayIcon.cpp src/UserSettingsPage.cpp src/UsersModel.cpp - src/RoomsModel.cpp src/RoomDirectoryModel.cpp + src/RoomsModel.cpp src/Utils.cpp src/WebRTCSession.cpp src/WelcomePage.cpp @@ -565,8 +565,8 @@ qt5_wrap_cpp(MOC_HEADERS src/TrayIcon.h src/UserSettingsPage.h src/UsersModel.h - src/RoomsModel.h src/RoomDirectoryModel.h + src/RoomsModel.h src/WebRTCSession.h src/WelcomePage.h src/ReadReceiptsModel.h diff --git a/resources/qml/RoomDirectory.qml b/resources/qml/RoomDirectory.qml index f31df64d..b01c1e00 100644 --- a/resources/qml/RoomDirectory.qml +++ b/resources/qml/RoomDirectory.qml @@ -6,7 +6,6 @@ import QtQuick 2.9 import QtQuick.Controls 2.3 import QtQuick.Layouts 1.3 import im.nheko 1.0 -import im.nheko.RoomDirectoryModel 1.0 ApplicationWindow { id: roomDirectoryWindow @@ -40,7 +39,7 @@ ApplicationWindow { Layout.fillWidth: true font.pixelSize: fontMetrics.font.pixelSize - padding: Math.ceil(1.5 * Nheko.paddingSmall) + padding: Nheko.paddingMedium color: Nheko.colors.text placeholderText: qsTr("Search for public rooms") onTextChanged: searchTimer.restart() @@ -57,7 +56,6 @@ ApplicationWindow { ListView { id: roomDirView anchors.fill: parent - height: parent.height - searchBarLayout.height model: RoomDirectoryModel { id: roomDir } @@ -67,7 +65,7 @@ ApplicationWindow { property color background: Nheko.colors.window property color importantText: Nheko.colors.text property color unimportantText: Nheko.colors.buttonText - property int avatarSize: Math.ceil(fontMetrics.lineSpacing * 4) + property int avatarSize: fontMetrics.lineSpacing * 4 color: background @@ -121,7 +119,7 @@ ApplicationWindow { Layout.preferredWidth: parent.width spacing: Nheko.paddingSmall Layout.alignment: Qt.AlignVCenter | Qt.AlignLeft - Layout.preferredHeight: Math.ceil(fontMetrics.lineSpacing * 4) + Layout.preferredHeight: fontMetrics.lineSpacing * 4 Label { id: roomTopic diff --git a/resources/qml/RoomList.qml b/resources/qml/RoomList.qml index e8aacf75..6074f063 100644 --- a/resources/qml/RoomList.qml +++ b/resources/qml/RoomList.qml @@ -565,7 +565,6 @@ Component { ToolTip.text: qsTr("Room directory") Layout.margins: Nheko.paddingMedium onClicked: { - console.debug("Roomdir clicked"); var win = roomDirectoryComponent.createObject(timelineRoot); win.show(); } diff --git a/resources/res.qrc b/resources/res.qrc index 3e417d4c..b46b726c 100644 --- a/resources/res.qrc +++ b/resources/res.qrc @@ -143,11 +143,6 @@ qml/emoji/StickerPicker.qml qml/UserProfile.qml qml/RoomDirectory.qml - qml/delegates/MessageDelegate.qml - qml/delegates/TextMessage.qml - qml/delegates/NoticeMessage.qml - qml/delegates/ImageMessage.qml - qml/delegates/PlayableMediaMessage.qml qml/delegates/MessageDelegate.qml qml/delegates/Encrypted.qml qml/delegates/FileMessage.qml diff --git a/src/RoomDirectoryModel.cpp b/src/RoomDirectoryModel.cpp index 06bd9d8a..2c633491 100644 --- a/src/RoomDirectoryModel.cpp +++ b/src/RoomDirectoryModel.cpp @@ -42,9 +42,7 @@ RoomDirectoryModel::resetDisplayedData() nextBatch_ = ""; canFetchMore_ = true; - beginRemoveRows(QModelIndex(), 0, static_cast(publicRoomsData_.size())); publicRoomsData_.clear(); - endRemoveRows(); endResetModel(); } @@ -87,8 +85,7 @@ RoomDirectoryModel::getViasForRoom(const std::vector &aliases) std::transform( aliases.begin(), aliases.end(), std::back_inserter(vias), [](const auto &alias) { - const auto roomAliasDelimiter = ":"; - return alias.substr(alias.find(roomAliasDelimiter) + 1); + return alias.substr(alias.find(":") + 1); }); return vias; diff --git a/src/RoomDirectoryModel.h b/src/RoomDirectoryModel.h index c7089a1e..952ae3ff 100644 --- a/src/RoomDirectoryModel.h +++ b/src/RoomDirectoryModel.h @@ -51,7 +51,6 @@ public: inline bool canFetchMore(const QModelIndex &) const override { - nhlog::net()->debug("determining if can fetch more"); return canFetchMore_; } void fetchMore(const QModelIndex &) override; diff --git a/src/timeline/TimelineViewManager.cpp b/src/timeline/TimelineViewManager.cpp index da68d503..ff60856a 100644 --- a/src/timeline/TimelineViewManager.cpp +++ b/src/timeline/TimelineViewManager.cpp @@ -287,7 +287,7 @@ TimelineViewManager::TimelineViewManager(CallManager *callManager, ChatPage *par "Error: Only enums"); qmlRegisterType( - "im.nheko.RoomDirectoryModel", 1, 0, "RoomDirectoryModel"); + "im.nheko", 1, 0, "RoomDirectoryModel"); #ifdef USE_QUICK_VIEW view = new QQuickView(parent); From 570e5ffde236056ad5c70a59681ad6fc1e445717 Mon Sep 17 00:00:00 2001 From: kamathmanu Date: Thu, 12 Aug 2021 09:50:52 -0400 Subject: [PATCH 012/232] Added Spinner while rooms load --- resources/qml/RoomDirectory.qml | 33 +++++++++++++++++++++++++++------ src/RoomDirectoryModel.cpp | 22 +++++++++++++++++----- src/RoomDirectoryModel.h | 22 +++++++++++++++------- 3 files changed, 59 insertions(+), 18 deletions(-) diff --git a/resources/qml/RoomDirectory.qml b/resources/qml/RoomDirectory.qml index b01c1e00..65685b9a 100644 --- a/resources/qml/RoomDirectory.qml +++ b/resources/qml/RoomDirectory.qml @@ -2,6 +2,7 @@ // // SPDX-License-Identifier: GPL-3.0-or-later +import "./ui" import QtQuick 2.9 import QtQuick.Controls 2.3 import QtQuick.Layouts 1.3 @@ -11,6 +12,8 @@ ApplicationWindow { id: roomDirectoryWindow visible: true + property RoomDirectoryModel publicRooms : RoomDirectoryModel {} + x: MainWindow.x + (MainWindow.width / 2) - (width / 2) y: MainWindow.y + (MainWindow.height / 2) - (height / 2) minimumWidth: 650 @@ -56,9 +59,7 @@ ApplicationWindow { ListView { id: roomDirView anchors.fill: parent - model: RoomDirectoryModel { - id: roomDir - } + model: publicRooms delegate: Rectangle { id: roomDirDelegate @@ -158,16 +159,36 @@ ApplicationWindow { width: joinRoomButton.width Button { id: joinRoomButton - visible: roomDir.canJoinRoom(model.roomid) + visible: publicRooms.canJoinRoom(model.roomid) anchors.centerIn: parent width: Math.ceil(0.1 * roomDirectoryWindow.width) text: "Join" - onClicked: roomDir.joinRoom(model.index) + onClicked: publicRooms.joinRoom(model.index) } } } } } - } + } + + footer: Item { + anchors.horizontalCenter: parent.horizontalCenter + width: parent.width + visible: (publicRooms.reachedEndOfPagination == false) && publicRooms.loadingMoreRooms + // hacky but works + height: loadingSpinner.height + 2 * Nheko.paddingLarge + anchors.margins: Nheko.paddingLarge + + Spinner { + id: loadingSpinner + + anchors.centerIn: parent + anchors.margins: Nheko.paddingLarge + running: visible + foreground: Nheko.colors.mid + z: 7 + } + } + } } diff --git a/src/RoomDirectoryModel.cpp b/src/RoomDirectoryModel.cpp index 2c633491..7d6be13e 100644 --- a/src/RoomDirectoryModel.cpp +++ b/src/RoomDirectoryModel.cpp @@ -11,7 +11,6 @@ RoomDirectoryModel::RoomDirectoryModel(QObject *parent, const std::string &s) : QAbstractListModel(parent) , server_(s) - , canFetchMore_(true) { connect(this, &RoomDirectoryModel::fetchedRoomsBatch, @@ -127,6 +126,8 @@ RoomDirectoryModel::data(const QModelIndex &index, int role) const void RoomDirectoryModel::fetchMore(const QModelIndex &) { + if (!canFetchMore_) return; + nhlog::net()->debug("Fetching more rooms from mtxclient..."); mtx::requests::PublicRooms req; @@ -136,10 +137,19 @@ RoomDirectoryModel::fetchMore(const QModelIndex &) // req.third_party_instance_id = third_party_instance_id; auto requested_server = server_; + reachedEndOfPagination_ = false; + emit reachedEndOfPaginationChanged(); + + loadingMoreRooms_ = true; + emit loadingMoreRoomsChanged(); + http::client()->post_public_rooms( req, [requested_server, this, req](const mtx::responses::PublicRooms &res, mtx::http::RequestErr err) { + loadingMoreRooms_ = false; + emit loadingMoreRoomsChanged(); + if (err) { nhlog::net()->error( "Failed to retrieve rooms from mtxclient - {} - {} - {}", @@ -149,7 +159,7 @@ RoomDirectoryModel::fetchMore(const QModelIndex &) } else if (req.filter.generic_search_term == this->userSearchString_ && req.since == this->prevBatch_ && requested_server == this->server_) { nhlog::net()->debug("signalling chunk to GUI thread"); - emit fetchedRoomsBatch(res.chunk, res.prev_batch, res.next_batch); + emit fetchedRoomsBatch(res.chunk, res.next_batch); } }, requested_server); @@ -157,11 +167,9 @@ RoomDirectoryModel::fetchMore(const QModelIndex &) void RoomDirectoryModel::displayRooms(std::vector fetched_rooms, - const std::string &prev_batch, const std::string &next_batch) { - nhlog::net()->debug("Prev batch: {} | Next batch: {}", prevBatch_, nextBatch_); - nhlog::net()->debug("NP batch: {} | NN batch: {}", prev_batch, next_batch); + nhlog::net()->debug("Prev batch: {} | Next batch: {}", prevBatch_, next_batch); if (fetched_rooms.empty()) { nhlog::net()->error("mtxclient helper thread yielded empty chunk!"); @@ -177,7 +185,11 @@ RoomDirectoryModel::displayRooms(std::vector f if (next_batch.empty()) { canFetchMore_ = false; + reachedEndOfPagination_ = true; + emit reachedEndOfPaginationChanged(); } prevBatch_ = next_batch; + + nhlog::ui()->debug ("Finished loading rooms"); } diff --git a/src/RoomDirectoryModel.h b/src/RoomDirectoryModel.h index 952ae3ff..a7e6c0bc 100644 --- a/src/RoomDirectoryModel.h +++ b/src/RoomDirectoryModel.h @@ -27,6 +27,9 @@ class RoomDirectoryModel : public QAbstractListModel { Q_OBJECT + Q_PROPERTY (bool loadingMoreRooms READ loadingMoreRooms NOTIFY loadingMoreRoomsChanged) + Q_PROPERTY (bool reachedEndOfPagination READ reachedEndOfPagination NOTIFY reachedEndOfPaginationChanged) + public: explicit RoomDirectoryModel(QObject *parent = nullptr, const std::string &s = ""); @@ -49,10 +52,15 @@ public: return static_cast(publicRoomsData_.size()); } - inline bool canFetchMore(const QModelIndex &) const override + bool canFetchMore(const QModelIndex &) const override { return canFetchMore_; } + + bool loadingMoreRooms() const { return loadingMoreRooms_; } + + bool reachedEndOfPagination() const { return reachedEndOfPagination_; } + void fetchMore(const QModelIndex &) override; Q_INVOKABLE bool canJoinRoom(const QByteArray &room); @@ -60,15 +68,13 @@ public: signals: void fetchedRoomsBatch(std::vector rooms, - const std::string &prev_batch, const std::string &next_batch); - void serverChanged(); - void searchTermEntered(); + void loadingMoreRoomsChanged(); + void reachedEndOfPaginationChanged(); public slots: void displayRooms(std::vector rooms, - const std::string &prev, - const std::string &next); + const std::string &next_batch); void setMatrixServer(const QString &s = ""); void setSearchTerm(const QString &f); @@ -79,7 +85,9 @@ private: std::string userSearchString_; std::string prevBatch_; std::string nextBatch_; - bool canFetchMore_; + bool canFetchMore_ {true}; + bool loadingMoreRooms_ {false}; + bool reachedEndOfPagination_ {false}; std::vector publicRoomsData_; std::vector getViasForRoom(const std::vector &room); From f2560b7531aeb97d4c581fcc19205d192606a2d2 Mon Sep 17 00:00:00 2001 From: kamathmanu Date: Thu, 12 Aug 2021 09:57:26 -0400 Subject: [PATCH 013/232] Make search text selectable by clicking --- resources/qml/RoomDirectory.qml | 1 + 1 file changed, 1 insertion(+) diff --git a/resources/qml/RoomDirectory.qml b/resources/qml/RoomDirectory.qml index 65685b9a..a3c3d4da 100644 --- a/resources/qml/RoomDirectory.qml +++ b/resources/qml/RoomDirectory.qml @@ -40,6 +40,7 @@ ApplicationWindow { id: roomSearch Layout.fillWidth: true + selectByMouse: true font.pixelSize: fontMetrics.font.pixelSize padding: Nheko.paddingMedium From 9ab129613177f974afd10070e3a7d065d7aebbfa Mon Sep 17 00:00:00 2001 From: kamathmanu Date: Thu, 12 Aug 2021 10:45:42 -0400 Subject: [PATCH 014/232] Ran qmlformat and make license --- resources/qml/RoomDirectory.qml | 411 ++++++++++++++------------- src/RoomDirectoryModel.cpp | 31 +- src/RoomDirectoryModel.h | 24 +- src/timeline/TimelineViewManager.cpp | 3 +- 4 files changed, 244 insertions(+), 225 deletions(-) diff --git a/resources/qml/RoomDirectory.qml b/resources/qml/RoomDirectory.qml index a3c3d4da..d507b796 100644 --- a/resources/qml/RoomDirectory.qml +++ b/resources/qml/RoomDirectory.qml @@ -1,195 +1,216 @@ -// SPDX-FileCopyrightText: 2021 Nheko Contributors -// -// SPDX-License-Identifier: GPL-3.0-or-later - -import "./ui" -import QtQuick 2.9 -import QtQuick.Controls 2.3 -import QtQuick.Layouts 1.3 -import im.nheko 1.0 - -ApplicationWindow { - id: roomDirectoryWindow - visible: true - - property RoomDirectoryModel publicRooms : RoomDirectoryModel {} - - x: MainWindow.x + (MainWindow.width / 2) - (width / 2) - y: MainWindow.y + (MainWindow.height / 2) - (height / 2) - minimumWidth: 650 - minimumHeight: 420 - palette: Nheko.colors - color: Nheko.colors.window - modality: Qt.WindowModal - flags: Qt.Dialog | Qt.WindowCloseButtonHint - title: qsTr("Explore Public Rooms") - - Shortcut { - sequence: StandardKey.Cancel - onActivated: roomDirectoryWindow.close() - } - - header: RowLayout { - id: searchBarLayout - spacing: Nheko.paddingMedium - width: parent.width - - implicitHeight: roomSearch.height - - MatrixTextField { - id: roomSearch - - Layout.fillWidth: true - selectByMouse: true - - font.pixelSize: fontMetrics.font.pixelSize - padding: Nheko.paddingMedium - color: Nheko.colors.text - placeholderText: qsTr("Search for public rooms") - onTextChanged: searchTimer.restart() - } - - Timer { - id: searchTimer - - interval: 350 - onTriggered: roomDirView.model.setSearchTerm(roomSearch.text) - } - } - - ListView { - id: roomDirView - anchors.fill: parent - model: publicRooms - delegate: Rectangle { - id: roomDirDelegate - - property color background: Nheko.colors.window - property color importantText: Nheko.colors.text - property color unimportantText: Nheko.colors.buttonText - property int avatarSize: fontMetrics.lineSpacing * 4 - - color: background - - height: avatarSize + 2.5 * Nheko.paddingMedium - width: ListView.view.width - - RowLayout { - - spacing: Nheko.paddingMedium - anchors.fill: parent - anchors.margins: Nheko.paddingMedium - implicitHeight: textContent.height - - Avatar { - id: roomAvatar - - Layout.alignment: Qt.AlignVCenter - width: avatarSize - height: avatarSize - url: model.avatarUrl.replace("mxc://", "image://MxcImage/") - displayName: model.name - } - - ColumnLayout { - id: textContent - - Layout.alignment: Qt.AlignLeft - Layout.fillWidth: true - width: parent.width - avatar.width - Layout.preferredWidth: parent.width - avatar.width - Layout.preferredHeight: roomNameRow.height + roomDescriptionRow.height - spacing: Nheko.paddingSmall - - RowLayout { - id: roomNameRow - Layout.fillWidth: true - spacing: 0 - - ElidedLabel { - Layout.alignment: Qt.AlignBottom - color: roomDirDelegate.importantText - elideWidth: textContent.width * 0.5 - Nheko.paddingMedium - font.pixelSize: fontMetrics.font.pixelSize * 1.1 - fullText: model.name - } - } - - RowLayout { - id: roomDescriptionRow - Layout.fillWidth: true - Layout.preferredWidth: parent.width - spacing: Nheko.paddingSmall - Layout.alignment: Qt.AlignVCenter | Qt.AlignLeft - Layout.preferredHeight: fontMetrics.lineSpacing * 4 - - Label { - id: roomTopic - color: roomDirDelegate.unimportantText - font.weight: Font.Thin - Layout.alignment: Qt.AlignVCenter | Qt.AlignLeft - font.pixelSize: fontMetrics.font.pixelSize - elide: Text.ElideRight - maximumLineCount: 2 - Layout.fillWidth: true - text: model.topic - verticalAlignment: Text.AlignVCenter - wrapMode: Text.WordWrap - } - Item { - id: numMembersRectangle - Layout.fillWidth: false - Layout.margins: Nheko.paddingSmall - width: roomCount.width - - Label { - id: roomCount - color: roomDirDelegate.unimportantText - anchors.centerIn: parent - Layout.fillWidth: false - font.weight: Font.Thin - font.pixelSize: fontMetrics.font.pixelSize - text: model.numMembers.toString() - } - } - - Item { - id: buttonRectangle - Layout.fillWidth: false - Layout.margins: Nheko.paddingSmall - width: joinRoomButton.width - Button { - id: joinRoomButton - visible: publicRooms.canJoinRoom(model.roomid) - anchors.centerIn: parent - width: Math.ceil(0.1 * roomDirectoryWindow.width) - text: "Join" - onClicked: publicRooms.joinRoom(model.index) - } - } - } - } - } - } - - footer: Item { - anchors.horizontalCenter: parent.horizontalCenter - width: parent.width - visible: (publicRooms.reachedEndOfPagination == false) && publicRooms.loadingMoreRooms - // hacky but works - height: loadingSpinner.height + 2 * Nheko.paddingLarge - anchors.margins: Nheko.paddingLarge - - Spinner { - id: loadingSpinner - - anchors.centerIn: parent - anchors.margins: Nheko.paddingLarge - running: visible - foreground: Nheko.colors.mid - z: 7 - } - } - - } -} +// SPDX-FileCopyrightText: 2021 Nheko Contributors +// +// SPDX-License-Identifier: GPL-3.0-or-later + +import "./ui" +import QtQuick 2.9 +import QtQuick.Controls 2.3 +import QtQuick.Layouts 1.3 +import im.nheko 1.0 + +ApplicationWindow { + id: roomDirectoryWindow + + property RoomDirectoryModel publicRooms + + visible: true + x: MainWindow.x + (MainWindow.width / 2) - (width / 2) + y: MainWindow.y + (MainWindow.height / 2) - (height / 2) + minimumWidth: 650 + minimumHeight: 420 + palette: Nheko.colors + color: Nheko.colors.window + modality: Qt.WindowModal + flags: Qt.Dialog | Qt.WindowCloseButtonHint + title: qsTr("Explore Public Rooms") + + Shortcut { + sequence: StandardKey.Cancel + onActivated: roomDirectoryWindow.close() + } + + ListView { + id: roomDirView + + anchors.fill: parent + model: publicRooms + + delegate: Rectangle { + id: roomDirDelegate + + property color background: Nheko.colors.window + property color importantText: Nheko.colors.text + property color unimportantText: Nheko.colors.buttonText + property int avatarSize: fontMetrics.lineSpacing * 4 + + color: background + height: avatarSize + 2.5 * Nheko.paddingMedium + width: ListView.view.width + + RowLayout { + spacing: Nheko.paddingMedium + anchors.fill: parent + anchors.margins: Nheko.paddingMedium + implicitHeight: textContent.height + + Avatar { + id: roomAvatar + + Layout.alignment: Qt.AlignVCenter + width: avatarSize + height: avatarSize + url: model.avatarUrl.replace("mxc://", "image://MxcImage/") + displayName: model.name + } + + ColumnLayout { + id: textContent + + Layout.alignment: Qt.AlignLeft + Layout.fillWidth: true + width: parent.width - avatar.width + Layout.preferredWidth: parent.width - avatar.width + Layout.preferredHeight: roomNameRow.height + roomDescriptionRow.height + spacing: Nheko.paddingSmall + + RowLayout { + id: roomNameRow + + Layout.fillWidth: true + spacing: 0 + + ElidedLabel { + Layout.alignment: Qt.AlignBottom + color: roomDirDelegate.importantText + elideWidth: textContent.width * 0.5 - Nheko.paddingMedium + font.pixelSize: fontMetrics.font.pixelSize * 1.1 + fullText: model.name + } + + } + + RowLayout { + id: roomDescriptionRow + + Layout.fillWidth: true + Layout.preferredWidth: parent.width + spacing: Nheko.paddingSmall + Layout.alignment: Qt.AlignVCenter | Qt.AlignLeft + Layout.preferredHeight: fontMetrics.lineSpacing * 4 + + Label { + id: roomTopic + + color: roomDirDelegate.unimportantText + font.weight: Font.Thin + Layout.alignment: Qt.AlignVCenter | Qt.AlignLeft + font.pixelSize: fontMetrics.font.pixelSize + elide: Text.ElideRight + maximumLineCount: 2 + Layout.fillWidth: true + text: model.topic + verticalAlignment: Text.AlignVCenter + wrapMode: Text.WordWrap + } + + Item { + id: numMembersRectangle + + Layout.fillWidth: false + Layout.margins: Nheko.paddingSmall + width: roomCount.width + + Label { + id: roomCount + + color: roomDirDelegate.unimportantText + anchors.centerIn: parent + Layout.fillWidth: false + font.weight: Font.Thin + font.pixelSize: fontMetrics.font.pixelSize + text: model.numMembers.toString() + } + + } + + Item { + id: buttonRectangle + + Layout.fillWidth: false + Layout.margins: Nheko.paddingSmall + width: joinRoomButton.width + + Button { + id: joinRoomButton + + visible: publicRooms.canJoinRoom(model.roomid) + anchors.centerIn: parent + width: Math.ceil(0.1 * roomDirectoryWindow.width) + text: "Join" + onClicked: publicRooms.joinRoom(model.index) + } + + } + + } + + } + + } + + } + + footer: Item { + anchors.horizontalCenter: parent.horizontalCenter + width: parent.width + visible: (publicRooms.reachedEndOfPagination == false) && publicRooms.loadingMoreRooms + // hacky but works + height: loadingSpinner.height + 2 * Nheko.paddingLarge + anchors.margins: Nheko.paddingLarge + + Spinner { + id: loadingSpinner + + anchors.centerIn: parent + anchors.margins: Nheko.paddingLarge + running: visible + foreground: Nheko.colors.mid + z: 7 + } + + } + + } + + publicRooms: RoomDirectoryModel { + } + + header: RowLayout { + id: searchBarLayout + + spacing: Nheko.paddingMedium + width: parent.width + implicitHeight: roomSearch.height + + MatrixTextField { + id: roomSearch + + Layout.fillWidth: true + selectByMouse: true + font.pixelSize: fontMetrics.font.pixelSize + padding: Nheko.paddingMedium + color: Nheko.colors.text + placeholderText: qsTr("Search for public rooms") + onTextChanged: searchTimer.restart() + } + + Timer { + id: searchTimer + + interval: 350 + onTriggered: roomDirView.model.setSearchTerm(roomSearch.text) + } + + } + +} diff --git a/src/RoomDirectoryModel.cpp b/src/RoomDirectoryModel.cpp index 7d6be13e..5873771f 100644 --- a/src/RoomDirectoryModel.cpp +++ b/src/RoomDirectoryModel.cpp @@ -82,10 +82,10 @@ RoomDirectoryModel::getViasForRoom(const std::vector &aliases) vias.reserve(aliases.size()); - std::transform( - aliases.begin(), aliases.end(), std::back_inserter(vias), [](const auto &alias) { - return alias.substr(alias.find(":") + 1); - }); + std::transform(aliases.begin(), + aliases.end(), + std::back_inserter(vias), + [](const auto &alias) { return alias.substr(alias.find(":") + 1); }); return vias; } @@ -126,7 +126,8 @@ RoomDirectoryModel::data(const QModelIndex &index, int role) const void RoomDirectoryModel::fetchMore(const QModelIndex &) { - if (!canFetchMore_) return; + if (!canFetchMore_) + return; nhlog::net()->debug("Fetching more rooms from mtxclient..."); @@ -137,18 +138,18 @@ RoomDirectoryModel::fetchMore(const QModelIndex &) // req.third_party_instance_id = third_party_instance_id; auto requested_server = server_; - reachedEndOfPagination_ = false; - emit reachedEndOfPaginationChanged(); + reachedEndOfPagination_ = false; + emit reachedEndOfPaginationChanged(); - loadingMoreRooms_ = true; - emit loadingMoreRoomsChanged(); + loadingMoreRooms_ = true; + emit loadingMoreRoomsChanged(); http::client()->post_public_rooms( req, [requested_server, this, req](const mtx::responses::PublicRooms &res, mtx::http::RequestErr err) { - loadingMoreRooms_ = false; - emit loadingMoreRoomsChanged(); + loadingMoreRooms_ = false; + emit loadingMoreRoomsChanged(); if (err) { nhlog::net()->error( @@ -184,12 +185,12 @@ RoomDirectoryModel::displayRooms(std::vector f endInsertRows(); if (next_batch.empty()) { - canFetchMore_ = false; - reachedEndOfPagination_ = true; - emit reachedEndOfPaginationChanged(); + canFetchMore_ = false; + reachedEndOfPagination_ = true; + emit reachedEndOfPaginationChanged(); } prevBatch_ = next_batch; - nhlog::ui()->debug ("Finished loading rooms"); + nhlog::ui()->debug("Finished loading rooms"); } diff --git a/src/RoomDirectoryModel.h b/src/RoomDirectoryModel.h index a7e6c0bc..b7eda00d 100644 --- a/src/RoomDirectoryModel.h +++ b/src/RoomDirectoryModel.h @@ -27,8 +27,9 @@ class RoomDirectoryModel : public QAbstractListModel { Q_OBJECT - Q_PROPERTY (bool loadingMoreRooms READ loadingMoreRooms NOTIFY loadingMoreRoomsChanged) - Q_PROPERTY (bool reachedEndOfPagination READ reachedEndOfPagination NOTIFY reachedEndOfPaginationChanged) + Q_PROPERTY(bool loadingMoreRooms READ loadingMoreRooms NOTIFY loadingMoreRoomsChanged) + Q_PROPERTY(bool reachedEndOfPagination READ reachedEndOfPagination NOTIFY + reachedEndOfPaginationChanged) public: explicit RoomDirectoryModel(QObject *parent = nullptr, const std::string &s = ""); @@ -52,14 +53,11 @@ public: return static_cast(publicRoomsData_.size()); } - bool canFetchMore(const QModelIndex &) const override - { - return canFetchMore_; - } + bool canFetchMore(const QModelIndex &) const override { return canFetchMore_; } - bool loadingMoreRooms() const { return loadingMoreRooms_; } + bool loadingMoreRooms() const { return loadingMoreRooms_; } - bool reachedEndOfPagination() const { return reachedEndOfPagination_; } + bool reachedEndOfPagination() const { return reachedEndOfPagination_; } void fetchMore(const QModelIndex &) override; @@ -69,8 +67,8 @@ public: signals: void fetchedRoomsBatch(std::vector rooms, const std::string &next_batch); - void loadingMoreRoomsChanged(); - void reachedEndOfPaginationChanged(); + void loadingMoreRoomsChanged(); + void reachedEndOfPaginationChanged(); public slots: void displayRooms(std::vector rooms, @@ -85,9 +83,9 @@ private: std::string userSearchString_; std::string prevBatch_; std::string nextBatch_; - bool canFetchMore_ {true}; - bool loadingMoreRooms_ {false}; - bool reachedEndOfPagination_ {false}; + bool canFetchMore_{true}; + bool loadingMoreRooms_{false}; + bool reachedEndOfPagination_{false}; std::vector publicRoomsData_; std::vector getViasForRoom(const std::vector &room); diff --git a/src/timeline/TimelineViewManager.cpp b/src/timeline/TimelineViewManager.cpp index ff60856a..6f935760 100644 --- a/src/timeline/TimelineViewManager.cpp +++ b/src/timeline/TimelineViewManager.cpp @@ -286,8 +286,7 @@ TimelineViewManager::TimelineViewManager(CallManager *callManager, ChatPage *par "EmojiCategory", "Error: Only enums"); - qmlRegisterType( - "im.nheko", 1, 0, "RoomDirectoryModel"); + qmlRegisterType("im.nheko", 1, 0, "RoomDirectoryModel"); #ifdef USE_QUICK_VIEW view = new QQuickView(parent); From eaddfb4f7308a3ae4a84c11fff279d9b872932ef Mon Sep 17 00:00:00 2001 From: kamathmanu Date: Sat, 14 Aug 2021 09:44:34 -0400 Subject: [PATCH 015/232] Clean up final nits --- resources/qml/RoomDirectory.qml | 17 ++++++++--------- src/RoomDirectoryModel.cpp | 4 +--- src/RoomDirectoryModel.h | 7 +++++-- 3 files changed, 14 insertions(+), 14 deletions(-) diff --git a/resources/qml/RoomDirectory.qml b/resources/qml/RoomDirectory.qml index d507b796..abd35c57 100644 --- a/resources/qml/RoomDirectory.qml +++ b/resources/qml/RoomDirectory.qml @@ -22,6 +22,7 @@ ApplicationWindow { color: Nheko.colors.window modality: Qt.WindowModal flags: Qt.Dialog | Qt.WindowCloseButtonHint + Component.onCompleted: Nheko.reparent(roomDirectoryWindow) title: qsTr("Explore Public Rooms") Shortcut { @@ -35,6 +36,12 @@ ApplicationWindow { anchors.fill: parent model: publicRooms + ScrollHelper { + flickable: parent + anchors.fill: parent + enabled: !Settings.mobileMode + } + delegate: Rectangle { id: roomDirDelegate @@ -44,7 +51,7 @@ ApplicationWindow { property int avatarSize: fontMetrics.lineSpacing * 4 color: background - height: avatarSize + 2.5 * Nheko.paddingMedium + height: avatarSize + Nheko.paddingLarge width: ListView.view.width RowLayout { @@ -67,7 +74,6 @@ ApplicationWindow { id: textContent Layout.alignment: Qt.AlignLeft - Layout.fillWidth: true width: parent.width - avatar.width Layout.preferredWidth: parent.width - avatar.width Layout.preferredHeight: roomNameRow.height + roomDescriptionRow.height @@ -76,7 +82,6 @@ ApplicationWindow { RowLayout { id: roomNameRow - Layout.fillWidth: true spacing: 0 ElidedLabel { @@ -92,7 +97,6 @@ ApplicationWindow { RowLayout { id: roomDescriptionRow - Layout.fillWidth: true Layout.preferredWidth: parent.width spacing: Nheko.paddingSmall Layout.alignment: Qt.AlignVCenter | Qt.AlignLeft @@ -116,7 +120,6 @@ ApplicationWindow { Item { id: numMembersRectangle - Layout.fillWidth: false Layout.margins: Nheko.paddingSmall width: roomCount.width @@ -125,8 +128,6 @@ ApplicationWindow { color: roomDirDelegate.unimportantText anchors.centerIn: parent - Layout.fillWidth: false - font.weight: Font.Thin font.pixelSize: fontMetrics.font.pixelSize text: model.numMembers.toString() } @@ -136,7 +137,6 @@ ApplicationWindow { Item { id: buttonRectangle - Layout.fillWidth: false Layout.margins: Nheko.paddingSmall width: joinRoomButton.width @@ -175,7 +175,6 @@ ApplicationWindow { anchors.margins: Nheko.paddingLarge running: visible foreground: Nheko.colors.mid - z: 7 } } diff --git a/src/RoomDirectoryModel.cpp b/src/RoomDirectoryModel.cpp index 5873771f..61c3eb72 100644 --- a/src/RoomDirectoryModel.cpp +++ b/src/RoomDirectoryModel.cpp @@ -69,10 +69,8 @@ RoomDirectoryModel::setSearchTerm(const QString &f) bool RoomDirectoryModel::canJoinRoom(const QByteArray &room) { - const auto &cache = cache::roomInfo(); const QString room_id(room); - const bool validRoom = !room_id.isNull() && !room_id.isEmpty(); - return validRoom && !cache.contains(room_id); + return !room_id.isEmpty() && !cache::getRoomInfo({room_id.toStdString()}).count(room_id); } std::vector diff --git a/src/RoomDirectoryModel.h b/src/RoomDirectoryModel.h index b7eda00d..791384fa 100644 --- a/src/RoomDirectoryModel.h +++ b/src/RoomDirectoryModel.h @@ -71,11 +71,14 @@ signals: void reachedEndOfPaginationChanged(); public slots: - void displayRooms(std::vector rooms, - const std::string &next_batch); void setMatrixServer(const QString &s = ""); void setSearchTerm(const QString &f); +private slots: + + void displayRooms(std::vector rooms, + const std::string &next_batch); + private: static constexpr size_t limit_ = 50; From 0f4a7b1ba63a50f6b5603cb94e686d7fe230371e Mon Sep 17 00:00:00 2001 From: kamathmanu Date: Sat, 14 Aug 2021 09:49:18 -0400 Subject: [PATCH 016/232] Formatting + Licensing --- resources/qml/RoomDirectory.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/qml/RoomDirectory.qml b/resources/qml/RoomDirectory.qml index abd35c57..cd409bea 100644 --- a/resources/qml/RoomDirectory.qml +++ b/resources/qml/RoomDirectory.qml @@ -36,7 +36,7 @@ ApplicationWindow { anchors.fill: parent model: publicRooms - ScrollHelper { + ScrollHelper { flickable: parent anchors.fill: parent enabled: !Settings.mobileMode From 7321af8a7d9cf01317b0a46c36eab25f2e95f5a8 Mon Sep 17 00:00:00 2001 From: kamathmanu Date: Sat, 14 Aug 2021 21:47:11 -0400 Subject: [PATCH 017/232] Cleanup more nits --- resources/qml/RoomDirectory.qml | 27 ++++++++------------------- 1 file changed, 8 insertions(+), 19 deletions(-) diff --git a/resources/qml/RoomDirectory.qml b/resources/qml/RoomDirectory.qml index cd409bea..129391a5 100644 --- a/resources/qml/RoomDirectory.qml +++ b/resources/qml/RoomDirectory.qml @@ -14,8 +14,6 @@ ApplicationWindow { property RoomDirectoryModel publicRooms visible: true - x: MainWindow.x + (MainWindow.width / 2) - (width / 2) - y: MainWindow.y + (MainWindow.height / 2) - (height / 2) minimumWidth: 650 minimumHeight: 420 palette: Nheko.colors @@ -57,7 +55,7 @@ ApplicationWindow { RowLayout { spacing: Nheko.paddingMedium anchors.fill: parent - anchors.margins: Nheko.paddingMedium + anchors.margins: Nheko.paddingLarge implicitHeight: textContent.height Avatar { @@ -76,22 +74,14 @@ ApplicationWindow { Layout.alignment: Qt.AlignLeft width: parent.width - avatar.width Layout.preferredWidth: parent.width - avatar.width - Layout.preferredHeight: roomNameRow.height + roomDescriptionRow.height spacing: Nheko.paddingSmall - RowLayout { - id: roomNameRow - - spacing: 0 - - ElidedLabel { - Layout.alignment: Qt.AlignBottom - color: roomDirDelegate.importantText - elideWidth: textContent.width * 0.5 - Nheko.paddingMedium - font.pixelSize: fontMetrics.font.pixelSize * 1.1 - fullText: model.name - } - + ElidedLabel { + Layout.alignment: Qt.AlignBottom + color: roomDirDelegate.importantText + elideWidth: textContent.width - numMembersRectangle.width - buttonRectangle.width + font.pixelSize: fontMetrics.font.pixelSize * 1.1 + fullText: model.name } RowLayout { @@ -106,7 +96,6 @@ ApplicationWindow { id: roomTopic color: roomDirDelegate.unimportantText - font.weight: Font.Thin Layout.alignment: Qt.AlignVCenter | Qt.AlignLeft font.pixelSize: fontMetrics.font.pixelSize elide: Text.ElideRight @@ -163,7 +152,7 @@ ApplicationWindow { footer: Item { anchors.horizontalCenter: parent.horizontalCenter width: parent.width - visible: (publicRooms.reachedEndOfPagination == false) && publicRooms.loadingMoreRooms + visible: !publicRooms.reachedEndOfPagination && publicRooms.loadingMoreRooms // hacky but works height: loadingSpinner.height + 2 * Nheko.paddingLarge anchors.margins: Nheko.paddingLarge From 820665db92f1f400c6f0d118d3aed36f557dde6c Mon Sep 17 00:00:00 2001 From: kamathmanu Date: Sat, 14 Aug 2021 21:58:56 -0400 Subject: [PATCH 018/232] Formatting --- resources/qml/RoomDirectory.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/qml/RoomDirectory.qml b/resources/qml/RoomDirectory.qml index 129391a5..4db24f01 100644 --- a/resources/qml/RoomDirectory.qml +++ b/resources/qml/RoomDirectory.qml @@ -79,7 +79,7 @@ ApplicationWindow { ElidedLabel { Layout.alignment: Qt.AlignBottom color: roomDirDelegate.importantText - elideWidth: textContent.width - numMembersRectangle.width - buttonRectangle.width + elideWidth: textContent.width - numMembersRectangle.width - buttonRectangle.width font.pixelSize: fontMetrics.font.pixelSize * 1.1 fullText: model.name } From 0cdc0a1d833fd5802f1058f3bb7f375101167af1 Mon Sep 17 00:00:00 2001 From: Weblate Date: Mon, 16 Aug 2021 17:27:52 -0400 Subject: [PATCH 019/232] Update translation files Updated by "Cleanup translation files" hook in Weblate. Co-authored-by: Weblate Translate-URL: https://weblate.nheko.im/projects/nheko/nheko-master/ Translation: Nheko/nheko --- resources/langs/nheko_fi.ts | 117 ------------------------------------ 1 file changed, 117 deletions(-) diff --git a/resources/langs/nheko_fi.ts b/resources/langs/nheko_fi.ts index 8db7eb99..bb89902f 100644 --- a/resources/langs/nheko_fi.ts +++ b/resources/langs/nheko_fi.ts @@ -496,41 +496,21 @@ Encrypted - - - There is no key to unlock this message. We requested the key automatically, but you can try requesting it again if you are impatient. - - This message couldn't be decrypted, because we only have a key for newer messages. You can try requesting access to this message. - - - There was an internal error reading the decryption key from the database. - - There was an error decrypting this message. - - - The message couldn't be parsed. - - The encryption key was reused! Someone is possibly trying to insert false messages into this chat! - - - Unknown decryption error - - Request key @@ -604,63 +584,32 @@ ImagePackEditorDialog - - - Editing image pack - - Add images - - - Stickers (*.png *.webp) - - State key - - - Packname - - Attribution - - - - Use as Emoji - - Use as Sticker - - - Shortcode - - Body - - - Cancel - Peruuta - Save @@ -674,11 +623,6 @@ Image pack settings - - - Create account pack - - New room pack @@ -709,11 +653,6 @@ Enables this pack to be used in all rooms - - - Edit - Muokkaa - Close @@ -1259,11 +1198,6 @@ Esimerkki: https://server.my:8787 ReadReceipts - - - Read receipts - Lukukuittaukset - ReadReceiptsModel @@ -1321,11 +1255,6 @@ Esimerkki: https://server.my:8787 No supported registration flows! Ei tuettuja rekisteröintejä! - - - Registration token - - Please enter a valid registration token. @@ -1517,21 +1446,11 @@ Esimerkki: https://server.my:8787 Invite more people - - - This room is not encrypted! - - This user is verified. - - - This user isn't verified, but is still using the same master key from the first time you met. - - This user has unverified devices! @@ -1575,11 +1494,6 @@ Esimerkki: https://server.my:8787 All messages Kaikki viestit - - - Room access - - Anyone and guests @@ -1747,22 +1661,11 @@ Esimerkki: https://server.my:8787 SingleImagePackModel - - - - Failed to update image pack: {} - - Failed to delete old image pack: {} - - - Failed to open image: {} - - Failed to upload image: {} @@ -2058,21 +1961,11 @@ Esimerkki: https://server.my:8787 No room selected Ei valittua huonetta - - - This room is not encrypted! - - This room contains only verified devices. - - - This rooms contain verified devices and devices which have never changed their master key. - - This room contains unverified devices! @@ -2409,11 +2302,6 @@ Tämä yleensä saa sovelluksen kuvakkeen liikkumaan jollain tapaa tehtäväpalk Make font size larger if messages with only a few emojis are displayed. Suurenna fonttikokoa jos näytetään viestit vain muutamalla emojilla. - - - Send encrypted messages to verified users only - - Requires a user to be verified to send encrypted messages to them. This improves safety but makes E2EE more tedious. @@ -2424,11 +2312,6 @@ Tämä yleensä saa sovelluksen kuvakkeen liikkumaan jollain tapaa tehtäväpalk Share keys with verified users and devices Jaa avaimet vahvistettujen käyttäjien ja laitteiden kanssa - - - Automatically replies to key requests from other users, if they are verified, even if that device shouldn't have access to those keys otherwise. - - CACHED From e5dea361c02b729939f974518513723a897fe380 Mon Sep 17 00:00:00 2001 From: Joseph Donofry Date: Mon, 16 Aug 2021 17:30:01 -0400 Subject: [PATCH 020/232] Update translations --- resources/langs/nheko_cs.ts | 430 +++++++++++++++++++++----------- resources/langs/nheko_de.ts | 428 +++++++++++++++++++++----------- resources/langs/nheko_el.ts | 426 +++++++++++++++++++++----------- resources/langs/nheko_en.ts | 432 ++++++++++++++++++++++----------- resources/langs/nheko_eo.ts | 428 +++++++++++++++++++++----------- resources/langs/nheko_es.ts | 430 +++++++++++++++++++++----------- resources/langs/nheko_et.ts | 432 ++++++++++++++++++++++----------- resources/langs/nheko_fi.ts | 117 +++++++++ resources/langs/nheko_fr.ts | 428 +++++++++++++++++++++----------- resources/langs/nheko_hu.ts | 432 ++++++++++++++++++++++----------- resources/langs/nheko_it.ts | 426 +++++++++++++++++++++----------- resources/langs/nheko_ja.ts | 426 +++++++++++++++++++++----------- resources/langs/nheko_ml.ts | 430 +++++++++++++++++++++----------- resources/langs/nheko_nl.ts | 426 +++++++++++++++++++++----------- resources/langs/nheko_pl.ts | 426 +++++++++++++++++++++----------- resources/langs/nheko_pt_BR.ts | 430 +++++++++++++++++++++----------- resources/langs/nheko_pt_PT.ts | 430 +++++++++++++++++++++----------- resources/langs/nheko_ro.ts | 426 +++++++++++++++++++++----------- resources/langs/nheko_ru.ts | 428 +++++++++++++++++++++----------- resources/langs/nheko_si.ts | 426 +++++++++++++++++++++----------- resources/langs/nheko_sv.ts | 432 ++++++++++++++++++++++----------- resources/langs/nheko_zh_CN.ts | 426 +++++++++++++++++++++----------- 22 files changed, 6212 insertions(+), 2903 deletions(-) diff --git a/resources/langs/nheko_cs.ts b/resources/langs/nheko_cs.ts index 99813f81..10777538 100644 --- a/resources/langs/nheko_cs.ts +++ b/resources/langs/nheko_cs.ts @@ -56,7 +56,7 @@ CallInvite - + Video Call @@ -125,7 +125,7 @@ ChatPage - + Failed to invite user: %1 @@ -157,12 +157,12 @@ - + Confirm invite - + Do you really want to invite %1 (%2)? @@ -227,12 +227,12 @@ - + Do you really want to start a private chat with %1? - + Cache migration failed! @@ -367,7 +367,7 @@ - + Decryption failed @@ -494,6 +494,49 @@ + + Encrypted + + + There is no key to unlock this message. We requested the key automatically, but you can try requesting it again if you are impatient. + + + + + This message couldn't be decrypted, because we only have a key for newer messages. You can try requesting access to this message. + + + + + There was an internal error reading the decryption key from the database. + + + + + There was an error decrypting this message. + + + + + The message couldn't be parsed. + + + + + The encryption key was reused! Someone is possibly trying to insert false messages into this chat! + + + + + Unknown decryption error + + + + + Request key + + + EncryptionIndicator @@ -517,51 +560,6 @@ - - EventStore - - - -- Encrypted Event (No keys found for decryption) -- - Placeholder, when the message was not decrypted yet or can't be decrypted. - - - - - -- Encrypted Event (Key not valid for this index) -- - Placeholder, when the message can't be decrypted with this key since it is not valid for this index - - - - - - -- Decryption Error (failed to retrieve megolm keys from db) -- - Placeholder, when the message can't be decrypted, because the DB access failed. - - - - - - -- Decryption Error (%1) -- - Placeholder, when the message can't be decrypted. In this case, the Olm decrytion returned an error, which is passed as %1. - - - - - -- Encrypted Event (Unknown event type) -- - Placeholder, when the message was decrypted, but we couldn't parse it, because Nheko/mtxclient don't support that event type yet. - - - - - -- Replay attack! This message index was reused! -- - - - - - -- Message by unverified device! -- - - - Failed @@ -604,6 +602,71 @@ + + ImagePackEditorDialog + + + Editing image pack + + + + + Add images + + + + + Stickers (*.png *.webp) + + + + + State key + + + + + Packname + + + + + Attribution + + + + + + Use as Emoji + + + + + + Use as Sticker + + + + + Shortcode + + + + + Body + + + + + Cancel + + + + + Save + + + ImagePackSettingsDialog @@ -612,7 +675,17 @@ - + + Create account pack + + + + + New room pack + + + + Private pack @@ -627,7 +700,7 @@ - + Enable globally @@ -637,7 +710,12 @@ - + + Edit + + + + Close @@ -807,12 +885,12 @@ Example: https://server.my:8787 MessageDelegate - + Encryption enabled - + room name changed to: %1 @@ -867,13 +945,13 @@ Example: https://server.my:8787 - + removed - + %1 ended the call. @@ -901,7 +979,7 @@ Example: https://server.my:8787 - + Stickers @@ -924,7 +1002,7 @@ Example: https://server.my:8787 MessageView - + Edit @@ -944,7 +1022,7 @@ Example: https://server.my:8787 - + &Copy @@ -1175,21 +1253,37 @@ Example: https://server.my:8787 + + ReadReceipts + + + Read receipts + + + + + ReadReceiptsModel + + + Yesterday, %1 + + + RegisterPage - + Username - + The username must not be empty, and must contain only the characters a-z, 0-9, ., _, =, -, and /. - + Password @@ -1209,7 +1303,7 @@ Example: https://server.my:8787 - + A server that allows registration. Since matrix is decentralized, you need to first find a server you can register on or host your own. @@ -1219,22 +1313,27 @@ Example: https://server.my:8787 - + No supported registration flows! - - One or more fields have invalid inputs. Please correct those issues and try again. + + Registration token - + + Please enter a valid registration token. + + + + Autodiscovery failed. Received malformed response. - + Autodiscovery failed. Unknown error when requesting .well-known. @@ -1244,7 +1343,7 @@ Example: https://server.my:8787 - + Received malformed response. Make sure the homeserver domain is valid. @@ -1254,17 +1353,17 @@ Example: https://server.my:8787 - + Password is not long enough (min 8 chars) - + Passwords don't match - + Invalid server name @@ -1272,7 +1371,7 @@ Example: https://server.my:8787 ReplyPopup - + Close @@ -1285,7 +1384,7 @@ Example: https://server.my:8787 RoomInfo - + no version stored @@ -1293,7 +1392,7 @@ Example: https://server.my:8787 RoomList - + New tag @@ -1343,7 +1442,7 @@ Example: https://server.my:8787 - + Status Message @@ -1363,7 +1462,7 @@ Example: https://server.my:8787 - + Logout @@ -1396,7 +1495,7 @@ Example: https://server.my:8787 RoomMembers - + Members of %1 @@ -1415,11 +1514,31 @@ Example: https://server.my:8787 Invite more people + + + This room is not encrypted! + + + + + This user is verified. + + + + + This user isn't verified, but is still using the same master key from the first time you met. + + + + + This user has unverified devices! + + RoomSettings - + Room Settings @@ -1454,7 +1573,12 @@ Example: https://server.my:8787 - + + Room access + + + + Anyone and guests @@ -1618,6 +1742,30 @@ Example: https://server.my:8787 + + SingleImagePackModel + + + + Failed to update image pack: {} + + + + + Failed to delete old image pack: {} + + + + + Failed to open image: {} + + + + + Failed to upload image: {} + + + StatusIndicator @@ -1670,7 +1818,7 @@ Example: https://server.my:8787 TimelineModel - + Message redaction failed: %1 @@ -1701,7 +1849,7 @@ Example: https://server.my:8787 - + %1 and %2 are typing. Multiple users are typing. First argument is a comma separated list of potentially multiple users. Second argument is the last user of that list. (If only one user is typing, %1 is empty. You should still use it in your string though to silence Qt warnings.) @@ -1816,12 +1964,12 @@ Example: https://server.my:8787 - + You joined this room. - + %1 has changed their avatar and changed their display name to %2. @@ -1850,7 +1998,7 @@ Example: https://server.my:8787 TimelineRow - + Edited @@ -1858,12 +2006,12 @@ Example: https://server.my:8787 TimelineView - + No room open - + %1 member(s) @@ -1891,7 +2039,7 @@ Example: https://server.my:8787 TimelineViewManager - + No encrypted private chat found with this user. Create an encrypted private chat with this user and try again. @@ -1899,17 +2047,37 @@ Example: https://server.my:8787 TopBar - + Back to room list - + No room selected - + + This room is not encrypted! + + + + + This room contains only verified devices. + + + + + This rooms contain verified devices and devices which have never changed their master key. + + + + + This room contains unverified devices! + + + + Room options @@ -1950,7 +2118,7 @@ Example: https://server.my:8787 UserProfile - + Global User Profile @@ -1960,7 +2128,7 @@ Example: https://server.my:8787 - + Verify @@ -2009,8 +2177,8 @@ Example: https://server.my:8787 UserSettings - - + + Default @@ -2018,7 +2186,7 @@ Example: https://server.my:8787 UserSettingsPage - + Minimize to tray @@ -2038,12 +2206,12 @@ Example: https://server.my:8787 - + profile: %1 - + Default @@ -2229,12 +2397,27 @@ This usually causes the application icon in the task bar to animate in some fash - + + Send encrypted messages to verified users only + + + + + Requires a user to be verified to send encrypted messages to them. This improves safety but makes E2EE more tedious. + + + + Share keys with verified users and devices - + + Automatically replies to key requests from other users, if they are verified, even if that device shouldn't have access to those keys otherwise. + + + + CACHED @@ -2244,7 +2427,7 @@ This usually causes the application icon in the task bar to animate in some fash - + Scale factor @@ -2339,12 +2522,12 @@ This usually causes the application icon in the task bar to animate in some fash - + GENERAL - + INTERFACE @@ -2364,12 +2547,7 @@ This usually causes the application icon in the task bar to animate in some fash - - Automatically replies to key requests from other users, if they are verified. - - - - + Master signing key @@ -2419,7 +2597,7 @@ This usually causes the application icon in the task bar to animate in some fash - + Open Sessions File @@ -2668,32 +2846,6 @@ Media size: %2 - - dialogs::ReadReceipts - - - Read receipts - - - - - Close - - - - - dialogs::ReceiptItem - - - Today %1 - - - - - Yesterday %1 - - - message-description sent: diff --git a/resources/langs/nheko_de.ts b/resources/langs/nheko_de.ts index cb5b54fb..d37debc2 100644 --- a/resources/langs/nheko_de.ts +++ b/resources/langs/nheko_de.ts @@ -56,7 +56,7 @@ CallInvite - + Video Call Videoanruf @@ -125,7 +125,7 @@ ChatPage - + Failed to invite user: %1 Nutzer konnte nicht eingeladen werden: %1 @@ -157,12 +157,12 @@ - + Confirm invite Einladung bestätigen - + Do you really want to invite %1 (%2)? Nutzer %1 (%2) wirklich einladen? @@ -227,12 +227,12 @@ Verbannung aufgehoben: %1 - + Do you really want to start a private chat with %1? Möchtest du wirklich eine private Konversation mit %1 beginnen? - + Cache migration failed! Migration des Caches fehlgeschlagen! @@ -367,7 +367,7 @@ Gib deinen Wiederherstellungsschlüssel oder dein Wiederherstellungspasswort mit dem Namen %1 ein um deine Geheimnisse zu entschlüsseln: - + Decryption failed Entschlüsseln fehlgeschlagen @@ -494,6 +494,49 @@ Sie stimmen überein! + + Encrypted + + + There is no key to unlock this message. We requested the key automatically, but you can try requesting it again if you are impatient. + + + + + This message couldn't be decrypted, because we only have a key for newer messages. You can try requesting access to this message. + + + + + There was an internal error reading the decryption key from the database. + + + + + There was an error decrypting this message. + + + + + The message couldn't be parsed. + + + + + The encryption key was reused! Someone is possibly trying to insert false messages into this chat! + + + + + Unknown decryption error + + + + + Request key + + + EncryptionIndicator @@ -517,51 +560,6 @@ Von einem unverifizierten Gerät verschlüsselt - - EventStore - - - -- Encrypted Event (No keys found for decryption) -- - Placeholder, when the message was not decrypted yet or can't be decrypted. - -- Verschlüsseltes Event (keine Schlüssel zur Entschlüsselung gefunden) -- - - - - -- Encrypted Event (Key not valid for this index) -- - Placeholder, when the message can't be decrypted with this key since it is not valid for this index - -- Verschlüsseltes Event (Schlüssel passt nicht für diesen Nachrichtenindex) -- - - - - - -- Decryption Error (failed to retrieve megolm keys from db) -- - Placeholder, when the message can't be decrypted, because the DB access failed. - -- Entschlüsselungsfehler (Fehler bei Suche nach Megolm Schlüsseln in Datenbank) -- - - - - - -- Decryption Error (%1) -- - Placeholder, when the message can't be decrypted. In this case, the Olm decrytion returned an error, which is passed as %1. - -- Entschlüsselungsfehler (%1) -- - - - - -- Encrypted Event (Unknown event type) -- - Placeholder, when the message was decrypted, but we couldn't parse it, because Nheko/mtxclient don't support that event type yet. - -- Verschlüsseltes Event (Unbekannter Eventtyp) -- - - - - -- Replay attack! This message index was reused! -- - -- Replay-angriff! Der Nachrichtenindex wurde wiederverwendet! -- - - - - -- Message by unverified device! -- - -- Nachricht von einem unverifizierten Gerät! -- - - Failed @@ -604,6 +602,71 @@ Nachricht weiterleiten + + ImagePackEditorDialog + + + Editing image pack + + + + + Add images + + + + + Stickers (*.png *.webp) + + + + + State key + + + + + Packname + + + + + Attribution + + + + + + Use as Emoji + + + + + + Use as Sticker + + + + + Shortcode + + + + + Body + + + + + Cancel + Abbrechen + + + + Save + + + ImagePackSettingsDialog @@ -612,7 +675,17 @@ - + + Create account pack + + + + + New room pack + + + + Private pack @@ -627,7 +700,7 @@ - + Enable globally @@ -637,7 +710,12 @@ - + + Edit + Bearbeiten + + + Close Schließen @@ -811,7 +889,7 @@ Beispiel: https://mein.server:8787 MessageDelegate - + removed entfernt @@ -822,7 +900,7 @@ Beispiel: https://mein.server:8787 Verschlüsselung aktiviert - + room name changed to: %1 Raumname wurde gändert auf: %1 @@ -905,7 +983,7 @@ Beispiel: https://mein.server:8787 Schreibe eine Nachricht… - + Stickers Sticker @@ -928,7 +1006,7 @@ Beispiel: https://mein.server:8787 MessageView - + Edit Bearbeiten @@ -948,7 +1026,7 @@ Beispiel: https://mein.server:8787 Optionen - + &Copy &Kopieren @@ -1179,21 +1257,37 @@ Beispiel: https://mein.server:8787 Profilname + + ReadReceipts + + + Read receipts + Lesebestätigungen + + + + ReadReceiptsModel + + + Yesterday, %1 + + + RegisterPage - + Username Benutzername - + The username must not be empty, and must contain only the characters a-z, 0-9, ., _, =, -, and /. Der Benutzername sollte nicht leer sein und nur aus a-z, 0-9, ., _, =, - und / bestehen. - + Password Passwort @@ -1213,7 +1307,7 @@ Beispiel: https://mein.server:8787 Heimserver - + A server that allows registration. Since matrix is decentralized, you need to first find a server you can register on or host your own. Ein Server, der Registrierungen zulässt. Weil Matrix ein dezentralisiertes Protokoll ist, musst du erst einen Server ausfindig machen oder einen persönlichen Server aufsetzen. @@ -1223,22 +1317,27 @@ Beispiel: https://mein.server:8787 REGISTRIEREN - + No supported registration flows! Keine unterstützten Registrierungsmethoden! - - One or more fields have invalid inputs. Please correct those issues and try again. - Mindestens ein Feld hat invalide Werte. Bitte behebe diese Fehler und versuche es erneut. + + Registration token + - + + Please enter a valid registration token. + + + + Autodiscovery failed. Received malformed response. Automatische Erkennung fehlgeschlagen. Antwort war fehlerhaft. - + Autodiscovery failed. Unknown error when requesting .well-known. Automatische Erkennung fehlgeschlagen. Unbekannter Fehler bei Anfrage .well-known. @@ -1248,7 +1347,7 @@ Beispiel: https://mein.server:8787 Benötigte Ansprechpunkte nicht auffindbar. Möglicherweise kein Matrixserver. - + Received malformed response. Make sure the homeserver domain is valid. Erhaltene Antwort war fehlerhaft. Bitte Homeserverdomain prüfen. @@ -1258,17 +1357,17 @@ Beispiel: https://mein.server:8787 Ein unbekannter Fehler ist aufgetreten. Bitte Homeserverdomain prüfen. - + Password is not long enough (min 8 chars) Passwort nicht lang genug (mind. 8 Zeichen) - + Passwords don't match Passwörter stimmen nicht überein - + Invalid server name Ungültiger Servername @@ -1276,7 +1375,7 @@ Beispiel: https://mein.server:8787 ReplyPopup - + Close Schließen @@ -1289,7 +1388,7 @@ Beispiel: https://mein.server:8787 RoomInfo - + no version stored keine Version gespeichert @@ -1297,7 +1396,7 @@ Beispiel: https://mein.server:8787 RoomList - + New tag Neuer Tag @@ -1347,7 +1446,7 @@ Beispiel: https://mein.server:8787 Neuen Tag erstellen... - + Status Message Statusnachricht @@ -1367,7 +1466,7 @@ Beispiel: https://mein.server:8787 Setze eine Statusnachricht - + Logout Abmelden @@ -1400,7 +1499,7 @@ Beispiel: https://mein.server:8787 RoomMembers - + Members of %1 Teilnehmer in %1 @@ -1418,11 +1517,31 @@ Beispiel: https://mein.server:8787 Invite more people Lade mehr Leute ein + + + This room is not encrypted! + + + + + This user is verified. + + + + + This user isn't verified, but is still using the same master key from the first time you met. + + + + + This user has unverified devices! + + RoomSettings - + Room Settings Raumeinstellungen @@ -1457,7 +1576,12 @@ Beispiel: https://mein.server:8787 Alle Nachrichten - + + Room access + + + + Anyone and guests Jeder (inkl. Gäste) @@ -1621,6 +1745,30 @@ Beispiel: https://mein.server:8787 Abbrechen + + SingleImagePackModel + + + + Failed to update image pack: {} + + + + + Failed to delete old image pack: {} + + + + + Failed to open image: {} + + + + + Failed to upload image: {} + + + StatusIndicator @@ -1673,7 +1821,7 @@ Beispiel: https://mein.server:8787 TimelineModel - + Message redaction failed: %1 Nachricht zurückziehen fehlgeschlagen: %1 @@ -1704,7 +1852,7 @@ Beispiel: https://mein.server:8787 Datei speichern - + %1 and %2 are typing. Multiple users are typing. First argument is a comma separated list of potentially multiple users. Second argument is the last user of that list. (If only one user is typing, %1 is empty. You should still use it in your string though to silence Qt warnings.) @@ -1818,12 +1966,12 @@ Beispiel: https://mein.server:8787 %1 hat das Anklopfen zurückgezogen. - + You joined this room. Du bist dem Raum beigetreten. - + %1 has changed their avatar and changed their display name to %2. %1 hat den eigenen Avatar und Namen geändert zu %2. @@ -1852,7 +2000,7 @@ Beispiel: https://mein.server:8787 TimelineRow - + Edited Bearbeitet @@ -1860,12 +2008,12 @@ Beispiel: https://mein.server:8787 TimelineView - + No room open Kein Raum geöffnet - + %1 member(s) %1 Teilnehmer @@ -1893,7 +2041,7 @@ Beispiel: https://mein.server:8787 TimelineViewManager - + No encrypted private chat found with this user. Create an encrypted private chat with this user and try again. Keinen verschlüsselten Chat mit diesem User gefunden. Erstelle einen verschlüsselten 1:1 Chat mit diesem Nutzer und versuche es erneut. @@ -1901,17 +2049,37 @@ Beispiel: https://mein.server:8787 TopBar - + Back to room list Zurück zur Raumliste - + No room selected Kein Raum ausgewählt - + + This room is not encrypted! + + + + + This room contains only verified devices. + + + + + This rooms contain verified devices and devices which have never changed their master key. + + + + + This room contains unverified devices! + + + + Room options Raumoptionen @@ -1952,7 +2120,7 @@ Beispiel: https://mein.server:8787 UserProfile - + Global User Profile Globales Nutzerprofil @@ -1962,7 +2130,7 @@ Beispiel: https://mein.server:8787 Raumspezifisches Nutzerprofil - + Verify Verifizieren @@ -2011,8 +2179,8 @@ Beispiel: https://mein.server:8787 UserSettings - - + + Default Standard @@ -2020,7 +2188,7 @@ Beispiel: https://mein.server:8787 UserSettingsPage - + Minimize to tray Ins Benachrichtigungsfeld minimieren @@ -2040,12 +2208,12 @@ Beispiel: https://mein.server:8787 Runde Profilbilder - + profile: %1 Profil: %1 - + Default Standard @@ -2241,12 +2409,27 @@ Normalerweise animiert das den Taskbaricon oder färbt das Fenster orange ein.Erhöht die Schriftgröße, wenn die Nachricht nur aus ein paar Emoji besteht. - + + Send encrypted messages to verified users only + + + + + Requires a user to be verified to send encrypted messages to them. This improves safety but makes E2EE more tedious. + + + + Share keys with verified users and devices Teile Schlüssel mit verifizierten Nutzern und Geräten - + + Automatically replies to key requests from other users, if they are verified, even if that device shouldn't have access to those keys otherwise. + + + + CACHED IM CACHE @@ -2256,7 +2439,7 @@ Normalerweise animiert das den Taskbaricon oder färbt das Fenster orange ein.NICHT IM CACHE - + Scale factor Skalierungsfaktor @@ -2351,12 +2534,12 @@ Normalerweise animiert das den Taskbaricon oder färbt das Fenster orange ein.VERSCHLÜSSELUNG - + GENERAL ALLGEMEINES - + INTERFACE OBERFLÄCHE @@ -2376,12 +2559,7 @@ Normalerweise animiert das den Taskbaricon oder färbt das Fenster orange ein.Emojischriftart - - Automatically replies to key requests from other users, if they are verified. - Antortet automatsch auf Schlüsselanfragen von anderen Nutzern, wenn du diese verifiziert hast. - - - + Master signing key Masterverifizierungsschlüssel @@ -2431,7 +2609,7 @@ Normalerweise animiert das den Taskbaricon oder färbt das Fenster orange ein.Alle Dateien (*) - + Open Sessions File Öffne Sessions Datei @@ -2682,32 +2860,6 @@ Medien-Größe: %2 Löse das reCAPTCHA und drücke den "Bestätigen"-Knopf - - dialogs::ReadReceipts - - - Read receipts - Lesebestätigungen - - - - Close - Schließen - - - - dialogs::ReceiptItem - - - Today %1 - Heute %1 - - - - Yesterday %1 - Gestern %1 - - message-description sent: diff --git a/resources/langs/nheko_el.ts b/resources/langs/nheko_el.ts index d40a6433..9866b563 100644 --- a/resources/langs/nheko_el.ts +++ b/resources/langs/nheko_el.ts @@ -56,7 +56,7 @@ CallInvite - + Video Call @@ -125,7 +125,7 @@ ChatPage - + Failed to invite user: %1 @@ -157,12 +157,12 @@ - + Confirm invite - + Do you really want to invite %1 (%2)? @@ -227,12 +227,12 @@ - + Do you really want to start a private chat with %1? - + Cache migration failed! @@ -367,7 +367,7 @@ - + Decryption failed @@ -494,6 +494,49 @@ + + Encrypted + + + There is no key to unlock this message. We requested the key automatically, but you can try requesting it again if you are impatient. + + + + + This message couldn't be decrypted, because we only have a key for newer messages. You can try requesting access to this message. + + + + + There was an internal error reading the decryption key from the database. + + + + + There was an error decrypting this message. + + + + + The message couldn't be parsed. + + + + + The encryption key was reused! Someone is possibly trying to insert false messages into this chat! + + + + + Unknown decryption error + + + + + Request key + + + EncryptionIndicator @@ -517,51 +560,6 @@ - - EventStore - - - -- Encrypted Event (No keys found for decryption) -- - Placeholder, when the message was not decrypted yet or can't be decrypted. - - - - - -- Encrypted Event (Key not valid for this index) -- - Placeholder, when the message can't be decrypted with this key since it is not valid for this index - - - - - - -- Decryption Error (failed to retrieve megolm keys from db) -- - Placeholder, when the message can't be decrypted, because the DB access failed. - - - - - - -- Decryption Error (%1) -- - Placeholder, when the message can't be decrypted. In this case, the Olm decrytion returned an error, which is passed as %1. - - - - - -- Encrypted Event (Unknown event type) -- - Placeholder, when the message was decrypted, but we couldn't parse it, because Nheko/mtxclient don't support that event type yet. - - - - - -- Replay attack! This message index was reused! -- - - - - - -- Message by unverified device! -- - - - Failed @@ -604,6 +602,71 @@ + + ImagePackEditorDialog + + + Editing image pack + + + + + Add images + + + + + Stickers (*.png *.webp) + + + + + State key + + + + + Packname + + + + + Attribution + + + + + + Use as Emoji + + + + + + Use as Sticker + + + + + Shortcode + + + + + Body + + + + + Cancel + Άκυρο + + + + Save + + + ImagePackSettingsDialog @@ -612,7 +675,17 @@ - + + Create account pack + + + + + New room pack + + + + Private pack @@ -627,7 +700,7 @@ - + Enable globally @@ -637,7 +710,12 @@ - + + Edit + + + + Close @@ -807,7 +885,7 @@ Example: https://server.my:8787 MessageDelegate - + removed @@ -818,7 +896,7 @@ Example: https://server.my:8787 - + room name changed to: %1 @@ -901,7 +979,7 @@ Example: https://server.my:8787 Γράψε ένα μήνυμα... - + Stickers @@ -924,7 +1002,7 @@ Example: https://server.my:8787 MessageView - + Edit @@ -944,7 +1022,7 @@ Example: https://server.my:8787 - + &Copy @@ -1175,21 +1253,37 @@ Example: https://server.my:8787 + + ReadReceipts + + + Read receipts + + + + + ReadReceiptsModel + + + Yesterday, %1 + + + RegisterPage - + Username Όνομα χρήστη - + The username must not be empty, and must contain only the characters a-z, 0-9, ., _, =, -, and /. - + Password Κωδικός @@ -1209,7 +1303,7 @@ Example: https://server.my:8787 - + A server that allows registration. Since matrix is decentralized, you need to first find a server you can register on or host your own. @@ -1219,22 +1313,27 @@ Example: https://server.my:8787 ΕΓΓΡΑΦΗ - + No supported registration flows! - - One or more fields have invalid inputs. Please correct those issues and try again. + + Registration token - + + Please enter a valid registration token. + + + + Autodiscovery failed. Received malformed response. - + Autodiscovery failed. Unknown error when requesting .well-known. @@ -1244,7 +1343,7 @@ Example: https://server.my:8787 - + Received malformed response. Make sure the homeserver domain is valid. @@ -1254,17 +1353,17 @@ Example: https://server.my:8787 - + Password is not long enough (min 8 chars) Ο κωδικός δεν αποτελείται από αρκετους χαρακτήρες - + Passwords don't match Οι κωδικοί δεν ταιριίαζουν - + Invalid server name Λανθασμένο όνομα διακομιστή @@ -1272,7 +1371,7 @@ Example: https://server.my:8787 ReplyPopup - + Close @@ -1285,7 +1384,7 @@ Example: https://server.my:8787 RoomInfo - + no version stored @@ -1293,7 +1392,7 @@ Example: https://server.my:8787 RoomList - + New tag @@ -1343,7 +1442,7 @@ Example: https://server.my:8787 - + Status Message @@ -1363,7 +1462,7 @@ Example: https://server.my:8787 - + Logout @@ -1396,7 +1495,7 @@ Example: https://server.my:8787 RoomMembers - + Members of %1 @@ -1414,11 +1513,31 @@ Example: https://server.my:8787 Invite more people + + + This room is not encrypted! + + + + + This user is verified. + + + + + This user isn't verified, but is still using the same master key from the first time you met. + + + + + This user has unverified devices! + + RoomSettings - + Room Settings @@ -1453,7 +1572,12 @@ Example: https://server.my:8787 - + + Room access + + + + Anyone and guests @@ -1617,6 +1741,30 @@ Example: https://server.my:8787 Άκυρο + + SingleImagePackModel + + + + Failed to update image pack: {} + + + + + Failed to delete old image pack: {} + + + + + Failed to open image: {} + + + + + Failed to upload image: {} + + + StatusIndicator @@ -1669,7 +1817,7 @@ Example: https://server.my:8787 TimelineModel - + Message redaction failed: %1 @@ -1700,7 +1848,7 @@ Example: https://server.my:8787 - + %1 and %2 are typing. Multiple users are typing. First argument is a comma separated list of potentially multiple users. Second argument is the last user of that list. (If only one user is typing, %1 is empty. You should still use it in your string though to silence Qt warnings.) @@ -1814,12 +1962,12 @@ Example: https://server.my:8787 - + You joined this room. - + %1 has changed their avatar and changed their display name to %2. @@ -1848,7 +1996,7 @@ Example: https://server.my:8787 TimelineRow - + Edited @@ -1856,12 +2004,12 @@ Example: https://server.my:8787 TimelineView - + No room open - + %1 member(s) @@ -1889,7 +2037,7 @@ Example: https://server.my:8787 TimelineViewManager - + No encrypted private chat found with this user. Create an encrypted private chat with this user and try again. @@ -1897,17 +2045,37 @@ Example: https://server.my:8787 TopBar - + Back to room list - + No room selected - + + This room is not encrypted! + + + + + This room contains only verified devices. + + + + + This rooms contain verified devices and devices which have never changed their master key. + + + + + This room contains unverified devices! + + + + Room options @@ -1948,7 +2116,7 @@ Example: https://server.my:8787 UserProfile - + Global User Profile @@ -1958,7 +2126,7 @@ Example: https://server.my:8787 - + Verify @@ -2007,8 +2175,8 @@ Example: https://server.my:8787 UserSettings - - + + Default @@ -2016,7 +2184,7 @@ Example: https://server.my:8787 UserSettingsPage - + Minimize to tray Ελαχιστοποίηση @@ -2036,12 +2204,12 @@ Example: https://server.my:8787 - + profile: %1 - + Default @@ -2227,12 +2395,27 @@ This usually causes the application icon in the task bar to animate in some fash - + + Send encrypted messages to verified users only + + + + + Requires a user to be verified to send encrypted messages to them. This improves safety but makes E2EE more tedious. + + + + Share keys with verified users and devices - + + Automatically replies to key requests from other users, if they are verified, even if that device shouldn't have access to those keys otherwise. + + + + CACHED @@ -2242,7 +2425,7 @@ This usually causes the application icon in the task bar to animate in some fash - + Scale factor @@ -2337,12 +2520,12 @@ This usually causes the application icon in the task bar to animate in some fash - + GENERAL ΓΕΝΙΚΑ - + INTERFACE @@ -2362,12 +2545,7 @@ This usually causes the application icon in the task bar to animate in some fash - - Automatically replies to key requests from other users, if they are verified. - - - - + Master signing key @@ -2417,7 +2595,7 @@ This usually causes the application icon in the task bar to animate in some fash Όλα τα αρχεία (*) - + Open Sessions File @@ -2666,32 +2844,6 @@ Media size: %2 - - dialogs::ReadReceipts - - - Read receipts - - - - - Close - - - - - dialogs::ReceiptItem - - - Today %1 - - - - - Yesterday %1 - - - message-description sent: diff --git a/resources/langs/nheko_en.ts b/resources/langs/nheko_en.ts index 1851fff1..4feabb84 100644 --- a/resources/langs/nheko_en.ts +++ b/resources/langs/nheko_en.ts @@ -56,7 +56,7 @@ CallInvite - + Video Call Video Call @@ -125,7 +125,7 @@ ChatPage - + Failed to invite user: %1 Failed to invite user: %1 @@ -157,12 +157,12 @@ - + Confirm invite Confirm invite - + Do you really want to invite %1 (%2)? Do you really want to invite %1 (%2)? @@ -227,12 +227,12 @@ Unbanned user: %1 - + Do you really want to start a private chat with %1? Do you really want to start a private chat with %1? - + Cache migration failed! Cache migration failed! @@ -367,7 +367,7 @@ Enter your recovery key or passphrase called %1 to decrypt your secrets: - + Decryption failed Decryption failed @@ -494,6 +494,49 @@ They match! + + Encrypted + + + There is no key to unlock this message. We requested the key automatically, but you can try requesting it again if you are impatient. + + + + + This message couldn't be decrypted, because we only have a key for newer messages. You can try requesting access to this message. + + + + + There was an internal error reading the decryption key from the database. + + + + + There was an error decrypting this message. + + + + + The message couldn't be parsed. + + + + + The encryption key was reused! Someone is possibly trying to insert false messages into this chat! + + + + + Unknown decryption error + + + + + Request key + + + EncryptionIndicator @@ -517,51 +560,6 @@ Encrypted by an unverified device - - EventStore - - - -- Encrypted Event (No keys found for decryption) -- - Placeholder, when the message was not decrypted yet or can't be decrypted. - -- Encrypted Event (No keys found for decryption) -- - - - - -- Encrypted Event (Key not valid for this index) -- - Placeholder, when the message can't be decrypted with this key since it is not valid for this index - -- Encrypted Event (Key not valid for this index) -- - - - - - -- Decryption Error (failed to retrieve megolm keys from db) -- - Placeholder, when the message can't be decrypted, because the DB access failed. - -- Decryption Error (failed to retrieve megolm keys from db) -- - - - - - -- Decryption Error (%1) -- - Placeholder, when the message can't be decrypted. In this case, the Olm decrytion returned an error, which is passed as %1. - -- Decryption Error (%1) -- - - - - -- Encrypted Event (Unknown event type) -- - Placeholder, when the message was decrypted, but we couldn't parse it, because Nheko/mtxclient don't support that event type yet. - -- Encrypted Event (Unknown event type) -- - - - - -- Replay attack! This message index was reused! -- - -- Replay attack! This message index was reused! -- - - - - -- Message by unverified device! -- - -- Message by unverified device! -- - - Failed @@ -604,6 +602,71 @@ Forward Message + + ImagePackEditorDialog + + + Editing image pack + + + + + Add images + + + + + Stickers (*.png *.webp) + + + + + State key + + + + + Packname + + + + + Attribution + + + + + + Use as Emoji + + + + + + Use as Sticker + + + + + Shortcode + + + + + Body + + + + + Cancel + Cancel + + + + Save + + + ImagePackSettingsDialog @@ -612,7 +675,17 @@ - + + Create account pack + + + + + New room pack + + + + Private pack @@ -627,7 +700,7 @@ - + Enable globally @@ -637,7 +710,12 @@ - + + Edit + Edit + + + Close Close @@ -811,12 +889,12 @@ Example: https://server.my:8787 MessageDelegate - + Encryption enabled Encryption enabled - + room name changed to: %1 room name changed to: %1 @@ -871,13 +949,13 @@ Example: https://server.my:8787 %1 answered the call. - + removed removed - + %1 ended the call. %1 ended the call. @@ -905,7 +983,7 @@ Example: https://server.my:8787 Write a message… - + Stickers Stickers @@ -928,7 +1006,7 @@ Example: https://server.my:8787 MessageView - + Edit Edit @@ -948,7 +1026,7 @@ Example: https://server.my:8787 Options - + &Copy &Copy @@ -1179,21 +1257,37 @@ Example: https://server.my:8787 profile name + + ReadReceipts + + + Read receipts + Read receipts + + + + ReadReceiptsModel + + + Yesterday, %1 + + + RegisterPage - + Username Username - + The username must not be empty, and must contain only the characters a-z, 0-9, ., _, =, -, and /. The username must not be empty, and must contain only the characters a-z, 0-9, ., _, =, -, and /. - + Password Password @@ -1213,7 +1307,7 @@ Example: https://server.my:8787 Homeserver - + A server that allows registration. Since matrix is decentralized, you need to first find a server you can register on or host your own. A server that allows registration. Since matrix is decentralized, you need to first find a server you can register on or host your own. @@ -1223,22 +1317,27 @@ Example: https://server.my:8787 REGISTER - + No supported registration flows! No supported registration flows! - - One or more fields have invalid inputs. Please correct those issues and try again. - One or more fields have invalid inputs. Please correct those issues and try again. + + Registration token + - + + Please enter a valid registration token. + + + + Autodiscovery failed. Received malformed response. Autodiscovery failed. Received malformed response. - + Autodiscovery failed. Unknown error when requesting .well-known. Autodiscovery failed. Unknown error while requesting .well-known. @@ -1248,7 +1347,7 @@ Example: https://server.my:8787 The required endpoints were not found. Possibly not a Matrix server. - + Received malformed response. Make sure the homeserver domain is valid. Received malformed response. Make sure the homeserver domain is valid. @@ -1258,17 +1357,17 @@ Example: https://server.my:8787 An unknown error occured. Make sure the homeserver domain is valid. - + Password is not long enough (min 8 chars) Password is not long enough (min 8 chars) - + Passwords don't match Passwords don't match - + Invalid server name Invalid server name @@ -1276,7 +1375,7 @@ Example: https://server.my:8787 ReplyPopup - + Close Close @@ -1289,7 +1388,7 @@ Example: https://server.my:8787 RoomInfo - + no version stored no version stored @@ -1297,7 +1396,7 @@ Example: https://server.my:8787 RoomList - + New tag New tag @@ -1347,7 +1446,7 @@ Example: https://server.my:8787 Create new tag... - + Status Message Status Message @@ -1367,7 +1466,7 @@ Example: https://server.my:8787 Set status message - + Logout Logout @@ -1400,7 +1499,7 @@ Example: https://server.my:8787 RoomMembers - + Members of %1 Members of %1 @@ -1418,11 +1517,31 @@ Example: https://server.my:8787 Invite more people Invite more people + + + This room is not encrypted! + + + + + This user is verified. + + + + + This user isn't verified, but is still using the same master key from the first time you met. + + + + + This user has unverified devices! + + RoomSettings - + Room Settings Room Settings @@ -1457,7 +1576,12 @@ Example: https://server.my:8787 All messages - + + Room access + + + + Anyone and guests Anyone and guests @@ -1621,6 +1745,30 @@ Example: https://server.my:8787 Cancel + + SingleImagePackModel + + + + Failed to update image pack: {} + + + + + Failed to delete old image pack: {} + + + + + Failed to open image: {} + + + + + Failed to upload image: {} + + + StatusIndicator @@ -1673,7 +1821,7 @@ Example: https://server.my:8787 TimelineModel - + Message redaction failed: %1 Message redaction failed: %1 @@ -1704,7 +1852,7 @@ Example: https://server.my:8787 Save file - + %1 and %2 are typing. Multiple users are typing. First argument is a comma separated list of potentially multiple users. Second argument is the last user of that list. (If only one user is typing, %1 is empty. You should still use it in your string though to silence Qt warnings.) @@ -1818,12 +1966,12 @@ Example: https://server.my:8787 %1 redacted their knock. - + You joined this room. You joined this room. - + %1 has changed their avatar and changed their display name to %2. %1 has changed their avatar and changed their display name to %2. @@ -1852,7 +2000,7 @@ Example: https://server.my:8787 TimelineRow - + Edited Edited @@ -1860,12 +2008,12 @@ Example: https://server.my:8787 TimelineView - + No room open No room open - + %1 member(s) %1 member(s) @@ -1893,7 +2041,7 @@ Example: https://server.my:8787 TimelineViewManager - + No encrypted private chat found with this user. Create an encrypted private chat with this user and try again. No encrypted private chat found with this user. Create an encrypted private chat with this user and try again. @@ -1901,17 +2049,37 @@ Example: https://server.my:8787 TopBar - + Back to room list Back to room list - + No room selected No room selected - + + This room is not encrypted! + + + + + This room contains only verified devices. + + + + + This rooms contain verified devices and devices which have never changed their master key. + + + + + This room contains unverified devices! + + + + Room options Room options @@ -1952,7 +2120,7 @@ Example: https://server.my:8787 UserProfile - + Global User Profile Global User Profile @@ -1962,7 +2130,7 @@ Example: https://server.my:8787 Room User Profile - + Verify Verify @@ -2011,8 +2179,8 @@ Example: https://server.my:8787 UserSettings - - + + Default Default @@ -2020,7 +2188,7 @@ Example: https://server.my:8787 UserSettingsPage - + Minimize to tray Minimize to tray @@ -2040,12 +2208,12 @@ Example: https://server.my:8787 Circular Avatars - + profile: %1 profile: %1 - + Default Default @@ -2242,12 +2410,27 @@ This usually causes the application icon in the task bar to animate in some fash Make font size larger if messages with only a few emojis are displayed. - + + Send encrypted messages to verified users only + + + + + Requires a user to be verified to send encrypted messages to them. This improves safety but makes E2EE more tedious. + + + + Share keys with verified users and devices Share keys with verified users and devices - + + Automatically replies to key requests from other users, if they are verified, even if that device shouldn't have access to those keys otherwise. + + + + CACHED CACHED @@ -2257,7 +2440,7 @@ This usually causes the application icon in the task bar to animate in some fash NOT CACHED - + Scale factor Scale factor @@ -2352,12 +2535,12 @@ This usually causes the application icon in the task bar to animate in some fash ENCRYPTION - + GENERAL GENERAL - + INTERFACE INTERFACE @@ -2377,12 +2560,7 @@ This usually causes the application icon in the task bar to animate in some fash Emoji Font Family - - Automatically replies to key requests from other users, if they are verified. - Automatically replies to key requests from other users, if they are verified. - - - + Master signing key Master signing key @@ -2432,7 +2610,7 @@ This usually causes the application icon in the task bar to animate in some fash All Files (*) - + Open Sessions File Open Sessions File @@ -2683,32 +2861,6 @@ Media size: %2 Solve the reCAPTCHA and press the confirm button - - dialogs::ReadReceipts - - - Read receipts - Read receipts - - - - Close - Close - - - - dialogs::ReceiptItem - - - Today %1 - Today %1 - - - - Yesterday %1 - Yesterday %1 - - message-description sent: diff --git a/resources/langs/nheko_eo.ts b/resources/langs/nheko_eo.ts index a77c5c25..74364e52 100644 --- a/resources/langs/nheko_eo.ts +++ b/resources/langs/nheko_eo.ts @@ -56,7 +56,7 @@ CallInvite - + Video Call Vidvoko @@ -125,7 +125,7 @@ ChatPage - + Failed to invite user: %1 Malsukcesis inviti uzanton: %1 @@ -158,12 +158,12 @@ - + Confirm invite Konfirmu inviton - + Do you really want to invite %1 (%2)? Ĉu vi certe volas inviti uzanton %1 (%2)? @@ -228,12 +228,12 @@ Malforbaris uzanton: %1 - + Do you really want to start a private chat with %1? Ĉu vi certe volas komenci privatan babilon kun %1? - + Cache migration failed! Malsukcesis migrado de kaŝmemoro! @@ -368,7 +368,7 @@ - + Decryption failed Malsukcesis malĉifrado @@ -495,6 +495,49 @@ Ili akordas! + + Encrypted + + + There is no key to unlock this message. We requested the key automatically, but you can try requesting it again if you are impatient. + + + + + This message couldn't be decrypted, because we only have a key for newer messages. You can try requesting access to this message. + + + + + There was an internal error reading the decryption key from the database. + + + + + There was an error decrypting this message. + + + + + The message couldn't be parsed. + + + + + The encryption key was reused! Someone is possibly trying to insert false messages into this chat! + + + + + Unknown decryption error + + + + + Request key + + + EncryptionIndicator @@ -518,51 +561,6 @@ Ĉifrita de nekontrolita aparato - - EventStore - - - -- Encrypted Event (No keys found for decryption) -- - Placeholder, when the message was not decrypted yet or can't be decrypted. - - - - - -- Encrypted Event (Key not valid for this index) -- - Placeholder, when the message can't be decrypted with this key since it is not valid for this index - - - - - - -- Decryption Error (failed to retrieve megolm keys from db) -- - Placeholder, when the message can't be decrypted, because the DB access failed. - - - - - - -- Decryption Error (%1) -- - Placeholder, when the message can't be decrypted. In this case, the Olm decrytion returned an error, which is passed as %1. - - - - - -- Encrypted Event (Unknown event type) -- - Placeholder, when the message was decrypted, but we couldn't parse it, because Nheko/mtxclient don't support that event type yet. - - - - - -- Replay attack! This message index was reused! -- - - - - - -- Message by unverified device! -- - - - Failed @@ -605,6 +603,71 @@ + + ImagePackEditorDialog + + + Editing image pack + + + + + Add images + + + + + Stickers (*.png *.webp) + + + + + State key + + + + + Packname + + + + + Attribution + + + + + + Use as Emoji + + + + + + Use as Sticker + + + + + Shortcode + + + + + Body + + + + + Cancel + Nuligi + + + + Save + + + ImagePackSettingsDialog @@ -613,7 +676,17 @@ - + + Create account pack + + + + + New room pack + + + + Private pack @@ -628,7 +701,7 @@ - + Enable globally @@ -638,7 +711,12 @@ - + + Edit + Redakti + + + Close Fermi @@ -814,7 +892,7 @@ Ekzemplo: https://servilo.mia:8787 MessageDelegate - + removed forigita @@ -825,7 +903,7 @@ Ekzemplo: https://servilo.mia:8787 - + room name changed to: %1 Nomo da ĉambro ŝanĝiĝis al: %1 @@ -908,7 +986,7 @@ Ekzemplo: https://servilo.mia:8787 Skribu mesaĝon… - + Stickers @@ -931,7 +1009,7 @@ Ekzemplo: https://servilo.mia:8787 MessageView - + Edit Redakti @@ -951,7 +1029,7 @@ Ekzemplo: https://servilo.mia:8787 Elektebloj - + &Copy @@ -1182,21 +1260,37 @@ Ekzemplo: https://servilo.mia:8787 nomo de profilo + + ReadReceipts + + + Read receipts + Kvitancoj + + + + ReadReceiptsModel + + + Yesterday, %1 + + + RegisterPage - + Username Uzantonomo - + The username must not be empty, and must contain only the characters a-z, 0-9, ., _, =, -, and /. La uzantonomo devas ne esti malplena, kaj devas enhavi nur la signojn a–z, 0–9, ., _, =, -, kaj /. - + Password Pasvorto @@ -1216,7 +1310,7 @@ Ekzemplo: https://servilo.mia:8787 Hejmservilo - + A server that allows registration. Since matrix is decentralized, you need to first find a server you can register on or host your own. Servilo, kiu permesas registriĝon. Ĉar Matrikso estas federa, vi bezonas unue trovi servilon, kie vi povus registriĝi, aŭ gastigi vian propran. @@ -1226,22 +1320,27 @@ Ekzemplo: https://servilo.mia:8787 REGISTRIĜI - + No supported registration flows! Neniuj subtenataj manieroj de registriĝo! - - One or more fields have invalid inputs. Please correct those issues and try again. - Unu aŭ pliaj kampoj havas nevalidajn enigojn. Bonvolu korekti la problemojn kaj reprovi. + + Registration token + - + + Please enter a valid registration token. + + + + Autodiscovery failed. Received malformed response. - + Autodiscovery failed. Unknown error when requesting .well-known. @@ -1251,7 +1350,7 @@ Ekzemplo: https://servilo.mia:8787 - + Received malformed response. Make sure the homeserver domain is valid. @@ -1261,17 +1360,17 @@ Ekzemplo: https://servilo.mia:8787 - + Password is not long enough (min 8 chars) Pasvorto nesufiĉe longas (almenaŭ 8 signoj) - + Passwords don't match Pasvortoj ne akordas - + Invalid server name Nevalida nomo de servilo @@ -1279,7 +1378,7 @@ Ekzemplo: https://servilo.mia:8787 ReplyPopup - + Close Fermi @@ -1292,7 +1391,7 @@ Ekzemplo: https://servilo.mia:8787 RoomInfo - + no version stored @@ -1300,7 +1399,7 @@ Ekzemplo: https://servilo.mia:8787 RoomList - + New tag @@ -1350,7 +1449,7 @@ Ekzemplo: https://servilo.mia:8787 - + Status Message @@ -1370,7 +1469,7 @@ Ekzemplo: https://servilo.mia:8787 - + Logout Adiaŭi @@ -1403,7 +1502,7 @@ Ekzemplo: https://servilo.mia:8787 RoomMembers - + Members of %1 @@ -1421,11 +1520,31 @@ Ekzemplo: https://servilo.mia:8787 Invite more people + + + This room is not encrypted! + + + + + This user is verified. + + + + + This user isn't verified, but is still using the same master key from the first time you met. + + + + + This user has unverified devices! + + RoomSettings - + Room Settings Agordoj de ĉambro @@ -1460,7 +1579,12 @@ Ekzemplo: https://servilo.mia:8787 Ĉiuj mesaĝoj - + + Room access + + + + Anyone and guests Ĉiu ajn, inkluzive gastojn @@ -1624,6 +1748,30 @@ Ekzemplo: https://servilo.mia:8787 Nuligi + + SingleImagePackModel + + + + Failed to update image pack: {} + + + + + Failed to delete old image pack: {} + + + + + Failed to open image: {} + + + + + Failed to upload image: {} + + + StatusIndicator @@ -1677,7 +1825,7 @@ Ekzemplo: https://servilo.mia:8787 TimelineModel - + Message redaction failed: %1 @@ -1708,7 +1856,7 @@ Ekzemplo: https://servilo.mia:8787 Konservi dosieron - + %1 and %2 are typing. Multiple users are typing. First argument is a comma separated list of potentially multiple users. Second argument is the last user of that list. (If only one user is typing, %1 is empty. You should still use it in your string though to silence Qt warnings.) @@ -1824,12 +1972,12 @@ Ekzemplo: https://servilo.mia:8787 - + You joined this room. Vi aliĝis ĉi tiun ĉambron. - + %1 has changed their avatar and changed their display name to %2. @@ -1858,7 +2006,7 @@ Ekzemplo: https://servilo.mia:8787 TimelineRow - + Edited Redaktita @@ -1866,12 +2014,12 @@ Ekzemplo: https://servilo.mia:8787 TimelineView - + No room open - + %1 member(s) %1 ĉambrano(j) @@ -1899,7 +2047,7 @@ Ekzemplo: https://servilo.mia:8787 TimelineViewManager - + No encrypted private chat found with this user. Create an encrypted private chat with this user and try again. @@ -1907,17 +2055,37 @@ Ekzemplo: https://servilo.mia:8787 TopBar - + Back to room list - + No room selected - + + This room is not encrypted! + + + + + This room contains only verified devices. + + + + + This rooms contain verified devices and devices which have never changed their master key. + + + + + This room contains unverified devices! + + + + Room options @@ -1958,7 +2126,7 @@ Ekzemplo: https://servilo.mia:8787 UserProfile - + Global User Profile @@ -1968,7 +2136,7 @@ Ekzemplo: https://servilo.mia:8787 - + Verify @@ -2017,8 +2185,8 @@ Ekzemplo: https://servilo.mia:8787 UserSettings - - + + Default @@ -2026,7 +2194,7 @@ Ekzemplo: https://servilo.mia:8787 UserSettingsPage - + Minimize to tray @@ -2046,12 +2214,12 @@ Ekzemplo: https://servilo.mia:8787 - + profile: %1 - + Default @@ -2254,12 +2422,27 @@ This usually causes the application icon in the task bar to animate in some fash - + + Send encrypted messages to verified users only + + + + + Requires a user to be verified to send encrypted messages to them. This improves safety but makes E2EE more tedious. + + + + Share keys with verified users and devices - + + Automatically replies to key requests from other users, if they are verified, even if that device shouldn't have access to those keys otherwise. + + + + CACHED @@ -2269,7 +2452,7 @@ This usually causes the application icon in the task bar to animate in some fash - + Scale factor @@ -2364,12 +2547,12 @@ This usually causes the application icon in the task bar to animate in some fash - + GENERAL - + INTERFACE @@ -2389,12 +2572,7 @@ This usually causes the application icon in the task bar to animate in some fash - - Automatically replies to key requests from other users, if they are verified. - - - - + Master signing key @@ -2444,7 +2622,7 @@ This usually causes the application icon in the task bar to animate in some fash Ĉiuj dosieroj (*) - + Open Sessions File @@ -2694,32 +2872,6 @@ Media size: %2 - - dialogs::ReadReceipts - - - Read receipts - Kvitancoj - - - - Close - Fermi - - - - dialogs::ReceiptItem - - - Today %1 - Hodiaŭ %1 - - - - Yesterday %1 - Hieraŭ %1 - - message-description sent: diff --git a/resources/langs/nheko_es.ts b/resources/langs/nheko_es.ts index 922e0500..cb4a6c1d 100644 --- a/resources/langs/nheko_es.ts +++ b/resources/langs/nheko_es.ts @@ -56,7 +56,7 @@ CallInvite - + Video Call Videollamada @@ -125,7 +125,7 @@ ChatPage - + Failed to invite user: %1 No se pudo invitar al usuario: %1 @@ -157,12 +157,12 @@ - + Confirm invite Confirmar invitación - + Do you really want to invite %1 (%2)? @@ -227,12 +227,12 @@ - + Do you really want to start a private chat with %1? - + Cache migration failed! @@ -367,7 +367,7 @@ - + Decryption failed @@ -494,6 +494,49 @@ + + Encrypted + + + There is no key to unlock this message. We requested the key automatically, but you can try requesting it again if you are impatient. + + + + + This message couldn't be decrypted, because we only have a key for newer messages. You can try requesting access to this message. + + + + + There was an internal error reading the decryption key from the database. + + + + + There was an error decrypting this message. + + + + + The message couldn't be parsed. + + + + + The encryption key was reused! Someone is possibly trying to insert false messages into this chat! + + + + + Unknown decryption error + + + + + Request key + + + EncryptionIndicator @@ -517,51 +560,6 @@ - - EventStore - - - -- Encrypted Event (No keys found for decryption) -- - Placeholder, when the message was not decrypted yet or can't be decrypted. - - - - - -- Encrypted Event (Key not valid for this index) -- - Placeholder, when the message can't be decrypted with this key since it is not valid for this index - - - - - - -- Decryption Error (failed to retrieve megolm keys from db) -- - Placeholder, when the message can't be decrypted, because the DB access failed. - - - - - - -- Decryption Error (%1) -- - Placeholder, when the message can't be decrypted. In this case, the Olm decrytion returned an error, which is passed as %1. - - - - - -- Encrypted Event (Unknown event type) -- - Placeholder, when the message was decrypted, but we couldn't parse it, because Nheko/mtxclient don't support that event type yet. - - - - - -- Replay attack! This message index was reused! -- - - - - - -- Message by unverified device! -- - - - Failed @@ -604,6 +602,71 @@ + + ImagePackEditorDialog + + + Editing image pack + + + + + Add images + + + + + Stickers (*.png *.webp) + + + + + State key + + + + + Packname + + + + + Attribution + + + + + + Use as Emoji + + + + + + Use as Sticker + + + + + Shortcode + + + + + Body + + + + + Cancel + Cancelar + + + + Save + + + ImagePackSettingsDialog @@ -612,7 +675,17 @@ - + + Create account pack + + + + + New room pack + + + + Private pack @@ -627,7 +700,7 @@ - + Enable globally @@ -637,7 +710,12 @@ - + + Edit + + + + Close @@ -807,12 +885,12 @@ Example: https://server.my:8787 MessageDelegate - + Encryption enabled - + room name changed to: %1 @@ -867,13 +945,13 @@ Example: https://server.my:8787 - + removed - + %1 ended the call. @@ -901,7 +979,7 @@ Example: https://server.my:8787 - + Stickers @@ -924,7 +1002,7 @@ Example: https://server.my:8787 MessageView - + Edit @@ -944,7 +1022,7 @@ Example: https://server.my:8787 - + &Copy @@ -1175,21 +1253,37 @@ Example: https://server.my:8787 + + ReadReceipts + + + Read receipts + + + + + ReadReceiptsModel + + + Yesterday, %1 + + + RegisterPage - + Username - + The username must not be empty, and must contain only the characters a-z, 0-9, ., _, =, -, and /. - + Password @@ -1209,7 +1303,7 @@ Example: https://server.my:8787 - + A server that allows registration. Since matrix is decentralized, you need to first find a server you can register on or host your own. @@ -1219,22 +1313,27 @@ Example: https://server.my:8787 - + No supported registration flows! - - One or more fields have invalid inputs. Please correct those issues and try again. + + Registration token - + + Please enter a valid registration token. + + + + Autodiscovery failed. Received malformed response. - + Autodiscovery failed. Unknown error when requesting .well-known. @@ -1244,7 +1343,7 @@ Example: https://server.my:8787 - + Received malformed response. Make sure the homeserver domain is valid. @@ -1254,17 +1353,17 @@ Example: https://server.my:8787 - + Password is not long enough (min 8 chars) - + Passwords don't match - + Invalid server name @@ -1272,7 +1371,7 @@ Example: https://server.my:8787 ReplyPopup - + Close @@ -1285,7 +1384,7 @@ Example: https://server.my:8787 RoomInfo - + no version stored @@ -1293,7 +1392,7 @@ Example: https://server.my:8787 RoomList - + New tag @@ -1343,7 +1442,7 @@ Example: https://server.my:8787 - + Status Message @@ -1363,7 +1462,7 @@ Example: https://server.my:8787 - + Logout @@ -1396,7 +1495,7 @@ Example: https://server.my:8787 RoomMembers - + Members of %1 @@ -1414,11 +1513,31 @@ Example: https://server.my:8787 Invite more people + + + This room is not encrypted! + + + + + This user is verified. + + + + + This user isn't verified, but is still using the same master key from the first time you met. + + + + + This user has unverified devices! + + RoomSettings - + Room Settings @@ -1453,7 +1572,12 @@ Example: https://server.my:8787 - + + Room access + + + + Anyone and guests @@ -1617,6 +1741,30 @@ Example: https://server.my:8787 Cancelar + + SingleImagePackModel + + + + Failed to update image pack: {} + + + + + Failed to delete old image pack: {} + + + + + Failed to open image: {} + + + + + Failed to upload image: {} + + + StatusIndicator @@ -1669,7 +1817,7 @@ Example: https://server.my:8787 TimelineModel - + Message redaction failed: %1 @@ -1700,7 +1848,7 @@ Example: https://server.my:8787 - + %1 and %2 are typing. Multiple users are typing. First argument is a comma separated list of potentially multiple users. Second argument is the last user of that list. (If only one user is typing, %1 is empty. You should still use it in your string though to silence Qt warnings.) @@ -1824,12 +1972,12 @@ Example: https://server.my:8787 - + You joined this room. Te has unido a esta sala. - + Rejected the knock from %1. @@ -1848,7 +1996,7 @@ Example: https://server.my:8787 TimelineRow - + Edited @@ -1856,12 +2004,12 @@ Example: https://server.my:8787 TimelineView - + No room open - + %1 member(s) @@ -1889,7 +2037,7 @@ Example: https://server.my:8787 TimelineViewManager - + No encrypted private chat found with this user. Create an encrypted private chat with this user and try again. @@ -1897,17 +2045,37 @@ Example: https://server.my:8787 TopBar - + Back to room list - + No room selected - + + This room is not encrypted! + + + + + This room contains only verified devices. + + + + + This rooms contain verified devices and devices which have never changed their master key. + + + + + This room contains unverified devices! + + + + Room options @@ -1948,7 +2116,7 @@ Example: https://server.my:8787 UserProfile - + Global User Profile @@ -1958,7 +2126,7 @@ Example: https://server.my:8787 - + Verify @@ -2007,8 +2175,8 @@ Example: https://server.my:8787 UserSettings - - + + Default @@ -2016,7 +2184,7 @@ Example: https://server.my:8787 UserSettingsPage - + Minimize to tray @@ -2036,12 +2204,12 @@ Example: https://server.my:8787 - + profile: %1 - + Default @@ -2227,12 +2395,27 @@ This usually causes the application icon in the task bar to animate in some fash - + + Send encrypted messages to verified users only + + + + + Requires a user to be verified to send encrypted messages to them. This improves safety but makes E2EE more tedious. + + + + Share keys with verified users and devices - + + Automatically replies to key requests from other users, if they are verified, even if that device shouldn't have access to those keys otherwise. + + + + CACHED @@ -2242,7 +2425,7 @@ This usually causes the application icon in the task bar to animate in some fash - + Scale factor @@ -2337,12 +2520,12 @@ This usually causes the application icon in the task bar to animate in some fash - + GENERAL - + INTERFACE @@ -2362,12 +2545,7 @@ This usually causes the application icon in the task bar to animate in some fash - - Automatically replies to key requests from other users, if they are verified. - - - - + Master signing key @@ -2417,7 +2595,7 @@ This usually causes the application icon in the task bar to animate in some fash - + Open Sessions File @@ -2666,32 +2844,6 @@ Media size: %2 - - dialogs::ReadReceipts - - - Read receipts - - - - - Close - - - - - dialogs::ReceiptItem - - - Today %1 - - - - - Yesterday %1 - - - message-description sent: diff --git a/resources/langs/nheko_et.ts b/resources/langs/nheko_et.ts index 57eac1c3..13ab1f1b 100644 --- a/resources/langs/nheko_et.ts +++ b/resources/langs/nheko_et.ts @@ -56,7 +56,7 @@ CallInvite - + Video Call Videokõne @@ -125,7 +125,7 @@ ChatPage - + Failed to invite user: %1 Kutse saatmine kasutajale ei õnnestunud: %1 @@ -157,12 +157,12 @@ - + Confirm invite Kinnita kutse - + Do you really want to invite %1 (%2)? Kas sa tõesti soovid saata kutset kasutajale %1 (%2)? @@ -227,12 +227,12 @@ Suhtluskeeld eemaldatud: %1 - + Do you really want to start a private chat with %1? Kas sa kindlasti soovid alustada otsevestlust kasutajaga %1? - + Cache migration failed! Puhvri versiooniuuendus ebaõnnestus! @@ -367,7 +367,7 @@ Andmete dekrüptimiseks sisesta oma taastevõti või salafraas nimega %1: - + Decryption failed Dekrüptimine ei õnnestunud @@ -494,6 +494,49 @@ Mõlemal pool on ühesugused emojid! + + Encrypted + + + There is no key to unlock this message. We requested the key automatically, but you can try requesting it again if you are impatient. + + + + + This message couldn't be decrypted, because we only have a key for newer messages. You can try requesting access to this message. + + + + + There was an internal error reading the decryption key from the database. + + + + + There was an error decrypting this message. + + + + + The message couldn't be parsed. + + + + + The encryption key was reused! Someone is possibly trying to insert false messages into this chat! + + + + + Unknown decryption error + + + + + Request key + + + EncryptionIndicator @@ -517,51 +560,6 @@ Krüptitud verifitseerimata seadmes - - EventStore - - - -- Encrypted Event (No keys found for decryption) -- - Placeholder, when the message was not decrypted yet or can't be decrypted. - -- Krüptitud sündmus (Dekrüptimisvõtmeid ei leidunud) -- - - - - -- Encrypted Event (Key not valid for this index) -- - Placeholder, when the message can't be decrypted with this key since it is not valid for this index - -- Krüptitud sündmus (võti pole selle indeksi jaoks sobilik) -- - - - - - -- Decryption Error (failed to retrieve megolm keys from db) -- - Placeholder, when the message can't be decrypted, because the DB access failed. - -- Dekrüptimise viga (megolm'i võtmete laadimine andmebaasist ei õnnestunud) -- - - - - - -- Decryption Error (%1) -- - Placeholder, when the message can't be decrypted. In this case, the Olm decrytion returned an error, which is passed as %1. - -- Dekrüptimise viga (%1) -- - - - - -- Encrypted Event (Unknown event type) -- - Placeholder, when the message was decrypted, but we couldn't parse it, because Nheko/mtxclient don't support that event type yet. - -- Krüptitud sündmus (Tundmatu sündmuse tüüp) -- - - - - -- Replay attack! This message index was reused! -- - -- Kordusel põhinev rünne! Selle sõnumi indeksit on uuesti kasutatud! -- - - - - -- Message by unverified device! -- - -- Sõnum verifitseerimata seadmest! -- - - Failed @@ -604,6 +602,71 @@ Suuna sõnum edasi + + ImagePackEditorDialog + + + Editing image pack + + + + + Add images + + + + + Stickers (*.png *.webp) + + + + + State key + + + + + Packname + + + + + Attribution + + + + + + Use as Emoji + + + + + + Use as Sticker + + + + + Shortcode + + + + + Body + + + + + Cancel + + + + + Save + + + ImagePackSettingsDialog @@ -612,7 +675,17 @@ - + + Create account pack + + + + + New room pack + + + + Private pack @@ -627,7 +700,7 @@ - + Enable globally @@ -637,7 +710,12 @@ - + + Edit + Muuda + + + Close Sulge @@ -811,12 +889,12 @@ Näiteks: https://server.minu:8787 MessageDelegate - + Encryption enabled Krüptimine on kasutusel - + room name changed to: %1 jututoa uus nimi on: %1 @@ -871,13 +949,13 @@ Näiteks: https://server.minu:8787 %1 vastas kõnele. - + removed eemaldatud - + %1 ended the call. %1 lõpetas kõne. @@ -905,7 +983,7 @@ Näiteks: https://server.minu:8787 Kirjuta sõnum… - + Stickers @@ -928,7 +1006,7 @@ Näiteks: https://server.minu:8787 MessageView - + Edit Muuda @@ -948,7 +1026,7 @@ Näiteks: https://server.minu:8787 Valikud - + &Copy &Kopeeri @@ -1179,21 +1257,37 @@ Näiteks: https://server.minu:8787 Profiili nimi + + ReadReceipts + + + Read receipts + Lugemisteatised + + + + ReadReceiptsModel + + + Yesterday, %1 + + + RegisterPage - + Username Kasutajanimi - + The username must not be empty, and must contain only the characters a-z, 0-9, ., _, =, -, and /. Kasutajanimi ei tohi olla tühi ning võib sisaldada vaid a-z, 0-9, ., _, =, -, / tähemärke. - + Password Salasõna @@ -1213,7 +1307,7 @@ Näiteks: https://server.minu:8787 Koduserver - + A server that allows registration. Since matrix is decentralized, you need to first find a server you can register on or host your own. See on server, kus sa oma kasutajakonto registreerid. Kuna Matrix on hajutatud suhtlusvõrk, siis esmalt pead leidma sulle sobiliku koduserveri või panema püsti täitsa oma enda koduserveri. @@ -1223,22 +1317,27 @@ Näiteks: https://server.minu:8787 REGISTREERI - + No supported registration flows! Selline registreerimise töövoog pole toetatud! - - One or more fields have invalid inputs. Please correct those issues and try again. - Ühel või enamal andmeväljal on vigane väärtus. Palun paranda vead ja proovi uuesti. + + Registration token + - + + Please enter a valid registration token. + + + + Autodiscovery failed. Received malformed response. Koduserveri automaatne tuvastamine ei õnnestunud: päringuvastus oli vigane. - + Autodiscovery failed. Unknown error when requesting .well-known. Koduserveri automaatne tuvastamine ei õnnestunud: tundmatu viga .well-known päringu tegemisel. @@ -1248,7 +1347,7 @@ Näiteks: https://server.minu:8787 Protokolli järgi nõutavaid lõpppunkte ei leidunud. Ilmselt pole tegemist Matrix'i serveriga. - + Received malformed response. Make sure the homeserver domain is valid. Päringule sain tagasi vigase vastuse. Palun kontrolli, et koduserveri domeen oleks õige. @@ -1258,17 +1357,17 @@ Näiteks: https://server.minu:8787 Tekkis teadmata viga. Palun kontrolli, et koduserveri domeen on õige. - + Password is not long enough (min 8 chars) Salasõna pole piisavalt pikk (vähemalt 8 tähemärki) - + Passwords don't match Salasõnad ei klapi omavahel - + Invalid server name Vigane koduserveri nimi @@ -1276,7 +1375,7 @@ Näiteks: https://server.minu:8787 ReplyPopup - + Close Sulge @@ -1289,7 +1388,7 @@ Näiteks: https://server.minu:8787 RoomInfo - + no version stored salvestatud versiooni ei leidu @@ -1297,7 +1396,7 @@ Näiteks: https://server.minu:8787 RoomList - + New tag Uus silt @@ -1347,7 +1446,7 @@ Näiteks: https://server.minu:8787 Loo uus silt... - + Status Message Olekuteade @@ -1367,7 +1466,7 @@ Näiteks: https://server.minu:8787 Sisesta olekuteade - + Logout Logi välja @@ -1400,7 +1499,7 @@ Näiteks: https://server.minu:8787 RoomMembers - + Members of %1 @@ -1418,11 +1517,31 @@ Näiteks: https://server.minu:8787 Invite more people + + + This room is not encrypted! + + + + + This user is verified. + + + + + This user isn't verified, but is still using the same master key from the first time you met. + + + + + This user has unverified devices! + + RoomSettings - + Room Settings Jututoa seadistused @@ -1457,7 +1576,12 @@ Näiteks: https://server.minu:8787 Kõik sõnumid - + + Room access + + + + Anyone and guests Kõik (sealhulgas külalised) @@ -1621,6 +1745,30 @@ Näiteks: https://server.minu:8787 Loobu + + SingleImagePackModel + + + + Failed to update image pack: {} + + + + + Failed to delete old image pack: {} + + + + + Failed to open image: {} + + + + + Failed to upload image: {} + + + StatusIndicator @@ -1673,7 +1821,7 @@ Näiteks: https://server.minu:8787 TimelineModel - + Message redaction failed: %1 Sõnumi ümbersõnastamine ebaõnnestus: %1 @@ -1704,7 +1852,7 @@ Näiteks: https://server.minu:8787 Salvesta fail - + %1 and %2 are typing. Multiple users are typing. First argument is a comma separated list of potentially multiple users. Second argument is the last user of that list. (If only one user is typing, %1 is empty. You should still use it in your string though to silence Qt warnings.) @@ -1818,12 +1966,12 @@ Näiteks: https://server.minu:8787 %1 muutis oma koputust jututoa uksele. - + You joined this room. Sa liitusid jututoaga. - + %1 has changed their avatar and changed their display name to %2. %1 muutis oma tunnuspilti ja seadistas uueks kuvatavaks nimeks %2. @@ -1852,7 +2000,7 @@ Näiteks: https://server.minu:8787 TimelineRow - + Edited Muudetud @@ -1860,12 +2008,12 @@ Näiteks: https://server.minu:8787 TimelineView - + No room open Ühtegi jututuba pole avatud - + %1 member(s) %1 liige(t) @@ -1893,7 +2041,7 @@ Näiteks: https://server.minu:8787 TimelineViewManager - + No encrypted private chat found with this user. Create an encrypted private chat with this user and try again. Ühtegi krüptitud vestlust selle kasutajaga ei leidunud. Palun loo temaga krüptitud vestlus ja proovi uuesti. @@ -1901,17 +2049,37 @@ Näiteks: https://server.minu:8787 TopBar - + Back to room list Tagasi jututubade loendisse - + No room selected Jututuba on valimata - + + This room is not encrypted! + + + + + This room contains only verified devices. + + + + + This rooms contain verified devices and devices which have never changed their master key. + + + + + This room contains unverified devices! + + + + Room options Jututoa valikud @@ -1952,7 +2120,7 @@ Näiteks: https://server.minu:8787 UserProfile - + Global User Profile Üldine kasutajaprofiil @@ -1962,7 +2130,7 @@ Näiteks: https://server.minu:8787 Kasutajaprofiil jututoas - + Verify Verifitseeri @@ -2011,8 +2179,8 @@ Näiteks: https://server.minu:8787 UserSettings - - + + Default Vaikimisi @@ -2020,7 +2188,7 @@ Näiteks: https://server.minu:8787 UserSettingsPage - + Minimize to tray Vähenda tegumiribale @@ -2040,12 +2208,12 @@ Näiteks: https://server.minu:8787 Ümmargused tunnuspildid - + profile: %1 Profiil: %1 - + Default Vaikimisi @@ -2242,12 +2410,27 @@ See tavaliselt tähendab, et rakenduse ikoon tegumiribal annab mingit sorti anim Tee sõnumi font suuremaks, kui sõnumis on vaid mõned emojid. - + + Send encrypted messages to verified users only + + + + + Requires a user to be verified to send encrypted messages to them. This improves safety but makes E2EE more tedious. + + + + Share keys with verified users and devices Jaga võtmeid verifitseeritud kasutajate ja seadmetega - + + Automatically replies to key requests from other users, if they are verified, even if that device shouldn't have access to those keys otherwise. + + + + CACHED PUHVERDATUD @@ -2257,7 +2440,7 @@ See tavaliselt tähendab, et rakenduse ikoon tegumiribal annab mingit sorti anim PUHVERDAMATA - + Scale factor Mastaabitegur @@ -2352,12 +2535,12 @@ See tavaliselt tähendab, et rakenduse ikoon tegumiribal annab mingit sorti anim KRÜPTIMINE - + GENERAL ÜLDISED SEADISTUSED - + INTERFACE LIIDES @@ -2377,12 +2560,7 @@ See tavaliselt tähendab, et rakenduse ikoon tegumiribal annab mingit sorti anim Fondiperekond emojide jaoks - - Automatically replies to key requests from other users, if they are verified. - Vasta verifitseeritud kasutajate krüptovõtmete päringutele automaatselt. - - - + Master signing key Üldine allkirjavõti @@ -2432,7 +2610,7 @@ See tavaliselt tähendab, et rakenduse ikoon tegumiribal annab mingit sorti anim Kõik failid (*) - + Open Sessions File Ava sessioonide fail @@ -2683,32 +2861,6 @@ Meedia suurus: %2 Vasta reCAPTCHA küsimustele ja vajuta kinnita-nuppu - - dialogs::ReadReceipts - - - Read receipts - Lugemisteatised - - - - Close - Sulge - - - - dialogs::ReceiptItem - - - Today %1 - Täna %1 - - - - Yesterday %1 - Eile %1 - - message-description sent: diff --git a/resources/langs/nheko_fi.ts b/resources/langs/nheko_fi.ts index bb89902f..8db7eb99 100644 --- a/resources/langs/nheko_fi.ts +++ b/resources/langs/nheko_fi.ts @@ -496,21 +496,41 @@ Encrypted + + + There is no key to unlock this message. We requested the key automatically, but you can try requesting it again if you are impatient. + + This message couldn't be decrypted, because we only have a key for newer messages. You can try requesting access to this message. + + + There was an internal error reading the decryption key from the database. + + There was an error decrypting this message. + + + The message couldn't be parsed. + + The encryption key was reused! Someone is possibly trying to insert false messages into this chat! + + + Unknown decryption error + + Request key @@ -584,32 +604,63 @@ ImagePackEditorDialog + + + Editing image pack + + Add images + + + Stickers (*.png *.webp) + + State key + + + Packname + + Attribution + + + + Use as Emoji + + Use as Sticker + + + Shortcode + + Body + + + Cancel + Peruuta + Save @@ -623,6 +674,11 @@ Image pack settings + + + Create account pack + + New room pack @@ -653,6 +709,11 @@ Enables this pack to be used in all rooms + + + Edit + Muokkaa + Close @@ -1198,6 +1259,11 @@ Esimerkki: https://server.my:8787 ReadReceipts + + + Read receipts + Lukukuittaukset + ReadReceiptsModel @@ -1255,6 +1321,11 @@ Esimerkki: https://server.my:8787 No supported registration flows! Ei tuettuja rekisteröintejä! + + + Registration token + + Please enter a valid registration token. @@ -1446,11 +1517,21 @@ Esimerkki: https://server.my:8787 Invite more people + + + This room is not encrypted! + + This user is verified. + + + This user isn't verified, but is still using the same master key from the first time you met. + + This user has unverified devices! @@ -1494,6 +1575,11 @@ Esimerkki: https://server.my:8787 All messages Kaikki viestit + + + Room access + + Anyone and guests @@ -1661,11 +1747,22 @@ Esimerkki: https://server.my:8787 SingleImagePackModel + + + + Failed to update image pack: {} + + Failed to delete old image pack: {} + + + Failed to open image: {} + + Failed to upload image: {} @@ -1961,11 +2058,21 @@ Esimerkki: https://server.my:8787 No room selected Ei valittua huonetta + + + This room is not encrypted! + + This room contains only verified devices. + + + This rooms contain verified devices and devices which have never changed their master key. + + This room contains unverified devices! @@ -2302,6 +2409,11 @@ Tämä yleensä saa sovelluksen kuvakkeen liikkumaan jollain tapaa tehtäväpalk Make font size larger if messages with only a few emojis are displayed. Suurenna fonttikokoa jos näytetään viestit vain muutamalla emojilla. + + + Send encrypted messages to verified users only + + Requires a user to be verified to send encrypted messages to them. This improves safety but makes E2EE more tedious. @@ -2312,6 +2424,11 @@ Tämä yleensä saa sovelluksen kuvakkeen liikkumaan jollain tapaa tehtäväpalk Share keys with verified users and devices Jaa avaimet vahvistettujen käyttäjien ja laitteiden kanssa + + + Automatically replies to key requests from other users, if they are verified, even if that device shouldn't have access to those keys otherwise. + + CACHED diff --git a/resources/langs/nheko_fr.ts b/resources/langs/nheko_fr.ts index c6d42299..9d11ce25 100644 --- a/resources/langs/nheko_fr.ts +++ b/resources/langs/nheko_fr.ts @@ -56,7 +56,7 @@ CallInvite - + Video Call Appel vidéo @@ -125,7 +125,7 @@ ChatPage - + Failed to invite user: %1 Échec lors de l'invitation de %1 @@ -157,12 +157,12 @@ - + Confirm invite Confirmer l'invitation - + Do you really want to invite %1 (%2)? Voulez-vous vraiment inviter %1 (%2) ? @@ -227,12 +227,12 @@ %1 n'est plus banni(e) - + Do you really want to start a private chat with %1? Voulez-vous vraimer commencer une discussion privée avec %1 ? - + Cache migration failed! Échec de la migration du cache ! @@ -367,7 +367,7 @@ Entrez votre clé de récupération ou votre phrase de passe nommée %1 pour déchiffrer vos secrets  : - + Decryption failed Échec du déchiffrement @@ -494,6 +494,49 @@ Elles sont identiques ! + + Encrypted + + + There is no key to unlock this message. We requested the key automatically, but you can try requesting it again if you are impatient. + + + + + This message couldn't be decrypted, because we only have a key for newer messages. You can try requesting access to this message. + + + + + There was an internal error reading the decryption key from the database. + + + + + There was an error decrypting this message. + + + + + The message couldn't be parsed. + + + + + The encryption key was reused! Someone is possibly trying to insert false messages into this chat! + + + + + Unknown decryption error + + + + + Request key + + + EncryptionIndicator @@ -517,51 +560,6 @@ - - EventStore - - - -- Encrypted Event (No keys found for decryption) -- - Placeholder, when the message was not decrypted yet or can't be decrypted. - -- Évènement chiffré (pas de clé trouvée pour le déchiffrement) -- - - - - -- Encrypted Event (Key not valid for this index) -- - Placeholder, when the message can't be decrypted with this key since it is not valid for this index - -- Événement chiffré (clé invalide pour cet index) -- - - - - - -- Decryption Error (failed to retrieve megolm keys from db) -- - Placeholder, when the message can't be decrypted, because the DB access failed. - -- Échec du déchiffrement (échec de la récupération des clés megolm depuis la base de données) -- - - - - - -- Decryption Error (%1) -- - Placeholder, when the message can't be decrypted. In this case, the Olm decrytion returned an error, which is passed as %1. - -- Erreur de déchiffrement (%1) -- - - - - -- Encrypted Event (Unknown event type) -- - Placeholder, when the message was decrypted, but we couldn't parse it, because Nheko/mtxclient don't support that event type yet. - -- Évènement chiffré (type d'évènement inconnu) -- - - - - -- Replay attack! This message index was reused! -- - -- Attaque par rejeu (replay attack) ! Cet index de message a été réutilisé ! -- - - - - -- Message by unverified device! -- - -- Message d'un appareil non vérifié  -- - - Failed @@ -604,6 +602,71 @@ + + ImagePackEditorDialog + + + Editing image pack + + + + + Add images + + + + + Stickers (*.png *.webp) + + + + + State key + + + + + Packname + + + + + Attribution + + + + + + Use as Emoji + + + + + + Use as Sticker + + + + + Shortcode + + + + + Body + + + + + Cancel + Annuler + + + + Save + + + ImagePackSettingsDialog @@ -612,7 +675,17 @@ - + + Create account pack + + + + + New room pack + + + + Private pack @@ -627,7 +700,7 @@ - + Enable globally @@ -637,7 +710,12 @@ - + + Edit + Modifier + + + Close Fermer @@ -811,7 +889,7 @@ Exemple : https ://monserveur.example.com :8787 MessageDelegate - + removed retiré @@ -822,7 +900,7 @@ Exemple : https ://monserveur.example.com :8787 Chiffrement activé - + room name changed to: %1 nom du salon changé en : %1 @@ -905,7 +983,7 @@ Exemple : https ://monserveur.example.com :8787 Écrivez un message… - + Stickers @@ -928,7 +1006,7 @@ Exemple : https ://monserveur.example.com :8787 MessageView - + Edit Modifier @@ -948,7 +1026,7 @@ Exemple : https ://monserveur.example.com :8787 Options - + &Copy @@ -1179,21 +1257,37 @@ Exemple : https ://monserveur.example.com :8787 nom du profil + + ReadReceipts + + + Read receipts + Accusés de lecture + + + + ReadReceiptsModel + + + Yesterday, %1 + + + RegisterPage - + Username Nom d'utilisateur - + The username must not be empty, and must contain only the characters a-z, 0-9, ., _, =, -, and /. Le nom d'utilisateur ne doit pas être vide, et ne peut contenir que les caractères a à z, 0 à 9, et « . _ = - / ». - + Password Mot de passe @@ -1213,7 +1307,7 @@ Exemple : https ://monserveur.example.com :8787 Serveur - + A server that allows registration. Since matrix is decentralized, you need to first find a server you can register on or host your own. Un serveur qui autorise les créations de compte. Matrix étant décentralisé, vous devez tout d'abord trouver un serveur sur lequel vous pouvez vous inscrire, ou bien héberger le vôtre. @@ -1223,22 +1317,27 @@ Exemple : https ://monserveur.example.com :8787 S'ENREGISTRER - + No supported registration flows! Pas de méthode d'inscription supportée ! - - One or more fields have invalid inputs. Please correct those issues and try again. - Un ou plusieurs champs ont des entrées invalides. Veuillez les corriger et réessayer. + + Registration token + - + + Please enter a valid registration token. + + + + Autodiscovery failed. Received malformed response. Échec de la découverte automatique. Réponse mal formatée reçue. - + Autodiscovery failed. Unknown error when requesting .well-known. Échec de la découverte automatique. Erreur inconnue lors de la demande de .well-known. @@ -1248,7 +1347,7 @@ Exemple : https ://monserveur.example.com :8787 Les chemins requis n'ont pas été trouvés. Possible qu'il ne s'agisse pas d'un serveur Matrix. - + Received malformed response. Make sure the homeserver domain is valid. Réponse mal formée reçue. Vérifiez que le nom de domaine du serveur est valide. @@ -1258,17 +1357,17 @@ Exemple : https ://monserveur.example.com :8787 Une erreur inconnue est survenue. Vérifiez que le nom de domaine du serveur est valide. - + Password is not long enough (min 8 chars) Le mot de passe n'est pas assez long (8 caractères minimum) - + Passwords don't match Les mots de passe ne sont pas identiques - + Invalid server name Le nom du serveur est invalide @@ -1276,7 +1375,7 @@ Exemple : https ://monserveur.example.com :8787 ReplyPopup - + Close Fermer @@ -1289,7 +1388,7 @@ Exemple : https ://monserveur.example.com :8787 RoomInfo - + no version stored pas de version enregistrée @@ -1297,7 +1396,7 @@ Exemple : https ://monserveur.example.com :8787 RoomList - + New tag @@ -1347,7 +1446,7 @@ Exemple : https ://monserveur.example.com :8787 - + Status Message @@ -1367,7 +1466,7 @@ Exemple : https ://monserveur.example.com :8787 - + Logout Se déconnecter @@ -1400,7 +1499,7 @@ Exemple : https ://monserveur.example.com :8787 RoomMembers - + Members of %1 @@ -1418,11 +1517,31 @@ Exemple : https ://monserveur.example.com :8787 Invite more people + + + This room is not encrypted! + + + + + This user is verified. + + + + + This user isn't verified, but is still using the same master key from the first time you met. + + + + + This user has unverified devices! + + RoomSettings - + Room Settings Configuration du salon @@ -1457,7 +1576,12 @@ Exemple : https ://monserveur.example.com :8787 Tous les messages - + + Room access + + + + Anyone and guests Tous le monde et les invités @@ -1621,6 +1745,30 @@ Exemple : https ://monserveur.example.com :8787 Annuler + + SingleImagePackModel + + + + Failed to update image pack: {} + + + + + Failed to delete old image pack: {} + + + + + Failed to open image: {} + + + + + Failed to upload image: {} + + + StatusIndicator @@ -1673,7 +1821,7 @@ Exemple : https ://monserveur.example.com :8787 TimelineModel - + Message redaction failed: %1 Échec de la suppression du message : %1 @@ -1704,7 +1852,7 @@ Exemple : https ://monserveur.example.com :8787 Enregistrer le fichier - + %1 and %2 are typing. Multiple users are typing. First argument is a comma separated list of potentially multiple users. Second argument is the last user of that list. (If only one user is typing, %1 is empty. You should still use it in your string though to silence Qt warnings.) @@ -1818,12 +1966,12 @@ Exemple : https ://monserveur.example.com :8787 %1 ne frappe plus au salon. - + You joined this room. Vous avez rejoint ce salon. - + %1 has changed their avatar and changed their display name to %2. @@ -1852,7 +2000,7 @@ Exemple : https ://monserveur.example.com :8787 TimelineRow - + Edited Modifié @@ -1860,12 +2008,12 @@ Exemple : https ://monserveur.example.com :8787 TimelineView - + No room open Aucun salon ouvert - + %1 member(s) %1 membre(s) @@ -1893,7 +2041,7 @@ Exemple : https ://monserveur.example.com :8787 TimelineViewManager - + No encrypted private chat found with this user. Create an encrypted private chat with this user and try again. Pas de discussion privée et chiffrée trouvée avec cet utilisateur. Créez-en une et réessayez. @@ -1901,17 +2049,37 @@ Exemple : https ://monserveur.example.com :8787 TopBar - + Back to room list Revenir à la liste des salons - + No room selected Pas de salon sélectionné - + + This room is not encrypted! + + + + + This room contains only verified devices. + + + + + This rooms contain verified devices and devices which have never changed their master key. + + + + + This room contains unverified devices! + + + + Room options Options du salon @@ -1952,7 +2120,7 @@ Exemple : https ://monserveur.example.com :8787 UserProfile - + Global User Profile Profil général de l'utilisateur @@ -1962,7 +2130,7 @@ Exemple : https ://monserveur.example.com :8787 Profil utilisateur spécifique au salon - + Verify Vérifier @@ -2011,8 +2179,8 @@ Exemple : https ://monserveur.example.com :8787 UserSettings - - + + Default Défaut @@ -2020,7 +2188,7 @@ Exemple : https ://monserveur.example.com :8787 UserSettingsPage - + Minimize to tray Réduire à la barre des tâches @@ -2040,12 +2208,12 @@ Exemple : https ://monserveur.example.com :8787 Avatars circulaires - + profile: %1 profil : %1 - + Default Défaut @@ -2244,12 +2412,27 @@ Cela met l'application en évidence dans la barre des tâches.Augmente la taille de la police lors de l'affichage de messages contenant uniquement quelques emojis. - + + Send encrypted messages to verified users only + + + + + Requires a user to be verified to send encrypted messages to them. This improves safety but makes E2EE more tedious. + + + + Share keys with verified users and devices Partager vos clés avec les utilisateurs et appareils que vous avez vérifiés - + + Automatically replies to key requests from other users, if they are verified, even if that device shouldn't have access to those keys otherwise. + + + + CACHED EN CACHE @@ -2259,7 +2442,7 @@ Cela met l'application en évidence dans la barre des tâches.PAS DANS LE CACHE - + Scale factor Facteur d'échelle @@ -2354,12 +2537,12 @@ Cela met l'application en évidence dans la barre des tâches.CHIFFREMENT - + GENERAL GÉNÉRAL - + INTERFACE INTERFACE @@ -2379,12 +2562,7 @@ Cela met l'application en évidence dans la barre des tâches.Nom de Police Emoji - - Automatically replies to key requests from other users, if they are verified. - Automatiquement répondre aux demandes de clés de déchiffrement des autres utilisateurs, si ceux-ci sont vérifiés. - - - + Master signing key Clé de signature de l'utilisateur @@ -2434,7 +2612,7 @@ Cela met l'application en évidence dans la barre des tâches.Tous les types de fichiers (*) - + Open Sessions File Ouvrir fichier de sessions @@ -2685,32 +2863,6 @@ Taille du média : %2 Résolvez le reCAPTCHA puis appuyez sur le bouton de confirmation - - dialogs::ReadReceipts - - - Read receipts - Accusés de lecture - - - - Close - Fermer - - - - dialogs::ReceiptItem - - - Today %1 - Aujourd'hui %1 - - - - Yesterday %1 - Hier %1 - - message-description sent: diff --git a/resources/langs/nheko_hu.ts b/resources/langs/nheko_hu.ts index 7c29338c..a8e4d0e9 100644 --- a/resources/langs/nheko_hu.ts +++ b/resources/langs/nheko_hu.ts @@ -56,7 +56,7 @@ CallInvite - + Video Call Videóhívás @@ -125,7 +125,7 @@ ChatPage - + Failed to invite user: %1 Nem sikerült meghívni a felhasználót: %1 @@ -157,12 +157,12 @@ - + Confirm invite Meghívás megerősítése - + Do you really want to invite %1 (%2)? Biztos, hogy meg akarod hívni a következő felhasználót: %1 (%2)? @@ -227,12 +227,12 @@ Kitiltás feloldva a felhasználónak: %1 - + Do you really want to start a private chat with %1? Biztosan privát csevegést akarsz indítani %1 felhasználóval? - + Cache migration failed! Gyorsítótár migráció nem sikerült! @@ -367,7 +367,7 @@ Add meg a %1 nevű helyreállítási kulcsodat vagy a jelmondatodat a titkos tároló feloldásához: - + Decryption failed Titkosítás feloldása nem sikerült @@ -494,6 +494,49 @@ Megegyeznek! + + Encrypted + + + There is no key to unlock this message. We requested the key automatically, but you can try requesting it again if you are impatient. + + + + + This message couldn't be decrypted, because we only have a key for newer messages. You can try requesting access to this message. + + + + + There was an internal error reading the decryption key from the database. + + + + + There was an error decrypting this message. + + + + + The message couldn't be parsed. + + + + + The encryption key was reused! Someone is possibly trying to insert false messages into this chat! + + + + + Unknown decryption error + + + + + Request key + + + EncryptionIndicator @@ -517,51 +560,6 @@ - - EventStore - - - -- Encrypted Event (No keys found for decryption) -- - Placeholder, when the message was not decrypted yet or can't be decrypted. - -- Titkosított esemény (Nem találhatók kulcsok a titkosítás feloldásához) -- - - - - -- Encrypted Event (Key not valid for this index) -- - Placeholder, when the message can't be decrypted with this key since it is not valid for this index - -- Titkosított esemény (a kulcs nem érvényes ehhez az indexhez) -- - - - - - -- Decryption Error (failed to retrieve megolm keys from db) -- - Placeholder, when the message can't be decrypted, because the DB access failed. - -- Hiba a titkosítás feloldásakor (nem sikerült lekérni a megolm kulcsokat az adatbázisból) -- - - - - - -- Decryption Error (%1) -- - Placeholder, when the message can't be decrypted. In this case, the Olm decrytion returned an error, which is passed as %1. - -- Hiba a titkosítás feloldásakor (%1) -- - - - - -- Encrypted Event (Unknown event type) -- - Placeholder, when the message was decrypted, but we couldn't parse it, because Nheko/mtxclient don't support that event type yet. - -- Titkosított esemény (Ismeretlen eseménytípus) -- - - - - -- Replay attack! This message index was reused! -- - -- Újrajátszási támadás! Ez az üzenetindex újra fel lett használva! -- - - - - -- Message by unverified device! -- - -- Nem hitelesített eszközről érkezett üzenet! -- - - Failed @@ -604,6 +602,71 @@ + + ImagePackEditorDialog + + + Editing image pack + + + + + Add images + + + + + Stickers (*.png *.webp) + + + + + State key + + + + + Packname + + + + + Attribution + + + + + + Use as Emoji + + + + + + Use as Sticker + + + + + Shortcode + + + + + Body + + + + + Cancel + Mégse + + + + Save + + + ImagePackSettingsDialog @@ -612,7 +675,17 @@ - + + Create account pack + + + + + New room pack + + + + Private pack @@ -627,7 +700,7 @@ - + Enable globally @@ -637,7 +710,12 @@ - + + Edit + Szerkesztés + + + Close Bezárás @@ -811,12 +889,12 @@ Példa: https://szerver.em:8787 MessageDelegate - + Encryption enabled Titkosítás bekapcsolva - + room name changed to: %1 a szoba neve megváltoztatva erre: %1 @@ -871,13 +949,13 @@ Példa: https://szerver.em:8787 %1 fogadta a hívást. - + removed eltávolítva - + %1 ended the call. %1 befejezte a hívást. @@ -905,7 +983,7 @@ Példa: https://szerver.em:8787 Írj egy üzenetet… - + Stickers @@ -928,7 +1006,7 @@ Példa: https://szerver.em:8787 MessageView - + Edit Szerkesztés @@ -948,7 +1026,7 @@ Példa: https://szerver.em:8787 Műveletek - + &Copy @@ -1179,21 +1257,37 @@ Példa: https://szerver.em:8787 profilnév + + ReadReceipts + + + Read receipts + Olvasási jegyek + + + + ReadReceiptsModel + + + Yesterday, %1 + + + RegisterPage - + Username Felhasználónév - + The username must not be empty, and must contain only the characters a-z, 0-9, ., _, =, -, and /. A felhasználónév nem lehet üres és csak a következő karaktereket tartalmazhatja: a-z, 0-9, ., _, =, - és /. - + Password Jelszó @@ -1213,7 +1307,7 @@ Példa: https://szerver.em:8787 Homeszerver - + A server that allows registration. Since matrix is decentralized, you need to first find a server you can register on or host your own. Egy szerver, amelyen engedélyezve vannak a regisztrációk. Mivel a Matrix decentralizált, először találnod kell egy szervert, ahol regisztrálhatsz, vagy be kell állítanod a saját szervered. @@ -1223,22 +1317,27 @@ Példa: https://szerver.em:8787 REGISZTRÁCIÓ - + No supported registration flows! Nem támogatott regisztrációs folyamat! - - One or more fields have invalid inputs. Please correct those issues and try again. - Egy vagy több mező tartalma nem helyes. Kérlek, javítsd ki azokat a hibákat, és próbáld újra! + + Registration token + - + + Please enter a valid registration token. + + + + Autodiscovery failed. Received malformed response. Az automatikus felderítés nem sikerült. Helytelen válasz érkezett. - + Autodiscovery failed. Unknown error when requesting .well-known. Az automatikus felderítés nem sikerült. Ismeretlen hiba a .well-known lekérése közben. @@ -1248,7 +1347,7 @@ Példa: https://szerver.em:8787 Nem találhatók szükséges végpontok. Lehet, hogy nem egy Matrixszerver. - + Received malformed response. Make sure the homeserver domain is valid. Helytelen válasz érkezett. Ellenőrizd, hogy a homeszervered domainje helyes. @@ -1258,17 +1357,17 @@ Példa: https://szerver.em:8787 Egy ismeretlen hiba történt. Ellenőrizd, hogy a homeszervered domainje helyes. - + Password is not long enough (min 8 chars) A jelszó nem elég hosszú (legalább 8 karakter) - + Passwords don't match A jelszavak nem egyeznek - + Invalid server name Nem megfelelő szervernév @@ -1276,7 +1375,7 @@ Példa: https://szerver.em:8787 ReplyPopup - + Close Bezárás @@ -1289,7 +1388,7 @@ Példa: https://szerver.em:8787 RoomInfo - + no version stored nincs tárolva verzió @@ -1297,7 +1396,7 @@ Példa: https://szerver.em:8787 RoomList - + New tag @@ -1347,7 +1446,7 @@ Példa: https://szerver.em:8787 - + Status Message @@ -1367,7 +1466,7 @@ Példa: https://szerver.em:8787 - + Logout Kijelentkezés @@ -1400,7 +1499,7 @@ Példa: https://szerver.em:8787 RoomMembers - + Members of %1 @@ -1417,11 +1516,31 @@ Példa: https://szerver.em:8787 Invite more people + + + This room is not encrypted! + + + + + This user is verified. + + + + + This user isn't verified, but is still using the same master key from the first time you met. + + + + + This user has unverified devices! + + RoomSettings - + Room Settings Szobabeállítások @@ -1456,7 +1575,12 @@ Példa: https://szerver.em:8787 Az összes üzenet - + + Room access + + + + Anyone and guests Bárki és vendégek @@ -1620,6 +1744,30 @@ Példa: https://szerver.em:8787 Mégse + + SingleImagePackModel + + + + Failed to update image pack: {} + + + + + Failed to delete old image pack: {} + + + + + Failed to open image: {} + + + + + Failed to upload image: {} + + + StatusIndicator @@ -1672,7 +1820,7 @@ Példa: https://szerver.em:8787 TimelineModel - + Message redaction failed: %1 Az üzenet visszavonása nem sikerült: %1 @@ -1703,7 +1851,7 @@ Példa: https://szerver.em:8787 Fájl mentése - + %1 and %2 are typing. Multiple users are typing. First argument is a comma separated list of potentially multiple users. Second argument is the last user of that list. (If only one user is typing, %1 is empty. You should still use it in your string though to silence Qt warnings.) @@ -1816,12 +1964,12 @@ Példa: https://szerver.em:8787 %1 visszavonta a kopogását. - + You joined this room. Csatlakoztál ehhez a szobához. - + %1 has changed their avatar and changed their display name to %2. @@ -1850,7 +1998,7 @@ Példa: https://szerver.em:8787 TimelineRow - + Edited Szerkesztve @@ -1858,12 +2006,12 @@ Példa: https://szerver.em:8787 TimelineView - + No room open Nincs nyitott szoba - + %1 member(s) %1 tag @@ -1891,7 +2039,7 @@ Példa: https://szerver.em:8787 TimelineViewManager - + No encrypted private chat found with this user. Create an encrypted private chat with this user and try again. Nem található titkosított privát csevegés ezzel a felhasználóval. Hozz létre egy titkosított privát csevegést vele, és próbáld újra! @@ -1899,17 +2047,37 @@ Példa: https://szerver.em:8787 TopBar - + Back to room list Vissza a szobák listájára - + No room selected Nincs kiválasztva szoba - + + This room is not encrypted! + + + + + This room contains only verified devices. + + + + + This rooms contain verified devices and devices which have never changed their master key. + + + + + This room contains unverified devices! + + + + Room options Szoba beállításai @@ -1950,7 +2118,7 @@ Példa: https://szerver.em:8787 UserProfile - + Global User Profile Globális felhasználói profil @@ -1960,7 +2128,7 @@ Példa: https://szerver.em:8787 Szobai felhasználói profil - + Verify Hitelesítés @@ -2009,8 +2177,8 @@ Példa: https://szerver.em:8787 UserSettings - - + + Default Alapértelmezett @@ -2018,7 +2186,7 @@ Példa: https://szerver.em:8787 UserSettingsPage - + Minimize to tray Kicsinyítés a tálcára @@ -2038,12 +2206,12 @@ Példa: https://szerver.em:8787 Kerekített profilképek - + profile: %1 profil: %1 - + Default Alapértelmezett @@ -2241,12 +2409,27 @@ Ettől általában animálttá válik az alkalmazásablakok listáján szereplő A betűméret megnövelése, ha az üzenetek csak néhány hangulatjelet tartalmaznak. - + + Send encrypted messages to verified users only + + + + + Requires a user to be verified to send encrypted messages to them. This improves safety but makes E2EE more tedious. + + + + Share keys with verified users and devices Kulcsok megosztása hitelesített felhasználókkal és eszközökkel - + + Automatically replies to key requests from other users, if they are verified, even if that device shouldn't have access to those keys otherwise. + + + + CACHED GYORSÍTÓTÁRAZVA @@ -2256,7 +2439,7 @@ Ettől általában animálttá válik az alkalmazásablakok listáján szereplő NINCS GYORSÍTÓTÁRAZVA - + Scale factor Nagyítási tényező @@ -2351,12 +2534,12 @@ Ettől általában animálttá válik az alkalmazásablakok listáján szereplő TITKOSÍTÁS - + GENERAL ÁLTALÁNOS - + INTERFACE FELÜLET @@ -2376,12 +2559,7 @@ Ettől általában animálttá válik az alkalmazásablakok listáján szereplő Hangulatjelek betűtípusa - - Automatically replies to key requests from other users, if they are verified. - Automatikus válasz a más felhasználóktól érkező kulcskérelmekre, ha ők hitelesítve vannak. - - - + Master signing key Mester-aláírókulcs @@ -2431,7 +2609,7 @@ Ettől általában animálttá válik az alkalmazásablakok listáján szereplő Minden fájl (*) - + Open Sessions File Munkameneti fájl megnyitása @@ -2682,32 +2860,6 @@ Média mérete: %2 Oldd meg a reCAPTCHA feladványát, és nyomd meg a „Megerősítés” gombot - - dialogs::ReadReceipts - - - Read receipts - Olvasási jegyek - - - - Close - Bezárás - - - - dialogs::ReceiptItem - - - Today %1 - Ma %1 - - - - Yesterday %1 - Tegnap %1 - - message-description sent: diff --git a/resources/langs/nheko_it.ts b/resources/langs/nheko_it.ts index 5a1d45f8..31146e76 100644 --- a/resources/langs/nheko_it.ts +++ b/resources/langs/nheko_it.ts @@ -56,7 +56,7 @@ CallInvite - + Video Call Chiamata video @@ -125,7 +125,7 @@ ChatPage - + Failed to invite user: %1 Impossibile invitare l'utente: %1 @@ -157,12 +157,12 @@ - + Confirm invite Conferma Invito - + Do you really want to invite %1 (%2)? Vuoi davvero inviare %1 (%2)? @@ -227,12 +227,12 @@ Rimosso il ban dall'utente: %1 - + Do you really want to start a private chat with %1? Sei sicuro di voler avviare una chat privata con %1? - + Cache migration failed! Migrazione della cache fallita! @@ -367,7 +367,7 @@ Inserisci la tua chiave di recupero o la parola chiave chiamata %1 per decifrare i tuoi segreti: - + Decryption failed Decrittazione fallita @@ -494,6 +494,49 @@ Corrispondono! + + Encrypted + + + There is no key to unlock this message. We requested the key automatically, but you can try requesting it again if you are impatient. + + + + + This message couldn't be decrypted, because we only have a key for newer messages. You can try requesting access to this message. + + + + + There was an internal error reading the decryption key from the database. + + + + + There was an error decrypting this message. + + + + + The message couldn't be parsed. + + + + + The encryption key was reused! Someone is possibly trying to insert false messages into this chat! + + + + + Unknown decryption error + + + + + Request key + + + EncryptionIndicator @@ -517,51 +560,6 @@ Criptato da un dispositivo non verificato - - EventStore - - - -- Encrypted Event (No keys found for decryption) -- - Placeholder, when the message was not decrypted yet or can't be decrypted. - -- Evento Criptato (Nessuna chiave privata per la decriptazione) -- - - - - -- Encrypted Event (Key not valid for this index) -- - Placeholder, when the message can't be decrypted with this key since it is not valid for this index - -- Evento Criptato (Chiave non valida per questo indice) -- - - - - - -- Decryption Error (failed to retrieve megolm keys from db) -- - Placeholder, when the message can't be decrypted, because the DB access failed. - -- Errore di Decrittazione (impossibile recuperare le chiavi megolm dal DB) -- - - - - - -- Decryption Error (%1) -- - Placeholder, when the message can't be decrypted. In this case, the Olm decrytion returned an error, which is passed as %1. - -- Errore di Decrittazione (%1) -- - - - - -- Encrypted Event (Unknown event type) -- - Placeholder, when the message was decrypted, but we couldn't parse it, because Nheko/mtxclient don't support that event type yet. - -- Evento Criptato (Tipo di evento ignoto) -- - - - - -- Replay attack! This message index was reused! -- - - - - - -- Message by unverified device! -- - - - Failed @@ -604,6 +602,71 @@ Inoltra Messaggio + + ImagePackEditorDialog + + + Editing image pack + + + + + Add images + + + + + Stickers (*.png *.webp) + + + + + State key + + + + + Packname + + + + + Attribution + + + + + + Use as Emoji + + + + + + Use as Sticker + + + + + Shortcode + + + + + Body + + + + + Cancel + Annulla + + + + Save + + + ImagePackSettingsDialog @@ -612,7 +675,17 @@ - + + Create account pack + + + + + New room pack + + + + Private pack @@ -627,7 +700,7 @@ - + Enable globally @@ -637,7 +710,12 @@ - + + Edit + Modifica + + + Close Chiudi @@ -811,7 +889,7 @@ Esempio: https://server.mio:8787 MessageDelegate - + removed rimosso @@ -822,7 +900,7 @@ Esempio: https://server.mio:8787 Crittografia abilitata - + room name changed to: %1 nome della stanza cambiato in: %1 @@ -905,7 +983,7 @@ Esempio: https://server.mio:8787 Scrivi un messaggio… - + Stickers @@ -928,7 +1006,7 @@ Esempio: https://server.mio:8787 MessageView - + Edit Modifica @@ -948,7 +1026,7 @@ Esempio: https://server.mio:8787 Opzioni - + &Copy @@ -1180,21 +1258,37 @@ Verificare %1 adesso? + + ReadReceipts + + + Read receipts + Ricevute di lettura + + + + ReadReceiptsModel + + + Yesterday, %1 + + + RegisterPage - + Username Nome utente - + The username must not be empty, and must contain only the characters a-z, 0-9, ., _, =, -, and /. Il nome utente non deve essere vuoto e deve contenere solo i caratteri a-z, 0-9, ., _, =, -, e /. - + Password Password @@ -1214,7 +1308,7 @@ Verificare %1 adesso? Homeserver - + A server that allows registration. Since matrix is decentralized, you need to first find a server you can register on or host your own. Un server che consente la registrazione. Siccome matrix è decentralizzata, devi prima trovare un server su cui registrarti o ospitarne uno tuo. @@ -1224,22 +1318,27 @@ Verificare %1 adesso? REGISTRATI - + No supported registration flows! Non ci sono processi di registrazione supportati! - - One or more fields have invalid inputs. Please correct those issues and try again. + + Registration token - + + Please enter a valid registration token. + + + + Autodiscovery failed. Received malformed response. Ricerca automatica fallita. Ricevuta risposta malformata. - + Autodiscovery failed. Unknown error when requesting .well-known. Ricerca automatica fallita. Errore ignoto durante la richiesta di .well-known. @@ -1249,7 +1348,7 @@ Verificare %1 adesso? Gli endpoint richiesti non sono stati trovati. Forse non è un server Matrix. - + Received malformed response. Make sure the homeserver domain is valid. Ricevuta risposta malformata. Assicurati che il dominio dell'homeserver sia valido. @@ -1259,17 +1358,17 @@ Verificare %1 adesso? Avvenuto un errore sconosciuto. Assicurati che il dominio dell'homeserver sia valido. - + Password is not long enough (min 8 chars) La password non è abbastanza lunga (minimo 8 caratteri) - + Passwords don't match Le password non corrispondono - + Invalid server name Nome del server non valido @@ -1277,7 +1376,7 @@ Verificare %1 adesso? ReplyPopup - + Close Chiudi @@ -1290,7 +1389,7 @@ Verificare %1 adesso? RoomInfo - + no version stored nessuna versione memorizzata @@ -1298,7 +1397,7 @@ Verificare %1 adesso? RoomList - + New tag @@ -1348,7 +1447,7 @@ Verificare %1 adesso? - + Status Message @@ -1368,7 +1467,7 @@ Verificare %1 adesso? - + Logout Disconnettiti @@ -1401,7 +1500,7 @@ Verificare %1 adesso? RoomMembers - + Members of %1 @@ -1419,11 +1518,31 @@ Verificare %1 adesso? Invite more people + + + This room is not encrypted! + + + + + This user is verified. + + + + + This user isn't verified, but is still using the same master key from the first time you met. + + + + + This user has unverified devices! + + RoomSettings - + Room Settings @@ -1458,7 +1577,12 @@ Verificare %1 adesso? - + + Room access + + + + Anyone and guests @@ -1622,6 +1746,30 @@ Verificare %1 adesso? Annulla + + SingleImagePackModel + + + + Failed to update image pack: {} + + + + + Failed to delete old image pack: {} + + + + + Failed to open image: {} + + + + + Failed to upload image: {} + + + StatusIndicator @@ -1674,7 +1822,7 @@ Verificare %1 adesso? TimelineModel - + Message redaction failed: %1 Oscuramento del messaggio fallito: %1 @@ -1705,7 +1853,7 @@ Verificare %1 adesso? Salva file - + %1 and %2 are typing. Multiple users are typing. First argument is a comma separated list of potentially multiple users. Second argument is the last user of that list. (If only one user is typing, %1 is empty. You should still use it in your string though to silence Qt warnings.) @@ -1819,12 +1967,12 @@ Verificare %1 adesso? %1 ha oscurato la sua bussata. - + You joined this room. Sei entrato in questa stanza. - + %1 has changed their avatar and changed their display name to %2. @@ -1853,7 +2001,7 @@ Verificare %1 adesso? TimelineRow - + Edited @@ -1861,12 +2009,12 @@ Verificare %1 adesso? TimelineView - + No room open Nessuna stanza aperta - + %1 member(s) @@ -1894,7 +2042,7 @@ Verificare %1 adesso? TimelineViewManager - + No encrypted private chat found with this user. Create an encrypted private chat with this user and try again. @@ -1902,17 +2050,37 @@ Verificare %1 adesso? TopBar - + Back to room list - + No room selected - + + This room is not encrypted! + + + + + This room contains only verified devices. + + + + + This rooms contain verified devices and devices which have never changed their master key. + + + + + This room contains unverified devices! + + + + Room options Opzioni della stanza @@ -1953,7 +2121,7 @@ Verificare %1 adesso? UserProfile - + Global User Profile @@ -1963,7 +2131,7 @@ Verificare %1 adesso? - + Verify @@ -2012,8 +2180,8 @@ Verificare %1 adesso? UserSettings - - + + Default @@ -2021,7 +2189,7 @@ Verificare %1 adesso? UserSettingsPage - + Minimize to tray Minimizza nella tray @@ -2041,12 +2209,12 @@ Verificare %1 adesso? Avatar Circolari - + profile: %1 - + Default @@ -2232,12 +2400,27 @@ This usually causes the application icon in the task bar to animate in some fash - + + Send encrypted messages to verified users only + + + + + Requires a user to be verified to send encrypted messages to them. This improves safety but makes E2EE more tedious. + + + + Share keys with verified users and devices - + + Automatically replies to key requests from other users, if they are verified, even if that device shouldn't have access to those keys otherwise. + + + + CACHED @@ -2247,7 +2430,7 @@ This usually causes the application icon in the task bar to animate in some fash - + Scale factor Fattore di scala @@ -2342,12 +2525,12 @@ This usually causes the application icon in the task bar to animate in some fash CRITTOGRAFIA - + GENERAL GENERALE - + INTERFACE INTERFACCIA @@ -2367,12 +2550,7 @@ This usually causes the application icon in the task bar to animate in some fash Famiglia dei caratteri delle Emoji - - Automatically replies to key requests from other users, if they are verified. - - - - + Master signing key @@ -2422,7 +2600,7 @@ This usually causes the application icon in the task bar to animate in some fash Tutti i File (*) - + Open Sessions File Apri File delle Sessioni @@ -2673,32 +2851,6 @@ Peso media: %2 Risolvi il reCAPTCHA e premi il pulsante di conferma - - dialogs::ReadReceipts - - - Read receipts - Ricevute di lettura - - - - Close - Chiudi - - - - dialogs::ReceiptItem - - - Today %1 - Oggi %1 - - - - Yesterday %1 - Ieri %1 - - message-description sent: diff --git a/resources/langs/nheko_ja.ts b/resources/langs/nheko_ja.ts index 1ec862b0..52260a23 100644 --- a/resources/langs/nheko_ja.ts +++ b/resources/langs/nheko_ja.ts @@ -56,7 +56,7 @@ CallInvite - + Video Call @@ -125,7 +125,7 @@ ChatPage - + Failed to invite user: %1 ユーザーを招待できませんでした: %1 @@ -157,12 +157,12 @@ - + Confirm invite - + Do you really want to invite %1 (%2)? @@ -227,12 +227,12 @@ 永久追放を解除されたユーザー: %1 - + Do you really want to start a private chat with %1? - + Cache migration failed! @@ -367,7 +367,7 @@ - + Decryption failed @@ -494,6 +494,49 @@ + + Encrypted + + + There is no key to unlock this message. We requested the key automatically, but you can try requesting it again if you are impatient. + + + + + This message couldn't be decrypted, because we only have a key for newer messages. You can try requesting access to this message. + + + + + There was an internal error reading the decryption key from the database. + + + + + There was an error decrypting this message. + + + + + The message couldn't be parsed. + + + + + The encryption key was reused! Someone is possibly trying to insert false messages into this chat! + + + + + Unknown decryption error + + + + + Request key + + + EncryptionIndicator @@ -517,51 +560,6 @@ - - EventStore - - - -- Encrypted Event (No keys found for decryption) -- - Placeholder, when the message was not decrypted yet or can't be decrypted. - -- 暗号化イベント (復号鍵が見つかりません) -- - - - - -- Encrypted Event (Key not valid for this index) -- - Placeholder, when the message can't be decrypted with this key since it is not valid for this index - - - - - - -- Decryption Error (failed to retrieve megolm keys from db) -- - Placeholder, when the message can't be decrypted, because the DB access failed. - -- 復号エラー (データベースからmegolm鍵を取得できませんでした) -- - - - - - -- Decryption Error (%1) -- - Placeholder, when the message can't be decrypted. In this case, the Olm decrytion returned an error, which is passed as %1. - -- 復号エラー (%1) -- - - - - -- Encrypted Event (Unknown event type) -- - Placeholder, when the message was decrypted, but we couldn't parse it, because Nheko/mtxclient don't support that event type yet. - -- 暗号化イベント (不明なイベント型です) -- - - - - -- Replay attack! This message index was reused! -- - - - - - -- Message by unverified device! -- - - - Failed @@ -604,6 +602,71 @@ + + ImagePackEditorDialog + + + Editing image pack + + + + + Add images + + + + + Stickers (*.png *.webp) + + + + + State key + + + + + Packname + + + + + Attribution + + + + + + Use as Emoji + + + + + + Use as Sticker + + + + + Shortcode + + + + + Body + + + + + Cancel + キャンセル + + + + Save + + + ImagePackSettingsDialog @@ -612,7 +675,17 @@ - + + Create account pack + + + + + New room pack + + + + Private pack @@ -627,7 +700,7 @@ - + Enable globally @@ -637,7 +710,12 @@ - + + Edit + + + + Close 閉じる @@ -807,7 +885,7 @@ Example: https://server.my:8787 MessageDelegate - + removed @@ -818,7 +896,7 @@ Example: https://server.my:8787 暗号化が有効です - + room name changed to: %1 部屋名が変更されました: %1 @@ -901,7 +979,7 @@ Example: https://server.my:8787 メッセージを書く... - + Stickers @@ -924,7 +1002,7 @@ Example: https://server.my:8787 MessageView - + Edit @@ -944,7 +1022,7 @@ Example: https://server.my:8787 オプション - + &Copy @@ -1175,21 +1253,37 @@ Example: https://server.my:8787 + + ReadReceipts + + + Read receipts + 開封確認 + + + + ReadReceiptsModel + + + Yesterday, %1 + + + RegisterPage - + Username ユーザー名 - + The username must not be empty, and must contain only the characters a-z, 0-9, ., _, =, -, and /. - + Password パスワード @@ -1209,7 +1303,7 @@ Example: https://server.my:8787 - + A server that allows registration. Since matrix is decentralized, you need to first find a server you can register on or host your own. @@ -1219,22 +1313,27 @@ Example: https://server.my:8787 登録 - + No supported registration flows! - - One or more fields have invalid inputs. Please correct those issues and try again. + + Registration token - + + Please enter a valid registration token. + + + + Autodiscovery failed. Received malformed response. 自動検出できませんでした。不正な形式の応答を受信しました。 - + Autodiscovery failed. Unknown error when requesting .well-known. 自動検出できませんでした。.well-known要求時の不明なエラー。 @@ -1244,7 +1343,7 @@ Example: https://server.my:8787 必要な端点が見つかりません。Matrixサーバーではないかもしれません。 - + Received malformed response. Make sure the homeserver domain is valid. 不正な形式の応答を受信しました。ホームサーバーのドメイン名が有効であるかを確認して下さい。 @@ -1254,17 +1353,17 @@ Example: https://server.my:8787 不明なエラーが発生しました。ホームサーバーのドメイン名が有効であるかを確認して下さい。 - + Password is not long enough (min 8 chars) パスワード長が不足しています (最小8文字) - + Passwords don't match パスワードが一致しません - + Invalid server name 無効なサーバー名です @@ -1272,7 +1371,7 @@ Example: https://server.my:8787 ReplyPopup - + Close 閉じる @@ -1285,7 +1384,7 @@ Example: https://server.my:8787 RoomInfo - + no version stored バージョンが保存されていません @@ -1293,7 +1392,7 @@ Example: https://server.my:8787 RoomList - + New tag @@ -1343,7 +1442,7 @@ Example: https://server.my:8787 - + Status Message @@ -1363,7 +1462,7 @@ Example: https://server.my:8787 - + Logout ログアウト @@ -1396,7 +1495,7 @@ Example: https://server.my:8787 RoomMembers - + Members of %1 @@ -1413,11 +1512,31 @@ Example: https://server.my:8787 Invite more people + + + This room is not encrypted! + + + + + This user is verified. + + + + + This user isn't verified, but is still using the same master key from the first time you met. + + + + + This user has unverified devices! + + RoomSettings - + Room Settings @@ -1452,7 +1571,12 @@ Example: https://server.my:8787 - + + Room access + + + + Anyone and guests @@ -1616,6 +1740,30 @@ Example: https://server.my:8787 キャンセル + + SingleImagePackModel + + + + Failed to update image pack: {} + + + + + Failed to delete old image pack: {} + + + + + Failed to open image: {} + + + + + Failed to upload image: {} + + + StatusIndicator @@ -1668,7 +1816,7 @@ Example: https://server.my:8787 TimelineModel - + Message redaction failed: %1 メッセージを編集できませんでした: %1 @@ -1699,7 +1847,7 @@ Example: https://server.my:8787 ファイルを保存 - + %1 and %2 are typing. Multiple users are typing. First argument is a comma separated list of potentially multiple users. Second argument is the last user of that list. (If only one user is typing, %1 is empty. You should still use it in your string though to silence Qt warnings.) @@ -1812,12 +1960,12 @@ Example: https://server.my:8787 %1がノックを編集しました。 - + You joined this room. - + %1 has changed their avatar and changed their display name to %2. @@ -1846,7 +1994,7 @@ Example: https://server.my:8787 TimelineRow - + Edited @@ -1854,12 +2002,12 @@ Example: https://server.my:8787 TimelineView - + No room open 部屋が開いていません - + %1 member(s) @@ -1887,7 +2035,7 @@ Example: https://server.my:8787 TimelineViewManager - + No encrypted private chat found with this user. Create an encrypted private chat with this user and try again. @@ -1895,17 +2043,37 @@ Example: https://server.my:8787 TopBar - + Back to room list - + No room selected - + + This room is not encrypted! + + + + + This room contains only verified devices. + + + + + This rooms contain verified devices and devices which have never changed their master key. + + + + + This room contains unverified devices! + + + + Room options 部屋のオプション @@ -1946,7 +2114,7 @@ Example: https://server.my:8787 UserProfile - + Global User Profile @@ -1956,7 +2124,7 @@ Example: https://server.my:8787 - + Verify @@ -2005,8 +2173,8 @@ Example: https://server.my:8787 UserSettings - - + + Default @@ -2014,7 +2182,7 @@ Example: https://server.my:8787 UserSettingsPage - + Minimize to tray トレイへ最小化 @@ -2034,12 +2202,12 @@ Example: https://server.my:8787 円形アバター - + profile: %1 - + Default @@ -2225,12 +2393,27 @@ This usually causes the application icon in the task bar to animate in some fash - + + Send encrypted messages to verified users only + + + + + Requires a user to be verified to send encrypted messages to them. This improves safety but makes E2EE more tedious. + + + + Share keys with verified users and devices - + + Automatically replies to key requests from other users, if they are verified, even if that device shouldn't have access to those keys otherwise. + + + + CACHED @@ -2240,7 +2423,7 @@ This usually causes the application icon in the task bar to animate in some fash - + Scale factor 尺度係数 @@ -2335,12 +2518,12 @@ This usually causes the application icon in the task bar to animate in some fash 暗号化 - + GENERAL 全般 - + INTERFACE @@ -2360,12 +2543,7 @@ This usually causes the application icon in the task bar to animate in some fash - - Automatically replies to key requests from other users, if they are verified. - - - - + Master signing key @@ -2415,7 +2593,7 @@ This usually causes the application icon in the task bar to animate in some fash 全てのファイル (*) - + Open Sessions File セッションファイルを開く @@ -2666,32 +2844,6 @@ Media size: %2 reCAPTCHAに解答して、確認ボタンを押して下さい - - dialogs::ReadReceipts - - - Read receipts - 開封確認 - - - - Close - 閉じる - - - - dialogs::ReceiptItem - - - Today %1 - 今日 %1 - - - - Yesterday %1 - 昨日 %1 - - message-description sent: diff --git a/resources/langs/nheko_ml.ts b/resources/langs/nheko_ml.ts index 011107c2..497491ca 100644 --- a/resources/langs/nheko_ml.ts +++ b/resources/langs/nheko_ml.ts @@ -56,7 +56,7 @@ CallInvite - + Video Call വീഡിയോ കോൾ @@ -125,7 +125,7 @@ ChatPage - + Failed to invite user: %1 ഉപയോക്താവിനെ ക്ഷണിക്കുന്നതിൽ പരാജയപ്പെട്ടു: %1 @@ -157,12 +157,12 @@ - + Confirm invite ക്ഷണം ഉറപ്പാക്കു - + Do you really want to invite %1 (%2)? @@ -227,12 +227,12 @@ - + Do you really want to start a private chat with %1? - + Cache migration failed! @@ -367,7 +367,7 @@ - + Decryption failed @@ -494,6 +494,49 @@ അവ പൊരുത്തപ്പെടുന്നു! + + Encrypted + + + There is no key to unlock this message. We requested the key automatically, but you can try requesting it again if you are impatient. + + + + + This message couldn't be decrypted, because we only have a key for newer messages. You can try requesting access to this message. + + + + + There was an internal error reading the decryption key from the database. + + + + + There was an error decrypting this message. + + + + + The message couldn't be parsed. + + + + + The encryption key was reused! Someone is possibly trying to insert false messages into this chat! + + + + + Unknown decryption error + + + + + Request key + + + EncryptionIndicator @@ -517,51 +560,6 @@ - - EventStore - - - -- Encrypted Event (No keys found for decryption) -- - Placeholder, when the message was not decrypted yet or can't be decrypted. - - - - - -- Encrypted Event (Key not valid for this index) -- - Placeholder, when the message can't be decrypted with this key since it is not valid for this index - - - - - - -- Decryption Error (failed to retrieve megolm keys from db) -- - Placeholder, when the message can't be decrypted, because the DB access failed. - - - - - - -- Decryption Error (%1) -- - Placeholder, when the message can't be decrypted. In this case, the Olm decrytion returned an error, which is passed as %1. - - - - - -- Encrypted Event (Unknown event type) -- - Placeholder, when the message was decrypted, but we couldn't parse it, because Nheko/mtxclient don't support that event type yet. - - - - - -- Replay attack! This message index was reused! -- - - - - - -- Message by unverified device! -- - - - Failed @@ -604,6 +602,71 @@ + + ImagePackEditorDialog + + + Editing image pack + + + + + Add images + + + + + Stickers (*.png *.webp) + + + + + State key + + + + + Packname + + + + + Attribution + + + + + + Use as Emoji + + + + + + Use as Sticker + + + + + Shortcode + + + + + Body + + + + + Cancel + റദ്ദാക്കു + + + + Save + + + ImagePackSettingsDialog @@ -612,7 +675,17 @@ - + + Create account pack + + + + + New room pack + + + + Private pack @@ -627,7 +700,7 @@ - + Enable globally @@ -637,7 +710,12 @@ - + + Edit + + + + Close അടയ്‌ക്കുക @@ -807,12 +885,12 @@ Example: https://server.my:8787 MessageDelegate - + Encryption enabled - + room name changed to: %1 @@ -867,13 +945,13 @@ Example: https://server.my:8787 - + removed നീക്കംചെയ്‌തു - + %1 ended the call. @@ -901,7 +979,7 @@ Example: https://server.my:8787 ഒരു സന്ദേശം എഴുതുക…. - + Stickers @@ -924,7 +1002,7 @@ Example: https://server.my:8787 MessageView - + Edit @@ -944,7 +1022,7 @@ Example: https://server.my:8787 - + &Copy @@ -1175,21 +1253,37 @@ Example: https://server.my:8787 + + ReadReceipts + + + Read receipts + + + + + ReadReceiptsModel + + + Yesterday, %1 + + + RegisterPage - + Username - + The username must not be empty, and must contain only the characters a-z, 0-9, ., _, =, -, and /. - + Password പാസ്‍വേഡ് @@ -1209,7 +1303,7 @@ Example: https://server.my:8787 - + A server that allows registration. Since matrix is decentralized, you need to first find a server you can register on or host your own. @@ -1219,22 +1313,27 @@ Example: https://server.my:8787 - + No supported registration flows! - - One or more fields have invalid inputs. Please correct those issues and try again. + + Registration token - + + Please enter a valid registration token. + + + + Autodiscovery failed. Received malformed response. - + Autodiscovery failed. Unknown error when requesting .well-known. @@ -1244,7 +1343,7 @@ Example: https://server.my:8787 - + Received malformed response. Make sure the homeserver domain is valid. @@ -1254,17 +1353,17 @@ Example: https://server.my:8787 - + Password is not long enough (min 8 chars) - + Passwords don't match - + Invalid server name @@ -1272,7 +1371,7 @@ Example: https://server.my:8787 ReplyPopup - + Close അടയ്‌ക്കുക @@ -1285,7 +1384,7 @@ Example: https://server.my:8787 RoomInfo - + no version stored @@ -1293,7 +1392,7 @@ Example: https://server.my:8787 RoomList - + New tag @@ -1343,7 +1442,7 @@ Example: https://server.my:8787 - + Status Message @@ -1363,7 +1462,7 @@ Example: https://server.my:8787 - + Logout @@ -1396,7 +1495,7 @@ Example: https://server.my:8787 RoomMembers - + Members of %1 @@ -1414,11 +1513,31 @@ Example: https://server.my:8787 Invite more people + + + This room is not encrypted! + + + + + This user is verified. + + + + + This user isn't verified, but is still using the same master key from the first time you met. + + + + + This user has unverified devices! + + RoomSettings - + Room Settings @@ -1453,7 +1572,12 @@ Example: https://server.my:8787 - + + Room access + + + + Anyone and guests @@ -1617,6 +1741,30 @@ Example: https://server.my:8787 റദ്ദാക്കു + + SingleImagePackModel + + + + Failed to update image pack: {} + + + + + Failed to delete old image pack: {} + + + + + Failed to open image: {} + + + + + Failed to upload image: {} + + + StatusIndicator @@ -1669,7 +1817,7 @@ Example: https://server.my:8787 TimelineModel - + Message redaction failed: %1 @@ -1700,7 +1848,7 @@ Example: https://server.my:8787 - + %1 and %2 are typing. Multiple users are typing. First argument is a comma separated list of potentially multiple users. Second argument is the last user of that list. (If only one user is typing, %1 is empty. You should still use it in your string though to silence Qt warnings.) @@ -1814,12 +1962,12 @@ Example: https://server.my:8787 - + You joined this room. നിങ്ങൾ ഈ മുറിയിൽ ചേർന്നു. - + %1 has changed their avatar and changed their display name to %2. @@ -1848,7 +1996,7 @@ Example: https://server.my:8787 TimelineRow - + Edited @@ -1856,12 +2004,12 @@ Example: https://server.my:8787 TimelineView - + No room open - + %1 member(s) @@ -1889,7 +2037,7 @@ Example: https://server.my:8787 TimelineViewManager - + No encrypted private chat found with this user. Create an encrypted private chat with this user and try again. @@ -1897,17 +2045,37 @@ Example: https://server.my:8787 TopBar - + Back to room list - + No room selected - + + This room is not encrypted! + + + + + This room contains only verified devices. + + + + + This rooms contain verified devices and devices which have never changed their master key. + + + + + This room contains unverified devices! + + + + Room options @@ -1948,7 +2116,7 @@ Example: https://server.my:8787 UserProfile - + Global User Profile @@ -1958,7 +2126,7 @@ Example: https://server.my:8787 - + Verify @@ -2007,8 +2175,8 @@ Example: https://server.my:8787 UserSettings - - + + Default @@ -2016,7 +2184,7 @@ Example: https://server.my:8787 UserSettingsPage - + Minimize to tray @@ -2036,12 +2204,12 @@ Example: https://server.my:8787 - + profile: %1 - + Default @@ -2227,12 +2395,27 @@ This usually causes the application icon in the task bar to animate in some fash - + + Send encrypted messages to verified users only + + + + + Requires a user to be verified to send encrypted messages to them. This improves safety but makes E2EE more tedious. + + + + Share keys with verified users and devices - + + Automatically replies to key requests from other users, if they are verified, even if that device shouldn't have access to those keys otherwise. + + + + CACHED @@ -2242,7 +2425,7 @@ This usually causes the application icon in the task bar to animate in some fash - + Scale factor @@ -2337,12 +2520,12 @@ This usually causes the application icon in the task bar to animate in some fash - + GENERAL - + INTERFACE @@ -2362,12 +2545,7 @@ This usually causes the application icon in the task bar to animate in some fash - - Automatically replies to key requests from other users, if they are verified. - - - - + Master signing key @@ -2417,7 +2595,7 @@ This usually causes the application icon in the task bar to animate in some fash - + Open Sessions File @@ -2666,32 +2844,6 @@ Media size: %2 - - dialogs::ReadReceipts - - - Read receipts - - - - - Close - അടയ്‌ക്കുക - - - - dialogs::ReceiptItem - - - Today %1 - - - - - Yesterday %1 - - - message-description sent: diff --git a/resources/langs/nheko_nl.ts b/resources/langs/nheko_nl.ts index b21db075..93236498 100644 --- a/resources/langs/nheko_nl.ts +++ b/resources/langs/nheko_nl.ts @@ -56,7 +56,7 @@ CallInvite - + Video Call @@ -125,7 +125,7 @@ ChatPage - + Failed to invite user: %1 Gebruiker uitnodigen mislukt: %1 @@ -157,12 +157,12 @@ - + Confirm invite - + Do you really want to invite %1 (%2)? @@ -227,12 +227,12 @@ - + Do you really want to start a private chat with %1? - + Cache migration failed! @@ -367,7 +367,7 @@ - + Decryption failed @@ -494,6 +494,49 @@ + + Encrypted + + + There is no key to unlock this message. We requested the key automatically, but you can try requesting it again if you are impatient. + + + + + This message couldn't be decrypted, because we only have a key for newer messages. You can try requesting access to this message. + + + + + There was an internal error reading the decryption key from the database. + + + + + There was an error decrypting this message. + + + + + The message couldn't be parsed. + + + + + The encryption key was reused! Someone is possibly trying to insert false messages into this chat! + + + + + Unknown decryption error + + + + + Request key + + + EncryptionIndicator @@ -517,51 +560,6 @@ - - EventStore - - - -- Encrypted Event (No keys found for decryption) -- - Placeholder, when the message was not decrypted yet or can't be decrypted. - - - - - -- Encrypted Event (Key not valid for this index) -- - Placeholder, when the message can't be decrypted with this key since it is not valid for this index - - - - - - -- Decryption Error (failed to retrieve megolm keys from db) -- - Placeholder, when the message can't be decrypted, because the DB access failed. - - - - - - -- Decryption Error (%1) -- - Placeholder, when the message can't be decrypted. In this case, the Olm decrytion returned an error, which is passed as %1. - - - - - -- Encrypted Event (Unknown event type) -- - Placeholder, when the message was decrypted, but we couldn't parse it, because Nheko/mtxclient don't support that event type yet. - - - - - -- Replay attack! This message index was reused! -- - - - - - -- Message by unverified device! -- - - - Failed @@ -604,6 +602,71 @@ + + ImagePackEditorDialog + + + Editing image pack + + + + + Add images + + + + + Stickers (*.png *.webp) + + + + + State key + + + + + Packname + + + + + Attribution + + + + + + Use as Emoji + + + + + + Use as Sticker + + + + + Shortcode + + + + + Body + + + + + Cancel + Annuleren + + + + Save + + + ImagePackSettingsDialog @@ -612,7 +675,17 @@ - + + Create account pack + + + + + New room pack + + + + Private pack @@ -627,7 +700,7 @@ - + Enable globally @@ -637,7 +710,12 @@ - + + Edit + + + + Close @@ -807,7 +885,7 @@ Example: https://server.my:8787 MessageDelegate - + removed @@ -818,7 +896,7 @@ Example: https://server.my:8787 - + room name changed to: %1 @@ -901,7 +979,7 @@ Example: https://server.my:8787 Typ een bericht... - + Stickers @@ -924,7 +1002,7 @@ Example: https://server.my:8787 MessageView - + Edit @@ -944,7 +1022,7 @@ Example: https://server.my:8787 - + &Copy @@ -1175,21 +1253,37 @@ Example: https://server.my:8787 + + ReadReceipts + + + Read receipts + Leesbevestigingen + + + + ReadReceiptsModel + + + Yesterday, %1 + + + RegisterPage - + Username Gebruikersnaam - + The username must not be empty, and must contain only the characters a-z, 0-9, ., _, =, -, and /. - + Password Wachtwoord @@ -1209,7 +1303,7 @@ Example: https://server.my:8787 - + A server that allows registration. Since matrix is decentralized, you need to first find a server you can register on or host your own. @@ -1219,22 +1313,27 @@ Example: https://server.my:8787 REGISTREREN - + No supported registration flows! - - One or more fields have invalid inputs. Please correct those issues and try again. + + Registration token - + + Please enter a valid registration token. + + + + Autodiscovery failed. Received malformed response. - + Autodiscovery failed. Unknown error when requesting .well-known. @@ -1244,7 +1343,7 @@ Example: https://server.my:8787 - + Received malformed response. Make sure the homeserver domain is valid. @@ -1254,17 +1353,17 @@ Example: https://server.my:8787 - + Password is not long enough (min 8 chars) Het wachtwoord is niet lang genoeg (minimaal 8 tekens) - + Passwords don't match De wachtwoorden komen niet overeen - + Invalid server name Ongeldige servernaam @@ -1272,7 +1371,7 @@ Example: https://server.my:8787 ReplyPopup - + Close @@ -1285,7 +1384,7 @@ Example: https://server.my:8787 RoomInfo - + no version stored @@ -1293,7 +1392,7 @@ Example: https://server.my:8787 RoomList - + New tag @@ -1343,7 +1442,7 @@ Example: https://server.my:8787 - + Status Message @@ -1363,7 +1462,7 @@ Example: https://server.my:8787 - + Logout @@ -1396,7 +1495,7 @@ Example: https://server.my:8787 RoomMembers - + Members of %1 @@ -1414,11 +1513,31 @@ Example: https://server.my:8787 Invite more people + + + This room is not encrypted! + + + + + This user is verified. + + + + + This user isn't verified, but is still using the same master key from the first time you met. + + + + + This user has unverified devices! + + RoomSettings - + Room Settings @@ -1453,7 +1572,12 @@ Example: https://server.my:8787 - + + Room access + + + + Anyone and guests @@ -1617,6 +1741,30 @@ Example: https://server.my:8787 Annuleren + + SingleImagePackModel + + + + Failed to update image pack: {} + + + + + Failed to delete old image pack: {} + + + + + Failed to open image: {} + + + + + Failed to upload image: {} + + + StatusIndicator @@ -1669,7 +1817,7 @@ Example: https://server.my:8787 TimelineModel - + Message redaction failed: %1 @@ -1700,7 +1848,7 @@ Example: https://server.my:8787 - + %1 and %2 are typing. Multiple users are typing. First argument is a comma separated list of potentially multiple users. Second argument is the last user of that list. (If only one user is typing, %1 is empty. You should still use it in your string though to silence Qt warnings.) @@ -1814,12 +1962,12 @@ Example: https://server.my:8787 - + You joined this room. Je bent lid geworden van deze kamer. - + %1 has changed their avatar and changed their display name to %2. @@ -1848,7 +1996,7 @@ Example: https://server.my:8787 TimelineRow - + Edited @@ -1856,12 +2004,12 @@ Example: https://server.my:8787 TimelineView - + No room open - + %1 member(s) @@ -1889,7 +2037,7 @@ Example: https://server.my:8787 TimelineViewManager - + No encrypted private chat found with this user. Create an encrypted private chat with this user and try again. @@ -1897,17 +2045,37 @@ Example: https://server.my:8787 TopBar - + Back to room list - + No room selected - + + This room is not encrypted! + + + + + This room contains only verified devices. + + + + + This rooms contain verified devices and devices which have never changed their master key. + + + + + This room contains unverified devices! + + + + Room options @@ -1948,7 +2116,7 @@ Example: https://server.my:8787 UserProfile - + Global User Profile @@ -1958,7 +2126,7 @@ Example: https://server.my:8787 - + Verify @@ -2007,8 +2175,8 @@ Example: https://server.my:8787 UserSettings - - + + Default @@ -2016,7 +2184,7 @@ Example: https://server.my:8787 UserSettingsPage - + Minimize to tray Minimaliseren naar systeemvak @@ -2036,12 +2204,12 @@ Example: https://server.my:8787 - + profile: %1 - + Default @@ -2227,12 +2395,27 @@ This usually causes the application icon in the task bar to animate in some fash - + + Send encrypted messages to verified users only + + + + + Requires a user to be verified to send encrypted messages to them. This improves safety but makes E2EE more tedious. + + + + Share keys with verified users and devices - + + Automatically replies to key requests from other users, if they are verified, even if that device shouldn't have access to those keys otherwise. + + + + CACHED @@ -2242,7 +2425,7 @@ This usually causes the application icon in the task bar to animate in some fash - + Scale factor @@ -2337,12 +2520,12 @@ This usually causes the application icon in the task bar to animate in some fash - + GENERAL ALGEMEEN - + INTERFACE @@ -2362,12 +2545,7 @@ This usually causes the application icon in the task bar to animate in some fash - - Automatically replies to key requests from other users, if they are verified. - - - - + Master signing key @@ -2417,7 +2595,7 @@ This usually causes the application icon in the task bar to animate in some fash Alle bestanden (*) - + Open Sessions File @@ -2668,32 +2846,6 @@ Mediagrootte: %2 Los de reCAPTCHA op en klik op 'Bevestigen' - - dialogs::ReadReceipts - - - Read receipts - Leesbevestigingen - - - - Close - - - - - dialogs::ReceiptItem - - - Today %1 - - - - - Yesterday %1 - - - message-description sent: diff --git a/resources/langs/nheko_pl.ts b/resources/langs/nheko_pl.ts index 72e4e771..33a44698 100644 --- a/resources/langs/nheko_pl.ts +++ b/resources/langs/nheko_pl.ts @@ -56,7 +56,7 @@ CallInvite - + Video Call @@ -125,7 +125,7 @@ ChatPage - + Failed to invite user: %1 Nie udało się zaprosić użytkownika: %1 @@ -157,12 +157,12 @@ - + Confirm invite - + Do you really want to invite %1 (%2)? Czy na pewno chcesz zaprosić %1 (%2)? @@ -227,12 +227,12 @@ Odblokowano użytkownika: %1 - + Do you really want to start a private chat with %1? - + Cache migration failed! Nie udało się przenieść pamięci podręcznej! @@ -367,7 +367,7 @@ - + Decryption failed @@ -494,6 +494,49 @@ Pasują! + + Encrypted + + + There is no key to unlock this message. We requested the key automatically, but you can try requesting it again if you are impatient. + + + + + This message couldn't be decrypted, because we only have a key for newer messages. You can try requesting access to this message. + + + + + There was an internal error reading the decryption key from the database. + + + + + There was an error decrypting this message. + + + + + The message couldn't be parsed. + + + + + The encryption key was reused! Someone is possibly trying to insert false messages into this chat! + + + + + Unknown decryption error + + + + + Request key + + + EncryptionIndicator @@ -517,51 +560,6 @@ - - EventStore - - - -- Encrypted Event (No keys found for decryption) -- - Placeholder, when the message was not decrypted yet or can't be decrypted. - -- Zdarzenie szyfrowania (Nie znaleziono kluczy deszyfrujących) - - - - -- Encrypted Event (Key not valid for this index) -- - Placeholder, when the message can't be decrypted with this key since it is not valid for this index - - - - - - -- Decryption Error (failed to retrieve megolm keys from db) -- - Placeholder, when the message can't be decrypted, because the DB access failed. - --Błąd deszyfrowania (nie udało się uzyskać kluczy megolm z bazy danych) -- - - - - - -- Decryption Error (%1) -- - Placeholder, when the message can't be decrypted. In this case, the Olm decrytion returned an error, which is passed as %1. - -- Błąd Deszyfracji (%1) -- - - - - -- Encrypted Event (Unknown event type) -- - Placeholder, when the message was decrypted, but we couldn't parse it, because Nheko/mtxclient don't support that event type yet. - -- Zdarzenie szyfrowania (Nieznany typ zdarzenia) -- - - - - -- Replay attack! This message index was reused! -- - -- Atak powtórzeniowy! Indeks tej wiadomości został użyty ponownie! -- - - - - -- Message by unverified device! -- - -- Wiadomość z niezweryfikowanego urządzenia! -- - - Failed @@ -604,6 +602,71 @@ + + ImagePackEditorDialog + + + Editing image pack + + + + + Add images + + + + + Stickers (*.png *.webp) + + + + + State key + + + + + Packname + + + + + Attribution + + + + + + Use as Emoji + + + + + + Use as Sticker + + + + + Shortcode + + + + + Body + + + + + Cancel + Anuluj + + + + Save + + + ImagePackSettingsDialog @@ -612,7 +675,17 @@ - + + Create account pack + + + + + New room pack + + + + Private pack @@ -627,7 +700,7 @@ - + Enable globally @@ -637,7 +710,12 @@ - + + Edit + + + + Close Zamknij @@ -809,7 +887,7 @@ Example: https://server.my:8787 MessageDelegate - + removed @@ -820,7 +898,7 @@ Example: https://server.my:8787 Szyfrowanie włączone - + room name changed to: %1 Nazwa pokoju zmieniona na: %1 @@ -903,7 +981,7 @@ Example: https://server.my:8787 Napisz wiadomość… - + Stickers @@ -926,7 +1004,7 @@ Example: https://server.my:8787 MessageView - + Edit @@ -946,7 +1024,7 @@ Example: https://server.my:8787 - + &Copy @@ -1177,21 +1255,37 @@ Example: https://server.my:8787 nazwa profilu + + ReadReceipts + + + Read receipts + Potwierdzenia przeczytania + + + + ReadReceiptsModel + + + Yesterday, %1 + + + RegisterPage - + Username Nazwa użytkownika - + The username must not be empty, and must contain only the characters a-z, 0-9, ., _, =, -, and /. Nazwa użytkownika nie może być pusta i może zawierać wyłącznie znaki a-z, 0-9, ., _, =, -, i /. - + Password Hasło @@ -1211,7 +1305,7 @@ Example: https://server.my:8787 Serwer domowy - + A server that allows registration. Since matrix is decentralized, you need to first find a server you can register on or host your own. Serwer, który pozwala na rejestrację. Ponieważ Matrix jest zdecentralizowany, musisz najpierw znaleźć serwer który pozwala na rejestrację bądź hostować swój własny. @@ -1221,22 +1315,27 @@ Example: https://server.my:8787 ZAREJESTRUJ - + No supported registration flows! Nie wspierana procedura rejestracji! - - One or more fields have invalid inputs. Please correct those issues and try again. + + Registration token - + + Please enter a valid registration token. + + + + Autodiscovery failed. Received malformed response. Automatyczne odkrywanie zakończone niepowodzeniem. Otrzymano nieprawidłową odpowiedź. - + Autodiscovery failed. Unknown error when requesting .well-known. Automatyczne odkrywanie zakończone niepowodzeniem. Napotkano nieznany błąd. .well-known. @@ -1246,7 +1345,7 @@ Example: https://server.my:8787 Nie odnaleziono wymaganych punktów końcowych. To może nie być serwer Matriksa. - + Received malformed response. Make sure the homeserver domain is valid. Otrzymano nieprawidłową odpowiedź. Upewnij się, że domena serwera domowego jest prawidłowa. @@ -1256,17 +1355,17 @@ Example: https://server.my:8787 Wystąpił nieznany błąd. Upewnij się, że domena serwera domowego jest prawidłowa. - + Password is not long enough (min 8 chars) Hasło jest zbyt krótkie (min. 8 znaków) - + Passwords don't match Hasła nie pasują do siebie - + Invalid server name Nieprawidłowa nazwa serwera @@ -1274,7 +1373,7 @@ Example: https://server.my:8787 ReplyPopup - + Close Zamknij @@ -1287,7 +1386,7 @@ Example: https://server.my:8787 RoomInfo - + no version stored @@ -1295,7 +1394,7 @@ Example: https://server.my:8787 RoomList - + New tag @@ -1345,7 +1444,7 @@ Example: https://server.my:8787 - + Status Message @@ -1365,7 +1464,7 @@ Example: https://server.my:8787 - + Logout Wyloguj @@ -1398,7 +1497,7 @@ Example: https://server.my:8787 RoomMembers - + Members of %1 @@ -1417,11 +1516,31 @@ Example: https://server.my:8787 Invite more people + + + This room is not encrypted! + + + + + This user is verified. + + + + + This user isn't verified, but is still using the same master key from the first time you met. + + + + + This user has unverified devices! + + RoomSettings - + Room Settings @@ -1456,7 +1575,12 @@ Example: https://server.my:8787 - + + Room access + + + + Anyone and guests @@ -1620,6 +1744,30 @@ Example: https://server.my:8787 Anuluj + + SingleImagePackModel + + + + Failed to update image pack: {} + + + + + Failed to delete old image pack: {} + + + + + Failed to open image: {} + + + + + Failed to upload image: {} + + + StatusIndicator @@ -1672,7 +1820,7 @@ Example: https://server.my:8787 TimelineModel - + Message redaction failed: %1 Redagowanie wiadomości nie powiodło się: %1 @@ -1703,7 +1851,7 @@ Example: https://server.my:8787 - + %1 and %2 are typing. Multiple users are typing. First argument is a comma separated list of potentially multiple users. Second argument is the last user of that list. (If only one user is typing, %1 is empty. You should still use it in your string though to silence Qt warnings.) @@ -1818,12 +1966,12 @@ Example: https://server.my:8787 - + You joined this room. Dołączyłeś(-łaś) do tego pokoju. - + %1 has changed their avatar and changed their display name to %2. @@ -1852,7 +2000,7 @@ Example: https://server.my:8787 TimelineRow - + Edited @@ -1860,12 +2008,12 @@ Example: https://server.my:8787 TimelineView - + No room open - + %1 member(s) @@ -1893,7 +2041,7 @@ Example: https://server.my:8787 TimelineViewManager - + No encrypted private chat found with this user. Create an encrypted private chat with this user and try again. @@ -1901,17 +2049,37 @@ Example: https://server.my:8787 TopBar - + Back to room list - + No room selected - + + This room is not encrypted! + + + + + This room contains only verified devices. + + + + + This rooms contain verified devices and devices which have never changed their master key. + + + + + This room contains unverified devices! + + + + Room options Ustawienia pokoju @@ -1952,7 +2120,7 @@ Example: https://server.my:8787 UserProfile - + Global User Profile @@ -1962,7 +2130,7 @@ Example: https://server.my:8787 - + Verify @@ -2011,8 +2179,8 @@ Example: https://server.my:8787 UserSettings - - + + Default @@ -2020,7 +2188,7 @@ Example: https://server.my:8787 UserSettingsPage - + Minimize to tray Zminimalizuj do paska zadań @@ -2040,12 +2208,12 @@ Example: https://server.my:8787 - + profile: %1 - + Default @@ -2231,12 +2399,27 @@ This usually causes the application icon in the task bar to animate in some fash - + + Send encrypted messages to verified users only + + + + + Requires a user to be verified to send encrypted messages to them. This improves safety but makes E2EE more tedious. + + + + Share keys with verified users and devices - + + Automatically replies to key requests from other users, if they are verified, even if that device shouldn't have access to those keys otherwise. + + + + CACHED @@ -2246,7 +2429,7 @@ This usually causes the application icon in the task bar to animate in some fash - + Scale factor @@ -2341,12 +2524,12 @@ This usually causes the application icon in the task bar to animate in some fash SZYFROWANIE - + GENERAL OGÓLNE - + INTERFACE @@ -2366,12 +2549,7 @@ This usually causes the application icon in the task bar to animate in some fash - - Automatically replies to key requests from other users, if they are verified. - - - - + Master signing key @@ -2421,7 +2599,7 @@ This usually causes the application icon in the task bar to animate in some fash Wszystkie pliki (*) - + Open Sessions File @@ -2672,32 +2850,6 @@ Rozmiar multimediów: %2 Rozwiąż reCAPTCHA i naciśnij przycisk „potwierdź” - - dialogs::ReadReceipts - - - Read receipts - Potwierdzenia przeczytania - - - - Close - Zamknij - - - - dialogs::ReceiptItem - - - Today %1 - - - - - Yesterday %1 - - - message-description sent: diff --git a/resources/langs/nheko_pt_BR.ts b/resources/langs/nheko_pt_BR.ts index b83585fb..0ea650d1 100644 --- a/resources/langs/nheko_pt_BR.ts +++ b/resources/langs/nheko_pt_BR.ts @@ -56,7 +56,7 @@ CallInvite - + Video Call Chamada de Vídeo @@ -125,7 +125,7 @@ ChatPage - + Failed to invite user: %1 Falha ao convidar usuário: %1 @@ -157,12 +157,12 @@ - + Confirm invite Confirmar convite - + Do you really want to invite %1 (%2)? @@ -227,12 +227,12 @@ Usuário desbanido: %1 - + Do you really want to start a private chat with %1? - + Cache migration failed! Migração do cache falhou! @@ -367,7 +367,7 @@ - + Decryption failed @@ -494,6 +494,49 @@ + + Encrypted + + + There is no key to unlock this message. We requested the key automatically, but you can try requesting it again if you are impatient. + + + + + This message couldn't be decrypted, because we only have a key for newer messages. You can try requesting access to this message. + + + + + There was an internal error reading the decryption key from the database. + + + + + There was an error decrypting this message. + + + + + The message couldn't be parsed. + + + + + The encryption key was reused! Someone is possibly trying to insert false messages into this chat! + + + + + Unknown decryption error + + + + + Request key + + + EncryptionIndicator @@ -517,51 +560,6 @@ - - EventStore - - - -- Encrypted Event (No keys found for decryption) -- - Placeholder, when the message was not decrypted yet or can't be decrypted. - - - - - -- Encrypted Event (Key not valid for this index) -- - Placeholder, when the message can't be decrypted with this key since it is not valid for this index - - - - - - -- Decryption Error (failed to retrieve megolm keys from db) -- - Placeholder, when the message can't be decrypted, because the DB access failed. - - - - - - -- Decryption Error (%1) -- - Placeholder, when the message can't be decrypted. In this case, the Olm decrytion returned an error, which is passed as %1. - - - - - -- Encrypted Event (Unknown event type) -- - Placeholder, when the message was decrypted, but we couldn't parse it, because Nheko/mtxclient don't support that event type yet. - - - - - -- Replay attack! This message index was reused! -- - - - - - -- Message by unverified device! -- - - - Failed @@ -604,6 +602,71 @@ + + ImagePackEditorDialog + + + Editing image pack + + + + + Add images + + + + + Stickers (*.png *.webp) + + + + + State key + + + + + Packname + + + + + Attribution + + + + + + Use as Emoji + + + + + + Use as Sticker + + + + + Shortcode + + + + + Body + + + + + Cancel + Cancelar + + + + Save + + + ImagePackSettingsDialog @@ -612,7 +675,17 @@ - + + Create account pack + + + + + New room pack + + + + Private pack @@ -627,7 +700,7 @@ - + Enable globally @@ -637,7 +710,12 @@ - + + Edit + + + + Close @@ -807,12 +885,12 @@ Example: https://server.my:8787 MessageDelegate - + Encryption enabled - + room name changed to: %1 @@ -867,13 +945,13 @@ Example: https://server.my:8787 - + removed - + %1 ended the call. @@ -901,7 +979,7 @@ Example: https://server.my:8787 - + Stickers @@ -924,7 +1002,7 @@ Example: https://server.my:8787 MessageView - + Edit @@ -944,7 +1022,7 @@ Example: https://server.my:8787 - + &Copy @@ -1175,21 +1253,37 @@ Example: https://server.my:8787 + + ReadReceipts + + + Read receipts + + + + + ReadReceiptsModel + + + Yesterday, %1 + + + RegisterPage - + Username - + The username must not be empty, and must contain only the characters a-z, 0-9, ., _, =, -, and /. - + Password @@ -1209,7 +1303,7 @@ Example: https://server.my:8787 - + A server that allows registration. Since matrix is decentralized, you need to first find a server you can register on or host your own. @@ -1219,22 +1313,27 @@ Example: https://server.my:8787 - + No supported registration flows! - - One or more fields have invalid inputs. Please correct those issues and try again. + + Registration token - + + Please enter a valid registration token. + + + + Autodiscovery failed. Received malformed response. - + Autodiscovery failed. Unknown error when requesting .well-known. @@ -1244,7 +1343,7 @@ Example: https://server.my:8787 - + Received malformed response. Make sure the homeserver domain is valid. @@ -1254,17 +1353,17 @@ Example: https://server.my:8787 - + Password is not long enough (min 8 chars) - + Passwords don't match - + Invalid server name @@ -1272,7 +1371,7 @@ Example: https://server.my:8787 ReplyPopup - + Close @@ -1285,7 +1384,7 @@ Example: https://server.my:8787 RoomInfo - + no version stored @@ -1293,7 +1392,7 @@ Example: https://server.my:8787 RoomList - + New tag @@ -1343,7 +1442,7 @@ Example: https://server.my:8787 - + Status Message @@ -1363,7 +1462,7 @@ Example: https://server.my:8787 - + Logout @@ -1396,7 +1495,7 @@ Example: https://server.my:8787 RoomMembers - + Members of %1 @@ -1414,11 +1513,31 @@ Example: https://server.my:8787 Invite more people + + + This room is not encrypted! + + + + + This user is verified. + + + + + This user isn't verified, but is still using the same master key from the first time you met. + + + + + This user has unverified devices! + + RoomSettings - + Room Settings @@ -1453,7 +1572,12 @@ Example: https://server.my:8787 - + + Room access + + + + Anyone and guests @@ -1617,6 +1741,30 @@ Example: https://server.my:8787 Cancelar + + SingleImagePackModel + + + + Failed to update image pack: {} + + + + + Failed to delete old image pack: {} + + + + + Failed to open image: {} + + + + + Failed to upload image: {} + + + StatusIndicator @@ -1669,7 +1817,7 @@ Example: https://server.my:8787 TimelineModel - + Message redaction failed: %1 @@ -1700,7 +1848,7 @@ Example: https://server.my:8787 - + %1 and %2 are typing. Multiple users are typing. First argument is a comma separated list of potentially multiple users. Second argument is the last user of that list. (If only one user is typing, %1 is empty. You should still use it in your string though to silence Qt warnings.) @@ -1814,12 +1962,12 @@ Example: https://server.my:8787 - + You joined this room. Você entrou nessa sala. - + %1 has changed their avatar and changed their display name to %2. @@ -1848,7 +1996,7 @@ Example: https://server.my:8787 TimelineRow - + Edited @@ -1856,12 +2004,12 @@ Example: https://server.my:8787 TimelineView - + No room open - + %1 member(s) @@ -1889,7 +2037,7 @@ Example: https://server.my:8787 TimelineViewManager - + No encrypted private chat found with this user. Create an encrypted private chat with this user and try again. @@ -1897,17 +2045,37 @@ Example: https://server.my:8787 TopBar - + Back to room list - + No room selected - + + This room is not encrypted! + + + + + This room contains only verified devices. + + + + + This rooms contain verified devices and devices which have never changed their master key. + + + + + This room contains unverified devices! + + + + Room options @@ -1948,7 +2116,7 @@ Example: https://server.my:8787 UserProfile - + Global User Profile @@ -1958,7 +2126,7 @@ Example: https://server.my:8787 - + Verify @@ -2007,8 +2175,8 @@ Example: https://server.my:8787 UserSettings - - + + Default @@ -2016,7 +2184,7 @@ Example: https://server.my:8787 UserSettingsPage - + Minimize to tray @@ -2036,12 +2204,12 @@ Example: https://server.my:8787 - + profile: %1 - + Default @@ -2227,12 +2395,27 @@ This usually causes the application icon in the task bar to animate in some fash - + + Send encrypted messages to verified users only + + + + + Requires a user to be verified to send encrypted messages to them. This improves safety but makes E2EE more tedious. + + + + Share keys with verified users and devices - + + Automatically replies to key requests from other users, if they are verified, even if that device shouldn't have access to those keys otherwise. + + + + CACHED @@ -2242,7 +2425,7 @@ This usually causes the application icon in the task bar to animate in some fash - + Scale factor @@ -2337,12 +2520,12 @@ This usually causes the application icon in the task bar to animate in some fash - + GENERAL - + INTERFACE @@ -2362,12 +2545,7 @@ This usually causes the application icon in the task bar to animate in some fash - - Automatically replies to key requests from other users, if they are verified. - - - - + Master signing key @@ -2417,7 +2595,7 @@ This usually causes the application icon in the task bar to animate in some fash - + Open Sessions File @@ -2666,32 +2844,6 @@ Media size: %2 - - dialogs::ReadReceipts - - - Read receipts - - - - - Close - - - - - dialogs::ReceiptItem - - - Today %1 - - - - - Yesterday %1 - - - message-description sent: diff --git a/resources/langs/nheko_pt_PT.ts b/resources/langs/nheko_pt_PT.ts index a0a8c8a8..ef7e46d1 100644 --- a/resources/langs/nheko_pt_PT.ts +++ b/resources/langs/nheko_pt_PT.ts @@ -56,7 +56,7 @@ CallInvite - + Video Call @@ -125,7 +125,7 @@ ChatPage - + Failed to invite user: %1 @@ -157,12 +157,12 @@ - + Confirm invite - + Do you really want to invite %1 (%2)? @@ -227,12 +227,12 @@ - + Do you really want to start a private chat with %1? - + Cache migration failed! @@ -367,7 +367,7 @@ - + Decryption failed @@ -494,6 +494,49 @@ + + Encrypted + + + There is no key to unlock this message. We requested the key automatically, but you can try requesting it again if you are impatient. + + + + + This message couldn't be decrypted, because we only have a key for newer messages. You can try requesting access to this message. + + + + + There was an internal error reading the decryption key from the database. + + + + + There was an error decrypting this message. + + + + + The message couldn't be parsed. + + + + + The encryption key was reused! Someone is possibly trying to insert false messages into this chat! + + + + + Unknown decryption error + + + + + Request key + + + EncryptionIndicator @@ -517,51 +560,6 @@ - - EventStore - - - -- Encrypted Event (No keys found for decryption) -- - Placeholder, when the message was not decrypted yet or can't be decrypted. - - - - - -- Encrypted Event (Key not valid for this index) -- - Placeholder, when the message can't be decrypted with this key since it is not valid for this index - - - - - - -- Decryption Error (failed to retrieve megolm keys from db) -- - Placeholder, when the message can't be decrypted, because the DB access failed. - - - - - - -- Decryption Error (%1) -- - Placeholder, when the message can't be decrypted. In this case, the Olm decrytion returned an error, which is passed as %1. - - - - - -- Encrypted Event (Unknown event type) -- - Placeholder, when the message was decrypted, but we couldn't parse it, because Nheko/mtxclient don't support that event type yet. - - - - - -- Replay attack! This message index was reused! -- - - - - - -- Message by unverified device! -- - - - Failed @@ -604,6 +602,71 @@ + + ImagePackEditorDialog + + + Editing image pack + + + + + Add images + + + + + Stickers (*.png *.webp) + + + + + State key + + + + + Packname + + + + + Attribution + + + + + + Use as Emoji + + + + + + Use as Sticker + + + + + Shortcode + + + + + Body + + + + + Cancel + + + + + Save + + + ImagePackSettingsDialog @@ -612,7 +675,17 @@ - + + Create account pack + + + + + New room pack + + + + Private pack @@ -627,7 +700,7 @@ - + Enable globally @@ -637,7 +710,12 @@ - + + Edit + + + + Close @@ -807,12 +885,12 @@ Example: https://server.my:8787 MessageDelegate - + Encryption enabled - + room name changed to: %1 @@ -867,13 +945,13 @@ Example: https://server.my:8787 - + removed - + %1 ended the call. @@ -901,7 +979,7 @@ Example: https://server.my:8787 - + Stickers @@ -924,7 +1002,7 @@ Example: https://server.my:8787 MessageView - + Edit @@ -944,7 +1022,7 @@ Example: https://server.my:8787 - + &Copy @@ -1175,21 +1253,37 @@ Example: https://server.my:8787 + + ReadReceipts + + + Read receipts + + + + + ReadReceiptsModel + + + Yesterday, %1 + + + RegisterPage - + Username - + The username must not be empty, and must contain only the characters a-z, 0-9, ., _, =, -, and /. - + Password @@ -1209,7 +1303,7 @@ Example: https://server.my:8787 - + A server that allows registration. Since matrix is decentralized, you need to first find a server you can register on or host your own. @@ -1219,22 +1313,27 @@ Example: https://server.my:8787 - + No supported registration flows! - - One or more fields have invalid inputs. Please correct those issues and try again. + + Registration token - + + Please enter a valid registration token. + + + + Autodiscovery failed. Received malformed response. - + Autodiscovery failed. Unknown error when requesting .well-known. @@ -1244,7 +1343,7 @@ Example: https://server.my:8787 - + Received malformed response. Make sure the homeserver domain is valid. @@ -1254,17 +1353,17 @@ Example: https://server.my:8787 - + Password is not long enough (min 8 chars) - + Passwords don't match - + Invalid server name @@ -1272,7 +1371,7 @@ Example: https://server.my:8787 ReplyPopup - + Close @@ -1285,7 +1384,7 @@ Example: https://server.my:8787 RoomInfo - + no version stored @@ -1293,7 +1392,7 @@ Example: https://server.my:8787 RoomList - + New tag @@ -1343,7 +1442,7 @@ Example: https://server.my:8787 - + Status Message @@ -1363,7 +1462,7 @@ Example: https://server.my:8787 - + Logout @@ -1396,7 +1495,7 @@ Example: https://server.my:8787 RoomMembers - + Members of %1 @@ -1414,11 +1513,31 @@ Example: https://server.my:8787 Invite more people + + + This room is not encrypted! + + + + + This user is verified. + + + + + This user isn't verified, but is still using the same master key from the first time you met. + + + + + This user has unverified devices! + + RoomSettings - + Room Settings @@ -1453,7 +1572,12 @@ Example: https://server.my:8787 - + + Room access + + + + Anyone and guests @@ -1617,6 +1741,30 @@ Example: https://server.my:8787 + + SingleImagePackModel + + + + Failed to update image pack: {} + + + + + Failed to delete old image pack: {} + + + + + Failed to open image: {} + + + + + Failed to upload image: {} + + + StatusIndicator @@ -1669,7 +1817,7 @@ Example: https://server.my:8787 TimelineModel - + Message redaction failed: %1 @@ -1700,7 +1848,7 @@ Example: https://server.my:8787 - + %1 and %2 are typing. Multiple users are typing. First argument is a comma separated list of potentially multiple users. Second argument is the last user of that list. (If only one user is typing, %1 is empty. You should still use it in your string though to silence Qt warnings.) @@ -1814,12 +1962,12 @@ Example: https://server.my:8787 - + You joined this room. - + %1 has changed their avatar and changed their display name to %2. @@ -1848,7 +1996,7 @@ Example: https://server.my:8787 TimelineRow - + Edited @@ -1856,12 +2004,12 @@ Example: https://server.my:8787 TimelineView - + No room open - + %1 member(s) @@ -1889,7 +2037,7 @@ Example: https://server.my:8787 TimelineViewManager - + No encrypted private chat found with this user. Create an encrypted private chat with this user and try again. @@ -1897,17 +2045,37 @@ Example: https://server.my:8787 TopBar - + Back to room list - + No room selected - + + This room is not encrypted! + + + + + This room contains only verified devices. + + + + + This rooms contain verified devices and devices which have never changed their master key. + + + + + This room contains unverified devices! + + + + Room options @@ -1948,7 +2116,7 @@ Example: https://server.my:8787 UserProfile - + Global User Profile @@ -1958,7 +2126,7 @@ Example: https://server.my:8787 - + Verify @@ -2007,8 +2175,8 @@ Example: https://server.my:8787 UserSettings - - + + Default @@ -2016,7 +2184,7 @@ Example: https://server.my:8787 UserSettingsPage - + Minimize to tray @@ -2036,12 +2204,12 @@ Example: https://server.my:8787 - + profile: %1 - + Default @@ -2227,12 +2395,27 @@ This usually causes the application icon in the task bar to animate in some fash - + + Send encrypted messages to verified users only + + + + + Requires a user to be verified to send encrypted messages to them. This improves safety but makes E2EE more tedious. + + + + Share keys with verified users and devices - + + Automatically replies to key requests from other users, if they are verified, even if that device shouldn't have access to those keys otherwise. + + + + CACHED @@ -2242,7 +2425,7 @@ This usually causes the application icon in the task bar to animate in some fash - + Scale factor @@ -2337,12 +2520,12 @@ This usually causes the application icon in the task bar to animate in some fash - + GENERAL - + INTERFACE @@ -2362,12 +2545,7 @@ This usually causes the application icon in the task bar to animate in some fash - - Automatically replies to key requests from other users, if they are verified. - - - - + Master signing key @@ -2417,7 +2595,7 @@ This usually causes the application icon in the task bar to animate in some fash - + Open Sessions File @@ -2666,32 +2844,6 @@ Media size: %2 - - dialogs::ReadReceipts - - - Read receipts - - - - - Close - - - - - dialogs::ReceiptItem - - - Today %1 - - - - - Yesterday %1 - - - message-description sent: diff --git a/resources/langs/nheko_ro.ts b/resources/langs/nheko_ro.ts index 6ea496f1..c05b5565 100644 --- a/resources/langs/nheko_ro.ts +++ b/resources/langs/nheko_ro.ts @@ -56,7 +56,7 @@ CallInvite - + Video Call @@ -125,7 +125,7 @@ ChatPage - + Failed to invite user: %1 Nu s-a putut invita utilizatorul: %1 @@ -157,12 +157,12 @@ - + Confirm invite - + Do you really want to invite %1 (%2)? @@ -227,12 +227,12 @@ Utilizator dezinterzis: %1 - + Do you really want to start a private chat with %1? - + Cache migration failed! Nu s-a putut migra cache-ul! @@ -367,7 +367,7 @@ - + Decryption failed @@ -494,6 +494,49 @@ + + Encrypted + + + There is no key to unlock this message. We requested the key automatically, but you can try requesting it again if you are impatient. + + + + + This message couldn't be decrypted, because we only have a key for newer messages. You can try requesting access to this message. + + + + + There was an internal error reading the decryption key from the database. + + + + + There was an error decrypting this message. + + + + + The message couldn't be parsed. + + + + + The encryption key was reused! Someone is possibly trying to insert false messages into this chat! + + + + + Unknown decryption error + + + + + Request key + + + EncryptionIndicator @@ -517,51 +560,6 @@ - - EventStore - - - -- Encrypted Event (No keys found for decryption) -- - Placeholder, when the message was not decrypted yet or can't be decrypted. - - - - - -- Encrypted Event (Key not valid for this index) -- - Placeholder, when the message can't be decrypted with this key since it is not valid for this index - - - - - - -- Decryption Error (failed to retrieve megolm keys from db) -- - Placeholder, when the message can't be decrypted, because the DB access failed. - - - - - - -- Decryption Error (%1) -- - Placeholder, when the message can't be decrypted. In this case, the Olm decrytion returned an error, which is passed as %1. - - - - - -- Encrypted Event (Unknown event type) -- - Placeholder, when the message was decrypted, but we couldn't parse it, because Nheko/mtxclient don't support that event type yet. - - - - - -- Replay attack! This message index was reused! -- - - - - - -- Message by unverified device! -- - - - Failed @@ -604,6 +602,71 @@ + + ImagePackEditorDialog + + + Editing image pack + + + + + Add images + + + + + Stickers (*.png *.webp) + + + + + State key + + + + + Packname + + + + + Attribution + + + + + + Use as Emoji + + + + + + Use as Sticker + + + + + Shortcode + + + + + Body + + + + + Cancel + + + + + Save + + + ImagePackSettingsDialog @@ -612,7 +675,17 @@ - + + Create account pack + + + + + New room pack + + + + Private pack @@ -627,7 +700,7 @@ - + Enable globally @@ -637,7 +710,12 @@ - + + Edit + + + + Close Închide @@ -811,7 +889,7 @@ Exemplu: https://serverul.meu:8787 MessageDelegate - + removed @@ -822,7 +900,7 @@ Exemplu: https://serverul.meu:8787 Criptare activată - + room name changed to: %1 numele camerei schimbat la: %1 @@ -905,7 +983,7 @@ Exemplu: https://serverul.meu:8787 - + Stickers @@ -928,7 +1006,7 @@ Exemplu: https://serverul.meu:8787 MessageView - + Edit @@ -948,7 +1026,7 @@ Exemplu: https://serverul.meu:8787 Opțiuni - + &Copy @@ -1179,21 +1257,37 @@ Exemplu: https://serverul.meu:8787 + + ReadReceipts + + + Read receipts + Confirmări de citire + + + + ReadReceiptsModel + + + Yesterday, %1 + + + RegisterPage - + Username Nume de utilizator - + The username must not be empty, and must contain only the characters a-z, 0-9, ., _, =, -, and /. Numele de utilizator nu poate fi gol, și trebuie să conțină doar caracterele a-z, 0-9, ., =, - și /. - + Password Parolă @@ -1213,7 +1307,7 @@ Exemplu: https://serverul.meu:8787 Homeserver - + A server that allows registration. Since matrix is decentralized, you need to first find a server you can register on or host your own. Un server care permite înregistrarea. Deoarece Matrix este decentralizat, trebuie să găsiți un server pe care să vă înregistrați sau să vă găzduiți propriul server. @@ -1223,22 +1317,27 @@ Exemplu: https://serverul.meu:8787 ÎNREGISTRARE - + No supported registration flows! Fluxuri de înregistrare nesuportate! - - One or more fields have invalid inputs. Please correct those issues and try again. + + Registration token - + + Please enter a valid registration token. + + + + Autodiscovery failed. Received malformed response. Autodescoperirea a eșuat. Răspunsul primit este defectuos. - + Autodiscovery failed. Unknown error when requesting .well-known. Autodescoperirea a eșuat. Eroare necunoscută la solicitarea .well-known. @@ -1248,7 +1347,7 @@ Exemplu: https://serverul.meu:8787 Punctele finale necesare nu au fost găsite. Posibil a nu fi un server Matrix. - + Received malformed response. Make sure the homeserver domain is valid. Răspuns eronat primit. Verificați ca domeniul homeserverului să fie valid. @@ -1258,17 +1357,17 @@ Exemplu: https://serverul.meu:8787 A apărut o eroare necunoscută. Verificați ca domeniul homeserverului să fie valid. - + Password is not long enough (min 8 chars) Parola nu este destul de lungă (minim 8 caractere) - + Passwords don't match Parolele nu se potrivesc - + Invalid server name Nume server invalid @@ -1276,7 +1375,7 @@ Exemplu: https://serverul.meu:8787 ReplyPopup - + Close Închide @@ -1289,7 +1388,7 @@ Exemplu: https://serverul.meu:8787 RoomInfo - + no version stored nicio versiune stocată @@ -1297,7 +1396,7 @@ Exemplu: https://serverul.meu:8787 RoomList - + New tag @@ -1347,7 +1446,7 @@ Exemplu: https://serverul.meu:8787 - + Status Message @@ -1367,7 +1466,7 @@ Exemplu: https://serverul.meu:8787 - + Logout Deconectare @@ -1400,7 +1499,7 @@ Exemplu: https://serverul.meu:8787 RoomMembers - + Members of %1 @@ -1419,11 +1518,31 @@ Exemplu: https://serverul.meu:8787 Invite more people + + + This room is not encrypted! + + + + + This user is verified. + + + + + This user isn't verified, but is still using the same master key from the first time you met. + + + + + This user has unverified devices! + + RoomSettings - + Room Settings @@ -1458,7 +1577,12 @@ Exemplu: https://serverul.meu:8787 - + + Room access + + + + Anyone and guests @@ -1622,6 +1746,30 @@ Exemplu: https://serverul.meu:8787 + + SingleImagePackModel + + + + Failed to update image pack: {} + + + + + Failed to delete old image pack: {} + + + + + Failed to open image: {} + + + + + Failed to upload image: {} + + + StatusIndicator @@ -1674,7 +1822,7 @@ Exemplu: https://serverul.meu:8787 TimelineModel - + Message redaction failed: %1 Redactare mesaj eșuată: %1 @@ -1705,7 +1853,7 @@ Exemplu: https://serverul.meu:8787 Salvați fișier - + %1 and %2 are typing. Multiple users are typing. First argument is a comma separated list of potentially multiple users. Second argument is the last user of that list. (If only one user is typing, %1 is empty. You should still use it in your string though to silence Qt warnings.) @@ -1820,12 +1968,12 @@ Exemplu: https://serverul.meu:8787 %1 și-a redactat ciocănitul. - + You joined this room. Te-ai alăturat camerei. - + %1 has changed their avatar and changed their display name to %2. @@ -1854,7 +2002,7 @@ Exemplu: https://serverul.meu:8787 TimelineRow - + Edited @@ -1862,12 +2010,12 @@ Exemplu: https://serverul.meu:8787 TimelineView - + No room open Nicio cameră deschisă - + %1 member(s) @@ -1895,7 +2043,7 @@ Exemplu: https://serverul.meu:8787 TimelineViewManager - + No encrypted private chat found with this user. Create an encrypted private chat with this user and try again. @@ -1903,17 +2051,37 @@ Exemplu: https://serverul.meu:8787 TopBar - + Back to room list - + No room selected - + + This room is not encrypted! + + + + + This room contains only verified devices. + + + + + This rooms contain verified devices and devices which have never changed their master key. + + + + + This room contains unverified devices! + + + + Room options @@ -1954,7 +2122,7 @@ Exemplu: https://serverul.meu:8787 UserProfile - + Global User Profile @@ -1964,7 +2132,7 @@ Exemplu: https://serverul.meu:8787 - + Verify @@ -2013,8 +2181,8 @@ Exemplu: https://serverul.meu:8787 UserSettings - - + + Default @@ -2022,7 +2190,7 @@ Exemplu: https://serverul.meu:8787 UserSettingsPage - + Minimize to tray Minimizează în bara de notificări @@ -2042,12 +2210,12 @@ Exemplu: https://serverul.meu:8787 Avatare rotunde - + profile: %1 - + Default @@ -2233,12 +2401,27 @@ This usually causes the application icon in the task bar to animate in some fash Mărește fontul mesajelor dacă doar câteva emojiuri sunt afișate. - + + Send encrypted messages to verified users only + + + + + Requires a user to be verified to send encrypted messages to them. This improves safety but makes E2EE more tedious. + + + + Share keys with verified users and devices - + + Automatically replies to key requests from other users, if they are verified, even if that device shouldn't have access to those keys otherwise. + + + + CACHED @@ -2248,7 +2431,7 @@ This usually causes the application icon in the task bar to animate in some fash - + Scale factor Factor de dimensiune @@ -2343,12 +2526,12 @@ This usually causes the application icon in the task bar to animate in some fash CRIPTARE - + GENERAL GENERAL - + INTERFACE INTERFAȚĂ @@ -2368,12 +2551,7 @@ This usually causes the application icon in the task bar to animate in some fash Familia de font pentru Emoji - - Automatically replies to key requests from other users, if they are verified. - - - - + Master signing key @@ -2423,7 +2601,7 @@ This usually causes the application icon in the task bar to animate in some fash Toate fișierele (*) - + Open Sessions File Deschide fișierul de sesiuni @@ -2674,32 +2852,6 @@ Dimensiune media: %2 Rezolvă reCAPTCHA și apasă butonul de confirmare - - dialogs::ReadReceipts - - - Read receipts - Confirmări de citire - - - - Close - Închide - - - - dialogs::ReceiptItem - - - Today %1 - Azi %1 - - - - Yesterday %1 - Ieri %1 - - message-description sent: diff --git a/resources/langs/nheko_ru.ts b/resources/langs/nheko_ru.ts index 67a306f2..3957f88c 100644 --- a/resources/langs/nheko_ru.ts +++ b/resources/langs/nheko_ru.ts @@ -56,7 +56,7 @@ CallInvite - + Video Call Видео Звонок @@ -125,7 +125,7 @@ ChatPage - + Failed to invite user: %1 Не удалось пригласить пользователя: %1 @@ -157,12 +157,12 @@ - + Confirm invite Подтвердите приглашение - + Do you really want to invite %1 (%2)? Вы точно хотите пригласить %1 (%2)? @@ -227,12 +227,12 @@ Разблокированный пользователь: %1 - + Do you really want to start a private chat with %1? Вы действительно хотите начать личную переписку с %1? - + Cache migration failed! Миграция кэша не удалась! @@ -367,7 +367,7 @@ Введите свой ключ восстановления или пароль названный %1 для расшифровки Ваших секретов: - + Decryption failed Расшифровка не удалась @@ -494,6 +494,49 @@ Они совпадают! + + Encrypted + + + There is no key to unlock this message. We requested the key automatically, but you can try requesting it again if you are impatient. + + + + + This message couldn't be decrypted, because we only have a key for newer messages. You can try requesting access to this message. + + + + + There was an internal error reading the decryption key from the database. + + + + + There was an error decrypting this message. + + + + + The message couldn't be parsed. + + + + + The encryption key was reused! Someone is possibly trying to insert false messages into this chat! + + + + + Unknown decryption error + + + + + Request key + + + EncryptionIndicator @@ -517,51 +560,6 @@ Зашифровано неверифицированым устройства - - EventStore - - - -- Encrypted Event (No keys found for decryption) -- - Placeholder, when the message was not decrypted yet or can't be decrypted. - -- Зашифрованное событие (Не найдено ключей для дешифрования) -- - - - - -- Encrypted Event (Key not valid for this index) -- - Placeholder, when the message can't be decrypted with this key since it is not valid for this index - -- Зашифрованное событие(Не найдено ключей для дешифрования) -- - - - - - -- Decryption Error (failed to retrieve megolm keys from db) -- - Placeholder, when the message can't be decrypted, because the DB access failed. - -- Ошибка дешифрования (Не удалось получить megolm-ключи из бд) -- - - - - - -- Decryption Error (%1) -- - Placeholder, when the message can't be decrypted. In this case, the Olm decrytion returned an error, which is passed as %1. - -- Ошибка Дешифрования (%1) -- - - - - -- Encrypted Event (Unknown event type) -- - Placeholder, when the message was decrypted, but we couldn't parse it, because Nheko/mtxclient don't support that event type yet. - -- Шифрованое Событие (Неизвестный тип события) -- - - - - -- Replay attack! This message index was reused! -- - -- Атака повтором! Индекс этого сообщение был использован снова! -- - - - - -- Message by unverified device! -- - -- Сообщение от неверифицированного устройства! -- - - Failed @@ -604,6 +602,71 @@ Переслать Сообщение + + ImagePackEditorDialog + + + Editing image pack + + + + + Add images + + + + + Stickers (*.png *.webp) + + + + + State key + + + + + Packname + + + + + Attribution + + + + + + Use as Emoji + + + + + + Use as Sticker + + + + + Shortcode + + + + + Body + + + + + Cancel + + + + + Save + + + ImagePackSettingsDialog @@ -612,7 +675,17 @@ - + + Create account pack + + + + + New room pack + + + + Private pack @@ -627,7 +700,7 @@ - + Enable globally @@ -637,7 +710,12 @@ - + + Edit + Редактировать + + + Close Закрыть @@ -811,7 +889,7 @@ Example: https://server.my:8787 MessageDelegate - + removed убрано @@ -822,7 +900,7 @@ Example: https://server.my:8787 Шифрование включено - + room name changed to: %1 имя комнаты изменено на: %1 @@ -905,7 +983,7 @@ Example: https://server.my:8787 Написать сообщение… - + Stickers @@ -928,7 +1006,7 @@ Example: https://server.my:8787 MessageView - + Edit Редактировать @@ -948,7 +1026,7 @@ Example: https://server.my:8787 Опции - + &Copy @@ -1179,21 +1257,37 @@ Example: https://server.my:8787 имя профиля + + ReadReceipts + + + Read receipts + + + + + ReadReceiptsModel + + + Yesterday, %1 + + + RegisterPage - + Username Имя пользователя - + The username must not be empty, and must contain only the characters a-z, 0-9, ., _, =, -, and /. Имя пользователя не должно быть пустым и должно содержать только символы a-z, 0-9, ., _, =, -, и /. - + Password Пароль @@ -1213,7 +1307,7 @@ Example: https://server.my:8787 Домашний сервер - + A server that allows registration. Since matrix is decentralized, you need to first find a server you can register on or host your own. Сервер разрешающий регистрацию.Поскольку matrix децентрализованный, нужно выбрать сервер где вы можете зарегистрироваться или поднимите свой сервер. @@ -1223,22 +1317,27 @@ Example: https://server.my:8787 РЕГИСТРАЦИЯ - + No supported registration flows! Нет поддреживаемых регистрационных потоков - - One or more fields have invalid inputs. Please correct those issues and try again. - Одно или более полей имеют некорректный ввод. Пожалуйста устраните ошибки и попробуйте снова. + + Registration token + - + + Please enter a valid registration token. + + + + Autodiscovery failed. Received malformed response. Автообноружение не удалось. Получен поврежденный ответ. - + Autodiscovery failed. Unknown error when requesting .well-known. Автообноружение не удалось. Не известаня ошибка во время запроса .well-known. @@ -1248,7 +1347,7 @@ Example: https://server.my:8787 Необходимые конечные точки не найдены. Возможно, это не сервер Matrix. - + Received malformed response. Make sure the homeserver domain is valid. Получен неверный ответ. Убедитесь, что домен homeserver действителен. @@ -1258,17 +1357,17 @@ Example: https://server.my:8787 Произошла неизвестная ошибка. Убедитесь, что домен homeserver действителен. - + Password is not long enough (min 8 chars) Слишком короткий пароль (минимум 8 символов) - + Passwords don't match Пароли не совпадают - + Invalid server name Неверное имя сервера @@ -1276,7 +1375,7 @@ Example: https://server.my:8787 ReplyPopup - + Close Закрыть @@ -1289,7 +1388,7 @@ Example: https://server.my:8787 RoomInfo - + no version stored нет сохраненной версии @@ -1297,7 +1396,7 @@ Example: https://server.my:8787 RoomList - + New tag @@ -1347,7 +1446,7 @@ Example: https://server.my:8787 - + Status Message @@ -1367,7 +1466,7 @@ Example: https://server.my:8787 - + Logout Выйти @@ -1400,7 +1499,7 @@ Example: https://server.my:8787 RoomMembers - + Members of %1 @@ -1419,11 +1518,31 @@ Example: https://server.my:8787 Invite more people + + + This room is not encrypted! + + + + + This user is verified. + + + + + This user isn't verified, but is still using the same master key from the first time you met. + + + + + This user has unverified devices! + + RoomSettings - + Room Settings Настройки комнаты @@ -1458,7 +1577,12 @@ Example: https://server.my:8787 Все сообщения - + + Room access + + + + Anyone and guests Каждый и гости @@ -1622,6 +1746,30 @@ Example: https://server.my:8787 Отмена + + SingleImagePackModel + + + + Failed to update image pack: {} + + + + + Failed to delete old image pack: {} + + + + + Failed to open image: {} + + + + + Failed to upload image: {} + + + StatusIndicator @@ -1674,7 +1822,7 @@ Example: https://server.my:8787 TimelineModel - + Message redaction failed: %1 Ошибка редактирования сообщения: %1 @@ -1705,7 +1853,7 @@ Example: https://server.my:8787 Сохранить файл - + %1 and %2 are typing. Multiple users are typing. First argument is a comma separated list of potentially multiple users. Second argument is the last user of that list. (If only one user is typing, %1 is empty. You should still use it in your string though to silence Qt warnings.) @@ -1820,12 +1968,12 @@ Example: https://server.my:8787 %1 отредактировал его "стук". - + You joined this room. Вы присоединились к этой комнате. - + %1 has changed their avatar and changed their display name to %2. @@ -1854,7 +2002,7 @@ Example: https://server.my:8787 TimelineRow - + Edited Изменено @@ -1862,12 +2010,12 @@ Example: https://server.my:8787 TimelineView - + No room open Комната не выбрана - + %1 member(s) %1 участник(ов) @@ -1895,7 +2043,7 @@ Example: https://server.my:8787 TimelineViewManager - + No encrypted private chat found with this user. Create an encrypted private chat with this user and try again. Не найдено личного чата с этим пользователем. Создайте зашифрованный личный чат с этим пользователем и попытайтесь еще раз. @@ -1903,17 +2051,37 @@ Example: https://server.my:8787 TopBar - + Back to room list Вернуться к списку комнат - + No room selected Комнаты не выбраны - + + This room is not encrypted! + + + + + This room contains only verified devices. + + + + + This rooms contain verified devices and devices which have never changed their master key. + + + + + This room contains unverified devices! + + + + Room options Настройки комнаты @@ -1954,7 +2122,7 @@ Example: https://server.my:8787 UserProfile - + Global User Profile Глобальный Пользовательский Профиль @@ -1964,7 +2132,7 @@ Example: https://server.my:8787 Поользовательский Профиль в Комнате - + Verify Верифицировать @@ -2013,8 +2181,8 @@ Example: https://server.my:8787 UserSettings - - + + Default По умолчанию @@ -2022,7 +2190,7 @@ Example: https://server.my:8787 UserSettingsPage - + Minimize to tray Сворачивать в системную панель @@ -2042,12 +2210,12 @@ Example: https://server.my:8787 Округлый Аватар - + profile: %1 профиль: %1 - + Default По умолчанию @@ -2238,12 +2406,27 @@ This usually causes the application icon in the task bar to animate in some fash Делать шрифт больше, если сообщения содержать только несколько эмоджи. - + + Send encrypted messages to verified users only + + + + + Requires a user to be verified to send encrypted messages to them. This improves safety but makes E2EE more tedious. + + + + Share keys with verified users and devices Делиться ключами с проверенными участниками и устройствами - + + Automatically replies to key requests from other users, if they are verified, even if that device shouldn't have access to those keys otherwise. + + + + CACHED Закешировано @@ -2253,7 +2436,7 @@ This usually causes the application icon in the task bar to animate in some fash НЕ ЗАКЕШИРОВАНО - + Scale factor Масштаб @@ -2348,12 +2531,12 @@ This usually causes the application icon in the task bar to animate in some fash ШИФРОВАНИЕ - + GENERAL ГЛАВНОЕ - + INTERFACE ИНТЕРФЕЙС @@ -2373,12 +2556,7 @@ This usually causes the application icon in the task bar to animate in some fash Семья шрифта эмоджи - - Automatically replies to key requests from other users, if they are verified. - Автоматически отвечать на запросы ключей от других пользователей, если они верифицированы. - - - + Master signing key @@ -2428,7 +2606,7 @@ This usually causes the application icon in the task bar to animate in some fash Все файлы (*) - + Open Sessions File Открыть файл сеансов @@ -2680,32 +2858,6 @@ Media size: %2 Решите reCAPTCHA и нажмите кнопку подтверждения - - dialogs::ReadReceipts - - - Read receipts - Просмотреть получателей - - - - Close - Закрыть - - - - dialogs::ReceiptItem - - - Today %1 - Сегодня %1 - - - - Yesterday %1 - Вчера %1 - - message-description sent: diff --git a/resources/langs/nheko_si.ts b/resources/langs/nheko_si.ts index cf425990..36f3f9a4 100644 --- a/resources/langs/nheko_si.ts +++ b/resources/langs/nheko_si.ts @@ -56,7 +56,7 @@ CallInvite - + Video Call @@ -125,7 +125,7 @@ ChatPage - + Failed to invite user: %1 @@ -157,12 +157,12 @@ - + Confirm invite - + Do you really want to invite %1 (%2)? @@ -227,12 +227,12 @@ - + Do you really want to start a private chat with %1? - + Cache migration failed! @@ -367,7 +367,7 @@ - + Decryption failed @@ -494,6 +494,49 @@ + + Encrypted + + + There is no key to unlock this message. We requested the key automatically, but you can try requesting it again if you are impatient. + + + + + This message couldn't be decrypted, because we only have a key for newer messages. You can try requesting access to this message. + + + + + There was an internal error reading the decryption key from the database. + + + + + There was an error decrypting this message. + + + + + The message couldn't be parsed. + + + + + The encryption key was reused! Someone is possibly trying to insert false messages into this chat! + + + + + Unknown decryption error + + + + + Request key + + + EncryptionIndicator @@ -517,51 +560,6 @@ - - EventStore - - - -- Encrypted Event (No keys found for decryption) -- - Placeholder, when the message was not decrypted yet or can't be decrypted. - - - - - -- Encrypted Event (Key not valid for this index) -- - Placeholder, when the message can't be decrypted with this key since it is not valid for this index - - - - - - -- Decryption Error (failed to retrieve megolm keys from db) -- - Placeholder, when the message can't be decrypted, because the DB access failed. - - - - - - -- Decryption Error (%1) -- - Placeholder, when the message can't be decrypted. In this case, the Olm decrytion returned an error, which is passed as %1. - - - - - -- Encrypted Event (Unknown event type) -- - Placeholder, when the message was decrypted, but we couldn't parse it, because Nheko/mtxclient don't support that event type yet. - - - - - -- Replay attack! This message index was reused! -- - - - - - -- Message by unverified device! -- - - - Failed @@ -604,6 +602,71 @@ + + ImagePackEditorDialog + + + Editing image pack + + + + + Add images + + + + + Stickers (*.png *.webp) + + + + + State key + + + + + Packname + + + + + Attribution + + + + + + Use as Emoji + + + + + + Use as Sticker + + + + + Shortcode + + + + + Body + + + + + Cancel + + + + + Save + + + ImagePackSettingsDialog @@ -612,7 +675,17 @@ - + + Create account pack + + + + + New room pack + + + + Private pack @@ -627,7 +700,7 @@ - + Enable globally @@ -637,7 +710,12 @@ - + + Edit + + + + Close @@ -807,7 +885,7 @@ Example: https://server.my:8787 MessageDelegate - + removed @@ -818,7 +896,7 @@ Example: https://server.my:8787 - + room name changed to: %1 @@ -901,7 +979,7 @@ Example: https://server.my:8787 - + Stickers @@ -924,7 +1002,7 @@ Example: https://server.my:8787 MessageView - + Edit @@ -944,7 +1022,7 @@ Example: https://server.my:8787 - + &Copy @@ -1175,21 +1253,37 @@ Example: https://server.my:8787 + + ReadReceipts + + + Read receipts + + + + + ReadReceiptsModel + + + Yesterday, %1 + + + RegisterPage - + Username - + The username must not be empty, and must contain only the characters a-z, 0-9, ., _, =, -, and /. - + Password @@ -1209,7 +1303,7 @@ Example: https://server.my:8787 - + A server that allows registration. Since matrix is decentralized, you need to first find a server you can register on or host your own. @@ -1219,22 +1313,27 @@ Example: https://server.my:8787 - + No supported registration flows! - - One or more fields have invalid inputs. Please correct those issues and try again. + + Registration token - + + Please enter a valid registration token. + + + + Autodiscovery failed. Received malformed response. - + Autodiscovery failed. Unknown error when requesting .well-known. @@ -1244,7 +1343,7 @@ Example: https://server.my:8787 - + Received malformed response. Make sure the homeserver domain is valid. @@ -1254,17 +1353,17 @@ Example: https://server.my:8787 - + Password is not long enough (min 8 chars) - + Passwords don't match - + Invalid server name @@ -1272,7 +1371,7 @@ Example: https://server.my:8787 ReplyPopup - + Close @@ -1285,7 +1384,7 @@ Example: https://server.my:8787 RoomInfo - + no version stored @@ -1293,7 +1392,7 @@ Example: https://server.my:8787 RoomList - + New tag @@ -1343,7 +1442,7 @@ Example: https://server.my:8787 - + Status Message @@ -1363,7 +1462,7 @@ Example: https://server.my:8787 - + Logout @@ -1396,7 +1495,7 @@ Example: https://server.my:8787 RoomMembers - + Members of %1 @@ -1414,11 +1513,31 @@ Example: https://server.my:8787 Invite more people + + + This room is not encrypted! + + + + + This user is verified. + + + + + This user isn't verified, but is still using the same master key from the first time you met. + + + + + This user has unverified devices! + + RoomSettings - + Room Settings @@ -1453,7 +1572,12 @@ Example: https://server.my:8787 - + + Room access + + + + Anyone and guests @@ -1617,6 +1741,30 @@ Example: https://server.my:8787 + + SingleImagePackModel + + + + Failed to update image pack: {} + + + + + Failed to delete old image pack: {} + + + + + Failed to open image: {} + + + + + Failed to upload image: {} + + + StatusIndicator @@ -1669,7 +1817,7 @@ Example: https://server.my:8787 TimelineModel - + Message redaction failed: %1 @@ -1700,7 +1848,7 @@ Example: https://server.my:8787 - + %1 and %2 are typing. Multiple users are typing. First argument is a comma separated list of potentially multiple users. Second argument is the last user of that list. (If only one user is typing, %1 is empty. You should still use it in your string though to silence Qt warnings.) @@ -1814,12 +1962,12 @@ Example: https://server.my:8787 - + You joined this room. - + %1 has changed their avatar and changed their display name to %2. @@ -1848,7 +1996,7 @@ Example: https://server.my:8787 TimelineRow - + Edited @@ -1856,12 +2004,12 @@ Example: https://server.my:8787 TimelineView - + No room open - + %1 member(s) @@ -1889,7 +2037,7 @@ Example: https://server.my:8787 TimelineViewManager - + No encrypted private chat found with this user. Create an encrypted private chat with this user and try again. @@ -1897,17 +2045,37 @@ Example: https://server.my:8787 TopBar - + Back to room list - + No room selected - + + This room is not encrypted! + + + + + This room contains only verified devices. + + + + + This rooms contain verified devices and devices which have never changed their master key. + + + + + This room contains unverified devices! + + + + Room options @@ -1948,7 +2116,7 @@ Example: https://server.my:8787 UserProfile - + Global User Profile @@ -1958,7 +2126,7 @@ Example: https://server.my:8787 - + Verify @@ -2007,8 +2175,8 @@ Example: https://server.my:8787 UserSettings - - + + Default @@ -2016,7 +2184,7 @@ Example: https://server.my:8787 UserSettingsPage - + Minimize to tray @@ -2036,12 +2204,12 @@ Example: https://server.my:8787 - + profile: %1 - + Default @@ -2227,12 +2395,27 @@ This usually causes the application icon in the task bar to animate in some fash - + + Send encrypted messages to verified users only + + + + + Requires a user to be verified to send encrypted messages to them. This improves safety but makes E2EE more tedious. + + + + Share keys with verified users and devices - + + Automatically replies to key requests from other users, if they are verified, even if that device shouldn't have access to those keys otherwise. + + + + CACHED @@ -2242,7 +2425,7 @@ This usually causes the application icon in the task bar to animate in some fash - + Scale factor @@ -2337,12 +2520,12 @@ This usually causes the application icon in the task bar to animate in some fash - + GENERAL - + INTERFACE @@ -2362,12 +2545,7 @@ This usually causes the application icon in the task bar to animate in some fash - - Automatically replies to key requests from other users, if they are verified. - - - - + Master signing key @@ -2417,7 +2595,7 @@ This usually causes the application icon in the task bar to animate in some fash - + Open Sessions File @@ -2666,32 +2844,6 @@ Media size: %2 - - dialogs::ReadReceipts - - - Read receipts - - - - - Close - - - - - dialogs::ReceiptItem - - - Today %1 - - - - - Yesterday %1 - - - message-description sent: diff --git a/resources/langs/nheko_sv.ts b/resources/langs/nheko_sv.ts index 25db1c4b..b501f669 100644 --- a/resources/langs/nheko_sv.ts +++ b/resources/langs/nheko_sv.ts @@ -56,7 +56,7 @@ CallInvite - + Video Call Videosamtal @@ -125,7 +125,7 @@ ChatPage - + Failed to invite user: %1 Kunde inte bjuda in användare: %1 @@ -157,12 +157,12 @@ - + Confirm invite Bekräfta inbjudan - + Do you really want to invite %1 (%2)? Är du säker på att du vill bjuda in %1 (%2)? @@ -227,12 +227,12 @@ Hävde bannlysningen av användare: %1 - + Do you really want to start a private chat with %1? - + Cache migration failed! Cache-migration misslyckades! @@ -367,7 +367,7 @@ Ange din återställningsnyckel eller lösenfras vid namn %1 för att dekryptera dina hemliga nycklar: - + Decryption failed Dekryptering misslyckades @@ -494,6 +494,49 @@ De överensstämmer! + + Encrypted + + + There is no key to unlock this message. We requested the key automatically, but you can try requesting it again if you are impatient. + + + + + This message couldn't be decrypted, because we only have a key for newer messages. You can try requesting access to this message. + + + + + There was an internal error reading the decryption key from the database. + + + + + There was an error decrypting this message. + + + + + The message couldn't be parsed. + + + + + The encryption key was reused! Someone is possibly trying to insert false messages into this chat! + + + + + Unknown decryption error + + + + + Request key + + + EncryptionIndicator @@ -517,51 +560,6 @@ - - EventStore - - - -- Encrypted Event (No keys found for decryption) -- - Placeholder, when the message was not decrypted yet or can't be decrypted. - -- Krypterat Event (Inga nycklar kunde hittas för dekryptering) -- - - - - -- Encrypted Event (Key not valid for this index) -- - Placeholder, when the message can't be decrypted with this key since it is not valid for this index - - - - - - -- Decryption Error (failed to retrieve megolm keys from db) -- - Placeholder, when the message can't be decrypted, because the DB access failed. - -- Dekrypteringsfel (Kunde inte hämta megolm-nycklar från databas) -- - - - - - -- Decryption Error (%1) -- - Placeholder, when the message can't be decrypted. In this case, the Olm decrytion returned an error, which is passed as %1. - -- Dekrypteringsfel (%1) -- - - - - -- Encrypted Event (Unknown event type) -- - Placeholder, when the message was decrypted, but we couldn't parse it, because Nheko/mtxclient don't support that event type yet. - -- Krypterat Event (Okänd eventtyp) -- - - - - -- Replay attack! This message index was reused! -- - -- Replay-attack! Detta meddelandeindex har blivit återanvänt! -- - - - - -- Message by unverified device! -- - -- Meddelande från overifierad enhet! -- - - Failed @@ -604,6 +602,71 @@ + + ImagePackEditorDialog + + + Editing image pack + + + + + Add images + + + + + Stickers (*.png *.webp) + + + + + State key + + + + + Packname + + + + + Attribution + + + + + + Use as Emoji + + + + + + Use as Sticker + + + + + Shortcode + + + + + Body + + + + + Cancel + Avbryt + + + + Save + + + ImagePackSettingsDialog @@ -612,7 +675,17 @@ - + + Create account pack + + + + + New room pack + + + + Private pack @@ -627,7 +700,7 @@ - + Enable globally @@ -637,7 +710,12 @@ - + + Edit + + + + Close Stäng @@ -811,12 +889,12 @@ Exempel: https://server.my:8787 MessageDelegate - + Encryption enabled Kryptering aktiverad - + room name changed to: %1 rummets namn ändrat till: %1 @@ -871,13 +949,13 @@ Exempel: https://server.my:8787 %1 besvarade samtalet. - + removed borttagen - + %1 ended the call. %1 avslutade samtalet. @@ -905,7 +983,7 @@ Exempel: https://server.my:8787 Skriv ett meddelande… - + Stickers @@ -928,7 +1006,7 @@ Exempel: https://server.my:8787 MessageView - + Edit @@ -948,7 +1026,7 @@ Exempel: https://server.my:8787 Alternativ - + &Copy @@ -1179,21 +1257,37 @@ Exempel: https://server.my:8787 profilnamn + + ReadReceipts + + + Read receipts + Läskvitton + + + + ReadReceiptsModel + + + Yesterday, %1 + + + RegisterPage - + Username Användarnamn - + The username must not be empty, and must contain only the characters a-z, 0-9, ., _, =, -, and /. Användarnamnet kan inte vara tomt, och måste enbart innehålla tecknen a-z, 0-9, ., _, =, -, och /. - + Password Lösenord @@ -1213,7 +1307,7 @@ Exempel: https://server.my:8787 Hemserver - + A server that allows registration. Since matrix is decentralized, you need to first find a server you can register on or host your own. En server som tillåter registrering. Eftersom matrix är decentraliserat behöver du först hitta en server du kan registrera dig på, eller upprätta en på egen hand. @@ -1223,22 +1317,27 @@ Exempel: https://server.my:8787 REGISTRERA - + No supported registration flows! Inga stödda registreringsflöden! - - One or more fields have invalid inputs. Please correct those issues and try again. - Ett eller flera fält har ogiltigt innehåll. Vänligen korrigera problemen och försök igen. + + Registration token + - + + Please enter a valid registration token. + + + + Autodiscovery failed. Received malformed response. Autouppslag misslyckades. Mottog felkonstruerat svar. - + Autodiscovery failed. Unknown error when requesting .well-known. Autouppslag misslyckades. Okänt fel uppstod vid begäran av .well-known. @@ -1248,7 +1347,7 @@ Exempel: https://server.my:8787 Kunde inte hitta de nödvändiga ändpunkterna. Möjligtvis inte en Matrix-server. - + Received malformed response. Make sure the homeserver domain is valid. Mottog felkonstruerat svar. Se till att hemserver-domänen är giltig. @@ -1258,17 +1357,17 @@ Exempel: https://server.my:8787 Ett okänt fel uppstod. Se till att hemserver-domänen är giltig. - + Password is not long enough (min 8 chars) Lösenordet är inte långt nog (minst 8 tecken) - + Passwords don't match Lösenorden stämmer inte överens - + Invalid server name Ogiltigt servernamn @@ -1276,7 +1375,7 @@ Exempel: https://server.my:8787 ReplyPopup - + Close Stäng @@ -1289,7 +1388,7 @@ Exempel: https://server.my:8787 RoomInfo - + no version stored ingen version lagrad @@ -1297,7 +1396,7 @@ Exempel: https://server.my:8787 RoomList - + New tag @@ -1347,7 +1446,7 @@ Exempel: https://server.my:8787 - + Status Message @@ -1367,7 +1466,7 @@ Exempel: https://server.my:8787 - + Logout Logga ut @@ -1400,7 +1499,7 @@ Exempel: https://server.my:8787 RoomMembers - + Members of %1 @@ -1418,11 +1517,31 @@ Exempel: https://server.my:8787 Invite more people + + + This room is not encrypted! + + + + + This user is verified. + + + + + This user isn't verified, but is still using the same master key from the first time you met. + + + + + This user has unverified devices! + + RoomSettings - + Room Settings @@ -1457,7 +1576,12 @@ Exempel: https://server.my:8787 - + + Room access + + + + Anyone and guests @@ -1621,6 +1745,30 @@ Exempel: https://server.my:8787 Avbryt + + SingleImagePackModel + + + + Failed to update image pack: {} + + + + + Failed to delete old image pack: {} + + + + + Failed to open image: {} + + + + + Failed to upload image: {} + + + StatusIndicator @@ -1673,7 +1821,7 @@ Exempel: https://server.my:8787 TimelineModel - + Message redaction failed: %1 Kunde inte maskera meddelande: %1 @@ -1704,7 +1852,7 @@ Exempel: https://server.my:8787 Spara fil - + %1 and %2 are typing. Multiple users are typing. First argument is a comma separated list of potentially multiple users. Second argument is the last user of that list. (If only one user is typing, %1 is empty. You should still use it in your string though to silence Qt warnings.) @@ -1818,12 +1966,12 @@ Exempel: https://server.my:8787 %1 maskerade sin knackning. - + You joined this room. Du gick med i detta rum. - + %1 has changed their avatar and changed their display name to %2. @@ -1852,7 +2000,7 @@ Exempel: https://server.my:8787 TimelineRow - + Edited @@ -1860,12 +2008,12 @@ Exempel: https://server.my:8787 TimelineView - + No room open Inget rum öppet - + %1 member(s) @@ -1893,7 +2041,7 @@ Exempel: https://server.my:8787 TimelineViewManager - + No encrypted private chat found with this user. Create an encrypted private chat with this user and try again. Ingen krypterad privat chatt med denna användare kunde hittas. Skapa en krypterad privat chatt med användaren och försök igen. @@ -1901,17 +2049,37 @@ Exempel: https://server.my:8787 TopBar - + Back to room list Tillbaka till rumlista - + No room selected Inget rum markerat - + + This room is not encrypted! + + + + + This room contains only verified devices. + + + + + This rooms contain verified devices and devices which have never changed their master key. + + + + + This room contains unverified devices! + + + + Room options Alternativ för rum @@ -1952,7 +2120,7 @@ Exempel: https://server.my:8787 UserProfile - + Global User Profile @@ -1962,7 +2130,7 @@ Exempel: https://server.my:8787 - + Verify Bekräfta @@ -2011,8 +2179,8 @@ Exempel: https://server.my:8787 UserSettings - - + + Default @@ -2020,7 +2188,7 @@ Exempel: https://server.my:8787 UserSettingsPage - + Minimize to tray Minimera till systemtråg @@ -2040,12 +2208,12 @@ Exempel: https://server.my:8787 Cirkulära avatarer - + profile: %1 profil: %1 - + Default @@ -2239,12 +2407,27 @@ Detta gör vanligtvis att ikonen i aktivitetsfältet animeras på något sätt.< Öka fontstorleken på meddelanden som enbart innehåller ett par emoji. - + + Send encrypted messages to verified users only + + + + + Requires a user to be verified to send encrypted messages to them. This improves safety but makes E2EE more tedious. + + + + Share keys with verified users and devices Dela nycklar med verifierade användare och enheter - + + Automatically replies to key requests from other users, if they are verified, even if that device shouldn't have access to those keys otherwise. + + + + CACHED SPARAD @@ -2254,7 +2437,7 @@ Detta gör vanligtvis att ikonen i aktivitetsfältet animeras på något sätt.< EJ SPARAD - + Scale factor Storleksfaktor @@ -2349,12 +2532,12 @@ Detta gör vanligtvis att ikonen i aktivitetsfältet animeras på något sätt.< KRYPTERING - + GENERAL ALLMÄNT - + INTERFACE GRÄNSSNITT @@ -2374,12 +2557,7 @@ Detta gör vanligtvis att ikonen i aktivitetsfältet animeras på något sätt.< Emoji font-familj - - Automatically replies to key requests from other users, if they are verified. - Svarar automatiskt på nyckelförfrågningar från andra användare om de är verifierade. - - - + Master signing key Primär signeringsnyckel @@ -2429,7 +2607,7 @@ Detta gör vanligtvis att ikonen i aktivitetsfältet animeras på något sätt.< Alla Filer (*) - + Open Sessions File Öppna sessionsfil @@ -2680,32 +2858,6 @@ Mediastorlek: %2 Lös reCAPTCHAn och tryck på Bekräfta - - dialogs::ReadReceipts - - - Read receipts - Läskvitton - - - - Close - Stäng - - - - dialogs::ReceiptItem - - - Today %1 - Idag %1 - - - - Yesterday %1 - Igår %1 - - message-description sent: diff --git a/resources/langs/nheko_zh_CN.ts b/resources/langs/nheko_zh_CN.ts index 75e9db95..be4f1e49 100644 --- a/resources/langs/nheko_zh_CN.ts +++ b/resources/langs/nheko_zh_CN.ts @@ -56,7 +56,7 @@ CallInvite - + Video Call @@ -125,7 +125,7 @@ ChatPage - + Failed to invite user: %1 邀请用户失败: %1 @@ -157,12 +157,12 @@ - + Confirm invite - + Do you really want to invite %1 (%2)? @@ -227,12 +227,12 @@ 解禁用户: %1 - + Do you really want to start a private chat with %1? - + Cache migration failed! 缓存迁移失败! @@ -367,7 +367,7 @@ - + Decryption failed @@ -494,6 +494,49 @@ + + Encrypted + + + There is no key to unlock this message. We requested the key automatically, but you can try requesting it again if you are impatient. + + + + + This message couldn't be decrypted, because we only have a key for newer messages. You can try requesting access to this message. + + + + + There was an internal error reading the decryption key from the database. + + + + + There was an error decrypting this message. + + + + + The message couldn't be parsed. + + + + + The encryption key was reused! Someone is possibly trying to insert false messages into this chat! + + + + + Unknown decryption error + + + + + Request key + + + EncryptionIndicator @@ -517,51 +560,6 @@ - - EventStore - - - -- Encrypted Event (No keys found for decryption) -- - Placeholder, when the message was not decrypted yet or can't be decrypted. - - - - - -- Encrypted Event (Key not valid for this index) -- - Placeholder, when the message can't be decrypted with this key since it is not valid for this index - - - - - - -- Decryption Error (failed to retrieve megolm keys from db) -- - Placeholder, when the message can't be decrypted, because the DB access failed. - - - - - - -- Decryption Error (%1) -- - Placeholder, when the message can't be decrypted. In this case, the Olm decrytion returned an error, which is passed as %1. - - - - - -- Encrypted Event (Unknown event type) -- - Placeholder, when the message was decrypted, but we couldn't parse it, because Nheko/mtxclient don't support that event type yet. - - - - - -- Replay attack! This message index was reused! -- - - - - - -- Message by unverified device! -- - - - Failed @@ -604,6 +602,71 @@ + + ImagePackEditorDialog + + + Editing image pack + + + + + Add images + + + + + Stickers (*.png *.webp) + + + + + State key + + + + + Packname + + + + + Attribution + + + + + + Use as Emoji + + + + + + Use as Sticker + + + + + Shortcode + + + + + Body + + + + + Cancel + 取消 + + + + Save + + + ImagePackSettingsDialog @@ -612,7 +675,17 @@ - + + Create account pack + + + + + New room pack + + + + Private pack @@ -627,7 +700,7 @@ - + Enable globally @@ -637,7 +710,12 @@ - + + Edit + + + + Close @@ -807,7 +885,7 @@ Example: https://server.my:8787 MessageDelegate - + removed @@ -818,7 +896,7 @@ Example: https://server.my:8787 - + room name changed to: %1 @@ -901,7 +979,7 @@ Example: https://server.my:8787 写一条消息… - + Stickers @@ -924,7 +1002,7 @@ Example: https://server.my:8787 MessageView - + Edit @@ -944,7 +1022,7 @@ Example: https://server.my:8787 - + &Copy @@ -1175,21 +1253,37 @@ Example: https://server.my:8787 + + ReadReceipts + + + Read receipts + 阅读回执 + + + + ReadReceiptsModel + + + Yesterday, %1 + + + RegisterPage - + Username 用户名 - + The username must not be empty, and must contain only the characters a-z, 0-9, ., _, =, -, and /. - + Password 密码 @@ -1209,7 +1303,7 @@ Example: https://server.my:8787 - + A server that allows registration. Since matrix is decentralized, you need to first find a server you can register on or host your own. @@ -1219,22 +1313,27 @@ Example: https://server.my:8787 注册 - + No supported registration flows! - - One or more fields have invalid inputs. Please correct those issues and try again. + + Registration token - + + Please enter a valid registration token. + + + + Autodiscovery failed. Received malformed response. - + Autodiscovery failed. Unknown error when requesting .well-known. @@ -1244,7 +1343,7 @@ Example: https://server.my:8787 没找到要求的终端。可能不是一个 Matrix 服务器。 - + Received malformed response. Make sure the homeserver domain is valid. 收到形式错误的响应。请确认服务器域名合法。 @@ -1254,17 +1353,17 @@ Example: https://server.my:8787 发生了一个未知错误。请确认服务器域名合法。 - + Password is not long enough (min 8 chars) 密码不够长(至少8个字符) - + Passwords don't match 密码不匹配 - + Invalid server name 无效的服务器名 @@ -1272,7 +1371,7 @@ Example: https://server.my:8787 ReplyPopup - + Close @@ -1285,7 +1384,7 @@ Example: https://server.my:8787 RoomInfo - + no version stored @@ -1293,7 +1392,7 @@ Example: https://server.my:8787 RoomList - + New tag @@ -1343,7 +1442,7 @@ Example: https://server.my:8787 - + Status Message @@ -1363,7 +1462,7 @@ Example: https://server.my:8787 - + Logout 登出 @@ -1396,7 +1495,7 @@ Example: https://server.my:8787 RoomMembers - + Members of %1 @@ -1413,11 +1512,31 @@ Example: https://server.my:8787 Invite more people + + + This room is not encrypted! + + + + + This user is verified. + + + + + This user isn't verified, but is still using the same master key from the first time you met. + + + + + This user has unverified devices! + + RoomSettings - + Room Settings @@ -1452,7 +1571,12 @@ Example: https://server.my:8787 - + + Room access + + + + Anyone and guests @@ -1616,6 +1740,30 @@ Example: https://server.my:8787 取消 + + SingleImagePackModel + + + + Failed to update image pack: {} + + + + + Failed to delete old image pack: {} + + + + + Failed to open image: {} + + + + + Failed to upload image: {} + + + StatusIndicator @@ -1668,7 +1816,7 @@ Example: https://server.my:8787 TimelineModel - + Message redaction failed: %1 删除消息失败:%1 @@ -1699,7 +1847,7 @@ Example: https://server.my:8787 - + %1 and %2 are typing. Multiple users are typing. First argument is a comma separated list of potentially multiple users. Second argument is the last user of that list. (If only one user is typing, %1 is empty. You should still use it in your string though to silence Qt warnings.) @@ -1812,12 +1960,12 @@ Example: https://server.my:8787 - + You joined this room. 您已加入此房间 - + %1 has changed their avatar and changed their display name to %2. @@ -1846,7 +1994,7 @@ Example: https://server.my:8787 TimelineRow - + Edited @@ -1854,12 +2002,12 @@ Example: https://server.my:8787 TimelineView - + No room open - + %1 member(s) @@ -1887,7 +2035,7 @@ Example: https://server.my:8787 TimelineViewManager - + No encrypted private chat found with this user. Create an encrypted private chat with this user and try again. @@ -1895,17 +2043,37 @@ Example: https://server.my:8787 TopBar - + Back to room list - + No room selected - + + This room is not encrypted! + + + + + This room contains only verified devices. + + + + + This rooms contain verified devices and devices which have never changed their master key. + + + + + This room contains unverified devices! + + + + Room options 聊天室选项 @@ -1946,7 +2114,7 @@ Example: https://server.my:8787 UserProfile - + Global User Profile @@ -1956,7 +2124,7 @@ Example: https://server.my:8787 - + Verify @@ -2005,8 +2173,8 @@ Example: https://server.my:8787 UserSettings - - + + Default @@ -2014,7 +2182,7 @@ Example: https://server.my:8787 UserSettingsPage - + Minimize to tray 最小化至托盘 @@ -2034,12 +2202,12 @@ Example: https://server.my:8787 - + profile: %1 - + Default @@ -2225,12 +2393,27 @@ This usually causes the application icon in the task bar to animate in some fash - + + Send encrypted messages to verified users only + + + + + Requires a user to be verified to send encrypted messages to them. This improves safety but makes E2EE more tedious. + + + + Share keys with verified users and devices - + + Automatically replies to key requests from other users, if they are verified, even if that device shouldn't have access to those keys otherwise. + + + + CACHED @@ -2240,7 +2423,7 @@ This usually causes the application icon in the task bar to animate in some fash - + Scale factor @@ -2335,12 +2518,12 @@ This usually causes the application icon in the task bar to animate in some fash 加密 - + GENERAL 通用 - + INTERFACE @@ -2360,12 +2543,7 @@ This usually causes the application icon in the task bar to animate in some fash - - Automatically replies to key requests from other users, if they are verified. - - - - + Master signing key @@ -2415,7 +2593,7 @@ This usually causes the application icon in the task bar to animate in some fash 所有文件(*) - + Open Sessions File 打开会话文件 @@ -2666,32 +2844,6 @@ Media size: %2 解决 reCAPTCHA 并按确认按钮 - - dialogs::ReadReceipts - - - Read receipts - 阅读回执 - - - - Close - - - - - dialogs::ReceiptItem - - - Today %1 - - - - - Yesterday %1 - - - message-description sent: From 56db0dbc7d0878ce1621a4dc998024d470d200a5 Mon Sep 17 00:00:00 2001 From: Nicolas Werner Date: Tue, 17 Aug 2021 03:23:51 +0200 Subject: [PATCH 021/232] Allow downloading keys from key backup --- CMakeLists.txt | 2 +- io.github.NhekoReborn.Nheko.yaml | 2 +- resources/qml/EncryptionIndicator.qml | 2 +- src/Cache.cpp | 47 ++++++++++++ src/CacheCryptoStructs.h | 25 +++++++ src/Cache_p.h | 4 + src/ChatPage.cpp | 100 +++++++++++++++++++++++++ src/ChatPage.h | 1 + src/MainWindow.cpp | 37 ++++----- src/Olm.cpp | 104 +++++++++++++++++++++++++- src/Olm.h | 4 +- src/UserSettingsPage.cpp | 24 ++++++ src/UserSettingsPage.h | 7 ++ src/timeline/EventStore.cpp | 7 +- src/timeline/TimelineModel.cpp | 10 +-- 15 files changed, 342 insertions(+), 34 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 8ef4470c..db77c1f7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -381,7 +381,7 @@ if(USE_BUNDLED_MTXCLIENT) FetchContent_Declare( MatrixClient GIT_REPOSITORY https://github.com/Nheko-Reborn/mtxclient.git - GIT_TAG deb51ef1d6df870098069312f0a1999550e1eb85 + GIT_TAG 513196f520733e2f70576168aff7aaf16e0df180 ) set(BUILD_LIB_EXAMPLES OFF CACHE INTERNAL "") set(BUILD_LIB_TESTS OFF CACHE INTERNAL "") diff --git a/io.github.NhekoReborn.Nheko.yaml b/io.github.NhekoReborn.Nheko.yaml index c9caddc8..9d4b3b2e 100644 --- a/io.github.NhekoReborn.Nheko.yaml +++ b/io.github.NhekoReborn.Nheko.yaml @@ -163,7 +163,7 @@ modules: buildsystem: cmake-ninja name: mtxclient sources: - - commit: deb51ef1d6df870098069312f0a1999550e1eb85 + - commit: 513196f520733e2f70576168aff7aaf16e0df180 type: git url: https://github.com/Nheko-Reborn/mtxclient.git - config-opts: diff --git a/resources/qml/EncryptionIndicator.qml b/resources/qml/EncryptionIndicator.qml index 52d2eeed..6bc16a18 100644 --- a/resources/qml/EncryptionIndicator.qml +++ b/resources/qml/EncryptionIndicator.qml @@ -39,7 +39,7 @@ Image { case Crypto.TOFU: return qsTr("Encrypted by an unverified device, but you have trusted that user so far."); default: - return qsTr("Encrypted by an unverified device"); + return qsTr("Encrypted by an unverified device or the key is from an untrusted source like the key backup."); } } diff --git a/src/Cache.cpp b/src/Cache.cpp index 8b8b2985..6b28ca91 100644 --- a/src/Cache.cpp +++ b/src/Cache.cpp @@ -42,6 +42,7 @@ static const std::string SECRET("secret"); static const std::string_view NEXT_BATCH_KEY("next_batch"); static const std::string_view OLM_ACCOUNT_KEY("olm_account"); static const std::string_view CACHE_FORMAT_VERSION_KEY("cache_format_version"); +static const std::string_view CURRENT_ONLINE_BACKUP_VERSION("current_online_backup_version"); constexpr size_t MAX_RESTORED_MESSAGES = 30'000; @@ -723,6 +724,36 @@ Cache::restoreOlmAccount() return std::string(pickled.data(), pickled.size()); } +void +Cache::saveBackupVersion(const OnlineBackupVersion &data) +{ + auto txn = lmdb::txn::begin(env_); + syncStateDb_.put(txn, CURRENT_ONLINE_BACKUP_VERSION, nlohmann::json(data).dump()); + txn.commit(); +} + +void +Cache::deleteBackupVersion() +{ + auto txn = lmdb::txn::begin(env_); + syncStateDb_.del(txn, CURRENT_ONLINE_BACKUP_VERSION); + txn.commit(); +} + +std::optional +Cache::backupVersion() +{ + try { + auto txn = ro_txn(env_); + std::string_view v; + syncStateDb_.get(txn, CURRENT_ONLINE_BACKUP_VERSION, v); + + return nlohmann::json::parse(v).get(); + } catch (...) { + return std::nullopt; + } +} + void Cache::storeSecret(const std::string name, const std::string secret) { @@ -4114,6 +4145,20 @@ from_json(const json &j, VerificationCache &info) info.device_blocked = j.at("device_blocked").get>(); } +void +to_json(json &j, const OnlineBackupVersion &info) +{ + j["v"] = info.version; + j["a"] = info.algorithm; +} + +void +from_json(const json &j, OnlineBackupVersion &info) +{ + info.version = j.at("v").get(); + info.algorithm = j.at("a").get(); +} + std::optional Cache::verificationCache(const std::string &user_id, lmdb::txn &txn) { @@ -4461,6 +4506,7 @@ to_json(nlohmann::json &obj, const GroupSessionData &msg) { obj["message_index"] = msg.message_index; obj["ts"] = msg.timestamp; + obj["trust"] = msg.trusted; obj["sender_claimed_ed25519_key"] = msg.sender_claimed_ed25519_key; obj["forwarding_curve25519_key_chain"] = msg.forwarding_curve25519_key_chain; @@ -4475,6 +4521,7 @@ from_json(const nlohmann::json &obj, GroupSessionData &msg) { msg.message_index = obj.at("message_index"); msg.timestamp = obj.value("ts", 0ULL); + msg.trusted = obj.value("trust", true); msg.sender_claimed_ed25519_key = obj.value("sender_claimed_ed25519_key", ""); msg.forwarding_curve25519_key_chain = diff --git a/src/CacheCryptoStructs.h b/src/CacheCryptoStructs.h index 6c402674..80dd1046 100644 --- a/src/CacheCryptoStructs.h +++ b/src/CacheCryptoStructs.h @@ -47,6 +47,11 @@ struct GroupSessionData uint64_t message_index = 0; uint64_t timestamp = 0; + // If we got the session via key sharing or forwarding, we can usually trust it. + // If it came from asymmetric key backup, it is not trusted. + // TODO(Nico): What about forwards? They might come from key backup? + bool trusted = true; + std::string sender_claimed_ed25519_key; std::vector forwarding_curve25519_key_chain; @@ -83,6 +88,13 @@ from_json(const nlohmann::json &obj, DevicePublicKeys &msg); //! Represents a unique megolm session identifier. struct MegolmSessionIndex { + MegolmSessionIndex() = default; + MegolmSessionIndex(std::string room_id_, const mtx::events::msg::Encrypted &e) + : room_id(std::move(room_id_)) + , session_id(e.session_id) + , sender_key(e.sender_key) + {} + //! The room in which this session exists. std::string room_id; //! The session_id of the megolm session. @@ -167,3 +179,16 @@ void to_json(nlohmann::json &j, const VerificationCache &info); void from_json(const nlohmann::json &j, VerificationCache &info); + +struct OnlineBackupVersion +{ + //! the version of the online backup currently enabled + std::string version; + //! the algorithm used by the backup + std::string algorithm; +}; + +void +to_json(nlohmann::json &j, const OnlineBackupVersion &info); +void +from_json(const nlohmann::json &j, OnlineBackupVersion &info); diff --git a/src/Cache_p.h b/src/Cache_p.h index 748404d1..8322a6af 100644 --- a/src/Cache_p.h +++ b/src/Cache_p.h @@ -287,6 +287,10 @@ public: void saveOlmAccount(const std::string &pickled); std::string restoreOlmAccount(); + void saveBackupVersion(const OnlineBackupVersion &data); + void deleteBackupVersion(); + std::optional backupVersion(); + void storeSecret(const std::string name, const std::string secret); void deleteSecret(const std::string name); std::optional secret(const std::string name); diff --git a/src/ChatPage.cpp b/src/ChatPage.cpp index 8a0e891b..639f3818 100644 --- a/src/ChatPage.cpp +++ b/src/ChatPage.cpp @@ -385,6 +385,7 @@ ChatPage::bootstrap(QString userid, QString homeserver, QString token) } getProfileInfo(); + getBackupVersion(); tryInitialSync(); } @@ -418,6 +419,7 @@ ChatPage::loadStateFromCache() nhlog::crypto()->info("curve25519: {}", olm::client()->identity_keys().curve25519); getProfileInfo(); + getBackupVersion(); emit contentLoaded(); @@ -974,6 +976,104 @@ ChatPage::getProfileInfo() }); } +void +ChatPage::getBackupVersion() +{ + if (!UserSettings::instance()->useOnlineKeyBackup()) { + nhlog::crypto()->info("Online key backup disabled."); + return; + } + + http::client()->backup_version( + [this](const mtx::responses::backup::BackupVersion &res, mtx::http::RequestErr err) { + if (err) { + nhlog::net()->warn("Failed to retrieve backup version"); + if (err->status_code == 404) + cache::client()->deleteBackupVersion(); + return; + } + + // switch to UI thread for secrets stuff + QTimer::singleShot(0, this, [this, res] { + auto auth_data = nlohmann::json::parse(res.auth_data); + + if (res.algorithm == "m.megolm_backup.v1.curve25519-aes-sha2") { + auto key = + cache::secret(mtx::secret_storage::secrets::megolm_backup_v1); + if (!key) { + nhlog::crypto()->info("No key for online key backup."); + cache::client()->deleteBackupVersion(); + return; + } + + using namespace mtx::crypto; + auto pubkey = CURVE25519_public_key_from_private( + to_binary_buf(base642bin(*key))); + + if (auth_data["public_key"].get() != pubkey) { + nhlog::crypto()->info( + "Our backup key {} does not match the one " + "used in the online backup {}", + pubkey, + auth_data["public_key"]); + cache::client()->deleteBackupVersion(); + return; + } + + nhlog::crypto()->info("Using online key backup."); + OnlineBackupVersion data{}; + data.algorithm = res.algorithm; + data.version = res.version; + cache::client()->saveBackupVersion(data); + + // We really don't need to add our signature. + // if (!auth_data["signatures"] + // [http::client()->user_id().to_string()] + // .contains("ed25519:" + + // http::client()->device_id())) { + // // add our signature + // // This is not strictly necessary, but some Element + // // clients rely on it. We assume the master_key + // signature + // // already exists and add our device signature just to + // be + // // safe, even though just the master signature is + // enough, + // // if cross-signing is used. + // auto signatures = auth_data["signatures"]; + // auth_data.erase("signatures"); + // auth_data.erase("unsigned"); + // signatures[http::client()->user_id().to_string()] + // ["ed25519:" + http::client()->device_id()] = + // olm::client()->sign_message(auth_data.dump()); + // auth_data["signatures"] = signatures; + + // auto copy = res; + // copy.auth_data = auth_data.dump(); + // http::client()->update_backup_version( + // res.version, copy, [](mtx::http::RequestErr e) { + // if (e) { + // nhlog::crypto()->error( + // "Failed to update online backup " + // "signatures: {} - {}", + // mtx::errors::to_string( + // e->matrix_error.errcode), + // e->matrix_error.error); + // } else { + // nhlog::crypto()->info( + // "Updated key backup signatures"); + // } + // }); + //} + } else { + nhlog::crypto()->info("Unsupported key backup algorithm: {}", + res.algorithm); + cache::client()->deleteBackupVersion(); + } + }); + }); +} + void ChatPage::initiateLogout() { diff --git a/src/ChatPage.h b/src/ChatPage.h index c90b87f5..dfe94c37 100644 --- a/src/ChatPage.h +++ b/src/ChatPage.h @@ -182,6 +182,7 @@ private: void trySync(); void ensureOneTimeKeyCount(const std::map &counts); void getProfileInfo(); + void getBackupVersion(); //! Check if the given room is currently open. bool isRoomActive(const QString &room_id); diff --git a/src/MainWindow.cpp b/src/MainWindow.cpp index 8bc90f29..396e1ab1 100644 --- a/src/MainWindow.cpp +++ b/src/MainWindow.cpp @@ -130,26 +130,29 @@ MainWindow::MainWindow(QWidget *parent) trayIcon_->setVisible(userSettings_->tray()); - if (hasActiveUser()) { - QString token = userSettings_->accessToken(); - QString home_server = userSettings_->homeserver(); - QString user_id = userSettings_->userId(); - QString device_id = userSettings_->deviceId(); + // load cache on event loop + QTimer::singleShot(0, this, [this] { + if (hasActiveUser()) { + QString token = userSettings_->accessToken(); + QString home_server = userSettings_->homeserver(); + QString user_id = userSettings_->userId(); + QString device_id = userSettings_->deviceId(); - http::client()->set_access_token(token.toStdString()); - http::client()->set_server(home_server.toStdString()); - http::client()->set_device_id(device_id.toStdString()); + http::client()->set_access_token(token.toStdString()); + http::client()->set_server(home_server.toStdString()); + http::client()->set_device_id(device_id.toStdString()); - try { - using namespace mtx::identifiers; - http::client()->set_user(parse(user_id.toStdString())); - } catch (const std::invalid_argument &) { - nhlog::ui()->critical("bootstrapped with invalid user_id: {}", - user_id.toStdString()); + try { + using namespace mtx::identifiers; + http::client()->set_user(parse(user_id.toStdString())); + } catch (const std::invalid_argument &) { + nhlog::ui()->critical("bootstrapped with invalid user_id: {}", + user_id.toStdString()); + } + + showChatPage(); } - - showChatPage(); - } + }); if (loadJdenticonPlugin()) { nhlog::ui()->info("loaded jdenticon."); diff --git a/src/Olm.cpp b/src/Olm.cpp index 2c9ac5a3..05eefce4 100644 --- a/src/Olm.cpp +++ b/src/Olm.cpp @@ -833,6 +833,8 @@ import_inbound_megolm_session( data.forwarding_curve25519_key_chain = roomKey.content.forwarding_curve25519_key_chain; data.sender_claimed_ed25519_key = roomKey.content.sender_claimed_ed25519_key; + // may have come from online key backup, so we can't trust it... + data.trusted = false; cache::saveInboundMegolmSession(index, std::move(megolm_session), data); } catch (const lmdb::error &e) { @@ -856,6 +858,97 @@ mark_keys_as_published() cache::saveOlmAccount(olm::client()->save(STORAGE_SECRET_KEY)); } +void +lookup_keybackup(const std::string room, const std::string session_id) +{ + if (!UserSettings::instance()->useOnlineKeyBackup()) { + // Online key backup disabled + return; + } + + auto backupVersion = cache::client()->backupVersion(); + if (!backupVersion) { + // no trusted OKB + return; + } + + using namespace mtx::crypto; + + auto decryptedSecret = cache::secret(mtx::secret_storage::secrets::megolm_backup_v1); + if (!decryptedSecret) { + // no backup key available + return; + } + auto sessionDecryptionKey = to_binary_buf(base642bin(*decryptedSecret)); + + http::client()->room_keys( + backupVersion->version, + room, + session_id, + [room, session_id, sessionDecryptionKey](const mtx::responses::backup::SessionBackup &bk, + mtx::http::RequestErr err) { + if (err) { + if (err->status_code != 404) + nhlog::crypto()->error( + "Failed to dowload key {}:{}: {} - {}", + room, + session_id, + mtx::errors::to_string(err->matrix_error.errcode), + err->matrix_error.error); + return; + } + try { + auto session = decrypt_session(bk.session_data, sessionDecryptionKey); + + if (session.algorithm != mtx::crypto::MEGOLM_ALGO) + // don't know this algorithm + return; + + MegolmSessionIndex index; + index.room_id = room; + index.session_id = session_id; + index.sender_key = session.sender_key; + + GroupSessionData data{}; + data.forwarding_curve25519_key_chain = + session.forwarding_curve25519_key_chain; + data.sender_claimed_ed25519_key = session.sender_claimed_keys["ed25519"]; + // online key backup can't be trusted, because anyone can upload to it. + data.trusted = false; + + auto megolm_session = + olm::client()->import_inbound_group_session(session.session_key); + + if (!cache::inboundMegolmSessionExists(index) || + olm_inbound_group_session_first_known_index(megolm_session.get()) < + olm_inbound_group_session_first_known_index( + cache::getInboundMegolmSession(index).get())) { + cache::saveInboundMegolmSession( + index, std::move(megolm_session), data); + + nhlog::crypto()->info("imported inbound megolm session " + "from key backup ({}, {})", + room, + session_id); + + // call on UI thread + QTimer::singleShot(0, ChatPage::instance(), [index] { + ChatPage::instance()->receivedSessionKey( + index.room_id, index.session_id); + }); + } + } catch (const lmdb::error &e) { + nhlog::crypto()->critical("failed to save inbound megolm session: {}", + e.what()); + return; + } catch (const mtx::crypto::olm_exception &e) { + nhlog::crypto()->critical("failed to import inbound megolm session: {}", + e.what()); + return; + } + }); +} + void send_key_request_for(mtx::events::EncryptedEvent e, const std::string &request_id, @@ -898,6 +991,8 @@ send_key_request_for(mtx::events::EncryptedEvent e, e.sender, e.content.device_id); }); + + // http::client()->room_keys } void @@ -1095,12 +1190,15 @@ decryptEvent(const MegolmSessionIndex &index, } crypto::Trust -calculate_trust(const std::string &user_id, const std::string &curve25519) +calculate_trust(const std::string &user_id, const MegolmSessionIndex &index) { auto status = cache::client()->verificationStatus(user_id); + auto megolmData = cache::client()->getMegolmSessionData(index); crypto::Trust trustlevel = crypto::Trust::Unverified; - if (status.verified_device_keys.count(curve25519)) - trustlevel = status.verified_device_keys.at(curve25519); + + if (megolmData && megolmData->trusted && + status.verified_device_keys.count(index.sender_key)) + trustlevel = status.verified_device_keys.at(index.sender_key); return trustlevel; } diff --git a/src/Olm.h b/src/Olm.h index ab86ca00..eb60ae3a 100644 --- a/src/Olm.h +++ b/src/Olm.h @@ -70,6 +70,8 @@ create_inbound_megolm_session(const mtx::events::DeviceEvent &roomKey); +void +lookup_keybackup(const std::string room, const std::string session_id); nlohmann::json handle_pre_key_olm_message(const std::string &sender, @@ -87,7 +89,7 @@ decryptEvent(const MegolmSessionIndex &index, const mtx::events::EncryptedEvent &event, bool dont_write_db = false); crypto::Trust -calculate_trust(const std::string &user_id, const std::string &curve25519); +calculate_trust(const std::string &user_id, const MegolmSessionIndex &index); void mark_keys_as_published(); diff --git a/src/UserSettingsPage.cpp b/src/UserSettingsPage.cpp index f67c5e2d..ab1e442c 100644 --- a/src/UserSettingsPage.cpp +++ b/src/UserSettingsPage.cpp @@ -124,6 +124,7 @@ UserSettings::load(std::optional profile) .toBool(); onlyShareKeysWithVerifiedUsers_ = settings.value(prefix + "user/only_share_keys_with_verified_users", false).toBool(); + useOnlineKeyBackup_ = settings.value(prefix + "user/online_key_backup", true).toBool(); disableCertificateValidation_ = settings.value("disable_certificate_validation", false).toBool(); @@ -425,6 +426,17 @@ UserSettings::setShareKeysWithTrustedUsers(bool shareKeys) save(); } +void +UserSettings::setUseOnlineKeyBackup(bool useBackup) +{ + if (useBackup == useOnlineKeyBackup_) + return; + + useOnlineKeyBackup_ = useBackup; + emit useOnlineKeyBackupChanged(useBackup); + save(); +} + void UserSettings::setRingtone(QString ringtone) { @@ -664,6 +676,7 @@ UserSettings::save() shareKeysWithTrustedUsers_); settings.setValue(prefix + "user/only_share_keys_with_verified_users", onlyShareKeysWithVerifiedUsers_); + settings.setValue(prefix + "user/online_key_backup", useOnlineKeyBackup_); settings.setValue("disable_certificate_validation", disableCertificateValidation_); @@ -725,6 +738,7 @@ UserSettingsPage::UserSettingsPage(QSharedPointer settings, QWidge privacyScreen_ = new Toggle{this}; onlyShareKeysWithVerifiedUsers_ = new Toggle(this); shareKeysWithTrustedUsers_ = new Toggle(this); + useOnlineKeyBackup_ = new Toggle(this); groupViewToggle_ = new Toggle{this}; timelineButtonsToggle_ = new Toggle{this}; typingNotifications_ = new Toggle{this}; @@ -756,6 +770,7 @@ UserSettingsPage::UserSettingsPage(QSharedPointer settings, QWidge privacyScreen_->setChecked(settings_->privacyScreen()); onlyShareKeysWithVerifiedUsers_->setChecked(settings_->onlyShareKeysWithVerifiedUsers()); shareKeysWithTrustedUsers_->setChecked(settings_->shareKeysWithTrustedUsers()); + useOnlineKeyBackup_->setChecked(settings_->useOnlineKeyBackup()); groupViewToggle_->setChecked(settings_->groupView()); timelineButtonsToggle_->setChecked(settings_->buttonsInTimeline()); typingNotifications_->setChecked(settings_->typingNotifications()); @@ -1033,6 +1048,10 @@ UserSettingsPage::UserSettingsPage(QSharedPointer settings, QWidge shareKeysWithTrustedUsers_, tr("Automatically replies to key requests from other users, if they are verified, " "even if that device shouldn't have access to those keys otherwise.")); + boxWrap(tr("Online Key Backup"), + useOnlineKeyBackup_, + tr("Download message encryption keys from and upload to the encrypted online key " + "backup.")); formLayout_->addRow(new HorizontalLine{this}); formLayout_->addRow(sessionKeysLabel, sessionKeysLayout); formLayout_->addRow(crossSigningKeysLabel, crossSigningKeysLayout); @@ -1208,6 +1227,10 @@ UserSettingsPage::UserSettingsPage(QSharedPointer settings, QWidge settings_->setShareKeysWithTrustedUsers(enabled); }); + connect(useOnlineKeyBackup_, &Toggle::toggled, this, [this](bool enabled) { + settings_->setUseOnlineKeyBackup(enabled); + }); + connect(avatarCircles_, &Toggle::toggled, this, [this](bool enabled) { settings_->setAvatarCircles(enabled); }); @@ -1298,6 +1321,7 @@ UserSettingsPage::showEvent(QShowEvent *) privacyScreen_->setState(settings_->privacyScreen()); onlyShareKeysWithVerifiedUsers_->setState(settings_->onlyShareKeysWithVerifiedUsers()); shareKeysWithTrustedUsers_->setState(settings_->shareKeysWithTrustedUsers()); + useOnlineKeyBackup_->setState(settings_->useOnlineKeyBackup()); avatarCircles_->setState(settings_->avatarCircles()); typingNotifications_->setState(settings_->typingNotifications()); sortByImportance_->setState(settings_->sortByImportance()); diff --git a/src/UserSettingsPage.h b/src/UserSettingsPage.h index 84940e47..ab9c9a3b 100644 --- a/src/UserSettingsPage.h +++ b/src/UserSettingsPage.h @@ -93,6 +93,8 @@ class UserSettings : public QObject setOnlyShareKeysWithVerifiedUsers NOTIFY onlyShareKeysWithVerifiedUsersChanged) Q_PROPERTY(bool shareKeysWithTrustedUsers READ shareKeysWithTrustedUsers WRITE setShareKeysWithTrustedUsers NOTIFY shareKeysWithTrustedUsersChanged) + Q_PROPERTY(bool useOnlineKeyBackup READ useOnlineKeyBackup WRITE setUseOnlineKeyBackup + NOTIFY useOnlineKeyBackupChanged) Q_PROPERTY(QString profile READ profile WRITE setProfile NOTIFY profileChanged) Q_PROPERTY(QString userId READ userId WRITE setUserId NOTIFY userIdChanged) Q_PROPERTY( @@ -159,6 +161,7 @@ public: void setUseStunServer(bool state); void setOnlyShareKeysWithVerifiedUsers(bool state); void setShareKeysWithTrustedUsers(bool state); + void setUseOnlineKeyBackup(bool state); void setProfile(QString profile); void setUserId(QString userId); void setAccessToken(QString accessToken); @@ -215,6 +218,7 @@ public: bool useStunServer() const { return useStunServer_; } bool shareKeysWithTrustedUsers() const { return shareKeysWithTrustedUsers_; } bool onlyShareKeysWithVerifiedUsers() const { return onlyShareKeysWithVerifiedUsers_; } + bool useOnlineKeyBackup() const { return useOnlineKeyBackup_; } QString profile() const { return profile_; } QString userId() const { return userId_; } QString accessToken() const { return accessToken_; } @@ -261,6 +265,7 @@ signals: void useStunServerChanged(bool state); void onlyShareKeysWithVerifiedUsersChanged(bool state); void shareKeysWithTrustedUsersChanged(bool state); + void useOnlineKeyBackupChanged(bool state); void profileChanged(QString profile); void userIdChanged(QString userId); void accessTokenChanged(QString accessToken); @@ -293,6 +298,7 @@ private: int privacyScreenTimeout_; bool shareKeysWithTrustedUsers_; bool onlyShareKeysWithVerifiedUsers_; + bool useOnlineKeyBackup_; bool mobileMode_; int timelineMaxWidth_; int roomListWidth_; @@ -384,6 +390,7 @@ private: QSpinBox *privacyScreenTimeout_; Toggle *shareKeysWithTrustedUsers_; Toggle *onlyShareKeysWithVerifiedUsers_; + Toggle *useOnlineKeyBackup_; Toggle *mobileMode_; QLabel *deviceFingerprintValue_; QLabel *deviceIdValue_; diff --git a/src/timeline/EventStore.cpp b/src/timeline/EventStore.cpp index 742f8dbb..8860bc75 100644 --- a/src/timeline/EventStore.cpp +++ b/src/timeline/EventStore.cpp @@ -643,10 +643,7 @@ EventStore::decryptEvent(const IdIndex &idx, if (auto cachedEvent = decryptedEvents_.object(idx)) return cachedEvent; - MegolmSessionIndex index; - index.room_id = room_id_; - index.session_id = e.content.session_id; - index.sender_key = e.content.sender_key; + MegolmSessionIndex index(room_id_, e.content); auto asCacheEntry = [&idx](olm::DecryptionResult &&event) { auto event_ptr = new olm::DecryptionResult(std::move(event)); @@ -726,6 +723,7 @@ EventStore::requestSession(const mtx::events::EncryptedEventgenerate_txn_id(); request.requested_at = QDateTime::currentSecsSinceEpoch(); request.events.push_back(copy); + olm::lookup_keybackup(room_id_, ev.content.session_id); olm::send_key_request_for(copy, request.request_id); pending_key_requests[ev.content.session_id] = request; } diff --git a/src/timeline/TimelineModel.cpp b/src/timeline/TimelineModel.cpp index 79c28edf..88d575fa 100644 --- a/src/timeline/TimelineModel.cpp +++ b/src/timeline/TimelineModel.cpp @@ -641,8 +641,9 @@ TimelineModel::data(const mtx::events::collections::TimelineEvents &event, int r if (auto encrypted = std::get_if>( &*encrypted_event)) { - return olm::calculate_trust(encrypted->sender, - encrypted->content.sender_key); + return olm::calculate_trust( + encrypted->sender, + MegolmSessionIndex(room_id_.toStdString(), encrypted->content)); } } return crypto::Trust::Unverified; @@ -840,10 +841,7 @@ TimelineModel::addEvents(const mtx::responses::Timeline &timeline) for (auto e : timeline.events) { if (auto encryptedEvent = std::get_if>(&e)) { - MegolmSessionIndex index; - index.room_id = room_id_.toStdString(); - index.session_id = encryptedEvent->content.session_id; - index.sender_key = encryptedEvent->content.sender_key; + MegolmSessionIndex index(room_id_.toStdString(), encryptedEvent->content); auto result = olm::decryptEvent(index, *encryptedEvent); if (result.event) From 7d62af7cda6fe8d470df91b4a84452671cb5997e Mon Sep 17 00:00:00 2001 From: Nicolas Werner Date: Tue, 17 Aug 2021 09:59:13 +0200 Subject: [PATCH 022/232] Remove unneeded code --- src/ChatPage.cpp | 42 +----------------------------------------- 1 file changed, 1 insertion(+), 41 deletions(-) diff --git a/src/ChatPage.cpp b/src/ChatPage.cpp index 639f3818..88d393ce 100644 --- a/src/ChatPage.cpp +++ b/src/ChatPage.cpp @@ -994,7 +994,7 @@ ChatPage::getBackupVersion() } // switch to UI thread for secrets stuff - QTimer::singleShot(0, this, [this, res] { + QTimer::singleShot(0, this, [res] { auto auth_data = nlohmann::json::parse(res.auth_data); if (res.algorithm == "m.megolm_backup.v1.curve25519-aes-sha2") { @@ -1025,46 +1025,6 @@ ChatPage::getBackupVersion() data.algorithm = res.algorithm; data.version = res.version; cache::client()->saveBackupVersion(data); - - // We really don't need to add our signature. - // if (!auth_data["signatures"] - // [http::client()->user_id().to_string()] - // .contains("ed25519:" + - // http::client()->device_id())) { - // // add our signature - // // This is not strictly necessary, but some Element - // // clients rely on it. We assume the master_key - // signature - // // already exists and add our device signature just to - // be - // // safe, even though just the master signature is - // enough, - // // if cross-signing is used. - // auto signatures = auth_data["signatures"]; - // auth_data.erase("signatures"); - // auth_data.erase("unsigned"); - // signatures[http::client()->user_id().to_string()] - // ["ed25519:" + http::client()->device_id()] = - // olm::client()->sign_message(auth_data.dump()); - // auth_data["signatures"] = signatures; - - // auto copy = res; - // copy.auth_data = auth_data.dump(); - // http::client()->update_backup_version( - // res.version, copy, [](mtx::http::RequestErr e) { - // if (e) { - // nhlog::crypto()->error( - // "Failed to update online backup " - // "signatures: {} - {}", - // mtx::errors::to_string( - // e->matrix_error.errcode), - // e->matrix_error.error); - // } else { - // nhlog::crypto()->info( - // "Updated key backup signatures"); - // } - // }); - //} } else { nhlog::crypto()->info("Unsupported key backup algorithm: {}", res.algorithm); From 028bcd5b7c116564b6b44a4ce4c4bdac5ddcb926 Mon Sep 17 00:00:00 2001 From: Nicolas Werner Date: Tue, 17 Aug 2021 14:21:04 +0200 Subject: [PATCH 023/232] Update join button in room directory after join --- resources/qml/RoomDirectory.qml | 3 +-- src/RoomDirectoryModel.cpp | 25 ++++++++++++++++++++----- src/RoomDirectoryModel.h | 8 +++++--- src/timeline/RoomlistModel.cpp | 2 ++ 4 files changed, 28 insertions(+), 10 deletions(-) diff --git a/resources/qml/RoomDirectory.qml b/resources/qml/RoomDirectory.qml index 4db24f01..cc36f008 100644 --- a/resources/qml/RoomDirectory.qml +++ b/resources/qml/RoomDirectory.qml @@ -132,9 +132,8 @@ ApplicationWindow { Button { id: joinRoomButton - visible: publicRooms.canJoinRoom(model.roomid) + visible: model.canJoin anchors.centerIn: parent - width: Math.ceil(0.1 * roomDirectoryWindow.width) text: "Join" onClicked: publicRooms.joinRoom(model.index) } diff --git a/src/RoomDirectoryModel.cpp b/src/RoomDirectoryModel.cpp index 61c3eb72..14e0fe84 100644 --- a/src/RoomDirectoryModel.cpp +++ b/src/RoomDirectoryModel.cpp @@ -8,10 +8,23 @@ #include -RoomDirectoryModel::RoomDirectoryModel(QObject *parent, const std::string &s) +RoomDirectoryModel::RoomDirectoryModel(QObject *parent, const std::string &server) : QAbstractListModel(parent) - , server_(s) + , server_(server) { + connect(ChatPage::instance(), &ChatPage::newRoom, this, [this](const QString &roomid) { + auto roomid_ = roomid.toStdString(); + + int i = 0; + for (const auto &room : publicRoomsData_) { + if (room.room_id == roomid_) { + emit dataChanged(index(i), index(i), {Roles::CanJoin}); + break; + } + i++; + } + }); + connect(this, &RoomDirectoryModel::fetchedRoomsBatch, this, @@ -29,6 +42,7 @@ RoomDirectoryModel::roleNames() const {Roles::Topic, "topic"}, {Roles::MemberCount, "numMembers"}, {Roles::Previewable, "canPreview"}, + {Roles::CanJoin, "canJoin"}, }; } @@ -67,10 +81,9 @@ RoomDirectoryModel::setSearchTerm(const QString &f) } bool -RoomDirectoryModel::canJoinRoom(const QByteArray &room) +RoomDirectoryModel::canJoinRoom(const QString &room) const { - const QString room_id(room); - return !room_id.isEmpty() && !cache::getRoomInfo({room_id.toStdString()}).count(room_id); + return !room.isEmpty() && cache::getRoomInfo({room.toStdString()}).empty(); } std::vector @@ -116,6 +129,8 @@ RoomDirectoryModel::data(const QModelIndex &index, int role) const return QVariant::fromValue(room_chunk.num_joined_members); case Roles::Previewable: return QVariant::fromValue(room_chunk.world_readable); + case Roles::CanJoin: + return canJoinRoom(QString::fromStdString(room_chunk.room_id)); } } return {}; diff --git a/src/RoomDirectoryModel.h b/src/RoomDirectoryModel.h index 791384fa..0bec3943 100644 --- a/src/RoomDirectoryModel.h +++ b/src/RoomDirectoryModel.h @@ -32,7 +32,7 @@ class RoomDirectoryModel : public QAbstractListModel reachedEndOfPaginationChanged) public: - explicit RoomDirectoryModel(QObject *parent = nullptr, const std::string &s = ""); + explicit RoomDirectoryModel(QObject *parent = nullptr, const std::string &server = ""); enum Roles { @@ -41,7 +41,8 @@ public: AvatarUrl, Topic, MemberCount, - Previewable + Previewable, + CanJoin, }; QHash roleNames() const override; @@ -61,7 +62,6 @@ public: void fetchMore(const QModelIndex &) override; - Q_INVOKABLE bool canJoinRoom(const QByteArray &room); Q_INVOKABLE void joinRoom(const int &index = -1); signals: @@ -80,6 +80,8 @@ private slots: const std::string &next_batch); private: + bool canJoinRoom(const QString &room) const; + static constexpr size_t limit_ = 50; std::string server_; diff --git a/src/timeline/RoomlistModel.cpp b/src/timeline/RoomlistModel.cpp index f4c927ac..942a4b05 100644 --- a/src/timeline/RoomlistModel.cpp +++ b/src/timeline/RoomlistModel.cpp @@ -379,6 +379,8 @@ RoomlistModel::addRoom(const QString &room_id, bool suppressInsertNotification) if (!suppressInsertNotification && ((!wasInvite && !wasPreview) || !previewedRooms.empty())) endInsertRows(); + + emit ChatPage::instance()->newRoom(room_id); } } From 995b62122ad5a239c776adb52d9235ec3c04f83b Mon Sep 17 00:00:00 2001 From: Nicolas Werner Date: Tue, 17 Aug 2021 14:22:37 +0200 Subject: [PATCH 024/232] Fi lineendings --- src/RoomDirectoryModel.cpp | 418 ++++++++++++++++++------------------- src/RoomDirectoryModel.h | 196 ++++++++--------- 2 files changed, 307 insertions(+), 307 deletions(-) diff --git a/src/RoomDirectoryModel.cpp b/src/RoomDirectoryModel.cpp index 14e0fe84..de5d430a 100644 --- a/src/RoomDirectoryModel.cpp +++ b/src/RoomDirectoryModel.cpp @@ -1,209 +1,209 @@ -// SPDX-FileCopyrightText: 2021 Nheko Contributors -// -// SPDX-License-Identifier: GPL-3.0-or-later - -#include "RoomDirectoryModel.h" -#include "Cache.h" -#include "ChatPage.h" - -#include - -RoomDirectoryModel::RoomDirectoryModel(QObject *parent, const std::string &server) - : QAbstractListModel(parent) - , server_(server) -{ - connect(ChatPage::instance(), &ChatPage::newRoom, this, [this](const QString &roomid) { - auto roomid_ = roomid.toStdString(); - - int i = 0; - for (const auto &room : publicRoomsData_) { - if (room.room_id == roomid_) { - emit dataChanged(index(i), index(i), {Roles::CanJoin}); - break; - } - i++; - } - }); - - connect(this, - &RoomDirectoryModel::fetchedRoomsBatch, - this, - &RoomDirectoryModel::displayRooms, - Qt::QueuedConnection); -} - -QHash -RoomDirectoryModel::roleNames() const -{ - return { - {Roles::Name, "name"}, - {Roles::Id, "roomid"}, - {Roles::AvatarUrl, "avatarUrl"}, - {Roles::Topic, "topic"}, - {Roles::MemberCount, "numMembers"}, - {Roles::Previewable, "canPreview"}, - {Roles::CanJoin, "canJoin"}, - }; -} - -void -RoomDirectoryModel::resetDisplayedData() -{ - beginResetModel(); - - prevBatch_ = ""; - nextBatch_ = ""; - canFetchMore_ = true; - - publicRoomsData_.clear(); - - endResetModel(); -} - -void -RoomDirectoryModel::setMatrixServer(const QString &s) -{ - server_ = s.toStdString(); - - nhlog::ui()->debug("Received matrix server: {}", server_); - - resetDisplayedData(); -} - -void -RoomDirectoryModel::setSearchTerm(const QString &f) -{ - userSearchString_ = f.toStdString(); - - nhlog::ui()->debug("Received user query: {}", userSearchString_); - - resetDisplayedData(); -} - -bool -RoomDirectoryModel::canJoinRoom(const QString &room) const -{ - return !room.isEmpty() && cache::getRoomInfo({room.toStdString()}).empty(); -} - -std::vector -RoomDirectoryModel::getViasForRoom(const std::vector &aliases) -{ - std::vector vias; - - vias.reserve(aliases.size()); - - std::transform(aliases.begin(), - aliases.end(), - std::back_inserter(vias), - [](const auto &alias) { return alias.substr(alias.find(":") + 1); }); - - return vias; -} - -void -RoomDirectoryModel::joinRoom(const int &index) -{ - if (index >= 0 && static_cast(index) < publicRoomsData_.size()) { - const auto &chunk = publicRoomsData_[index]; - nhlog::ui()->debug("'Joining room {}", chunk.room_id); - ChatPage::instance()->joinRoomVia(chunk.room_id, getViasForRoom(chunk.aliases)); - } -} - -QVariant -RoomDirectoryModel::data(const QModelIndex &index, int role) const -{ - if (hasIndex(index.row(), index.column(), index.parent())) { - const auto &room_chunk = publicRoomsData_[index.row()]; - switch (role) { - case Roles::Name: - return QString::fromStdString(room_chunk.name); - case Roles::Id: - return QString::fromStdString(room_chunk.room_id); - case Roles::AvatarUrl: - return QString::fromStdString(room_chunk.avatar_url); - case Roles::Topic: - return QString::fromStdString(room_chunk.topic); - case Roles::MemberCount: - return QVariant::fromValue(room_chunk.num_joined_members); - case Roles::Previewable: - return QVariant::fromValue(room_chunk.world_readable); - case Roles::CanJoin: - return canJoinRoom(QString::fromStdString(room_chunk.room_id)); - } - } - return {}; -} - -void -RoomDirectoryModel::fetchMore(const QModelIndex &) -{ - if (!canFetchMore_) - return; - - nhlog::net()->debug("Fetching more rooms from mtxclient..."); - - mtx::requests::PublicRooms req; - req.limit = limit_; - req.since = prevBatch_; - req.filter.generic_search_term = userSearchString_; - // req.third_party_instance_id = third_party_instance_id; - auto requested_server = server_; - - reachedEndOfPagination_ = false; - emit reachedEndOfPaginationChanged(); - - loadingMoreRooms_ = true; - emit loadingMoreRoomsChanged(); - - http::client()->post_public_rooms( - req, - [requested_server, this, req](const mtx::responses::PublicRooms &res, - mtx::http::RequestErr err) { - loadingMoreRooms_ = false; - emit loadingMoreRoomsChanged(); - - if (err) { - nhlog::net()->error( - "Failed to retrieve rooms from mtxclient - {} - {} - {}", - mtx::errors::to_string(err->matrix_error.errcode), - err->matrix_error.error, - err->parse_error); - } else if (req.filter.generic_search_term == this->userSearchString_ && - req.since == this->prevBatch_ && requested_server == this->server_) { - nhlog::net()->debug("signalling chunk to GUI thread"); - emit fetchedRoomsBatch(res.chunk, res.next_batch); - } - }, - requested_server); -} - -void -RoomDirectoryModel::displayRooms(std::vector fetched_rooms, - const std::string &next_batch) -{ - nhlog::net()->debug("Prev batch: {} | Next batch: {}", prevBatch_, next_batch); - - if (fetched_rooms.empty()) { - nhlog::net()->error("mtxclient helper thread yielded empty chunk!"); - return; - } - - beginInsertRows(QModelIndex(), - static_cast(publicRoomsData_.size()), - static_cast(publicRoomsData_.size() + fetched_rooms.size()) - 1); - this->publicRoomsData_.insert( - this->publicRoomsData_.end(), fetched_rooms.begin(), fetched_rooms.end()); - endInsertRows(); - - if (next_batch.empty()) { - canFetchMore_ = false; - reachedEndOfPagination_ = true; - emit reachedEndOfPaginationChanged(); - } - - prevBatch_ = next_batch; - - nhlog::ui()->debug("Finished loading rooms"); -} +// SPDX-FileCopyrightText: 2021 Nheko Contributors +// +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "RoomDirectoryModel.h" +#include "Cache.h" +#include "ChatPage.h" + +#include + +RoomDirectoryModel::RoomDirectoryModel(QObject *parent, const std::string &server) + : QAbstractListModel(parent) + , server_(server) +{ + connect(ChatPage::instance(), &ChatPage::newRoom, this, [this](const QString &roomid) { + auto roomid_ = roomid.toStdString(); + + int i = 0; + for (const auto &room : publicRoomsData_) { + if (room.room_id == roomid_) { + emit dataChanged(index(i), index(i), {Roles::CanJoin}); + break; + } + i++; + } + }); + + connect(this, + &RoomDirectoryModel::fetchedRoomsBatch, + this, + &RoomDirectoryModel::displayRooms, + Qt::QueuedConnection); +} + +QHash +RoomDirectoryModel::roleNames() const +{ + return { + {Roles::Name, "name"}, + {Roles::Id, "roomid"}, + {Roles::AvatarUrl, "avatarUrl"}, + {Roles::Topic, "topic"}, + {Roles::MemberCount, "numMembers"}, + {Roles::Previewable, "canPreview"}, + {Roles::CanJoin, "canJoin"}, + }; +} + +void +RoomDirectoryModel::resetDisplayedData() +{ + beginResetModel(); + + prevBatch_ = ""; + nextBatch_ = ""; + canFetchMore_ = true; + + publicRoomsData_.clear(); + + endResetModel(); +} + +void +RoomDirectoryModel::setMatrixServer(const QString &s) +{ + server_ = s.toStdString(); + + nhlog::ui()->debug("Received matrix server: {}", server_); + + resetDisplayedData(); +} + +void +RoomDirectoryModel::setSearchTerm(const QString &f) +{ + userSearchString_ = f.toStdString(); + + nhlog::ui()->debug("Received user query: {}", userSearchString_); + + resetDisplayedData(); +} + +bool +RoomDirectoryModel::canJoinRoom(const QString &room) const +{ + return !room.isEmpty() && cache::getRoomInfo({room.toStdString()}).empty(); +} + +std::vector +RoomDirectoryModel::getViasForRoom(const std::vector &aliases) +{ + std::vector vias; + + vias.reserve(aliases.size()); + + std::transform(aliases.begin(), + aliases.end(), + std::back_inserter(vias), + [](const auto &alias) { return alias.substr(alias.find(":") + 1); }); + + return vias; +} + +void +RoomDirectoryModel::joinRoom(const int &index) +{ + if (index >= 0 && static_cast(index) < publicRoomsData_.size()) { + const auto &chunk = publicRoomsData_[index]; + nhlog::ui()->debug("'Joining room {}", chunk.room_id); + ChatPage::instance()->joinRoomVia(chunk.room_id, getViasForRoom(chunk.aliases)); + } +} + +QVariant +RoomDirectoryModel::data(const QModelIndex &index, int role) const +{ + if (hasIndex(index.row(), index.column(), index.parent())) { + const auto &room_chunk = publicRoomsData_[index.row()]; + switch (role) { + case Roles::Name: + return QString::fromStdString(room_chunk.name); + case Roles::Id: + return QString::fromStdString(room_chunk.room_id); + case Roles::AvatarUrl: + return QString::fromStdString(room_chunk.avatar_url); + case Roles::Topic: + return QString::fromStdString(room_chunk.topic); + case Roles::MemberCount: + return QVariant::fromValue(room_chunk.num_joined_members); + case Roles::Previewable: + return QVariant::fromValue(room_chunk.world_readable); + case Roles::CanJoin: + return canJoinRoom(QString::fromStdString(room_chunk.room_id)); + } + } + return {}; +} + +void +RoomDirectoryModel::fetchMore(const QModelIndex &) +{ + if (!canFetchMore_) + return; + + nhlog::net()->debug("Fetching more rooms from mtxclient..."); + + mtx::requests::PublicRooms req; + req.limit = limit_; + req.since = prevBatch_; + req.filter.generic_search_term = userSearchString_; + // req.third_party_instance_id = third_party_instance_id; + auto requested_server = server_; + + reachedEndOfPagination_ = false; + emit reachedEndOfPaginationChanged(); + + loadingMoreRooms_ = true; + emit loadingMoreRoomsChanged(); + + http::client()->post_public_rooms( + req, + [requested_server, this, req](const mtx::responses::PublicRooms &res, + mtx::http::RequestErr err) { + loadingMoreRooms_ = false; + emit loadingMoreRoomsChanged(); + + if (err) { + nhlog::net()->error( + "Failed to retrieve rooms from mtxclient - {} - {} - {}", + mtx::errors::to_string(err->matrix_error.errcode), + err->matrix_error.error, + err->parse_error); + } else if (req.filter.generic_search_term == this->userSearchString_ && + req.since == this->prevBatch_ && requested_server == this->server_) { + nhlog::net()->debug("signalling chunk to GUI thread"); + emit fetchedRoomsBatch(res.chunk, res.next_batch); + } + }, + requested_server); +} + +void +RoomDirectoryModel::displayRooms(std::vector fetched_rooms, + const std::string &next_batch) +{ + nhlog::net()->debug("Prev batch: {} | Next batch: {}", prevBatch_, next_batch); + + if (fetched_rooms.empty()) { + nhlog::net()->error("mtxclient helper thread yielded empty chunk!"); + return; + } + + beginInsertRows(QModelIndex(), + static_cast(publicRoomsData_.size()), + static_cast(publicRoomsData_.size() + fetched_rooms.size()) - 1); + this->publicRoomsData_.insert( + this->publicRoomsData_.end(), fetched_rooms.begin(), fetched_rooms.end()); + endInsertRows(); + + if (next_batch.empty()) { + canFetchMore_ = false; + reachedEndOfPagination_ = true; + emit reachedEndOfPaginationChanged(); + } + + prevBatch_ = next_batch; + + nhlog::ui()->debug("Finished loading rooms"); +} diff --git a/src/RoomDirectoryModel.h b/src/RoomDirectoryModel.h index 0bec3943..80c04612 100644 --- a/src/RoomDirectoryModel.h +++ b/src/RoomDirectoryModel.h @@ -1,98 +1,98 @@ -// SPDX-FileCopyrightText: 2021 Nheko Contributors -// -// SPDX-License-Identifier: GPL-3.0-or-later - -#pragma once - -#include -#include -#include -#include -#include - -#include "MatrixClient.h" -#include -#include - -#include "Logging.h" - -namespace mtx::http { -using RequestErr = const std::optional &; -} -namespace mtx::responses { -struct PublicRooms; -} - -class RoomDirectoryModel : public QAbstractListModel -{ - Q_OBJECT - - Q_PROPERTY(bool loadingMoreRooms READ loadingMoreRooms NOTIFY loadingMoreRoomsChanged) - Q_PROPERTY(bool reachedEndOfPagination READ reachedEndOfPagination NOTIFY - reachedEndOfPaginationChanged) - -public: - explicit RoomDirectoryModel(QObject *parent = nullptr, const std::string &server = ""); - - enum Roles - { - Name = Qt::UserRole, - Id, - AvatarUrl, - Topic, - MemberCount, - Previewable, - CanJoin, - }; - QHash roleNames() const override; - - QVariant data(const QModelIndex &index, int role) const override; - - inline int rowCount(const QModelIndex &parent = QModelIndex()) const override - { - (void)parent; - return static_cast(publicRoomsData_.size()); - } - - bool canFetchMore(const QModelIndex &) const override { return canFetchMore_; } - - bool loadingMoreRooms() const { return loadingMoreRooms_; } - - bool reachedEndOfPagination() const { return reachedEndOfPagination_; } - - void fetchMore(const QModelIndex &) override; - - Q_INVOKABLE void joinRoom(const int &index = -1); - -signals: - void fetchedRoomsBatch(std::vector rooms, - const std::string &next_batch); - void loadingMoreRoomsChanged(); - void reachedEndOfPaginationChanged(); - -public slots: - void setMatrixServer(const QString &s = ""); - void setSearchTerm(const QString &f); - -private slots: - - void displayRooms(std::vector rooms, - const std::string &next_batch); - -private: - bool canJoinRoom(const QString &room) const; - - static constexpr size_t limit_ = 50; - - std::string server_; - std::string userSearchString_; - std::string prevBatch_; - std::string nextBatch_; - bool canFetchMore_{true}; - bool loadingMoreRooms_{false}; - bool reachedEndOfPagination_{false}; - std::vector publicRoomsData_; - - std::vector getViasForRoom(const std::vector &room); - void resetDisplayedData(); -}; +// SPDX-FileCopyrightText: 2021 Nheko Contributors +// +// SPDX-License-Identifier: GPL-3.0-or-later + +#pragma once + +#include +#include +#include +#include +#include + +#include "MatrixClient.h" +#include +#include + +#include "Logging.h" + +namespace mtx::http { +using RequestErr = const std::optional &; +} +namespace mtx::responses { +struct PublicRooms; +} + +class RoomDirectoryModel : public QAbstractListModel +{ + Q_OBJECT + + Q_PROPERTY(bool loadingMoreRooms READ loadingMoreRooms NOTIFY loadingMoreRoomsChanged) + Q_PROPERTY(bool reachedEndOfPagination READ reachedEndOfPagination NOTIFY + reachedEndOfPaginationChanged) + +public: + explicit RoomDirectoryModel(QObject *parent = nullptr, const std::string &server = ""); + + enum Roles + { + Name = Qt::UserRole, + Id, + AvatarUrl, + Topic, + MemberCount, + Previewable, + CanJoin, + }; + QHash roleNames() const override; + + QVariant data(const QModelIndex &index, int role) const override; + + inline int rowCount(const QModelIndex &parent = QModelIndex()) const override + { + (void)parent; + return static_cast(publicRoomsData_.size()); + } + + bool canFetchMore(const QModelIndex &) const override { return canFetchMore_; } + + bool loadingMoreRooms() const { return loadingMoreRooms_; } + + bool reachedEndOfPagination() const { return reachedEndOfPagination_; } + + void fetchMore(const QModelIndex &) override; + + Q_INVOKABLE void joinRoom(const int &index = -1); + +signals: + void fetchedRoomsBatch(std::vector rooms, + const std::string &next_batch); + void loadingMoreRoomsChanged(); + void reachedEndOfPaginationChanged(); + +public slots: + void setMatrixServer(const QString &s = ""); + void setSearchTerm(const QString &f); + +private slots: + + void displayRooms(std::vector rooms, + const std::string &next_batch); + +private: + bool canJoinRoom(const QString &room) const; + + static constexpr size_t limit_ = 50; + + std::string server_; + std::string userSearchString_; + std::string prevBatch_; + std::string nextBatch_; + bool canFetchMore_{true}; + bool loadingMoreRooms_{false}; + bool reachedEndOfPagination_{false}; + std::vector publicRoomsData_; + + std::vector getViasForRoom(const std::vector &room); + void resetDisplayedData(); +}; From 5287ba38f9c05c39a49b66d3738a63f2fcfb2fa8 Mon Sep 17 00:00:00 2001 From: Nicolas Werner Date: Tue, 17 Aug 2021 19:00:00 +0200 Subject: [PATCH 025/232] Fix all rooms being opened on startup --- src/ChatPage.cpp | 3 ++- src/ChatPage.h | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/ChatPage.cpp b/src/ChatPage.cpp index 88d393ce..615e96fe 100644 --- a/src/ChatPage.cpp +++ b/src/ChatPage.cpp @@ -140,7 +140,7 @@ ChatPage::ChatPage(QSharedPointer userSettings, QWidget *parent) }); connect(this, &ChatPage::leftRoom, this, &ChatPage::removeRoom); - connect(this, &ChatPage::newRoom, this, &ChatPage::changeRoom, Qt::QueuedConnection); + connect(this, &ChatPage::changeToRoom, this, &ChatPage::changeRoom, Qt::QueuedConnection); connect(this, &ChatPage::notificationsRetrieved, this, &ChatPage::sendNotifications); connect(this, &ChatPage::highlightedNotifsRetrieved, @@ -751,6 +751,7 @@ ChatPage::createRoom(const mtx::requests::CreateRoom &req) QString newRoomId = QString::fromStdString(res.room_id.to_string()); emit showNotification(tr("Room %1 created.").arg(newRoomId)); emit newRoom(newRoomId); + emit changeToRoom(newRoomId); }); } diff --git a/src/ChatPage.h b/src/ChatPage.h index dfe94c37..d79bee46 100644 --- a/src/ChatPage.h +++ b/src/ChatPage.h @@ -125,6 +125,7 @@ signals: void newSyncResponse(const mtx::responses::Sync &res, const std::string &prev_batch_token); void leftRoom(const QString &room_id); void newRoom(const QString &room_id); + void changeToRoom(const QString &room_id); void initializeViews(const mtx::responses::Rooms &rooms); void initializeEmptyViews(); From 56b24f8d9329a43d684b72ca3da17356abb68106 Mon Sep 17 00:00:00 2001 From: Nicolas Werner Date: Tue, 17 Aug 2021 19:11:09 +0200 Subject: [PATCH 026/232] Load message list async --- resources/qml/TimelineView.qml | 1 + 1 file changed, 1 insertion(+) diff --git a/resources/qml/TimelineView.qml b/resources/qml/TimelineView.qml index c8ac6bc7..0da7d6c2 100644 --- a/resources/qml/TimelineView.qml +++ b/resources/qml/TimelineView.qml @@ -86,6 +86,7 @@ Item { Loader { active: room || roomPreview + asynchronous: true Layout.fillWidth: true sourceComponent: MessageView { From 5b460861b126a49f1e186c8b59ffb0faf0109aab Mon Sep 17 00:00:00 2001 From: Nicolas Werner Date: Tue, 17 Aug 2021 23:31:25 +0200 Subject: [PATCH 027/232] Allow accepting knocks in the timeline As well as selecting more join rules. --- resources/qml/Avatar.qml | 2 +- resources/qml/RoomList.qml | 9 +-- resources/qml/RoomSettings.qml | 11 +++- resources/qml/delegates/MessageDelegate.qml | 24 ++++++-- src/timeline/TimelineModel.cpp | 66 ++++++++++++++++++++- src/timeline/TimelineModel.h | 2 + src/ui/RoomSettings.cpp | 30 +++++++++- src/ui/RoomSettings.h | 4 ++ 8 files changed, 135 insertions(+), 13 deletions(-) diff --git a/resources/qml/Avatar.qml b/resources/qml/Avatar.qml index 4a9a565c..ab067eee 100644 --- a/resources/qml/Avatar.qml +++ b/resources/qml/Avatar.qml @@ -49,7 +49,7 @@ Rectangle { smooth: true sourceSize.width: avatar.width sourceSize.height: avatar.height - source: avatar.url ? (avatar.url + "?radius=" + (Settings.avatarCircles ? 100.0 : 25.0) + ((avatar.crop) ? "" : "&scale")) : "" + source: avatar.url ? (avatar.url + "?radius=" + (Settings.avatarCircles ? 100 : 25) + ((avatar.crop) ? "" : "&scale")) : "" MouseArea { id: mouseArea diff --git a/resources/qml/RoomList.qml b/resources/qml/RoomList.qml index b84b4c36..a0009174 100644 --- a/resources/qml/RoomList.qml +++ b/resources/qml/RoomList.qml @@ -16,12 +16,13 @@ Page { property int avatarSize: Math.ceil(fontMetrics.lineSpacing * 2.3) property bool collapsed: false -Component { + Component { id: roomDirectoryComponent RoomDirectory { } - } + + } ListView { id: roomlist @@ -570,10 +571,10 @@ Component { ToolTip.visible: hovered ToolTip.text: qsTr("Room directory") Layout.margins: Nheko.paddingMedium - onClicked: { + onClicked: { var win = roomDirectoryComponent.createObject(timelineRoot); win.show(); - } + } } ImageButton { diff --git a/resources/qml/RoomSettings.qml b/resources/qml/RoomSettings.qml index 491a336f..92cd431a 100644 --- a/resources/qml/RoomSettings.qml +++ b/resources/qml/RoomSettings.qml @@ -186,7 +186,16 @@ ApplicationWindow { ComboBox { enabled: roomSettings.canChangeJoinRules - model: [qsTr("Anyone and guests"), qsTr("Anyone"), qsTr("Invited users")] + model: { + let opts = [qsTr("Anyone and guests"), qsTr("Anyone"), qsTr("Invited users")]; + if (roomSettings.supportsKnocking) + opts.push(qsTr("By knocking")); + + if (roomSettings.supportsRestricted) + opts.push(qsTr("Restricted by membership in other rooms")); + + return opts; + } currentIndex: roomSettings.accessJoinRules onActivated: { roomSettings.changeAccessRules(index); diff --git a/resources/qml/delegates/MessageDelegate.qml b/resources/qml/delegates/MessageDelegate.qml index a8bdf183..893edc77 100644 --- a/resources/qml/delegates/MessageDelegate.qml +++ b/resources/qml/delegates/MessageDelegate.qml @@ -3,6 +3,8 @@ // SPDX-License-Identifier: GPL-3.0-or-later import QtQuick 2.6 +import QtQuick.Controls 2.1 +import QtQuick.Layouts 1.2 import im.nheko 1.0 Item { @@ -357,11 +359,23 @@ Item { DelegateChoice { roleValue: MtxEvent.Member - NoticeMessage { - body: formatted - isOnlyEmoji: false - isReply: d.isReply - formatted: d.relatedEventCacheBuster, room.formatMemberEvent(d.eventId) + ColumnLayout { + width: parent ? parent.width : undefined + + NoticeMessage { + body: formatted + isOnlyEmoji: false + isReply: d.isReply + formatted: d.relatedEventCacheBuster, room.formatMemberEvent(d.eventId) + } + + Button { + visible: d.relatedEventCacheBuster, room.showAcceptKnockButton(d.eventId) + palette: Nheko.colors + text: qsTr("Allow them in") + onClicked: room.acceptKnock(eventId) + } + } } diff --git a/src/timeline/TimelineModel.cpp b/src/timeline/TimelineModel.cpp index 88d575fa..1e369b46 100644 --- a/src/timeline/TimelineModel.cpp +++ b/src/timeline/TimelineModel.cpp @@ -1689,6 +1689,19 @@ TimelineModel::formatJoinRuleEvent(QString id) return tr("%1 opened the room to the public.").arg(name); case mtx::events::state::JoinRule::Invite: return tr("%1 made this room require and invitation to join.").arg(name); + case mtx::events::state::JoinRule::Knock: + return tr("%1 allowed to join this room by knocking.").arg(name); + case mtx::events::state::JoinRule::Restricted: { + QStringList rooms; + for (const auto &r : event->content.allow) { + if (r.type == mtx::events::state::JoinAllowanceType::RoomMembership) + rooms.push_back(QString::fromStdString(r.room_id)); + } + return tr("%1 allowed members of the following rooms to automatically join this " + "room: %2") + .arg(name) + .arg(rooms.join(", ")); + } default: // Currently, knock and private are reserved keywords and not implemented in Matrix. return ""; @@ -1771,6 +1784,51 @@ TimelineModel::formatPowerLevelEvent(QString id) return tr("%1 has changed the room's permissions.").arg(name); } +void +TimelineModel::acceptKnock(QString id) +{ + mtx::events::collections::TimelineEvents *e = events.get(id.toStdString(), ""); + if (!e) + return; + + auto event = std::get_if>(e); + if (!event) + return; + + if (!permissions_.canInvite()) + return; + + if (cache::isRoomMember(event->state_key, room_id_.toStdString())) + return; + + using namespace mtx::events::state; + if (event->content.membership != Membership::Knock) + return; + + ChatPage::instance()->inviteUser(QString::fromStdString(event->state_key), ""); +} + +bool +TimelineModel::showAcceptKnockButton(QString id) +{ + mtx::events::collections::TimelineEvents *e = events.get(id.toStdString(), ""); + if (!e) + return false; + + auto event = std::get_if>(e); + if (!event) + return false; + + if (!permissions_.canInvite()) + return false; + + if (cache::isRoomMember(event->state_key, room_id_.toStdString())) + return false; + + using namespace mtx::events::state; + return event->content.membership == Membership::Knock; +} + QString TimelineModel::formatMemberEvent(QString id) { @@ -1826,7 +1884,13 @@ TimelineModel::formatMemberEvent(QString id) // the case of nothing changed but join follows join shouldn't happen, so // just show it as join } else { - rendered = tr("%1 joined.").arg(name); + if (event->content.join_authorised_via_users_server.empty()) + rendered = tr("%1 joined.").arg(name); + else + rendered = tr("%1 joined via authorisation from %2's server.") + .arg(name) + .arg(QString::fromStdString( + event->content.join_authorised_via_users_server)); } break; case Membership::Leave: diff --git a/src/timeline/TimelineModel.h b/src/timeline/TimelineModel.h index aa07fe01..e3ca8811 100644 --- a/src/timeline/TimelineModel.h +++ b/src/timeline/TimelineModel.h @@ -238,6 +238,8 @@ public: Q_INVOKABLE QString avatarUrl(QString id) const; Q_INVOKABLE QString formatDateSeparator(QDate date) const; Q_INVOKABLE QString formatTypingUsers(const std::vector &users, QColor bg); + Q_INVOKABLE bool showAcceptKnockButton(QString id); + Q_INVOKABLE void acceptKnock(QString id); Q_INVOKABLE QString formatMemberEvent(QString id); Q_INVOKABLE QString formatJoinRuleEvent(QString id); Q_INVOKABLE QString formatHistoryVisibilityEvent(QString id); diff --git a/src/ui/RoomSettings.cpp b/src/ui/RoomSettings.cpp index fcba8205..2fb93325 100644 --- a/src/ui/RoomSettings.cpp +++ b/src/ui/RoomSettings.cpp @@ -218,8 +218,12 @@ RoomSettings::RoomSettings(QString roomid, QObject *parent) } else { accessRules_ = 1; } - } else { + } else if (info_.join_rule == state::JoinRule::Invite) { accessRules_ = 2; + } else if (info_.join_rule == state::JoinRule::Knock) { + accessRules_ = 3; + } else if (info_.join_rule == state::JoinRule::Restricted) { + accessRules_ = 4; } emit accessJoinRulesChanged(); } @@ -368,6 +372,21 @@ RoomSettings::isEncryptionEnabled() const return usesEncryption_; } +bool +RoomSettings::supportsKnocking() const +{ + return info_.version != "" && info_.version != "1" && info_.version != "2" && + info_.version != "3" && info_.version != "4" && info_.version != "5" && + info_.version != "6"; +} +bool +RoomSettings::supportsRestricted() const +{ + return info_.version != "" && info_.version != "1" && info_.version != "2" && + info_.version != "3" && info_.version != "4" && info_.version != "5" && + info_.version != "6" && info_.version != "7"; +} + void RoomSettings::openEditModal() { @@ -464,6 +483,15 @@ RoomSettings::changeAccessRules(int index) case 1: event.join_rule = state::JoinRule::Public; break; + case 2: + event.join_rule = state::JoinRule::Invite; + break; + case 3: + event.join_rule = state::JoinRule::Knock; + break; + case 4: + event.join_rule = state::JoinRule::Restricted; + break; default: event.join_rule = state::JoinRule::Invite; } diff --git a/src/ui/RoomSettings.h b/src/ui/RoomSettings.h index 1c8b47d6..ab768ffe 100644 --- a/src/ui/RoomSettings.h +++ b/src/ui/RoomSettings.h @@ -78,6 +78,8 @@ class RoomSettings : public QObject Q_PROPERTY(bool canChangeJoinRules READ canChangeJoinRules CONSTANT) Q_PROPERTY(bool canChangeNameAndTopic READ canChangeNameAndTopic CONSTANT) Q_PROPERTY(bool isEncryptionEnabled READ isEncryptionEnabled NOTIFY encryptionChanged) + Q_PROPERTY(bool supportsKnocking READ supportsKnocking CONSTANT) + Q_PROPERTY(bool supportsRestricted READ supportsRestricted CONSTANT) public: RoomSettings(QString roomid, QObject *parent = nullptr); @@ -98,6 +100,8 @@ public: //! Whether the user has enough power level to send m.room.avatar event. bool canChangeAvatar() const; bool isEncryptionEnabled() const; + bool supportsKnocking() const; + bool supportsRestricted() const; Q_INVOKABLE void enableEncryption(); Q_INVOKABLE void updateAvatar(); From 9b99eaae77074701ea5d9e934a8c259a4af38eb0 Mon Sep 17 00:00:00 2001 From: Weblate Date: Tue, 17 Aug 2021 17:42:19 -0400 Subject: [PATCH 028/232] Translated using Weblate (Estonian) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Currently translated at 100.0% (542 of 542 strings) Co-authored-by: Priit Jõerüüt Translate-URL: https://weblate.nheko.im/projects/nheko/nheko-master/et/ Translation: Nheko/nheko --- resources/langs/nheko_et.ts | 38 ++++++++++++++++++------------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/resources/langs/nheko_et.ts b/resources/langs/nheko_et.ts index 13ab1f1b..60a3d7cb 100644 --- a/resources/langs/nheko_et.ts +++ b/resources/langs/nheko_et.ts @@ -743,33 +743,33 @@ Invite users to %1 - + Kutsu kasutajaid %1 jututuppa User ID to invite - Kasutajatunnus, kellele soovid kutset saata + Kasutajatunnus, kellele soovid kutset saata @joe:matrix.org Example user id. The name 'joe' can be localized however you want. - + @kadri:matrix.org Add - + Lisa Invite - + Saada kutse Cancel - + Loobu @@ -985,7 +985,7 @@ Näiteks: https://server.minu:8787 Stickers - + Kleepsud @@ -1501,21 +1501,21 @@ Näiteks: https://server.minu:8787 Members of %1 - + %1 jututoa liikmed %n people in %1 Summary above list of members - - - + + %n osaline %1 jututoas + %n osalist %1 jututoas Invite more people - + Kutsu veel liikmeid @@ -1678,17 +1678,17 @@ Näiteks: https://server.minu:8787 Pending invite. - + Ootel kutse. Previewing this room - + Jututoa eelvaade No preview available - + Eelvaade pole saadaval @@ -1797,7 +1797,7 @@ Näiteks: https://server.minu:8787 Search - Otsi + Otsi @@ -2020,17 +2020,17 @@ Näiteks: https://server.minu:8787 join the conversation - + liitu vestlusega accept invite - + võta kutse vastu decline invite - + lükka kutse tagasi From ee58ba94017fb6bad1a036311cdd9bef4b60cffb Mon Sep 17 00:00:00 2001 From: Nicolas Werner Date: Tue, 17 Aug 2021 23:40:28 +0200 Subject: [PATCH 029/232] bump mtxclient --- CMakeLists.txt | 2 +- io.github.NhekoReborn.Nheko.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index aff561c9..c70a6f5e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -382,7 +382,7 @@ if(USE_BUNDLED_MTXCLIENT) FetchContent_Declare( MatrixClient GIT_REPOSITORY https://github.com/Nheko-Reborn/mtxclient.git - GIT_TAG 513196f520733e2f70576168aff7aaf16e0df180 + GIT_TAG a368db306d0148d1c8c17a532ebe4ff05a2a08c8 ) set(BUILD_LIB_EXAMPLES OFF CACHE INTERNAL "") set(BUILD_LIB_TESTS OFF CACHE INTERNAL "") diff --git a/io.github.NhekoReborn.Nheko.yaml b/io.github.NhekoReborn.Nheko.yaml index 9d4b3b2e..b71e1879 100644 --- a/io.github.NhekoReborn.Nheko.yaml +++ b/io.github.NhekoReborn.Nheko.yaml @@ -163,7 +163,7 @@ modules: buildsystem: cmake-ninja name: mtxclient sources: - - commit: 513196f520733e2f70576168aff7aaf16e0df180 + - commit: a368db306d0148d1c8c17a532ebe4ff05a2a08c8 type: git url: https://github.com/Nheko-Reborn/mtxclient.git - config-opts: From c2e777f3e41cbd7e3e73502127e80dcceeeb020e Mon Sep 17 00:00:00 2001 From: Nicolas Werner Date: Tue, 17 Aug 2021 23:51:15 +0200 Subject: [PATCH 030/232] Update translations --- resources/langs/nheko_cs.ts | 147 +++++++++++++------ resources/langs/nheko_de.ts | 147 +++++++++++++------ resources/langs/nheko_el.ts | 145 ++++++++++++------ resources/langs/nheko_en.ts | 259 ++++++++++++++++++++------------- resources/langs/nheko_eo.ts | 147 +++++++++++++------ resources/langs/nheko_es.ts | 145 ++++++++++++------ resources/langs/nheko_et.ts | 149 +++++++++++++------ resources/langs/nheko_fi.ts | 147 +++++++++++++------ resources/langs/nheko_fr.ts | 145 ++++++++++++------ resources/langs/nheko_hu.ts | 147 +++++++++++++------ resources/langs/nheko_it.ts | 147 +++++++++++++------ resources/langs/nheko_ja.ts | 145 ++++++++++++------ resources/langs/nheko_ml.ts | 147 +++++++++++++------ resources/langs/nheko_nl.ts | 145 ++++++++++++------ resources/langs/nheko_pl.ts | 145 ++++++++++++------ resources/langs/nheko_pt_BR.ts | 147 +++++++++++++------ resources/langs/nheko_pt_PT.ts | 147 +++++++++++++------ resources/langs/nheko_ro.ts | 145 ++++++++++++------ resources/langs/nheko_ru.ts | 149 +++++++++++++------ resources/langs/nheko_si.ts | 145 ++++++++++++------ resources/langs/nheko_sv.ts | 147 +++++++++++++------ resources/langs/nheko_zh_CN.ts | 145 ++++++++++++------ src/SingleImagePackModel.cpp | 12 +- 23 files changed, 2255 insertions(+), 1089 deletions(-) diff --git a/resources/langs/nheko_cs.ts b/resources/langs/nheko_cs.ts index 10777538..fb380e76 100644 --- a/resources/langs/nheko_cs.ts +++ b/resources/langs/nheko_cs.ts @@ -131,17 +131,17 @@ - + Invited user: %1 - + Migrating the cache to the current version failed. This can have different reasons. Please open an issue and try to use an older version in the mean time. Alternatively you can try deleting the cache manually. - + Confirm join @@ -156,13 +156,13 @@ - - + + Confirm invite - + Do you really want to invite %1 (%2)? @@ -227,12 +227,12 @@ - + Do you really want to start a private chat with %1? - + Cache migration failed! @@ -247,7 +247,7 @@ - + Failed to restore OLM account. Please login again. @@ -257,7 +257,7 @@ - + Failed to setup encryption keys. Server response: %1 %2. Please try again later. @@ -288,7 +288,7 @@ - + Failed to leave room: %1 @@ -352,7 +352,7 @@ CrossSigningSecrets - + Decrypt secrets @@ -556,7 +556,7 @@ - Encrypted by an unverified device + Encrypted by an unverified device or the key is from an untrusted source like the key backup. @@ -885,7 +885,7 @@ Example: https://server.my:8787 MessageDelegate - + Encryption enabled @@ -940,7 +940,12 @@ Example: https://server.my:8787 - + + Allow them in + + + + %1 answered the call. @@ -1381,10 +1386,23 @@ Example: https://server.my:8787 + + RoomDirectory + + + Explore Public Rooms + + + + + Search for public rooms + + + RoomInfo - + no version stored @@ -1392,7 +1410,7 @@ Example: https://server.my:8787 RoomList - + New tag @@ -1487,7 +1505,7 @@ Example: https://server.my:8787 - + User settings @@ -1578,7 +1596,7 @@ Example: https://server.my:8787 - + Anyone and guests @@ -1593,7 +1611,17 @@ Example: https://server.my:8787 - + + By knocking + + + + + Restricted by membership in other rooms + + + + Encryption @@ -1639,12 +1667,12 @@ Example: https://server.my:8787 - + Failed to enable encryption: %1 - + Select an avatar @@ -1747,22 +1775,22 @@ Example: https://server.my:8787 - Failed to update image pack: {} + Failed to update image pack: %1 - Failed to delete old image pack: {} + Failed to delete old image pack: %1 - Failed to open image: {} + Failed to open image: %1 - Failed to upload image: {} + Failed to upload image: %1 @@ -1818,7 +1846,7 @@ Example: https://server.my:8787 TimelineModel - + Message redaction failed: %1 @@ -1869,7 +1897,17 @@ Example: https://server.my:8787 - + + %1 allowed to join this room by knocking. + + + + + %1 allowed members of the following rooms to automatically join this room: %2 + + + + %1 made the room open to guests. @@ -1904,7 +1942,7 @@ Example: https://server.my:8787 - + %1 was invited. @@ -1919,12 +1957,17 @@ Example: https://server.my:8787 - + %1 joined. - + + %1 joined via authorisation from %2's server. + + + + %1 rejected their invite. @@ -1964,12 +2007,12 @@ Example: https://server.my:8787 - + You joined this room. - + %1 has changed their avatar and changed their display name to %2. @@ -1979,7 +2022,7 @@ Example: https://server.my:8787 - + Rejected the knock from %1. @@ -2011,7 +2054,7 @@ Example: https://server.my:8787 - + %1 member(s) @@ -2039,7 +2082,7 @@ Example: https://server.my:8787 TimelineViewManager - + No encrypted private chat found with this user. Create an encrypted private chat with this user and try again. @@ -2177,8 +2220,8 @@ Example: https://server.my:8787 UserSettings - - + + Default @@ -2186,7 +2229,7 @@ Example: https://server.my:8787 UserSettingsPage - + Minimize to tray @@ -2206,12 +2249,12 @@ Example: https://server.my:8787 - + profile: %1 - + Default @@ -2417,7 +2460,17 @@ This usually causes the application icon in the task bar to animate in some fash - + + Online Key Backup + + + + + Download message encryption keys from and upload to the encrypted online key backup. + + + + CACHED @@ -2427,7 +2480,7 @@ This usually causes the application icon in the task bar to animate in some fash - + Scale factor @@ -2522,12 +2575,12 @@ This usually causes the application icon in the task bar to animate in some fash - + GENERAL - + INTERFACE @@ -2547,7 +2600,7 @@ This usually causes the application icon in the task bar to animate in some fash - + Master signing key @@ -2597,7 +2650,7 @@ This usually causes the application icon in the task bar to animate in some fash - + Open Sessions File diff --git a/resources/langs/nheko_de.ts b/resources/langs/nheko_de.ts index d37debc2..d4bd2d53 100644 --- a/resources/langs/nheko_de.ts +++ b/resources/langs/nheko_de.ts @@ -131,17 +131,17 @@ - + Invited user: %1 Eingeladener Benutzer: %1 - + Migrating the cache to the current version failed. This can have different reasons. Please open an issue and try to use an older version in the mean time. Alternatively you can try deleting the cache manually. Das Migrieren des Caches auf die aktuelle Version ist fehlgeschlagen. Das kann verschiedene Gründe als Ursache haben. Bitte melde den Fehler und verwende in der Zwischenzeit eine ältere Version. Alternativ kannst du den Cache manuell löschen. - + Confirm join Beitritt bestätigen @@ -156,13 +156,13 @@ Raum %1 erzeugt. - - + + Confirm invite Einladung bestätigen - + Do you really want to invite %1 (%2)? Nutzer %1 (%2) wirklich einladen? @@ -227,12 +227,12 @@ Verbannung aufgehoben: %1 - + Do you really want to start a private chat with %1? Möchtest du wirklich eine private Konversation mit %1 beginnen? - + Cache migration failed! Migration des Caches fehlgeschlagen! @@ -247,7 +247,7 @@ Der Cache auf der Festplatte wurde mit einer neueren Nheko - Version angelegt. Bitte aktualisiere Nheko oder entferne den Cache. - + Failed to restore OLM account. Please login again. Wiederherstellung des OLM Accounts fehlgeschlagen. Bitte logge dich erneut ein. @@ -257,7 +257,7 @@ Gespeicherte Nachrichten konnten nicht wiederhergestellt werden. Bitte melde Dich erneut an. - + Failed to setup encryption keys. Server response: %1 %2. Please try again later. Fehler beim Setup der Verschlüsselungsschlüssel. Servermeldung: %1 %2. Bitte versuche es später erneut. @@ -288,7 +288,7 @@ Raum konnte nicht erstellt werden: %1 - + Failed to leave room: %1 Konnte den Raum nicht verlassen: %1 @@ -352,7 +352,7 @@ CrossSigningSecrets - + Decrypt secrets Geheimnisse entschlüsseln @@ -556,8 +556,8 @@ - Encrypted by an unverified device - Von einem unverifizierten Gerät verschlüsselt + Encrypted by an unverified device or the key is from an untrusted source like the key backup. + @@ -889,7 +889,7 @@ Beispiel: https://mein.server:8787 MessageDelegate - + removed entfernt @@ -959,6 +959,11 @@ Beispiel: https://mein.server:8787 Negotiating call... Wählt… + + + Allow them in + + MessageInput @@ -1385,10 +1390,23 @@ Beispiel: https://mein.server:8787 Bearbeiten abbrechen + + RoomDirectory + + + Explore Public Rooms + + + + + Search for public rooms + + + RoomInfo - + no version stored keine Version gespeichert @@ -1396,7 +1414,7 @@ Beispiel: https://mein.server:8787 RoomList - + New tag Neuer Tag @@ -1491,7 +1509,7 @@ Beispiel: https://mein.server:8787 Raumverzeichnis - + User settings Benutzereinstellungen @@ -1581,7 +1599,7 @@ Beispiel: https://mein.server:8787 - + Anyone and guests Jeder (inkl. Gäste) @@ -1596,7 +1614,17 @@ Beispiel: https://mein.server:8787 Eingeladene Nutzer - + + By knocking + + + + + Restricted by membership in other rooms + + + + Encryption Verschlüsselung @@ -1642,12 +1670,12 @@ Beispiel: https://mein.server:8787 Raumversion - + Failed to enable encryption: %1 Aktivierung der Verschlüsselung fehlgeschlagen: %1 - + Select an avatar Wähle einen Avatar @@ -1750,22 +1778,22 @@ Beispiel: https://mein.server:8787 - Failed to update image pack: {} + Failed to update image pack: %1 - Failed to delete old image pack: {} + Failed to delete old image pack: %1 - Failed to open image: {} + Failed to open image: %1 - Failed to upload image: {} + Failed to upload image: %1 @@ -1821,7 +1849,7 @@ Beispiel: https://mein.server:8787 TimelineModel - + Message redaction failed: %1 Nachricht zurückziehen fehlgeschlagen: %1 @@ -1871,7 +1899,17 @@ Beispiel: https://mein.server:8787 %1 hat eingestellt, dass dieser Raum eine Einladung benötigt um beizutreten. - + + %1 allowed to join this room by knocking. + + + + + %1 allowed members of the following rooms to automatically join this room: %2 + + + + %1 made the room open to guests. %1 hat Gästen erlaubt den Raum zu betreten. @@ -1906,7 +1944,7 @@ Beispiel: https://mein.server:8787 %1 hat die Berechtigungen dieses Raums bearbeitet. - + %1 was invited. %1 wurde eingeladen. @@ -1921,12 +1959,17 @@ Beispiel: https://mein.server:8787 %1 hat etwas im Profil geändert. - + %1 joined. %1 hat den Raum betreten. - + + %1 joined via authorisation from %2's server. + + + + %1 rejected their invite. %1 hat die Einladung abgewiesen. @@ -1966,12 +2009,12 @@ Beispiel: https://mein.server:8787 %1 hat das Anklopfen zurückgezogen. - + You joined this room. Du bist dem Raum beigetreten. - + %1 has changed their avatar and changed their display name to %2. %1 hat den eigenen Avatar und Namen geändert zu %2. @@ -1981,7 +2024,7 @@ Beispiel: https://mein.server:8787 %1 hat den eigenen Namen geändert zu %2. - + Rejected the knock from %1. Hat das Anklopfen von %1 abgewiesen. @@ -2013,7 +2056,7 @@ Beispiel: https://mein.server:8787 Kein Raum geöffnet - + %1 member(s) %1 Teilnehmer @@ -2041,7 +2084,7 @@ Beispiel: https://mein.server:8787 TimelineViewManager - + No encrypted private chat found with this user. Create an encrypted private chat with this user and try again. Keinen verschlüsselten Chat mit diesem User gefunden. Erstelle einen verschlüsselten 1:1 Chat mit diesem Nutzer und versuche es erneut. @@ -2179,8 +2222,8 @@ Beispiel: https://mein.server:8787 UserSettings - - + + Default Standard @@ -2188,7 +2231,7 @@ Beispiel: https://mein.server:8787 UserSettingsPage - + Minimize to tray Ins Benachrichtigungsfeld minimieren @@ -2208,12 +2251,12 @@ Beispiel: https://mein.server:8787 Runde Profilbilder - + profile: %1 Profil: %1 - + Default Standard @@ -2429,7 +2472,17 @@ Normalerweise animiert das den Taskbaricon oder färbt das Fenster orange ein. - + + Online Key Backup + + + + + Download message encryption keys from and upload to the encrypted online key backup. + + + + CACHED IM CACHE @@ -2439,7 +2492,7 @@ Normalerweise animiert das den Taskbaricon oder färbt das Fenster orange ein.NICHT IM CACHE - + Scale factor Skalierungsfaktor @@ -2534,12 +2587,12 @@ Normalerweise animiert das den Taskbaricon oder färbt das Fenster orange ein.VERSCHLÜSSELUNG - + GENERAL ALLGEMEINES - + INTERFACE OBERFLÄCHE @@ -2559,7 +2612,7 @@ Normalerweise animiert das den Taskbaricon oder färbt das Fenster orange ein.Emojischriftart - + Master signing key Masterverifizierungsschlüssel @@ -2609,7 +2662,7 @@ Normalerweise animiert das den Taskbaricon oder färbt das Fenster orange ein.Alle Dateien (*) - + Open Sessions File Öffne Sessions Datei diff --git a/resources/langs/nheko_el.ts b/resources/langs/nheko_el.ts index 9866b563..c5bf4222 100644 --- a/resources/langs/nheko_el.ts +++ b/resources/langs/nheko_el.ts @@ -131,17 +131,17 @@ - + Invited user: %1 - + Migrating the cache to the current version failed. This can have different reasons. Please open an issue and try to use an older version in the mean time. Alternatively you can try deleting the cache manually. - + Confirm join @@ -156,13 +156,13 @@ - - + + Confirm invite - + Do you really want to invite %1 (%2)? @@ -227,12 +227,12 @@ - + Do you really want to start a private chat with %1? - + Cache migration failed! @@ -247,7 +247,7 @@ - + Failed to restore OLM account. Please login again. @@ -257,7 +257,7 @@ - + Failed to setup encryption keys. Server response: %1 %2. Please try again later. @@ -288,7 +288,7 @@ - + Failed to leave room: %1 @@ -352,7 +352,7 @@ CrossSigningSecrets - + Decrypt secrets @@ -556,7 +556,7 @@ - Encrypted by an unverified device + Encrypted by an unverified device or the key is from an untrusted source like the key backup. @@ -885,7 +885,7 @@ Example: https://server.my:8787 MessageDelegate - + removed @@ -955,6 +955,11 @@ Example: https://server.my:8787 Negotiating call... + + + Allow them in + + MessageInput @@ -1381,10 +1386,23 @@ Example: https://server.my:8787 + + RoomDirectory + + + Explore Public Rooms + + + + + Search for public rooms + + + RoomInfo - + no version stored @@ -1392,7 +1410,7 @@ Example: https://server.my:8787 RoomList - + New tag @@ -1487,7 +1505,7 @@ Example: https://server.my:8787 - + User settings @@ -1577,7 +1595,7 @@ Example: https://server.my:8787 - + Anyone and guests @@ -1592,7 +1610,17 @@ Example: https://server.my:8787 - + + By knocking + + + + + Restricted by membership in other rooms + + + + Encryption @@ -1638,12 +1666,12 @@ Example: https://server.my:8787 - + Failed to enable encryption: %1 - + Select an avatar @@ -1746,22 +1774,22 @@ Example: https://server.my:8787 - Failed to update image pack: {} + Failed to update image pack: %1 - Failed to delete old image pack: {} + Failed to delete old image pack: %1 - Failed to open image: {} + Failed to open image: %1 - Failed to upload image: {} + Failed to upload image: %1 @@ -1817,7 +1845,7 @@ Example: https://server.my:8787 TimelineModel - + Message redaction failed: %1 @@ -1867,7 +1895,17 @@ Example: https://server.my:8787 - + + %1 allowed to join this room by knocking. + + + + + %1 allowed members of the following rooms to automatically join this room: %2 + + + + %1 made the room open to guests. @@ -1902,7 +1940,7 @@ Example: https://server.my:8787 - + %1 was invited. @@ -1917,12 +1955,17 @@ Example: https://server.my:8787 - + %1 joined. - + + %1 joined via authorisation from %2's server. + + + + %1 rejected their invite. @@ -1962,12 +2005,12 @@ Example: https://server.my:8787 - + You joined this room. - + %1 has changed their avatar and changed their display name to %2. @@ -1977,7 +2020,7 @@ Example: https://server.my:8787 - + Rejected the knock from %1. @@ -2009,7 +2052,7 @@ Example: https://server.my:8787 - + %1 member(s) @@ -2037,7 +2080,7 @@ Example: https://server.my:8787 TimelineViewManager - + No encrypted private chat found with this user. Create an encrypted private chat with this user and try again. @@ -2175,8 +2218,8 @@ Example: https://server.my:8787 UserSettings - - + + Default @@ -2184,7 +2227,7 @@ Example: https://server.my:8787 UserSettingsPage - + Minimize to tray Ελαχιστοποίηση @@ -2204,12 +2247,12 @@ Example: https://server.my:8787 - + profile: %1 - + Default @@ -2415,7 +2458,17 @@ This usually causes the application icon in the task bar to animate in some fash - + + Online Key Backup + + + + + Download message encryption keys from and upload to the encrypted online key backup. + + + + CACHED @@ -2425,7 +2478,7 @@ This usually causes the application icon in the task bar to animate in some fash - + Scale factor @@ -2520,12 +2573,12 @@ This usually causes the application icon in the task bar to animate in some fash - + GENERAL ΓΕΝΙΚΑ - + INTERFACE @@ -2545,7 +2598,7 @@ This usually causes the application icon in the task bar to animate in some fash - + Master signing key @@ -2595,7 +2648,7 @@ This usually causes the application icon in the task bar to animate in some fash Όλα τα αρχεία (*) - + Open Sessions File diff --git a/resources/langs/nheko_en.ts b/resources/langs/nheko_en.ts index 4feabb84..86a9b36a 100644 --- a/resources/langs/nheko_en.ts +++ b/resources/langs/nheko_en.ts @@ -131,17 +131,17 @@ - + Invited user: %1 Invited user: %1 - + Migrating the cache to the current version failed. This can have different reasons. Please open an issue and try to use an older version in the mean time. Alternatively you can try deleting the cache manually. Migrating the cache to the current version failed. This can have different reasons. Please open an issue and try to use an older version in the mean time. Alternatively you can try deleting the cache manually. - + Confirm join Confirm join @@ -156,13 +156,13 @@ Room %1 created. - - + + Confirm invite Confirm invite - + Do you really want to invite %1 (%2)? Do you really want to invite %1 (%2)? @@ -227,12 +227,12 @@ Unbanned user: %1 - + Do you really want to start a private chat with %1? Do you really want to start a private chat with %1? - + Cache migration failed! Cache migration failed! @@ -247,7 +247,7 @@ The cache on your disk is newer than this version of Nheko supports. Please update or clear your cache. - + Failed to restore OLM account. Please login again. Failed to restore OLM account. Please login again. @@ -257,7 +257,7 @@ Failed to restore save data. Please login again. - + Failed to setup encryption keys. Server response: %1 %2. Please try again later. Failed to setup encryption keys. Server response: %1 %2. Please try again later. @@ -288,7 +288,7 @@ Room creation failed: %1 - + Failed to leave room: %1 Failed to leave room: %1 @@ -352,7 +352,7 @@ CrossSigningSecrets - + Decrypt secrets Decrypt secrets @@ -499,42 +499,42 @@ There is no key to unlock this message. We requested the key automatically, but you can try requesting it again if you are impatient. - + There is no key to unlock this message. We requested the key automatically, but you can try requesting it again if you are impatient. This message couldn't be decrypted, because we only have a key for newer messages. You can try requesting access to this message. - + This message couldn't be decrypted, because we only have a key for newer messages. You can try requesting access to this message. There was an internal error reading the decryption key from the database. - + There was an internal error reading the decryption key from the database. There was an error decrypting this message. - + There was an error decrypting this message. The message couldn't be parsed. - + The message couldn't be parsed. The encryption key was reused! Someone is possibly trying to insert false messages into this chat! - + The encryption key was reused! Someone is possibly trying to insert false messages into this chat! Unknown decryption error - + Unknown decryption error Request key - + Request key @@ -556,8 +556,8 @@ - Encrypted by an unverified device - Encrypted by an unverified device + Encrypted by an unverified device or the key is from an untrusted source like the key backup. + Encrypted by an unverified device or the key is from an untrusted source like the key backup. @@ -607,64 +607,64 @@ Editing image pack - + Editing image pack Add images - + Add images Stickers (*.png *.webp) - + Stickers (*.png *.webp) State key - + State key Packname - + Packname Attribution - + Attribution Use as Emoji - + Use as Emoji Use as Sticker - + Use as Sticker Shortcode - + Shortcode Body - + Body Cancel - Cancel + Cancel Save - + Save @@ -672,52 +672,52 @@ Image pack settings - + Image pack settings Create account pack - + Create account pack New room pack - + New room pack Private pack - + Private pack Pack from this room - + Pack from this room Globally enabled pack - + Globally enabled pack Enable globally - + Enable globally Enables this pack to be used in all rooms - + Enables this pack to be used in all rooms Edit - Edit + Edit Close - Close + Close @@ -889,7 +889,7 @@ Example: https://server.my:8787 MessageDelegate - + Encryption enabled Encryption enabled @@ -944,7 +944,12 @@ Example: https://server.my:8787 Negotiating call… - + + Allow them in + Allow them in + + + %1 answered the call. %1 answered the call. @@ -1262,7 +1267,7 @@ Example: https://server.my:8787 Read receipts - Read receipts + Read receipts @@ -1270,7 +1275,7 @@ Example: https://server.my:8787 Yesterday, %1 - + Yesterday, %1 @@ -1324,12 +1329,12 @@ Example: https://server.my:8787 Registration token - + Registration token Please enter a valid registration token. - + Please enter a valid registration token. @@ -1385,10 +1390,23 @@ Example: https://server.my:8787 Cancel edit + + RoomDirectory + + + Explore Public Rooms + Explore Public Rooms + + + + Search for public rooms + Search for public rooms + + RoomInfo - + no version stored no version stored @@ -1396,7 +1414,7 @@ Example: https://server.my:8787 RoomList - + New tag New tag @@ -1408,12 +1426,12 @@ Example: https://server.my:8787 Leave Room - + Leave room Are you sure you want to leave this room? - + Are you sure you want to leave this room? @@ -1491,7 +1509,7 @@ Example: https://server.my:8787 Room directory - + User settings User settings @@ -1520,22 +1538,22 @@ Example: https://server.my:8787 This room is not encrypted! - + This room is not encrypted! This user is verified. - + This user is verified. This user isn't verified, but is still using the same master key from the first time you met. - + This user isn't verified, but is still using the same master key from the first time you met. This user has unverified devices! - + This user has unverified devices! @@ -1578,10 +1596,10 @@ Example: https://server.my:8787 Room access - + Room access - + Anyone and guests Anyone and guests @@ -1596,7 +1614,17 @@ Example: https://server.my:8787 Invited users - + + By knocking + By knocking + + + + Restricted by membership in other rooms + Restricted by membership in other rooms + + + Encryption Encryption @@ -1614,17 +1642,17 @@ Example: https://server.my:8787 Sticker & Emote Settings - + Sticker & Emote Settings Change - + Change Change what packs are enabled, remove packs or create new ones - + Change what packs are enabled, remove packs or create new ones @@ -1642,12 +1670,12 @@ Example: https://server.my:8787 Room Version - + Failed to enable encryption: %1 Failed to enable encryption: %1 - + Select an avatar Select an avatar @@ -1750,23 +1778,23 @@ Example: https://server.my:8787 - Failed to update image pack: {} - + Failed to update image pack: %1 + Failed to update image pack: %1 - Failed to delete old image pack: {} - + Failed to delete old image pack: %1 + Failed to delete old image pack: %1 - Failed to open image: {} - + Failed to open image: %1 + Failed to open image: %1 - Failed to upload image: {} - + Failed to upload image: %1 + Failed to upload image: %1 @@ -1821,7 +1849,7 @@ Example: https://server.my:8787 TimelineModel - + Message redaction failed: %1 Message redaction failed: %1 @@ -1871,7 +1899,17 @@ Example: https://server.my:8787 %1 made this room require an invitation to join. - + + %1 allowed to join this room by knocking. + %1 allowed to join this room by knocking. + + + + %1 allowed members of the following rooms to automatically join this room: %2 + %1 allowed members of the following rooms to automatically join this room: %2 + + + %1 made the room open to guests. %1 made the room open to guests. @@ -1906,7 +1944,7 @@ Example: https://server.my:8787 %1 has changed the room's permissions. - + %1 was invited. %1 was invited. @@ -1921,12 +1959,17 @@ Example: https://server.my:8787 %1 changed some profile info. - + %1 joined. %1 joined. - + + %1 joined via authorisation from %2's server. + %1 joined via authorisation from %2's server. + + + %1 rejected their invite. %1 rejected their invite. @@ -1966,12 +2009,12 @@ Example: https://server.my:8787 %1 redacted their knock. - + You joined this room. You joined this room. - + %1 has changed their avatar and changed their display name to %2. %1 has changed their avatar and changed their display name to %2. @@ -1981,7 +2024,7 @@ Example: https://server.my:8787 %1 has changed their display name to %2. - + Rejected the knock from %1. Rejected the knock from %1. @@ -2013,7 +2056,7 @@ Example: https://server.my:8787 No room open - + %1 member(s) %1 member(s) @@ -2041,7 +2084,7 @@ Example: https://server.my:8787 TimelineViewManager - + No encrypted private chat found with this user. Create an encrypted private chat with this user and try again. No encrypted private chat found with this user. Create an encrypted private chat with this user and try again. @@ -2061,22 +2104,22 @@ Example: https://server.my:8787 This room is not encrypted! - + This room is not encrypted! This room contains only verified devices. - + This room contains only verified devices. This rooms contain verified devices and devices which have never changed their master key. - + This rooms contain verified devices and devices which have never changed their master key. This room contains unverified devices! - + This room contains unverified devices! @@ -2179,8 +2222,8 @@ Example: https://server.my:8787 UserSettings - - + + Default Default @@ -2188,7 +2231,7 @@ Example: https://server.my:8787 UserSettingsPage - + Minimize to tray Minimize to tray @@ -2208,12 +2251,12 @@ Example: https://server.my:8787 Circular Avatars - + profile: %1 profile: %1 - + Default Default @@ -2412,12 +2455,12 @@ This usually causes the application icon in the task bar to animate in some fash Send encrypted messages to verified users only - + Send encrypted messages to verified users only Requires a user to be verified to send encrypted messages to them. This improves safety but makes E2EE more tedious. - + Requires a user to be verified to send encrypted messages to them. This improves safety but makes E2EE more tedious. @@ -2427,10 +2470,20 @@ This usually causes the application icon in the task bar to animate in some fash Automatically replies to key requests from other users, if they are verified, even if that device shouldn't have access to those keys otherwise. - + Automatically replies to key requests from other users, if they are verified, even if that device shouldn't have access to those keys otherwise. - + + Online Key Backup + Online Key Backup + + + + Download message encryption keys from and upload to the encrypted online key backup. + Download message encryption keys from and upload to the encrypted online key backup. + + + CACHED CACHED @@ -2440,7 +2493,7 @@ This usually causes the application icon in the task bar to animate in some fash NOT CACHED - + Scale factor Scale factor @@ -2535,12 +2588,12 @@ This usually causes the application icon in the task bar to animate in some fash ENCRYPTION - + GENERAL GENERAL - + INTERFACE INTERFACE @@ -2560,7 +2613,7 @@ This usually causes the application icon in the task bar to animate in some fash Emoji Font Family - + Master signing key Master signing key @@ -2610,7 +2663,7 @@ This usually causes the application icon in the task bar to animate in some fash All Files (*) - + Open Sessions File Open Sessions File diff --git a/resources/langs/nheko_eo.ts b/resources/langs/nheko_eo.ts index 74364e52..88828a4a 100644 --- a/resources/langs/nheko_eo.ts +++ b/resources/langs/nheko_eo.ts @@ -131,17 +131,17 @@ - + Invited user: %1 Invitita uzanto: %1 - + Migrating the cache to the current version failed. This can have different reasons. Please open an issue and try to use an older version in the mean time. Alternatively you can try deleting the cache manually. Malsukcesis migrado de kaŝmemoro al nuna versio. Tio povas havi diversajn kialojn. Bonvolu raporti eraron kaj dume provi malpli novan version. Alternative, vi povas provi forigi la kaŝmemoron permane. - + Confirm join Konfirmu aliĝon @@ -157,13 +157,13 @@ Ĉambro %1 farit. - - + + Confirm invite Konfirmu inviton - + Do you really want to invite %1 (%2)? Ĉu vi certe volas inviti uzanton %1 (%2)? @@ -228,12 +228,12 @@ Malforbaris uzanton: %1 - + Do you really want to start a private chat with %1? Ĉu vi certe volas komenci privatan babilon kun %1? - + Cache migration failed! Malsukcesis migrado de kaŝmemoro! @@ -248,7 +248,7 @@ La kaŝmemoro sur via disko estas pli nova ol kiom ĉi tiu versio de Nheko subtenas. Bonvolu ĝisdatigi la programon aŭ vakigi vian kaŝmemoron. - + Failed to restore OLM account. Please login again. @@ -258,7 +258,7 @@ - + Failed to setup encryption keys. Server response: %1 %2. Please try again later. Malsukcesis agordi ĉifrajn ŝlosilojn. Respondo de servilo: %1 %2. Bonvolu reprovi poste. @@ -289,7 +289,7 @@ Malsukcesis krei ĉambron: %1 - + Failed to leave room: %1 Malsukcesis eliri el ĉambro: %1 @@ -353,7 +353,7 @@ CrossSigningSecrets - + Decrypt secrets Malĉifri sekretojn @@ -557,8 +557,8 @@ - Encrypted by an unverified device - Ĉifrita de nekontrolita aparato + Encrypted by an unverified device or the key is from an untrusted source like the key backup. + @@ -892,7 +892,7 @@ Ekzemplo: https://servilo.mia:8787 MessageDelegate - + removed forigita @@ -962,6 +962,11 @@ Ekzemplo: https://servilo.mia:8787 Negotiating call... Traktante vokon… + + + Allow them in + + MessageInput @@ -1388,10 +1393,23 @@ Ekzemplo: https://servilo.mia:8787 Nuligi redakton + + RoomDirectory + + + Explore Public Rooms + + + + + Search for public rooms + + + RoomInfo - + no version stored @@ -1399,7 +1417,7 @@ Ekzemplo: https://servilo.mia:8787 RoomList - + New tag @@ -1494,7 +1512,7 @@ Ekzemplo: https://servilo.mia:8787 Ĉambra dosierujo - + User settings Agordoj de uzanto @@ -1584,7 +1602,7 @@ Ekzemplo: https://servilo.mia:8787 - + Anyone and guests Ĉiu ajn, inkluzive gastojn @@ -1599,7 +1617,17 @@ Ekzemplo: https://servilo.mia:8787 Invititoj - + + By knocking + + + + + Restricted by membership in other rooms + + + + Encryption Ĉifrado @@ -1645,12 +1673,12 @@ Ekzemplo: https://servilo.mia:8787 Versio de ĉambro - + Failed to enable encryption: %1 Malsukcesis ŝalti ĉifradon: %1 - + Select an avatar Elektu bildon de ĉambro @@ -1753,22 +1781,22 @@ Ekzemplo: https://servilo.mia:8787 - Failed to update image pack: {} + Failed to update image pack: %1 - Failed to delete old image pack: {} + Failed to delete old image pack: %1 - Failed to open image: {} + Failed to open image: %1 - Failed to upload image: {} + Failed to upload image: %1 @@ -1825,7 +1853,7 @@ Ekzemplo: https://servilo.mia:8787 TimelineModel - + Message redaction failed: %1 @@ -1875,7 +1903,17 @@ Ekzemplo: https://servilo.mia:8787 %1 ekpostulis inviton por aliĝoj al la ĉamrbo. - + + %1 allowed to join this room by knocking. + + + + + %1 allowed members of the following rooms to automatically join this room: %2 + + + + %1 made the room open to guests. %1 malfermis la ĉambron al gastoj. @@ -1910,7 +1948,7 @@ Ekzemplo: https://servilo.mia:8787 - + %1 was invited. %1 estis invitata. %1 estis invitita. @@ -1927,12 +1965,17 @@ Ekzemplo: https://servilo.mia:8787 - + %1 joined. %1 aliĝis. - + + %1 joined via authorisation from %2's server. + + + + %1 rejected their invite. @@ -1972,12 +2015,12 @@ Ekzemplo: https://servilo.mia:8787 - + You joined this room. Vi aliĝis ĉi tiun ĉambron. - + %1 has changed their avatar and changed their display name to %2. @@ -1987,7 +2030,7 @@ Ekzemplo: https://servilo.mia:8787 - + Rejected the knock from %1. @@ -2019,7 +2062,7 @@ Ekzemplo: https://servilo.mia:8787 - + %1 member(s) %1 ĉambrano(j) @@ -2047,7 +2090,7 @@ Ekzemplo: https://servilo.mia:8787 TimelineViewManager - + No encrypted private chat found with this user. Create an encrypted private chat with this user and try again. @@ -2185,8 +2228,8 @@ Ekzemplo: https://servilo.mia:8787 UserSettings - - + + Default @@ -2194,7 +2237,7 @@ Ekzemplo: https://servilo.mia:8787 UserSettingsPage - + Minimize to tray @@ -2214,12 +2257,12 @@ Ekzemplo: https://servilo.mia:8787 - + profile: %1 - + Default @@ -2442,7 +2485,17 @@ This usually causes the application icon in the task bar to animate in some fash - + + Online Key Backup + + + + + Download message encryption keys from and upload to the encrypted online key backup. + + + + CACHED @@ -2452,7 +2505,7 @@ This usually causes the application icon in the task bar to animate in some fash - + Scale factor @@ -2547,12 +2600,12 @@ This usually causes the application icon in the task bar to animate in some fash - + GENERAL - + INTERFACE @@ -2572,7 +2625,7 @@ This usually causes the application icon in the task bar to animate in some fash - + Master signing key @@ -2622,7 +2675,7 @@ This usually causes the application icon in the task bar to animate in some fash Ĉiuj dosieroj (*) - + Open Sessions File diff --git a/resources/langs/nheko_es.ts b/resources/langs/nheko_es.ts index cb4a6c1d..638a2922 100644 --- a/resources/langs/nheko_es.ts +++ b/resources/langs/nheko_es.ts @@ -131,17 +131,17 @@ - + Invited user: %1 Usuario invitado: %1 - + Migrating the cache to the current version failed. This can have different reasons. Please open an issue and try to use an older version in the mean time. Alternatively you can try deleting the cache manually. La migración de la caché a la versión actual ha fallado. Esto puede deberse a distintos motivos. Por favor, reporte el incidente y mientras tanto intente usar una versión anterior. También puede probar a borrar la caché manualmente. - + Confirm join @@ -156,13 +156,13 @@ Sala %1 creada. - - + + Confirm invite Confirmar invitación - + Do you really want to invite %1 (%2)? @@ -227,12 +227,12 @@ - + Do you really want to start a private chat with %1? - + Cache migration failed! @@ -247,7 +247,7 @@ - + Failed to restore OLM account. Please login again. @@ -257,7 +257,7 @@ - + Failed to setup encryption keys. Server response: %1 %2. Please try again later. @@ -288,7 +288,7 @@ - + Failed to leave room: %1 @@ -352,7 +352,7 @@ CrossSigningSecrets - + Decrypt secrets @@ -556,7 +556,7 @@ - Encrypted by an unverified device + Encrypted by an unverified device or the key is from an untrusted source like the key backup. @@ -885,7 +885,7 @@ Example: https://server.my:8787 MessageDelegate - + Encryption enabled @@ -940,7 +940,12 @@ Example: https://server.my:8787 - + + Allow them in + + + + %1 answered the call. @@ -1381,10 +1386,23 @@ Example: https://server.my:8787 + + RoomDirectory + + + Explore Public Rooms + + + + + Search for public rooms + + + RoomInfo - + no version stored @@ -1392,7 +1410,7 @@ Example: https://server.my:8787 RoomList - + New tag @@ -1487,7 +1505,7 @@ Example: https://server.my:8787 - + User settings @@ -1577,7 +1595,7 @@ Example: https://server.my:8787 - + Anyone and guests @@ -1592,7 +1610,17 @@ Example: https://server.my:8787 - + + By knocking + + + + + Restricted by membership in other rooms + + + + Encryption @@ -1638,12 +1666,12 @@ Example: https://server.my:8787 - + Failed to enable encryption: %1 - + Select an avatar @@ -1746,22 +1774,22 @@ Example: https://server.my:8787 - Failed to update image pack: {} + Failed to update image pack: %1 - Failed to delete old image pack: {} + Failed to delete old image pack: %1 - Failed to open image: {} + Failed to open image: %1 - Failed to upload image: {} + Failed to upload image: %1 @@ -1817,7 +1845,7 @@ Example: https://server.my:8787 TimelineModel - + Message redaction failed: %1 @@ -1867,7 +1895,17 @@ Example: https://server.my:8787 - + + %1 allowed to join this room by knocking. + + + + + %1 allowed members of the following rooms to automatically join this room: %2 + + + + %1 made the room open to guests. @@ -1902,7 +1940,7 @@ Example: https://server.my:8787 - + %1 was invited. @@ -1927,12 +1965,17 @@ Example: https://server.my:8787 - + %1 joined. - + + %1 joined via authorisation from %2's server. + + + + %1 rejected their invite. @@ -1972,12 +2015,12 @@ Example: https://server.my:8787 - + You joined this room. Te has unido a esta sala. - + Rejected the knock from %1. @@ -2009,7 +2052,7 @@ Example: https://server.my:8787 - + %1 member(s) @@ -2037,7 +2080,7 @@ Example: https://server.my:8787 TimelineViewManager - + No encrypted private chat found with this user. Create an encrypted private chat with this user and try again. @@ -2175,8 +2218,8 @@ Example: https://server.my:8787 UserSettings - - + + Default @@ -2184,7 +2227,7 @@ Example: https://server.my:8787 UserSettingsPage - + Minimize to tray @@ -2204,12 +2247,12 @@ Example: https://server.my:8787 - + profile: %1 - + Default @@ -2415,7 +2458,17 @@ This usually causes the application icon in the task bar to animate in some fash - + + Online Key Backup + + + + + Download message encryption keys from and upload to the encrypted online key backup. + + + + CACHED @@ -2425,7 +2478,7 @@ This usually causes the application icon in the task bar to animate in some fash - + Scale factor @@ -2520,12 +2573,12 @@ This usually causes the application icon in the task bar to animate in some fash - + GENERAL - + INTERFACE @@ -2545,7 +2598,7 @@ This usually causes the application icon in the task bar to animate in some fash - + Master signing key @@ -2595,7 +2648,7 @@ This usually causes the application icon in the task bar to animate in some fash - + Open Sessions File diff --git a/resources/langs/nheko_et.ts b/resources/langs/nheko_et.ts index 60a3d7cb..4b0d5a97 100644 --- a/resources/langs/nheko_et.ts +++ b/resources/langs/nheko_et.ts @@ -131,17 +131,17 @@ - + Invited user: %1 Kutsutud kasutaja: %1 - + Migrating the cache to the current version failed. This can have different reasons. Please open an issue and try to use an older version in the mean time. Alternatively you can try deleting the cache manually. Puhverdatud andmete muutmine sobivaks rakenduse praeguse versiooniga ei õnnestunud. Sellel võib olla erinevaid põhjuseid. Palun saada meile veateade ja seni kasuta vanemat rakenduse versiooni. Aga kui sa soovid proovida, siis kustuta puhverdatud andmed käsitsi. - + Confirm join Kinnita liitumine @@ -156,13 +156,13 @@ %1 jututuba on loodud. - - + + Confirm invite Kinnita kutse - + Do you really want to invite %1 (%2)? Kas sa tõesti soovid saata kutset kasutajale %1 (%2)? @@ -227,12 +227,12 @@ Suhtluskeeld eemaldatud: %1 - + Do you really want to start a private chat with %1? Kas sa kindlasti soovid alustada otsevestlust kasutajaga %1? - + Cache migration failed! Puhvri versiooniuuendus ebaõnnestus! @@ -247,7 +247,7 @@ Sinu andmekandjale salvestatud puhvri versioon on uuem, kui käesolev Nheko versioon kasutada oskab. Palun tee Nheko uuendus või kustuta puhverdatud andmed. - + Failed to restore OLM account. Please login again. OLM konto taastamine ei õnnestunud. Palun logi uuesti sisse. @@ -257,7 +257,7 @@ Salvestatud andmete taastamine ei õnnestunud. Palun logi uuesti sisse. - + Failed to setup encryption keys. Server response: %1 %2. Please try again later. Krüptovõtmete kasutusele võtmine ei õnnestunud. Koduserveri vastus päringule: %1 %2. Palun proovi hiljem uuesti. @@ -288,7 +288,7 @@ Jututoa loomine ei õnnestunud: %1 - + Failed to leave room: %1 Jututoast lahkumine ei õnnestunud: %1 @@ -352,7 +352,7 @@ CrossSigningSecrets - + Decrypt secrets Dekrüpti andmed @@ -556,8 +556,8 @@ - Encrypted by an unverified device - Krüptitud verifitseerimata seadmes + Encrypted by an unverified device or the key is from an untrusted source like the key backup. + @@ -889,7 +889,7 @@ Näiteks: https://server.minu:8787 MessageDelegate - + Encryption enabled Krüptimine on kasutusel @@ -944,7 +944,12 @@ Näiteks: https://server.minu:8787 Ühendan kõnet… - + + Allow them in + + + + %1 answered the call. %1 vastas kõnele. @@ -1385,10 +1390,23 @@ Näiteks: https://server.minu:8787 Tühista muudatused + + RoomDirectory + + + Explore Public Rooms + + + + + Search for public rooms + + + RoomInfo - + no version stored salvestatud versiooni ei leidu @@ -1396,7 +1414,7 @@ Näiteks: https://server.minu:8787 RoomList - + New tag Uus silt @@ -1491,7 +1509,7 @@ Näiteks: https://server.minu:8787 Jututubade loend - + User settings Kasutaja seadistused @@ -1581,7 +1599,7 @@ Näiteks: https://server.minu:8787 - + Anyone and guests Kõik (sealhulgas külalised) @@ -1596,7 +1614,17 @@ Näiteks: https://server.minu:8787 Kutsutud kasutajad - + + By knocking + + + + + Restricted by membership in other rooms + + + + Encryption Krüptimine @@ -1642,12 +1670,12 @@ Näiteks: https://server.minu:8787 Jututoa versioon - + Failed to enable encryption: %1 Krüptimise kasutuselevõtmine ei õnnestunud: %1 - + Select an avatar Vali tunnuspilt @@ -1750,22 +1778,22 @@ Näiteks: https://server.minu:8787 - Failed to update image pack: {} + Failed to update image pack: %1 - Failed to delete old image pack: {} + Failed to delete old image pack: %1 - Failed to open image: {} + Failed to open image: %1 - Failed to upload image: {} + Failed to upload image: %1 @@ -1821,7 +1849,7 @@ Näiteks: https://server.minu:8787 TimelineModel - + Message redaction failed: %1 Sõnumi ümbersõnastamine ebaõnnestus: %1 @@ -1871,7 +1899,17 @@ Näiteks: https://server.minu:8787 %1 seadistas, et selle jututoaga liitumine eeldab kutset. - + + %1 allowed to join this room by knocking. + + + + + %1 allowed members of the following rooms to automatically join this room: %2 + + + + %1 made the room open to guests. %1 muutis selle jututoa külalistele ligipääsetavaks. @@ -1906,7 +1944,7 @@ Näiteks: https://server.minu:8787 %1 muutis selle jututoa õigusi. - + %1 was invited. %1 sai kutse. @@ -1921,12 +1959,17 @@ Näiteks: https://server.minu:8787 %1 muutis oma profiili. - + %1 joined. %1 liitus jututoaga. - + + %1 joined via authorisation from %2's server. + + + + %1 rejected their invite. %1 lükkas liitumiskutse tagasi. @@ -1966,12 +2009,12 @@ Näiteks: https://server.minu:8787 %1 muutis oma koputust jututoa uksele. - + You joined this room. Sa liitusid jututoaga. - + %1 has changed their avatar and changed their display name to %2. %1 muutis oma tunnuspilti ja seadistas uueks kuvatavaks nimeks %2. @@ -1981,7 +2024,7 @@ Näiteks: https://server.minu:8787 %1 seadistas uueks kuvatavaks nimeks %2. - + Rejected the knock from %1. Lükkas tagasi %1 koputuse jututoa uksele. @@ -2013,7 +2056,7 @@ Näiteks: https://server.minu:8787 Ühtegi jututuba pole avatud - + %1 member(s) %1 liige(t) @@ -2041,7 +2084,7 @@ Näiteks: https://server.minu:8787 TimelineViewManager - + No encrypted private chat found with this user. Create an encrypted private chat with this user and try again. Ühtegi krüptitud vestlust selle kasutajaga ei leidunud. Palun loo temaga krüptitud vestlus ja proovi uuesti. @@ -2179,8 +2222,8 @@ Näiteks: https://server.minu:8787 UserSettings - - + + Default Vaikimisi @@ -2188,7 +2231,7 @@ Näiteks: https://server.minu:8787 UserSettingsPage - + Minimize to tray Vähenda tegumiribale @@ -2208,12 +2251,12 @@ Näiteks: https://server.minu:8787 Ümmargused tunnuspildid - + profile: %1 Profiil: %1 - + Default Vaikimisi @@ -2430,7 +2473,17 @@ See tavaliselt tähendab, et rakenduse ikoon tegumiribal annab mingit sorti anim - + + Online Key Backup + + + + + Download message encryption keys from and upload to the encrypted online key backup. + + + + CACHED PUHVERDATUD @@ -2440,7 +2493,7 @@ See tavaliselt tähendab, et rakenduse ikoon tegumiribal annab mingit sorti anim PUHVERDAMATA - + Scale factor Mastaabitegur @@ -2535,12 +2588,12 @@ See tavaliselt tähendab, et rakenduse ikoon tegumiribal annab mingit sorti anim KRÜPTIMINE - + GENERAL ÜLDISED SEADISTUSED - + INTERFACE LIIDES @@ -2560,7 +2613,7 @@ See tavaliselt tähendab, et rakenduse ikoon tegumiribal annab mingit sorti anim Fondiperekond emojide jaoks - + Master signing key Üldine allkirjavõti @@ -2610,7 +2663,7 @@ See tavaliselt tähendab, et rakenduse ikoon tegumiribal annab mingit sorti anim Kõik failid (*) - + Open Sessions File Ava sessioonide fail diff --git a/resources/langs/nheko_fi.ts b/resources/langs/nheko_fi.ts index 8db7eb99..c5de1d45 100644 --- a/resources/langs/nheko_fi.ts +++ b/resources/langs/nheko_fi.ts @@ -131,17 +131,17 @@ - + Invited user: %1 Kutsuttu käyttäjä: %1 - + Migrating the cache to the current version failed. This can have different reasons. Please open an issue and try to use an older version in the mean time. Alternatively you can try deleting the cache manually. Välimuistin tuominen nykyiseen versioon epäonnistui. Tällä voi olla eri syitä. Luo vikailmoitus ja yritä sillä aikaa käyttää vanhempaa versiota. Voit myös vaihtoehtoisesti koettaa tyhjentää välimuistin käsin. - + Confirm join Vahvista liittyminen @@ -156,13 +156,13 @@ Huone %1 luotu. - - + + Confirm invite Vahvista kutsu - + Do you really want to invite %1 (%2)? Haluatko kutsua %1 (%2)? @@ -227,12 +227,12 @@ Purettiin porttikielto käyttäjältä %1 - + Do you really want to start a private chat with %1? Haluatko luoda yksityisen keskustelun käyttäjän %1 kanssa? - + Cache migration failed! Välimuistin siirto epäonnistui! @@ -247,7 +247,7 @@ Levylläsi oleva välimuisti on uudempaa kuin mitä tämä Nhekon versio tukee. Päivitä tai poista välimuistisi. - + Failed to restore OLM account. Please login again. OLM-tilin palauttaminen epäonnistui. Ole hyvä ja kirjaudu sisään uudelleen. @@ -257,7 +257,7 @@ Tallennettujen tietojen palauttaminen epäonnistui. Ole hyvä ja kirjaudu sisään uudelleen. - + Failed to setup encryption keys. Server response: %1 %2. Please try again later. Salausavainten lähetys epäonnistui. Palvelimen vastaus: %1 %2. Ole hyvä ja yritä uudelleen myöhemmin. @@ -288,7 +288,7 @@ Huoneen luominen epäonnistui: %1 - + Failed to leave room: %1 Huoneesta poistuminen epäonnistui: %1 @@ -352,7 +352,7 @@ CrossSigningSecrets - + Decrypt secrets Salaisuuksien salauksen purku @@ -556,8 +556,8 @@ - Encrypted by an unverified device - Vahvistamattoman laitteen salaama + Encrypted by an unverified device or the key is from an untrusted source like the key backup. + @@ -889,7 +889,7 @@ Esimerkki: https://server.my:8787 MessageDelegate - + removed poistettu @@ -959,6 +959,11 @@ Esimerkki: https://server.my:8787 Negotiating call... Neuvotellaan puhelua.… + + + Allow them in + + MessageInput @@ -1385,10 +1390,23 @@ Esimerkki: https://server.my:8787 Peruuta muokkaus + + RoomDirectory + + + Explore Public Rooms + + + + + Search for public rooms + + + RoomInfo - + no version stored ei tallennettua versiota @@ -1396,7 +1414,7 @@ Esimerkki: https://server.my:8787 RoomList - + New tag Uusi tagi @@ -1491,7 +1509,7 @@ Esimerkki: https://server.my:8787 Huoneluettelo - + User settings Käyttäjäasetukset @@ -1581,7 +1599,7 @@ Esimerkki: https://server.my:8787 - + Anyone and guests Kaikki ja vieraat @@ -1596,7 +1614,17 @@ Esimerkki: https://server.my:8787 Kutsutut käyttäjät - + + By knocking + + + + + Restricted by membership in other rooms + + + + Encryption Salaus @@ -1642,12 +1670,12 @@ Esimerkki: https://server.my:8787 Huoneen versio - + Failed to enable encryption: %1 Salauksen aktivointi epäonnistui: %1 - + Select an avatar Valitse profiilikuva @@ -1750,22 +1778,22 @@ Esimerkki: https://server.my:8787 - Failed to update image pack: {} + Failed to update image pack: %1 - Failed to delete old image pack: {} + Failed to delete old image pack: %1 - Failed to open image: {} + Failed to open image: %1 - Failed to upload image: {} + Failed to upload image: %1 @@ -1821,7 +1849,7 @@ Esimerkki: https://server.my:8787 TimelineModel - + Message redaction failed: %1 Viestin muokkaus epäonnistui: %1 @@ -1871,7 +1899,17 @@ Esimerkki: https://server.my:8787 %1 teki tästä huoneesta liittymiskutsun vaativan. - + + %1 allowed to join this room by knocking. + + + + + %1 allowed members of the following rooms to automatically join this room: %2 + + + + %1 made the room open to guests. %1 teki huoneesta avoimen vieraille. @@ -1906,7 +1944,7 @@ Esimerkki: https://server.my:8787 %1 on muuttanut huoneen lupia. - + %1 was invited. &1 kutsuttiin. @@ -1921,12 +1959,17 @@ Esimerkki: https://server.my:8787 %1 muutti joitain tietoja profiilistaan. - + %1 joined. %1 liittyi. - + + %1 joined via authorisation from %2's server. + + + + %1 rejected their invite. %1 hylkäsi kutsunsa. @@ -1966,12 +2009,12 @@ Esimerkki: https://server.my:8787 %1 perui koputuksensa. - + You joined this room. Sinä liityit tähän huoneeseen. - + %1 has changed their avatar and changed their display name to %2. %1 vaihtoi avatariaan ja vaihtoi näyttönimekseen %2. @@ -1981,7 +2024,7 @@ Esimerkki: https://server.my:8787 %1 vaihtoi näyttönimekseen %2. - + Rejected the knock from %1. Hylättiin koputus käyttäjältä %1. @@ -2013,7 +2056,7 @@ Esimerkki: https://server.my:8787 Ei avointa huonetta - + %1 member(s) %1 jäsentä @@ -2041,7 +2084,7 @@ Esimerkki: https://server.my:8787 TimelineViewManager - + No encrypted private chat found with this user. Create an encrypted private chat with this user and try again. Salattua keskustelua ei löydetty tälle käyttäjälle. Luo salattu yksityiskeskustelu tämän käyttäjän kanssa ja yritä uudestaan. @@ -2179,8 +2222,8 @@ Esimerkki: https://server.my:8787 UserSettings - - + + Default Oletus @@ -2188,7 +2231,7 @@ Esimerkki: https://server.my:8787 UserSettingsPage - + Minimize to tray Pienennä ilmoitusalueelle @@ -2208,12 +2251,12 @@ Esimerkki: https://server.my:8787 Pyöreät avatarit - + profile: %1 profiili: %1 - + Default Oletus @@ -2430,7 +2473,17 @@ Tämä yleensä saa sovelluksen kuvakkeen liikkumaan jollain tapaa tehtäväpalk - + + Online Key Backup + + + + + Download message encryption keys from and upload to the encrypted online key backup. + + + + CACHED VÄLIMUISTISSA @@ -2440,7 +2493,7 @@ Tämä yleensä saa sovelluksen kuvakkeen liikkumaan jollain tapaa tehtäväpalk EI VÄLIMUISTISSA - + Scale factor Mittakerroin @@ -2535,12 +2588,12 @@ Tämä yleensä saa sovelluksen kuvakkeen liikkumaan jollain tapaa tehtäväpalk SALAUS - + GENERAL YLEISET ASETUKSET - + INTERFACE KÄYTTÖLIITTYMÄ @@ -2560,7 +2613,7 @@ Tämä yleensä saa sovelluksen kuvakkeen liikkumaan jollain tapaa tehtäväpalk Emojien fonttiperhe - + Master signing key Päätason allekirjoittava avain @@ -2610,7 +2663,7 @@ Tämä yleensä saa sovelluksen kuvakkeen liikkumaan jollain tapaa tehtäväpalk Kaikki Tiedostot (*) - + Open Sessions File Avaa Istuntoavaintiedosto diff --git a/resources/langs/nheko_fr.ts b/resources/langs/nheko_fr.ts index 9d11ce25..04027b85 100644 --- a/resources/langs/nheko_fr.ts +++ b/resources/langs/nheko_fr.ts @@ -131,17 +131,17 @@ - + Invited user: %1 %1 a été invité(e) - + Migrating the cache to the current version failed. This can have different reasons. Please open an issue and try to use an older version in the mean time. Alternatively you can try deleting the cache manually. La migration du cache vers la version actuelle a échoué. Cela peut arriver pour différentes raisons. Signalez le problème et essayez d'utiliser une ancienne version en attendant. Vous pouvez également supprimer le cache manuellement. - + Confirm join Confirmez la participation @@ -156,13 +156,13 @@ Salon %1 créé. - - + + Confirm invite Confirmer l'invitation - + Do you really want to invite %1 (%2)? Voulez-vous vraiment inviter %1 (%2) ? @@ -227,12 +227,12 @@ %1 n'est plus banni(e) - + Do you really want to start a private chat with %1? Voulez-vous vraimer commencer une discussion privée avec %1 ? - + Cache migration failed! Échec de la migration du cache ! @@ -247,7 +247,7 @@ Le cache sur votre disque est plus récent que cette version de Nheko ne supporte. Veuillez mettre à jour ou supprimer votre cache. - + Failed to restore OLM account. Please login again. Échec de la restauration du compte OLM. Veuillez vous reconnecter. @@ -257,7 +257,7 @@ Échec de la restauration des données sauvegardées. Veuillez vous reconnecter. - + Failed to setup encryption keys. Server response: %1 %2. Please try again later. Échec de la configuration des clés de chiffrement. Réponse du serveur : %1 %2. Veuillez réessayer plus tard. @@ -288,7 +288,7 @@ Échec de la création du salon : %1 - + Failed to leave room: %1 Impossible de quitter le salon : %1 @@ -352,7 +352,7 @@ CrossSigningSecrets - + Decrypt secrets Déchiffrer les secrets @@ -556,7 +556,7 @@ - Encrypted by an unverified device + Encrypted by an unverified device or the key is from an untrusted source like the key backup. @@ -889,7 +889,7 @@ Exemple : https ://monserveur.example.com :8787 MessageDelegate - + removed retiré @@ -959,6 +959,11 @@ Exemple : https ://monserveur.example.com :8787 Negotiating call... Négociation de l'appel… + + + Allow them in + + MessageInput @@ -1385,10 +1390,23 @@ Exemple : https ://monserveur.example.com :8787 Abandonner la modification + + RoomDirectory + + + Explore Public Rooms + + + + + Search for public rooms + + + RoomInfo - + no version stored pas de version enregistrée @@ -1396,7 +1414,7 @@ Exemple : https ://monserveur.example.com :8787 RoomList - + New tag @@ -1491,7 +1509,7 @@ Exemple : https ://monserveur.example.com :8787 Annuaire des salons - + User settings Paramètres utilisateur @@ -1581,7 +1599,7 @@ Exemple : https ://monserveur.example.com :8787 - + Anyone and guests Tous le monde et les invités @@ -1596,7 +1614,17 @@ Exemple : https ://monserveur.example.com :8787 Utilisateurs invités - + + By knocking + + + + + Restricted by membership in other rooms + + + + Encryption Chiffrement @@ -1642,12 +1670,12 @@ Exemple : https ://monserveur.example.com :8787 Version du salon - + Failed to enable encryption: %1 Échec de l'activation du chiffrement  : %1 - + Select an avatar Sélectionner un avatar @@ -1750,22 +1778,22 @@ Exemple : https ://monserveur.example.com :8787 - Failed to update image pack: {} + Failed to update image pack: %1 - Failed to delete old image pack: {} + Failed to delete old image pack: %1 - Failed to open image: {} + Failed to open image: %1 - Failed to upload image: {} + Failed to upload image: %1 @@ -1821,7 +1849,7 @@ Exemple : https ://monserveur.example.com :8787 TimelineModel - + Message redaction failed: %1 Échec de la suppression du message : %1 @@ -1871,7 +1899,17 @@ Exemple : https ://monserveur.example.com :8787 %1 a rendu le rendu le salon joignable uniquement sur invitation. - + + %1 allowed to join this room by knocking. + + + + + %1 allowed members of the following rooms to automatically join this room: %2 + + + + %1 made the room open to guests. %1 a rendu le salon ouvert aux invités. @@ -1906,7 +1944,7 @@ Exemple : https ://monserveur.example.com :8787 %1 a changé les permissions du salon. - + %1 was invited. %1 a été invité(e). @@ -1921,12 +1959,17 @@ Exemple : https ://monserveur.example.com :8787 %1 a changé ses informations de profil. - + %1 joined. %1 a rejoint le salon. - + + %1 joined via authorisation from %2's server. + + + + %1 rejected their invite. %1 a rejeté son invitation. @@ -1966,12 +2009,12 @@ Exemple : https ://monserveur.example.com :8787 %1 ne frappe plus au salon. - + You joined this room. Vous avez rejoint ce salon. - + %1 has changed their avatar and changed their display name to %2. @@ -1981,7 +2024,7 @@ Exemple : https ://monserveur.example.com :8787 - + Rejected the knock from %1. %1 a été rejeté après avoir frappé au salon. @@ -2013,7 +2056,7 @@ Exemple : https ://monserveur.example.com :8787 Aucun salon ouvert - + %1 member(s) %1 membre(s) @@ -2041,7 +2084,7 @@ Exemple : https ://monserveur.example.com :8787 TimelineViewManager - + No encrypted private chat found with this user. Create an encrypted private chat with this user and try again. Pas de discussion privée et chiffrée trouvée avec cet utilisateur. Créez-en une et réessayez. @@ -2179,8 +2222,8 @@ Exemple : https ://monserveur.example.com :8787 UserSettings - - + + Default Défaut @@ -2188,7 +2231,7 @@ Exemple : https ://monserveur.example.com :8787 UserSettingsPage - + Minimize to tray Réduire à la barre des tâches @@ -2208,12 +2251,12 @@ Exemple : https ://monserveur.example.com :8787 Avatars circulaires - + profile: %1 profil : %1 - + Default Défaut @@ -2432,7 +2475,17 @@ Cela met l'application en évidence dans la barre des tâches. - + + Online Key Backup + + + + + Download message encryption keys from and upload to the encrypted online key backup. + + + + CACHED EN CACHE @@ -2442,7 +2495,7 @@ Cela met l'application en évidence dans la barre des tâches.PAS DANS LE CACHE - + Scale factor Facteur d'échelle @@ -2537,12 +2590,12 @@ Cela met l'application en évidence dans la barre des tâches.CHIFFREMENT - + GENERAL GÉNÉRAL - + INTERFACE INTERFACE @@ -2562,7 +2615,7 @@ Cela met l'application en évidence dans la barre des tâches.Nom de Police Emoji - + Master signing key Clé de signature de l'utilisateur @@ -2612,7 +2665,7 @@ Cela met l'application en évidence dans la barre des tâches.Tous les types de fichiers (*) - + Open Sessions File Ouvrir fichier de sessions diff --git a/resources/langs/nheko_hu.ts b/resources/langs/nheko_hu.ts index a8e4d0e9..961fcffe 100644 --- a/resources/langs/nheko_hu.ts +++ b/resources/langs/nheko_hu.ts @@ -131,17 +131,17 @@ - + Invited user: %1 A felhasználó meg lett hívva: %1 - + Migrating the cache to the current version failed. This can have different reasons. Please open an issue and try to use an older version in the mean time. Alternatively you can try deleting the cache manually. A gyorsítótár átvitele a jelenlegi verzióhoz nem sikerült. Ennek több oka is lehet. Kérlek, írj egy hibajelentést és egyelőre próbálj meg egy régebbi verziót használni! Alternatív megoldásként megprobálhatod eltávolítani a gyorsítótárat kézzel. - + Confirm join Csatlakozás megerősítése @@ -156,13 +156,13 @@ A %1 nevű szoba létre lett hozva. - - + + Confirm invite Meghívás megerősítése - + Do you really want to invite %1 (%2)? Biztos, hogy meg akarod hívni a következő felhasználót: %1 (%2)? @@ -227,12 +227,12 @@ Kitiltás feloldva a felhasználónak: %1 - + Do you really want to start a private chat with %1? Biztosan privát csevegést akarsz indítani %1 felhasználóval? - + Cache migration failed! Gyorsítótár migráció nem sikerült! @@ -247,7 +247,7 @@ A lemezeden lévő gyorsítótár újabb, mint amit a Nheko jelenlegi verziója támogat. Kérlek, frissítsd vagy töröld a gyorsítótárat! - + Failed to restore OLM account. Please login again. Nem sikerült visszaállítani az OLM fiókot. Kérlek, jelentkezz be ismét! @@ -257,7 +257,7 @@ Nem sikerült visszaállítani a mentési adatot. Kérlek, jelentkezz be ismét! - + Failed to setup encryption keys. Server response: %1 %2. Please try again later. Nem sikerült beállítani a titkosítási kulcsokat. Válasz a szervertől: %1 %2. Kérlek, próbáld újra később! @@ -288,7 +288,7 @@ Nem sikerült létrehozni a szobát: %1 - + Failed to leave room: %1 Nem sikerült elhagyni a szobát: %1 @@ -352,7 +352,7 @@ CrossSigningSecrets - + Decrypt secrets Titkos tároló feloldása @@ -556,7 +556,7 @@ - Encrypted by an unverified device + Encrypted by an unverified device or the key is from an untrusted source like the key backup. @@ -889,7 +889,7 @@ Példa: https://szerver.em:8787 MessageDelegate - + Encryption enabled Titkosítás bekapcsolva @@ -944,7 +944,12 @@ Példa: https://szerver.em:8787 Hívás előkészítése… - + + Allow them in + + + + %1 answered the call. %1 fogadta a hívást. @@ -1385,10 +1390,23 @@ Példa: https://szerver.em:8787 Szerkesztés megszakítása + + RoomDirectory + + + Explore Public Rooms + + + + + Search for public rooms + + + RoomInfo - + no version stored nincs tárolva verzió @@ -1396,7 +1414,7 @@ Példa: https://szerver.em:8787 RoomList - + New tag @@ -1491,7 +1509,7 @@ Példa: https://szerver.em:8787 Szobák jegyzéke - + User settings Felhasználói beállítások @@ -1580,7 +1598,7 @@ Példa: https://szerver.em:8787 - + Anyone and guests Bárki és vendégek @@ -1595,7 +1613,17 @@ Példa: https://szerver.em:8787 Meghívott felhasználók - + + By knocking + + + + + Restricted by membership in other rooms + + + + Encryption Titkosítás @@ -1641,12 +1669,12 @@ Példa: https://szerver.em:8787 Szoba verziója - + Failed to enable encryption: %1 Nem sikerült a titkosítás aktiválása: %1 - + Select an avatar Profilkép kiválasztása @@ -1749,22 +1777,22 @@ Példa: https://szerver.em:8787 - Failed to update image pack: {} + Failed to update image pack: %1 - Failed to delete old image pack: {} + Failed to delete old image pack: %1 - Failed to open image: {} + Failed to open image: %1 - Failed to upload image: {} + Failed to upload image: %1 @@ -1820,7 +1848,7 @@ Példa: https://szerver.em:8787 TimelineModel - + Message redaction failed: %1 Az üzenet visszavonása nem sikerült: %1 @@ -1869,7 +1897,17 @@ Példa: https://szerver.em:8787 %1 beállította, hogy meghívással lehessen csatlakozni ehhez a szobához. - + + %1 allowed to join this room by knocking. + + + + + %1 allowed members of the following rooms to automatically join this room: %2 + + + + %1 made the room open to guests. %1 elérhetővé tette a szobát vendégeknek. @@ -1904,7 +1942,7 @@ Példa: https://szerver.em:8787 %1 megváltoztatta a szoba engedélyeit. - + %1 was invited. %1 meg lett hívva. @@ -1919,12 +1957,17 @@ Példa: https://szerver.em:8787 %1 megváltoztatta a profiladatait. - + %1 joined. %1 csatlakozott. - + + %1 joined via authorisation from %2's server. + + + + %1 rejected their invite. %1 elutasította a meghívását. @@ -1964,12 +2007,12 @@ Példa: https://szerver.em:8787 %1 visszavonta a kopogását. - + You joined this room. Csatlakoztál ehhez a szobához. - + %1 has changed their avatar and changed their display name to %2. @@ -1979,7 +2022,7 @@ Példa: https://szerver.em:8787 - + Rejected the knock from %1. Kopogás elutasítva tőle: %1. @@ -2011,7 +2054,7 @@ Példa: https://szerver.em:8787 Nincs nyitott szoba - + %1 member(s) %1 tag @@ -2039,7 +2082,7 @@ Példa: https://szerver.em:8787 TimelineViewManager - + No encrypted private chat found with this user. Create an encrypted private chat with this user and try again. Nem található titkosított privát csevegés ezzel a felhasználóval. Hozz létre egy titkosított privát csevegést vele, és próbáld újra! @@ -2177,8 +2220,8 @@ Példa: https://szerver.em:8787 UserSettings - - + + Default Alapértelmezett @@ -2186,7 +2229,7 @@ Példa: https://szerver.em:8787 UserSettingsPage - + Minimize to tray Kicsinyítés a tálcára @@ -2206,12 +2249,12 @@ Példa: https://szerver.em:8787 Kerekített profilképek - + profile: %1 profil: %1 - + Default Alapértelmezett @@ -2429,7 +2472,17 @@ Ettől általában animálttá válik az alkalmazásablakok listáján szereplő - + + Online Key Backup + + + + + Download message encryption keys from and upload to the encrypted online key backup. + + + + CACHED GYORSÍTÓTÁRAZVA @@ -2439,7 +2492,7 @@ Ettől általában animálttá válik az alkalmazásablakok listáján szereplő NINCS GYORSÍTÓTÁRAZVA - + Scale factor Nagyítási tényező @@ -2534,12 +2587,12 @@ Ettől általában animálttá válik az alkalmazásablakok listáján szereplő TITKOSÍTÁS - + GENERAL ÁLTALÁNOS - + INTERFACE FELÜLET @@ -2559,7 +2612,7 @@ Ettől általában animálttá válik az alkalmazásablakok listáján szereplő Hangulatjelek betűtípusa - + Master signing key Mester-aláírókulcs @@ -2609,7 +2662,7 @@ Ettől általában animálttá válik az alkalmazásablakok listáján szereplő Minden fájl (*) - + Open Sessions File Munkameneti fájl megnyitása diff --git a/resources/langs/nheko_it.ts b/resources/langs/nheko_it.ts index 31146e76..0b44bd80 100644 --- a/resources/langs/nheko_it.ts +++ b/resources/langs/nheko_it.ts @@ -131,17 +131,17 @@ - + Invited user: %1 Invitato utente: %1 - + Migrating the cache to the current version failed. This can have different reasons. Please open an issue and try to use an older version in the mean time. Alternatively you can try deleting the cache manually. Migrazione della cache alla versione corrente fallita. Questo può avere diverse cause. Per favore apri una issue e nel frattempo prova ad usare una versione più vecchia. In alternativa puoi provare a cancellare la cache manualmente. - + Confirm join Conferma collegamento @@ -156,13 +156,13 @@ Stanza %1 creata. - - + + Confirm invite Conferma Invito - + Do you really want to invite %1 (%2)? Vuoi davvero inviare %1 (%2)? @@ -227,12 +227,12 @@ Rimosso il ban dall'utente: %1 - + Do you really want to start a private chat with %1? Sei sicuro di voler avviare una chat privata con %1? - + Cache migration failed! Migrazione della cache fallita! @@ -247,7 +247,7 @@ La cache sul tuo disco è più nuova di quella supportata da questa versione di Nheko. Per favore aggiorna o pulisci la tua cache. - + Failed to restore OLM account. Please login again. Impossibile ripristinare l'account OLM. Per favore accedi nuovamente. @@ -257,7 +257,7 @@ Impossibile ripristinare i dati salvati. Per favore accedi nuovamente. - + Failed to setup encryption keys. Server response: %1 %2. Please try again later. Impossibile configurare le chiavi crittografiche. Risposta del server: %1 %2. Per favore riprova in seguito. @@ -288,7 +288,7 @@ Creazione della stanza fallita: %1 - + Failed to leave room: %1 Impossibile lasciare la stanza: %1 @@ -352,7 +352,7 @@ CrossSigningSecrets - + Decrypt secrets Decifra i segreti @@ -556,8 +556,8 @@ - Encrypted by an unverified device - Criptato da un dispositivo non verificato + Encrypted by an unverified device or the key is from an untrusted source like the key backup. + @@ -889,7 +889,7 @@ Esempio: https://server.mio:8787 MessageDelegate - + removed rimosso @@ -959,6 +959,11 @@ Esempio: https://server.mio:8787 Negotiating call... + + + Allow them in + + MessageInput @@ -1386,10 +1391,23 @@ Verificare %1 adesso? + + RoomDirectory + + + Explore Public Rooms + + + + + Search for public rooms + + + RoomInfo - + no version stored nessuna versione memorizzata @@ -1397,7 +1415,7 @@ Verificare %1 adesso? RoomList - + New tag @@ -1492,7 +1510,7 @@ Verificare %1 adesso? Elenco delle stanze - + User settings Impostazioni utente @@ -1582,7 +1600,7 @@ Verificare %1 adesso? - + Anyone and guests @@ -1597,7 +1615,17 @@ Verificare %1 adesso? - + + By knocking + + + + + Restricted by membership in other rooms + + + + Encryption @@ -1643,12 +1671,12 @@ Verificare %1 adesso? - + Failed to enable encryption: %1 Impossibile abilitare la crittografia: %1 - + Select an avatar Scegli un avatar @@ -1751,22 +1779,22 @@ Verificare %1 adesso? - Failed to update image pack: {} + Failed to update image pack: %1 - Failed to delete old image pack: {} + Failed to delete old image pack: %1 - Failed to open image: {} + Failed to open image: %1 - Failed to upload image: {} + Failed to upload image: %1 @@ -1822,7 +1850,7 @@ Verificare %1 adesso? TimelineModel - + Message redaction failed: %1 Oscuramento del messaggio fallito: %1 @@ -1872,7 +1900,17 @@ Verificare %1 adesso? %1 ha configurato questa stanza per richiedere un invito per entrare. - + + %1 allowed to join this room by knocking. + + + + + %1 allowed members of the following rooms to automatically join this room: %2 + + + + %1 made the room open to guests. %1 ha configurato questa stanza affinché sia aperta ai visitatori. @@ -1907,7 +1945,7 @@ Verificare %1 adesso? %1 ha cambiato i permessi della stanza. - + %1 was invited. %1 è stato invitato. @@ -1922,12 +1960,17 @@ Verificare %1 adesso? - + %1 joined. %1 è entrato. - + + %1 joined via authorisation from %2's server. + + + + %1 rejected their invite. %1 ha rifiutato il suo invito. @@ -1967,12 +2010,12 @@ Verificare %1 adesso? %1 ha oscurato la sua bussata. - + You joined this room. Sei entrato in questa stanza. - + %1 has changed their avatar and changed their display name to %2. @@ -1982,7 +2025,7 @@ Verificare %1 adesso? - + Rejected the knock from %1. Rifiutata la bussata di %1. @@ -2014,7 +2057,7 @@ Verificare %1 adesso? Nessuna stanza aperta - + %1 member(s) @@ -2042,7 +2085,7 @@ Verificare %1 adesso? TimelineViewManager - + No encrypted private chat found with this user. Create an encrypted private chat with this user and try again. @@ -2180,8 +2223,8 @@ Verificare %1 adesso? UserSettings - - + + Default @@ -2189,7 +2232,7 @@ Verificare %1 adesso? UserSettingsPage - + Minimize to tray Minimizza nella tray @@ -2209,12 +2252,12 @@ Verificare %1 adesso? Avatar Circolari - + profile: %1 - + Default @@ -2420,7 +2463,17 @@ This usually causes the application icon in the task bar to animate in some fash - + + Online Key Backup + + + + + Download message encryption keys from and upload to the encrypted online key backup. + + + + CACHED @@ -2430,7 +2483,7 @@ This usually causes the application icon in the task bar to animate in some fash - + Scale factor Fattore di scala @@ -2525,12 +2578,12 @@ This usually causes the application icon in the task bar to animate in some fash CRITTOGRAFIA - + GENERAL GENERALE - + INTERFACE INTERFACCIA @@ -2550,7 +2603,7 @@ This usually causes the application icon in the task bar to animate in some fash Famiglia dei caratteri delle Emoji - + Master signing key @@ -2600,7 +2653,7 @@ This usually causes the application icon in the task bar to animate in some fash Tutti i File (*) - + Open Sessions File Apri File delle Sessioni diff --git a/resources/langs/nheko_ja.ts b/resources/langs/nheko_ja.ts index 52260a23..9ca8f970 100644 --- a/resources/langs/nheko_ja.ts +++ b/resources/langs/nheko_ja.ts @@ -131,17 +131,17 @@ - + Invited user: %1 招待されたユーザー: %1 - + Migrating the cache to the current version failed. This can have different reasons. Please open an issue and try to use an older version in the mean time. Alternatively you can try deleting the cache manually. - + Confirm join @@ -156,13 +156,13 @@ - - + + Confirm invite - + Do you really want to invite %1 (%2)? @@ -227,12 +227,12 @@ 永久追放を解除されたユーザー: %1 - + Do you really want to start a private chat with %1? - + Cache migration failed! @@ -247,7 +247,7 @@ - + Failed to restore OLM account. Please login again. OLMアカウントを復元できませんでした。もう一度ログインして下さい。 @@ -257,7 +257,7 @@ セーブデータを復元できませんでした。もう一度ログインして下さい。 - + Failed to setup encryption keys. Server response: %1 %2. Please try again later. 暗号化鍵を設定できませんでした。サーバーの応答: %1 %2. 後でやり直して下さい。 @@ -288,7 +288,7 @@ 部屋を作成できませんでした: %1 - + Failed to leave room: %1 部屋から出られませんでした: %1 @@ -352,7 +352,7 @@ CrossSigningSecrets - + Decrypt secrets @@ -556,7 +556,7 @@ - Encrypted by an unverified device + Encrypted by an unverified device or the key is from an untrusted source like the key backup. @@ -885,7 +885,7 @@ Example: https://server.my:8787 MessageDelegate - + removed @@ -955,6 +955,11 @@ Example: https://server.my:8787 Negotiating call... + + + Allow them in + + MessageInput @@ -1381,10 +1386,23 @@ Example: https://server.my:8787 + + RoomDirectory + + + Explore Public Rooms + + + + + Search for public rooms + + + RoomInfo - + no version stored バージョンが保存されていません @@ -1392,7 +1410,7 @@ Example: https://server.my:8787 RoomList - + New tag @@ -1487,7 +1505,7 @@ Example: https://server.my:8787 部屋一覧 - + User settings ユーザー設定 @@ -1576,7 +1594,7 @@ Example: https://server.my:8787 - + Anyone and guests @@ -1591,7 +1609,17 @@ Example: https://server.my:8787 - + + By knocking + + + + + Restricted by membership in other rooms + + + + Encryption @@ -1637,12 +1665,12 @@ Example: https://server.my:8787 - + Failed to enable encryption: %1 暗号化を有効にできませんでした: %1 - + Select an avatar アバターを選択 @@ -1745,22 +1773,22 @@ Example: https://server.my:8787 - Failed to update image pack: {} + Failed to update image pack: %1 - Failed to delete old image pack: {} + Failed to delete old image pack: %1 - Failed to open image: {} + Failed to open image: %1 - Failed to upload image: {} + Failed to upload image: %1 @@ -1816,7 +1844,7 @@ Example: https://server.my:8787 TimelineModel - + Message redaction failed: %1 メッセージを編集できませんでした: %1 @@ -1865,7 +1893,17 @@ Example: https://server.my:8787 - + + %1 allowed to join this room by knocking. + + + + + %1 allowed members of the following rooms to automatically join this room: %2 + + + + %1 made the room open to guests. @@ -1900,7 +1938,7 @@ Example: https://server.my:8787 - + %1 was invited. %1が招待されました。 @@ -1915,12 +1953,17 @@ Example: https://server.my:8787 - + %1 joined. %1が参加しました。 - + + %1 joined via authorisation from %2's server. + + + + %1 rejected their invite. %1が招待を拒否しました。 @@ -1960,12 +2003,12 @@ Example: https://server.my:8787 %1がノックを編集しました。 - + You joined this room. - + %1 has changed their avatar and changed their display name to %2. @@ -1975,7 +2018,7 @@ Example: https://server.my:8787 - + Rejected the knock from %1. %1からのノックを拒否しました。 @@ -2007,7 +2050,7 @@ Example: https://server.my:8787 部屋が開いていません - + %1 member(s) @@ -2035,7 +2078,7 @@ Example: https://server.my:8787 TimelineViewManager - + No encrypted private chat found with this user. Create an encrypted private chat with this user and try again. @@ -2173,8 +2216,8 @@ Example: https://server.my:8787 UserSettings - - + + Default @@ -2182,7 +2225,7 @@ Example: https://server.my:8787 UserSettingsPage - + Minimize to tray トレイへ最小化 @@ -2202,12 +2245,12 @@ Example: https://server.my:8787 円形アバター - + profile: %1 - + Default @@ -2413,7 +2456,17 @@ This usually causes the application icon in the task bar to animate in some fash - + + Online Key Backup + + + + + Download message encryption keys from and upload to the encrypted online key backup. + + + + CACHED @@ -2423,7 +2476,7 @@ This usually causes the application icon in the task bar to animate in some fash - + Scale factor 尺度係数 @@ -2518,12 +2571,12 @@ This usually causes the application icon in the task bar to animate in some fash 暗号化 - + GENERAL 全般 - + INTERFACE @@ -2543,7 +2596,7 @@ This usually causes the application icon in the task bar to animate in some fash - + Master signing key @@ -2593,7 +2646,7 @@ This usually causes the application icon in the task bar to animate in some fash 全てのファイル (*) - + Open Sessions File セッションファイルを開く diff --git a/resources/langs/nheko_ml.ts b/resources/langs/nheko_ml.ts index 497491ca..115da2bd 100644 --- a/resources/langs/nheko_ml.ts +++ b/resources/langs/nheko_ml.ts @@ -131,17 +131,17 @@ - + Invited user: %1 ക്ഷണിച്ച ഉപയോക്താവ്:% 1 - + Migrating the cache to the current version failed. This can have different reasons. Please open an issue and try to use an older version in the mean time. Alternatively you can try deleting the cache manually. - + Confirm join @@ -156,13 +156,13 @@ %1 മുറി സൃഷ്ടിച്ചു - - + + Confirm invite ക്ഷണം ഉറപ്പാക്കു - + Do you really want to invite %1 (%2)? @@ -227,12 +227,12 @@ - + Do you really want to start a private chat with %1? - + Cache migration failed! @@ -247,7 +247,7 @@ - + Failed to restore OLM account. Please login again. @@ -257,7 +257,7 @@ - + Failed to setup encryption keys. Server response: %1 %2. Please try again later. @@ -288,7 +288,7 @@ - + Failed to leave room: %1 @@ -352,7 +352,7 @@ CrossSigningSecrets - + Decrypt secrets @@ -556,7 +556,7 @@ - Encrypted by an unverified device + Encrypted by an unverified device or the key is from an untrusted source like the key backup. @@ -885,7 +885,7 @@ Example: https://server.my:8787 MessageDelegate - + Encryption enabled @@ -940,7 +940,12 @@ Example: https://server.my:8787 - + + Allow them in + + + + %1 answered the call. @@ -1381,10 +1386,23 @@ Example: https://server.my:8787 + + RoomDirectory + + + Explore Public Rooms + + + + + Search for public rooms + + + RoomInfo - + no version stored @@ -1392,7 +1410,7 @@ Example: https://server.my:8787 RoomList - + New tag @@ -1487,7 +1505,7 @@ Example: https://server.my:8787 - + User settings @@ -1577,7 +1595,7 @@ Example: https://server.my:8787 - + Anyone and guests @@ -1592,7 +1610,17 @@ Example: https://server.my:8787 - + + By knocking + + + + + Restricted by membership in other rooms + + + + Encryption @@ -1638,12 +1666,12 @@ Example: https://server.my:8787 - + Failed to enable encryption: %1 - + Select an avatar @@ -1746,22 +1774,22 @@ Example: https://server.my:8787 - Failed to update image pack: {} + Failed to update image pack: %1 - Failed to delete old image pack: {} + Failed to delete old image pack: %1 - Failed to open image: {} + Failed to open image: %1 - Failed to upload image: {} + Failed to upload image: %1 @@ -1817,7 +1845,7 @@ Example: https://server.my:8787 TimelineModel - + Message redaction failed: %1 @@ -1867,7 +1895,17 @@ Example: https://server.my:8787 - + + %1 allowed to join this room by knocking. + + + + + %1 allowed members of the following rooms to automatically join this room: %2 + + + + %1 made the room open to guests. @@ -1902,7 +1940,7 @@ Example: https://server.my:8787 - + %1 was invited. @@ -1917,12 +1955,17 @@ Example: https://server.my:8787 - + %1 joined. - + + %1 joined via authorisation from %2's server. + + + + %1 rejected their invite. @@ -1962,12 +2005,12 @@ Example: https://server.my:8787 - + You joined this room. നിങ്ങൾ ഈ മുറിയിൽ ചേർന്നു. - + %1 has changed their avatar and changed their display name to %2. @@ -1977,7 +2020,7 @@ Example: https://server.my:8787 - + Rejected the knock from %1. @@ -2009,7 +2052,7 @@ Example: https://server.my:8787 - + %1 member(s) @@ -2037,7 +2080,7 @@ Example: https://server.my:8787 TimelineViewManager - + No encrypted private chat found with this user. Create an encrypted private chat with this user and try again. @@ -2175,8 +2218,8 @@ Example: https://server.my:8787 UserSettings - - + + Default @@ -2184,7 +2227,7 @@ Example: https://server.my:8787 UserSettingsPage - + Minimize to tray @@ -2204,12 +2247,12 @@ Example: https://server.my:8787 - + profile: %1 - + Default @@ -2415,7 +2458,17 @@ This usually causes the application icon in the task bar to animate in some fash - + + Online Key Backup + + + + + Download message encryption keys from and upload to the encrypted online key backup. + + + + CACHED @@ -2425,7 +2478,7 @@ This usually causes the application icon in the task bar to animate in some fash - + Scale factor @@ -2520,12 +2573,12 @@ This usually causes the application icon in the task bar to animate in some fash - + GENERAL - + INTERFACE @@ -2545,7 +2598,7 @@ This usually causes the application icon in the task bar to animate in some fash - + Master signing key @@ -2595,7 +2648,7 @@ This usually causes the application icon in the task bar to animate in some fash - + Open Sessions File diff --git a/resources/langs/nheko_nl.ts b/resources/langs/nheko_nl.ts index 93236498..d8aed2a1 100644 --- a/resources/langs/nheko_nl.ts +++ b/resources/langs/nheko_nl.ts @@ -131,17 +131,17 @@ - + Invited user: %1 Gebruiker uitgenodigd: %1 - + Migrating the cache to the current version failed. This can have different reasons. Please open an issue and try to use an older version in the mean time. Alternatively you can try deleting the cache manually. - + Confirm join @@ -156,13 +156,13 @@ Kamer %1 gecreëerd. - - + + Confirm invite - + Do you really want to invite %1 (%2)? @@ -227,12 +227,12 @@ - + Do you really want to start a private chat with %1? - + Cache migration failed! @@ -247,7 +247,7 @@ - + Failed to restore OLM account. Please login again. @@ -257,7 +257,7 @@ - + Failed to setup encryption keys. Server response: %1 %2. Please try again later. @@ -288,7 +288,7 @@ - + Failed to leave room: %1 @@ -352,7 +352,7 @@ CrossSigningSecrets - + Decrypt secrets @@ -556,7 +556,7 @@ - Encrypted by an unverified device + Encrypted by an unverified device or the key is from an untrusted source like the key backup. @@ -885,7 +885,7 @@ Example: https://server.my:8787 MessageDelegate - + removed @@ -955,6 +955,11 @@ Example: https://server.my:8787 Negotiating call... + + + Allow them in + + MessageInput @@ -1381,10 +1386,23 @@ Example: https://server.my:8787 + + RoomDirectory + + + Explore Public Rooms + + + + + Search for public rooms + + + RoomInfo - + no version stored @@ -1392,7 +1410,7 @@ Example: https://server.my:8787 RoomList - + New tag @@ -1487,7 +1505,7 @@ Example: https://server.my:8787 - + User settings @@ -1577,7 +1595,7 @@ Example: https://server.my:8787 - + Anyone and guests @@ -1592,7 +1610,17 @@ Example: https://server.my:8787 - + + By knocking + + + + + Restricted by membership in other rooms + + + + Encryption @@ -1638,12 +1666,12 @@ Example: https://server.my:8787 - + Failed to enable encryption: %1 - + Select an avatar @@ -1746,22 +1774,22 @@ Example: https://server.my:8787 - Failed to update image pack: {} + Failed to update image pack: %1 - Failed to delete old image pack: {} + Failed to delete old image pack: %1 - Failed to open image: {} + Failed to open image: %1 - Failed to upload image: {} + Failed to upload image: %1 @@ -1817,7 +1845,7 @@ Example: https://server.my:8787 TimelineModel - + Message redaction failed: %1 @@ -1867,7 +1895,17 @@ Example: https://server.my:8787 - + + %1 allowed to join this room by knocking. + + + + + %1 allowed members of the following rooms to automatically join this room: %2 + + + + %1 made the room open to guests. @@ -1902,7 +1940,7 @@ Example: https://server.my:8787 - + %1 was invited. @@ -1917,12 +1955,17 @@ Example: https://server.my:8787 - + %1 joined. - + + %1 joined via authorisation from %2's server. + + + + %1 rejected their invite. @@ -1962,12 +2005,12 @@ Example: https://server.my:8787 - + You joined this room. Je bent lid geworden van deze kamer. - + %1 has changed their avatar and changed their display name to %2. @@ -1977,7 +2020,7 @@ Example: https://server.my:8787 - + Rejected the knock from %1. @@ -2009,7 +2052,7 @@ Example: https://server.my:8787 - + %1 member(s) @@ -2037,7 +2080,7 @@ Example: https://server.my:8787 TimelineViewManager - + No encrypted private chat found with this user. Create an encrypted private chat with this user and try again. @@ -2175,8 +2218,8 @@ Example: https://server.my:8787 UserSettings - - + + Default @@ -2184,7 +2227,7 @@ Example: https://server.my:8787 UserSettingsPage - + Minimize to tray Minimaliseren naar systeemvak @@ -2204,12 +2247,12 @@ Example: https://server.my:8787 - + profile: %1 - + Default @@ -2415,7 +2458,17 @@ This usually causes the application icon in the task bar to animate in some fash - + + Online Key Backup + + + + + Download message encryption keys from and upload to the encrypted online key backup. + + + + CACHED @@ -2425,7 +2478,7 @@ This usually causes the application icon in the task bar to animate in some fash - + Scale factor @@ -2520,12 +2573,12 @@ This usually causes the application icon in the task bar to animate in some fash - + GENERAL ALGEMEEN - + INTERFACE @@ -2545,7 +2598,7 @@ This usually causes the application icon in the task bar to animate in some fash - + Master signing key @@ -2595,7 +2648,7 @@ This usually causes the application icon in the task bar to animate in some fash Alle bestanden (*) - + Open Sessions File diff --git a/resources/langs/nheko_pl.ts b/resources/langs/nheko_pl.ts index 33a44698..2315503d 100644 --- a/resources/langs/nheko_pl.ts +++ b/resources/langs/nheko_pl.ts @@ -131,17 +131,17 @@ - + Invited user: %1 - + Migrating the cache to the current version failed. This can have different reasons. Please open an issue and try to use an older version in the mean time. Alternatively you can try deleting the cache manually. - + Confirm join @@ -156,13 +156,13 @@ - - + + Confirm invite - + Do you really want to invite %1 (%2)? Czy na pewno chcesz zaprosić %1 (%2)? @@ -227,12 +227,12 @@ Odblokowano użytkownika: %1 - + Do you really want to start a private chat with %1? - + Cache migration failed! Nie udało się przenieść pamięci podręcznej! @@ -247,7 +247,7 @@ Pamięć podręczna na Twoim dysku jest nowsza niż wersja obsługiwana przez Nheko. Zaktualizuj lub wyczyść pamięć podręczną. - + Failed to restore OLM account. Please login again. Nie udało się przywrócić konta OLM. Spróbuj zalogować się ponownie. @@ -257,7 +257,7 @@ Nie udało się przywrócić zapisanych danych. Spróbuj zalogować się ponownie. - + Failed to setup encryption keys. Server response: %1 %2. Please try again later. Nie udało się ustawić kluczy szyfrujących. Odpowiedź serwera: %1 %2. Spróbuj ponownie później. @@ -288,7 +288,7 @@ Tworzenie pokoju nie powiodło się: %1 - + Failed to leave room: %1 Nie udało się opuścić pokoju: %1 @@ -352,7 +352,7 @@ CrossSigningSecrets - + Decrypt secrets @@ -556,7 +556,7 @@ - Encrypted by an unverified device + Encrypted by an unverified device or the key is from an untrusted source like the key backup. @@ -887,7 +887,7 @@ Example: https://server.my:8787 MessageDelegate - + removed @@ -957,6 +957,11 @@ Example: https://server.my:8787 Negotiating call... Negocjowanie połączenia… + + + Allow them in + + MessageInput @@ -1383,10 +1388,23 @@ Example: https://server.my:8787 + + RoomDirectory + + + Explore Public Rooms + + + + + Search for public rooms + + + RoomInfo - + no version stored @@ -1394,7 +1412,7 @@ Example: https://server.my:8787 RoomList - + New tag @@ -1489,7 +1507,7 @@ Example: https://server.my:8787 Katalog pokojów - + User settings Ustawienia użytkownika @@ -1580,7 +1598,7 @@ Example: https://server.my:8787 - + Anyone and guests @@ -1595,7 +1613,17 @@ Example: https://server.my:8787 - + + By knocking + + + + + Restricted by membership in other rooms + + + + Encryption @@ -1641,12 +1669,12 @@ Example: https://server.my:8787 - + Failed to enable encryption: %1 Nie udało się włączyć szyfrowania: %1 - + Select an avatar Wybierz awatar @@ -1749,22 +1777,22 @@ Example: https://server.my:8787 - Failed to update image pack: {} + Failed to update image pack: %1 - Failed to delete old image pack: {} + Failed to delete old image pack: %1 - Failed to open image: {} + Failed to open image: %1 - Failed to upload image: {} + Failed to upload image: %1 @@ -1820,7 +1848,7 @@ Example: https://server.my:8787 TimelineModel - + Message redaction failed: %1 Redagowanie wiadomości nie powiodło się: %1 @@ -1871,7 +1899,17 @@ Example: https://server.my:8787 - + + %1 allowed to join this room by knocking. + + + + + %1 allowed members of the following rooms to automatically join this room: %2 + + + + %1 made the room open to guests. @@ -1906,7 +1944,7 @@ Example: https://server.my:8787 - + %1 was invited. @@ -1921,12 +1959,17 @@ Example: https://server.my:8787 - + %1 joined. - + + %1 joined via authorisation from %2's server. + + + + %1 rejected their invite. @@ -1966,12 +2009,12 @@ Example: https://server.my:8787 - + You joined this room. Dołączyłeś(-łaś) do tego pokoju. - + %1 has changed their avatar and changed their display name to %2. @@ -1981,7 +2024,7 @@ Example: https://server.my:8787 - + Rejected the knock from %1. @@ -2013,7 +2056,7 @@ Example: https://server.my:8787 - + %1 member(s) @@ -2041,7 +2084,7 @@ Example: https://server.my:8787 TimelineViewManager - + No encrypted private chat found with this user. Create an encrypted private chat with this user and try again. @@ -2179,8 +2222,8 @@ Example: https://server.my:8787 UserSettings - - + + Default @@ -2188,7 +2231,7 @@ Example: https://server.my:8787 UserSettingsPage - + Minimize to tray Zminimalizuj do paska zadań @@ -2208,12 +2251,12 @@ Example: https://server.my:8787 - + profile: %1 - + Default @@ -2419,7 +2462,17 @@ This usually causes the application icon in the task bar to animate in some fash - + + Online Key Backup + + + + + Download message encryption keys from and upload to the encrypted online key backup. + + + + CACHED @@ -2429,7 +2482,7 @@ This usually causes the application icon in the task bar to animate in some fash - + Scale factor @@ -2524,12 +2577,12 @@ This usually causes the application icon in the task bar to animate in some fash SZYFROWANIE - + GENERAL OGÓLNE - + INTERFACE @@ -2549,7 +2602,7 @@ This usually causes the application icon in the task bar to animate in some fash - + Master signing key @@ -2599,7 +2652,7 @@ This usually causes the application icon in the task bar to animate in some fash Wszystkie pliki (*) - + Open Sessions File diff --git a/resources/langs/nheko_pt_BR.ts b/resources/langs/nheko_pt_BR.ts index 0ea650d1..931c8da7 100644 --- a/resources/langs/nheko_pt_BR.ts +++ b/resources/langs/nheko_pt_BR.ts @@ -131,17 +131,17 @@ - + Invited user: %1 Usuário convidado: %1 - + Migrating the cache to the current version failed. This can have different reasons. Please open an issue and try to use an older version in the mean time. Alternatively you can try deleting the cache manually. - + Confirm join Confirmar entrada @@ -156,13 +156,13 @@ Sala %1 criada. - - + + Confirm invite Confirmar convite - + Do you really want to invite %1 (%2)? @@ -227,12 +227,12 @@ Usuário desbanido: %1 - + Do you really want to start a private chat with %1? - + Cache migration failed! Migração do cache falhou! @@ -247,7 +247,7 @@ - + Failed to restore OLM account. Please login again. Falha ao restaurar conta OLM. Por favor faça login novamente. @@ -257,7 +257,7 @@ Falha ao restaurar dados salvos. Por favor faça login novamente. - + Failed to setup encryption keys. Server response: %1 %2. Please try again later. @@ -288,7 +288,7 @@ - + Failed to leave room: %1 @@ -352,7 +352,7 @@ CrossSigningSecrets - + Decrypt secrets @@ -556,7 +556,7 @@ - Encrypted by an unverified device + Encrypted by an unverified device or the key is from an untrusted source like the key backup. @@ -885,7 +885,7 @@ Example: https://server.my:8787 MessageDelegate - + Encryption enabled @@ -940,7 +940,12 @@ Example: https://server.my:8787 - + + Allow them in + + + + %1 answered the call. @@ -1381,10 +1386,23 @@ Example: https://server.my:8787 + + RoomDirectory + + + Explore Public Rooms + + + + + Search for public rooms + + + RoomInfo - + no version stored @@ -1392,7 +1410,7 @@ Example: https://server.my:8787 RoomList - + New tag @@ -1487,7 +1505,7 @@ Example: https://server.my:8787 - + User settings @@ -1577,7 +1595,7 @@ Example: https://server.my:8787 - + Anyone and guests @@ -1592,7 +1610,17 @@ Example: https://server.my:8787 - + + By knocking + + + + + Restricted by membership in other rooms + + + + Encryption @@ -1638,12 +1666,12 @@ Example: https://server.my:8787 - + Failed to enable encryption: %1 - + Select an avatar @@ -1746,22 +1774,22 @@ Example: https://server.my:8787 - Failed to update image pack: {} + Failed to update image pack: %1 - Failed to delete old image pack: {} + Failed to delete old image pack: %1 - Failed to open image: {} + Failed to open image: %1 - Failed to upload image: {} + Failed to upload image: %1 @@ -1817,7 +1845,7 @@ Example: https://server.my:8787 TimelineModel - + Message redaction failed: %1 @@ -1867,7 +1895,17 @@ Example: https://server.my:8787 - + + %1 allowed to join this room by knocking. + + + + + %1 allowed members of the following rooms to automatically join this room: %2 + + + + %1 made the room open to guests. @@ -1902,7 +1940,7 @@ Example: https://server.my:8787 - + %1 was invited. @@ -1917,12 +1955,17 @@ Example: https://server.my:8787 - + %1 joined. - + + %1 joined via authorisation from %2's server. + + + + %1 rejected their invite. @@ -1962,12 +2005,12 @@ Example: https://server.my:8787 - + You joined this room. Você entrou nessa sala. - + %1 has changed their avatar and changed their display name to %2. @@ -1977,7 +2020,7 @@ Example: https://server.my:8787 - + Rejected the knock from %1. @@ -2009,7 +2052,7 @@ Example: https://server.my:8787 - + %1 member(s) @@ -2037,7 +2080,7 @@ Example: https://server.my:8787 TimelineViewManager - + No encrypted private chat found with this user. Create an encrypted private chat with this user and try again. @@ -2175,8 +2218,8 @@ Example: https://server.my:8787 UserSettings - - + + Default @@ -2184,7 +2227,7 @@ Example: https://server.my:8787 UserSettingsPage - + Minimize to tray @@ -2204,12 +2247,12 @@ Example: https://server.my:8787 - + profile: %1 - + Default @@ -2415,7 +2458,17 @@ This usually causes the application icon in the task bar to animate in some fash - + + Online Key Backup + + + + + Download message encryption keys from and upload to the encrypted online key backup. + + + + CACHED @@ -2425,7 +2478,7 @@ This usually causes the application icon in the task bar to animate in some fash - + Scale factor @@ -2520,12 +2573,12 @@ This usually causes the application icon in the task bar to animate in some fash - + GENERAL - + INTERFACE @@ -2545,7 +2598,7 @@ This usually causes the application icon in the task bar to animate in some fash - + Master signing key @@ -2595,7 +2648,7 @@ This usually causes the application icon in the task bar to animate in some fash - + Open Sessions File diff --git a/resources/langs/nheko_pt_PT.ts b/resources/langs/nheko_pt_PT.ts index ef7e46d1..eb8467b3 100644 --- a/resources/langs/nheko_pt_PT.ts +++ b/resources/langs/nheko_pt_PT.ts @@ -131,17 +131,17 @@ - + Invited user: %1 - + Migrating the cache to the current version failed. This can have different reasons. Please open an issue and try to use an older version in the mean time. Alternatively you can try deleting the cache manually. - + Confirm join @@ -156,13 +156,13 @@ - - + + Confirm invite - + Do you really want to invite %1 (%2)? @@ -227,12 +227,12 @@ - + Do you really want to start a private chat with %1? - + Cache migration failed! @@ -247,7 +247,7 @@ - + Failed to restore OLM account. Please login again. @@ -257,7 +257,7 @@ - + Failed to setup encryption keys. Server response: %1 %2. Please try again later. @@ -288,7 +288,7 @@ - + Failed to leave room: %1 @@ -352,7 +352,7 @@ CrossSigningSecrets - + Decrypt secrets @@ -556,7 +556,7 @@ - Encrypted by an unverified device + Encrypted by an unverified device or the key is from an untrusted source like the key backup. @@ -885,7 +885,7 @@ Example: https://server.my:8787 MessageDelegate - + Encryption enabled @@ -940,7 +940,12 @@ Example: https://server.my:8787 - + + Allow them in + + + + %1 answered the call. @@ -1381,10 +1386,23 @@ Example: https://server.my:8787 + + RoomDirectory + + + Explore Public Rooms + + + + + Search for public rooms + + + RoomInfo - + no version stored @@ -1392,7 +1410,7 @@ Example: https://server.my:8787 RoomList - + New tag @@ -1487,7 +1505,7 @@ Example: https://server.my:8787 - + User settings @@ -1577,7 +1595,7 @@ Example: https://server.my:8787 - + Anyone and guests @@ -1592,7 +1610,17 @@ Example: https://server.my:8787 - + + By knocking + + + + + Restricted by membership in other rooms + + + + Encryption @@ -1638,12 +1666,12 @@ Example: https://server.my:8787 - + Failed to enable encryption: %1 - + Select an avatar @@ -1746,22 +1774,22 @@ Example: https://server.my:8787 - Failed to update image pack: {} + Failed to update image pack: %1 - Failed to delete old image pack: {} + Failed to delete old image pack: %1 - Failed to open image: {} + Failed to open image: %1 - Failed to upload image: {} + Failed to upload image: %1 @@ -1817,7 +1845,7 @@ Example: https://server.my:8787 TimelineModel - + Message redaction failed: %1 @@ -1867,7 +1895,17 @@ Example: https://server.my:8787 - + + %1 allowed to join this room by knocking. + + + + + %1 allowed members of the following rooms to automatically join this room: %2 + + + + %1 made the room open to guests. @@ -1902,7 +1940,7 @@ Example: https://server.my:8787 - + %1 was invited. @@ -1917,12 +1955,17 @@ Example: https://server.my:8787 - + %1 joined. - + + %1 joined via authorisation from %2's server. + + + + %1 rejected their invite. @@ -1962,12 +2005,12 @@ Example: https://server.my:8787 - + You joined this room. - + %1 has changed their avatar and changed their display name to %2. @@ -1977,7 +2020,7 @@ Example: https://server.my:8787 - + Rejected the knock from %1. @@ -2009,7 +2052,7 @@ Example: https://server.my:8787 - + %1 member(s) @@ -2037,7 +2080,7 @@ Example: https://server.my:8787 TimelineViewManager - + No encrypted private chat found with this user. Create an encrypted private chat with this user and try again. @@ -2175,8 +2218,8 @@ Example: https://server.my:8787 UserSettings - - + + Default @@ -2184,7 +2227,7 @@ Example: https://server.my:8787 UserSettingsPage - + Minimize to tray @@ -2204,12 +2247,12 @@ Example: https://server.my:8787 - + profile: %1 - + Default @@ -2415,7 +2458,17 @@ This usually causes the application icon in the task bar to animate in some fash - + + Online Key Backup + + + + + Download message encryption keys from and upload to the encrypted online key backup. + + + + CACHED @@ -2425,7 +2478,7 @@ This usually causes the application icon in the task bar to animate in some fash - + Scale factor @@ -2520,12 +2573,12 @@ This usually causes the application icon in the task bar to animate in some fash - + GENERAL - + INTERFACE @@ -2545,7 +2598,7 @@ This usually causes the application icon in the task bar to animate in some fash - + Master signing key @@ -2595,7 +2648,7 @@ This usually causes the application icon in the task bar to animate in some fash - + Open Sessions File diff --git a/resources/langs/nheko_ro.ts b/resources/langs/nheko_ro.ts index c05b5565..85514304 100644 --- a/resources/langs/nheko_ro.ts +++ b/resources/langs/nheko_ro.ts @@ -131,17 +131,17 @@ - + Invited user: %1 Utilizator invitat: %1 - + Migrating the cache to the current version failed. This can have different reasons. Please open an issue and try to use an older version in the mean time. Alternatively you can try deleting the cache manually. Nu s-a putut muta cache-ul pe versiunea curentă. Acest lucru poate avea diferite cauze. Vă rugăm să deschideți un issue și încercați să folosiți o versiune mai veche între timp. O altă opțiune ar fi să încercați să ștergeți cache-ul manual. - + Confirm join @@ -156,13 +156,13 @@ Camera %1 a fost creată. - - + + Confirm invite - + Do you really want to invite %1 (%2)? @@ -227,12 +227,12 @@ Utilizator dezinterzis: %1 - + Do you really want to start a private chat with %1? - + Cache migration failed! Nu s-a putut migra cache-ul! @@ -247,7 +247,7 @@ Cache-ul de pe disc este mai nou decât versiunea pe care Nheko o suportă. Vă rugăm actualizați sau ștergeți cache-ul. - + Failed to restore OLM account. Please login again. Nu s-a putut restabili contul OLM. Vă rugăm să vă reconectați. @@ -257,7 +257,7 @@ Nu s-au putut restabili datele salvate. Vă rugăm să vă reconectați. - + Failed to setup encryption keys. Server response: %1 %2. Please try again later. Nu s-au putut stabili cheile. Răspunsul serverului: %1 %2. Vă rugăm încercați mai târziu. @@ -288,7 +288,7 @@ Nu s-a putut crea camera: %1 - + Failed to leave room: %1 Nu s-a putut părăsi camera: %1 @@ -352,7 +352,7 @@ CrossSigningSecrets - + Decrypt secrets @@ -556,7 +556,7 @@ - Encrypted by an unverified device + Encrypted by an unverified device or the key is from an untrusted source like the key backup. @@ -889,7 +889,7 @@ Exemplu: https://serverul.meu:8787 MessageDelegate - + removed @@ -959,6 +959,11 @@ Exemplu: https://serverul.meu:8787 Negotiating call... + + + Allow them in + + MessageInput @@ -1385,10 +1390,23 @@ Exemplu: https://serverul.meu:8787 + + RoomDirectory + + + Explore Public Rooms + + + + + Search for public rooms + + + RoomInfo - + no version stored nicio versiune stocată @@ -1396,7 +1414,7 @@ Exemplu: https://serverul.meu:8787 RoomList - + New tag @@ -1491,7 +1509,7 @@ Exemplu: https://serverul.meu:8787 Registru de camere - + User settings Setări utilizator @@ -1582,7 +1600,7 @@ Exemplu: https://serverul.meu:8787 - + Anyone and guests @@ -1597,7 +1615,17 @@ Exemplu: https://serverul.meu:8787 - + + By knocking + + + + + Restricted by membership in other rooms + + + + Encryption @@ -1643,12 +1671,12 @@ Exemplu: https://serverul.meu:8787 - + Failed to enable encryption: %1 Nu s-a putut activa criptarea: %1 - + Select an avatar Selectează un avatar @@ -1751,22 +1779,22 @@ Exemplu: https://serverul.meu:8787 - Failed to update image pack: {} + Failed to update image pack: %1 - Failed to delete old image pack: {} + Failed to delete old image pack: %1 - Failed to open image: {} + Failed to open image: %1 - Failed to upload image: {} + Failed to upload image: %1 @@ -1822,7 +1850,7 @@ Exemplu: https://serverul.meu:8787 TimelineModel - + Message redaction failed: %1 Redactare mesaj eșuată: %1 @@ -1873,7 +1901,17 @@ Exemplu: https://serverul.meu:8787 %1 a făcut ca această cameră să necesite o invitație pentru alăturare. - + + %1 allowed to join this room by knocking. + + + + + %1 allowed members of the following rooms to automatically join this room: %2 + + + + %1 made the room open to guests. %1 a deschis camera pentru vizitatori. @@ -1908,7 +1946,7 @@ Exemplu: https://serverul.meu:8787 %1 a modificat permisiunile camerei. - + %1 was invited. %1 a fost invitat(ă). @@ -1923,12 +1961,17 @@ Exemplu: https://serverul.meu:8787 %1 și-a schimbat niște informații de pe profil. - + %1 joined. %1 s-a alăturat. - + + %1 joined via authorisation from %2's server. + + + + %1 rejected their invite. %1 a respins invitația. @@ -1968,12 +2011,12 @@ Exemplu: https://serverul.meu:8787 %1 și-a redactat ciocănitul. - + You joined this room. Te-ai alăturat camerei. - + %1 has changed their avatar and changed their display name to %2. @@ -1983,7 +2026,7 @@ Exemplu: https://serverul.meu:8787 - + Rejected the knock from %1. Ciocănit refuzat de la %1. @@ -2015,7 +2058,7 @@ Exemplu: https://serverul.meu:8787 Nicio cameră deschisă - + %1 member(s) @@ -2043,7 +2086,7 @@ Exemplu: https://serverul.meu:8787 TimelineViewManager - + No encrypted private chat found with this user. Create an encrypted private chat with this user and try again. @@ -2181,8 +2224,8 @@ Exemplu: https://serverul.meu:8787 UserSettings - - + + Default @@ -2190,7 +2233,7 @@ Exemplu: https://serverul.meu:8787 UserSettingsPage - + Minimize to tray Minimizează în bara de notificări @@ -2210,12 +2253,12 @@ Exemplu: https://serverul.meu:8787 Avatare rotunde - + profile: %1 - + Default @@ -2421,7 +2464,17 @@ This usually causes the application icon in the task bar to animate in some fash - + + Online Key Backup + + + + + Download message encryption keys from and upload to the encrypted online key backup. + + + + CACHED @@ -2431,7 +2484,7 @@ This usually causes the application icon in the task bar to animate in some fash - + Scale factor Factor de dimensiune @@ -2526,12 +2579,12 @@ This usually causes the application icon in the task bar to animate in some fash CRIPTARE - + GENERAL GENERAL - + INTERFACE INTERFAȚĂ @@ -2551,7 +2604,7 @@ This usually causes the application icon in the task bar to animate in some fash Familia de font pentru Emoji - + Master signing key @@ -2601,7 +2654,7 @@ This usually causes the application icon in the task bar to animate in some fash Toate fișierele (*) - + Open Sessions File Deschide fișierul de sesiuni diff --git a/resources/langs/nheko_ru.ts b/resources/langs/nheko_ru.ts index 3957f88c..52c76005 100644 --- a/resources/langs/nheko_ru.ts +++ b/resources/langs/nheko_ru.ts @@ -131,17 +131,17 @@ - + Invited user: %1 Приглашенный пользователь: %1 - + Migrating the cache to the current version failed. This can have different reasons. Please open an issue and try to use an older version in the mean time. Alternatively you can try deleting the cache manually. Миграция кэша для текущей версии не удалась. Это может происходить по разным причинам. Пожалуйста сообщите о проблеме и попробуйте временно использовать старую версию. Так-же вы можете попробовать удалить кэш самостоятельно. - + Confirm join Подтвердить вход @@ -156,13 +156,13 @@ Комната %1 создана. - - + + Confirm invite Подтвердите приглашение - + Do you really want to invite %1 (%2)? Вы точно хотите пригласить %1 (%2)? @@ -227,12 +227,12 @@ Разблокированный пользователь: %1 - + Do you really want to start a private chat with %1? Вы действительно хотите начать личную переписку с %1? - + Cache migration failed! Миграция кэша не удалась! @@ -247,7 +247,7 @@ Ваш кэш новее, чем эта версия Nheko поддерживает. Пожалуйста обновитесь или отчистите ваш кэш. - + Failed to restore OLM account. Please login again. Не удалось восстановить учетную запись OLM. Пожалуйста, войдите снова. @@ -257,7 +257,7 @@ Не удалось восстановить сохраненные данные. Пожалуйста, войдите снова. - + Failed to setup encryption keys. Server response: %1 %2. Please try again later. Не удалось настроить ключи шифрования. Ответ сервера:%1 %2. Пожалуйста, попробуйте позже. @@ -288,7 +288,7 @@ Не удалось создать комнату: %1 - + Failed to leave room: %1 Не удалось покинуть комнату: %1 @@ -352,7 +352,7 @@ CrossSigningSecrets - + Decrypt secrets Расшифровать секреты @@ -556,8 +556,8 @@ - Encrypted by an unverified device - Зашифровано неверифицированым устройства + Encrypted by an unverified device or the key is from an untrusted source like the key backup. + @@ -889,7 +889,7 @@ Example: https://server.my:8787 MessageDelegate - + removed убрано @@ -959,6 +959,11 @@ Example: https://server.my:8787 Negotiating call... Совершение звонка... + + + Allow them in + + MessageInput @@ -1262,7 +1267,7 @@ Example: https://server.my:8787 Read receipts - + Просмотр получателей @@ -1385,10 +1390,23 @@ Example: https://server.my:8787 Отменить редактирование + + RoomDirectory + + + Explore Public Rooms + + + + + Search for public rooms + + + RoomInfo - + no version stored нет сохраненной версии @@ -1396,7 +1414,7 @@ Example: https://server.my:8787 RoomList - + New tag @@ -1491,7 +1509,7 @@ Example: https://server.my:8787 Каталог комнат - + User settings Пользовательские настройки @@ -1582,7 +1600,7 @@ Example: https://server.my:8787 - + Anyone and guests Каждый и гости @@ -1597,7 +1615,17 @@ Example: https://server.my:8787 Приглашённые пользователи - + + By knocking + + + + + Restricted by membership in other rooms + + + + Encryption Шифрование @@ -1643,12 +1671,12 @@ Example: https://server.my:8787 Версия Комнаты - + Failed to enable encryption: %1 Не удалось включить шифрование: %1 - + Select an avatar Выберите аватар @@ -1751,22 +1779,22 @@ Example: https://server.my:8787 - Failed to update image pack: {} + Failed to update image pack: %1 - Failed to delete old image pack: {} + Failed to delete old image pack: %1 - Failed to open image: {} + Failed to open image: %1 - Failed to upload image: {} + Failed to upload image: %1 @@ -1822,7 +1850,7 @@ Example: https://server.my:8787 TimelineModel - + Message redaction failed: %1 Ошибка редактирования сообщения: %1 @@ -1873,7 +1901,17 @@ Example: https://server.my:8787 %1 сделал вход в комнату по приглашению. - + + %1 allowed to join this room by knocking. + + + + + %1 allowed members of the following rooms to automatically join this room: %2 + + + + %1 made the room open to guests. %1 сделал комнату открытой для гостей. @@ -1908,7 +1946,7 @@ Example: https://server.my:8787 %1 поменял разрешения для комнаты. - + %1 was invited. %1 был приглашен. @@ -1923,12 +1961,17 @@ Example: https://server.my:8787 %1 поменял информацию в профиле. - + %1 joined. %1 присоединился. - + + %1 joined via authorisation from %2's server. + + + + %1 rejected their invite. %1 отклонил приглашение. @@ -1968,12 +2011,12 @@ Example: https://server.my:8787 %1 отредактировал его "стук". - + You joined this room. Вы присоединились к этой комнате. - + %1 has changed their avatar and changed their display name to %2. @@ -1983,7 +2026,7 @@ Example: https://server.my:8787 - + Rejected the knock from %1. Отверг "стук" от %1 @@ -2015,7 +2058,7 @@ Example: https://server.my:8787 Комната не выбрана - + %1 member(s) %1 участник(ов) @@ -2043,7 +2086,7 @@ Example: https://server.my:8787 TimelineViewManager - + No encrypted private chat found with this user. Create an encrypted private chat with this user and try again. Не найдено личного чата с этим пользователем. Создайте зашифрованный личный чат с этим пользователем и попытайтесь еще раз. @@ -2181,8 +2224,8 @@ Example: https://server.my:8787 UserSettings - - + + Default По умолчанию @@ -2190,7 +2233,7 @@ Example: https://server.my:8787 UserSettingsPage - + Minimize to tray Сворачивать в системную панель @@ -2210,12 +2253,12 @@ Example: https://server.my:8787 Округлый Аватар - + profile: %1 профиль: %1 - + Default По умолчанию @@ -2426,7 +2469,17 @@ This usually causes the application icon in the task bar to animate in some fash - + + Online Key Backup + + + + + Download message encryption keys from and upload to the encrypted online key backup. + + + + CACHED Закешировано @@ -2436,7 +2489,7 @@ This usually causes the application icon in the task bar to animate in some fash НЕ ЗАКЕШИРОВАНО - + Scale factor Масштаб @@ -2531,12 +2584,12 @@ This usually causes the application icon in the task bar to animate in some fash ШИФРОВАНИЕ - + GENERAL ГЛАВНОЕ - + INTERFACE ИНТЕРФЕЙС @@ -2556,7 +2609,7 @@ This usually causes the application icon in the task bar to animate in some fash Семья шрифта эмоджи - + Master signing key @@ -2606,7 +2659,7 @@ This usually causes the application icon in the task bar to animate in some fash Все файлы (*) - + Open Sessions File Открыть файл сеансов diff --git a/resources/langs/nheko_si.ts b/resources/langs/nheko_si.ts index 36f3f9a4..b0a86c9d 100644 --- a/resources/langs/nheko_si.ts +++ b/resources/langs/nheko_si.ts @@ -131,17 +131,17 @@ - + Invited user: %1 - + Migrating the cache to the current version failed. This can have different reasons. Please open an issue and try to use an older version in the mean time. Alternatively you can try deleting the cache manually. - + Confirm join @@ -156,13 +156,13 @@ - - + + Confirm invite - + Do you really want to invite %1 (%2)? @@ -227,12 +227,12 @@ - + Do you really want to start a private chat with %1? - + Cache migration failed! @@ -247,7 +247,7 @@ - + Failed to restore OLM account. Please login again. @@ -257,7 +257,7 @@ - + Failed to setup encryption keys. Server response: %1 %2. Please try again later. @@ -288,7 +288,7 @@ - + Failed to leave room: %1 @@ -352,7 +352,7 @@ CrossSigningSecrets - + Decrypt secrets @@ -556,7 +556,7 @@ - Encrypted by an unverified device + Encrypted by an unverified device or the key is from an untrusted source like the key backup. @@ -885,7 +885,7 @@ Example: https://server.my:8787 MessageDelegate - + removed @@ -955,6 +955,11 @@ Example: https://server.my:8787 Negotiating call... + + + Allow them in + + MessageInput @@ -1381,10 +1386,23 @@ Example: https://server.my:8787 + + RoomDirectory + + + Explore Public Rooms + + + + + Search for public rooms + + + RoomInfo - + no version stored @@ -1392,7 +1410,7 @@ Example: https://server.my:8787 RoomList - + New tag @@ -1487,7 +1505,7 @@ Example: https://server.my:8787 - + User settings @@ -1577,7 +1595,7 @@ Example: https://server.my:8787 - + Anyone and guests @@ -1592,7 +1610,17 @@ Example: https://server.my:8787 - + + By knocking + + + + + Restricted by membership in other rooms + + + + Encryption @@ -1638,12 +1666,12 @@ Example: https://server.my:8787 - + Failed to enable encryption: %1 - + Select an avatar @@ -1746,22 +1774,22 @@ Example: https://server.my:8787 - Failed to update image pack: {} + Failed to update image pack: %1 - Failed to delete old image pack: {} + Failed to delete old image pack: %1 - Failed to open image: {} + Failed to open image: %1 - Failed to upload image: {} + Failed to upload image: %1 @@ -1817,7 +1845,7 @@ Example: https://server.my:8787 TimelineModel - + Message redaction failed: %1 @@ -1867,7 +1895,17 @@ Example: https://server.my:8787 - + + %1 allowed to join this room by knocking. + + + + + %1 allowed members of the following rooms to automatically join this room: %2 + + + + %1 made the room open to guests. @@ -1902,7 +1940,7 @@ Example: https://server.my:8787 - + %1 was invited. @@ -1917,12 +1955,17 @@ Example: https://server.my:8787 - + %1 joined. - + + %1 joined via authorisation from %2's server. + + + + %1 rejected their invite. @@ -1962,12 +2005,12 @@ Example: https://server.my:8787 - + You joined this room. - + %1 has changed their avatar and changed their display name to %2. @@ -1977,7 +2020,7 @@ Example: https://server.my:8787 - + Rejected the knock from %1. @@ -2009,7 +2052,7 @@ Example: https://server.my:8787 - + %1 member(s) @@ -2037,7 +2080,7 @@ Example: https://server.my:8787 TimelineViewManager - + No encrypted private chat found with this user. Create an encrypted private chat with this user and try again. @@ -2175,8 +2218,8 @@ Example: https://server.my:8787 UserSettings - - + + Default @@ -2184,7 +2227,7 @@ Example: https://server.my:8787 UserSettingsPage - + Minimize to tray @@ -2204,12 +2247,12 @@ Example: https://server.my:8787 - + profile: %1 - + Default @@ -2415,7 +2458,17 @@ This usually causes the application icon in the task bar to animate in some fash - + + Online Key Backup + + + + + Download message encryption keys from and upload to the encrypted online key backup. + + + + CACHED @@ -2425,7 +2478,7 @@ This usually causes the application icon in the task bar to animate in some fash - + Scale factor @@ -2520,12 +2573,12 @@ This usually causes the application icon in the task bar to animate in some fash - + GENERAL - + INTERFACE @@ -2545,7 +2598,7 @@ This usually causes the application icon in the task bar to animate in some fash - + Master signing key @@ -2595,7 +2648,7 @@ This usually causes the application icon in the task bar to animate in some fash - + Open Sessions File diff --git a/resources/langs/nheko_sv.ts b/resources/langs/nheko_sv.ts index b501f669..09a91b13 100644 --- a/resources/langs/nheko_sv.ts +++ b/resources/langs/nheko_sv.ts @@ -131,17 +131,17 @@ - + Invited user: %1 Bjöd in användare: %1 - + Migrating the cache to the current version failed. This can have different reasons. Please open an issue and try to use an older version in the mean time. Alternatively you can try deleting the cache manually. Kunde inte migrera cachen till den nuvarande versionen. Detta kan bero på flera anledningar, vänligen rapportera problemet och prova en äldre version under tiden. Du kan också försöka att manuellt radera cachen. - + Confirm join @@ -156,13 +156,13 @@ Rum %1 skapat. - - + + Confirm invite Bekräfta inbjudan - + Do you really want to invite %1 (%2)? Är du säker på att du vill bjuda in %1 (%2)? @@ -227,12 +227,12 @@ Hävde bannlysningen av användare: %1 - + Do you really want to start a private chat with %1? - + Cache migration failed! Cache-migration misslyckades! @@ -247,7 +247,7 @@ Cachen på ditt lagringsmedia är nyare än vad denna version av Nheko stödjer. Vänligen uppdatera eller rensa din cache. - + Failed to restore OLM account. Please login again. Kunde inte återställa OLM-konto. Vänligen logga in på nytt. @@ -257,7 +257,7 @@ Kunde inte återställa sparad data. Vänligen logga in på nytt. - + Failed to setup encryption keys. Server response: %1 %2. Please try again later. Kunde inte sätta upp krypteringsnycklar. Svar från servern: %1 %2. Vänligen försök igen senare. @@ -288,7 +288,7 @@ Kunde inte skapa rum: %1 - + Failed to leave room: %1 Kunde inte lämna rum: %1 @@ -352,7 +352,7 @@ CrossSigningSecrets - + Decrypt secrets Dekryptera hemliga nycklar @@ -556,7 +556,7 @@ - Encrypted by an unverified device + Encrypted by an unverified device or the key is from an untrusted source like the key backup. @@ -889,7 +889,7 @@ Exempel: https://server.my:8787 MessageDelegate - + Encryption enabled Kryptering aktiverad @@ -944,7 +944,12 @@ Exempel: https://server.my:8787 Förhandlar samtal… - + + Allow them in + + + + %1 answered the call. %1 besvarade samtalet. @@ -1385,10 +1390,23 @@ Exempel: https://server.my:8787 + + RoomDirectory + + + Explore Public Rooms + + + + + Search for public rooms + + + RoomInfo - + no version stored ingen version lagrad @@ -1396,7 +1414,7 @@ Exempel: https://server.my:8787 RoomList - + New tag @@ -1491,7 +1509,7 @@ Exempel: https://server.my:8787 Rumkatalog - + User settings Användarinställningar @@ -1581,7 +1599,7 @@ Exempel: https://server.my:8787 - + Anyone and guests @@ -1596,7 +1614,17 @@ Exempel: https://server.my:8787 - + + By knocking + + + + + Restricted by membership in other rooms + + + + Encryption @@ -1642,12 +1670,12 @@ Exempel: https://server.my:8787 - + Failed to enable encryption: %1 Kunde inte aktivera kryptering: %1 - + Select an avatar Välj en avatar @@ -1750,22 +1778,22 @@ Exempel: https://server.my:8787 - Failed to update image pack: {} + Failed to update image pack: %1 - Failed to delete old image pack: {} + Failed to delete old image pack: %1 - Failed to open image: {} + Failed to open image: %1 - Failed to upload image: {} + Failed to upload image: %1 @@ -1821,7 +1849,7 @@ Exempel: https://server.my:8787 TimelineModel - + Message redaction failed: %1 Kunde inte maskera meddelande: %1 @@ -1871,7 +1899,17 @@ Exempel: https://server.my:8787 %1 satte rummet till att kräva inbjudan för att gå med. - + + %1 allowed to join this room by knocking. + + + + + %1 allowed members of the following rooms to automatically join this room: %2 + + + + %1 made the room open to guests. %1 öppnade rummet för gäståtkomst. @@ -1906,7 +1944,7 @@ Exempel: https://server.my:8787 %1 har ändrat rummets behörigheter. - + %1 was invited. %1 blev inbjuden. @@ -1921,12 +1959,17 @@ Exempel: https://server.my:8787 %1 ändrade någon profilinfo. - + %1 joined. %1 gick med. - + + %1 joined via authorisation from %2's server. + + + + %1 rejected their invite. %1 avvisade sin inbjudan. @@ -1966,12 +2009,12 @@ Exempel: https://server.my:8787 %1 maskerade sin knackning. - + You joined this room. Du gick med i detta rum. - + %1 has changed their avatar and changed their display name to %2. @@ -1981,7 +2024,7 @@ Exempel: https://server.my:8787 - + Rejected the knock from %1. Avvisade knackningen från %1. @@ -2013,7 +2056,7 @@ Exempel: https://server.my:8787 Inget rum öppet - + %1 member(s) @@ -2041,7 +2084,7 @@ Exempel: https://server.my:8787 TimelineViewManager - + No encrypted private chat found with this user. Create an encrypted private chat with this user and try again. Ingen krypterad privat chatt med denna användare kunde hittas. Skapa en krypterad privat chatt med användaren och försök igen. @@ -2179,8 +2222,8 @@ Exempel: https://server.my:8787 UserSettings - - + + Default @@ -2188,7 +2231,7 @@ Exempel: https://server.my:8787 UserSettingsPage - + Minimize to tray Minimera till systemtråg @@ -2208,12 +2251,12 @@ Exempel: https://server.my:8787 Cirkulära avatarer - + profile: %1 profil: %1 - + Default @@ -2427,7 +2470,17 @@ Detta gör vanligtvis att ikonen i aktivitetsfältet animeras på något sätt.< - + + Online Key Backup + + + + + Download message encryption keys from and upload to the encrypted online key backup. + + + + CACHED SPARAD @@ -2437,7 +2490,7 @@ Detta gör vanligtvis att ikonen i aktivitetsfältet animeras på något sätt.< EJ SPARAD - + Scale factor Storleksfaktor @@ -2532,12 +2585,12 @@ Detta gör vanligtvis att ikonen i aktivitetsfältet animeras på något sätt.< KRYPTERING - + GENERAL ALLMÄNT - + INTERFACE GRÄNSSNITT @@ -2557,7 +2610,7 @@ Detta gör vanligtvis att ikonen i aktivitetsfältet animeras på något sätt.< Emoji font-familj - + Master signing key Primär signeringsnyckel @@ -2607,7 +2660,7 @@ Detta gör vanligtvis att ikonen i aktivitetsfältet animeras på något sätt.< Alla Filer (*) - + Open Sessions File Öppna sessionsfil diff --git a/resources/langs/nheko_zh_CN.ts b/resources/langs/nheko_zh_CN.ts index be4f1e49..b788a569 100644 --- a/resources/langs/nheko_zh_CN.ts +++ b/resources/langs/nheko_zh_CN.ts @@ -131,17 +131,17 @@ - + Invited user: %1 邀请已发送: %1 - + Migrating the cache to the current version failed. This can have different reasons. Please open an issue and try to use an older version in the mean time. Alternatively you can try deleting the cache manually. 无法迁移缓存到目前版本,可能有多种原因引发此类问题。您可以新建一个议题并继续使用之前版本,或者您可以尝试手动删除缓存。 - + Confirm join @@ -156,13 +156,13 @@ 房间“%1”已创建 - - + + Confirm invite - + Do you really want to invite %1 (%2)? @@ -227,12 +227,12 @@ 解禁用户: %1 - + Do you really want to start a private chat with %1? - + Cache migration failed! 缓存迁移失败! @@ -247,7 +247,7 @@ 本地缓存版本比现用的Nheko版本新。请升级Nheko或手动清除缓存。 - + Failed to restore OLM account. Please login again. 恢复 OLM 账户失败。请重新登录。 @@ -257,7 +257,7 @@ 恢复保存的数据失败。请重新登录。 - + Failed to setup encryption keys. Server response: %1 %2. Please try again later. 设置密钥失败。服务器返回信息: %1 %2。请稍后再试。 @@ -288,7 +288,7 @@ 创建聊天室失败:%1 - + Failed to leave room: %1 离开聊天室失败:%1 @@ -352,7 +352,7 @@ CrossSigningSecrets - + Decrypt secrets @@ -556,7 +556,7 @@ - Encrypted by an unverified device + Encrypted by an unverified device or the key is from an untrusted source like the key backup. @@ -885,7 +885,7 @@ Example: https://server.my:8787 MessageDelegate - + removed @@ -955,6 +955,11 @@ Example: https://server.my:8787 Negotiating call... + + + Allow them in + + MessageInput @@ -1381,10 +1386,23 @@ Example: https://server.my:8787 + + RoomDirectory + + + Explore Public Rooms + + + + + Search for public rooms + + + RoomInfo - + no version stored @@ -1392,7 +1410,7 @@ Example: https://server.my:8787 RoomList - + New tag @@ -1487,7 +1505,7 @@ Example: https://server.my:8787 聊天室目录 - + User settings 用户设置 @@ -1576,7 +1594,7 @@ Example: https://server.my:8787 - + Anyone and guests @@ -1591,7 +1609,17 @@ Example: https://server.my:8787 - + + By knocking + + + + + Restricted by membership in other rooms + + + + Encryption @@ -1637,12 +1665,12 @@ Example: https://server.my:8787 - + Failed to enable encryption: %1 启用加密失败:%1 - + Select an avatar 选择一个头像 @@ -1745,22 +1773,22 @@ Example: https://server.my:8787 - Failed to update image pack: {} + Failed to update image pack: %1 - Failed to delete old image pack: {} + Failed to delete old image pack: %1 - Failed to open image: {} + Failed to open image: %1 - Failed to upload image: {} + Failed to upload image: %1 @@ -1816,7 +1844,7 @@ Example: https://server.my:8787 TimelineModel - + Message redaction failed: %1 删除消息失败:%1 @@ -1865,7 +1893,17 @@ Example: https://server.my:8787 - + + %1 allowed to join this room by knocking. + + + + + %1 allowed members of the following rooms to automatically join this room: %2 + + + + %1 made the room open to guests. @@ -1900,7 +1938,7 @@ Example: https://server.my:8787 - + %1 was invited. @@ -1915,12 +1953,17 @@ Example: https://server.my:8787 - + %1 joined. - + + %1 joined via authorisation from %2's server. + + + + %1 rejected their invite. @@ -1960,12 +2003,12 @@ Example: https://server.my:8787 - + You joined this room. 您已加入此房间 - + %1 has changed their avatar and changed their display name to %2. @@ -1975,7 +2018,7 @@ Example: https://server.my:8787 - + Rejected the knock from %1. @@ -2007,7 +2050,7 @@ Example: https://server.my:8787 - + %1 member(s) @@ -2035,7 +2078,7 @@ Example: https://server.my:8787 TimelineViewManager - + No encrypted private chat found with this user. Create an encrypted private chat with this user and try again. @@ -2173,8 +2216,8 @@ Example: https://server.my:8787 UserSettings - - + + Default @@ -2182,7 +2225,7 @@ Example: https://server.my:8787 UserSettingsPage - + Minimize to tray 最小化至托盘 @@ -2202,12 +2245,12 @@ Example: https://server.my:8787 - + profile: %1 - + Default @@ -2413,7 +2456,17 @@ This usually causes the application icon in the task bar to animate in some fash - + + Online Key Backup + + + + + Download message encryption keys from and upload to the encrypted online key backup. + + + + CACHED @@ -2423,7 +2476,7 @@ This usually causes the application icon in the task bar to animate in some fash - + Scale factor @@ -2518,12 +2571,12 @@ This usually causes the application icon in the task bar to animate in some fash 加密 - + GENERAL 通用 - + INTERFACE @@ -2543,7 +2596,7 @@ This usually causes the application icon in the task bar to animate in some fash - + Master signing key @@ -2593,7 +2646,7 @@ This usually causes the application icon in the task bar to animate in some fash 所有文件(*) - + Open Sessions File 打开会话文件 diff --git a/src/SingleImagePackModel.cpp b/src/SingleImagePackModel.cpp index 7bf55617..4eb120d9 100644 --- a/src/SingleImagePackModel.cpp +++ b/src/SingleImagePackModel.cpp @@ -258,7 +258,7 @@ SingleImagePackModel::save() http::client()->put_account_data(pack, [](mtx::http::RequestErr e) { if (e) ChatPage::instance()->showNotification( - tr("Failed to update image pack: {}") + tr("Failed to update image pack: %1") .arg(QString::fromStdString(e->matrix_error.error))); }); } else { @@ -271,7 +271,7 @@ SingleImagePackModel::save() [](const mtx::responses::EventId &, mtx::http::RequestErr e) { if (e) ChatPage::instance()->showNotification( - tr("Failed to delete old image pack: {}") + tr("Failed to delete old image pack: %1") .arg(QString::fromStdString(e->matrix_error.error))); }); } @@ -283,10 +283,10 @@ SingleImagePackModel::save() [this](const mtx::responses::EventId &, mtx::http::RequestErr e) { if (e) ChatPage::instance()->showNotification( - tr("Failed to update image pack: {}") + tr("Failed to update image pack: %1") .arg(QString::fromStdString(e->matrix_error.error))); - nhlog::net()->info("Uploaded image pack: {}", statekey_); + nhlog::net()->info("Uploaded image pack: %1", statekey_); }); } } @@ -298,7 +298,7 @@ SingleImagePackModel::addStickers(QList files) auto file = QFile(f.toLocalFile()); if (!file.open(QFile::ReadOnly)) { ChatPage::instance()->showNotification( - tr("Failed to open image: {}").arg(f.toLocalFile())); + tr("Failed to open image: %1").arg(f.toLocalFile())); return; } @@ -325,7 +325,7 @@ SingleImagePackModel::addStickers(QList files) mtx::http::RequestErr e) { if (e) { ChatPage::instance()->showNotification( - tr("Failed to upload image: {}") + tr("Failed to upload image: %1") .arg(QString::fromStdString(e->matrix_error.error))); return; } From 3d2f503305c6fc26dcc06cdfb790b9fb9574bad9 Mon Sep 17 00:00:00 2001 From: Nicolas Werner Date: Wed, 18 Aug 2021 18:16:29 +0200 Subject: [PATCH 031/232] Add workaround for stickers not showing on iOS see: https://github.com/vector-im/element-ios/issues/2353 --- src/timeline/InputBar.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/timeline/InputBar.cpp b/src/timeline/InputBar.cpp index f17081e5..c82099f0 100644 --- a/src/timeline/InputBar.cpp +++ b/src/timeline/InputBar.cpp @@ -515,6 +515,13 @@ InputBar::sticker(CombinedImagePackModel *model, int row) sticker.url = img.url; sticker.body = img.body; + // workaround for https://github.com/vector-im/element-ios/issues/2353 + sticker.info.thumbnail_url = sticker.url; + sticker.info.thumbnail_info.mimetype = sticker.info.mimetype; + sticker.info.thumbnail_info.size = sticker.info.size; + sticker.info.thumbnail_info.h = sticker.info.h; + sticker.info.thumbnail_info.w = sticker.info.w; + if (!room->reply().isEmpty()) { sticker.relations.relations.push_back( {mtx::common::RelationType::InReplyTo, room->reply().toStdString()}); From 8ee043f0421a865cc0b9f3bf66ce5c1f04f76aa7 Mon Sep 17 00:00:00 2001 From: Thomas Karpiniec Date: Thu, 19 Aug 2021 23:39:35 +1000 Subject: [PATCH 032/232] Clear emoji search field when picker closed --- resources/qml/emoji/EmojiPicker.qml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/resources/qml/emoji/EmojiPicker.qml b/resources/qml/emoji/EmojiPicker.qml index 354e340c..e83f8a5e 100644 --- a/resources/qml/emoji/EmojiPicker.qml +++ b/resources/qml/emoji/EmojiPicker.qml @@ -72,7 +72,8 @@ Menu { onVisibleChanged: { if (visible) forceActiveFocus(); - + else + clear(); } Timer { From 9504d02f18c23e7ef6918a6279bb8ff50734351f Mon Sep 17 00:00:00 2001 From: Joseph Donofry Date: Thu, 19 Aug 2021 10:55:54 -0400 Subject: [PATCH 033/232] Add Qt.WindowTitleHint to qml dialogs --- resources/qml/InviteDialog.qml | 2 +- resources/qml/RawMessageDialog.qml | 2 +- resources/qml/ReadReceipts.qml | 2 +- resources/qml/RoomDirectory.qml | 2 +- resources/qml/RoomMembers.qml | 2 +- resources/qml/RoomSettings.qml | 2 +- resources/qml/UserProfile.qml | 2 +- resources/qml/device-verification/DeviceVerification.qml | 2 +- resources/qml/dialogs/ImagePackEditorDialog.qml | 2 +- resources/qml/dialogs/ImagePackSettingsDialog.qml | 2 +- 10 files changed, 10 insertions(+), 10 deletions(-) diff --git a/resources/qml/InviteDialog.qml b/resources/qml/InviteDialog.qml index 2c0e15a7..916bdd39 100644 --- a/resources/qml/InviteDialog.qml +++ b/resources/qml/InviteDialog.qml @@ -34,7 +34,7 @@ ApplicationWindow { width: 340 palette: Nheko.colors color: Nheko.colors.window - flags: Qt.Dialog | Qt.WindowCloseButtonHint + flags: Qt.Dialog | Qt.WindowCloseButtonHint | Qt.WindowTitleHint Component.onCompleted: Nheko.reparent(inviteDialogRoot) Shortcut { diff --git a/resources/qml/RawMessageDialog.qml b/resources/qml/RawMessageDialog.qml index e2a476cd..c171de7e 100644 --- a/resources/qml/RawMessageDialog.qml +++ b/resources/qml/RawMessageDialog.qml @@ -15,7 +15,7 @@ ApplicationWindow { width: 420 palette: Nheko.colors color: Nheko.colors.window - flags: Qt.Tool | Qt.WindowStaysOnTopHint | Qt.WindowCloseButtonHint + flags: Qt.Tool | Qt.WindowStaysOnTopHint | Qt.WindowCloseButtonHint | Qt.WindowTitleHint Component.onCompleted: Nheko.reparent(rawMessageRoot) Shortcut { diff --git a/resources/qml/ReadReceipts.qml b/resources/qml/ReadReceipts.qml index 9adbfd5c..e1dd7c00 100644 --- a/resources/qml/ReadReceipts.qml +++ b/resources/qml/ReadReceipts.qml @@ -19,7 +19,7 @@ ApplicationWindow { minimumWidth: headerTitle.width + 2 * Nheko.paddingMedium palette: Nheko.colors color: Nheko.colors.window - flags: Qt.Dialog | Qt.WindowCloseButtonHint + flags: Qt.Dialog | Qt.WindowCloseButtonHint | Qt.WindowTitleHint Component.onCompleted: Nheko.reparent(readReceiptsRoot) Shortcut { diff --git a/resources/qml/RoomDirectory.qml b/resources/qml/RoomDirectory.qml index cc36f008..2d7b3a34 100644 --- a/resources/qml/RoomDirectory.qml +++ b/resources/qml/RoomDirectory.qml @@ -19,7 +19,7 @@ ApplicationWindow { palette: Nheko.colors color: Nheko.colors.window modality: Qt.WindowModal - flags: Qt.Dialog | Qt.WindowCloseButtonHint + flags: Qt.Dialog | Qt.WindowCloseButtonHint | Qt.WindowTitleHint Component.onCompleted: Nheko.reparent(roomDirectoryWindow) title: qsTr("Explore Public Rooms") diff --git a/resources/qml/RoomMembers.qml b/resources/qml/RoomMembers.qml index 8e44855c..62175bf0 100644 --- a/resources/qml/RoomMembers.qml +++ b/resources/qml/RoomMembers.qml @@ -21,7 +21,7 @@ ApplicationWindow { minimumHeight: 420 palette: Nheko.colors color: Nheko.colors.window - flags: Qt.Dialog | Qt.WindowCloseButtonHint + flags: Qt.Dialog | Qt.WindowCloseButtonHint | Qt.WindowTitleHint Component.onCompleted: Nheko.reparent(roomMembersRoot) Shortcut { diff --git a/resources/qml/RoomSettings.qml b/resources/qml/RoomSettings.qml index 92cd431a..a70cd71a 100644 --- a/resources/qml/RoomSettings.qml +++ b/resources/qml/RoomSettings.qml @@ -20,7 +20,7 @@ ApplicationWindow { palette: Nheko.colors color: Nheko.colors.window modality: Qt.NonModal - flags: Qt.Dialog | Qt.WindowCloseButtonHint + flags: Qt.Dialog | Qt.WindowCloseButtonHint | Qt.WindowTitleHint Component.onCompleted: Nheko.reparent(roomSettingsDialog) title: qsTr("Room Settings") diff --git a/resources/qml/UserProfile.qml b/resources/qml/UserProfile.qml index 767d2317..95fd2bbe 100644 --- a/resources/qml/UserProfile.qml +++ b/resources/qml/UserProfile.qml @@ -25,7 +25,7 @@ ApplicationWindow { color: Nheko.colors.window title: profile.isGlobalUserProfile ? qsTr("Global User Profile") : qsTr("Room User Profile") modality: Qt.NonModal - flags: Qt.Dialog | Qt.WindowCloseButtonHint + flags: Qt.Dialog | Qt.WindowCloseButtonHint | Qt.WindowTitleHint Component.onCompleted: Nheko.reparent(userProfileDialog) Shortcut { diff --git a/resources/qml/device-verification/DeviceVerification.qml b/resources/qml/device-verification/DeviceVerification.qml index 8e0271d6..01e3bad4 100644 --- a/resources/qml/device-verification/DeviceVerification.qml +++ b/resources/qml/device-verification/DeviceVerification.qml @@ -18,7 +18,7 @@ ApplicationWindow { palette: Nheko.colors height: stack.implicitHeight width: stack.implicitWidth - flags: Qt.Dialog | Qt.WindowCloseButtonHint + flags: Qt.Dialog | Qt.WindowCloseButtonHint | Qt.WindowTitleHint Component.onCompleted: Nheko.reparent(dialog) StackView { diff --git a/resources/qml/dialogs/ImagePackEditorDialog.qml b/resources/qml/dialogs/ImagePackEditorDialog.qml index b0f431f6..103f19a9 100644 --- a/resources/qml/dialogs/ImagePackEditorDialog.qml +++ b/resources/qml/dialogs/ImagePackEditorDialog.qml @@ -27,7 +27,7 @@ ApplicationWindow { palette: Nheko.colors color: Nheko.colors.base modality: Qt.WindowModal - flags: Qt.Dialog | Qt.WindowCloseButtonHint + flags: Qt.Dialog | Qt.WindowCloseButtonHint | Qt.WindowTitleHint AdaptiveLayout { id: adaptiveView diff --git a/resources/qml/dialogs/ImagePackSettingsDialog.qml b/resources/qml/dialogs/ImagePackSettingsDialog.qml index 5181619c..b217abdd 100644 --- a/resources/qml/dialogs/ImagePackSettingsDialog.qml +++ b/resources/qml/dialogs/ImagePackSettingsDialog.qml @@ -25,7 +25,7 @@ ApplicationWindow { palette: Nheko.colors color: Nheko.colors.base modality: Qt.NonModal - flags: Qt.Dialog | Qt.WindowCloseButtonHint + flags: Qt.Dialog | Qt.WindowCloseButtonHint | Qt.WindowTitleHint Component.onCompleted: Nheko.reparent(win) Component { From c853dd35931712b5a662de7dc372e6019e12046d Mon Sep 17 00:00:00 2001 From: Weblate Date: Thu, 19 Aug 2021 21:54:08 -0400 Subject: [PATCH 034/232] Translated using Weblate (Estonian) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Currently translated at 88.5% (489 of 552 strings) Co-authored-by: Priit Jõerüüt Translate-URL: https://weblate.nheko.im/projects/nheko/nheko-master/et/ Translation: Nheko/nheko --- resources/langs/nheko_et.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/resources/langs/nheko_et.ts b/resources/langs/nheko_et.ts index 4b0d5a97..b99b56ce 100644 --- a/resources/langs/nheko_et.ts +++ b/resources/langs/nheko_et.ts @@ -712,12 +712,12 @@ Edit - Muuda + Muuda Close - Sulge + Sulge @@ -1267,7 +1267,7 @@ Näiteks: https://server.minu:8787 Read receipts - Lugemisteatised + Lugemisteatised From b63289ba54edcaced8cb6dc68cec815584650f7f Mon Sep 17 00:00:00 2001 From: Nicolas Werner Date: Thu, 19 Aug 2021 18:26:05 +0200 Subject: [PATCH 035/232] Cleanup @room escape logic a bit --- src/Utils.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/Utils.cpp b/src/Utils.cpp index 41013e39..3f524c6c 100644 --- a/src/Utils.cpp +++ b/src/Utils.cpp @@ -64,6 +64,8 @@ utils::stripReplyFromBody(const std::string &bodyi) body.remove(plainQuote); if (body.startsWith("\n")) body.remove(0, 1); + + body.replace("@room", QString::fromUtf8("@\u2060room")); return body.toStdString(); } @@ -73,7 +75,7 @@ utils::stripReplyFromFormattedBody(const std::string &formatted_bodyi) QString formatted_body = QString::fromStdString(formatted_bodyi); formatted_body.remove(QRegularExpression(".*", QRegularExpression::DotMatchesEverythingOption)); - formatted_body.replace("@room", "@\u2060room"); + formatted_body.replace("@room", QString::fromUtf8("@\u2060room")); return formatted_body.toStdString(); } @@ -91,7 +93,6 @@ utils::stripReplyFallbacks(const TimelineEvent &event, std::string id, QString r related.quoted_body = QString::fromStdString(stripReplyFromBody(related.quoted_body.toStdString())); related.quoted_body = utils::getQuoteBody(related); - related.quoted_body.replace("@room", QString::fromUtf8("@\u2060room")); // get quoted body and strip reply fallback related.quoted_formatted_body = mtx::accessors::formattedBodyWithFallback(event); From 71a153538f4c1a300d9396d46c69d28bf1e9cf0b Mon Sep 17 00:00:00 2001 From: Nicolas Werner Date: Fri, 20 Aug 2021 13:59:36 +0200 Subject: [PATCH 036/232] Disable async timeline loading, since it hangs in a layout loop sometimes --- resources/qml/TimelineView.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/qml/TimelineView.qml b/resources/qml/TimelineView.qml index 0da7d6c2..9015bafa 100644 --- a/resources/qml/TimelineView.qml +++ b/resources/qml/TimelineView.qml @@ -86,7 +86,7 @@ Item { Loader { active: room || roomPreview - asynchronous: true + asynchronous: false Layout.fillWidth: true sourceComponent: MessageView { From 54169880577d3ec98aad0f96020d665052776027 Mon Sep 17 00:00:00 2001 From: Nicolas Werner Date: Fri, 20 Aug 2021 14:00:16 +0200 Subject: [PATCH 037/232] Trust key forwards from the original sender --- src/Olm.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/Olm.cpp b/src/Olm.cpp index 05eefce4..c0360e1c 100644 --- a/src/Olm.cpp +++ b/src/Olm.cpp @@ -835,6 +835,13 @@ import_inbound_megolm_session( data.sender_claimed_ed25519_key = roomKey.content.sender_claimed_ed25519_key; // may have come from online key backup, so we can't trust it... data.trusted = false; + // if we got it forwarded from the sender, assume it is trusted. They may still have + // used key backup, but it is unlikely. + if (roomKey.content.forwarding_curve25519_key_chain.size() == 1 && + roomKey.content.forwarding_curve25519_key_chain.back() == + roomKey.content.sender_key) { + data.trusted = true; + } cache::saveInboundMegolmSession(index, std::move(megolm_session), data); } catch (const lmdb::error &e) { From 327a889ad5ef47352ceb8d5e36b8ced938473754 Mon Sep 17 00:00:00 2001 From: Nicolas Werner Date: Sat, 21 Aug 2021 03:13:02 +0200 Subject: [PATCH 038/232] Simplify message delegate size calculation a bit --- resources/qml/delegates/MessageDelegate.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/qml/delegates/MessageDelegate.qml b/resources/qml/delegates/MessageDelegate.qml index 893edc77..fde43559 100644 --- a/resources/qml/delegates/MessageDelegate.qml +++ b/resources/qml/delegates/MessageDelegate.qml @@ -34,7 +34,7 @@ Item { required property int encryptionError required property int relatedEventCacheBuster - height: chooser.childrenRect.height + height: chooser.child.height DelegateChooser { id: chooser From 47ad58ef4980d688c425804a87903651d694fadb Mon Sep 17 00:00:00 2001 From: Thomas Karpiniec Date: Sat, 21 Aug 2021 13:29:27 +1000 Subject: [PATCH 039/232] Close popup on SelectAll --- resources/qml/MessageInput.qml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/resources/qml/MessageInput.qml b/resources/qml/MessageInput.qml index 7fb09684..fab5a375 100644 --- a/resources/qml/MessageInput.qml +++ b/resources/qml/MessageInput.qml @@ -176,7 +176,11 @@ Rectangle { } else if (event.key == Qt.Key_Escape && popup.opened) { completerTriggeredAt = -1; popup.completerName = ""; + popup.close(); event.accepted = true; + } else if (event.matches(StandardKey.SelectAll) && popup.opened) { + completerTriggeredAt = -1; + popup.completerName = ""; popup.close(); } else if (event.matches(StandardKey.InsertParagraphSeparator)) { if (popup.opened) { From a24348b57442820473c308f4bbefbdc7b66557b4 Mon Sep 17 00:00:00 2001 From: Thomas Karpiniec Date: Sat, 21 Aug 2021 13:31:27 +1000 Subject: [PATCH 040/232] Allow opening a completer starting with selected text --- resources/qml/MessageInput.qml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/resources/qml/MessageInput.qml b/resources/qml/MessageInput.qml index fab5a375..401d4d85 100644 --- a/resources/qml/MessageInput.qml +++ b/resources/qml/MessageInput.qml @@ -165,13 +165,13 @@ Rectangle { } else if (event.modifiers == Qt.ControlModifier && event.key == Qt.Key_N) { messageInput.text = room.input.nextText(); } else if (event.key == Qt.Key_At) { - messageInput.openCompleter(cursorPosition, "user"); + messageInput.openCompleter(selectionStart, "user"); popup.open(); } else if (event.key == Qt.Key_Colon) { - messageInput.openCompleter(cursorPosition, "emoji"); + messageInput.openCompleter(selectionStart, "emoji"); popup.open(); } else if (event.key == Qt.Key_NumberSign) { - messageInput.openCompleter(cursorPosition, "roomAliases"); + messageInput.openCompleter(selectionStart, "roomAliases"); popup.open(); } else if (event.key == Qt.Key_Escape && popup.opened) { completerTriggeredAt = -1; From 92e8cd0681c94b86edf557a539f63ceec2bf0996 Mon Sep 17 00:00:00 2001 From: Thomas Karpiniec Date: Sat, 21 Aug 2021 14:01:10 +1000 Subject: [PATCH 041/232] Avoid completer jitter before closing --- resources/qml/MessageInput.qml | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/resources/qml/MessageInput.qml b/resources/qml/MessageInput.qml index 401d4d85..7599036e 100644 --- a/resources/qml/MessageInput.qml +++ b/resources/qml/MessageInput.qml @@ -93,7 +93,7 @@ Rectangle { TextArea { id: messageInput - property int completerTriggeredAt: -1 + property int completerTriggeredAt: 0 function insertCompletion(completion) { messageInput.remove(completerTriggeredAt, cursorPosition); @@ -134,8 +134,7 @@ Rectangle { return ; room.input.updateState(selectionStart, selectionEnd, cursorPosition, text); - if (cursorPosition <= completerTriggeredAt) { - completerTriggeredAt = -1; + if (popup.opened && cursorPosition <= completerTriggeredAt) { popup.close(); } if (popup.opened) @@ -145,7 +144,7 @@ Rectangle { onSelectionStartChanged: room.input.updateState(selectionStart, selectionEnd, cursorPosition, text) onSelectionEndChanged: room.input.updateState(selectionStart, selectionEnd, cursorPosition, text) // Ensure that we get escape key press events first. - Keys.onShortcutOverride: event.accepted = (completerTriggeredAt != -1 && (event.key === Qt.Key_Escape || event.key === Qt.Key_Tab || event.key === Qt.Key_Enter)) + Keys.onShortcutOverride: event.accepted = (popup.opened && (event.key === Qt.Key_Escape || event.key === Qt.Key_Tab || event.key === Qt.Key_Enter)) Keys.onPressed: { if (event.matches(StandardKey.Paste)) { room.input.paste(false); @@ -174,12 +173,10 @@ Rectangle { messageInput.openCompleter(selectionStart, "roomAliases"); popup.open(); } else if (event.key == Qt.Key_Escape && popup.opened) { - completerTriggeredAt = -1; popup.completerName = ""; popup.close(); event.accepted = true; } else if (event.matches(StandardKey.SelectAll) && popup.opened) { - completerTriggeredAt = -1; popup.completerName = ""; popup.close(); } else if (event.matches(StandardKey.InsertParagraphSeparator)) { @@ -270,7 +267,6 @@ Rectangle { if (room) messageInput.append(room.input.text()); - messageInput.completerTriggeredAt = -1; popup.completerName = ""; messageInput.forceActiveFocus(); } @@ -289,8 +285,8 @@ Rectangle { Completer { id: popup - x: messageInput.completerTriggeredAt >= 0 ? messageInput.positionToRectangle(messageInput.completerTriggeredAt).x : 0 - y: messageInput.completerTriggeredAt >= 0 ? messageInput.positionToRectangle(messageInput.completerTriggeredAt).y - height : 0 + x: messageInput.positionToRectangle(messageInput.completerTriggeredAt).x + y: messageInput.positionToRectangle(messageInput.completerTriggeredAt).y - height } Connections { From c7dc9fb17a663e734a4c31dfbc7930213c7b631d Mon Sep 17 00:00:00 2001 From: Weblate Date: Sun, 22 Aug 2021 05:33:18 -0400 Subject: [PATCH 042/232] Translated using Weblate (Estonian) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Currently translated at 100.0% (552 of 552 strings) Co-authored-by: Priit Jõerüüt Translate-URL: https://weblate.nheko.im/projects/nheko/nheko-master/et/ Translation: Nheko/nheko --- resources/langs/nheko_et.ts | 128 ++++++++++++++++++------------------ 1 file changed, 64 insertions(+), 64 deletions(-) diff --git a/resources/langs/nheko_et.ts b/resources/langs/nheko_et.ts index b99b56ce..36f25081 100644 --- a/resources/langs/nheko_et.ts +++ b/resources/langs/nheko_et.ts @@ -499,42 +499,42 @@ There is no key to unlock this message. We requested the key automatically, but you can try requesting it again if you are impatient. - + Selle sõnumi dekrüptimiseks pole veel vajalikke võtmeid. Me oleme neid serverist automaatselt laadimas, kuid kui sul on väga kiire, siis võid seda uuesti teha. This message couldn't be decrypted, because we only have a key for newer messages. You can try requesting access to this message. - + Meil on krüptovõtmed vaid uuemate sõnumite jaoks ja seda sõnumit ei saa dekrüptida. Sa võid proovida vajalikke võtmeid eraldi laadida. There was an internal error reading the decryption key from the database. - + Krüptovõtmete andmekogust lugemisel tekkis rakenduses viga. There was an error decrypting this message. - + Sõnumi dekrüptimisel tekkis viga. The message couldn't be parsed. - + Sõnumi töötlemisel tekkis viga. The encryption key was reused! Someone is possibly trying to insert false messages into this chat! - + Krüptovõtit on kasutatud korduvalt! Keegi võib proovida siia vestlusesse valesõnumite lisamist! Unknown decryption error - + Teadmata viga dekrüptimisel Request key - + Laadi krüptovõti @@ -557,7 +557,7 @@ Encrypted by an unverified device or the key is from an untrusted source like the key backup. - + Krüptitud verifitseerimata seadme poolt või krüptovõtmed on pärit allikast, mida sa pole üheselt usaldanud (näiteks varundatud võtmed). @@ -607,64 +607,64 @@ Editing image pack - + Muudan pildipakki Add images - + Lisa pilte Stickers (*.png *.webp) - + Kleepsud (*.png *.webp) State key - + Olekuvõti Packname - + Pildikogu nimi Attribution - + Viide allikale Use as Emoji - + Kasuta emojina Use as Sticker - + Kasuta kleepsuna Shortcode - + Lühend Body - + Sisu Cancel - + Loobu Save - + Salvesta @@ -672,42 +672,42 @@ Image pack settings - + Pildikogu seadistused Create account pack - + Losa kasutajakontokohane pildipakk New room pack - + Uus jututoa pildipakk Private pack - + Isiklik pildipakk Pack from this room - + Pildipakk sellest jututoast Globally enabled pack - + Üldkasutatav pildipakk Enable globally - + Luba kasutada üldiselt Enables this pack to be used in all rooms - + Sellega võimaldad pildipaki kasutamist kõikides jututubades @@ -946,7 +946,7 @@ Näiteks: https://server.minu:8787 Allow them in - + Luba neid @@ -1275,7 +1275,7 @@ Näiteks: https://server.minu:8787 Yesterday, %1 - + Eile, %1 @@ -1329,12 +1329,12 @@ Näiteks: https://server.minu:8787 Registration token - + Registreerimise tunnuslubatunnusluba Please enter a valid registration token. - + Registreerimiseks palun sisesta kehtiv tunnusluba. @@ -1395,12 +1395,12 @@ Näiteks: https://server.minu:8787 Explore Public Rooms - + Tutvu avalike jututubadega Search for public rooms - + Otsi avalikke jututube @@ -1426,12 +1426,12 @@ Näiteks: https://server.minu:8787 Leave Room - + Lahku jututoast Are you sure you want to leave this room? - + Kas sa oled kindel, et soovid lahkuda sellest jututoast? @@ -1538,22 +1538,22 @@ Näiteks: https://server.minu:8787 This room is not encrypted! - + See jututuba on krüptimata! This user is verified. - + See kasutaja on verifitseeritud. This user isn't verified, but is still using the same master key from the first time you met. - + See kasutaja ei ole verifitseeritud, kuid ta kasutab jätkuvalt krüpto jaoks juurvõtmeid sellest ajast, kui te kohtusite. This user has unverified devices! - + Sellel kasutajal on verifitseerimata seadmeid! @@ -1596,7 +1596,7 @@ Näiteks: https://server.minu:8787 Room access - + Ligipääs jututuppa @@ -1616,12 +1616,12 @@ Näiteks: https://server.minu:8787 By knocking - + Koputades Restricted by membership in other rooms - + Piiratud teiste jututubade liikmelisusega @@ -1642,17 +1642,17 @@ Näiteks: https://server.minu:8787 Sticker & Emote Settings - + Kleepsude ja emotikonide seadistused Change - + Muuda Change what packs are enabled, remove packs or create new ones - + Muuda missugused efektipakid on kasutusel, eemalda neid ja loo uusi @@ -1779,22 +1779,22 @@ Näiteks: https://server.minu:8787 Failed to update image pack: %1 - + Pildipaki uuendamine ei õnnestunud: %1 Failed to delete old image pack: %1 - + Vana pildipaki kustutamine ei õnnestunud: %1 Failed to open image: %1 - + Pildi avamine ei õnnestunud: %1 Failed to upload image: %1 - + Faili üleslaadimine ei õnnestunud: %1 @@ -1901,12 +1901,12 @@ Näiteks: https://server.minu:8787 %1 allowed to join this room by knocking. - + %1 pääses jututuppa peale uksele koputamist. %1 allowed members of the following rooms to automatically join this room: %2 - + %1 lubas järgmiste jututubade liikmetel selle jututoaga liituda: %2 @@ -1966,7 +1966,7 @@ Näiteks: https://server.minu:8787 %1 joined via authorisation from %2's server. - + %1 liitus peale autentimist serverist %2. @@ -2104,22 +2104,22 @@ Näiteks: https://server.minu:8787 This room is not encrypted! - + See jututuba on krüptimata! This room contains only verified devices. - + Selles jututoas on vaid verifitseeritud seadmed. This rooms contain verified devices and devices which have never changed their master key. - + Selles jututoas on vaid verifitseeritud seadmed ning nad ei ole kunagi muutnud oma juurvõtit. This room contains unverified devices! - + Selles jututoas leidub verifitseerimata seadmeid! @@ -2455,12 +2455,12 @@ See tavaliselt tähendab, et rakenduse ikoon tegumiribal annab mingit sorti anim Send encrypted messages to verified users only - + Saada krüptitud sõnumeid vaid verifitseeritud kasutajatele Requires a user to be verified to send encrypted messages to them. This improves safety but makes E2EE more tedious. - + Selle tingimuse alusel peab kasutaja olema krüptitud sõnumivahetuse jaoks verifitseeritud. Niisugune nõue parandab turvalisust, kuid teeb läbiva krüptimise natuke ebamugavamaks. @@ -2470,17 +2470,17 @@ See tavaliselt tähendab, et rakenduse ikoon tegumiribal annab mingit sorti anim Automatically replies to key requests from other users, if they are verified, even if that device shouldn't have access to those keys otherwise. - + Kui teised kasutajad on verifitseeritud, siis luba automaatselt vastata nende krüptovõtmete päringutele isegi siis, kui too seade ei peaks saama neid võtmeid kasutada. Online Key Backup - + Krüptovõtmete varundus võrgus Download message encryption keys from and upload to the encrypted online key backup. - + Luba krüptitud võtmete varunduseks laadida sõnumite krüptovõtmeid sinu serverisse või sinu serverist. @@ -2615,7 +2615,7 @@ See tavaliselt tähendab, et rakenduse ikoon tegumiribal annab mingit sorti anim Master signing key - Üldine allkirjavõti + Allkirjastamise juurvõti From 63998a217acd4cdec6058b00f4e63d53df2b945c Mon Sep 17 00:00:00 2001 From: Nicolas Werner Date: Sun, 22 Aug 2021 14:45:57 +0200 Subject: [PATCH 043/232] Add db migration that clears the cache This fixes spaces or stickers not showing up for old databases as well as the wrong format of the state_keys db. --- src/Cache.cpp | 56 ++++++++++++++++++++++++++++++++++++++++++--------- src/Cache_p.h | 1 - 2 files changed, 47 insertions(+), 10 deletions(-) diff --git a/src/Cache.cpp b/src/Cache.cpp index 6b28ca91..1adf8eee 100644 --- a/src/Cache.cpp +++ b/src/Cache.cpp @@ -35,7 +35,7 @@ //! Should be changed when a breaking change occurs in the cache format. //! This will reset client's data. -static const std::string CURRENT_CACHE_FORMAT_VERSION("2020.10.20"); +static const std::string CURRENT_CACHE_FORMAT_VERSION("2021.08.22"); static const std::string SECRET("secret"); //! Keys used for the DB @@ -891,12 +891,6 @@ Cache::setNextBatchToken(lmdb::txn &txn, const std::string &token) syncStateDb_.put(txn, NEXT_BATCH_KEY, token); } -void -Cache::setNextBatchToken(lmdb::txn &txn, const QString &token) -{ - setNextBatchToken(txn, token.toStdString()); -} - bool Cache::isInitialized() { @@ -1137,6 +1131,51 @@ Cache::runMigrations() nhlog::db()->info("Successfully migrated olm sessions."); return true; }}, + {"2021.08.22", + [this]() { + try { + auto txn = lmdb::txn::begin(env_, nullptr); + auto try_drop = [this, &txn](const std::string &dbName) { + try { + auto db = lmdb::dbi::open(txn, dbName.c_str()); + db.drop(txn, true); + } catch (std::exception &e) { + nhlog::db()->warning( + "Failed to drop '{}': {}", dbName, e.what()); + } + }; + + auto room_ids = getRoomIds(txn); + + for (const auto &room : room_ids) { + try_drop(room + "/state"); + try_drop(room + "/state_by_key"); + try_drop(room + "/account_data"); + try_drop(room + "/members"); + try_drop(room + "/mentions"); + try_drop(room + "/events"); + try_drop(room + "/event_order"); + try_drop(room + "/event2order"); + try_drop(room + "/msg2order"); + try_drop(room + "/order2msg"); + try_drop(room + "/pending"); + try_drop(room + "/related"); + } + + // clear db, don't delete + roomsDb_.drop(txn, false); + setNextBatchToken(txn, ""); + + txn.commit(); + } catch (const lmdb::error &) { + nhlog::db()->critical("Failed to clear cache!"); + return false; + } + + nhlog::db()->info( + "Successfully cleared the cache. Will do a clean sync after startup."); + return true; + }}, }; nhlog::db()->info("Running migrations, this may take a while!"); @@ -3230,8 +3269,7 @@ Cache::isNotificationSent(const std::string &event_id) std::vector Cache::getRoomIds(lmdb::txn &txn) { - auto db = lmdb::dbi::open(txn, ROOMS_DB, MDB_CREATE); - auto cursor = lmdb::cursor::open(txn, db); + auto cursor = lmdb::cursor::open(txn, roomsDb_); std::vector rooms; diff --git a/src/Cache_p.h b/src/Cache_p.h index 8322a6af..d97eb531 100644 --- a/src/Cache_p.h +++ b/src/Cache_p.h @@ -692,7 +692,6 @@ private: std::optional userKeys_(const std::string &user_id, lmdb::txn &txn); void setNextBatchToken(lmdb::txn &txn, const std::string &token); - void setNextBatchToken(lmdb::txn &txn, const QString &token); lmdb::env env_; lmdb::dbi syncStateDb_; From 055f0d67091ed02f276ebc0b68234755b899075e Mon Sep 17 00:00:00 2001 From: Nicolas Werner Date: Sun, 22 Aug 2021 14:56:44 +0200 Subject: [PATCH 044/232] Even if you just change the log level, ensure that this compiles before pushing... --- src/Cache.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Cache.cpp b/src/Cache.cpp index 1adf8eee..45fc852d 100644 --- a/src/Cache.cpp +++ b/src/Cache.cpp @@ -1140,7 +1140,7 @@ Cache::runMigrations() auto db = lmdb::dbi::open(txn, dbName.c_str()); db.drop(txn, true); } catch (std::exception &e) { - nhlog::db()->warning( + nhlog::db()->warn( "Failed to drop '{}': {}", dbName, e.what()); } }; From 2cabd107bf5b304442cf858f0b988f42b088251e Mon Sep 17 00:00:00 2001 From: Nicolas Werner Date: Sun, 22 Aug 2021 15:09:18 +0200 Subject: [PATCH 045/232] Useless capture --- src/Cache.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Cache.cpp b/src/Cache.cpp index 45fc852d..bc364c34 100644 --- a/src/Cache.cpp +++ b/src/Cache.cpp @@ -1135,7 +1135,7 @@ Cache::runMigrations() [this]() { try { auto txn = lmdb::txn::begin(env_, nullptr); - auto try_drop = [this, &txn](const std::string &dbName) { + auto try_drop = [&txn](const std::string &dbName) { try { auto db = lmdb::dbi::open(txn, dbName.c_str()); db.drop(txn, true); From b5af1d81df9525ae035a71dd35d94993f75a3a69 Mon Sep 17 00:00:00 2001 From: resolritter Date: Sun, 22 Aug 2021 13:02:26 -0300 Subject: [PATCH 046/232] accept Backtab and Shift-Tab for hovering selection backwards --- resources/qml/ForwardCompleter.qml | 13 +++++++------ resources/qml/MessageInput.qml | 8 ++++++-- resources/qml/QuickSwitcher.qml | 13 +++++++------ 3 files changed, 20 insertions(+), 14 deletions(-) diff --git a/resources/qml/ForwardCompleter.qml b/resources/qml/ForwardCompleter.qml index 26752f92..fdfcec6f 100644 --- a/resources/qml/ForwardCompleter.qml +++ b/resources/qml/ForwardCompleter.qml @@ -80,15 +80,16 @@ Popup { completerPopup.completer.searchString = text; } Keys.onPressed: { - if (event.key == Qt.Key_Up && completerPopup.opened) { + if ((event.key == Qt.Key_Up || event.key == Qt.Key_Backtab) && completerPopup.opened) { event.accepted = true; completerPopup.up(); - } else if (event.key == Qt.Key_Down && completerPopup.opened) { + } else if ((event.key == Qt.Key_Down || event.key == Qt.Key_Tab) && completerPopup.opened) { event.accepted = true; - completerPopup.down(); - } else if (event.key == Qt.Key_Tab && completerPopup.opened) { - event.accepted = true; - completerPopup.down(); + if (event.key == Qt.Key_Tab && (event.modifiers & Qt.ShiftModifier)) { + completerPopup.up(); + } else { + completerPopup.down(); + } } else if (event.matches(StandardKey.InsertParagraphSeparator)) { completerPopup.finishCompletion(); event.accepted = true; diff --git a/resources/qml/MessageInput.qml b/resources/qml/MessageInput.qml index 7599036e..36d8fbce 100644 --- a/resources/qml/MessageInput.qml +++ b/resources/qml/MessageInput.qml @@ -195,7 +195,11 @@ Rectangle { } else if (event.key == Qt.Key_Tab) { event.accepted = true; if (popup.opened) { - popup.up(); + if (event.modifiers & Qt.ShiftModifier) { + popup.down(); + } else { + popup.up(); + } } else { var pos = cursorPosition - 1; while (pos > -1) { @@ -219,7 +223,7 @@ Rectangle { } else if (event.key == Qt.Key_Up && popup.opened) { event.accepted = true; popup.up(); - } else if (event.key == Qt.Key_Down && popup.opened) { + } else if ((event.key == Qt.Key_Down || event.key == Qt.Key_Backtab) && popup.opened) { event.accepted = true; popup.down(); } else if (event.key == Qt.Key_Up && event.modifiers == Qt.NoModifier) { diff --git a/resources/qml/QuickSwitcher.qml b/resources/qml/QuickSwitcher.qml index defcc611..fe1936af 100644 --- a/resources/qml/QuickSwitcher.qml +++ b/resources/qml/QuickSwitcher.qml @@ -39,15 +39,16 @@ Popup { completerPopup.completer.searchString = text; } Keys.onPressed: { - if (event.key == Qt.Key_Up && completerPopup.opened) { + if ((event.key == Qt.Key_Up || event.key == Qt.Key_Backtab) && completerPopup.opened) { event.accepted = true; completerPopup.up(); - } else if (event.key == Qt.Key_Down && completerPopup.opened) { + } else if ((event.key == Qt.Key_Down || event.key == Qt.Key_Tab) && completerPopup.opened) { event.accepted = true; - completerPopup.down(); - } else if (event.key == Qt.Key_Tab && completerPopup.opened) { - event.accepted = true; - completerPopup.down(); + if (event.key == Qt.Key_Tab && (event.modifiers & Qt.ShiftModifier)) { + completerPopup.up(); + } else { + completerPopup.down(); + } } else if (event.matches(StandardKey.InsertParagraphSeparator)) { completerPopup.finishCompletion(); event.accepted = true; From 8e5060b25e6fd9f2a830e2c272bd3ea8b55e1273 Mon Sep 17 00:00:00 2001 From: Weblate Date: Mon, 23 Aug 2021 07:00:46 -0400 Subject: [PATCH 047/232] Translated using Weblate (German) Currently translated at 88.7% (490 of 552 strings) Co-authored-by: DeepBlueV7.X Translate-URL: https://weblate.nheko.im/projects/nheko/nheko-master/de/ Translation: Nheko/nheko --- resources/langs/nheko_de.ts | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/resources/langs/nheko_de.ts b/resources/langs/nheko_de.ts index d4bd2d53..a67e648a 100644 --- a/resources/langs/nheko_de.ts +++ b/resources/langs/nheko_de.ts @@ -17,17 +17,17 @@ You are screen sharing - Du teilst deinen Bildschirm + Bildschirm wird geteilt Hide/Show Picture-in-Picture - Bild-in-Bild Teilen/Verstecken + Bild-in-Bild zeigen/verstecken Unmute Mic - Stummschaltung Aufheben + Mikrofon aktivieren @@ -659,7 +659,7 @@ Cancel - Abbrechen + Abbrechen @@ -712,12 +712,12 @@ Edit - Bearbeiten + Bearbeiten Close - Schließen + Schließen @@ -1267,7 +1267,7 @@ Beispiel: https://mein.server:8787 Read receipts - Lesebestätigungen + Lesebestätigungen From a5688226ca06ca65d2bbdccb5569ad2b5485e790 Mon Sep 17 00:00:00 2001 From: Max Harmathy Date: Wed, 25 Aug 2021 18:00:32 +0200 Subject: [PATCH 048/232] Extend FAQs --- README.md | 24 ++++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 1cf5d705..80f1d08f 100644 --- a/README.md +++ b/README.md @@ -126,11 +126,31 @@ choco install nheko-reborn ### FAQ -## +--- + **Q:** Why don't videos run for me on Windows? **A:** You're probably missing the required video codecs, download [K-Lite Codec Pack](https://codecguide.com/download_kl.htm). -## + +--- + +**Q:** What commands are supported by nheko? + +**A:** See + +--- + +**Q:** Does nheko support end-to-end encryption (EE2E)? + +**A:** Yes, see [feature list](#features) + +--- + +**Q:** Can I test a bleeding edge development version? + +**A:** Checkout nightly builds + +--- ### Build Requirements From 23697e28a3f26b683a4fab309bb700f625414cd5 Mon Sep 17 00:00:00 2001 From: Nicolas Werner Date: Wed, 25 Aug 2021 23:36:13 +0200 Subject: [PATCH 049/232] warn about online keybackup and default to off --- src/UserSettingsPage.cpp | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/src/UserSettingsPage.cpp b/src/UserSettingsPage.cpp index ab1e442c..82b29456 100644 --- a/src/UserSettingsPage.cpp +++ b/src/UserSettingsPage.cpp @@ -124,7 +124,7 @@ UserSettings::load(std::optional profile) .toBool(); onlyShareKeysWithVerifiedUsers_ = settings.value(prefix + "user/only_share_keys_with_verified_users", false).toBool(); - useOnlineKeyBackup_ = settings.value(prefix + "user/online_key_backup", true).toBool(); + useOnlineKeyBackup_ = settings.value(prefix + "user/online_key_backup", false).toBool(); disableCertificateValidation_ = settings.value("disable_certificate_validation", false).toBool(); @@ -1228,6 +1228,17 @@ UserSettingsPage::UserSettingsPage(QSharedPointer settings, QWidge }); connect(useOnlineKeyBackup_, &Toggle::toggled, this, [this](bool enabled) { + if (enabled) { + if (QMessageBox::question( + this, + tr("Enable online key backup"), + tr("The Nheko authors recommend not enabling online key backup until " + "symmetric online key backup is available. Enable anyway?")) != + QMessageBox::StandardButton::NoButton) { + useOnlineKeyBackup_->setState(false); + return; + } + } settings_->setUseOnlineKeyBackup(enabled); }); From d02e77f69abbe1e3af20345b99dc2eb60f63cb8e Mon Sep 17 00:00:00 2001 From: Nicolas Werner Date: Thu, 26 Aug 2021 03:42:58 +0200 Subject: [PATCH 050/232] Implement key backup upload --- src/Olm.cpp | 76 ++++++++++++++++++++++++++++++++++++++++ src/UserSettingsPage.cpp | 2 +- 2 files changed, 77 insertions(+), 1 deletion(-) diff --git a/src/Olm.cpp b/src/Olm.cpp index c0360e1c..9e6c73e0 100644 --- a/src/Olm.cpp +++ b/src/Olm.cpp @@ -32,6 +32,11 @@ constexpr auto MEGOLM_ALGO = "m.megolm.v1.aes-sha2"; } namespace olm { +static void +backup_session_key(const MegolmSessionIndex &idx, + const GroupSessionData &data, + mtx::crypto::InboundGroupSessionPtr &session); + void from_json(const nlohmann::json &obj, OlmMessage &msg) { @@ -682,6 +687,7 @@ encrypt_group_message(const std::string &room_id, const std::string &device_id, index.sender_key = olm::client()->identity_keys().curve25519; auto megolm_session = olm::client()->init_inbound_group_session(session_key); + backup_session_key(index, session_data, megolm_session); cache::saveInboundMegolmSession( index, std::move(megolm_session), session_data); } @@ -801,6 +807,7 @@ create_inbound_megolm_session(const mtx::events::DeviceEventinit_inbound_group_session(roomKey.content.session_key); + backup_session_key(index, data, megolm_session); cache::saveInboundMegolmSession(index, std::move(megolm_session), data); } catch (const lmdb::error &e) { nhlog::crypto()->critical("failed to save inbound megolm session: {}", e.what()); @@ -843,6 +850,7 @@ import_inbound_megolm_session( data.trusted = true; } + backup_session_key(index, data, megolm_session); cache::saveInboundMegolmSession(index, std::move(megolm_session), data); } catch (const lmdb::error &e) { nhlog::crypto()->critical("failed to save inbound megolm session: {}", e.what()); @@ -858,6 +866,74 @@ import_inbound_megolm_session( ChatPage::instance()->receivedSessionKey(index.room_id, index.session_id); } +void +backup_session_key(const MegolmSessionIndex &idx, + const GroupSessionData &data, + mtx::crypto::InboundGroupSessionPtr &session) +{ + try { + if (!UserSettings::instance()->useOnlineKeyBackup()) { + // Online key backup disabled + return; + } + + auto backupVersion = cache::client()->backupVersion(); + if (!backupVersion) { + // no trusted OKB + return; + } + + using namespace mtx::crypto; + + auto decryptedSecret = + cache::secret(mtx::secret_storage::secrets::megolm_backup_v1); + if (!decryptedSecret) { + // no backup key available + return; + } + auto sessionDecryptionKey = to_binary_buf(base642bin(*decryptedSecret)); + + auto public_key = + mtx::crypto::CURVE25519_public_key_from_private(sessionDecryptionKey); + + mtx::responses::backup::SessionData sessionData; + sessionData.algorithm = mtx::crypto::MEGOLM_ALGO; + sessionData.forwarding_curve25519_key_chain = data.forwarding_curve25519_key_chain; + sessionData.sender_claimed_keys["ed25519"] = data.sender_claimed_ed25519_key; + sessionData.sender_key = idx.sender_key; + sessionData.session_key = mtx::crypto::export_session(session.get(), -1); + + auto encrypt_session = mtx::crypto::encrypt_session(sessionData, public_key); + + mtx::responses::backup::SessionBackup bk; + bk.first_message_index = olm_inbound_group_session_first_known_index(session.get()); + bk.forwarded_count = data.forwarding_curve25519_key_chain.size(); + bk.is_verified = false; + bk.session_data = std::move(encrypt_session); + + http::client()->put_room_keys( + backupVersion->version, + idx.room_id, + idx.session_id, + bk, + [idx](const mtx::http::RequestErr &err) { + if (err) { + nhlog::net()->warn( + "failed to backup session key ({}:{}): {} ({})", + idx.room_id, + idx.session_id, + err->matrix_error.error, + static_cast(err->status_code)); + } else { + nhlog::crypto()->debug( + "backed up session key ({}:{})", idx.room_id, idx.session_id); + } + }); + } catch (std::exception &e) { + nhlog::net()->warn("failed to backup session key: {}", e.what()); + } +} + void mark_keys_as_published() { diff --git a/src/UserSettingsPage.cpp b/src/UserSettingsPage.cpp index 82b29456..fa94616f 100644 --- a/src/UserSettingsPage.cpp +++ b/src/UserSettingsPage.cpp @@ -1234,7 +1234,7 @@ UserSettingsPage::UserSettingsPage(QSharedPointer settings, QWidge tr("Enable online key backup"), tr("The Nheko authors recommend not enabling online key backup until " "symmetric online key backup is available. Enable anyway?")) != - QMessageBox::StandardButton::NoButton) { + QMessageBox::StandardButton::Yes) { useOnlineKeyBackup_->setState(false); return; } From 55e43dc4b864e983882a629c80f105b9cc1dae7e Mon Sep 17 00:00:00 2001 From: Nicolas Werner Date: Thu, 26 Aug 2021 03:49:15 +0200 Subject: [PATCH 051/232] bump mtxclient --- CMakeLists.txt | 2 +- io.github.NhekoReborn.Nheko.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index c70a6f5e..c2cef7b7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -382,7 +382,7 @@ if(USE_BUNDLED_MTXCLIENT) FetchContent_Declare( MatrixClient GIT_REPOSITORY https://github.com/Nheko-Reborn/mtxclient.git - GIT_TAG a368db306d0148d1c8c17a532ebe4ff05a2a08c8 + GIT_TAG 8de4df36b43bb3882e71ed609ec8d7c8fe713ce0 ) set(BUILD_LIB_EXAMPLES OFF CACHE INTERNAL "") set(BUILD_LIB_TESTS OFF CACHE INTERNAL "") diff --git a/io.github.NhekoReborn.Nheko.yaml b/io.github.NhekoReborn.Nheko.yaml index b71e1879..c35a3429 100644 --- a/io.github.NhekoReborn.Nheko.yaml +++ b/io.github.NhekoReborn.Nheko.yaml @@ -163,7 +163,7 @@ modules: buildsystem: cmake-ninja name: mtxclient sources: - - commit: a368db306d0148d1c8c17a532ebe4ff05a2a08c8 + - commit: 8de4df36b43bb3882e71ed609ec8d7c8fe713ce0 type: git url: https://github.com/Nheko-Reborn/mtxclient.git - config-opts: From e88fc1996e2caaeac3331176cb9a80f4fa30ff29 Mon Sep 17 00:00:00 2001 From: Nicolas Werner Date: Thu, 26 Aug 2021 04:02:09 +0200 Subject: [PATCH 052/232] Fix warning about double reference qualifier --- src/Olm.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Olm.cpp b/src/Olm.cpp index 9e6c73e0..25f4bfc0 100644 --- a/src/Olm.cpp +++ b/src/Olm.cpp @@ -916,7 +916,7 @@ backup_session_key(const MegolmSessionIndex &idx, idx.room_id, idx.session_id, bk, - [idx](const mtx::http::RequestErr &err) { + [idx](mtx::http::RequestErr err) { if (err) { nhlog::net()->warn( "failed to backup session key ({}:{}): {} ({})", From a1bc2eeaa970f1efa1a23a654efeba3f0b5a2dbe Mon Sep 17 00:00:00 2001 From: Matan Alfasi <18633227+alfasi@users.noreply.github.com> Date: Fri, 27 Aug 2021 01:22:40 +0300 Subject: [PATCH 053/232] Add repo sync command for Gentoo distro users --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 80f1d08f..61dfdfc3 100644 --- a/README.md +++ b/README.md @@ -77,6 +77,7 @@ sudo dnf install nheko #### Gentoo Linux ```bash sudo eselect repository enable guru +sudo emaint sync -r guru sudo emerge -a nheko ``` From db37667266a1517c2c89533eb76cfa5324e145a3 Mon Sep 17 00:00:00 2001 From: Weblate Date: Fri, 27 Aug 2021 22:04:20 -0400 Subject: [PATCH 054/232] Translated using Weblate (Polish) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Currently translated at 25.7% (142 of 552 strings) Translated using Weblate (Polish) Currently translated at 25.7% (142 of 552 strings) Co-authored-by: AXD Co-authored-by: Stanisław Borowy Translate-URL: https://weblate.nheko.im/projects/nheko/nheko-master/pl/ Translation: Nheko/nheko --- resources/langs/nheko_pl.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/resources/langs/nheko_pl.ts b/resources/langs/nheko_pl.ts index 2315503d..1c9a3998 100644 --- a/resources/langs/nheko_pl.ts +++ b/resources/langs/nheko_pl.ts @@ -12,17 +12,17 @@ Connecting... - + Łączenie... You are screen sharing - + Udostępniasz ekran Hide/Show Picture-in-Picture - + Ukryj/Pokaż Obraz w obrazie From 4ddf0674084d9c547e4946c73cc7c1ad3246a8ad Mon Sep 17 00:00:00 2001 From: Nicolas Werner Date: Sat, 28 Aug 2021 16:21:52 +0200 Subject: [PATCH 055/232] Remove CC-BY as main project license Some of our build files and resource files are licensed as that, but not our actual code. This leads to funny issues, that GNOME software considers Nheko proprietary. To work around that, just put GPL there. Fixes https://github.com/flathub/io.github.NhekoReborn.Nheko/issues/1 --- resources/nheko.appdata.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/nheko.appdata.xml b/resources/nheko.appdata.xml index 8cd2bfa3..4fe9e722 100644 --- a/resources/nheko.appdata.xml +++ b/resources/nheko.appdata.xml @@ -3,7 +3,7 @@ nheko.desktop CC0-1.0 - GPL-3.0-or-later and CC-BY + GPL-3.0-or-later nheko Desktop client for the Matrix protocol From 09c041c8ac40d2d3608c7224614fde69e1e4f08b Mon Sep 17 00:00:00 2001 From: Nicolas Werner Date: Sat, 28 Aug 2021 00:38:33 +0200 Subject: [PATCH 056/232] Use in memory media player instead of storing unencrypted files on disk --- CMakeLists.txt | 4 +- resources/qml/ForwardCompleter.qml | 5 +- resources/qml/MessageInput.qml | 9 +- resources/qml/QuickSwitcher.qml | 5 +- .../qml/delegates/PlayableMediaMessage.qml | 52 +++---- src/timeline/TimelineModel.h | 9 ++ src/timeline/TimelineViewManager.cpp | 2 + src/ui/MxcMediaProxy.cpp | 142 ++++++++++++++++++ src/ui/MxcMediaProxy.h | 80 ++++++++++ 9 files changed, 267 insertions(+), 41 deletions(-) create mode 100644 src/ui/MxcMediaProxy.cpp create mode 100644 src/ui/MxcMediaProxy.h diff --git a/CMakeLists.txt b/CMakeLists.txt index c2cef7b7..f24cffef 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -311,6 +311,7 @@ set(SRC_FILES src/ui/InfoMessage.cpp src/ui/Label.cpp src/ui/LoadingIndicator.cpp + src/ui/MxcMediaProxy.cpp src/ui/NhekoCursorShape.cpp src/ui/NhekoDropArea.cpp src/ui/NhekoGlobalObject.cpp @@ -350,7 +351,7 @@ set(SRC_FILES src/MemberList.cpp src/MxcImageProvider.cpp src/Olm.cpp - src/ReadReceiptsModel.cpp + src/ReadReceiptsModel.cpp src/RegisterPage.cpp src/SSOHandler.cpp src/CombinedImagePackModel.cpp @@ -521,6 +522,7 @@ qt5_wrap_cpp(MOC_HEADERS src/ui/InfoMessage.h src/ui/Label.h src/ui/LoadingIndicator.h + src/ui/MxcMediaProxy.h src/ui/Menu.h src/ui/NhekoCursorShape.h src/ui/NhekoDropArea.h diff --git a/resources/qml/ForwardCompleter.qml b/resources/qml/ForwardCompleter.qml index fdfcec6f..eccd6ce9 100644 --- a/resources/qml/ForwardCompleter.qml +++ b/resources/qml/ForwardCompleter.qml @@ -85,11 +85,10 @@ Popup { completerPopup.up(); } else if ((event.key == Qt.Key_Down || event.key == Qt.Key_Tab) && completerPopup.opened) { event.accepted = true; - if (event.key == Qt.Key_Tab && (event.modifiers & Qt.ShiftModifier)) { + if (event.key == Qt.Key_Tab && (event.modifiers & Qt.ShiftModifier)) completerPopup.up(); - } else { + else completerPopup.down(); - } } else if (event.matches(StandardKey.InsertParagraphSeparator)) { completerPopup.finishCompletion(); event.accepted = true; diff --git a/resources/qml/MessageInput.qml b/resources/qml/MessageInput.qml index 36d8fbce..e1bf3f06 100644 --- a/resources/qml/MessageInput.qml +++ b/resources/qml/MessageInput.qml @@ -134,9 +134,9 @@ Rectangle { return ; room.input.updateState(selectionStart, selectionEnd, cursorPosition, text); - if (popup.opened && cursorPosition <= completerTriggeredAt) { + if (popup.opened && cursorPosition <= completerTriggeredAt) popup.close(); - } + if (popup.opened) popup.completer.setSearchString(messageInput.getText(completerTriggeredAt, cursorPosition)); @@ -195,11 +195,10 @@ Rectangle { } else if (event.key == Qt.Key_Tab) { event.accepted = true; if (popup.opened) { - if (event.modifiers & Qt.ShiftModifier) { + if (event.modifiers & Qt.ShiftModifier) popup.down(); - } else { + else popup.up(); - } } else { var pos = cursorPosition - 1; while (pos > -1) { diff --git a/resources/qml/QuickSwitcher.qml b/resources/qml/QuickSwitcher.qml index fe1936af..c7141c81 100644 --- a/resources/qml/QuickSwitcher.qml +++ b/resources/qml/QuickSwitcher.qml @@ -44,11 +44,10 @@ Popup { completerPopup.up(); } else if ((event.key == Qt.Key_Down || event.key == Qt.Key_Tab) && completerPopup.opened) { event.accepted = true; - if (event.key == Qt.Key_Tab && (event.modifiers & Qt.ShiftModifier)) { + if (event.key == Qt.Key_Tab && (event.modifiers & Qt.ShiftModifier)) completerPopup.up(); - } else { + else completerPopup.down(); - } } else if (event.matches(StandardKey.InsertParagraphSeparator)) { completerPopup.finishCompletion(); event.accepted = true; diff --git a/resources/qml/delegates/PlayableMediaMessage.qml b/resources/qml/delegates/PlayableMediaMessage.qml index 73c74ec0..94c058ab 100644 --- a/resources/qml/delegates/PlayableMediaMessage.qml +++ b/resources/qml/delegates/PlayableMediaMessage.qml @@ -3,9 +3,9 @@ // SPDX-License-Identifier: GPL-3.0-or-later import "../" -import QtMultimedia 5.6 -import QtQuick 2.12 -import QtQuick.Controls 2.1 +import QtMultimedia 5.15 +import QtQuick 2.15 +import QtQuick.Controls 2.15 import QtQuick.Layouts 1.2 import im.nheko 1.0 @@ -55,7 +55,8 @@ Rectangle { VideoOutput { anchors.fill: parent fillMode: VideoOutput.PreserveAspectFit - source: media + flushMode: VideoOutput.FirstFrame + source: mxcmedia } } @@ -93,15 +94,15 @@ Rectangle { return hh + ":" + mm + ":" + ss; } - positionText.text = formatTime(new Date(media.position)); - durationText.text = formatTime(new Date(media.duration)); + positionText.text = formatTime(new Date(mxcmedia.position)); + durationText.text = formatTime(new Date(mxcmedia.duration)); } Layout.fillWidth: true - value: media.position + value: mxcmedia.position from: 0 - to: media.duration - onMoved: media.seek(value) + to: mxcmedia.duration + onMoved: mxcmedia.position = value onValueChanged: updatePositionTexts() palette: Nheko.colors } @@ -132,15 +133,15 @@ Rectangle { onClicked: { switch (button.state) { case "": - room.cacheMedia(eventId); + mxcmedia.eventId = eventId; break; case "stopped": - media.play(); + mxcmedia.play(); console.log("play"); button.state = "playing"; break; case "playing": - media.pause(); + mxcmedia.pause(); console.log("pause"); button.state = "stopped"; break; @@ -172,29 +173,22 @@ Rectangle { cursorShape: Qt.PointingHandCursor } - MediaPlayer { - id: media + MxcMedia { + id: mxcmedia + roomm: room onError: console.log(errorString) - onStatusChanged: { - if (status == MediaPlayer.Loaded) + onMediaStatusChanged: { + if (status == MxcMedia.LoadedMedia) { progress.updatePositionTexts(); - - } - onStopped: button.state = "stopped" - } - - Connections { - function onMediaCached(mxcUrl, cacheUrl) { - if (mxcUrl == url) { - media.source = cacheUrl; button.state = "stopped"; - console.log("media loaded: " + mxcUrl + " at " + cacheUrl); } - console.log("media cached: " + mxcUrl + " at " + cacheUrl); } - - target: room + onStateChanged: { + if (state == MxcMedia.StoppedState) { + button.state = "stopped"; + } + } } } diff --git a/src/timeline/TimelineModel.h b/src/timeline/TimelineModel.h index e3ca8811..417fbb7f 100644 --- a/src/timeline/TimelineModel.h +++ b/src/timeline/TimelineModel.h @@ -293,6 +293,15 @@ public: crypto::Trust trustlevel() const; int roomMemberCount() const; + std::optional eventById(const QString &id) + { + auto e = events.get(id.toStdString(), ""); + if (e) + return *e; + else + return std::nullopt; + } + public slots: void setCurrentIndex(int index); int currentIndex() const { return idToIndex(currentId); } diff --git a/src/timeline/TimelineViewManager.cpp b/src/timeline/TimelineViewManager.cpp index 97b60b0c..f42ec02f 100644 --- a/src/timeline/TimelineViewManager.cpp +++ b/src/timeline/TimelineViewManager.cpp @@ -35,6 +35,7 @@ #include "dialogs/ImageOverlay.h" #include "emoji/EmojiModel.h" #include "emoji/Provider.h" +#include "ui/MxcMediaProxy.h" #include "ui/NhekoCursorShape.h" #include "ui/NhekoDropArea.h" #include "ui/NhekoGlobalObject.h" @@ -176,6 +177,7 @@ TimelineViewManager::TimelineViewManager(CallManager *callManager, ChatPage *par qmlRegisterType("im.nheko", 1, 0, "DelegateChooser"); qmlRegisterType("im.nheko", 1, 0, "NhekoDropArea"); qmlRegisterType("im.nheko", 1, 0, "CursorShape"); + qmlRegisterType("im.nheko", 1, 0, "MxcMedia"); qmlRegisterUncreatableType( "im.nheko", 1, 0, "DeviceVerificationFlow", "Can't create verification flow from QML!"); qmlRegisterUncreatableType( diff --git a/src/ui/MxcMediaProxy.cpp b/src/ui/MxcMediaProxy.cpp new file mode 100644 index 00000000..c1de2c31 --- /dev/null +++ b/src/ui/MxcMediaProxy.cpp @@ -0,0 +1,142 @@ +// SPDX-FileCopyrightText: 2021 Nheko Contributors +// +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "MxcMediaProxy.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "EventAccessors.h" +#include "Logging.h" +#include "MatrixClient.h" +#include "timeline/TimelineModel.h" + +void +MxcMediaProxy::setVideoSurface(QAbstractVideoSurface *surface) +{ + qDebug() << "Changing surface"; + m_surface = surface; + setVideoOutput(m_surface); +} + +QAbstractVideoSurface * +MxcMediaProxy::getVideoSurface() +{ + return m_surface; +} + +void +MxcMediaProxy::startDownload() +{ + if (!room_) + return; + if (eventId_.isEmpty()) + return; + + auto event = room_->eventById(eventId_); + if (!event) { + nhlog::ui()->error("Failed to load media for event {}, event not found.", + eventId_.toStdString()); + return; + } + + QString mxcUrl = QString::fromStdString(mtx::accessors::url(*event)); + QString originalFilename = QString::fromStdString(mtx::accessors::filename(*event)); + QString mimeType = QString::fromStdString(mtx::accessors::mimetype(*event)); + + auto encryptionInfo = mtx::accessors::file(*event); + + // If the message is a link to a non mxcUrl, don't download it + if (!mxcUrl.startsWith("mxc://")) { + return; + } + + QString suffix = QMimeDatabase().mimeTypeForName(mimeType).preferredSuffix(); + + const auto url = mxcUrl.toStdString(); + const auto name = QString(mxcUrl).remove("mxc://"); + QFileInfo filename(QString("%1/media_cache/media/%2.%3") + .arg(QStandardPaths::writableLocation(QStandardPaths::CacheLocation)) + .arg(name) + .arg(suffix)); + if (QDir::cleanPath(name) != name) { + nhlog::net()->warn("mxcUrl '{}' is not safe, not downloading file", url); + return; + } + + QDir().mkpath(filename.path()); + + QPointer self = this; + + auto processBuffer = [this, encryptionInfo, filename, self](QIODevice &device) { + if (!self) + return; + + if (encryptionInfo) { + QByteArray ba = device.readAll(); + std::string temp(ba.constData(), ba.size()); + temp = mtx::crypto::to_string( + mtx::crypto::decrypt_file(temp, encryptionInfo.value())); + buffer.setData(temp.data(), temp.size()); + } else { + buffer.setData(device.readAll()); + } + buffer.open(QIODevice::ReadOnly); + buffer.reset(); + + QTimer::singleShot(0, this, [this, self, filename] { + nhlog::ui()->info("Playing buffer with size: {}, {}", + buffer.bytesAvailable(), + buffer.isOpen()); + self->setMedia(QMediaContent(filename.fileName()), &buffer); + emit loadedChanged(); + }); + }; + + if (filename.isReadable()) { + QFile f(filename.filePath()); + if (f.open(QIODevice::ReadOnly)) { + processBuffer(f); + return; + } + } + + http::client()->download( + url, + [filename, url, processBuffer](const std::string &data, + const std::string &, + const std::string &, + mtx::http::RequestErr err) { + if (err) { + nhlog::net()->warn("failed to retrieve media {}: {} {}", + url, + err->matrix_error.error, + static_cast(err->status_code)); + return; + } + + try { + QFile file(filename.filePath()); + + if (!file.open(QIODevice::WriteOnly)) + return; + + QByteArray ba(data.data(), (int)data.size()); + file.write(ba); + file.close(); + + QBuffer buf(&ba); + buf.open(QBuffer::ReadOnly); + processBuffer(buf); + } catch (const std::exception &e) { + nhlog::ui()->warn("Error while saving file to: {}", e.what()); + } + }); +} diff --git a/src/ui/MxcMediaProxy.h b/src/ui/MxcMediaProxy.h new file mode 100644 index 00000000..14541815 --- /dev/null +++ b/src/ui/MxcMediaProxy.h @@ -0,0 +1,80 @@ +// SPDX-FileCopyrightText: 2021 Nheko Contributors +// +// SPDX-License-Identifier: GPL-3.0-or-later + +#pragma once + +#include +#include +#include +#include +#include +#include +#include + +#include "Logging.h" + +class TimelineModel; + +// I failed to get my own buffer into the MediaPlayer in qml, so just make our own. For that we just +// need the videoSurface property, so that part is really easy! +class MxcMediaProxy : public QMediaPlayer +{ + Q_OBJECT + Q_PROPERTY(TimelineModel *roomm READ room WRITE setRoom NOTIFY roomChanged REQUIRED) + Q_PROPERTY(QString eventId READ eventId WRITE setEventId NOTIFY eventIdChanged) + Q_PROPERTY(QAbstractVideoSurface *videoSurface READ getVideoSurface WRITE setVideoSurface) + Q_PROPERTY(bool loaded READ loaded NOTIFY loadedChanged) +public: + MxcMediaProxy(QObject *parent = nullptr) + : QMediaPlayer(parent) + { + connect(this, &MxcMediaProxy::eventIdChanged, &MxcMediaProxy::startDownload); + connect(this, &MxcMediaProxy::roomChanged, &MxcMediaProxy::startDownload); + connect(this, + qOverload(&MxcMediaProxy::error), + [this](QMediaPlayer::Error error) { + nhlog::ui()->info("Media player error {} and errorStr {}", + error, + this->errorString().toStdString()); + }); + connect(this, + &MxcMediaProxy::mediaStatusChanged, + [this](QMediaPlayer::MediaStatus status) { + nhlog::ui()->info( + "Media player status {} and error {}", status, this->error()); + }); + } + + bool loaded() const { return buffer.size() > 0; } + QString eventId() const { return eventId_; } + TimelineModel *room() const { return room_; } + void setEventId(QString newEventId) + { + eventId_ = newEventId; + emit eventIdChanged(); + } + void setRoom(TimelineModel *room) + { + room_ = room; + emit roomChanged(); + } + void setVideoSurface(QAbstractVideoSurface *surface); + QAbstractVideoSurface *getVideoSurface(); + +signals: + void roomChanged(); + void eventIdChanged(); + void loadedChanged(); + void newBuffer(QMediaContent, QIODevice *buf); + +private slots: + void startDownload(); + +private: + TimelineModel *room_ = nullptr; + QString eventId_; + QString filename_; + QBuffer buffer; + QAbstractVideoSurface *m_surface = nullptr; +}; From ef068ac2b30bd5f9ed0f299dbc75eb3ace000042 Mon Sep 17 00:00:00 2001 From: Nicolas Werner Date: Sun, 29 Aug 2021 05:20:23 +0200 Subject: [PATCH 057/232] Support animated images fixes #461 --- CMakeLists.txt | 2 + resources/qml/delegates/ImageMessage.qml | 69 ++++---- resources/qml/delegates/MessageDelegate.qml | 2 + .../qml/delegates/PlayableMediaMessage.qml | 4 +- src/timeline/TimelineViewManager.cpp | 2 + src/ui/MxcAnimatedImage.cpp | 164 ++++++++++++++++++ src/ui/MxcAnimatedImage.h | 79 +++++++++ src/ui/MxcMediaProxy.cpp | 4 +- 8 files changed, 293 insertions(+), 33 deletions(-) create mode 100644 src/ui/MxcAnimatedImage.cpp create mode 100644 src/ui/MxcAnimatedImage.h diff --git a/CMakeLists.txt b/CMakeLists.txt index f24cffef..fb83ae2f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -311,6 +311,7 @@ set(SRC_FILES src/ui/InfoMessage.cpp src/ui/Label.cpp src/ui/LoadingIndicator.cpp + src/ui/MxcAnimatedImage.cpp src/ui/MxcMediaProxy.cpp src/ui/NhekoCursorShape.cpp src/ui/NhekoDropArea.cpp @@ -522,6 +523,7 @@ qt5_wrap_cpp(MOC_HEADERS src/ui/InfoMessage.h src/ui/Label.h src/ui/LoadingIndicator.h + src/ui/MxcAnimatedImage.h src/ui/MxcMediaProxy.h src/ui/Menu.h src/ui/NhekoCursorShape.h diff --git a/resources/qml/delegates/ImageMessage.qml b/resources/qml/delegates/ImageMessage.qml index b432018c..f39176b3 100644 --- a/resources/qml/delegates/ImageMessage.qml +++ b/resources/qml/delegates/ImageMessage.qml @@ -14,6 +14,7 @@ Item { required property string body required property string filename required property bool isReply + required property string eventId property double tempWidth: Math.min(parent ? parent.width : undefined, originalWidth < 1 ? 200 : originalWidth) property double tempHeight: tempWidth * proportionalHeight property double divisor: isReply ? 5 : 3 @@ -37,6 +38,7 @@ Item { Image { id: img + visible: !mxcimage.loaded anchors.fill: parent source: url.replace("mxc://", "image://MxcImage/") asynchronous: true @@ -53,38 +55,47 @@ Item { gesturePolicy: TapHandler.ReleaseWithinBounds } - HoverHandler { - id: mouseArea + } + + MxcAnimatedImage { + id: mxcimage + + visible: loaded + anchors.fill: parent + roomm: room + eventId: parent.eventId + } + + HoverHandler { + id: mouseArea + } + + Item { + id: overlay + + anchors.fill: parent + visible: mouseArea.hovered + + Rectangle { + id: container + + width: parent.width + implicitHeight: imgcaption.implicitHeight + anchors.bottom: overlay.bottom + color: Nheko.colors.window + opacity: 0.75 } - Item { - id: overlay - - anchors.fill: parent - visible: mouseArea.hovered - - Rectangle { - id: container - - width: parent.width - implicitHeight: imgcaption.implicitHeight - anchors.bottom: overlay.bottom - color: Nheko.colors.window - opacity: 0.75 - } - - Text { - id: imgcaption - - anchors.fill: container - elide: Text.ElideMiddle - horizontalAlignment: Text.AlignHCenter - verticalAlignment: Text.AlignVCenter - // See this MSC: https://github.com/matrix-org/matrix-doc/pull/2530 - text: filename ? filename : body - color: Nheko.colors.text - } + Text { + id: imgcaption + anchors.fill: container + elide: Text.ElideMiddle + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + // See this MSC: https://github.com/matrix-org/matrix-doc/pull/2530 + text: filename ? filename : body + color: Nheko.colors.text } } diff --git a/resources/qml/delegates/MessageDelegate.qml b/resources/qml/delegates/MessageDelegate.qml index fde43559..4086a1a8 100644 --- a/resources/qml/delegates/MessageDelegate.qml +++ b/resources/qml/delegates/MessageDelegate.qml @@ -102,6 +102,7 @@ Item { body: d.body filename: d.filename isReply: d.isReply + eventId: d.eventId } } @@ -118,6 +119,7 @@ Item { body: d.body filename: d.filename isReply: d.isReply + eventId: d.eventId } } diff --git a/resources/qml/delegates/PlayableMediaMessage.qml b/resources/qml/delegates/PlayableMediaMessage.qml index 94c058ab..eb6db291 100644 --- a/resources/qml/delegates/PlayableMediaMessage.qml +++ b/resources/qml/delegates/PlayableMediaMessage.qml @@ -185,9 +185,9 @@ Rectangle { } } onStateChanged: { - if (state == MxcMedia.StoppedState) { + if (state == MxcMedia.StoppedState) button.state = "stopped"; - } + } } diff --git a/src/timeline/TimelineViewManager.cpp b/src/timeline/TimelineViewManager.cpp index f42ec02f..681cbe09 100644 --- a/src/timeline/TimelineViewManager.cpp +++ b/src/timeline/TimelineViewManager.cpp @@ -35,6 +35,7 @@ #include "dialogs/ImageOverlay.h" #include "emoji/EmojiModel.h" #include "emoji/Provider.h" +#include "ui/MxcAnimatedImage.h" #include "ui/MxcMediaProxy.h" #include "ui/NhekoCursorShape.h" #include "ui/NhekoDropArea.h" @@ -177,6 +178,7 @@ TimelineViewManager::TimelineViewManager(CallManager *callManager, ChatPage *par qmlRegisterType("im.nheko", 1, 0, "DelegateChooser"); qmlRegisterType("im.nheko", 1, 0, "NhekoDropArea"); qmlRegisterType("im.nheko", 1, 0, "CursorShape"); + qmlRegisterType("im.nheko", 1, 0, "MxcAnimatedImage"); qmlRegisterType("im.nheko", 1, 0, "MxcMedia"); qmlRegisterUncreatableType( "im.nheko", 1, 0, "DeviceVerificationFlow", "Can't create verification flow from QML!"); diff --git a/src/ui/MxcAnimatedImage.cpp b/src/ui/MxcAnimatedImage.cpp new file mode 100644 index 00000000..cfc03827 --- /dev/null +++ b/src/ui/MxcAnimatedImage.cpp @@ -0,0 +1,164 @@ +// SPDX-FileCopyrightText: 2021 Nheko Contributors +// +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "MxcAnimatedImage.h" + +#include +#include +#include +#include +#include +#include + +#include "EventAccessors.h" +#include "Logging.h" +#include "MatrixClient.h" +#include "timeline/TimelineModel.h" + +void +MxcAnimatedImage::startDownload() +{ + if (!room_) + return; + if (eventId_.isEmpty()) + return; + + auto event = room_->eventById(eventId_); + if (!event) { + nhlog::ui()->error("Failed to load media for event {}, event not found.", + eventId_.toStdString()); + return; + } + + QByteArray mimeType = QString::fromStdString(mtx::accessors::mimetype(*event)).toUtf8(); + + animatable_ = QMovie::supportedFormats().contains(mimeType.split('/').back()); + animatableChanged(); + + if (!animatable_) + return; + + QString mxcUrl = QString::fromStdString(mtx::accessors::url(*event)); + QString originalFilename = QString::fromStdString(mtx::accessors::filename(*event)); + + auto encryptionInfo = mtx::accessors::file(*event); + + // If the message is a link to a non mxcUrl, don't download it + if (!mxcUrl.startsWith("mxc://")) { + return; + } + + QString suffix = QMimeDatabase().mimeTypeForName(mimeType).preferredSuffix(); + + const auto url = mxcUrl.toStdString(); + const auto name = QString(mxcUrl).remove("mxc://"); + QFileInfo filename(QString("%1/media_cache/media/%2.%3") + .arg(QStandardPaths::writableLocation(QStandardPaths::CacheLocation)) + .arg(name) + .arg(suffix)); + if (QDir::cleanPath(name) != name) { + nhlog::net()->warn("mxcUrl '{}' is not safe, not downloading file", url); + return; + } + + QDir().mkpath(filename.path()); + + QPointer self = this; + + auto processBuffer = [this, mimeType, encryptionInfo, self](QIODevice &device) { + if (!self) + return; + + if (buffer.isOpen()) { + movie.stop(); + movie.setDevice(nullptr); + buffer.close(); + } + + if (encryptionInfo) { + QByteArray ba = device.readAll(); + std::string temp(ba.constData(), ba.size()); + temp = mtx::crypto::to_string( + mtx::crypto::decrypt_file(temp, encryptionInfo.value())); + buffer.setData(temp.data(), temp.size()); + } else { + buffer.setData(device.readAll()); + } + buffer.open(QIODevice::ReadOnly); + buffer.reset(); + + QTimer::singleShot(0, this, [this, mimeType] { + nhlog::ui()->info("Playing movie with size: {}, {}", + buffer.bytesAvailable(), + buffer.isOpen()); + movie.setFormat(mimeType); + movie.setDevice(&buffer); + movie.start(); + emit loadedChanged(); + }); + }; + + if (filename.isReadable()) { + QFile f(filename.filePath()); + if (f.open(QIODevice::ReadOnly)) { + processBuffer(f); + return; + } + } + + http::client()->download( + url, + [filename, url, processBuffer](const std::string &data, + const std::string &, + const std::string &, + mtx::http::RequestErr err) { + if (err) { + nhlog::net()->warn("failed to retrieve media {}: {} {}", + url, + err->matrix_error.error, + static_cast(err->status_code)); + return; + } + + try { + QFile file(filename.filePath()); + + if (!file.open(QIODevice::WriteOnly)) + return; + + QByteArray ba(data.data(), (int)data.size()); + file.write(ba); + file.close(); + + QBuffer buf(&ba); + buf.open(QBuffer::ReadOnly); + processBuffer(buf); + } catch (const std::exception &e) { + nhlog::ui()->warn("Error while saving file to: {}", e.what()); + } + }); +} + +QSGNode * +MxcAnimatedImage::updatePaintNode(QSGNode *oldNode, QQuickItem::UpdatePaintNodeData *) +{ + imageDirty = false; + QSGImageNode *n = static_cast(oldNode); + if (!n) + n = window()->createImageNode(); + + // n->setTexture(nullptr); + auto img = movie.currentImage(); + if (!img.isNull()) + n->setTexture(window()->createTextureFromImage(img)); + else + return nullptr; + + n->setSourceRect(img.rect()); + n->setRect(QRect(0, 0, width(), height())); + n->setFiltering(QSGTexture::Linear); + n->setMipmapFiltering(QSGTexture::Linear); + + return n; +} diff --git a/src/ui/MxcAnimatedImage.h b/src/ui/MxcAnimatedImage.h new file mode 100644 index 00000000..7b9502e0 --- /dev/null +++ b/src/ui/MxcAnimatedImage.h @@ -0,0 +1,79 @@ +// SPDX-FileCopyrightText: 2021 Nheko Contributors +// +// SPDX-License-Identifier: GPL-3.0-or-later + +#pragma once + +#include +#include +#include +#include + +class TimelineModel; + +// This is an AnimatedImage, that can draw encrypted images +class MxcAnimatedImage : public QQuickItem +{ + Q_OBJECT + Q_PROPERTY(TimelineModel *roomm READ room WRITE setRoom NOTIFY roomChanged REQUIRED) + Q_PROPERTY(QString eventId READ eventId WRITE setEventId NOTIFY eventIdChanged) + Q_PROPERTY(bool animatable READ animatable NOTIFY animatableChanged) + Q_PROPERTY(bool loaded READ loaded NOTIFY loadedChanged) +public: + MxcAnimatedImage(QQuickItem *parent = nullptr) + : QQuickItem(parent) + { + connect(this, &MxcAnimatedImage::eventIdChanged, &MxcAnimatedImage::startDownload); + connect(this, &MxcAnimatedImage::roomChanged, &MxcAnimatedImage::startDownload); + connect(&movie, &QMovie::frameChanged, this, &MxcAnimatedImage::newFrame); + setFlag(QQuickItem::ItemHasContents); + // setAcceptHoverEvents(true); + } + + bool animatable() const { return animatable_; } + bool loaded() const { return buffer.size() > 0; } + QString eventId() const { return eventId_; } + TimelineModel *room() const { return room_; } + void setEventId(QString newEventId) + { + if (eventId_ != newEventId) { + eventId_ = newEventId; + emit eventIdChanged(); + } + } + void setRoom(TimelineModel *room) + { + if (room_ != room) { + room_ = room; + emit roomChanged(); + } + } + + QSGNode *updatePaintNode(QSGNode *oldNode, + QQuickItem::UpdatePaintNodeData *updatePaintNodeData) override; + +signals: + void roomChanged(); + void eventIdChanged(); + void animatableChanged(); + void loadedChanged(); + +private slots: + void startDownload(); + void newFrame(int frame) + { + currentFrame = frame; + imageDirty = true; + update(); + } + +private: + TimelineModel *room_ = nullptr; + QString eventId_; + QString filename_; + bool animatable_ = false; + QBuffer buffer; + QMovie movie; + int currentFrame = 0; + bool imageDirty = true; +}; diff --git a/src/ui/MxcMediaProxy.cpp b/src/ui/MxcMediaProxy.cpp index c1de2c31..dc65de7c 100644 --- a/src/ui/MxcMediaProxy.cpp +++ b/src/ui/MxcMediaProxy.cpp @@ -91,11 +91,11 @@ MxcMediaProxy::startDownload() buffer.open(QIODevice::ReadOnly); buffer.reset(); - QTimer::singleShot(0, this, [this, self, filename] { + QTimer::singleShot(0, this, [this, filename] { nhlog::ui()->info("Playing buffer with size: {}, {}", buffer.bytesAvailable(), buffer.isOpen()); - self->setMedia(QMediaContent(filename.fileName()), &buffer); + this->setMedia(QMediaContent(filename.fileName()), &buffer); emit loadedChanged(); }); }; From 47c7c4c777d848a96c799374dedf999ca3d320d9 Mon Sep 17 00:00:00 2001 From: Nicolas Werner Date: Sun, 29 Aug 2021 14:57:32 +0200 Subject: [PATCH 058/232] cleanup QSettings usage a bit --- src/ChatPage.cpp | 13 ++++++------- src/MainWindow.cpp | 22 +++++++++++----------- src/main.cpp | 1 - 3 files changed, 17 insertions(+), 19 deletions(-) diff --git a/src/ChatPage.cpp b/src/ChatPage.cpp index 615e96fe..d0de7ab8 100644 --- a/src/ChatPage.cpp +++ b/src/ChatPage.cpp @@ -6,7 +6,6 @@ #include #include #include -#include #include @@ -277,15 +276,15 @@ ChatPage::resetUI() void ChatPage::deleteConfigs() { - QSettings settings; + auto settings = UserSettings::instance()->qsettings(); if (UserSettings::instance()->profile() != "") { - settings.beginGroup("profile"); - settings.beginGroup(UserSettings::instance()->profile()); + settings->beginGroup("profile"); + settings->beginGroup(UserSettings::instance()->profile()); } - settings.beginGroup("auth"); - settings.remove(""); - settings.endGroup(); // auth + settings->beginGroup("auth"); + settings->remove(""); + settings->endGroup(); // auth http::client()->shutdown(); cache::deleteData(); diff --git a/src/MainWindow.cpp b/src/MainWindow.cpp index 396e1ab1..7eadc6df 100644 --- a/src/MainWindow.cpp +++ b/src/MainWindow.cpp @@ -7,7 +7,6 @@ #include #include #include -#include #include #include @@ -188,9 +187,10 @@ MainWindow::event(QEvent *event) void MainWindow::restoreWindowSize() { - QSettings settings; - int savedWidth = settings.value("window/width").toInt(); - int savedheight = settings.value("window/height").toInt(); + int savedWidth = userSettings_->qsettings()->value("window/width").toInt(); + int savedheight = userSettings_->qsettings()->value("window/height").toInt(); + + nhlog::ui()->info("Restoring window size {}x{}", savedWidth, savedheight); if (savedWidth == 0 || savedheight == 0) resize(conf::window::width, conf::window::height); @@ -201,11 +201,11 @@ MainWindow::restoreWindowSize() void MainWindow::saveCurrentWindowSize() { - QSettings settings; + auto settings = userSettings_->qsettings(); QSize current = size(); - settings.setValue("window/width", current.width()); - settings.setValue("window/height", current.height()); + settings->setValue("window/width", current.width()); + settings->setValue("window/height", current.height()); } void @@ -301,14 +301,14 @@ MainWindow::iconActivated(QSystemTrayIcon::ActivationReason reason) bool MainWindow::hasActiveUser() { - QSettings settings; + auto settings = userSettings_->qsettings(); QString prefix; if (userSettings_->profile() != "") prefix = "profile/" + userSettings_->profile() + "/"; - return settings.contains(prefix + "auth/access_token") && - settings.contains(prefix + "auth/home_server") && - settings.contains(prefix + "auth/user_id"); + return settings->contains(prefix + "auth/access_token") && + settings->contains(prefix + "auth/home_server") && + settings->contains(prefix + "auth/user_id"); } void diff --git a/src/main.cpp b/src/main.cpp index 29e93d49..09168e0c 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -18,7 +18,6 @@ #include #include #include -#include #include #include From 15bf643347e507513d6999dd346f0cce9c7952c8 Mon Sep 17 00:00:00 2001 From: Nicolas Werner Date: Sun, 29 Aug 2021 15:33:39 +0200 Subject: [PATCH 059/232] Add option to only play animated images on hover --- resources/qml/delegates/ImageMessage.qml | 1 + src/UserSettingsPage.cpp | 21 +++++++++++++++++++++ src/UserSettingsPage.h | 7 +++++++ src/ui/MxcAnimatedImage.cpp | 9 ++++++++- src/ui/MxcAnimatedImage.h | 12 ++++++++++++ 5 files changed, 49 insertions(+), 1 deletion(-) diff --git a/resources/qml/delegates/ImageMessage.qml b/resources/qml/delegates/ImageMessage.qml index f39176b3..64e365c8 100644 --- a/resources/qml/delegates/ImageMessage.qml +++ b/resources/qml/delegates/ImageMessage.qml @@ -63,6 +63,7 @@ Item { visible: loaded anchors.fill: parent roomm: room + play: !Settings.animateImagesOnHover || mouseArea.hovered eventId: parent.eventId } diff --git a/src/UserSettingsPage.cpp b/src/UserSettingsPage.cpp index fa94616f..d55a0f61 100644 --- a/src/UserSettingsPage.cpp +++ b/src/UserSettingsPage.cpp @@ -79,6 +79,7 @@ UserSettings::load(std::optional profile) enlargeEmojiOnlyMessages_ = settings.value("user/timeline/enlarge_emoji_only_msg", false).toBool(); markdown_ = settings.value("user/markdown_enabled", true).toBool(); + animateImagesOnHover_ = settings.value("user/animate_images_on_hover", false).toBool(); typingNotifications_ = settings.value("user/typing_notifications", true).toBool(); sortByImportance_ = settings.value("user/sort_by_unread", true).toBool(); readReceipts_ = settings.value("user/read_receipts", true).toBool(); @@ -207,6 +208,16 @@ UserSettings::setMarkdown(bool state) save(); } +void +UserSettings::setAnimateImagesOnHover(bool state) +{ + if (state == animateImagesOnHover_) + return; + animateImagesOnHover_ = state; + emit animateImagesOnHoverChanged(state); + save(); +} + void UserSettings::setReadReceipts(bool state) { @@ -643,6 +654,7 @@ UserSettings::save() settings.setValue("group_view", groupView_); settings.setValue("hidden_tags", hiddenTags_); settings.setValue("markdown_enabled", markdown_); + settings.setValue("animate_images_on_hover", animateImagesOnHover_); settings.setValue("desktop_notifications", hasDesktopNotifications_); settings.setValue("alert_on_notification", hasAlertOnNotification_); settings.setValue("theme", theme()); @@ -747,6 +759,7 @@ UserSettingsPage::UserSettingsPage(QSharedPointer settings, QWidge sortByImportance_ = new Toggle{this}; readReceipts_ = new Toggle{this}; markdown_ = new Toggle{this}; + animateImagesOnHover_ = new Toggle{this}; desktopNotifications_ = new Toggle{this}; alertOnNotification_ = new Toggle{this}; useStunServer_ = new Toggle{this}; @@ -779,6 +792,7 @@ UserSettingsPage::UserSettingsPage(QSharedPointer settings, QWidge sortByImportance_->setChecked(settings_->sortByImportance()); readReceipts_->setChecked(settings_->readReceipts()); markdown_->setChecked(settings_->markdown()); + animateImagesOnHover_->setChecked(settings_->animateImagesOnHover()); desktopNotifications_->setChecked(settings_->hasDesktopNotifications()); alertOnNotification_->setChecked(settings_->hasAlertOnNotification()); useStunServer_->setChecked(settings_->useStunServer()); @@ -973,6 +987,9 @@ UserSettingsPage::UserSettingsPage(QSharedPointer settings, QWidge markdown_, tr("Allow using markdown in messages.\nWhen disabled, all messages are sent as a plain " "text.")); + boxWrap(tr("Play animated images only on hover"), + animateImagesOnHover_, + tr("Plays media like GIFs or APNGs only when explicitly hovering over them.")); boxWrap(tr("Desktop notifications"), desktopNotifications_, tr("Notify about received message when the client is not currently focused.")); @@ -1250,6 +1267,10 @@ UserSettingsPage::UserSettingsPage(QSharedPointer settings, QWidge settings_->setMarkdown(enabled); }); + connect(animateImagesOnHover_, &Toggle::toggled, this, [this](bool enabled) { + settings_->setAnimateImagesOnHover(enabled); + }); + connect(typingNotifications_, &Toggle::toggled, this, [this](bool enabled) { settings_->setTypingNotifications(enabled); }); diff --git a/src/UserSettingsPage.h b/src/UserSettingsPage.h index ab9c9a3b..93b53211 100644 --- a/src/UserSettingsPage.h +++ b/src/UserSettingsPage.h @@ -40,6 +40,8 @@ class UserSettings : public QObject Q_PROPERTY(bool startInTray READ startInTray WRITE setStartInTray NOTIFY startInTrayChanged) Q_PROPERTY(bool groupView READ groupView WRITE setGroupView NOTIFY groupViewStateChanged) Q_PROPERTY(bool markdown READ markdown WRITE setMarkdown NOTIFY markdownChanged) + Q_PROPERTY(bool animateImagesOnHover READ animateImagesOnHover WRITE setAnimateImagesOnHover + NOTIFY animateImagesOnHoverChanged) Q_PROPERTY(bool typingNotifications READ typingNotifications WRITE setTypingNotifications NOTIFY typingNotificationsChanged) Q_PROPERTY(bool sortByImportance READ sortByImportance WRITE setSortByImportance NOTIFY @@ -135,6 +137,7 @@ public: void setEmojiFontFamily(QString family); void setGroupView(bool state); void setMarkdown(bool state); + void setAnimateImagesOnHover(bool state); void setReadReceipts(bool state); void setTypingNotifications(bool state); void setSortByImportance(bool state); @@ -181,6 +184,7 @@ public: bool privacyScreen() const { return privacyScreen_; } int privacyScreenTimeout() const { return privacyScreenTimeout_; } bool markdown() const { return markdown_; } + bool animateImagesOnHover() const { return animateImagesOnHover_; } bool typingNotifications() const { return typingNotifications_; } bool sortByImportance() const { return sortByImportance_; } bool buttonsInTimeline() const { return buttonsInTimeline_; } @@ -236,6 +240,7 @@ signals: void trayChanged(bool state); void startInTrayChanged(bool state); void markdownChanged(bool state); + void animateImagesOnHoverChanged(bool state); void typingNotificationsChanged(bool state); void buttonInTimelineChanged(bool state); void readReceiptsChanged(bool state); @@ -286,6 +291,7 @@ private: bool startInTray_; bool groupView_; bool markdown_; + bool animateImagesOnHover_; bool typingNotifications_; bool sortByImportance_; bool buttonsInTimeline_; @@ -381,6 +387,7 @@ private: Toggle *sortByImportance_; Toggle *readReceipts_; Toggle *markdown_; + Toggle *animateImagesOnHover_; Toggle *desktopNotifications_; Toggle *alertOnNotification_; Toggle *avatarCircles_; diff --git a/src/ui/MxcAnimatedImage.cpp b/src/ui/MxcAnimatedImage.cpp index cfc03827..3c93cc2b 100644 --- a/src/ui/MxcAnimatedImage.cpp +++ b/src/ui/MxcAnimatedImage.cpp @@ -94,8 +94,12 @@ MxcAnimatedImage::startDownload() buffer.isOpen()); movie.setFormat(mimeType); movie.setDevice(&buffer); - movie.start(); + if (play_) + movie.start(); + else + movie.jumpToFrame(0); emit loadedChanged(); + update(); }); }; @@ -143,6 +147,9 @@ MxcAnimatedImage::startDownload() QSGNode * MxcAnimatedImage::updatePaintNode(QSGNode *oldNode, QQuickItem::UpdatePaintNodeData *) { + if (!imageDirty) + return oldNode; + imageDirty = false; QSGImageNode *n = static_cast(oldNode); if (!n) diff --git a/src/ui/MxcAnimatedImage.h b/src/ui/MxcAnimatedImage.h index 7b9502e0..2e0489ad 100644 --- a/src/ui/MxcAnimatedImage.h +++ b/src/ui/MxcAnimatedImage.h @@ -19,6 +19,7 @@ class MxcAnimatedImage : public QQuickItem Q_PROPERTY(QString eventId READ eventId WRITE setEventId NOTIFY eventIdChanged) Q_PROPERTY(bool animatable READ animatable NOTIFY animatableChanged) Q_PROPERTY(bool loaded READ loaded NOTIFY loadedChanged) + Q_PROPERTY(bool play READ play WRITE setPlay NOTIFY playChanged) public: MxcAnimatedImage(QQuickItem *parent = nullptr) : QQuickItem(parent) @@ -32,6 +33,7 @@ public: bool animatable() const { return animatable_; } bool loaded() const { return buffer.size() > 0; } + bool play() const { return play_; } QString eventId() const { return eventId_; } TimelineModel *room() const { return room_; } void setEventId(QString newEventId) @@ -48,6 +50,14 @@ public: emit roomChanged(); } } + void setPlay(bool newPlay) + { + if (play_ != newPlay) { + play_ = newPlay; + movie.setPaused(!play_); + emit playChanged(); + } + } QSGNode *updatePaintNode(QSGNode *oldNode, QQuickItem::UpdatePaintNodeData *updatePaintNodeData) override; @@ -57,6 +67,7 @@ signals: void eventIdChanged(); void animatableChanged(); void loadedChanged(); + void playChanged(); private slots: void startDownload(); @@ -76,4 +87,5 @@ private: QMovie movie; int currentFrame = 0; bool imageDirty = true; + bool play_ = true; }; From 374ad0a816c039dff3daf54a8d77139350bddaab Mon Sep 17 00:00:00 2001 From: Nicolas Werner Date: Sun, 29 Aug 2021 17:22:49 +0200 Subject: [PATCH 060/232] Small image pack editor improvements - add missing mimetype - allow removal of images from pack - allow GIF as a format - don't divide size by 2 if the image is very small already --- resources/qml/dialogs/ImagePackEditorDialog.qml | 17 ++++++++++++++++- src/SingleImagePackModel.cpp | 17 +++++++++++++++++ src/SingleImagePackModel.h | 1 + 3 files changed, 34 insertions(+), 1 deletion(-) diff --git a/resources/qml/dialogs/ImagePackEditorDialog.qml b/resources/qml/dialogs/ImagePackEditorDialog.qml index 103f19a9..dda2c1ad 100644 --- a/resources/qml/dialogs/ImagePackEditorDialog.qml +++ b/resources/qml/dialogs/ImagePackEditorDialog.qml @@ -90,7 +90,7 @@ ApplicationWindow { folder: StandardPaths.writableLocation(StandardPaths.PicturesLocation) fileMode: FileDialog.OpenFiles - nameFilters: [qsTr("Stickers (*.png *.webp)")] + nameFilters: [qsTr("Stickers (*.png *.webp *.gif)")] onAccepted: imagePack.addStickers(files) } @@ -265,6 +265,21 @@ ApplicationWindow { Layout.alignment: Qt.AlignRight } + MatrixText { + text: qsTr("Remove from pack") + } + + Button { + text: qsTr("Remove") + onClicked: { + let temp = currentImageIndex; + currentImageIndex = -1; + imagePack.remove(temp); + } + Layout.alignment: Qt.AlignRight + } + + Item { Layout.columnSpan: 2 Layout.fillHeight: true diff --git a/src/SingleImagePackModel.cpp b/src/SingleImagePackModel.cpp index 4eb120d9..6d0f0ad9 100644 --- a/src/SingleImagePackModel.cpp +++ b/src/SingleImagePackModel.cpp @@ -310,11 +310,15 @@ SingleImagePackModel::addStickers(QList files) auto sz = img.size() / 2; if (sz.width() > 512 || sz.height() > 512) { sz.scale(512, 512, Qt::AspectRatioMode::KeepAspectRatio); + } else if (img.height() < 128 && img.width() < 128) { + sz = img.size(); } info.h = sz.height(); info.w = sz.width(); info.size = bytes.size(); + info.mimetype = + QMimeDatabase().mimeTypeForFile(f.toLocalFile()).name().toStdString(); auto filename = f.fileName().toStdString(); http::client()->upload( @@ -334,6 +338,19 @@ SingleImagePackModel::addStickers(QList files) }); } } + +void +SingleImagePackModel::remove(int idx) +{ + if (idx < (int)shortcodes.size() && idx >= 0) { + beginRemoveRows(QModelIndex(), idx, idx); + auto s = shortcodes.at(idx); + shortcodes.erase(shortcodes.begin() + idx); + pack.images.erase(s); + endRemoveRows(); + } +} + void SingleImagePackModel::addImageCb(std::string uri, std::string filename, mtx::common::ImageInfo info) { diff --git a/src/SingleImagePackModel.h b/src/SingleImagePackModel.h index cd38b3b6..60138d36 100644 --- a/src/SingleImagePackModel.h +++ b/src/SingleImagePackModel.h @@ -69,6 +69,7 @@ public: Q_INVOKABLE void save(); Q_INVOKABLE void addStickers(QList files); + Q_INVOKABLE void remove(int index); signals: void globallyEnabledChanged(); From 7645ab1736d46703bbb0740a995ba0f4af8c33df Mon Sep 17 00:00:00 2001 From: Nicolas Werner Date: Sun, 29 Aug 2021 18:38:11 +0200 Subject: [PATCH 061/232] Fix memory leak of animated image textures --- src/ui/MxcAnimatedImage.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/ui/MxcAnimatedImage.cpp b/src/ui/MxcAnimatedImage.cpp index 3c93cc2b..3db5ef60 100644 --- a/src/ui/MxcAnimatedImage.cpp +++ b/src/ui/MxcAnimatedImage.cpp @@ -152,8 +152,10 @@ MxcAnimatedImage::updatePaintNode(QSGNode *oldNode, QQuickItem::UpdatePaintNodeD imageDirty = false; QSGImageNode *n = static_cast(oldNode); - if (!n) + if (!n) { n = window()->createImageNode(); + n->setOwnsTexture(true); + } // n->setTexture(nullptr); auto img = movie.currentImage(); From b5b580fda54bab9d67b909a76fcdbc30ab79d308 Mon Sep 17 00:00:00 2001 From: Nicolas Werner Date: Sun, 29 Aug 2021 18:45:49 +0200 Subject: [PATCH 062/232] Loaded causes weird artifacts loading rooms --- resources/qml/TimelineView.qml | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/resources/qml/TimelineView.qml b/resources/qml/TimelineView.qml index 9015bafa..5a8b5381 100644 --- a/resources/qml/TimelineView.qml +++ b/resources/qml/TimelineView.qml @@ -84,17 +84,12 @@ Item { target: timelineView } - Loader { - active: room || roomPreview - asynchronous: false - Layout.fillWidth: true - sourceComponent: MessageView { + MessageView { implicitHeight: msgView.height - typingIndicator.height + Layout.fillWidth: true } - } - Loader { source: CallManager.isOnCall && CallManager.callType != CallType.VOICE ? "voip/VideoCall.qml" : "" onLoaded: TimelineManager.setVideoCallItem() From fc7df50d9aaa798ab4fdebf42064b1ad2f85d8b0 Mon Sep 17 00:00:00 2001 From: Nicolas Werner Date: Sun, 29 Aug 2021 19:24:14 +0200 Subject: [PATCH 063/232] Fix another leak when creating an animated image without an image --- src/ui/MxcAnimatedImage.cpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/ui/MxcAnimatedImage.cpp b/src/ui/MxcAnimatedImage.cpp index 3db5ef60..c691bab0 100644 --- a/src/ui/MxcAnimatedImage.cpp +++ b/src/ui/MxcAnimatedImage.cpp @@ -155,14 +155,19 @@ MxcAnimatedImage::updatePaintNode(QSGNode *oldNode, QQuickItem::UpdatePaintNodeD if (!n) { n = window()->createImageNode(); n->setOwnsTexture(true); + // n->setFlags(QSGNode::OwnedByParent | QSGNode::OwnsGeometry | + // GSGNode::OwnsMaterial); + n->setFlags(QSGNode::OwnedByParent); } // n->setTexture(nullptr); auto img = movie.currentImage(); if (!img.isNull()) n->setTexture(window()->createTextureFromImage(img)); - else + else { + delete n; return nullptr; + } n->setSourceRect(img.rect()); n->setRect(QRect(0, 0, width(), height())); From ded926cdf9b82bae12404651c74b14c1ca853f38 Mon Sep 17 00:00:00 2001 From: Nicolas Werner Date: Sun, 29 Aug 2021 19:24:44 +0200 Subject: [PATCH 064/232] Fix a few null warnings --- resources/qml/MessageView.qml | 10 +++++----- resources/qml/TimelineView.qml | 9 ++++----- resources/qml/dialogs/ImagePackEditorDialog.qml | 1 - 3 files changed, 9 insertions(+), 11 deletions(-) diff --git a/resources/qml/MessageView.qml b/resources/qml/MessageView.qml index e5c6b4ec..6446e61b 100644 --- a/resources/qml/MessageView.qml +++ b/resources/qml/MessageView.qml @@ -33,7 +33,7 @@ ScrollView { verticalLayoutDirection: ListView.BottomToTop onCountChanged: { // Mark timeline as read - if (atYEnd) + if (atYEnd && room) model.currentIndex = 0; } @@ -233,8 +233,8 @@ ScrollView { id: dateBubble anchors.horizontalCenter: parent ? parent.horizontalCenter : undefined - visible: previousMessageDay !== day - text: chat.model.formatDateSeparator(timestamp) + visible: room && previousMessageDay !== day + text: room ? room.formatDateSeparator(timestamp) : "" color: Nheko.colors.text height: Math.round(fontMetrics.height * 1.4) width: contentWidth * 1.2 @@ -257,10 +257,10 @@ ScrollView { width: Nheko.avatarSize height: Nheko.avatarSize - url: chat.model.avatarUrl(userId).replace("mxc://", "image://MxcImage/") + url: !room ? "" : room.avatarUrl(userId).replace("mxc://", "image://MxcImage/") displayName: userName userid: userId - onClicked: chat.model.openUserProfile(userId) + onClicked: room.openUserProfile(userId) ToolTip.visible: avatarHover.hovered ToolTip.text: userid diff --git a/resources/qml/TimelineView.qml b/resources/qml/TimelineView.qml index 5a8b5381..9bc4bef0 100644 --- a/resources/qml/TimelineView.qml +++ b/resources/qml/TimelineView.qml @@ -84,11 +84,10 @@ Item { target: timelineView } - - MessageView { - implicitHeight: msgView.height - typingIndicator.height - Layout.fillWidth: true - } + MessageView { + implicitHeight: msgView.height - typingIndicator.height + Layout.fillWidth: true + } Loader { source: CallManager.isOnCall && CallManager.callType != CallType.VOICE ? "voip/VideoCall.qml" : "" diff --git a/resources/qml/dialogs/ImagePackEditorDialog.qml b/resources/qml/dialogs/ImagePackEditorDialog.qml index dda2c1ad..e78213e0 100644 --- a/resources/qml/dialogs/ImagePackEditorDialog.qml +++ b/resources/qml/dialogs/ImagePackEditorDialog.qml @@ -279,7 +279,6 @@ ApplicationWindow { Layout.alignment: Qt.AlignRight } - Item { Layout.columnSpan: 2 Layout.fillHeight: true From 79ecad5a0930850f3dbff7cba361f6df68183d80 Mon Sep 17 00:00:00 2001 From: Nicolas Werner Date: Tue, 31 Aug 2021 01:51:03 +0200 Subject: [PATCH 065/232] Require a working secrets storage --- src/Cache.cpp | 30 ++++++++++++++++++++++++++++-- 1 file changed, 28 insertions(+), 2 deletions(-) diff --git a/src/Cache.cpp b/src/Cache.cpp index bc364c34..a966fe57 100644 --- a/src/Cache.cpp +++ b/src/Cache.cpp @@ -13,6 +13,7 @@ #include #include #include +#include #include #if __has_include() @@ -754,6 +755,24 @@ Cache::backupVersion() } } +static void +fatalSecretError() +{ + QMessageBox::critical( + ChatPage::instance(), + QCoreApplication::translate("SecretStorage", "Failed to connect to secret storage"), + QCoreApplication::translate( + "SecretStorage", + "Nheko could not connect to the secure storage to save encryption secrets to. " + "This can have multiple reasons. Check if your D-Bus service is running and " + "you have configured a service like KWallet, Gnome Secrets or the equivalent " + "for your platform. If you are having trouble, feel free to open an issue " + "here: https://github.com/Nheko-Reborn/nheko/issues")); + + QCoreApplication::exit(1); + exit(1); +} + void Cache::storeSecret(const std::string name, const std::string secret) { @@ -780,6 +799,7 @@ Cache::storeSecret(const std::string name, const std::string secret) nhlog::db()->warn("Storing secret '{}' failed: {}", name, job->errorString().toStdString()); + fatalSecretError(); } else { // if we emit the signal directly, qtkeychain breaks and won't execute new // jobs. You can't start a job from the finish signal of a job. @@ -840,8 +860,14 @@ Cache::secret(const std::string name) const QString secret = job.textData(); if (job.error()) { - nhlog::db()->debug( - "Restoring secret '{}' failed: {}", name, job.errorString().toStdString()); + if (job.error() == QKeychain::Error::EntryNotFound) + return std::nullopt; + nhlog::db()->error("Restoring secret '{}' failed ({}): {}", + name, + job.error(), + job.errorString().toStdString()); + + fatalSecretError(); return std::nullopt; } if (secret.isEmpty()) { From 0f361151d799ba1587cb865a48f4273326c256af Mon Sep 17 00:00:00 2001 From: Nicolas Werner Date: Tue, 31 Aug 2021 03:35:04 +0200 Subject: [PATCH 066/232] Fix parsing query keys responses with optional keys missing --- CMakeLists.txt | 2 +- io.github.NhekoReborn.Nheko.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index fb83ae2f..174fdb7f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -384,7 +384,7 @@ if(USE_BUNDLED_MTXCLIENT) FetchContent_Declare( MatrixClient GIT_REPOSITORY https://github.com/Nheko-Reborn/mtxclient.git - GIT_TAG 8de4df36b43bb3882e71ed609ec8d7c8fe713ce0 + GIT_TAG 6306ce3bab0b4d478ade213c09e79d30f04f161e ) set(BUILD_LIB_EXAMPLES OFF CACHE INTERNAL "") set(BUILD_LIB_TESTS OFF CACHE INTERNAL "") diff --git a/io.github.NhekoReborn.Nheko.yaml b/io.github.NhekoReborn.Nheko.yaml index c35a3429..855005c9 100644 --- a/io.github.NhekoReborn.Nheko.yaml +++ b/io.github.NhekoReborn.Nheko.yaml @@ -163,7 +163,7 @@ modules: buildsystem: cmake-ninja name: mtxclient sources: - - commit: 8de4df36b43bb3882e71ed609ec8d7c8fe713ce0 + - commit: 6306ce3bab0b4d478ade213c09e79d30f04f161e type: git url: https://github.com/Nheko-Reborn/mtxclient.git - config-opts: From c80e253a2432d39c5548052fabeeb7408eda1f23 Mon Sep 17 00:00:00 2001 From: Nicolas Werner Date: Tue, 31 Aug 2021 04:06:51 +0200 Subject: [PATCH 067/232] Stop encrypting all sessions with secret --- src/Cache.cpp | 65 +++++++++++++++++++++++++++++++++--------------- src/Cache_p.h | 10 +++++--- src/ChatPage.cpp | 12 +++++---- src/Olm.cpp | 5 ++-- 4 files changed, 61 insertions(+), 31 deletions(-) diff --git a/src/Cache.cpp b/src/Cache.cpp index a966fe57..be79a0e0 100644 --- a/src/Cache.cpp +++ b/src/Cache.cpp @@ -36,8 +36,7 @@ //! Should be changed when a breaking change occurs in the cache format. //! This will reset client's data. -static const std::string CURRENT_CACHE_FORMAT_VERSION("2021.08.22"); -static const std::string SECRET("secret"); +static const std::string CURRENT_CACHE_FORMAT_VERSION("2021.08.31"); //! Keys used for the DB static const std::string_view NEXT_BATCH_KEY("next_batch"); @@ -370,7 +369,8 @@ Cache::exportSessionKeys() ExportedSession exported; MegolmSessionIndex index; - auto saved_session = unpickle(std::string(value), SECRET); + auto saved_session = + unpickle(std::string(value), pickle_secret_); try { index = nlohmann::json::parse(key).get(); @@ -424,13 +424,14 @@ Cache::saveInboundMegolmSession(const MegolmSessionIndex &index, { using namespace mtx::crypto; const auto key = json(index).dump(); - const auto pickled = pickle(session.get(), SECRET); + const auto pickled = pickle(session.get(), pickle_secret_); auto txn = lmdb::txn::begin(env_); std::string_view value; if (inboundMegolmSessionDb_.get(txn, key, value)) { - auto oldSession = unpickle(std::string(value), SECRET); + auto oldSession = + unpickle(std::string(value), pickle_secret_); if (olm_inbound_group_session_first_known_index(session.get()) > olm_inbound_group_session_first_known_index(oldSession.get())) { nhlog::crypto()->warn( @@ -455,7 +456,8 @@ Cache::getInboundMegolmSession(const MegolmSessionIndex &index) std::string_view value; if (inboundMegolmSessionDb_.get(txn, key, value)) { - auto session = unpickle(std::string(value), SECRET); + auto session = + unpickle(std::string(value), pickle_secret_); return session; } } catch (std::exception &e) { @@ -502,7 +504,7 @@ Cache::updateOutboundMegolmSession(const std::string &room_id, // Save the updated pickled data for the session. json j; - j["session"] = pickle(ptr.get(), SECRET); + j["session"] = pickle(ptr.get(), pickle_secret_); auto txn = lmdb::txn::begin(env_); outboundMegolmSessionDb_.put(txn, room_id, j.dump()); @@ -532,7 +534,7 @@ Cache::saveOutboundMegolmSession(const std::string &room_id, mtx::crypto::OutboundGroupSessionPtr &session) { using namespace mtx::crypto; - const auto pickled = pickle(session.get(), SECRET); + const auto pickled = pickle(session.get(), pickle_secret_); GroupSessionData data = data_; data.message_index = olm_outbound_group_session_message_index(session.get()); @@ -575,7 +577,7 @@ Cache::getOutboundMegolmSession(const std::string &room_id) auto obj = json::parse(value); OutboundGroupSessionDataRef ref{}; - ref.session = unpickle(obj.at("session"), SECRET); + ref.session = unpickle(obj.at("session"), pickle_secret_); MegolmSessionIndex index; index.room_id = room_id; @@ -626,7 +628,7 @@ Cache::saveOlmSession(const std::string &curve25519, auto txn = lmdb::txn::begin(env_); auto db = getOlmSessionsDb(txn, curve25519); - const auto pickled = pickle(session.get(), SECRET); + const auto pickled = pickle(session.get(), pickle_secret_); const auto session_id = mtx::crypto::session_id(session.get()); StoredOlmSession stored_session; @@ -653,7 +655,7 @@ Cache::getOlmSession(const std::string &curve25519, const std::string &session_i if (found) { auto data = json::parse(pickled).get(); - return unpickle(data.pickled_session, SECRET); + return unpickle(data.pickled_session, pickle_secret_); } return std::nullopt; @@ -681,9 +683,9 @@ Cache::getLatestOlmSession(const std::string &curve25519) txn.commit(); - return currentNewest - ? std::optional(unpickle(currentNewest->pickled_session, SECRET)) - : std::nullopt; + return currentNewest ? std::optional(unpickle(currentNewest->pickled_session, + pickle_secret_)) + : std::nullopt; } std::vector @@ -719,6 +721,7 @@ std::string Cache::restoreOlmAccount() { auto txn = ro_txn(env_); + std::string_view pickled; syncStateDb_.get(txn, OLM_ACCOUNT_KEY, pickled); @@ -774,7 +777,7 @@ fatalSecretError() } void -Cache::storeSecret(const std::string name, const std::string secret) +Cache::storeSecret(const std::string name, const std::string secret, bool internal) { auto settings = UserSettings::instance(); auto job = new QKeychain::WritePasswordJob(QCoreApplication::applicationName()); @@ -783,7 +786,7 @@ Cache::storeSecret(const std::string name, const std::string secret) job->setSettings(UserSettings::instance()->qsettings()); job->setKey( - "matrix." + + (internal ? "nheko." : "matrix.") + QString(QCryptographicHash::hash(settings->profile().toUtf8(), QCryptographicHash::Sha256) .toBase64()) + "." + QString::fromStdString(name)); @@ -812,7 +815,7 @@ Cache::storeSecret(const std::string name, const std::string secret) } void -Cache::deleteSecret(const std::string name) +Cache::deleteSecret(const std::string name, bool internal) { auto settings = UserSettings::instance(); QKeychain::DeletePasswordJob job(QCoreApplication::applicationName()); @@ -821,7 +824,7 @@ Cache::deleteSecret(const std::string name) job.setSettings(UserSettings::instance()->qsettings()); job.setKey( - "matrix." + + (internal ? "nheko." : "matrix.") + QString(QCryptographicHash::hash(settings->profile().toUtf8(), QCryptographicHash::Sha256) .toBase64()) + "." + QString::fromStdString(name)); @@ -837,7 +840,7 @@ Cache::deleteSecret(const std::string name) } std::optional -Cache::secret(const std::string name) +Cache::secret(const std::string name, bool internal) { auto settings = UserSettings::instance(); QKeychain::ReadPasswordJob job(QCoreApplication::applicationName()); @@ -846,7 +849,7 @@ Cache::secret(const std::string name) job.setSettings(UserSettings::instance()->qsettings()); job.setKey( - "matrix." + + (internal ? "nheko." : "matrix.") + QString(QCryptographicHash::hash(settings->profile().toUtf8(), QCryptographicHash::Sha256) .toBase64()) + "." + QString::fromStdString(name)); @@ -878,6 +881,22 @@ Cache::secret(const std::string name) return secret.toStdString(); } +std::string +Cache::pickleSecret() +{ + if (pickle_secret_.empty()) { + auto s = secret("pickle_secret", true); + if (!s) { + this->pickle_secret_ = mtx::client::utils::random_token(64, true); + storeSecret("pickle_secret", pickle_secret_, true); + } else { + this->pickle_secret_ = *s; + } + } + + return pickle_secret_; +}; + void Cache::removeInvite(lmdb::txn &txn, const std::string &room_id) { @@ -979,6 +998,7 @@ Cache::deleteData() deleteSecret(mtx::secret_storage::secrets::cross_signing_master); deleteSecret(mtx::secret_storage::secrets::cross_signing_user_signing); deleteSecret(mtx::secret_storage::secrets::cross_signing_self_signing); + deleteSecret("pickle_secret", true); } //! migrates db to the current format @@ -1202,6 +1222,11 @@ Cache::runMigrations() "Successfully cleared the cache. Will do a clean sync after startup."); return true; }}, + {"2021.08.31", + [this]() { + storeSecret("pickle_secret", "secret", true); + return true; + }}, }; nhlog::db()->info("Running migrations, this may take a while!"); diff --git a/src/Cache_p.h b/src/Cache_p.h index d97eb531..6190413f 100644 --- a/src/Cache_p.h +++ b/src/Cache_p.h @@ -291,9 +291,11 @@ public: void deleteBackupVersion(); std::optional backupVersion(); - void storeSecret(const std::string name, const std::string secret); - void deleteSecret(const std::string name); - std::optional secret(const std::string name); + void storeSecret(const std::string name, const std::string secret, bool internal = false); + void deleteSecret(const std::string name, bool internal = false); + std::optional secret(const std::string name, bool internal = false); + + std::string pickleSecret(); template constexpr static bool isStateEvent_ = @@ -713,6 +715,8 @@ private: QString localUserId_; QString cacheDirectory_; + std::string pickle_secret_; + VerificationStorage verification_storage; bool databaseReady_ = false; diff --git a/src/ChatPage.cpp b/src/ChatPage.cpp index d0de7ab8..038c3e3c 100644 --- a/src/ChatPage.cpp +++ b/src/ChatPage.cpp @@ -32,9 +32,6 @@ #include "blurhash.hpp" -// TODO: Needs to be updated with an actual secret. -static const std::string STORAGE_SECRET_KEY("secret"); - ChatPage *ChatPage::instance_ = nullptr; constexpr int CHECK_CONNECTIVITY_INTERVAL = 15'000; constexpr int RETRY_TIMEOUT = 5'000; @@ -372,7 +369,7 @@ ChatPage::bootstrap(QString userid, QString homeserver, QString token) // There isn't a saved olm account to restore. nhlog::crypto()->info("creating new olm account"); olm::client()->create_new_account(); - cache::saveOlmAccount(olm::client()->save(STORAGE_SECRET_KEY)); + cache::saveOlmAccount(olm::client()->save(cache::client()->pickleSecret())); } catch (const lmdb::error &e) { nhlog::crypto()->critical("failed to save olm account {}", e.what()); emit dropToLoginPageCb(QString::fromStdString(e.what())); @@ -394,7 +391,7 @@ ChatPage::loadStateFromCache() nhlog::db()->info("restoring state from cache"); try { - olm::client()->load(cache::restoreOlmAccount(), STORAGE_SECRET_KEY); + olm::client()->load(cache::restoreOlmAccount(), cache::client()->pickleSecret()); emit initializeEmptyViews(); emit initializeMentions(cache::getTimelineMentions()); @@ -411,6 +408,11 @@ ChatPage::loadStateFromCache() return; } catch (const json::exception &e) { nhlog::db()->critical("failed to parse cache data: {}", e.what()); + emit dropToLoginPageCb(tr("Failed to restore save data. Please login again.")); + return; + } catch (const std::exception &e) { + nhlog::db()->critical("failed to load cache data: {}", e.what()); + emit dropToLoginPageCb(tr("Failed to restore save data. Please login again.")); return; } diff --git a/src/Olm.cpp b/src/Olm.cpp index 25f4bfc0..72dc582f 100644 --- a/src/Olm.cpp +++ b/src/Olm.cpp @@ -27,7 +27,6 @@ auto client_ = std::make_unique(); std::map request_id_to_secret_name; -const std::string STORAGE_SECRET_KEY("secret"); constexpr auto MEGOLM_ALGO = "m.megolm.v1.aes-sha2"; } @@ -483,7 +482,7 @@ handle_pre_key_olm_message(const std::string &sender, // We also remove the one time key used to establish that // session so we'll have to update our copy of the account object. - cache::saveOlmAccount(olm::client()->save("secret")); + cache::saveOlmAccount(olm::client()->save(cache::client()->pickleSecret())); } catch (const mtx::crypto::olm_exception &e) { nhlog::crypto()->critical( "failed to create inbound session with {}: {}", sender, e.what()); @@ -938,7 +937,7 @@ void mark_keys_as_published() { olm::client()->mark_keys_as_published(); - cache::saveOlmAccount(olm::client()->save(STORAGE_SECRET_KEY)); + cache::saveOlmAccount(olm::client()->save(cache::client()->pickleSecret())); } void From 529c93503d142a17569faf84111450eca468fdcb Mon Sep 17 00:00:00 2001 From: Nicolas Werner Date: Tue, 31 Aug 2021 04:13:51 +0200 Subject: [PATCH 068/232] Remove useless ; --- src/Cache.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Cache.cpp b/src/Cache.cpp index be79a0e0..ed6a5f34 100644 --- a/src/Cache.cpp +++ b/src/Cache.cpp @@ -895,7 +895,7 @@ Cache::pickleSecret() } return pickle_secret_; -}; +} void Cache::removeInvite(lmdb::txn &txn, const std::string &room_id) From 98533f01d9f39d19d04841a079b6ebe9284559f8 Mon Sep 17 00:00:00 2001 From: Nicolas Werner Date: Tue, 31 Aug 2021 16:40:31 +0200 Subject: [PATCH 069/232] Fix session always being rotated if 'verified only' is active --- src/Cache.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/Cache.cpp b/src/Cache.cpp index ed6a5f34..a7fe473f 100644 --- a/src/Cache.cpp +++ b/src/Cache.cpp @@ -3759,7 +3759,8 @@ Cache::getMembersWithKeys(const std::string &room_id, bool verified_only) if (res) { auto k = json::parse(keys).get(); if (verified_only) { - auto verif = verificationStatus(std::string(user_id)); + auto verif = verificationStatus_(std::string(user_id), txn); + if (verif.user_verified == crypto::Trust::Verified || !verif.verified_devices.empty()) { auto keyCopy = k; @@ -3807,7 +3808,8 @@ Cache::getMembersWithKeys(const std::string &room_id, bool verified_only) cursor.close(); return members; - } catch (std::exception &) { + } catch (std::exception &e) { + nhlog::db()->debug("Error retrieving members: {}", e.what()); return {}; } } From 3f8bb19ba1fed05d0a19ce16690f88c09f3aed79 Mon Sep 17 00:00:00 2001 From: resolritter Date: Wed, 25 Aug 2021 11:10:55 -0300 Subject: [PATCH 070/232] right-click tap handler for replies --- resources/qml/MessageView.qml | 34 +++++++++++++++++++++++++ resources/qml/delegates/Reply.qml | 28 +++++++++++++++----- resources/qml/delegates/TextMessage.qml | 7 +++++ 3 files changed, 62 insertions(+), 7 deletions(-) diff --git a/resources/qml/MessageView.qml b/resources/qml/MessageView.qml index e5c6b4ec..251e5ff1 100644 --- a/resources/qml/MessageView.qml +++ b/resources/qml/MessageView.qml @@ -648,4 +648,38 @@ ScrollView { } + Platform.Menu { + id: replyContextMenu + + property string text + property string link + + function show(text_, link_) { + text = text_; + link = link_; + open(); + } + + Platform.MenuItem { + visible: replyContextMenu.text + enabled: visible + text: qsTr("&Copy") + onTriggered: Clipboard.text = replyContextMenu.text + } + + Platform.MenuItem { + visible: replyContextMenu.link + enabled: visible + text: qsTr("Copy &link location") + onTriggered: Clipboard.text = replyContextMenu.link + } + + Platform.MenuItem { + visible: true + enabled: visible + text: qsTr("&Go to reply") + onTriggered: chat.model.showEvent(eventId) + } + } + } diff --git a/resources/qml/delegates/Reply.qml b/resources/qml/delegates/Reply.qml index 8bbce10e..6c0a6da4 100644 --- a/resources/qml/delegates/Reply.qml +++ b/resources/qml/delegates/Reply.qml @@ -7,6 +7,7 @@ import QtQuick.Controls 2.3 import QtQuick.Layouts 1.2 import QtQuick.Window 2.13 import im.nheko 1.0 +import Qt.labs.platform 1.1 as Platform Item { id: r @@ -36,11 +37,6 @@ Item { width: parent.width height: replyContainer.height - TapHandler { - onSingleTapped: chat.model.showEvent(eventId) - gesturePolicy: TapHandler.ReleaseWithinBounds - } - CursorShape { anchors.fill: parent cursorShape: Qt.PointingHandCursor @@ -62,6 +58,25 @@ Item { anchors.leftMargin: 4 width: parent.width - 8 + TapHandler { + acceptedButtons: Qt.LeftButton + onSingleTapped: chat.model.showEvent(r.eventId) + gesturePolicy: TapHandler.ReleaseWithinBounds + } + + TapHandler { + acceptedButtons: Qt.RightButton + onLongPressed: replyContextMenu.show( + reply.child.copyText, + reply.child.linkAt(eventPoint.position.x, eventPoint.position.y - userName_.implicitHeight) + ) + onSingleTapped: replyContextMenu.show( + reply.child.copyText, + reply.child.linkAt(eventPoint.position.x, eventPoint.position.y - userName_.implicitHeight) + ) + gesturePolicy: TapHandler.ReleaseWithinBounds + } + Text { id: userName_ @@ -73,7 +88,6 @@ Item { onSingleTapped: chat.model.openUserProfile(userId) gesturePolicy: TapHandler.ReleaseWithinBounds } - } MessageDelegate { @@ -99,11 +113,11 @@ Item { callType: r.callType relatedEventCacheBuster: r.relatedEventCacheBuster encryptionError: r.encryptionError + // This is disabled so that left clicking the reply goes to its location enabled: false width: parent.width isReply: true } - } Rectangle { diff --git a/resources/qml/delegates/TextMessage.qml b/resources/qml/delegates/TextMessage.qml index 58aa99ca..0bc45a9c 100644 --- a/resources/qml/delegates/TextMessage.qml +++ b/resources/qml/delegates/TextMessage.qml @@ -3,6 +3,7 @@ // SPDX-License-Identifier: GPL-3.0-or-later import ".." +import QtQuick.Controls 2.3 import im.nheko 1.0 MatrixText { @@ -35,4 +36,10 @@ MatrixText { clip: isReply selectByMouse: !Settings.mobileMode && !isReply font.pointSize: (Settings.enlargeEmojiOnlyMessages && isOnlyEmoji > 0 && isOnlyEmoji < 4) ? Settings.fontSize * 3 : Settings.fontSize + + CursorShape { + enabled: isReply + anchors.fill: parent + cursorShape: Qt.PointingHandCursor + } } From 69eddd82b5faf7a7e5fc4dac80a98eeb344ef366 Mon Sep 17 00:00:00 2001 From: Weblate Date: Tue, 31 Aug 2021 15:59:30 -0400 Subject: [PATCH 071/232] Translated using Weblate (Dutch) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Currently translated at 9.9% (55 of 552 strings) Translated using Weblate (Dutch) Currently translated at 9.9% (55 of 552 strings) Translated using Weblate (Dutch) Currently translated at 9.9% (55 of 552 strings) Co-authored-by: Jaron Viëtor Co-authored-by: Thijs Wester Co-authored-by: Weblate Translate-URL: https://weblate.nheko.im/projects/nheko/nheko-master/nl/ Translation: Nheko/nheko --- resources/langs/nheko_nl.ts | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/resources/langs/nheko_nl.ts b/resources/langs/nheko_nl.ts index d8aed2a1..0ff77fbc 100644 --- a/resources/langs/nheko_nl.ts +++ b/resources/langs/nheko_nl.ts @@ -6,33 +6,33 @@ Calling... - + Bellen... Connecting... - + Verbinden... You are screen sharing - + Scherm wordt gedeeld Hide/Show Picture-in-Picture - + Toon/verberg miniatuur Unmute Mic - + Microfoon aanzetten Mute Mic - + Microfoon dempen @@ -40,17 +40,17 @@ Awaiting Confirmation - + Wachten op bevestiging Waiting for other side to complete verification. - + Wachten op de andere kant om verificatie te voltooien. Cancel - Annuleren + Annuleren @@ -58,17 +58,17 @@ Video Call - + Video oproep Voice Call - + Audio oproep No microphone found. - + Geen microfoon gevonden. From debb7cb3b4a6373856963676846cfb8920731ad5 Mon Sep 17 00:00:00 2001 From: Weblate Date: Tue, 31 Aug 2021 18:56:17 -0400 Subject: [PATCH 072/232] Translated using Weblate (Dutch) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Currently translated at 100.0% (552 of 552 strings) Translated using Weblate (Dutch) Currently translated at 100.0% (552 of 552 strings) Translated using Weblate (Dutch) Currently translated at 100.0% (552 of 552 strings) Translated using Weblate (Dutch) Currently translated at 100.0% (552 of 552 strings) Co-authored-by: Bas van Rossem Co-authored-by: Glael Co-authored-by: Jaron Viëtor Co-authored-by: Thijs Wester Translate-URL: https://weblate.nheko.im/projects/nheko/nheko-master/nl/ Translation: Nheko/nheko --- resources/langs/nheko_nl.ts | 1037 ++++++++++++++++++----------------- 1 file changed, 526 insertions(+), 511 deletions(-) diff --git a/resources/langs/nheko_nl.ts b/resources/langs/nheko_nl.ts index 0ff77fbc..d9330110 100644 --- a/resources/langs/nheko_nl.ts +++ b/resources/langs/nheko_nl.ts @@ -76,42 +76,42 @@ Video Call - + Video oproep Voice Call - + Audio oproep Devices - + Apparaten Accept - Accepteren + Aanvaarden Unknown microphone: %1 - + Onbekende microfoon: %1 Unknown camera: %1 - + Onbekende camera: %1 Decline - Afwijzen + Afwijzen No microphone found. - + Geen microfoon gevonden. @@ -119,7 +119,7 @@ Entire screen - + Gehele scherm @@ -138,164 +138,164 @@ Migrating the cache to the current version failed. This can have different reasons. Please open an issue and try to use an older version in the mean time. Alternatively you can try deleting the cache manually. - + Het migreren can de cache naar de huidige versie is mislukt. Dit kan verscheidene redenen hebben. Maak a.u.b een issue aan en probeer in de tussentijd een oudere versie. Je kan ook proberen de cache handmatig te verwijderen. Confirm join - + Bevestig deelname Do you really want to join %1? - + Weet je zeker dat je %1 wil binnen gaan? Room %1 created. - Kamer %1 gecreëerd. + Kamer %1 gemaakt. Confirm invite - + Bevestig uitnodiging Do you really want to invite %1 (%2)? - + Weet je zeker dat je %1 (%2) wil uitnodigen? Failed to invite %1 to %2: %3 - + Uitnodigen van %1 naar %2 mislukt: %3 Confirm kick - + Bevestig verwijdering Do you really want to kick %1 (%2)? - + Weet je zeker dat je %1 (%2) uit de kamer wil verwijderen? Kicked user: %1 - + Uit kamer verwijderde gebruiker: %1 Confirm ban - + Bevestig verbannen Do you really want to ban %1 (%2)? - + Weet je zeker dat je gebruiker %1 (%2) wil verbannen? Failed to ban %1 in %2: %3 - + Verbannen van %1 uit %2 mislukt: %3 Banned user: %1 - + Verbannen gebruiker: %1 Confirm unban - + Bevestig ongedaan maken verbanning Do you really want to unban %1 (%2)? - + Weet je zeker dat je %1 (%2) opnieuw wil toelaten? Failed to unban %1 in %2: %3 - + Opnieuw toelaten van %1 in %2 mislukt: %3 Unbanned user: %1 - + Toegelaten gebruiker: %1 Do you really want to start a private chat with %1? - + Weet je zeker dat je een privé chat wil beginnen met %1? Cache migration failed! - + Migreren van de cache is mislukt! Incompatible cache version - + Incompatibele cacheversie The cache on your disk is newer than this version of Nheko supports. Please update or clear your cache. - + De opgeslagen cache is nieuwer dan deze versie van Nheko ondersteunt. Update Nheko, of verwijder je cache. Failed to restore OLM account. Please login again. - + Herstellen van OLM account mislukt. Log a.u.b. opnieuw in. Failed to restore save data. Please login again. - + Opgeslagen gegevens herstellen mislukt. Log a.u.b. opnieuw in. Failed to setup encryption keys. Server response: %1 %2. Please try again later. - + Instellen van de versleuteling is mislukt. Bericht van server: %1 %2. Probeer het a.u.b. later nog eens. Please try to login again: %1 - + Probeer a.u.b. opnieuw in te loggen: %1 Failed to join room: %1 - + Kamer binnengaan mislukt: %1 You joined the room - + Je bent de kamer binnengegaan. Failed to remove invite: %1 - + Uitnodiging verwijderen mislukt: %1 Room creation failed: %1 - + Kamer aanmaken mislukt: %1 Failed to leave room: %1 - + Kamer verlaten mislukt: %1 Failed to kick %1 from %2: %3 - + Kon %1 niet verwijderen uit %2: %3 @@ -303,7 +303,7 @@ Hide rooms with this tag or from this space by default. - + Verberg standaard kamers met deze markering of uit deze groep. @@ -311,42 +311,42 @@ All rooms - + Alle kamers Shows all rooms without filtering. - + Laat alles kamers zien zonder filters. Favourites - + Favorieten Rooms you have favourited. - + Je favoriete kamers. Low Priority - + Lage prioriteit Rooms with low priority. - + Kamers met lage prioriteit. Server Notices - + Serverberichten Messages from your server or administrator. - + Berichten van je server of beheerder. @@ -354,27 +354,27 @@ Decrypt secrets - + Ontsleutel geheimen Enter your recovery key or passphrase to decrypt your secrets: - + Voer je herstelsleutel of wachtwoordzin in om je geheimen te ontsleutelen: Enter your recovery key or passphrase called %1 to decrypt your secrets: - + Voer je herstelsleutel of wachtwoordzin in met de naam %1 om je geheimen te ontsleutelen: Decryption failed - + Ontsleutelen mislukt Failed to decrypt secrets with the provided recovery key or passphrase - + Geheimen konden niet worden ontsleuteld met de gegeven herstelsleutel of wachtwoordzin @@ -382,22 +382,22 @@ Verification Code - + Verificatiecode Please verify the following digits. You should see the same numbers on both sides. If they differ, please press 'They do not match!' to abort verification! - + Controleer de volgende getallen. Je zou dezelfde getallen moeten zien aan beide kanten. Druk als ze verschillen op 'Ze komen niet overeen!' om de verificatie te annuleren! They do not match! - + Ze komen niet overeen! They match! - + Ze zijn gelijk! @@ -405,12 +405,12 @@ Apply - + Toepassen Cancel - Annuleren + Annuleren @@ -428,47 +428,47 @@ Search - + Zoeken People - + Mensen Nature - + Natuur Food - + Eten Activity - Activiteit + Activiteiten Travel - + Reizen Objects - Objecten + Objecten Symbols - Symbolen + Symbolen Flags - Vlaggen + Vlaggen @@ -476,22 +476,22 @@ Verification Code - + Verificatiecode Please verify the following emoji. You should see the same emoji on both sides. If they differ, please press 'They do not match!' to abort verification! - + Vergelijk de volgende emoji. Je zou dezelfde moeten zien aan beide kanten. Als ze verschillen, druk dan op 'Ze komen niet overeen!' om de verificatie te annuleren! They do not match! - + Ze komen niet overeen! They match! - + Ze zijn gelijk! @@ -499,42 +499,42 @@ There is no key to unlock this message. We requested the key automatically, but you can try requesting it again if you are impatient. - + Er is geen sleutel om dit bericht te ontsleutelen. We hebben de sleutel aangevraagd, maar je kan het opnieuw proberen als je ongeduldig bent. This message couldn't be decrypted, because we only have a key for newer messages. You can try requesting access to this message. - + Het bericht kon niet worden ontsleuteld, omdat we alleen een sleutel hebben voor nieuwere berichten. Je kan proberen toegang tot dit bericht aan te vragen. There was an internal error reading the decryption key from the database. - + Er was een interne fout bij het lezen van de sleutel uit de database. There was an error decrypting this message. - + Er was een fout bij het ontsleutelen van dit bericht. The message couldn't be parsed. - + Het bericht kon niet worden verwerkt. The encryption key was reused! Someone is possibly trying to insert false messages into this chat! - + De versleuteling was herbruikt! Wellicht probeert iemand vervalsde berichten in dit gesprek te injecteren! Unknown decryption error - + Onbekende ontsleutelingsfout Request key - + Vraag sleutel aan @@ -542,22 +542,22 @@ This message is not encrypted! - + Dit bericht is niet versleuteld! Encrypted by a verified device - + Versleuteld door een geverifieerd apparaat Encrypted by an unverified device, but you have trusted that user so far. - + Versleuteld door een ongeverifieerd apparaat, maar je hebt de gebruiker tot nu toe vertrouwd. Encrypted by an unverified device or the key is from an untrusted source like the key backup. - + Versleuteld door een ongeverifieerd apparaat of de sleutel komt van een niet te vertrouwen bron zoals een reservesleutel. @@ -565,33 +565,33 @@ Verification failed - + Verificatie mislukt Other client does not support our verification protocol. - + De andere kant ondersteunt ons verificatieprotocol niet. Key mismatch detected! - + Verschil in sleutels gedetecteerd! Device verification timed out. - + Apparaatverificatie is verlopen. Other party canceled the verification. - + De andere kant heeft de verificatie geannuleerd. Close - + Sluiten @@ -599,7 +599,7 @@ Forward Message - + Bericht doorsturen @@ -607,64 +607,64 @@ Editing image pack - + Afbeeldingspakket aanpassen Add images - + Afbeeldingen toevoegen Stickers (*.png *.webp) - + Stickers (*.png *.webp) State key - + Staatsleutel Packname - + Afbeeldingspakketnaam Attribution - + Bronvermelding Use as Emoji - + Gebruik als emoji Use as Sticker - + Gebruik als sticker Shortcode - + Shortcode Body - + Tekstinhoud Cancel - Annuleren + Annuleren Save - + Opslaan @@ -672,52 +672,52 @@ Image pack settings - + Afbeeldingspakketinstellingen Create account pack - + Maak pakket voor je eigen account aan New room pack - + Nieuw afbeeldingspakket voor kamer Private pack - + Privé afbeeldingspakket Pack from this room - + Afbeeldingspakket uit deze kamer Globally enabled pack - + Globaal geactiveerd afbeeldingspakket Enable globally - + Globaal activeren Enables this pack to be used in all rooms - + Activeert dit afbeeldingspakket voor gebruik in alle kamers Edit - + Bewerken Close - + Sluiten @@ -725,17 +725,17 @@ Select a file - Kies een bestand + Selecteer een bestand All Files (*) - Alle bestanden (*) + Alle bestanden (*) Failed to upload media. Please try again. - + Het is niet is gelukt om de media te versturen. Probeer het a.u.b. opnieuw. @@ -743,33 +743,33 @@ Invite users to %1 - + Nodig gebruikers uit naar %1 User ID to invite - Uit te nodigen gebruikers-id + Gebruikers ID om uit te nodigen @joe:matrix.org Example user id. The name 'joe' can be localized however you want. - + @jan:matrix.org Add - + Toevoegen Invite - + Uitnodigen Cancel - Annuleren + Annuleren @@ -777,12 +777,12 @@ Matrix ID - Matrix-id + Matrix ID e.g @joe:matrix.org - b.v @jan:matrix.org< + bijv. @jan:matrix.org @@ -790,7 +790,10 @@ You can also put your homeserver address there, if your server doesn't support .well-known lookup. Example: @user:server.my If Nheko fails to discover your homeserver, it will show you a field to enter the server manually. - + Je inlognaam. Een mxid begint met @ gevolgd door de gebruikersnaam. Daarachter komt een dubbele punt (:) en de servernaam. +Je kan ook het adres van je thuisserver daar invoeren, als die geen .well-known ondersteund. +Voorbeeld: @gebruiker:mijnserver.nl +Als Nheko je thuisserver niet kan vinden, zal er een veld verschijnen om de server handmatig in te voeren. @@ -800,33 +803,34 @@ If Nheko fails to discover your homeserver, it will show you a field to enter th Your password. - + Je wachtwoord. Device name - + Apparaatnaam A name for this device, which will be shown to others, when verifying your devices. If none is provided a default is used. - + Een naam voor dit apparaat, welke zichtbaar zal zijn voor anderen als ze je apparaten verifiëren. Als niets is ingevuld zal er een standaardnaam worden gebruikt. Homeserver address - + Thuisserveradres server.my:8787 - + mijnserver.nl:8787 The address that can be used to contact you homeservers client API. Example: https://server.my:8787 - + Het adres dat gebruikt kan worden om contact te zoeken met je thuisserver's gebruikers API. +Voorbeeld: https://mijnserver.nl:8787 @@ -839,37 +843,37 @@ Example: https://server.my:8787 You have entered an invalid Matrix ID e.g @joe:matrix.org - + Je hebt een ongeldige Matrix ID ingevuld. Correct voorbeeld: @jan:matrix.org Autodiscovery failed. Received malformed response. - + Automatische herkenning mislukt. Ongeldig antwoord ontvangen. Autodiscovery failed. Unknown error when requesting .well-known. - + Automatische herkenning mislukt. Onbekende fout tijdens het opvragen van .well-known. The required endpoints were not found. Possibly not a Matrix server. - + De vereiste aanspreekpunten werden niet gevonden. Mogelijk geen Matrix server. Received malformed response. Make sure the homeserver domain is valid. - + Ongeldig antwoord ontvangen. Zorg dat de thuisserver geldig is. An unknown error occured. Make sure the homeserver domain is valid. - + Een onbekende fout trad op. Zorg dat de thuisserver geldig is. SSO LOGIN - + SSO INLOGGEN @@ -879,7 +883,7 @@ Example: https://server.my:8787 SSO login failed - + SSO inloggen mislukt @@ -888,77 +892,77 @@ Example: https://server.my:8787 removed - + verwijderd Encryption enabled - + Versleuteling geactiveerd room name changed to: %1 - + kamernaam veranderd in: %1 removed room name - + kamernaam verwijderd topic changed to: %1 - + onderwerp aangepast naar: %1 removed topic - + onderwerp verwijderd %1 changed the room avatar - + %1 heeft de kameravatar veranderd %1 created and configured room: %2 - + %1 maakte en configureerde de kamer: %2 %1 placed a voice call. - + %1 plaatste een spraakoproep. %1 placed a video call. - + %1 plaatste een video oproep. %1 placed a call. - + %1 plaatste een oproep. %1 answered the call. - + %1 beantwoordde de oproep. %1 ended the call. - + %1 beëindigde de oproep. Negotiating call... - + Onderhandelen oproep… Allow them in - + Binnenlaten @@ -966,42 +970,42 @@ Example: https://server.my:8787 Hang up - + Ophangen Place a call - + Plaats een oproep Send a file - + Verstuur een bestand Write a message... - Typ een bericht... + Typ een bericht… Stickers - + Stickers Emoji - + Emoji Send - + Verstuur You don't have permission to send messages in this room - + Je hebt geen toestemming om berichten te versturen in deze kamer @@ -1009,92 +1013,92 @@ Example: https://server.my:8787 Edit - + Bewerken React - + Reageren Reply - + Beantwoorden Options - + Opties &Copy - + &Kopiëren Copy &link location - + Kopieer &link Re&act - + Re&ageren Repl&y - + Beantwoo&rden &Edit - + B&ewerken Read receip&ts - + Leesbeves&tigingen &Forward - + &Doorsturen &Mark as read - + Gelezen &markeren View raw message - + Ruw bericht bekijken View decrypted raw message - + Ontsleuteld ruw bericht bekijken Remo&ve message - + &Verwijder bericht &Save as - + Op&slaan als &Open in external program - + In extern programma &openen Copy link to eve&nt - + Kopieer link naar gebeurte&nis @@ -1102,57 +1106,57 @@ Example: https://server.my:8787 Send Verification Request - + Verstuur verificatieverzoek Received Verification Request - + Ontvangen verificatieverzoek To allow other users to see, which of your devices actually belong to you, you can verify them. This also allows key backup to work automatically. Verify %1 now? - + Om andere gebruikers te laten weten welke apparaten echt van jou zijn, kan je ze verifiëren. Dit zorgt ook dat reservesleutels automatisch werken. Nu %1 verifiëren? To ensure that no malicious user can eavesdrop on your encrypted communications you can verify the other party. - + Om zeker te zijn dat niemand meeleest met je versleutelde gesprek kan je de andere kant verifiëren. %1 has requested to verify their device %2. - + %1 heeft verzocht om hun apparaat %2 te verifiëren. %1 using the device %2 has requested to be verified. - + %1, gebruikmakend van apparaat %2 heeft verzocht om verificatie. Your device (%1) has requested to be verified. - + Je apparaat (%1) heeft verzocht om verificatie. Cancel - Annuleren + Annuleren Deny - + Weigeren Start verification - + Begin verificatie Accept - Accepteren + Accepteren @@ -1162,41 +1166,41 @@ Example: https://server.my:8787 %1 sent an encrypted message - + %1 stuurde een versleuteld bericht * %1 %2 Format an emote message in a notification, %1 is the sender, %2 the message - + * %1 %2 %1 replied: %2 Format a reply in a notification. %1 is the sender, %2 the message - + %1 antwoordde: %2 %1: %2 Format a normal message in a notification. %1 is the sender, %2 the message - + %1: %2 %1 replied with an encrypted message - + %1 antwoordde met een versleuteld bericht %1 replied to a message - + %1 antwoordde op een bericht %1 sent a message - + %1 stuurde een bericht @@ -1204,32 +1208,32 @@ Example: https://server.my:8787 Place a call to %1? - + Bel %1? No microphone found. - + Geen microfoon gevonden. Voice - + Spraak Video - + Video Screen - + Scherm Cancel - Annuleren + Annuleren @@ -1237,7 +1241,7 @@ Example: https://server.my:8787 unimplemented event: - + Niet geïmplementeerd evenement: @@ -1245,17 +1249,17 @@ Example: https://server.my:8787 Create a unique profile, which allows you to log into several accounts at the same time and start multiple instances of nheko. - + Creëer een uniek profiel, waardoor je op meerdere accounts tegelijk kan inloggen, en meerdere kopieën van Nheko tegelijk kan starten. profile - + profiel profile name - + profielnaam @@ -1263,7 +1267,7 @@ Example: https://server.my:8787 Read receipts - Leesbevestigingen + Leesbevestigingen @@ -1271,7 +1275,7 @@ Example: https://server.my:8787 Yesterday, %1 - + Gisteren, %1 @@ -1285,7 +1289,7 @@ Example: https://server.my:8787 The username must not be empty, and must contain only the characters a-z, 0-9, ., _, =, -, and /. - + De gebruikersnaam mag niet leeg zijn, en mag alleen de volgende tekens bevatten: a-z, 0-9, ., _, =, -, en /. @@ -1295,7 +1299,7 @@ Example: https://server.my:8787 Please choose a secure password. The exact requirements for password strength may depend on your server. - + Kies a.u.b. een veilig wachtwoord. De exacte vereisten voor een wachtwoord kunnen per server verschillen. @@ -1305,12 +1309,12 @@ Example: https://server.my:8787 Homeserver - + Thuisserver A server that allows registration. Since matrix is decentralized, you need to first find a server you can register on or host your own. - + Een server die registratie toestaat. Omdat Matrix gedecentraliseerd is, moet je eerst zelf een server vinden om je op te registeren, of je eigen server hosten. @@ -1320,42 +1324,42 @@ Example: https://server.my:8787 No supported registration flows! - + Geen ondersteunde registratiestromingen! Registration token - + Registratieteken Please enter a valid registration token. - + Voer a.u.b een geldig registratieteken in. Autodiscovery failed. Received malformed response. - + Automatische herkenning mislukt. Onjuist gevormd antwoord ontvangen. Autodiscovery failed. Unknown error when requesting .well-known. - + Automatische herkenning mislukt. Onbekende fout bij opvragen van .well-known. The required endpoints were not found. Possibly not a Matrix server. - + De vereiste aanspreekpunten konden niet worden gevonden. Mogelijk geen Matrix server. Received malformed response. Make sure the homeserver domain is valid. - + Onjuist gevormd antwoord ontvangen. Zorg dat de thuisserver geldig is. An unknown error occured. Make sure the homeserver domain is valid. - + Een onbekende fout trad op. Zorg dat de thuisserver geldig is. @@ -1378,12 +1382,12 @@ Example: https://server.my:8787 Close - + Sluiten Cancel edit - + Bewerken annuleren @@ -1391,12 +1395,12 @@ Example: https://server.my:8787 Explore Public Rooms - + Verken openbare kamers Search for public rooms - + Zoek naar openbare kamers @@ -1404,7 +1408,7 @@ Example: https://server.my:8787 no version stored - + geen versie opgeslagen @@ -1412,102 +1416,102 @@ Example: https://server.my:8787 New tag - + Nieuwe markering Enter the tag you want to use: - + Voer de markering in die je wil gebruiken: Leave Room - + Verlaat kamer Are you sure you want to leave this room? - + Weet je zeker dat je de kamer wil verlaten? Leave room - Kamer verlaten + Kamer verlaten Tag room as: - + Markeer kamer als: Favourite - + Favoriet Low priority - + Lage prioriteit Server notice - + Serverbericht Create new tag... - + Maak nieuwe markering... Status Message - + Statusbericht Enter your status message: - + Voer je statusbericht in: Profile settings - + Profielinstellingen Set status message - + Stel statusbericht in Logout - + Uitloggen Start a new chat - + Nieuwe chat beginnen Join a room - Kamer betreden + Kamer binnengaan Create a new room - + Nieuwe kamer aanmaken Room directory - + Kamerlijst User settings - + Gebruikersinstellingen @@ -1515,41 +1519,41 @@ Example: https://server.my:8787 Members of %1 - + Deelnemers in %1 %n people in %1 Summary above list of members - - - + + %n persoon in %1 + %n personen in %1 Invite more people - + Nodig meer mensen uit This room is not encrypted! - + Deze kamer is niet versleuteld! This user is verified. - + Deze gebruiker is geverifieerd. This user isn't verified, but is still using the same master key from the first time you met. - + Deze gebruiker is niet geverifieerd, maar gebruikt nog dezelfde hoofdsleutel als de eerste keer. This user has unverified devices! - + Deze gebruiker heeft ongeverifieerde apparaten! @@ -1557,144 +1561,144 @@ Example: https://server.my:8787 Room Settings - + Kamerinstellingen %1 member(s) - + %1 deelnemer(s) SETTINGS - + INSTELLINGEN Notifications - + Meldingen Muted - + Gedempt Mentions only - + Alleen vermeldingen All messages - + Alle berichten Room access - + Kamertoegang Anyone and guests - + Iedereen (inclusief gasten) Anyone - + Iedereen Invited users - + Uitgenodigde gebruikers By knocking - + Door aan te kloppen Restricted by membership in other rooms - + Beperkt door deelname aan andere kamers Encryption - + Versleuteling End-to-End Encryption - + Eind-tot-eind versleuteling Encryption is currently experimental and things might break unexpectedly. <br> Please take note that it can't be disabled afterwards. - + Versleuteling is momenteel experimenteel en dingen kunnen onverwacht stuk gaan.<br>Let op: versleuteling kan achteraf niet uitgeschakeld worden. Sticker & Emote Settings - + Sticker & Emoji instellingen Change - + Bewerken Change what packs are enabled, remove packs or create new ones - + Verander welke afbeeldingspakketten zijn ingeschakeld, verwijder ze of voeg nieuwe toe INFO - + INFO Internal ID - + Interne ID Room Version - + Kamerversie Failed to enable encryption: %1 - + Versleuteling kon niet worden ingeschakeld: %1 Select an avatar - + Kies een avatar All Files (*) - Alle bestanden (*) + Alle bestanden (*) The selected file is not an image - + Het gekozen bestand is geen afbeelding Error while reading file: %1 - + Fout bij lezen van bestand: %1 Failed to upload image: %s - + Uploaden van afbeelding mislukt: %1 @@ -1702,17 +1706,17 @@ Example: https://server.my:8787 Pending invite. - + Wachtende uitnodiging. Previewing this room - + Voorbeeld van deze kamer No preview available - + Geen voorbeeld beschikbaar @@ -1720,53 +1724,53 @@ Example: https://server.my:8787 Share desktop with %1? - + Scherm delen met %1? Window: - + Scherm: Frame rate: - + Verversingssnelheid: Include your camera picture-in-picture - + Laat eigen cameraminiatuur zien Request remote camera - + Verzoek om camera van de andere kant View your callee's camera like a regular video call - + Bekijk de camera van degene die belt zoals bij een regulier videogesprek Hide mouse cursor - + Verstop muiscursor Share - + Delen Preview - + Voorbeeld Cancel - Annuleren + Annuleren @@ -1775,22 +1779,22 @@ Example: https://server.my:8787 Failed to update image pack: %1 - + Kon afbeeldingspakket niet updaten: %1 Failed to delete old image pack: %1 - + Kon oud afbeeldingspakket niet verwijderen: %1 Failed to open image: %1 - + Kon afbeelding niet openen: %1 Failed to upload image: %1 - + Kon afbeelding niet uploaden: %1 @@ -1798,22 +1802,22 @@ Example: https://server.my:8787 Failed - + Mislukt Sent - + Verstuurd Received - + Ontvangen Read - + Gelezen @@ -1821,7 +1825,7 @@ Example: https://server.my:8787 Search - + Zoeken @@ -1829,17 +1833,17 @@ Example: https://server.my:8787 Successful Verification - + Succesvolle verificatie Verification successful! Both sides verified their devices! - + Verificatie gelukt! Beide kanten hebben hun apparaat geverifieerd! Close - + Sluiten @@ -1847,193 +1851,193 @@ Example: https://server.my:8787 Message redaction failed: %1 - + Bericht intrekken mislukt: %1 Failed to encrypt event, sending aborted! - + Kon evenement niet versleutelen, versturen geannuleerd! Save image - Afbeelding opslaan + Afbeelding opslaan Save video - + Video opslaan Save audio - + Audio opslaan Save file - + Bestand opslaan %1 and %2 are typing. Multiple users are typing. First argument is a comma separated list of potentially multiple users. Second argument is the last user of that list. (If only one user is typing, %1 is empty. You should still use it in your string though to silence Qt warnings.) - - - + + 1%2% is aan het typen. + %1 en %2 zijn aan het typen. %1 opened the room to the public. - + %1 maakte de kamer openbaar. %1 made this room require and invitation to join. - + %1 maakte deze kamer uitnodiging-vereist. %1 allowed to join this room by knocking. - + %1 maakte deze kamer aanklopbaar. %1 allowed members of the following rooms to automatically join this room: %2 - + %1 stond toe dat deelnemers aan de volgende kamers automatisch mogen deelnemen: %2 %1 made the room open to guests. - + %1 maakte de kamer openbaar voor gasten. %1 has closed the room to guest access. - + %1 maakte de kamer gesloten voor gasten. %1 made the room history world readable. Events may be now read by non-joined people. - + %1 maakte de kamergeschiedenis openbaar. Niet-deelnemers kunnen nu de kamer inzien. %1 set the room history visible to members from this point on. - + %1 maakte de kamergeschiedenis deelname-vereist. %1 set the room history visible to members since they were invited. - + %1 maakte de kamergeschiedenis zichtbaar vanaf het moment van uitnodigen. %1 set the room history visible to members since they joined the room. - + %1 maakte de kamergeschiedenis zichtbaar vanaf moment van deelname. %1 has changed the room's permissions. - + %1 heeft de rechten van de kamer aangepast. %1 was invited. - + %1 is uitgenodigd. %1 changed their avatar. - + %1 is van avatar veranderd. %1 changed some profile info. - + %1 heeft wat profielinformatie aangepast. %1 joined. - + %1 neemt nu deel. %1 joined via authorisation from %2's server. - + %1 neemt deel via autorisatie van %2's server. %1 rejected their invite. - + %1 heeft de uitnodiging geweigerd. Revoked the invite to %1. - + Uitnodiging van %1 is ingetrokken. %1 left the room. - + %1 heeft de kamer verlaten. Kicked %1. - + %1 is verwijderd. Unbanned %1. - + %1 is opnieuw toegelaten. %1 was banned. - + %1 is verbannen. Reason: %1 - + Reden: %1 %1 redacted their knock. - + %1 heeft het aankloppen ingetrokken. You joined this room. - Je bent lid geworden van deze kamer. + Je neemt nu deel aan deze kamer. %1 has changed their avatar and changed their display name to %2. - + %1 is van avatar veranderd en heet nu %2. %1 has changed their display name to %2. - + %1 heet nu %2. Rejected the knock from %1. - + Aankloppen van %1 geweigerd. %1 left after having already left! This is a leave event after the user already left and shouldn't happen apart from state resets - + %1 is vertrokken na reeds vertrokken te zijn! %1 knocked. - + %1 klopt aan. @@ -2041,7 +2045,7 @@ Example: https://server.my:8787 Edited - + Bewerkt @@ -2049,32 +2053,32 @@ Example: https://server.my:8787 No room open - + Geen kamer open %1 member(s) - + %1 deelnemer(s) join the conversation - + Neem deel aan het gesprek accept invite - + accepteer uitnodiging decline invite - + wijs uitnodiging af Back to room list - + Terug naar kamerlijst @@ -2082,7 +2086,7 @@ Example: https://server.my:8787 No encrypted private chat found with this user. Create an encrypted private chat with this user and try again. - + Geen versleutelde chat gevonden met deze gebruiker. Maak een versleutelde chat aan met deze gebruiker en probeer het opnieuw. @@ -2090,57 +2094,57 @@ Example: https://server.my:8787 Back to room list - + Terug naar kamerlijst No room selected - + Geen kamer geselecteerd This room is not encrypted! - + Deze kamer is niet versleuteld! This room contains only verified devices. - + Deze kamer bevat alleen geverifieerde apparaten. This rooms contain verified devices and devices which have never changed their master key. - + Deze kamer bevat geverifieerde apparaten en apparaten die nooit hun hoofdsleutel hebben aangepast. This room contains unverified devices! - + Deze kamer bevat ongeverifieerde apparaten! Room options - + Kameropties Invite users - Gebruikers uitnodigen + Gebruikers uitnodigen Members - Leden + Deelnemers Leave room - Kamer verlaten + Kamer verlaten Settings - Instellingen + Instellingen @@ -2161,58 +2165,58 @@ Example: https://server.my:8787 Global User Profile - + Globaal gebruikersprofiel Room User Profile - + Kamerspecifiek gebruikersprofiel Verify - + Verifiëren Ban the user - + Verban de gebruiker Start a private chat - + Begin een privéchat Kick the user - + Verwijder de gebruiker Unverify - + On-verifiëren Select an avatar - + Kies een avatar All Files (*) - Alle bestanden (*) + Alle bestanden (*) The selected file is not an image - + Het gekozen bestand is geen afbeelding Error while reading file: %1 - + Fout bij lezen bestand: %1 @@ -2221,7 +2225,7 @@ Example: https://server.my:8787 Default - + Standaard @@ -2239,140 +2243,148 @@ Example: https://server.my:8787 Group's sidebar - Zijbalk van groep + Zijbalk met groepen Circular Avatars - + Ronde avatars profile: %1 - + profiel: %1 Default - + Standaard CALLS - + OPROEPEN Cross Signing Keys - + Kruisversleutelingssleutels REQUEST - + OPVRAGEN DOWNLOAD - + DOWNLOADEN Keep the application running in the background after closing the client window. - + Blijf draaien in de achtergrond na het sluiten van het scherm. Start the application in the background without showing the client window. - + Start de applicatie in de achtergrond zonder het scherm te tonen. Change the appearance of user avatars in chats. OFF - square, ON - Circle. - + Verander het uiterlijk van avatars in de chats. +UIT - vierkant, AAN - cirkel. Show a column containing groups and tags next to the room list. - + Laat een kolom zien met groepen en markeringen naast de kamerlijst. Decrypt messages in sidebar - + Ontsleutel berichten in de zijbalk Decrypt the messages shown in the sidebar. Only affects messages in encrypted chats. - + Ontsleutel de berichten getoond in de zijbalk. +Heeft alleen effect op versleutelde chats. Privacy Screen - + Privacy scherm When the window loses focus, the timeline will be blurred. - + Als het scherm focus verliest, zal de tijdlijn +worden geblurt. Privacy screen timeout (in seconds [0 - 3600]) - + Privacy scherm wachttijd (in seconden [0 - 3600]) Set timeout (in seconds) for how long after window loses focus before the screen will be blurred. Set to 0 to blur immediately after focus loss. Max value of 1 hour (3600 seconds) - + Stel wachttijd (in seconden) voor hoe lang het duurt nadat +focus weg is voordat het scherm wordt geblurt. +Kies 0 om direct te blurren. Maximale waarde is 1 uur (3600 seconden) Show buttons in timeline - + Laat knoppen zien in tijdlijn Show buttons to quickly reply, react or access additional options next to each message. - + Laat knoppen zien om snel te reageren, beantwoorden, of extra opties te kunnen gebruiken naast elk bericht. Limit width of timeline - + Beperk breedte van tijdlijn Set the max width of messages in the timeline (in pixels). This can help readability on wide screen, when Nheko is maximised - + Stel de maximale breedte in van berichten in de tijdlijn (in pixels). Dit kan helpen bij de leesbaarheid als Nheko gemaximaliseerd is. Typing notifications - Meldingen bij typen van berichten + Typnotificaties Show who is typing in a room. This will also enable or disable sending typing notifications to others. - + Laat zien wie er typt in een kamer. +Dit schakelt ook het versturen van je eigen typnotificaties naar anderen in of uit. Sort rooms by unreads - + Sorteer kamers op ongelezen berichten Display rooms with new messages first. If this is off, the list of rooms will only be sorted by the timestamp of the last message in a room. If this is on, rooms which have active notifications (the small circle with a number in it) will be sorted on top. Rooms, that you have muted, will still be sorted by timestamp, since you don't seem to consider them as important as the other rooms. - + Laat kamers met nieuwe berichten eerst zien. +Indien uitgeschakeld, staan kamers gesorteerd op de tijd van het laatst ontvangen bericht. +Indien ingeschakeld, staan kamers met actieve notificaties (het cirkeltje met een getal erin) bovenaan. Kamers die je hebt gedempt zullen nog steeds op tijd zijn gesorteerd, want die vind je blijkbaar niet zo belangrijk als de andere kamers. @@ -2383,119 +2395,122 @@ If this is on, rooms which have active notifications (the small circle with a nu Show if your message was read. Status is displayed next to timestamps. - + Laat zien of je bericht gelezen is. +De status staat naast de tijdsindicatie. Send messages as Markdown - + Verstuur berichten in Markdown Allow using markdown in messages. When disabled, all messages are sent as a plain text. - + Sta het gebruik van Markdown in berichten toe. +Indien uitgeschakeld worden alle berichten als platte tekst verstuurd. Desktop notifications - + Bureaubladnotificaties Notify about received message when the client is not currently focused. - + Verstuur een notificatie over ontvangen berichten als het scherm geen focus heeft. Alert on notification - + Melding bij notificatie Show an alert when a message is received. This usually causes the application icon in the task bar to animate in some fashion. - + Activeer een melding als een bericht binnen komt. +Meestal zorgt dit dat het icoon in de taakbalk op een manier animeert of iets dergelijks. Highlight message on hover - + Oplichten van berichten onder muis Change the background color of messages when you hover over them. - + Veranderd de achtergrondkleur van het bericht waar de muiscursor op staat. Large Emoji in timeline - + Grote emoji in de tijdlijn Make font size larger if messages with only a few emojis are displayed. - + Maakt het lettertype groter als berichten met slechts enkele emoji worden getoond. Send encrypted messages to verified users only - + Verstuur alleen versleutelde berichten naar geverifieerde gebruikers Requires a user to be verified to send encrypted messages to them. This improves safety but makes E2EE more tedious. - + Vereist dat een gebruiker geverifieerd is voordat berichten worden versleuteld. Verbetert de beveiliging maar maakt versleutelen irritanter om in te stellen. Share keys with verified users and devices - + Deel sleutels met geverifieerde gebruikers en apparaten Automatically replies to key requests from other users, if they are verified, even if that device shouldn't have access to those keys otherwise. - + Beantwoord automatisch sleutelverzoeken van andere gebruikers, indien geverifieerd, ook als dat apparaat normaal geen toegang tot die sleutels had moeten hebben. Online Key Backup - + Online reservesleutel Download message encryption keys from and upload to the encrypted online key backup. - + Download van en upload naar de online reservesleutel. CACHED - + IN CACHE NOT CACHED - + NIET IN CACHE Scale factor - + Schaalfactor Change the scale factor of the whole user interface. - + Verander de schaalfactor van de gehele gebruikersinterface. Font size - + Lettertypegrootte Font Family - + Lettertype @@ -2505,72 +2520,72 @@ This usually causes the application icon in the task bar to animate in some fash Ringtone - + Beltoon Set the notification sound to play when a call invite arrives - + Stel het geluid in dat speelt als een oproep binnen komt Microphone - + Microfoon Camera - + Camera Camera resolution - + Cameraresolutie Camera frame rate - + Cameraverversingssnelheid Allow fallback call assist server - + Sta terugval naar oproepassistentieserver toe Will use turn.matrix.org as assist when your home server does not offer one. - + Zal turn.matrix.org gebruiken om te assisteren als je thuisserver geen TURN server heeft. Device ID - + Apparaat ID Device Fingerprint - + Apparaat vingerafdruk Session Keys - + Sessiesleutels IMPORT - + IMPORTEREN EXPORT - + EXPORTEREN ENCRYPTION - + VERSLEUTELING @@ -2580,77 +2595,77 @@ This usually causes the application icon in the task bar to animate in some fash INTERFACE - + INTERFACE Touchscreen mode - + Touchscreenmodus Will prevent text selection in the timeline to make touch scrolling easier. - + Voorkomt dat tekst geselecteerd wordt in de tijdlijn, om scrollen makkelijker te maken. Emoji Font Family - + Emoji lettertype Master signing key - + Hoofdsleutel Your most important key. You don't need to have it cached, since not caching it makes it less likely it can be stolen and it is only needed to rotate your other signing keys. - + Je belangrijkste sleutel. Deze hoeft niet gecached te zijn, en dat maakt het minder waarschijnlijk dat hij ooit gestolen wordt. Hij is alleen nodig om je andere sleutels te roteren. User signing key - + Gebruikerssleutel The key to verify other users. If it is cached, verifying a user will verify all their devices. - + De sleutel die wordt gebruikt om andere gebruikers te verifiëren. Indien gecached zal het verifiëren van een gebruiker alle apparaten van die gebruiker verifiëren. Self signing key - + Zelf ondertekenen sleutel The key to verify your own devices. If it is cached, verifying one of your devices will mark it verified for all your other devices and for users, that have verified you. - + De sleutel om je eigen apparaten mee te verifiëren. Indien gecached zal één van je apparaten verifiëren dat doen voor andere apparaten en gebruikers die jou geverifieerd hebben. Backup key - + Reservesleutel The key to decrypt online key backups. If it is cached, you can enable online key backup to store encryption keys securely encrypted on the server. - + De sleutel om online reservesleutels mee te ontsleutelen. Indien gecached kan je online reservesleutel activeren om je sleutels veilig versleuteld op de server op te slaan. Select a file - Kies een bestand + Kies een bestand All Files (*) - Alle bestanden (*) + Alle bestanden (*) Open Sessions File - + Open sessiebestand @@ -2660,34 +2675,34 @@ This usually causes the application icon in the task bar to animate in some fash Error - + Fout File Password - + Wachtwoord voor bestand Enter the passphrase to decrypt the file: - + Voer de wachtwoordzin in om het bestand te ontsleutelen: The password cannot be empty - + Het wachtwoord kan niet leeg zijn Enter passphrase to encrypt your session keys: - + Voer wachtwoordzin in om je sessiesleutels mee te versleutelen: File to save the exported session keys - + Bestand om geëxporteerde sessiesleutels in op te slaan @@ -2695,27 +2710,27 @@ This usually causes the application icon in the task bar to animate in some fash Waiting for other party… - + Wachten op andere kant… Waiting for other side to accept the verification request. - + Wachten op de andere kant om het verificatieverzoek te accepteren. Waiting for other side to continue the verification process. - + Wachten op de andere kant om het verificatieproces voort te zetten. Waiting for other side to complete the verification process. - + Wachten op de andere kant om het verificatieproces af te ronden. Cancel - Annuleren + Annuleren @@ -2723,7 +2738,7 @@ This usually causes the application icon in the task bar to animate in some fash Welcome to nheko! The desktop client for the Matrix protocol. - Welkom bij nheko! Dé computerclient voor het Matrix-protocol. + Welkom bij Nheko! De bureaubladclient voor het Matrix-protocol. @@ -2746,7 +2761,7 @@ This usually causes the application icon in the task bar to animate in some fash Yesterday - + Gisteren @@ -2754,12 +2769,12 @@ This usually causes the application icon in the task bar to animate in some fash Create room - + Kamer maken Cancel - Annuleren + Annuleren @@ -2784,7 +2799,7 @@ This usually causes the application icon in the task bar to animate in some fash Room Preset - Kamer-voorinstellingen + Kamer voorinstellingen @@ -2797,22 +2812,22 @@ This usually causes the application icon in the task bar to animate in some fash Open Fallback in Browser - + Open fallback in browser Cancel - Annuleren + Annuleren Confirm - + Bevestigen Open the fallback, follow the steps and confirm after completing them. - + Open de fallback, volg de stappen, en bevestig nadat je klaar bent. @@ -2820,17 +2835,17 @@ This usually causes the application icon in the task bar to animate in some fash Join - + Deelnemen Cancel - Annuleren + Annuleren Room ID or alias - Kamer-id of alias + Kamer ID of alias @@ -2838,12 +2853,12 @@ This usually causes the application icon in the task bar to animate in some fash Cancel - Annuleren + Annuleren Are you sure you want to leave? - Weet je zeker dat je wilt vertrekken? + Weet je zeker dat je de kamer wil verlaten? @@ -2851,7 +2866,7 @@ This usually causes the application icon in the task bar to animate in some fash Cancel - Annuleren + Annuleren @@ -2876,7 +2891,7 @@ This usually causes the application icon in the task bar to animate in some fash Media type: %1 Media size: %2 - Mediasoort: %1 + Mediatype: %1 Mediagrootte: %2 @@ -2886,12 +2901,12 @@ Mediagrootte: %2 Cancel - Annuleren + Annuleren Confirm - + Bevestigen @@ -2904,112 +2919,112 @@ Mediagrootte: %2 You sent an audio clip - + Je verstuurde een audio clip %1 sent an audio clip - + %1 verstuurde een audio clip You sent an image - + Je verstuurde een afbeelding %1 sent an image - + %1 verstuurde een afbeelding You sent a file - + Je verstuurde een bestand %1 sent a file - + %1 verstuurde een bestand You sent a video - + Je verstuurde een video %1 sent a video - + %1 verstuurde een video You sent a sticker - + Je verstuurde een sticker %1 sent a sticker - + %1 verstuurde een sticker You sent a notification - + Je verstuurde een notificatie %1 sent a notification - + %1 verstuurde een notificatie You: %1 - + Jij: %1 %1: %2 - + %1: %2 You sent an encrypted message - + Je hebt een versleuteld bericht verstuurd %1 sent an encrypted message - + %1 heeft een versleuteld bericht verstuurd You placed a call - + Je hebt een oproep geplaatst %1 placed a call - + %1 plaatste een oproep You answered a call - + Je beantwoordde een oproep %1 answered a call - + %1 beantwoordde een oproep You ended a call - + Je hing op %1 ended a call - + %1 hing op @@ -3017,7 +3032,7 @@ Mediagrootte: %2 Unknown Message Type - + Onbekend berichttype From 1b07bde788e629feb5d6528a59668a5189a80ad6 Mon Sep 17 00:00:00 2001 From: Nicolas Werner Date: Wed, 1 Sep 2021 01:23:20 +0200 Subject: [PATCH 073/232] update translations --- resources/langs/nheko_cs.ts | 105 ++++++++++----- resources/langs/nheko_de.ts | 229 ++++++++++++++++++++------------- resources/langs/nheko_el.ts | 105 ++++++++++----- resources/langs/nheko_en.ts | 107 ++++++++++----- resources/langs/nheko_eo.ts | 105 ++++++++++----- resources/langs/nheko_es.ts | 105 ++++++++++----- resources/langs/nheko_et.ts | 107 ++++++++++----- resources/langs/nheko_fi.ts | 105 ++++++++++----- resources/langs/nheko_fr.ts | 105 ++++++++++----- resources/langs/nheko_hu.ts | 105 ++++++++++----- resources/langs/nheko_it.ts | 105 ++++++++++----- resources/langs/nheko_ja.ts | 105 ++++++++++----- resources/langs/nheko_ml.ts | 105 ++++++++++----- resources/langs/nheko_nl.ts | 107 ++++++++++----- resources/langs/nheko_pl.ts | 105 ++++++++++----- resources/langs/nheko_pt_BR.ts | 105 ++++++++++----- resources/langs/nheko_pt_PT.ts | 105 ++++++++++----- resources/langs/nheko_ro.ts | 105 ++++++++++----- resources/langs/nheko_ru.ts | 105 ++++++++++----- resources/langs/nheko_si.ts | 105 ++++++++++----- resources/langs/nheko_sv.ts | 105 ++++++++++----- resources/langs/nheko_zh_CN.ts | 105 ++++++++++----- src/UserSettingsPage.cpp | 2 +- 23 files changed, 1716 insertions(+), 726 deletions(-) diff --git a/resources/langs/nheko_cs.ts b/resources/langs/nheko_cs.ts index fb380e76..7af17b73 100644 --- a/resources/langs/nheko_cs.ts +++ b/resources/langs/nheko_cs.ts @@ -125,23 +125,23 @@ ChatPage - + Failed to invite user: %1 - + Invited user: %1 - + Migrating the cache to the current version failed. This can have different reasons. Please open an issue and try to use an older version in the mean time. Alternatively you can try deleting the cache manually. - + Confirm join @@ -232,7 +232,7 @@ - + Cache migration failed! @@ -252,12 +252,14 @@ + + Failed to restore save data. Please login again. - + Failed to setup encryption keys. Server response: %1 %2. Please try again later. @@ -431,7 +433,7 @@ - + People @@ -616,7 +618,7 @@ - Stickers (*.png *.webp) + Stickers (*.png *.webp *.gif) @@ -657,7 +659,17 @@ - + + Remove from pack + + + + + Remove + + + + Cancel @@ -733,7 +745,7 @@ - + Failed to upload media. Please try again. @@ -885,7 +897,7 @@ Example: https://server.my:8787 MessageDelegate - + Encryption enabled @@ -984,7 +996,7 @@ Example: https://server.my:8787 - + Stickers @@ -1243,7 +1255,7 @@ Example: https://server.my:8787 QCoreApplication - + Create a unique profile, which allows you to log into several accounts at the same time and start multiple instances of nheko. @@ -1402,7 +1414,7 @@ Example: https://server.my:8787 RoomInfo - + no version stored @@ -1770,6 +1782,19 @@ Example: https://server.my:8787 + + SecretStorage + + + Failed to connect to secret storage + + + + + Nheko could not connect to the secure storage to save encryption secrets to. This can have multiple reasons. Check if your D-Bus service is running and you have configured a service like KWallet, Gnome Secrets or the equivalent for your platform. If you are having trouble, feel free to open an issue here: https://github.com/Nheko-Reborn/nheko/issues + + + SingleImagePackModel @@ -1789,7 +1814,7 @@ Example: https://server.my:8787 - + Failed to upload image: %1 @@ -2054,7 +2079,7 @@ Example: https://server.my:8787 - + %1 member(s) @@ -2082,7 +2107,7 @@ Example: https://server.my:8787 TimelineViewManager - + No encrypted private chat found with this user. Create an encrypted private chat with this user and try again. @@ -2220,8 +2245,8 @@ Example: https://server.my:8787 UserSettings - - + + Default @@ -2229,7 +2254,7 @@ Example: https://server.my:8787 UserSettingsPage - + Minimize to tray @@ -2249,12 +2274,12 @@ Example: https://server.my:8787 - + profile: %1 - + Default @@ -2400,6 +2425,11 @@ When disabled, all messages are sent as a plain text. + Play animated images only on hover + + + + Desktop notifications @@ -2470,7 +2500,17 @@ This usually causes the application icon in the task bar to animate in some fash - + + Enable online key backup + + + + + The Nheko authors recommend not enabling online key backup until symmetric online key backup is available. Enable anyway? + + + + CACHED @@ -2480,7 +2520,7 @@ This usually causes the application icon in the task bar to animate in some fash - + Scale factor @@ -2555,7 +2595,7 @@ This usually causes the application icon in the task bar to animate in some fash - + Session Keys @@ -2575,17 +2615,22 @@ This usually causes the application icon in the task bar to animate in some fash - + GENERAL - + INTERFACE - + + Plays media like GIFs or WEBPs only when explicitly hovering over them. + + + + Touchscreen mode @@ -2650,7 +2695,7 @@ This usually causes the application icon in the task bar to animate in some fash - + Open Sessions File @@ -2746,7 +2791,7 @@ This usually causes the application icon in the task bar to animate in some fash descriptiveTime - + Yesterday diff --git a/resources/langs/nheko_de.ts b/resources/langs/nheko_de.ts index a67e648a..cd6d1df8 100644 --- a/resources/langs/nheko_de.ts +++ b/resources/langs/nheko_de.ts @@ -125,23 +125,23 @@ ChatPage - + Failed to invite user: %1 Nutzer konnte nicht eingeladen werden: %1 - + Invited user: %1 Eingeladener Benutzer: %1 - + Migrating the cache to the current version failed. This can have different reasons. Please open an issue and try to use an older version in the mean time. Alternatively you can try deleting the cache manually. Das Migrieren des Caches auf die aktuelle Version ist fehlgeschlagen. Das kann verschiedene Gründe als Ursache haben. Bitte melde den Fehler und verwende in der Zwischenzeit eine ältere Version. Alternativ kannst du den Cache manuell löschen. - + Confirm join Beitritt bestätigen @@ -232,7 +232,7 @@ Möchtest du wirklich eine private Konversation mit %1 beginnen? - + Cache migration failed! Migration des Caches fehlgeschlagen! @@ -252,12 +252,14 @@ Wiederherstellung des OLM Accounts fehlgeschlagen. Bitte logge dich erneut ein. + + Failed to restore save data. Please login again. Gespeicherte Nachrichten konnten nicht wiederhergestellt werden. Bitte melde Dich erneut an. - + Failed to setup encryption keys. Server response: %1 %2. Please try again later. Fehler beim Setup der Verschlüsselungsschlüssel. Servermeldung: %1 %2. Bitte versuche es später erneut. @@ -431,7 +433,7 @@ Suche - + People Leute @@ -499,42 +501,42 @@ There is no key to unlock this message. We requested the key automatically, but you can try requesting it again if you are impatient. - + Kein Schlüssel für diese Nachricht vorhanden. Wir haben den Schlüssel automatisch angefragt, aber wenn du ungeduldig bist, kannst du den Schlüssel nocheinmal anfragen. This message couldn't be decrypted, because we only have a key for newer messages. You can try requesting access to this message. - + Diese Nachricht konnte nicht entschlüsselt werden, weil unser Schlüssel nur für neuere Nachrichten gültig ist. Du kannst den Schlüssel für ältere Nachrichten anfragen. There was an internal error reading the decryption key from the database. - + Es ist ein interner Fehler beim Laden des Schlüssels aus der Datenbank aufgetreten. There was an error decrypting this message. - + Beim Entschlüsseln der Nachricht ist ein Fehler aufgetreten. The message couldn't be parsed. - + Nheko hat die Nachricht nach der Entschlüsselung nicht verstanden. The encryption key was reused! Someone is possibly trying to insert false messages into this chat! - + Der Schlüssel für diese Nachricht wurde schon einmal verwendet! Vermutlich versucht jemand falsche Nachrichten in diese Unterhaltung einzufügen! Unknown decryption error - + Unbekannter Entschlüsselungsfehler Request key - + Schlüssel anfragen @@ -557,7 +559,7 @@ Encrypted by an unverified device or the key is from an untrusted source like the key backup. - + Nachricht verschlüsselt bei einem unverifizierten Gerät oder der Schlüssel ist aus einer nicht vertrauenswürdigen Quelle wie der Onlineschlüsselsicherung. @@ -607,64 +609,74 @@ Editing image pack - + Bilderpackung bearbeiten Add images - + Bilder hinzufügen - Stickers (*.png *.webp) - + Stickers (*.png *.webp *.gif) + Sticker (*.png *.webp *.gif) State key - + Eindeutiger Name Packname - + Paketname Attribution - + Attribution Use as Emoji - + Als Emoji verwenden Use as Sticker - + Als Sticker verwenden Shortcode - + Abkürzung Body - + Beschreibung - + + Remove from pack + Vom Paket entfernen + + + + Remove + Entfernen + + + Cancel Abbrechen Save - + Spechern @@ -672,42 +684,42 @@ Image pack settings - + Bilderpackungseinstellungen Create account pack - + Neue Packung erstellen New room pack - + Neue Packung Private pack - + Private Packung Pack from this room - + Packung aus diesem Raum Globally enabled pack - + Global aktivierte Packung Enable globally - + Global aktivieren Enables this pack to be used in all rooms - + Macht diese Packung in allen Räumen verfügbar @@ -733,7 +745,7 @@ Alle Dateien (*) - + Failed to upload media. Please try again. Medienupload fehlgeschlagen. Bitte versuche es erneut. @@ -889,7 +901,7 @@ Beispiel: https://mein.server:8787 MessageDelegate - + removed entfernt @@ -962,7 +974,7 @@ Beispiel: https://mein.server:8787 Allow them in - + Reinlassen @@ -988,7 +1000,7 @@ Beispiel: https://mein.server:8787 Schreibe eine Nachricht… - + Stickers Sticker @@ -1247,7 +1259,7 @@ Beispiel: https://mein.server:8787 QCoreApplication - + Create a unique profile, which allows you to log into several accounts at the same time and start multiple instances of nheko. Benutze ein separates profil, wodurch mehrere Accounts und Nhekoinstanzen zur gleichen Zeit verwendet werden können. @@ -1275,7 +1287,7 @@ Beispiel: https://mein.server:8787 Yesterday, %1 - + Gestern, %1 @@ -1329,12 +1341,12 @@ Beispiel: https://mein.server:8787 Registration token - + Registrierungstoken Please enter a valid registration token. - + Bitte gebe ein gültiges Registrierungstoken ein. @@ -1395,18 +1407,18 @@ Beispiel: https://mein.server:8787 Explore Public Rooms - + Öffentliche Räume erkunden Search for public rooms - + Suche nach öffentlichen Räumen RoomInfo - + no version stored keine Version gespeichert @@ -1426,12 +1438,12 @@ Beispiel: https://mein.server:8787 Leave Room - + Raum verlassen Are you sure you want to leave this room? - + Willst du wirklich diesen Raum verlassen? @@ -1538,22 +1550,22 @@ Beispiel: https://mein.server:8787 This room is not encrypted! - + Dieser Raum ist nicht verschlüsselt! This user is verified. - + Der Nutzer ist verifiziert. This user isn't verified, but is still using the same master key from the first time you met. - + Der Nutzer ist nicht verifiziert, aber hat schon immer diese Identität verwendet. This user has unverified devices! - + Dieser Nutzer hat unverifizierte Geräte! @@ -1596,7 +1608,7 @@ Beispiel: https://mein.server:8787 Room access - + Zugangsberechtigungen @@ -1616,12 +1628,12 @@ Beispiel: https://mein.server:8787 By knocking - + Durch Anklopfen Restricted by membership in other rooms - + Durch Teilnahme an anderen Räumen @@ -1642,17 +1654,17 @@ Beispiel: https://mein.server:8787 Sticker & Emote Settings - + Sticker- & Emoteeinstellungen Change - + Ändern Change what packs are enabled, remove packs or create new ones - + Ändere welche Packungen aktiviert sind, entferne oder erstelle neue Packungen @@ -1773,28 +1785,41 @@ Beispiel: https://mein.server:8787 Abbrechen + + SecretStorage + + + Failed to connect to secret storage + Verbindung zum kryptografischen Speicher fehlgeschlagen + + + + Nheko could not connect to the secure storage to save encryption secrets to. This can have multiple reasons. Check if your D-Bus service is running and you have configured a service like KWallet, Gnome Secrets or the equivalent for your platform. If you are having trouble, feel free to open an issue here: https://github.com/Nheko-Reborn/nheko/issues + Nheko konnte sich nicht mit dem kryptografischen Speicher verbinden um die Geheimnisse zu speichern. Dies kann verschiedene Gründe haben. Stelle sicher, dass der D-Bus-Dienst läuft und du einen Dienst wie KWallet, Gnome Secrets oder dein Platformäquivalent konfiguriert hast. Wenn du Probleme hast, kannst du hier um Hilfe fragen: https://github.com/Nheko-Reborn/nheko/issues + + SingleImagePackModel Failed to update image pack: %1 - + Konnte die Bilderpackung nicht aktualisieren: %1 Failed to delete old image pack: %1 - + Konnte die alte Bilderpackung nicht löschen: %1 Failed to open image: %1 - + Konnte Bild nicht öffnen: %1 - + Failed to upload image: %1 - + Konnte Bild nicht hochladen: %1 @@ -1901,12 +1926,12 @@ Beispiel: https://mein.server:8787 %1 allowed to join this room by knocking. - + %1 hat erlaubt Leuten diesen Raum durch Anklopfen beizutreten. %1 allowed members of the following rooms to automatically join this room: %2 - + %1 hat erlaubt Mitglieder aus folgenden Räumen diesen Raum automatisch zu betreten: %2 @@ -1966,7 +1991,7 @@ Beispiel: https://mein.server:8787 %1 joined via authorisation from %2's server. - + %1 hat den Raum durch Authorisierung von %2s Server betreten. @@ -2056,7 +2081,7 @@ Beispiel: https://mein.server:8787 Kein Raum geöffnet - + %1 member(s) %1 Teilnehmer @@ -2084,7 +2109,7 @@ Beispiel: https://mein.server:8787 TimelineViewManager - + No encrypted private chat found with this user. Create an encrypted private chat with this user and try again. Keinen verschlüsselten Chat mit diesem User gefunden. Erstelle einen verschlüsselten 1:1 Chat mit diesem Nutzer und versuche es erneut. @@ -2104,22 +2129,22 @@ Beispiel: https://mein.server:8787 This room is not encrypted! - + Dieser Raum ist nicht verschlüsselt! This room contains only verified devices. - + Dieser Raum enthält nur verifizierte Geräte. This rooms contain verified devices and devices which have never changed their master key. - + Dieser Raum beinhaltet verifizierte Geräte und Geräte, die nie ihre Identität gewechselt haben. This room contains unverified devices! - + Dieser Raum enthält unverifizierte Geräte! @@ -2222,8 +2247,8 @@ Beispiel: https://mein.server:8787 UserSettings - - + + Default Standard @@ -2231,7 +2256,7 @@ Beispiel: https://mein.server:8787 UserSettingsPage - + Minimize to tray Ins Benachrichtigungsfeld minimieren @@ -2251,12 +2276,12 @@ Beispiel: https://mein.server:8787 Runde Profilbilder - + profile: %1 Profil: %1 - + Default Standard @@ -2411,6 +2436,11 @@ Wenn deaktiviert werden alle Nachrichten als unformatierter Text gesendet. + Play animated images only on hover + Animiete Bilder nur abspielen, wenn die Maus über diesen ist + + + Desktop notifications Desktopbenachrichtigungen @@ -2454,12 +2484,12 @@ Normalerweise animiert das den Taskbaricon oder färbt das Fenster orange ein. Send encrypted messages to verified users only - + Sende verschlüsselte Nachrichten nur an verifizierte Nutzer Requires a user to be verified to send encrypted messages to them. This improves safety but makes E2EE more tedious. - + Sendet Schlüssel für verschlüsselte Nachrichten nur an verifizierte Geräte. Das erhöht die Sicherheit, aber macht die Ende-zu-Ende Verschlüsselung komplizierter, weil jeder Nutzer verifiziert werden muss. @@ -2469,20 +2499,30 @@ Normalerweise animiert das den Taskbaricon oder färbt das Fenster orange ein. Automatically replies to key requests from other users, if they are verified, even if that device shouldn't have access to those keys otherwise. - + Automatisch Schlüssel an verifizierte Nutzer weiterleiten, auch wenn der Nutzer eigentlich keinen Zugriff auf diese Schlüssel haben sollte. Online Key Backup - + Onlinenachrichtenschlüsselspeicher Download message encryption keys from and upload to the encrypted online key backup. - + Speichere eine Kopie der Nachrichtenschlüssel verschlüsselt auf dem Server. - + + Enable online key backup + Onlinenachrichtenschlüsselspeicher aktivieren + + + + The Nheko authors recommend not enabling online key backup until symmetric online key backup is available. Enable anyway? + Die Nhekoentwickler empfehlen aktuell nicht die Onlinesicherung zu ativieren bevor die symmetrische Methode zu verfügung steht. Trotzdem aktivieren? + + + CACHED IM CACHE @@ -2492,7 +2532,7 @@ Normalerweise animiert das den Taskbaricon oder färbt das Fenster orange ein.NICHT IM CACHE - + Scale factor Skalierungsfaktor @@ -2567,7 +2607,7 @@ Normalerweise animiert das den Taskbaricon oder färbt das Fenster orange ein.Gerätefingerabdruck - + Session Keys Sitzungsschlüssel @@ -2587,17 +2627,22 @@ Normalerweise animiert das den Taskbaricon oder färbt das Fenster orange ein.VERSCHLÜSSELUNG - + GENERAL ALLGEMEINES - + INTERFACE OBERFLÄCHE - + + Plays media like GIFs or WEBPs only when explicitly hovering over them. + Spiele Medien wie GIF oder WEBP nur ab, wenn du die Maus darüber bewegst. + + + Touchscreen mode Touchscreenmodus @@ -2662,7 +2707,7 @@ Normalerweise animiert das den Taskbaricon oder färbt das Fenster orange ein.Alle Dateien (*) - + Open Sessions File Öffne Sessions Datei @@ -2758,7 +2803,7 @@ Normalerweise animiert das den Taskbaricon oder färbt das Fenster orange ein. descriptiveTime - + Yesterday Gestern diff --git a/resources/langs/nheko_el.ts b/resources/langs/nheko_el.ts index c5bf4222..928e0a93 100644 --- a/resources/langs/nheko_el.ts +++ b/resources/langs/nheko_el.ts @@ -125,23 +125,23 @@ ChatPage - + Failed to invite user: %1 - + Invited user: %1 - + Migrating the cache to the current version failed. This can have different reasons. Please open an issue and try to use an older version in the mean time. Alternatively you can try deleting the cache manually. - + Confirm join @@ -232,7 +232,7 @@ - + Cache migration failed! @@ -252,12 +252,14 @@ + + Failed to restore save data. Please login again. - + Failed to setup encryption keys. Server response: %1 %2. Please try again later. @@ -431,7 +433,7 @@ - + People @@ -616,7 +618,7 @@ - Stickers (*.png *.webp) + Stickers (*.png *.webp *.gif) @@ -657,7 +659,17 @@ - + + Remove from pack + + + + + Remove + + + + Cancel Άκυρο @@ -733,7 +745,7 @@ Όλα τα αρχεία (*) - + Failed to upload media. Please try again. @@ -885,7 +897,7 @@ Example: https://server.my:8787 MessageDelegate - + removed @@ -984,7 +996,7 @@ Example: https://server.my:8787 Γράψε ένα μήνυμα... - + Stickers @@ -1243,7 +1255,7 @@ Example: https://server.my:8787 QCoreApplication - + Create a unique profile, which allows you to log into several accounts at the same time and start multiple instances of nheko. @@ -1402,7 +1414,7 @@ Example: https://server.my:8787 RoomInfo - + no version stored @@ -1769,6 +1781,19 @@ Example: https://server.my:8787 Άκυρο + + SecretStorage + + + Failed to connect to secret storage + + + + + Nheko could not connect to the secure storage to save encryption secrets to. This can have multiple reasons. Check if your D-Bus service is running and you have configured a service like KWallet, Gnome Secrets or the equivalent for your platform. If you are having trouble, feel free to open an issue here: https://github.com/Nheko-Reborn/nheko/issues + + + SingleImagePackModel @@ -1788,7 +1813,7 @@ Example: https://server.my:8787 - + Failed to upload image: %1 @@ -2052,7 +2077,7 @@ Example: https://server.my:8787 - + %1 member(s) @@ -2080,7 +2105,7 @@ Example: https://server.my:8787 TimelineViewManager - + No encrypted private chat found with this user. Create an encrypted private chat with this user and try again. @@ -2218,8 +2243,8 @@ Example: https://server.my:8787 UserSettings - - + + Default @@ -2227,7 +2252,7 @@ Example: https://server.my:8787 UserSettingsPage - + Minimize to tray Ελαχιστοποίηση @@ -2247,12 +2272,12 @@ Example: https://server.my:8787 - + profile: %1 - + Default @@ -2398,6 +2423,11 @@ When disabled, all messages are sent as a plain text. + Play animated images only on hover + + + + Desktop notifications @@ -2468,7 +2498,17 @@ This usually causes the application icon in the task bar to animate in some fash - + + Enable online key backup + + + + + The Nheko authors recommend not enabling online key backup until symmetric online key backup is available. Enable anyway? + + + + CACHED @@ -2478,7 +2518,7 @@ This usually causes the application icon in the task bar to animate in some fash - + Scale factor @@ -2553,7 +2593,7 @@ This usually causes the application icon in the task bar to animate in some fash - + Session Keys @@ -2573,17 +2613,22 @@ This usually causes the application icon in the task bar to animate in some fash - + GENERAL ΓΕΝΙΚΑ - + INTERFACE - + + Plays media like GIFs or WEBPs only when explicitly hovering over them. + + + + Touchscreen mode @@ -2648,7 +2693,7 @@ This usually causes the application icon in the task bar to animate in some fash Όλα τα αρχεία (*) - + Open Sessions File @@ -2744,7 +2789,7 @@ This usually causes the application icon in the task bar to animate in some fash descriptiveTime - + Yesterday diff --git a/resources/langs/nheko_en.ts b/resources/langs/nheko_en.ts index 86a9b36a..59ba7f5f 100644 --- a/resources/langs/nheko_en.ts +++ b/resources/langs/nheko_en.ts @@ -125,23 +125,23 @@ ChatPage - + Failed to invite user: %1 Failed to invite user: %1 - + Invited user: %1 Invited user: %1 - + Migrating the cache to the current version failed. This can have different reasons. Please open an issue and try to use an older version in the mean time. Alternatively you can try deleting the cache manually. Migrating the cache to the current version failed. This can have different reasons. Please open an issue and try to use an older version in the mean time. Alternatively you can try deleting the cache manually. - + Confirm join Confirm join @@ -232,7 +232,7 @@ Do you really want to start a private chat with %1? - + Cache migration failed! Cache migration failed! @@ -252,12 +252,14 @@ Failed to restore OLM account. Please login again. + + Failed to restore save data. Please login again. Failed to restore save data. Please login again. - + Failed to setup encryption keys. Server response: %1 %2. Please try again later. Failed to setup encryption keys. Server response: %1 %2. Please try again later. @@ -431,7 +433,7 @@ Search - + People People @@ -616,8 +618,8 @@ - Stickers (*.png *.webp) - Stickers (*.png *.webp) + Stickers (*.png *.webp *.gif) + Stickers (*.png *.webp *.gif) @@ -657,7 +659,17 @@ Body - + + Remove from pack + Remove from pack + + + + Remove + Remove + + + Cancel Cancel @@ -733,7 +745,7 @@ All Files (*) - + Failed to upload media. Please try again. Failed to upload media. Please try again. @@ -889,7 +901,7 @@ Example: https://server.my:8787 MessageDelegate - + Encryption enabled Encryption enabled @@ -988,7 +1000,7 @@ Example: https://server.my:8787 Write a message… - + Stickers Stickers @@ -1247,7 +1259,7 @@ Example: https://server.my:8787 QCoreApplication - + Create a unique profile, which allows you to log into several accounts at the same time and start multiple instances of nheko. Create a unique profile, which allows you to log into several accounts at the same time and start multiple instances of nheko. @@ -1406,7 +1418,7 @@ Example: https://server.my:8787 RoomInfo - + no version stored no version stored @@ -1773,6 +1785,19 @@ Example: https://server.my:8787 Cancel + + SecretStorage + + + Failed to connect to secret storage + Failed to connect to secret storage + + + + Nheko could not connect to the secure storage to save encryption secrets to. This can have multiple reasons. Check if your D-Bus service is running and you have configured a service like KWallet, Gnome Secrets or the equivalent for your platform. If you are having trouble, feel free to open an issue here: https://github.com/Nheko-Reborn/nheko/issues + Nheko could not connect to the secure storage to save encryption secrets to. This can have multiple reasons. Check if your D-Bus service is running and you have configured a service like KWallet, Gnome Secrets or the equivalent for your platform. If you are having trouble, feel free to open an issue here: https://github.com/Nheko-Reborn/nheko/issues + + SingleImagePackModel @@ -1792,7 +1817,7 @@ Example: https://server.my:8787 Failed to open image: %1 - + Failed to upload image: %1 Failed to upload image: %1 @@ -2056,7 +2081,7 @@ Example: https://server.my:8787 No room open - + %1 member(s) %1 member(s) @@ -2084,7 +2109,7 @@ Example: https://server.my:8787 TimelineViewManager - + No encrypted private chat found with this user. Create an encrypted private chat with this user and try again. No encrypted private chat found with this user. Create an encrypted private chat with this user and try again. @@ -2222,8 +2247,8 @@ Example: https://server.my:8787 UserSettings - - + + Default Default @@ -2231,7 +2256,7 @@ Example: https://server.my:8787 UserSettingsPage - + Minimize to tray Minimize to tray @@ -2251,12 +2276,12 @@ Example: https://server.my:8787 Circular Avatars - + profile: %1 profile: %1 - + Default Default @@ -2412,6 +2437,11 @@ When disabled, all messages are sent as a plain text. + Play animated images only on hover + Play animated images only on hover + + + Desktop notifications Desktop notifications @@ -2483,7 +2513,17 @@ This usually causes the application icon in the task bar to animate in some fash Download message encryption keys from and upload to the encrypted online key backup. - + + Enable online key backup + Enable online key backup + + + + The Nheko authors recommend not enabling online key backup until symmetric online key backup is available. Enable anyway? + The Nheko authors recommend not enabling online key backup until symmetric online key backup is available. Enable anyway? + + + CACHED CACHED @@ -2493,7 +2533,7 @@ This usually causes the application icon in the task bar to animate in some fash NOT CACHED - + Scale factor Scale factor @@ -2568,7 +2608,7 @@ This usually causes the application icon in the task bar to animate in some fash Device Fingerprint - + Session Keys Session Keys @@ -2588,17 +2628,22 @@ This usually causes the application icon in the task bar to animate in some fash ENCRYPTION - + GENERAL GENERAL - + INTERFACE INTERFACE - + + Plays media like GIFs or WEBPs only when explicitly hovering over them. + Plays media like GIFs or WEBPs only when explicitly hovering over them. + + + Touchscreen mode Touchscreen mode @@ -2663,7 +2708,7 @@ This usually causes the application icon in the task bar to animate in some fash All Files (*) - + Open Sessions File Open Sessions File @@ -2759,7 +2804,7 @@ This usually causes the application icon in the task bar to animate in some fash descriptiveTime - + Yesterday Yesterday diff --git a/resources/langs/nheko_eo.ts b/resources/langs/nheko_eo.ts index 88828a4a..654acd23 100644 --- a/resources/langs/nheko_eo.ts +++ b/resources/langs/nheko_eo.ts @@ -125,23 +125,23 @@ ChatPage - + Failed to invite user: %1 Malsukcesis inviti uzanton: %1 - + Invited user: %1 Invitita uzanto: %1 - + Migrating the cache to the current version failed. This can have different reasons. Please open an issue and try to use an older version in the mean time. Alternatively you can try deleting the cache manually. Malsukcesis migrado de kaŝmemoro al nuna versio. Tio povas havi diversajn kialojn. Bonvolu raporti eraron kaj dume provi malpli novan version. Alternative, vi povas provi forigi la kaŝmemoron permane. - + Confirm join Konfirmu aliĝon @@ -233,7 +233,7 @@ Ĉu vi certe volas komenci privatan babilon kun %1? - + Cache migration failed! Malsukcesis migrado de kaŝmemoro! @@ -253,12 +253,14 @@ + + Failed to restore save data. Please login again. - + Failed to setup encryption keys. Server response: %1 %2. Please try again later. Malsukcesis agordi ĉifrajn ŝlosilojn. Respondo de servilo: %1 %2. Bonvolu reprovi poste. @@ -432,7 +434,7 @@ Serĉu - + People Homoj @@ -617,7 +619,7 @@ - Stickers (*.png *.webp) + Stickers (*.png *.webp *.gif) @@ -658,7 +660,17 @@ - + + Remove from pack + + + + + Remove + + + + Cancel Nuligi @@ -734,7 +746,7 @@ Ĉiuj dosieroj (*) - + Failed to upload media. Please try again. Malsukcesis alŝuti vidaŭdaĵojn. Bonvolu reprovi. @@ -892,7 +904,7 @@ Ekzemplo: https://servilo.mia:8787 MessageDelegate - + removed forigita @@ -991,7 +1003,7 @@ Ekzemplo: https://servilo.mia:8787 Skribu mesaĝon… - + Stickers @@ -1250,7 +1262,7 @@ Ekzemplo: https://servilo.mia:8787 QCoreApplication - + Create a unique profile, which allows you to log into several accounts at the same time and start multiple instances of nheko. @@ -1409,7 +1421,7 @@ Ekzemplo: https://servilo.mia:8787 RoomInfo - + no version stored @@ -1776,6 +1788,19 @@ Ekzemplo: https://servilo.mia:8787 Nuligi + + SecretStorage + + + Failed to connect to secret storage + + + + + Nheko could not connect to the secure storage to save encryption secrets to. This can have multiple reasons. Check if your D-Bus service is running and you have configured a service like KWallet, Gnome Secrets or the equivalent for your platform. If you are having trouble, feel free to open an issue here: https://github.com/Nheko-Reborn/nheko/issues + + + SingleImagePackModel @@ -1795,7 +1820,7 @@ Ekzemplo: https://servilo.mia:8787 - + Failed to upload image: %1 @@ -2062,7 +2087,7 @@ Ekzemplo: https://servilo.mia:8787 - + %1 member(s) %1 ĉambrano(j) @@ -2090,7 +2115,7 @@ Ekzemplo: https://servilo.mia:8787 TimelineViewManager - + No encrypted private chat found with this user. Create an encrypted private chat with this user and try again. @@ -2228,8 +2253,8 @@ Ekzemplo: https://servilo.mia:8787 UserSettings - - + + Default @@ -2237,7 +2262,7 @@ Ekzemplo: https://servilo.mia:8787 UserSettingsPage - + Minimize to tray @@ -2257,12 +2282,12 @@ Ekzemplo: https://servilo.mia:8787 - + profile: %1 - + Default @@ -2425,6 +2450,11 @@ Kun ĉi tio malŝaltita, ĉiuj mesaĝoj sendiĝas en plata teksto. + Play animated images only on hover + + + + Desktop notifications Labortablaj sciigoj @@ -2495,7 +2525,17 @@ This usually causes the application icon in the task bar to animate in some fash - + + Enable online key backup + + + + + The Nheko authors recommend not enabling online key backup until symmetric online key backup is available. Enable anyway? + + + + CACHED @@ -2505,7 +2545,7 @@ This usually causes the application icon in the task bar to animate in some fash - + Scale factor @@ -2580,7 +2620,7 @@ This usually causes the application icon in the task bar to animate in some fash - + Session Keys @@ -2600,17 +2640,22 @@ This usually causes the application icon in the task bar to animate in some fash - + GENERAL - + INTERFACE - + + Plays media like GIFs or WEBPs only when explicitly hovering over them. + + + + Touchscreen mode @@ -2675,7 +2720,7 @@ This usually causes the application icon in the task bar to animate in some fash Ĉiuj dosieroj (*) - + Open Sessions File @@ -2772,7 +2817,7 @@ This usually causes the application icon in the task bar to animate in some fash descriptiveTime - + Yesterday Hieraŭ diff --git a/resources/langs/nheko_es.ts b/resources/langs/nheko_es.ts index 638a2922..4cd55927 100644 --- a/resources/langs/nheko_es.ts +++ b/resources/langs/nheko_es.ts @@ -125,23 +125,23 @@ ChatPage - + Failed to invite user: %1 No se pudo invitar al usuario: %1 - + Invited user: %1 Usuario invitado: %1 - + Migrating the cache to the current version failed. This can have different reasons. Please open an issue and try to use an older version in the mean time. Alternatively you can try deleting the cache manually. La migración de la caché a la versión actual ha fallado. Esto puede deberse a distintos motivos. Por favor, reporte el incidente y mientras tanto intente usar una versión anterior. También puede probar a borrar la caché manualmente. - + Confirm join @@ -232,7 +232,7 @@ - + Cache migration failed! @@ -252,12 +252,14 @@ + + Failed to restore save data. Please login again. - + Failed to setup encryption keys. Server response: %1 %2. Please try again later. @@ -431,7 +433,7 @@ - + People @@ -616,7 +618,7 @@ - Stickers (*.png *.webp) + Stickers (*.png *.webp *.gif) @@ -657,7 +659,17 @@ - + + Remove from pack + + + + + Remove + + + + Cancel Cancelar @@ -733,7 +745,7 @@ - + Failed to upload media. Please try again. @@ -885,7 +897,7 @@ Example: https://server.my:8787 MessageDelegate - + Encryption enabled @@ -984,7 +996,7 @@ Example: https://server.my:8787 - + Stickers @@ -1243,7 +1255,7 @@ Example: https://server.my:8787 QCoreApplication - + Create a unique profile, which allows you to log into several accounts at the same time and start multiple instances of nheko. @@ -1402,7 +1414,7 @@ Example: https://server.my:8787 RoomInfo - + no version stored @@ -1769,6 +1781,19 @@ Example: https://server.my:8787 Cancelar + + SecretStorage + + + Failed to connect to secret storage + + + + + Nheko could not connect to the secure storage to save encryption secrets to. This can have multiple reasons. Check if your D-Bus service is running and you have configured a service like KWallet, Gnome Secrets or the equivalent for your platform. If you are having trouble, feel free to open an issue here: https://github.com/Nheko-Reborn/nheko/issues + + + SingleImagePackModel @@ -1788,7 +1813,7 @@ Example: https://server.my:8787 - + Failed to upload image: %1 @@ -2052,7 +2077,7 @@ Example: https://server.my:8787 - + %1 member(s) @@ -2080,7 +2105,7 @@ Example: https://server.my:8787 TimelineViewManager - + No encrypted private chat found with this user. Create an encrypted private chat with this user and try again. @@ -2218,8 +2243,8 @@ Example: https://server.my:8787 UserSettings - - + + Default @@ -2227,7 +2252,7 @@ Example: https://server.my:8787 UserSettingsPage - + Minimize to tray @@ -2247,12 +2272,12 @@ Example: https://server.my:8787 - + profile: %1 - + Default @@ -2398,6 +2423,11 @@ When disabled, all messages are sent as a plain text. + Play animated images only on hover + + + + Desktop notifications @@ -2468,7 +2498,17 @@ This usually causes the application icon in the task bar to animate in some fash - + + Enable online key backup + + + + + The Nheko authors recommend not enabling online key backup until symmetric online key backup is available. Enable anyway? + + + + CACHED @@ -2478,7 +2518,7 @@ This usually causes the application icon in the task bar to animate in some fash - + Scale factor @@ -2553,7 +2593,7 @@ This usually causes the application icon in the task bar to animate in some fash - + Session Keys @@ -2573,17 +2613,22 @@ This usually causes the application icon in the task bar to animate in some fash - + GENERAL - + INTERFACE - + + Plays media like GIFs or WEBPs only when explicitly hovering over them. + + + + Touchscreen mode @@ -2648,7 +2693,7 @@ This usually causes the application icon in the task bar to animate in some fash - + Open Sessions File @@ -2744,7 +2789,7 @@ This usually causes the application icon in the task bar to animate in some fash descriptiveTime - + Yesterday diff --git a/resources/langs/nheko_et.ts b/resources/langs/nheko_et.ts index 36f25081..2fd86764 100644 --- a/resources/langs/nheko_et.ts +++ b/resources/langs/nheko_et.ts @@ -125,23 +125,23 @@ ChatPage - + Failed to invite user: %1 Kutse saatmine kasutajale ei õnnestunud: %1 - + Invited user: %1 Kutsutud kasutaja: %1 - + Migrating the cache to the current version failed. This can have different reasons. Please open an issue and try to use an older version in the mean time. Alternatively you can try deleting the cache manually. Puhverdatud andmete muutmine sobivaks rakenduse praeguse versiooniga ei õnnestunud. Sellel võib olla erinevaid põhjuseid. Palun saada meile veateade ja seni kasuta vanemat rakenduse versiooni. Aga kui sa soovid proovida, siis kustuta puhverdatud andmed käsitsi. - + Confirm join Kinnita liitumine @@ -232,7 +232,7 @@ Kas sa kindlasti soovid alustada otsevestlust kasutajaga %1? - + Cache migration failed! Puhvri versiooniuuendus ebaõnnestus! @@ -252,12 +252,14 @@ OLM konto taastamine ei õnnestunud. Palun logi uuesti sisse. + + Failed to restore save data. Please login again. Salvestatud andmete taastamine ei õnnestunud. Palun logi uuesti sisse. - + Failed to setup encryption keys. Server response: %1 %2. Please try again later. Krüptovõtmete kasutusele võtmine ei õnnestunud. Koduserveri vastus päringule: %1 %2. Palun proovi hiljem uuesti. @@ -431,7 +433,7 @@ Otsi - + People Inimesed @@ -616,8 +618,8 @@ - Stickers (*.png *.webp) - Kleepsud (*.png *.webp) + Stickers (*.png *.webp *.gif) + @@ -657,7 +659,17 @@ Sisu - + + Remove from pack + + + + + Remove + + + + Cancel Loobu @@ -733,7 +745,7 @@ Kõik failid (*) - + Failed to upload media. Please try again. Meediafailide üleslaadimine ei õnnestunud. Palun proovi uuesti. @@ -889,7 +901,7 @@ Näiteks: https://server.minu:8787 MessageDelegate - + Encryption enabled Krüptimine on kasutusel @@ -988,7 +1000,7 @@ Näiteks: https://server.minu:8787 Kirjuta sõnum… - + Stickers Kleepsud @@ -1247,7 +1259,7 @@ Näiteks: https://server.minu:8787 QCoreApplication - + Create a unique profile, which allows you to log into several accounts at the same time and start multiple instances of nheko. Loo unikaalne profiil, mis võimaldab sul logida samaaegselt sisse erinevatele kasutajakontodele ning käivitada mitu Nheko programmiakent. @@ -1406,7 +1418,7 @@ Näiteks: https://server.minu:8787 RoomInfo - + no version stored salvestatud versiooni ei leidu @@ -1773,6 +1785,19 @@ Näiteks: https://server.minu:8787 Loobu + + SecretStorage + + + Failed to connect to secret storage + + + + + Nheko could not connect to the secure storage to save encryption secrets to. This can have multiple reasons. Check if your D-Bus service is running and you have configured a service like KWallet, Gnome Secrets or the equivalent for your platform. If you are having trouble, feel free to open an issue here: https://github.com/Nheko-Reborn/nheko/issues + + + SingleImagePackModel @@ -1792,7 +1817,7 @@ Näiteks: https://server.minu:8787 Pildi avamine ei õnnestunud: %1 - + Failed to upload image: %1 Faili üleslaadimine ei õnnestunud: %1 @@ -2056,7 +2081,7 @@ Näiteks: https://server.minu:8787 Ühtegi jututuba pole avatud - + %1 member(s) %1 liige(t) @@ -2084,7 +2109,7 @@ Näiteks: https://server.minu:8787 TimelineViewManager - + No encrypted private chat found with this user. Create an encrypted private chat with this user and try again. Ühtegi krüptitud vestlust selle kasutajaga ei leidunud. Palun loo temaga krüptitud vestlus ja proovi uuesti. @@ -2222,8 +2247,8 @@ Näiteks: https://server.minu:8787 UserSettings - - + + Default Vaikimisi @@ -2231,7 +2256,7 @@ Näiteks: https://server.minu:8787 UserSettingsPage - + Minimize to tray Vähenda tegumiribale @@ -2251,12 +2276,12 @@ Näiteks: https://server.minu:8787 Ümmargused tunnuspildid - + profile: %1 Profiil: %1 - + Default Vaikimisi @@ -2412,6 +2437,11 @@ Kui Markdown ei ole kasutusel, siis saadetakse kõik sõnumid vormindamata tekst + Play animated images only on hover + + + + Desktop notifications Töölauakeskkonna teavitused @@ -2483,7 +2513,17 @@ See tavaliselt tähendab, et rakenduse ikoon tegumiribal annab mingit sorti anim Luba krüptitud võtmete varunduseks laadida sõnumite krüptovõtmeid sinu serverisse või sinu serverist. - + + Enable online key backup + + + + + The Nheko authors recommend not enabling online key backup until symmetric online key backup is available. Enable anyway? + + + + CACHED PUHVERDATUD @@ -2493,7 +2533,7 @@ See tavaliselt tähendab, et rakenduse ikoon tegumiribal annab mingit sorti anim PUHVERDAMATA - + Scale factor Mastaabitegur @@ -2568,7 +2608,7 @@ See tavaliselt tähendab, et rakenduse ikoon tegumiribal annab mingit sorti anim Seadme sõrmejälg - + Session Keys Sessioonivõtmed @@ -2588,17 +2628,22 @@ See tavaliselt tähendab, et rakenduse ikoon tegumiribal annab mingit sorti anim KRÜPTIMINE - + GENERAL ÜLDISED SEADISTUSED - + INTERFACE LIIDES - + + Plays media like GIFs or WEBPs only when explicitly hovering over them. + + + + Touchscreen mode Puuteekraani režiim @@ -2663,7 +2708,7 @@ See tavaliselt tähendab, et rakenduse ikoon tegumiribal annab mingit sorti anim Kõik failid (*) - + Open Sessions File Ava sessioonide fail @@ -2759,7 +2804,7 @@ See tavaliselt tähendab, et rakenduse ikoon tegumiribal annab mingit sorti anim descriptiveTime - + Yesterday Eile diff --git a/resources/langs/nheko_fi.ts b/resources/langs/nheko_fi.ts index c5de1d45..cadf9497 100644 --- a/resources/langs/nheko_fi.ts +++ b/resources/langs/nheko_fi.ts @@ -125,23 +125,23 @@ ChatPage - + Failed to invite user: %1 Käyttäjää %1 ei onnistuttu kutsumaan - + Invited user: %1 Kutsuttu käyttäjä: %1 - + Migrating the cache to the current version failed. This can have different reasons. Please open an issue and try to use an older version in the mean time. Alternatively you can try deleting the cache manually. Välimuistin tuominen nykyiseen versioon epäonnistui. Tällä voi olla eri syitä. Luo vikailmoitus ja yritä sillä aikaa käyttää vanhempaa versiota. Voit myös vaihtoehtoisesti koettaa tyhjentää välimuistin käsin. - + Confirm join Vahvista liittyminen @@ -232,7 +232,7 @@ Haluatko luoda yksityisen keskustelun käyttäjän %1 kanssa? - + Cache migration failed! Välimuistin siirto epäonnistui! @@ -252,12 +252,14 @@ OLM-tilin palauttaminen epäonnistui. Ole hyvä ja kirjaudu sisään uudelleen. + + Failed to restore save data. Please login again. Tallennettujen tietojen palauttaminen epäonnistui. Ole hyvä ja kirjaudu sisään uudelleen. - + Failed to setup encryption keys. Server response: %1 %2. Please try again later. Salausavainten lähetys epäonnistui. Palvelimen vastaus: %1 %2. Ole hyvä ja yritä uudelleen myöhemmin. @@ -431,7 +433,7 @@ Hae - + People Ihmiset @@ -616,7 +618,7 @@ - Stickers (*.png *.webp) + Stickers (*.png *.webp *.gif) @@ -657,7 +659,17 @@ - + + Remove from pack + + + + + Remove + + + + Cancel Peruuta @@ -733,7 +745,7 @@ Kaikki Tiedostot (*) - + Failed to upload media. Please try again. Mediaa ei onnistuttu lataamaan. Yritä uudelleen. @@ -889,7 +901,7 @@ Esimerkki: https://server.my:8787 MessageDelegate - + removed poistettu @@ -988,7 +1000,7 @@ Esimerkki: https://server.my:8787 Kirjoita viesti… - + Stickers @@ -1247,7 +1259,7 @@ Esimerkki: https://server.my:8787 QCoreApplication - + Create a unique profile, which allows you to log into several accounts at the same time and start multiple instances of nheko. Luo uniikki profili, joka mahdollistaa kirjautumisen usealle tilille samanaikaisesti ja useamman nheko-instanssin aloittamisen. @@ -1406,7 +1418,7 @@ Esimerkki: https://server.my:8787 RoomInfo - + no version stored ei tallennettua versiota @@ -1773,6 +1785,19 @@ Esimerkki: https://server.my:8787 Peruuta + + SecretStorage + + + Failed to connect to secret storage + + + + + Nheko could not connect to the secure storage to save encryption secrets to. This can have multiple reasons. Check if your D-Bus service is running and you have configured a service like KWallet, Gnome Secrets or the equivalent for your platform. If you are having trouble, feel free to open an issue here: https://github.com/Nheko-Reborn/nheko/issues + + + SingleImagePackModel @@ -1792,7 +1817,7 @@ Esimerkki: https://server.my:8787 - + Failed to upload image: %1 @@ -2056,7 +2081,7 @@ Esimerkki: https://server.my:8787 Ei avointa huonetta - + %1 member(s) %1 jäsentä @@ -2084,7 +2109,7 @@ Esimerkki: https://server.my:8787 TimelineViewManager - + No encrypted private chat found with this user. Create an encrypted private chat with this user and try again. Salattua keskustelua ei löydetty tälle käyttäjälle. Luo salattu yksityiskeskustelu tämän käyttäjän kanssa ja yritä uudestaan. @@ -2222,8 +2247,8 @@ Esimerkki: https://server.my:8787 UserSettings - - + + Default Oletus @@ -2231,7 +2256,7 @@ Esimerkki: https://server.my:8787 UserSettingsPage - + Minimize to tray Pienennä ilmoitusalueelle @@ -2251,12 +2276,12 @@ Esimerkki: https://server.my:8787 Pyöreät avatarit - + profile: %1 profiili: %1 - + Default Oletus @@ -2412,6 +2437,11 @@ Kun poissa päältä, kaikki viestit lähetetään tavallisena tekstinä. + Play animated images only on hover + + + + Desktop notifications Työpöytäilmoitukset @@ -2483,7 +2513,17 @@ Tämä yleensä saa sovelluksen kuvakkeen liikkumaan jollain tapaa tehtäväpalk - + + Enable online key backup + + + + + The Nheko authors recommend not enabling online key backup until symmetric online key backup is available. Enable anyway? + + + + CACHED VÄLIMUISTISSA @@ -2493,7 +2533,7 @@ Tämä yleensä saa sovelluksen kuvakkeen liikkumaan jollain tapaa tehtäväpalk EI VÄLIMUISTISSA - + Scale factor Mittakerroin @@ -2568,7 +2608,7 @@ Tämä yleensä saa sovelluksen kuvakkeen liikkumaan jollain tapaa tehtäväpalk Laitteen sormenjälki - + Session Keys Istunnon avaimet @@ -2588,17 +2628,22 @@ Tämä yleensä saa sovelluksen kuvakkeen liikkumaan jollain tapaa tehtäväpalk SALAUS - + GENERAL YLEISET ASETUKSET - + INTERFACE KÄYTTÖLIITTYMÄ - + + Plays media like GIFs or WEBPs only when explicitly hovering over them. + + + + Touchscreen mode Kosketusnäyttötila @@ -2663,7 +2708,7 @@ Tämä yleensä saa sovelluksen kuvakkeen liikkumaan jollain tapaa tehtäväpalk Kaikki Tiedostot (*) - + Open Sessions File Avaa Istuntoavaintiedosto @@ -2759,7 +2804,7 @@ Tämä yleensä saa sovelluksen kuvakkeen liikkumaan jollain tapaa tehtäväpalk descriptiveTime - + Yesterday Eilen diff --git a/resources/langs/nheko_fr.ts b/resources/langs/nheko_fr.ts index 04027b85..8eec32bc 100644 --- a/resources/langs/nheko_fr.ts +++ b/resources/langs/nheko_fr.ts @@ -125,23 +125,23 @@ ChatPage - + Failed to invite user: %1 Échec lors de l'invitation de %1 - + Invited user: %1 %1 a été invité(e) - + Migrating the cache to the current version failed. This can have different reasons. Please open an issue and try to use an older version in the mean time. Alternatively you can try deleting the cache manually. La migration du cache vers la version actuelle a échoué. Cela peut arriver pour différentes raisons. Signalez le problème et essayez d'utiliser une ancienne version en attendant. Vous pouvez également supprimer le cache manuellement. - + Confirm join Confirmez la participation @@ -232,7 +232,7 @@ Voulez-vous vraimer commencer une discussion privée avec %1 ? - + Cache migration failed! Échec de la migration du cache ! @@ -252,12 +252,14 @@ Échec de la restauration du compte OLM. Veuillez vous reconnecter. + + Failed to restore save data. Please login again. Échec de la restauration des données sauvegardées. Veuillez vous reconnecter. - + Failed to setup encryption keys. Server response: %1 %2. Please try again later. Échec de la configuration des clés de chiffrement. Réponse du serveur : %1 %2. Veuillez réessayer plus tard. @@ -431,7 +433,7 @@ Chercher - + People Personnes @@ -616,7 +618,7 @@ - Stickers (*.png *.webp) + Stickers (*.png *.webp *.gif) @@ -657,7 +659,17 @@ - + + Remove from pack + + + + + Remove + + + + Cancel Annuler @@ -733,7 +745,7 @@ Tous les types de fichiers (*) - + Failed to upload media. Please try again. Échec de l'envoi du média. Veuillez réessayer. @@ -889,7 +901,7 @@ Exemple : https ://monserveur.example.com :8787 MessageDelegate - + removed retiré @@ -988,7 +1000,7 @@ Exemple : https ://monserveur.example.com :8787 Écrivez un message… - + Stickers @@ -1247,7 +1259,7 @@ Exemple : https ://monserveur.example.com :8787 QCoreApplication - + Create a unique profile, which allows you to log into several accounts at the same time and start multiple instances of nheko. Créer un profil unique, vous permettant de vous connecter simultanément à plusieurs comptes et à lancer plusieurs instances de nheko. @@ -1406,7 +1418,7 @@ Exemple : https ://monserveur.example.com :8787 RoomInfo - + no version stored pas de version enregistrée @@ -1773,6 +1785,19 @@ Exemple : https ://monserveur.example.com :8787 Annuler + + SecretStorage + + + Failed to connect to secret storage + + + + + Nheko could not connect to the secure storage to save encryption secrets to. This can have multiple reasons. Check if your D-Bus service is running and you have configured a service like KWallet, Gnome Secrets or the equivalent for your platform. If you are having trouble, feel free to open an issue here: https://github.com/Nheko-Reborn/nheko/issues + + + SingleImagePackModel @@ -1792,7 +1817,7 @@ Exemple : https ://monserveur.example.com :8787 - + Failed to upload image: %1 @@ -2056,7 +2081,7 @@ Exemple : https ://monserveur.example.com :8787 Aucun salon ouvert - + %1 member(s) %1 membre(s) @@ -2084,7 +2109,7 @@ Exemple : https ://monserveur.example.com :8787 TimelineViewManager - + No encrypted private chat found with this user. Create an encrypted private chat with this user and try again. Pas de discussion privée et chiffrée trouvée avec cet utilisateur. Créez-en une et réessayez. @@ -2222,8 +2247,8 @@ Exemple : https ://monserveur.example.com :8787 UserSettings - - + + Default Défaut @@ -2231,7 +2256,7 @@ Exemple : https ://monserveur.example.com :8787 UserSettingsPage - + Minimize to tray Réduire à la barre des tâches @@ -2251,12 +2276,12 @@ Exemple : https ://monserveur.example.com :8787 Avatars circulaires - + profile: %1 profil : %1 - + Default Défaut @@ -2414,6 +2439,11 @@ Lorsque désactivé, tous les messages sont envoyés en texte brut. + Play animated images only on hover + + + + Desktop notifications Notifier sur le bureau @@ -2485,7 +2515,17 @@ Cela met l'application en évidence dans la barre des tâches. - + + Enable online key backup + + + + + The Nheko authors recommend not enabling online key backup until symmetric online key backup is available. Enable anyway? + + + + CACHED EN CACHE @@ -2495,7 +2535,7 @@ Cela met l'application en évidence dans la barre des tâches.PAS DANS LE CACHE - + Scale factor Facteur d'échelle @@ -2570,7 +2610,7 @@ Cela met l'application en évidence dans la barre des tâches.Empreinte de l'appareil - + Session Keys Clés de session @@ -2590,17 +2630,22 @@ Cela met l'application en évidence dans la barre des tâches.CHIFFREMENT - + GENERAL GÉNÉRAL - + INTERFACE INTERFACE - + + Plays media like GIFs or WEBPs only when explicitly hovering over them. + + + + Touchscreen mode Mode écran tactile @@ -2665,7 +2710,7 @@ Cela met l'application en évidence dans la barre des tâches.Tous les types de fichiers (*) - + Open Sessions File Ouvrir fichier de sessions @@ -2761,7 +2806,7 @@ Cela met l'application en évidence dans la barre des tâches. descriptiveTime - + Yesterday Hier diff --git a/resources/langs/nheko_hu.ts b/resources/langs/nheko_hu.ts index 961fcffe..3626eff2 100644 --- a/resources/langs/nheko_hu.ts +++ b/resources/langs/nheko_hu.ts @@ -125,23 +125,23 @@ ChatPage - + Failed to invite user: %1 Nem sikerült meghívni a felhasználót: %1 - + Invited user: %1 A felhasználó meg lett hívva: %1 - + Migrating the cache to the current version failed. This can have different reasons. Please open an issue and try to use an older version in the mean time. Alternatively you can try deleting the cache manually. A gyorsítótár átvitele a jelenlegi verzióhoz nem sikerült. Ennek több oka is lehet. Kérlek, írj egy hibajelentést és egyelőre próbálj meg egy régebbi verziót használni! Alternatív megoldásként megprobálhatod eltávolítani a gyorsítótárat kézzel. - + Confirm join Csatlakozás megerősítése @@ -232,7 +232,7 @@ Biztosan privát csevegést akarsz indítani %1 felhasználóval? - + Cache migration failed! Gyorsítótár migráció nem sikerült! @@ -252,12 +252,14 @@ Nem sikerült visszaállítani az OLM fiókot. Kérlek, jelentkezz be ismét! + + Failed to restore save data. Please login again. Nem sikerült visszaállítani a mentési adatot. Kérlek, jelentkezz be ismét! - + Failed to setup encryption keys. Server response: %1 %2. Please try again later. Nem sikerült beállítani a titkosítási kulcsokat. Válasz a szervertől: %1 %2. Kérlek, próbáld újra később! @@ -431,7 +433,7 @@ Keresés - + People Emberek @@ -616,7 +618,7 @@ - Stickers (*.png *.webp) + Stickers (*.png *.webp *.gif) @@ -657,7 +659,17 @@ - + + Remove from pack + + + + + Remove + + + + Cancel Mégse @@ -733,7 +745,7 @@ Minden fájl (*) - + Failed to upload media. Please try again. Nem sikerült feltölteni a médiafájlt. Kérlek, próbáld újra! @@ -889,7 +901,7 @@ Példa: https://szerver.em:8787 MessageDelegate - + Encryption enabled Titkosítás bekapcsolva @@ -988,7 +1000,7 @@ Példa: https://szerver.em:8787 Írj egy üzenetet… - + Stickers @@ -1247,7 +1259,7 @@ Példa: https://szerver.em:8787 QCoreApplication - + Create a unique profile, which allows you to log into several accounts at the same time and start multiple instances of nheko. Egy egyedi profil létrehozása, amellyel be tudsz jelentkezni egyszerre több fiókon keresztül és a Nheko több példányát is tudod futtatni. @@ -1406,7 +1418,7 @@ Példa: https://szerver.em:8787 RoomInfo - + no version stored nincs tárolva verzió @@ -1772,6 +1784,19 @@ Példa: https://szerver.em:8787 Mégse + + SecretStorage + + + Failed to connect to secret storage + + + + + Nheko could not connect to the secure storage to save encryption secrets to. This can have multiple reasons. Check if your D-Bus service is running and you have configured a service like KWallet, Gnome Secrets or the equivalent for your platform. If you are having trouble, feel free to open an issue here: https://github.com/Nheko-Reborn/nheko/issues + + + SingleImagePackModel @@ -1791,7 +1816,7 @@ Példa: https://szerver.em:8787 - + Failed to upload image: %1 @@ -2054,7 +2079,7 @@ Példa: https://szerver.em:8787 Nincs nyitott szoba - + %1 member(s) %1 tag @@ -2082,7 +2107,7 @@ Példa: https://szerver.em:8787 TimelineViewManager - + No encrypted private chat found with this user. Create an encrypted private chat with this user and try again. Nem található titkosított privát csevegés ezzel a felhasználóval. Hozz létre egy titkosított privát csevegést vele, és próbáld újra! @@ -2220,8 +2245,8 @@ Példa: https://szerver.em:8787 UserSettings - - + + Default Alapértelmezett @@ -2229,7 +2254,7 @@ Példa: https://szerver.em:8787 UserSettingsPage - + Minimize to tray Kicsinyítés a tálcára @@ -2249,12 +2274,12 @@ Példa: https://szerver.em:8787 Kerekített profilképek - + profile: %1 profil: %1 - + Default Alapértelmezett @@ -2411,6 +2436,11 @@ Ha ki van kapcsolva, az összes üzenet sima szövegként lesz elküldve. + Play animated images only on hover + + + + Desktop notifications Asztali értesítések @@ -2482,7 +2512,17 @@ Ettől általában animálttá válik az alkalmazásablakok listáján szereplő - + + Enable online key backup + + + + + The Nheko authors recommend not enabling online key backup until symmetric online key backup is available. Enable anyway? + + + + CACHED GYORSÍTÓTÁRAZVA @@ -2492,7 +2532,7 @@ Ettől általában animálttá válik az alkalmazásablakok listáján szereplő NINCS GYORSÍTÓTÁRAZVA - + Scale factor Nagyítási tényező @@ -2567,7 +2607,7 @@ Ettől általában animálttá válik az alkalmazásablakok listáján szereplő Eszközujjlenyomat - + Session Keys Munkamenetkulcsok @@ -2587,17 +2627,22 @@ Ettől általában animálttá válik az alkalmazásablakok listáján szereplő TITKOSÍTÁS - + GENERAL ÁLTALÁNOS - + INTERFACE FELÜLET - + + Plays media like GIFs or WEBPs only when explicitly hovering over them. + + + + Touchscreen mode Érintő képernyős mód @@ -2662,7 +2707,7 @@ Ettől általában animálttá válik az alkalmazásablakok listáján szereplő Minden fájl (*) - + Open Sessions File Munkameneti fájl megnyitása @@ -2758,7 +2803,7 @@ Ettől általában animálttá válik az alkalmazásablakok listáján szereplő descriptiveTime - + Yesterday Tegnap diff --git a/resources/langs/nheko_it.ts b/resources/langs/nheko_it.ts index 0b44bd80..1ecd3358 100644 --- a/resources/langs/nheko_it.ts +++ b/resources/langs/nheko_it.ts @@ -125,23 +125,23 @@ ChatPage - + Failed to invite user: %1 Impossibile invitare l'utente: %1 - + Invited user: %1 Invitato utente: %1 - + Migrating the cache to the current version failed. This can have different reasons. Please open an issue and try to use an older version in the mean time. Alternatively you can try deleting the cache manually. Migrazione della cache alla versione corrente fallita. Questo può avere diverse cause. Per favore apri una issue e nel frattempo prova ad usare una versione più vecchia. In alternativa puoi provare a cancellare la cache manualmente. - + Confirm join Conferma collegamento @@ -232,7 +232,7 @@ Sei sicuro di voler avviare una chat privata con %1? - + Cache migration failed! Migrazione della cache fallita! @@ -252,12 +252,14 @@ Impossibile ripristinare l'account OLM. Per favore accedi nuovamente. + + Failed to restore save data. Please login again. Impossibile ripristinare i dati salvati. Per favore accedi nuovamente. - + Failed to setup encryption keys. Server response: %1 %2. Please try again later. Impossibile configurare le chiavi crittografiche. Risposta del server: %1 %2. Per favore riprova in seguito. @@ -431,7 +433,7 @@ Cerca - + People Membri @@ -616,7 +618,7 @@ - Stickers (*.png *.webp) + Stickers (*.png *.webp *.gif) @@ -657,7 +659,17 @@ - + + Remove from pack + + + + + Remove + + + + Cancel Annulla @@ -733,7 +745,7 @@ Tutti i File (*) - + Failed to upload media. Please try again. Impossibile inviare il file multimediale. Per favore riprova. @@ -889,7 +901,7 @@ Esempio: https://server.mio:8787 MessageDelegate - + removed rimosso @@ -988,7 +1000,7 @@ Esempio: https://server.mio:8787 Scrivi un messaggio… - + Stickers @@ -1248,7 +1260,7 @@ Verificare %1 adesso? QCoreApplication - + Create a unique profile, which allows you to log into several accounts at the same time and start multiple instances of nheko. @@ -1407,7 +1419,7 @@ Verificare %1 adesso? RoomInfo - + no version stored nessuna versione memorizzata @@ -1774,6 +1786,19 @@ Verificare %1 adesso? Annulla + + SecretStorage + + + Failed to connect to secret storage + + + + + Nheko could not connect to the secure storage to save encryption secrets to. This can have multiple reasons. Check if your D-Bus service is running and you have configured a service like KWallet, Gnome Secrets or the equivalent for your platform. If you are having trouble, feel free to open an issue here: https://github.com/Nheko-Reborn/nheko/issues + + + SingleImagePackModel @@ -1793,7 +1818,7 @@ Verificare %1 adesso? - + Failed to upload image: %1 @@ -2057,7 +2082,7 @@ Verificare %1 adesso? Nessuna stanza aperta - + %1 member(s) @@ -2085,7 +2110,7 @@ Verificare %1 adesso? TimelineViewManager - + No encrypted private chat found with this user. Create an encrypted private chat with this user and try again. @@ -2223,8 +2248,8 @@ Verificare %1 adesso? UserSettings - - + + Default @@ -2232,7 +2257,7 @@ Verificare %1 adesso? UserSettingsPage - + Minimize to tray Minimizza nella tray @@ -2252,12 +2277,12 @@ Verificare %1 adesso? Avatar Circolari - + profile: %1 - + Default @@ -2403,6 +2428,11 @@ When disabled, all messages are sent as a plain text. + Play animated images only on hover + + + + Desktop notifications Notifiche desktop @@ -2473,7 +2503,17 @@ This usually causes the application icon in the task bar to animate in some fash - + + Enable online key backup + + + + + The Nheko authors recommend not enabling online key backup until symmetric online key backup is available. Enable anyway? + + + + CACHED @@ -2483,7 +2523,7 @@ This usually causes the application icon in the task bar to animate in some fash - + Scale factor Fattore di scala @@ -2558,7 +2598,7 @@ This usually causes the application icon in the task bar to animate in some fash Impronta digitale del dispositivo - + Session Keys Chiavi di Sessione @@ -2578,17 +2618,22 @@ This usually causes the application icon in the task bar to animate in some fash CRITTOGRAFIA - + GENERAL GENERALE - + INTERFACE INTERFACCIA - + + Plays media like GIFs or WEBPs only when explicitly hovering over them. + + + + Touchscreen mode @@ -2653,7 +2698,7 @@ This usually causes the application icon in the task bar to animate in some fash Tutti i File (*) - + Open Sessions File Apri File delle Sessioni @@ -2749,7 +2794,7 @@ This usually causes the application icon in the task bar to animate in some fash descriptiveTime - + Yesterday Ieri diff --git a/resources/langs/nheko_ja.ts b/resources/langs/nheko_ja.ts index 9ca8f970..51451ede 100644 --- a/resources/langs/nheko_ja.ts +++ b/resources/langs/nheko_ja.ts @@ -125,23 +125,23 @@ ChatPage - + Failed to invite user: %1 ユーザーを招待できませんでした: %1 - + Invited user: %1 招待されたユーザー: %1 - + Migrating the cache to the current version failed. This can have different reasons. Please open an issue and try to use an older version in the mean time. Alternatively you can try deleting the cache manually. - + Confirm join @@ -232,7 +232,7 @@ - + Cache migration failed! @@ -252,12 +252,14 @@ OLMアカウントを復元できませんでした。もう一度ログインして下さい。 + + Failed to restore save data. Please login again. セーブデータを復元できませんでした。もう一度ログインして下さい。 - + Failed to setup encryption keys. Server response: %1 %2. Please try again later. 暗号化鍵を設定できませんでした。サーバーの応答: %1 %2. 後でやり直して下さい。 @@ -431,7 +433,7 @@ - + People @@ -616,7 +618,7 @@ - Stickers (*.png *.webp) + Stickers (*.png *.webp *.gif) @@ -657,7 +659,17 @@ - + + Remove from pack + + + + + Remove + + + + Cancel キャンセル @@ -733,7 +745,7 @@ 全てのファイル (*) - + Failed to upload media. Please try again. メディアをアップロードできませんでした。やり直して下さい。 @@ -885,7 +897,7 @@ Example: https://server.my:8787 MessageDelegate - + removed @@ -984,7 +996,7 @@ Example: https://server.my:8787 メッセージを書く... - + Stickers @@ -1243,7 +1255,7 @@ Example: https://server.my:8787 QCoreApplication - + Create a unique profile, which allows you to log into several accounts at the same time and start multiple instances of nheko. @@ -1402,7 +1414,7 @@ Example: https://server.my:8787 RoomInfo - + no version stored バージョンが保存されていません @@ -1768,6 +1780,19 @@ Example: https://server.my:8787 キャンセル + + SecretStorage + + + Failed to connect to secret storage + + + + + Nheko could not connect to the secure storage to save encryption secrets to. This can have multiple reasons. Check if your D-Bus service is running and you have configured a service like KWallet, Gnome Secrets or the equivalent for your platform. If you are having trouble, feel free to open an issue here: https://github.com/Nheko-Reborn/nheko/issues + + + SingleImagePackModel @@ -1787,7 +1812,7 @@ Example: https://server.my:8787 - + Failed to upload image: %1 @@ -2050,7 +2075,7 @@ Example: https://server.my:8787 部屋が開いていません - + %1 member(s) @@ -2078,7 +2103,7 @@ Example: https://server.my:8787 TimelineViewManager - + No encrypted private chat found with this user. Create an encrypted private chat with this user and try again. @@ -2216,8 +2241,8 @@ Example: https://server.my:8787 UserSettings - - + + Default @@ -2225,7 +2250,7 @@ Example: https://server.my:8787 UserSettingsPage - + Minimize to tray トレイへ最小化 @@ -2245,12 +2270,12 @@ Example: https://server.my:8787 円形アバター - + profile: %1 - + Default @@ -2396,6 +2421,11 @@ When disabled, all messages are sent as a plain text. + Play animated images only on hover + + + + Desktop notifications デスクトップ通知 @@ -2466,7 +2496,17 @@ This usually causes the application icon in the task bar to animate in some fash - + + Enable online key backup + + + + + The Nheko authors recommend not enabling online key backup until symmetric online key backup is available. Enable anyway? + + + + CACHED @@ -2476,7 +2516,7 @@ This usually causes the application icon in the task bar to animate in some fash - + Scale factor 尺度係数 @@ -2551,7 +2591,7 @@ This usually causes the application icon in the task bar to animate in some fash デバイスの指紋 - + Session Keys セッション鍵 @@ -2571,17 +2611,22 @@ This usually causes the application icon in the task bar to animate in some fash 暗号化 - + GENERAL 全般 - + INTERFACE - + + Plays media like GIFs or WEBPs only when explicitly hovering over them. + + + + Touchscreen mode @@ -2646,7 +2691,7 @@ This usually causes the application icon in the task bar to animate in some fash 全てのファイル (*) - + Open Sessions File セッションファイルを開く @@ -2742,7 +2787,7 @@ This usually causes the application icon in the task bar to animate in some fash descriptiveTime - + Yesterday 昨日 diff --git a/resources/langs/nheko_ml.ts b/resources/langs/nheko_ml.ts index 115da2bd..2d8ddab6 100644 --- a/resources/langs/nheko_ml.ts +++ b/resources/langs/nheko_ml.ts @@ -125,23 +125,23 @@ ChatPage - + Failed to invite user: %1 ഉപയോക്താവിനെ ക്ഷണിക്കുന്നതിൽ പരാജയപ്പെട്ടു: %1 - + Invited user: %1 ക്ഷണിച്ച ഉപയോക്താവ്:% 1 - + Migrating the cache to the current version failed. This can have different reasons. Please open an issue and try to use an older version in the mean time. Alternatively you can try deleting the cache manually. - + Confirm join @@ -232,7 +232,7 @@ - + Cache migration failed! @@ -252,12 +252,14 @@ + + Failed to restore save data. Please login again. - + Failed to setup encryption keys. Server response: %1 %2. Please try again later. @@ -431,7 +433,7 @@ തിരയുക - + People ആളുകൾ @@ -616,7 +618,7 @@ - Stickers (*.png *.webp) + Stickers (*.png *.webp *.gif) @@ -657,7 +659,17 @@ - + + Remove from pack + + + + + Remove + + + + Cancel റദ്ദാക്കു @@ -733,7 +745,7 @@ - + Failed to upload media. Please try again. @@ -885,7 +897,7 @@ Example: https://server.my:8787 MessageDelegate - + Encryption enabled @@ -984,7 +996,7 @@ Example: https://server.my:8787 ഒരു സന്ദേശം എഴുതുക…. - + Stickers @@ -1243,7 +1255,7 @@ Example: https://server.my:8787 QCoreApplication - + Create a unique profile, which allows you to log into several accounts at the same time and start multiple instances of nheko. @@ -1402,7 +1414,7 @@ Example: https://server.my:8787 RoomInfo - + no version stored @@ -1769,6 +1781,19 @@ Example: https://server.my:8787 റദ്ദാക്കു + + SecretStorage + + + Failed to connect to secret storage + + + + + Nheko could not connect to the secure storage to save encryption secrets to. This can have multiple reasons. Check if your D-Bus service is running and you have configured a service like KWallet, Gnome Secrets or the equivalent for your platform. If you are having trouble, feel free to open an issue here: https://github.com/Nheko-Reborn/nheko/issues + + + SingleImagePackModel @@ -1788,7 +1813,7 @@ Example: https://server.my:8787 - + Failed to upload image: %1 @@ -2052,7 +2077,7 @@ Example: https://server.my:8787 - + %1 member(s) @@ -2080,7 +2105,7 @@ Example: https://server.my:8787 TimelineViewManager - + No encrypted private chat found with this user. Create an encrypted private chat with this user and try again. @@ -2218,8 +2243,8 @@ Example: https://server.my:8787 UserSettings - - + + Default @@ -2227,7 +2252,7 @@ Example: https://server.my:8787 UserSettingsPage - + Minimize to tray @@ -2247,12 +2272,12 @@ Example: https://server.my:8787 - + profile: %1 - + Default @@ -2398,6 +2423,11 @@ When disabled, all messages are sent as a plain text. + Play animated images only on hover + + + + Desktop notifications @@ -2468,7 +2498,17 @@ This usually causes the application icon in the task bar to animate in some fash - + + Enable online key backup + + + + + The Nheko authors recommend not enabling online key backup until symmetric online key backup is available. Enable anyway? + + + + CACHED @@ -2478,7 +2518,7 @@ This usually causes the application icon in the task bar to animate in some fash - + Scale factor @@ -2553,7 +2593,7 @@ This usually causes the application icon in the task bar to animate in some fash - + Session Keys @@ -2573,17 +2613,22 @@ This usually causes the application icon in the task bar to animate in some fash - + GENERAL - + INTERFACE - + + Plays media like GIFs or WEBPs only when explicitly hovering over them. + + + + Touchscreen mode @@ -2648,7 +2693,7 @@ This usually causes the application icon in the task bar to animate in some fash - + Open Sessions File @@ -2744,7 +2789,7 @@ This usually causes the application icon in the task bar to animate in some fash descriptiveTime - + Yesterday diff --git a/resources/langs/nheko_nl.ts b/resources/langs/nheko_nl.ts index d9330110..55489cc1 100644 --- a/resources/langs/nheko_nl.ts +++ b/resources/langs/nheko_nl.ts @@ -125,23 +125,23 @@ ChatPage - + Failed to invite user: %1 Gebruiker uitnodigen mislukt: %1 - + Invited user: %1 Gebruiker uitgenodigd: %1 - + Migrating the cache to the current version failed. This can have different reasons. Please open an issue and try to use an older version in the mean time. Alternatively you can try deleting the cache manually. Het migreren can de cache naar de huidige versie is mislukt. Dit kan verscheidene redenen hebben. Maak a.u.b een issue aan en probeer in de tussentijd een oudere versie. Je kan ook proberen de cache handmatig te verwijderen. - + Confirm join Bevestig deelname @@ -232,7 +232,7 @@ Weet je zeker dat je een privé chat wil beginnen met %1? - + Cache migration failed! Migreren van de cache is mislukt! @@ -252,12 +252,14 @@ Herstellen van OLM account mislukt. Log a.u.b. opnieuw in. + + Failed to restore save data. Please login again. Opgeslagen gegevens herstellen mislukt. Log a.u.b. opnieuw in. - + Failed to setup encryption keys. Server response: %1 %2. Please try again later. Instellen van de versleuteling is mislukt. Bericht van server: %1 %2. Probeer het a.u.b. later nog eens. @@ -431,7 +433,7 @@ Zoeken - + People Mensen @@ -616,8 +618,8 @@ - Stickers (*.png *.webp) - Stickers (*.png *.webp) + Stickers (*.png *.webp *.gif) + @@ -657,7 +659,17 @@ Tekstinhoud - + + Remove from pack + + + + + Remove + + + + Cancel Annuleren @@ -733,7 +745,7 @@ Alle bestanden (*) - + Failed to upload media. Please try again. Het is niet is gelukt om de media te versturen. Probeer het a.u.b. opnieuw. @@ -889,7 +901,7 @@ Voorbeeld: https://mijnserver.nl:8787 MessageDelegate - + removed verwijderd @@ -988,7 +1000,7 @@ Voorbeeld: https://mijnserver.nl:8787 Typ een bericht… - + Stickers Stickers @@ -1247,7 +1259,7 @@ Voorbeeld: https://mijnserver.nl:8787 QCoreApplication - + Create a unique profile, which allows you to log into several accounts at the same time and start multiple instances of nheko. Creëer een uniek profiel, waardoor je op meerdere accounts tegelijk kan inloggen, en meerdere kopieën van Nheko tegelijk kan starten. @@ -1406,7 +1418,7 @@ Voorbeeld: https://mijnserver.nl:8787 RoomInfo - + no version stored geen versie opgeslagen @@ -1773,6 +1785,19 @@ Voorbeeld: https://mijnserver.nl:8787 Annuleren + + SecretStorage + + + Failed to connect to secret storage + + + + + Nheko could not connect to the secure storage to save encryption secrets to. This can have multiple reasons. Check if your D-Bus service is running and you have configured a service like KWallet, Gnome Secrets or the equivalent for your platform. If you are having trouble, feel free to open an issue here: https://github.com/Nheko-Reborn/nheko/issues + + + SingleImagePackModel @@ -1792,7 +1817,7 @@ Voorbeeld: https://mijnserver.nl:8787 Kon afbeelding niet openen: %1 - + Failed to upload image: %1 Kon afbeelding niet uploaden: %1 @@ -2056,7 +2081,7 @@ Voorbeeld: https://mijnserver.nl:8787 Geen kamer open - + %1 member(s) %1 deelnemer(s) @@ -2084,7 +2109,7 @@ Voorbeeld: https://mijnserver.nl:8787 TimelineViewManager - + No encrypted private chat found with this user. Create an encrypted private chat with this user and try again. Geen versleutelde chat gevonden met deze gebruiker. Maak een versleutelde chat aan met deze gebruiker en probeer het opnieuw. @@ -2222,8 +2247,8 @@ Voorbeeld: https://mijnserver.nl:8787 UserSettings - - + + Default Standaard @@ -2231,7 +2256,7 @@ Voorbeeld: https://mijnserver.nl:8787 UserSettingsPage - + Minimize to tray Minimaliseren naar systeemvak @@ -2251,12 +2276,12 @@ Voorbeeld: https://mijnserver.nl:8787 Ronde avatars - + profile: %1 profiel: %1 - + Default Standaard @@ -2412,6 +2437,11 @@ Indien uitgeschakeld worden alle berichten als platte tekst verstuurd. + Play animated images only on hover + + + + Desktop notifications Bureaubladnotificaties @@ -2483,7 +2513,17 @@ Meestal zorgt dit dat het icoon in de taakbalk op een manier animeert of iets de Download van en upload naar de online reservesleutel. - + + Enable online key backup + + + + + The Nheko authors recommend not enabling online key backup until symmetric online key backup is available. Enable anyway? + + + + CACHED IN CACHE @@ -2493,7 +2533,7 @@ Meestal zorgt dit dat het icoon in de taakbalk op een manier animeert of iets de NIET IN CACHE - + Scale factor Schaalfactor @@ -2568,7 +2608,7 @@ Meestal zorgt dit dat het icoon in de taakbalk op een manier animeert of iets de Apparaat vingerafdruk - + Session Keys Sessiesleutels @@ -2588,17 +2628,22 @@ Meestal zorgt dit dat het icoon in de taakbalk op een manier animeert of iets de VERSLEUTELING - + GENERAL ALGEMEEN - + INTERFACE INTERFACE - + + Plays media like GIFs or WEBPs only when explicitly hovering over them. + + + + Touchscreen mode Touchscreenmodus @@ -2663,7 +2708,7 @@ Meestal zorgt dit dat het icoon in de taakbalk op een manier animeert of iets de Alle bestanden (*) - + Open Sessions File Open sessiebestand @@ -2759,7 +2804,7 @@ Meestal zorgt dit dat het icoon in de taakbalk op een manier animeert of iets de descriptiveTime - + Yesterday Gisteren diff --git a/resources/langs/nheko_pl.ts b/resources/langs/nheko_pl.ts index 1c9a3998..c0058c9b 100644 --- a/resources/langs/nheko_pl.ts +++ b/resources/langs/nheko_pl.ts @@ -125,23 +125,23 @@ ChatPage - + Failed to invite user: %1 Nie udało się zaprosić użytkownika: %1 - + Invited user: %1 - + Migrating the cache to the current version failed. This can have different reasons. Please open an issue and try to use an older version in the mean time. Alternatively you can try deleting the cache manually. - + Confirm join @@ -232,7 +232,7 @@ - + Cache migration failed! Nie udało się przenieść pamięci podręcznej! @@ -252,12 +252,14 @@ Nie udało się przywrócić konta OLM. Spróbuj zalogować się ponownie. + + Failed to restore save data. Please login again. Nie udało się przywrócić zapisanych danych. Spróbuj zalogować się ponownie. - + Failed to setup encryption keys. Server response: %1 %2. Please try again later. Nie udało się ustawić kluczy szyfrujących. Odpowiedź serwera: %1 %2. Spróbuj ponownie później. @@ -431,7 +433,7 @@ Szukaj - + People Ludzie @@ -616,7 +618,7 @@ - Stickers (*.png *.webp) + Stickers (*.png *.webp *.gif) @@ -657,7 +659,17 @@ - + + Remove from pack + + + + + Remove + + + + Cancel Anuluj @@ -733,7 +745,7 @@ Wszystkie pliki (*) - + Failed to upload media. Please try again. @@ -887,7 +899,7 @@ Example: https://server.my:8787 MessageDelegate - + removed @@ -986,7 +998,7 @@ Example: https://server.my:8787 Napisz wiadomość… - + Stickers @@ -1245,7 +1257,7 @@ Example: https://server.my:8787 QCoreApplication - + Create a unique profile, which allows you to log into several accounts at the same time and start multiple instances of nheko. Stwórz unikalny profil, który pozwoli Ci na zalogowanie się do kilku kont jednocześnie i uruchomienie wielu instancji Nheko. @@ -1404,7 +1416,7 @@ Example: https://server.my:8787 RoomInfo - + no version stored @@ -1772,6 +1784,19 @@ Example: https://server.my:8787 Anuluj + + SecretStorage + + + Failed to connect to secret storage + + + + + Nheko could not connect to the secure storage to save encryption secrets to. This can have multiple reasons. Check if your D-Bus service is running and you have configured a service like KWallet, Gnome Secrets or the equivalent for your platform. If you are having trouble, feel free to open an issue here: https://github.com/Nheko-Reborn/nheko/issues + + + SingleImagePackModel @@ -1791,7 +1816,7 @@ Example: https://server.my:8787 - + Failed to upload image: %1 @@ -2056,7 +2081,7 @@ Example: https://server.my:8787 - + %1 member(s) @@ -2084,7 +2109,7 @@ Example: https://server.my:8787 TimelineViewManager - + No encrypted private chat found with this user. Create an encrypted private chat with this user and try again. @@ -2222,8 +2247,8 @@ Example: https://server.my:8787 UserSettings - - + + Default @@ -2231,7 +2256,7 @@ Example: https://server.my:8787 UserSettingsPage - + Minimize to tray Zminimalizuj do paska zadań @@ -2251,12 +2276,12 @@ Example: https://server.my:8787 - + profile: %1 - + Default @@ -2402,6 +2427,11 @@ When disabled, all messages are sent as a plain text. + Play animated images only on hover + + + + Desktop notifications Powiadomienia na pulpicie @@ -2472,7 +2502,17 @@ This usually causes the application icon in the task bar to animate in some fash - + + Enable online key backup + + + + + The Nheko authors recommend not enabling online key backup until symmetric online key backup is available. Enable anyway? + + + + CACHED @@ -2482,7 +2522,7 @@ This usually causes the application icon in the task bar to animate in some fash - + Scale factor @@ -2557,7 +2597,7 @@ This usually causes the application icon in the task bar to animate in some fash Odcisk palca urządzenia - + Session Keys @@ -2577,17 +2617,22 @@ This usually causes the application icon in the task bar to animate in some fash SZYFROWANIE - + GENERAL OGÓLNE - + INTERFACE - + + Plays media like GIFs or WEBPs only when explicitly hovering over them. + + + + Touchscreen mode @@ -2652,7 +2697,7 @@ This usually causes the application icon in the task bar to animate in some fash Wszystkie pliki (*) - + Open Sessions File @@ -2748,7 +2793,7 @@ This usually causes the application icon in the task bar to animate in some fash descriptiveTime - + Yesterday diff --git a/resources/langs/nheko_pt_BR.ts b/resources/langs/nheko_pt_BR.ts index 931c8da7..ebf55897 100644 --- a/resources/langs/nheko_pt_BR.ts +++ b/resources/langs/nheko_pt_BR.ts @@ -125,23 +125,23 @@ ChatPage - + Failed to invite user: %1 Falha ao convidar usuário: %1 - + Invited user: %1 Usuário convidado: %1 - + Migrating the cache to the current version failed. This can have different reasons. Please open an issue and try to use an older version in the mean time. Alternatively you can try deleting the cache manually. - + Confirm join Confirmar entrada @@ -232,7 +232,7 @@ - + Cache migration failed! Migração do cache falhou! @@ -252,12 +252,14 @@ Falha ao restaurar conta OLM. Por favor faça login novamente. + + Failed to restore save data. Please login again. Falha ao restaurar dados salvos. Por favor faça login novamente. - + Failed to setup encryption keys. Server response: %1 %2. Please try again later. @@ -431,7 +433,7 @@ - + People @@ -616,7 +618,7 @@ - Stickers (*.png *.webp) + Stickers (*.png *.webp *.gif) @@ -657,7 +659,17 @@ - + + Remove from pack + + + + + Remove + + + + Cancel Cancelar @@ -733,7 +745,7 @@ - + Failed to upload media. Please try again. @@ -885,7 +897,7 @@ Example: https://server.my:8787 MessageDelegate - + Encryption enabled @@ -984,7 +996,7 @@ Example: https://server.my:8787 - + Stickers @@ -1243,7 +1255,7 @@ Example: https://server.my:8787 QCoreApplication - + Create a unique profile, which allows you to log into several accounts at the same time and start multiple instances of nheko. @@ -1402,7 +1414,7 @@ Example: https://server.my:8787 RoomInfo - + no version stored @@ -1769,6 +1781,19 @@ Example: https://server.my:8787 Cancelar + + SecretStorage + + + Failed to connect to secret storage + + + + + Nheko could not connect to the secure storage to save encryption secrets to. This can have multiple reasons. Check if your D-Bus service is running and you have configured a service like KWallet, Gnome Secrets or the equivalent for your platform. If you are having trouble, feel free to open an issue here: https://github.com/Nheko-Reborn/nheko/issues + + + SingleImagePackModel @@ -1788,7 +1813,7 @@ Example: https://server.my:8787 - + Failed to upload image: %1 @@ -2052,7 +2077,7 @@ Example: https://server.my:8787 - + %1 member(s) @@ -2080,7 +2105,7 @@ Example: https://server.my:8787 TimelineViewManager - + No encrypted private chat found with this user. Create an encrypted private chat with this user and try again. @@ -2218,8 +2243,8 @@ Example: https://server.my:8787 UserSettings - - + + Default @@ -2227,7 +2252,7 @@ Example: https://server.my:8787 UserSettingsPage - + Minimize to tray @@ -2247,12 +2272,12 @@ Example: https://server.my:8787 - + profile: %1 - + Default @@ -2398,6 +2423,11 @@ When disabled, all messages are sent as a plain text. + Play animated images only on hover + + + + Desktop notifications @@ -2468,7 +2498,17 @@ This usually causes the application icon in the task bar to animate in some fash - + + Enable online key backup + + + + + The Nheko authors recommend not enabling online key backup until symmetric online key backup is available. Enable anyway? + + + + CACHED @@ -2478,7 +2518,7 @@ This usually causes the application icon in the task bar to animate in some fash - + Scale factor @@ -2553,7 +2593,7 @@ This usually causes the application icon in the task bar to animate in some fash - + Session Keys @@ -2573,17 +2613,22 @@ This usually causes the application icon in the task bar to animate in some fash - + GENERAL - + INTERFACE - + + Plays media like GIFs or WEBPs only when explicitly hovering over them. + + + + Touchscreen mode @@ -2648,7 +2693,7 @@ This usually causes the application icon in the task bar to animate in some fash - + Open Sessions File @@ -2744,7 +2789,7 @@ This usually causes the application icon in the task bar to animate in some fash descriptiveTime - + Yesterday diff --git a/resources/langs/nheko_pt_PT.ts b/resources/langs/nheko_pt_PT.ts index eb8467b3..bf63723f 100644 --- a/resources/langs/nheko_pt_PT.ts +++ b/resources/langs/nheko_pt_PT.ts @@ -125,23 +125,23 @@ ChatPage - + Failed to invite user: %1 - + Invited user: %1 - + Migrating the cache to the current version failed. This can have different reasons. Please open an issue and try to use an older version in the mean time. Alternatively you can try deleting the cache manually. - + Confirm join @@ -232,7 +232,7 @@ - + Cache migration failed! @@ -252,12 +252,14 @@ + + Failed to restore save data. Please login again. - + Failed to setup encryption keys. Server response: %1 %2. Please try again later. @@ -431,7 +433,7 @@ - + People @@ -616,7 +618,7 @@ - Stickers (*.png *.webp) + Stickers (*.png *.webp *.gif) @@ -657,7 +659,17 @@ - + + Remove from pack + + + + + Remove + + + + Cancel @@ -733,7 +745,7 @@ - + Failed to upload media. Please try again. @@ -885,7 +897,7 @@ Example: https://server.my:8787 MessageDelegate - + Encryption enabled @@ -984,7 +996,7 @@ Example: https://server.my:8787 - + Stickers @@ -1243,7 +1255,7 @@ Example: https://server.my:8787 QCoreApplication - + Create a unique profile, which allows you to log into several accounts at the same time and start multiple instances of nheko. @@ -1402,7 +1414,7 @@ Example: https://server.my:8787 RoomInfo - + no version stored @@ -1769,6 +1781,19 @@ Example: https://server.my:8787 + + SecretStorage + + + Failed to connect to secret storage + + + + + Nheko could not connect to the secure storage to save encryption secrets to. This can have multiple reasons. Check if your D-Bus service is running and you have configured a service like KWallet, Gnome Secrets or the equivalent for your platform. If you are having trouble, feel free to open an issue here: https://github.com/Nheko-Reborn/nheko/issues + + + SingleImagePackModel @@ -1788,7 +1813,7 @@ Example: https://server.my:8787 - + Failed to upload image: %1 @@ -2052,7 +2077,7 @@ Example: https://server.my:8787 - + %1 member(s) @@ -2080,7 +2105,7 @@ Example: https://server.my:8787 TimelineViewManager - + No encrypted private chat found with this user. Create an encrypted private chat with this user and try again. @@ -2218,8 +2243,8 @@ Example: https://server.my:8787 UserSettings - - + + Default @@ -2227,7 +2252,7 @@ Example: https://server.my:8787 UserSettingsPage - + Minimize to tray @@ -2247,12 +2272,12 @@ Example: https://server.my:8787 - + profile: %1 - + Default @@ -2398,6 +2423,11 @@ When disabled, all messages are sent as a plain text. + Play animated images only on hover + + + + Desktop notifications @@ -2468,7 +2498,17 @@ This usually causes the application icon in the task bar to animate in some fash - + + Enable online key backup + + + + + The Nheko authors recommend not enabling online key backup until symmetric online key backup is available. Enable anyway? + + + + CACHED @@ -2478,7 +2518,7 @@ This usually causes the application icon in the task bar to animate in some fash - + Scale factor @@ -2553,7 +2593,7 @@ This usually causes the application icon in the task bar to animate in some fash - + Session Keys @@ -2573,17 +2613,22 @@ This usually causes the application icon in the task bar to animate in some fash - + GENERAL - + INTERFACE - + + Plays media like GIFs or WEBPs only when explicitly hovering over them. + + + + Touchscreen mode @@ -2648,7 +2693,7 @@ This usually causes the application icon in the task bar to animate in some fash - + Open Sessions File @@ -2744,7 +2789,7 @@ This usually causes the application icon in the task bar to animate in some fash descriptiveTime - + Yesterday diff --git a/resources/langs/nheko_ro.ts b/resources/langs/nheko_ro.ts index 85514304..f04aff5d 100644 --- a/resources/langs/nheko_ro.ts +++ b/resources/langs/nheko_ro.ts @@ -125,23 +125,23 @@ ChatPage - + Failed to invite user: %1 Nu s-a putut invita utilizatorul: %1 - + Invited user: %1 Utilizator invitat: %1 - + Migrating the cache to the current version failed. This can have different reasons. Please open an issue and try to use an older version in the mean time. Alternatively you can try deleting the cache manually. Nu s-a putut muta cache-ul pe versiunea curentă. Acest lucru poate avea diferite cauze. Vă rugăm să deschideți un issue și încercați să folosiți o versiune mai veche între timp. O altă opțiune ar fi să încercați să ștergeți cache-ul manual. - + Confirm join @@ -232,7 +232,7 @@ - + Cache migration failed! Nu s-a putut migra cache-ul! @@ -252,12 +252,14 @@ Nu s-a putut restabili contul OLM. Vă rugăm să vă reconectați. + + Failed to restore save data. Please login again. Nu s-au putut restabili datele salvate. Vă rugăm să vă reconectați. - + Failed to setup encryption keys. Server response: %1 %2. Please try again later. Nu s-au putut stabili cheile. Răspunsul serverului: %1 %2. Vă rugăm încercați mai târziu. @@ -431,7 +433,7 @@ - + People @@ -616,7 +618,7 @@ - Stickers (*.png *.webp) + Stickers (*.png *.webp *.gif) @@ -657,7 +659,17 @@ - + + Remove from pack + + + + + Remove + + + + Cancel @@ -733,7 +745,7 @@ Toate fișierele (*) - + Failed to upload media. Please try again. @@ -889,7 +901,7 @@ Exemplu: https://serverul.meu:8787 MessageDelegate - + removed @@ -988,7 +1000,7 @@ Exemplu: https://serverul.meu:8787 - + Stickers @@ -1247,7 +1259,7 @@ Exemplu: https://serverul.meu:8787 QCoreApplication - + Create a unique profile, which allows you to log into several accounts at the same time and start multiple instances of nheko. @@ -1406,7 +1418,7 @@ Exemplu: https://serverul.meu:8787 RoomInfo - + no version stored nicio versiune stocată @@ -1774,6 +1786,19 @@ Exemplu: https://serverul.meu:8787 + + SecretStorage + + + Failed to connect to secret storage + + + + + Nheko could not connect to the secure storage to save encryption secrets to. This can have multiple reasons. Check if your D-Bus service is running and you have configured a service like KWallet, Gnome Secrets or the equivalent for your platform. If you are having trouble, feel free to open an issue here: https://github.com/Nheko-Reborn/nheko/issues + + + SingleImagePackModel @@ -1793,7 +1818,7 @@ Exemplu: https://serverul.meu:8787 - + Failed to upload image: %1 @@ -2058,7 +2083,7 @@ Exemplu: https://serverul.meu:8787 Nicio cameră deschisă - + %1 member(s) @@ -2086,7 +2111,7 @@ Exemplu: https://serverul.meu:8787 TimelineViewManager - + No encrypted private chat found with this user. Create an encrypted private chat with this user and try again. @@ -2224,8 +2249,8 @@ Exemplu: https://serverul.meu:8787 UserSettings - - + + Default @@ -2233,7 +2258,7 @@ Exemplu: https://serverul.meu:8787 UserSettingsPage - + Minimize to tray Minimizează în bara de notificări @@ -2253,12 +2278,12 @@ Exemplu: https://serverul.meu:8787 Avatare rotunde - + profile: %1 - + Default @@ -2404,6 +2429,11 @@ When disabled, all messages are sent as a plain text. + Play animated images only on hover + + + + Desktop notifications Notificări desktop @@ -2474,7 +2504,17 @@ This usually causes the application icon in the task bar to animate in some fash - + + Enable online key backup + + + + + The Nheko authors recommend not enabling online key backup until symmetric online key backup is available. Enable anyway? + + + + CACHED @@ -2484,7 +2524,7 @@ This usually causes the application icon in the task bar to animate in some fash - + Scale factor Factor de dimensiune @@ -2559,7 +2599,7 @@ This usually causes the application icon in the task bar to animate in some fash Amprentă Dispozitiv - + Session Keys Chei de sesiune @@ -2579,17 +2619,22 @@ This usually causes the application icon in the task bar to animate in some fash CRIPTARE - + GENERAL GENERAL - + INTERFACE INTERFAȚĂ - + + Plays media like GIFs or WEBPs only when explicitly hovering over them. + + + + Touchscreen mode @@ -2654,7 +2699,7 @@ This usually causes the application icon in the task bar to animate in some fash Toate fișierele (*) - + Open Sessions File Deschide fișierul de sesiuni @@ -2750,7 +2795,7 @@ This usually causes the application icon in the task bar to animate in some fash descriptiveTime - + Yesterday Ieri diff --git a/resources/langs/nheko_ru.ts b/resources/langs/nheko_ru.ts index 52c76005..11f8982b 100644 --- a/resources/langs/nheko_ru.ts +++ b/resources/langs/nheko_ru.ts @@ -125,23 +125,23 @@ ChatPage - + Failed to invite user: %1 Не удалось пригласить пользователя: %1 - + Invited user: %1 Приглашенный пользователь: %1 - + Migrating the cache to the current version failed. This can have different reasons. Please open an issue and try to use an older version in the mean time. Alternatively you can try deleting the cache manually. Миграция кэша для текущей версии не удалась. Это может происходить по разным причинам. Пожалуйста сообщите о проблеме и попробуйте временно использовать старую версию. Так-же вы можете попробовать удалить кэш самостоятельно. - + Confirm join Подтвердить вход @@ -232,7 +232,7 @@ Вы действительно хотите начать личную переписку с %1? - + Cache migration failed! Миграция кэша не удалась! @@ -252,12 +252,14 @@ Не удалось восстановить учетную запись OLM. Пожалуйста, войдите снова. + + Failed to restore save data. Please login again. Не удалось восстановить сохраненные данные. Пожалуйста, войдите снова. - + Failed to setup encryption keys. Server response: %1 %2. Please try again later. Не удалось настроить ключи шифрования. Ответ сервера:%1 %2. Пожалуйста, попробуйте позже. @@ -431,7 +433,7 @@ Поиск - + People Люди @@ -616,7 +618,7 @@ - Stickers (*.png *.webp) + Stickers (*.png *.webp *.gif) @@ -657,7 +659,17 @@ - + + Remove from pack + + + + + Remove + + + + Cancel @@ -733,7 +745,7 @@ Все файлы (*) - + Failed to upload media. Please try again. Не удалось загрузить медиа. Пожалуйста попробуйте ещё раз @@ -889,7 +901,7 @@ Example: https://server.my:8787 MessageDelegate - + removed убрано @@ -988,7 +1000,7 @@ Example: https://server.my:8787 Написать сообщение… - + Stickers @@ -1247,7 +1259,7 @@ Example: https://server.my:8787 QCoreApplication - + Create a unique profile, which allows you to log into several accounts at the same time and start multiple instances of nheko. Создать уникальный профиль, который позволяет вести несколько аккаунтов и запускать множество сущностей nheko. @@ -1406,7 +1418,7 @@ Example: https://server.my:8787 RoomInfo - + no version stored нет сохраненной версии @@ -1774,6 +1786,19 @@ Example: https://server.my:8787 Отмена + + SecretStorage + + + Failed to connect to secret storage + + + + + Nheko could not connect to the secure storage to save encryption secrets to. This can have multiple reasons. Check if your D-Bus service is running and you have configured a service like KWallet, Gnome Secrets or the equivalent for your platform. If you are having trouble, feel free to open an issue here: https://github.com/Nheko-Reborn/nheko/issues + + + SingleImagePackModel @@ -1793,7 +1818,7 @@ Example: https://server.my:8787 - + Failed to upload image: %1 @@ -2058,7 +2083,7 @@ Example: https://server.my:8787 Комната не выбрана - + %1 member(s) %1 участник(ов) @@ -2086,7 +2111,7 @@ Example: https://server.my:8787 TimelineViewManager - + No encrypted private chat found with this user. Create an encrypted private chat with this user and try again. Не найдено личного чата с этим пользователем. Создайте зашифрованный личный чат с этим пользователем и попытайтесь еще раз. @@ -2224,8 +2249,8 @@ Example: https://server.my:8787 UserSettings - - + + Default По умолчанию @@ -2233,7 +2258,7 @@ Example: https://server.my:8787 UserSettingsPage - + Minimize to tray Сворачивать в системную панель @@ -2253,12 +2278,12 @@ Example: https://server.my:8787 Округлый Аватар - + profile: %1 профиль: %1 - + Default По умолчанию @@ -2408,6 +2433,11 @@ When disabled, all messages are sent as a plain text. + Play animated images only on hover + + + + Desktop notifications Уведомления на рабочем столе @@ -2479,7 +2509,17 @@ This usually causes the application icon in the task bar to animate in some fash - + + Enable online key backup + + + + + The Nheko authors recommend not enabling online key backup until symmetric online key backup is available. Enable anyway? + + + + CACHED Закешировано @@ -2489,7 +2529,7 @@ This usually causes the application icon in the task bar to animate in some fash НЕ ЗАКЕШИРОВАНО - + Scale factor Масштаб @@ -2564,7 +2604,7 @@ This usually causes the application icon in the task bar to animate in some fash Отпечаток устройства - + Session Keys Ключи сеанса @@ -2584,17 +2624,22 @@ This usually causes the application icon in the task bar to animate in some fash ШИФРОВАНИЕ - + GENERAL ГЛАВНОЕ - + INTERFACE ИНТЕРФЕЙС - + + Plays media like GIFs or WEBPs only when explicitly hovering over them. + + + + Touchscreen mode Сенсорный режим @@ -2659,7 +2704,7 @@ This usually causes the application icon in the task bar to animate in some fash Все файлы (*) - + Open Sessions File Открыть файл сеансов @@ -2756,7 +2801,7 @@ This usually causes the application icon in the task bar to animate in some fash descriptiveTime - + Yesterday Вчера diff --git a/resources/langs/nheko_si.ts b/resources/langs/nheko_si.ts index b0a86c9d..84ae71ef 100644 --- a/resources/langs/nheko_si.ts +++ b/resources/langs/nheko_si.ts @@ -125,23 +125,23 @@ ChatPage - + Failed to invite user: %1 - + Invited user: %1 - + Migrating the cache to the current version failed. This can have different reasons. Please open an issue and try to use an older version in the mean time. Alternatively you can try deleting the cache manually. - + Confirm join @@ -232,7 +232,7 @@ - + Cache migration failed! @@ -252,12 +252,14 @@ + + Failed to restore save data. Please login again. - + Failed to setup encryption keys. Server response: %1 %2. Please try again later. @@ -431,7 +433,7 @@ - + People @@ -616,7 +618,7 @@ - Stickers (*.png *.webp) + Stickers (*.png *.webp *.gif) @@ -657,7 +659,17 @@ - + + Remove from pack + + + + + Remove + + + + Cancel @@ -733,7 +745,7 @@ - + Failed to upload media. Please try again. @@ -885,7 +897,7 @@ Example: https://server.my:8787 MessageDelegate - + removed @@ -984,7 +996,7 @@ Example: https://server.my:8787 - + Stickers @@ -1243,7 +1255,7 @@ Example: https://server.my:8787 QCoreApplication - + Create a unique profile, which allows you to log into several accounts at the same time and start multiple instances of nheko. @@ -1402,7 +1414,7 @@ Example: https://server.my:8787 RoomInfo - + no version stored @@ -1769,6 +1781,19 @@ Example: https://server.my:8787 + + SecretStorage + + + Failed to connect to secret storage + + + + + Nheko could not connect to the secure storage to save encryption secrets to. This can have multiple reasons. Check if your D-Bus service is running and you have configured a service like KWallet, Gnome Secrets or the equivalent for your platform. If you are having trouble, feel free to open an issue here: https://github.com/Nheko-Reborn/nheko/issues + + + SingleImagePackModel @@ -1788,7 +1813,7 @@ Example: https://server.my:8787 - + Failed to upload image: %1 @@ -2052,7 +2077,7 @@ Example: https://server.my:8787 - + %1 member(s) @@ -2080,7 +2105,7 @@ Example: https://server.my:8787 TimelineViewManager - + No encrypted private chat found with this user. Create an encrypted private chat with this user and try again. @@ -2218,8 +2243,8 @@ Example: https://server.my:8787 UserSettings - - + + Default @@ -2227,7 +2252,7 @@ Example: https://server.my:8787 UserSettingsPage - + Minimize to tray @@ -2247,12 +2272,12 @@ Example: https://server.my:8787 - + profile: %1 - + Default @@ -2398,6 +2423,11 @@ When disabled, all messages are sent as a plain text. + Play animated images only on hover + + + + Desktop notifications @@ -2468,7 +2498,17 @@ This usually causes the application icon in the task bar to animate in some fash - + + Enable online key backup + + + + + The Nheko authors recommend not enabling online key backup until symmetric online key backup is available. Enable anyway? + + + + CACHED @@ -2478,7 +2518,7 @@ This usually causes the application icon in the task bar to animate in some fash - + Scale factor @@ -2553,7 +2593,7 @@ This usually causes the application icon in the task bar to animate in some fash - + Session Keys @@ -2573,17 +2613,22 @@ This usually causes the application icon in the task bar to animate in some fash - + GENERAL - + INTERFACE - + + Plays media like GIFs or WEBPs only when explicitly hovering over them. + + + + Touchscreen mode @@ -2648,7 +2693,7 @@ This usually causes the application icon in the task bar to animate in some fash - + Open Sessions File @@ -2744,7 +2789,7 @@ This usually causes the application icon in the task bar to animate in some fash descriptiveTime - + Yesterday diff --git a/resources/langs/nheko_sv.ts b/resources/langs/nheko_sv.ts index 09a91b13..ff36093e 100644 --- a/resources/langs/nheko_sv.ts +++ b/resources/langs/nheko_sv.ts @@ -125,23 +125,23 @@ ChatPage - + Failed to invite user: %1 Kunde inte bjuda in användare: %1 - + Invited user: %1 Bjöd in användare: %1 - + Migrating the cache to the current version failed. This can have different reasons. Please open an issue and try to use an older version in the mean time. Alternatively you can try deleting the cache manually. Kunde inte migrera cachen till den nuvarande versionen. Detta kan bero på flera anledningar, vänligen rapportera problemet och prova en äldre version under tiden. Du kan också försöka att manuellt radera cachen. - + Confirm join @@ -232,7 +232,7 @@ - + Cache migration failed! Cache-migration misslyckades! @@ -252,12 +252,14 @@ Kunde inte återställa OLM-konto. Vänligen logga in på nytt. + + Failed to restore save data. Please login again. Kunde inte återställa sparad data. Vänligen logga in på nytt. - + Failed to setup encryption keys. Server response: %1 %2. Please try again later. Kunde inte sätta upp krypteringsnycklar. Svar från servern: %1 %2. Vänligen försök igen senare. @@ -431,7 +433,7 @@ Sök - + People Personer @@ -616,7 +618,7 @@ - Stickers (*.png *.webp) + Stickers (*.png *.webp *.gif) @@ -657,7 +659,17 @@ - + + Remove from pack + + + + + Remove + + + + Cancel Avbryt @@ -733,7 +745,7 @@ Alla Filer (*) - + Failed to upload media. Please try again. Kunde inte ladda upp media. Vänligen försök igen. @@ -889,7 +901,7 @@ Exempel: https://server.my:8787 MessageDelegate - + Encryption enabled Kryptering aktiverad @@ -988,7 +1000,7 @@ Exempel: https://server.my:8787 Skriv ett meddelande… - + Stickers @@ -1247,7 +1259,7 @@ Exempel: https://server.my:8787 QCoreApplication - + Create a unique profile, which allows you to log into several accounts at the same time and start multiple instances of nheko. Skapa en unik profil, vilket tillåter dig att logga in på flera konton samtidigt och starta flera instanser av Nheko. @@ -1406,7 +1418,7 @@ Exempel: https://server.my:8787 RoomInfo - + no version stored ingen version lagrad @@ -1773,6 +1785,19 @@ Exempel: https://server.my:8787 Avbryt + + SecretStorage + + + Failed to connect to secret storage + + + + + Nheko could not connect to the secure storage to save encryption secrets to. This can have multiple reasons. Check if your D-Bus service is running and you have configured a service like KWallet, Gnome Secrets or the equivalent for your platform. If you are having trouble, feel free to open an issue here: https://github.com/Nheko-Reborn/nheko/issues + + + SingleImagePackModel @@ -1792,7 +1817,7 @@ Exempel: https://server.my:8787 - + Failed to upload image: %1 @@ -2056,7 +2081,7 @@ Exempel: https://server.my:8787 Inget rum öppet - + %1 member(s) @@ -2084,7 +2109,7 @@ Exempel: https://server.my:8787 TimelineViewManager - + No encrypted private chat found with this user. Create an encrypted private chat with this user and try again. Ingen krypterad privat chatt med denna användare kunde hittas. Skapa en krypterad privat chatt med användaren och försök igen. @@ -2222,8 +2247,8 @@ Exempel: https://server.my:8787 UserSettings - - + + Default @@ -2231,7 +2256,7 @@ Exempel: https://server.my:8787 UserSettingsPage - + Minimize to tray Minimera till systemtråg @@ -2251,12 +2276,12 @@ Exempel: https://server.my:8787 Cirkulära avatarer - + profile: %1 profil: %1 - + Default @@ -2409,6 +2434,11 @@ Om denna inställning är av kommer alla meddelanden skickas som oformatterad te + Play animated images only on hover + + + + Desktop notifications Skrivbordsnotifikationer @@ -2480,7 +2510,17 @@ Detta gör vanligtvis att ikonen i aktivitetsfältet animeras på något sätt.< - + + Enable online key backup + + + + + The Nheko authors recommend not enabling online key backup until symmetric online key backup is available. Enable anyway? + + + + CACHED SPARAD @@ -2490,7 +2530,7 @@ Detta gör vanligtvis att ikonen i aktivitetsfältet animeras på något sätt.< EJ SPARAD - + Scale factor Storleksfaktor @@ -2565,7 +2605,7 @@ Detta gör vanligtvis att ikonen i aktivitetsfältet animeras på något sätt.< Enhetsfingeravtryck - + Session Keys Sessionsnycklar @@ -2585,17 +2625,22 @@ Detta gör vanligtvis att ikonen i aktivitetsfältet animeras på något sätt.< KRYPTERING - + GENERAL ALLMÄNT - + INTERFACE GRÄNSSNITT - + + Plays media like GIFs or WEBPs only when explicitly hovering over them. + + + + Touchscreen mode Touchskärmsläge @@ -2660,7 +2705,7 @@ Detta gör vanligtvis att ikonen i aktivitetsfältet animeras på något sätt.< Alla Filer (*) - + Open Sessions File Öppna sessionsfil @@ -2756,7 +2801,7 @@ Detta gör vanligtvis att ikonen i aktivitetsfältet animeras på något sätt.< descriptiveTime - + Yesterday Igår diff --git a/resources/langs/nheko_zh_CN.ts b/resources/langs/nheko_zh_CN.ts index b788a569..4bdcfd0c 100644 --- a/resources/langs/nheko_zh_CN.ts +++ b/resources/langs/nheko_zh_CN.ts @@ -125,23 +125,23 @@ ChatPage - + Failed to invite user: %1 邀请用户失败: %1 - + Invited user: %1 邀请已发送: %1 - + Migrating the cache to the current version failed. This can have different reasons. Please open an issue and try to use an older version in the mean time. Alternatively you can try deleting the cache manually. 无法迁移缓存到目前版本,可能有多种原因引发此类问题。您可以新建一个议题并继续使用之前版本,或者您可以尝试手动删除缓存。 - + Confirm join @@ -232,7 +232,7 @@ - + Cache migration failed! 缓存迁移失败! @@ -252,12 +252,14 @@ 恢复 OLM 账户失败。请重新登录。 + + Failed to restore save data. Please login again. 恢复保存的数据失败。请重新登录。 - + Failed to setup encryption keys. Server response: %1 %2. Please try again later. 设置密钥失败。服务器返回信息: %1 %2。请稍后再试。 @@ -431,7 +433,7 @@ - + People @@ -616,7 +618,7 @@ - Stickers (*.png *.webp) + Stickers (*.png *.webp *.gif) @@ -657,7 +659,17 @@ - + + Remove from pack + + + + + Remove + + + + Cancel 取消 @@ -733,7 +745,7 @@ 所有文件(*) - + Failed to upload media. Please try again. @@ -885,7 +897,7 @@ Example: https://server.my:8787 MessageDelegate - + removed @@ -984,7 +996,7 @@ Example: https://server.my:8787 写一条消息… - + Stickers @@ -1243,7 +1255,7 @@ Example: https://server.my:8787 QCoreApplication - + Create a unique profile, which allows you to log into several accounts at the same time and start multiple instances of nheko. @@ -1402,7 +1414,7 @@ Example: https://server.my:8787 RoomInfo - + no version stored @@ -1768,6 +1780,19 @@ Example: https://server.my:8787 取消 + + SecretStorage + + + Failed to connect to secret storage + + + + + Nheko could not connect to the secure storage to save encryption secrets to. This can have multiple reasons. Check if your D-Bus service is running and you have configured a service like KWallet, Gnome Secrets or the equivalent for your platform. If you are having trouble, feel free to open an issue here: https://github.com/Nheko-Reborn/nheko/issues + + + SingleImagePackModel @@ -1787,7 +1812,7 @@ Example: https://server.my:8787 - + Failed to upload image: %1 @@ -2050,7 +2075,7 @@ Example: https://server.my:8787 - + %1 member(s) @@ -2078,7 +2103,7 @@ Example: https://server.my:8787 TimelineViewManager - + No encrypted private chat found with this user. Create an encrypted private chat with this user and try again. @@ -2216,8 +2241,8 @@ Example: https://server.my:8787 UserSettings - - + + Default @@ -2225,7 +2250,7 @@ Example: https://server.my:8787 UserSettingsPage - + Minimize to tray 最小化至托盘 @@ -2245,12 +2270,12 @@ Example: https://server.my:8787 - + profile: %1 - + Default @@ -2396,6 +2421,11 @@ When disabled, all messages are sent as a plain text. + Play animated images only on hover + + + + Desktop notifications 桌面通知 @@ -2466,7 +2496,17 @@ This usually causes the application icon in the task bar to animate in some fash - + + Enable online key backup + + + + + The Nheko authors recommend not enabling online key backup until symmetric online key backup is available. Enable anyway? + + + + CACHED @@ -2476,7 +2516,7 @@ This usually causes the application icon in the task bar to animate in some fash - + Scale factor @@ -2551,7 +2591,7 @@ This usually causes the application icon in the task bar to animate in some fash 设备指纹 - + Session Keys 会话密钥 @@ -2571,17 +2611,22 @@ This usually causes the application icon in the task bar to animate in some fash 加密 - + GENERAL 通用 - + INTERFACE - + + Plays media like GIFs or WEBPs only when explicitly hovering over them. + + + + Touchscreen mode @@ -2646,7 +2691,7 @@ This usually causes the application icon in the task bar to animate in some fash 所有文件(*) - + Open Sessions File 打开会话文件 @@ -2742,7 +2787,7 @@ This usually causes the application icon in the task bar to animate in some fash descriptiveTime - + Yesterday diff --git a/src/UserSettingsPage.cpp b/src/UserSettingsPage.cpp index d55a0f61..af32344c 100644 --- a/src/UserSettingsPage.cpp +++ b/src/UserSettingsPage.cpp @@ -989,7 +989,7 @@ UserSettingsPage::UserSettingsPage(QSharedPointer settings, QWidge "text.")); boxWrap(tr("Play animated images only on hover"), animateImagesOnHover_, - tr("Plays media like GIFs or APNGs only when explicitly hovering over them.")); + tr("Plays media like GIFs or WEBPs only when explicitly hovering over them.")); boxWrap(tr("Desktop notifications"), desktopNotifications_, tr("Notify about received message when the client is not currently focused.")); From 5d8a4c1b59f186be23583e6e317a920c90aaebe5 Mon Sep 17 00:00:00 2001 From: Weblate Date: Tue, 31 Aug 2021 20:47:57 -0400 Subject: [PATCH 074/232] Translated using Weblate (Dutch) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Currently translated at 100.0% (560 of 560 strings) Co-authored-by: Jaron Viëtor Translate-URL: https://weblate.nheko.im/projects/nheko/nheko-master/nl/ Translation: Nheko/nheko --- resources/langs/nheko_nl.ts | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/resources/langs/nheko_nl.ts b/resources/langs/nheko_nl.ts index 55489cc1..18a69994 100644 --- a/resources/langs/nheko_nl.ts +++ b/resources/langs/nheko_nl.ts @@ -619,7 +619,7 @@ Stickers (*.png *.webp *.gif) - + Stickers (*.png *.webp *.gif) @@ -661,12 +661,12 @@ Remove from pack - + Verwijder uit afbeeldingspakket Remove - + Verwijder @@ -1790,12 +1790,12 @@ Voorbeeld: https://mijnserver.nl:8787 Failed to connect to secret storage - + Verbinden met geheimopslag mislukt Nheko could not connect to the secure storage to save encryption secrets to. This can have multiple reasons. Check if your D-Bus service is running and you have configured a service like KWallet, Gnome Secrets or the equivalent for your platform. If you are having trouble, feel free to open an issue here: https://github.com/Nheko-Reborn/nheko/issues - + Nheko kon niet verbinden met de geheimopslag om sleutels in te bewaren. Dit kan verscheidene redenen hebben. Controleer of je D-Bus service draait en of je een service zoals KWallet, Gnome Secrets of een dergelijk equivalent voor je platform hebt ingesteld. Als je problemen ondervindt kan je ook altijd hier een issue openen: https://github.com/Nheko-Reborn/nheko/issues @@ -1909,7 +1909,7 @@ Voorbeeld: https://mijnserver.nl:8787 %1 and %2 are typing. Multiple users are typing. First argument is a comma separated list of potentially multiple users. Second argument is the last user of that list. (If only one user is typing, %1 is empty. You should still use it in your string though to silence Qt warnings.) - 1%2% is aan het typen. + %1%2 is aan het typen. %1 en %2 zijn aan het typen. @@ -2438,7 +2438,7 @@ Indien uitgeschakeld worden alle berichten als platte tekst verstuurd. Play animated images only on hover - + Speel animaties in afbeeldingen alleen af tijdens muisover @@ -2515,12 +2515,12 @@ Meestal zorgt dit dat het icoon in de taakbalk op een manier animeert of iets de Enable online key backup - + Activeer online reservesleutelopslag The Nheko authors recommend not enabling online key backup until symmetric online key backup is available. Enable anyway? - + De Nheko auteurs raden af om online reservesleutelopslag te gebruiken totdat symmetrische reservesleutelopslag beschikbaar is. Toch activeren? @@ -2640,7 +2640,7 @@ Meestal zorgt dit dat het icoon in de taakbalk op een manier animeert of iets de Plays media like GIFs or WEBPs only when explicitly hovering over them. - + Speelt media zoals GIFs en WebPs alleen af terwijl de muiscursor erboven hangt. From 0ce200fee5d9956bb6b01db91fcd2141a9269da1 Mon Sep 17 00:00:00 2001 From: Weblate Date: Wed, 1 Sep 2021 04:42:32 -0400 Subject: [PATCH 075/232] Translated using Weblate (Estonian) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Currently translated at 100.0% (560 of 560 strings) Co-authored-by: Priit Jõerüüt Translate-URL: https://weblate.nheko.im/projects/nheko/nheko-master/et/ Translation: Nheko/nheko --- resources/langs/nheko_et.ts | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/resources/langs/nheko_et.ts b/resources/langs/nheko_et.ts index 2fd86764..6672c48c 100644 --- a/resources/langs/nheko_et.ts +++ b/resources/langs/nheko_et.ts @@ -619,7 +619,7 @@ Stickers (*.png *.webp *.gif) - + Kleepsud (*.png *.webp *.gif) @@ -661,12 +661,12 @@ Remove from pack - + Eemalda pakist Remove - + Eemalda @@ -1790,12 +1790,12 @@ Näiteks: https://server.minu:8787 Failed to connect to secret storage - + Ühenduse loomine võtmehoidlaga ei õnnestunud Nheko could not connect to the secure storage to save encryption secrets to. This can have multiple reasons. Check if your D-Bus service is running and you have configured a service like KWallet, Gnome Secrets or the equivalent for your platform. If you are having trouble, feel free to open an issue here: https://github.com/Nheko-Reborn/nheko/issues - + Krüptovõtmete salvestamiseks Nhekol ei õnnestunud luua ühendust võtmehoidlaga. Sellel võib olla mitu põhjust. Kontrolli, kas D-Bus'i alusteenus toimib ning sa oled seadistanud KWallet'i, Gnome Secrets'i või mõne muu sinu platvormil kasutatava turvalise andmehoidla teenuse. Probleemide korral palun ava siin https://github.com/Nheko-Reborn/nheko/issues veateade @@ -2438,7 +2438,7 @@ Kui Markdown ei ole kasutusel, siis saadetakse kõik sõnumid vormindamata tekst Play animated images only on hover - + Esita liikuvaid pilte vaid siis, kui kursor on pildi kohal @@ -2515,12 +2515,12 @@ See tavaliselt tähendab, et rakenduse ikoon tegumiribal annab mingit sorti anim Enable online key backup - + Võta kasutusele krüptovõtmete varundus võrgus The Nheko authors recommend not enabling online key backup until symmetric online key backup is available. Enable anyway? - + Seni kuni sümmetriline krüptovõtmete varundamine pole teostatav, siis Nheko arendajad ei soovita krüptovõtmeid võrgus salvestada. Kas ikkagi jätkame? @@ -2640,7 +2640,7 @@ See tavaliselt tähendab, et rakenduse ikoon tegumiribal annab mingit sorti anim Plays media like GIFs or WEBPs only when explicitly hovering over them. - + Esitame liikuvaid GIF ja WEBP pilte vaid siis, kui kursor on pildi kohal. From 3528fe4e5dee1684f43e14fc9c6a50b4c1e25faf Mon Sep 17 00:00:00 2001 From: Nicolas Werner Date: Thu, 2 Sep 2021 03:15:07 +0200 Subject: [PATCH 076/232] Warn the user before they ping the whole room --- resources/qml/MessageInput.qml | 2 +- resources/qml/NotificationWarning.qml | 38 +++++++++++++++++++++++++++ resources/qml/TimelineView.qml | 3 +++ resources/res.qrc | 1 + src/timeline/InputBar.cpp | 33 +++++++++++++++++++++++ src/timeline/InputBar.h | 10 ++++++- src/timeline/Permissions.cpp | 7 +++++ src/timeline/Permissions.h | 2 ++ 8 files changed, 94 insertions(+), 2 deletions(-) create mode 100644 resources/qml/NotificationWarning.qml diff --git a/resources/qml/MessageInput.qml b/resources/qml/MessageInput.qml index e1bf3f06..c95929ce 100644 --- a/resources/qml/MessageInput.qml +++ b/resources/qml/MessageInput.qml @@ -268,7 +268,7 @@ Rectangle { function onRoomChanged() { messageInput.clear(); if (room) - messageInput.append(room.input.text()); + messageInput.append(room.input.text); popup.completerName = ""; messageInput.forceActiveFocus(); diff --git a/resources/qml/NotificationWarning.qml b/resources/qml/NotificationWarning.qml new file mode 100644 index 00000000..b606581b --- /dev/null +++ b/resources/qml/NotificationWarning.qml @@ -0,0 +1,38 @@ +// SPDX-FileCopyrightText: 2021 Nheko Contributors +// +// SPDX-License-Identifier: GPL-3.0-or-later + +import QtQuick 2.9 +import QtQuick.Controls 2.3 +import QtQuick.Layouts 1.2 +import im.nheko 1.0 + +Item { + implicitHeight: warningRect.visible ? warningDisplay.implicitHeight : 0 + height: implicitHeight + Layout.fillWidth: true + + Rectangle { + id: warningRect + + visible: (room && room.permissions.canPingRoom && room.input.containsAtRoom) + color: Nheko.colors.base + anchors.fill: parent + z: 3 + + Label { + id: warningDisplay + + anchors.left: parent.left + anchors.leftMargin: 10 + anchors.right: parent.right + anchors.rightMargin: 10 + anchors.bottom: parent.bottom + color: Nheko.theme.red + text: qsTr("You will be pinging the whole room") + textFormat: Text.PlainText + } + + } + +} diff --git a/resources/qml/TimelineView.qml b/resources/qml/TimelineView.qml index 9bc4bef0..f12060f2 100644 --- a/resources/qml/TimelineView.qml +++ b/resources/qml/TimelineView.qml @@ -123,6 +123,9 @@ Item { color: Nheko.theme.separator } + NotificationWarning { + } + ReplyPopup { } diff --git a/resources/res.qrc b/resources/res.qrc index b46b726c..3514ebca 100644 --- a/resources/res.qrc +++ b/resources/res.qrc @@ -138,6 +138,7 @@ qml/QuickSwitcher.qml qml/ForwardCompleter.qml qml/TypingIndicator.qml + qml/NotificationWarning.qml qml/RoomSettings.qml qml/emoji/EmojiPicker.qml qml/emoji/StickerPicker.qml diff --git a/src/timeline/InputBar.cpp b/src/timeline/InputBar.cpp index c82099f0..ece9db62 100644 --- a/src/timeline/InputBar.cpp +++ b/src/timeline/InputBar.cpp @@ -11,6 +11,7 @@ #include #include #include +#include #include #include @@ -129,6 +130,34 @@ InputBar::insertMimeData(const QMimeData *md) } } +void +InputBar::updateAtRoom(const QString &t) +{ + bool roomMention = false; + + if (t.size() > 4) { + QTextBoundaryFinder finder(QTextBoundaryFinder::BoundaryType::Word, t); + + finder.toStart(); + do { + auto start = finder.position(); + finder.toNextBoundary(); + auto end = finder.position(); + if (start > 0 && end - start >= 4 && + t.midRef(start, end - start) == "room" && + t.at(start - 1) == QChar('@')) { + roomMention = true; + break; + } + } while (finder.position() < t.size()); + } + + if (roomMention != this->containsAtRoom_) { + this->containsAtRoom_ = roomMention; + emit containsAtRoomChanged(); + } +} + void InputBar::setText(QString newText) { @@ -157,6 +186,8 @@ InputBar::updateState(int selectionStart_, int selectionEnd_, int cursorPosition else history_.front() = text_; history_index_ = 0; + + updateAtRoom(text_); } selectionStart = selectionStart_; @@ -182,6 +213,7 @@ InputBar::previousText() else if (text().isEmpty()) history_index_--; + updateAtRoom(text()); return text(); } @@ -192,6 +224,7 @@ InputBar::nextText() if (history_index_ >= INPUT_HISTORY_SIZE) history_index_ = 0; + updateAtRoom(text()); return text(); } diff --git a/src/timeline/InputBar.h b/src/timeline/InputBar.h index 2e6fb5c0..cdc66a06 100644 --- a/src/timeline/InputBar.h +++ b/src/timeline/InputBar.h @@ -28,6 +28,8 @@ class InputBar : public QObject { Q_OBJECT Q_PROPERTY(bool uploading READ uploading NOTIFY uploadingChanged) + Q_PROPERTY(bool containsAtRoom READ containsAtRoom NOTIFY containsAtRoomChanged) + Q_PROPERTY(QString text READ text NOTIFY textChanged) public: InputBar(TimelineModel *parent) @@ -48,6 +50,8 @@ public slots: QString nextText(); void setText(QString newText); + bool containsAtRoom() const { return containsAtRoom_; } + void send(); void paste(bool fromMouse); void insertMimeData(const QMimeData *data); @@ -68,6 +72,7 @@ signals: void insertText(QString text); void textChanged(QString newText); void uploadingChanged(bool value); + void containsAtRoomChanged(); private: void emote(QString body, bool rainbowify); @@ -105,11 +110,14 @@ private: } } + void updateAtRoom(const QString &t); + QTimer typingRefresh_; QTimer typingTimeout_; TimelineModel *room; std::deque history_; std::size_t history_index_ = 0; int selectionStart = 0, selectionEnd = 0, cursorPosition = 0; - bool uploading_ = false; + bool uploading_ = false; + bool containsAtRoom_ = false; }; diff --git a/src/timeline/Permissions.cpp b/src/timeline/Permissions.cpp index e4957045..5dafc325 100644 --- a/src/timeline/Permissions.cpp +++ b/src/timeline/Permissions.cpp @@ -61,3 +61,10 @@ Permissions::canSend(int eventType) pl.event_level(to_string(qml_mtx_events::fromRoomEventType( static_cast(eventType)))); } + +bool +Permissions::canPingRoom() +{ + return pl.user_level(http::client()->user_id().to_string()) >= + pl.notification_level(mtx::events::state::notification_keys::room); +} diff --git a/src/timeline/Permissions.h b/src/timeline/Permissions.h index 7aab1ddb..349520d5 100644 --- a/src/timeline/Permissions.h +++ b/src/timeline/Permissions.h @@ -25,6 +25,8 @@ public: Q_INVOKABLE bool canChange(int eventType); Q_INVOKABLE bool canSend(int eventType); + Q_INVOKABLE bool canPingRoom(); + void invalidate(); private: From 7f965a82e28c7ce5e3540bcac849d8d2ee6fbf67 Mon Sep 17 00:00:00 2001 From: Nicolas Werner Date: Thu, 2 Sep 2021 03:22:15 +0200 Subject: [PATCH 077/232] bump mtxclient --- CMakeLists.txt | 2 +- io.github.NhekoReborn.Nheko.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 174fdb7f..20ef5cab 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -384,7 +384,7 @@ if(USE_BUNDLED_MTXCLIENT) FetchContent_Declare( MatrixClient GIT_REPOSITORY https://github.com/Nheko-Reborn/mtxclient.git - GIT_TAG 6306ce3bab0b4d478ade213c09e79d30f04f161e + GIT_TAG ef741d7dceed11ccd46a553a4c886491aedc973b ) set(BUILD_LIB_EXAMPLES OFF CACHE INTERNAL "") set(BUILD_LIB_TESTS OFF CACHE INTERNAL "") diff --git a/io.github.NhekoReborn.Nheko.yaml b/io.github.NhekoReborn.Nheko.yaml index 855005c9..a1d96db7 100644 --- a/io.github.NhekoReborn.Nheko.yaml +++ b/io.github.NhekoReborn.Nheko.yaml @@ -163,7 +163,7 @@ modules: buildsystem: cmake-ninja name: mtxclient sources: - - commit: 6306ce3bab0b4d478ade213c09e79d30f04f161e + - commit: ef741d7dceed11ccd46a553a4c886491aedc973b type: git url: https://github.com/Nheko-Reborn/mtxclient.git - config-opts: From f291dcc7b48d6f62608a18f2001828b819591727 Mon Sep 17 00:00:00 2001 From: Weblate Date: Thu, 2 Sep 2021 16:25:15 -0400 Subject: [PATCH 078/232] Translated using Weblate (French) Currently translated at 72.3% (405 of 560 strings) Translated using Weblate (French) Currently translated at 72.3% (405 of 560 strings) Translated using Weblate (French) Currently translated at 72.3% (405 of 560 strings) Co-authored-by: Carl Schwan Co-authored-by: Eldred HABERT Co-authored-by: Glandos Co-authored-by: Weblate Translate-URL: https://weblate.nheko.im/projects/nheko/nheko-master/fr/ Translation: Nheko/nheko --- resources/langs/nheko_fr.ts | 114 ++++++++++++++++++------------------ 1 file changed, 57 insertions(+), 57 deletions(-) diff --git a/resources/langs/nheko_fr.ts b/resources/langs/nheko_fr.ts index 8eec32bc..b8ea30be 100644 --- a/resources/langs/nheko_fr.ts +++ b/resources/langs/nheko_fr.ts @@ -91,17 +91,17 @@ Accept - Décrocher + Décrocher Unknown microphone: %1 - Microphone inconnu  : %1 + Microphone inconnu  : %1 Unknown camera: %1 - Caméra inconnue  : %1 + Caméra inconnue  : %1 @@ -164,12 +164,12 @@ Do you really want to invite %1 (%2)? - Voulez-vous vraiment inviter %1 (%2) ? + Voulez-vous vraiment inviter %1 (%2) ? Failed to invite %1 to %2: %3 - Échec de l'invitation de %1 dans %2 : %3 + Échec de l'invitation de %1 dans %2 : %3 @@ -179,7 +179,7 @@ Do you really want to kick %1 (%2)? - Voulez-vous vraiment expulser %1 (%2) ? + Voulez-vous vraiment expulser %1 (%2) ? @@ -194,7 +194,7 @@ Do you really want to ban %1 (%2)? - Voulez-vous vraiment bannir %1 (%2) ? + Voulez-vous vraiment bannir %1 (%2) ? @@ -214,12 +214,12 @@ Do you really want to unban %1 (%2)? - Voulez-vous vraiment annuler le bannissement de %1 (%2) ? + Voulez-vous vraiment annuler le bannissement de %1 (%2) ? Failed to unban %1 in %2: %3 - Échec de l'annulation du bannissement de %1 dans %2 : %3 + Échec de l'annulation du bannissement de %1 dans %2 : %3 @@ -234,7 +234,7 @@ Cache migration failed! - Échec de la migration du cache ! + Échec de la migration du cache ! @@ -261,18 +261,18 @@ Failed to setup encryption keys. Server response: %1 %2. Please try again later. - Échec de la configuration des clés de chiffrement. Réponse du serveur : %1 %2. Veuillez réessayer plus tard. + Échec de la configuration des clés de chiffrement. Réponse du serveur : %1 %2. Veuillez réessayer plus tard. Please try to login again: %1 - Veuillez vous reconnecter : %1 + Veuillez vous reconnecter : %1 Failed to join room: %1 - Impossible de rejoindre le salon : %1 + Impossible de rejoindre le salon : %1 @@ -282,22 +282,22 @@ Failed to remove invite: %1 - Impossible de supprimer l'invitation : %1 + Impossible de supprimer l'invitation : %1 Room creation failed: %1 - Échec de la création du salon : %1 + Échec de la création du salon : %1 Failed to leave room: %1 - Impossible de quitter le salon : %1 + Impossible de quitter le salon : %1 Failed to kick %1 from %2: %3 - Échec de l'expulsion de %1 depuis %2  : %3 + Échec de l'expulsion de %1 depuis %2  : %3 @@ -361,12 +361,12 @@ Enter your recovery key or passphrase to decrypt your secrets: - Entrez votre clé de récupération ou phrase de passe pour déchiffrer vos secrets  : + Entrez votre clé de récupération ou phrase de passe pour déchiffrer vos secrets  : Enter your recovery key or passphrase called %1 to decrypt your secrets: - Entrez votre clé de récupération ou votre phrase de passe nommée %1 pour déchiffrer vos secrets  : + Entrez votre clé de récupération ou votre phrase de passe nommée %1 pour déchiffrer vos secrets  : @@ -389,17 +389,17 @@ Please verify the following digits. You should see the same numbers on both sides. If they differ, please press 'They do not match!' to abort verification! - Veuillez vérifier les chiffres suivants. Vous devriez voir les mêmes chiffres des deux côtés. Si ceux-ci diffèrent, veuillez choisir « Ils sont différents ! » pour annuler la vérification ! + Veuillez vérifier les chiffres suivants. Vous devriez voir les mêmes chiffres des deux côtés. Si ceux-ci diffèrent, veuillez choisir « Ils sont différents ! » pour annuler la vérification ! They do not match! - Ils sont différents ! + Ils sont différents ! They match! - Ils sont identiques ! + Ils sont identiques ! @@ -483,17 +483,17 @@ Please verify the following emoji. You should see the same emoji on both sides. If they differ, please press 'They do not match!' to abort verification! - Veuillez vérifier les émoji suivantes. Vous devriez voir les mêmes émoji des deux côtés. Si celles-ci diffèrent, veuillez choisir « Elles sont différentes ! » pour annuler la vérification ! + Veuillez vérifier les émoji suivantes. Vous devriez voir les mêmes émoji des deux côtés. Si celles-ci diffèrent, veuillez choisir « Elles sont différentes ! » pour annuler la vérification ! They do not match! - Elles sont différentes ! + Elles sont différentes ! They match! - Elles sont identiques ! + Elles sont identiques ! @@ -544,7 +544,7 @@ This message is not encrypted! - Ce message n'est pas chiffré ! + Ce message n'est pas chiffré ! @@ -577,7 +577,7 @@ Key mismatch detected! - Clés non correspondantes détectées ! + Clés non correspondantes détectées ! @@ -802,9 +802,9 @@ You can also put your homeserver address there, if your server doesn't support .well-known lookup. Example: @user:server.my If Nheko fails to discover your homeserver, it will show you a field to enter the server manually. - Votre nom de connexion. Un mxid doit commencer par un « @ » suivi de l'identifiant. L'identifiant doit être suivi du nom de serveur, séparé de celui-ci par « : ». + Votre nom de connexion. Un mxid doit commencer par un « @ » suivi de l'identifiant. L'identifiant doit être suivi du nom de serveur, séparé de celui-ci par « : ». Vous pouvez également spécifier l'adresse de votre serveur ici, si votre serveur ne supporte pas l'identification .well-known. -Exemple : @utilisateur :monserveur.example.com +Exemple : @utilisateur :monserveur.example.com Si Nheko n'arrive pas à trouver votre serveur, il vous proposera de l'indiquer manuellement. @@ -842,7 +842,7 @@ Si Nheko n'arrive pas à trouver votre serveur, il vous proposera de l&apos The address that can be used to contact you homeservers client API. Example: https://server.my:8787 L'adresse qui peut être utilisée pour joindre l'API client de votre serveur. -Exemple : https ://monserveur.example.com :8787 +Exemple : https ://monserveur.example.com :8787 @@ -855,7 +855,7 @@ Exemple : https ://monserveur.example.com :8787 You have entered an invalid Matrix ID e.g @joe:matrix.org - Vous avez entré un identifiant Matrix invalide (exemple correct : @moi :mon.serveur.fr) + Vous avez entré un identifiant Matrix invalide (exemple correct : @moi :mon.serveur.fr) @@ -914,7 +914,7 @@ Exemple : https ://monserveur.example.com :8787 room name changed to: %1 - nom du salon changé en : %1 + nom du salon changé en : %1 @@ -924,7 +924,7 @@ Exemple : https ://monserveur.example.com :8787 topic changed to: %1 - sujet changé pour : %1 + sujet changé pour : %1 @@ -939,7 +939,7 @@ Exemple : https ://monserveur.example.com :8787 %1 created and configured room: %2 - %1 a créé et configuré le salon : %2 + %1 a créé et configuré le salon : %2 @@ -1128,7 +1128,7 @@ Exemple : https ://monserveur.example.com :8787 To allow other users to see, which of your devices actually belong to you, you can verify them. This also allows key backup to work automatically. Verify %1 now? - Pour permettre aux autres utilisateurs de vérifier quels appareils de votre compte sont sous votre contrôle, vous pouvez vérifier ceux-ci. Cela permet également à ces appareils de sauvegarder vos clés de chiffrement automatiquement. Vérifier %1 maintenant ? + Pour permettre aux autres utilisateurs de vérifier quels appareils de votre compte sont sous votre contrôle, vous pouvez vérifier ceux-ci. Cela permet également à ces appareils de sauvegarder vos clés de chiffrement automatiquement. Vérifier %1 maintenant ? @@ -1196,7 +1196,7 @@ Exemple : https ://monserveur.example.com :8787 %1: %2 Format a normal message in a notification. %1 is the sender, %2 the message - %1  : %2 + %1  : %2 @@ -1220,7 +1220,7 @@ Exemple : https ://monserveur.example.com :8787 Place a call to %1? - Appeler %1 ? + Appeler %1 ? @@ -1253,7 +1253,7 @@ Exemple : https ://monserveur.example.com :8787 unimplemented event: - Évènement non implémenté : + Évènement non implémenté : @@ -1301,7 +1301,7 @@ Exemple : https ://monserveur.example.com :8787 The username must not be empty, and must contain only the characters a-z, 0-9, ., _, =, -, and /. - Le nom d'utilisateur ne doit pas être vide, et ne peut contenir que les caractères a à z, 0 à 9, et « . _ = - / ». + Le nom d'utilisateur ne doit pas être vide, et ne peut contenir que les caractères a à z, 0 à 9, et « . _ = - / ». @@ -1336,7 +1336,7 @@ Exemple : https ://monserveur.example.com :8787 No supported registration flows! - Pas de méthode d'inscription supportée ! + Pas de méthode d'inscription supportée ! @@ -1453,7 +1453,7 @@ Exemple : https ://monserveur.example.com :8787 Tag room as: - Étiqueter le salon comme : + Étiqueter le salon comme : @@ -1684,7 +1684,7 @@ Exemple : https ://monserveur.example.com :8787 Failed to enable encryption: %1 - Échec de l'activation du chiffrement  : %1 + Échec de l'activation du chiffrement : %1 @@ -1704,13 +1704,13 @@ Exemple : https ://monserveur.example.com :8787 Error while reading file: %1 - Erreur lors de la lecture du fichier  : %1 + Erreur lors de la lecture du fichier  : %1 Failed to upload image: %s - Échec de l'envoi de l'image  : %s + Échec de l'envoi de l'image  : %s @@ -1736,17 +1736,17 @@ Exemple : https ://monserveur.example.com :8787 Share desktop with %1? - Partager le bureau avec %1  ? + Partager le bureau avec %1  ? Window: - Fenêtre  : + Fenêtre  : Frame rate: - Fréquence d'images  : + Fréquence d'images  : @@ -1863,7 +1863,7 @@ Exemple : https ://monserveur.example.com :8787 Verification successful! Both sides verified their devices! - Vérification réussie ! Les deux côtés ont vérifié leur appareil ! + Vérification réussie ! Les deux côtés ont vérifié leur appareil ! @@ -1876,13 +1876,13 @@ Exemple : https ://monserveur.example.com :8787 Message redaction failed: %1 - Échec de la suppression du message : %1 + Échec de la suppression du message : %1 Failed to encrypt event, sending aborted! - Échec du chiffrement de l'évènement, envoi abandonné ! + Échec du chiffrement de l'évènement, envoi abandonné ! @@ -2057,7 +2057,7 @@ Exemple : https ://monserveur.example.com :8787 %1 left after having already left! This is a leave event after the user already left and shouldn't happen apart from state resets - %1 a quitté le salon après l'avoir déjà quitté ! + %1 a quitté le salon après l'avoir déjà quitté ! @@ -2241,7 +2241,7 @@ Exemple : https ://monserveur.example.com :8787 Error while reading file: %1 - Erreur lors de la lecture du fichier  : %1 + Erreur lors de la lecture du fichier  : %1 @@ -2278,7 +2278,7 @@ Exemple : https ://monserveur.example.com :8787 profile: %1 - profil : %1 + profil : %1 @@ -2733,7 +2733,7 @@ Cela met l'application en évidence dans la barre des tâches. Enter the passphrase to decrypt the file: - Entrez la clé secrète pour déchiffrer le fichier  : + Entrez la clé secrète pour déchiffrer le fichier  : @@ -2744,7 +2744,7 @@ Cela met l'application en évidence dans la barre des tâches. Enter passphrase to encrypt your session keys: - Entrez une clé secrète pour chiffrer vos clés de session  : + Entrez une clé secrète pour chiffrer vos clés de session  : @@ -3026,12 +3026,12 @@ Taille du média : %2 You: %1 - Vous  : %1 + Vous  : %1 %1: %2 - %1  : %2 + %1  : %2 From e6e54fe41570006851e8a94cc5cd2d1f3d479a93 Mon Sep 17 00:00:00 2001 From: Weblate Date: Thu, 2 Sep 2021 17:09:57 -0400 Subject: [PATCH 079/232] Translated using Weblate (French) Currently translated at 93.0% (521 of 560 strings) Translated using Weblate (French) Currently translated at 93.0% (521 of 560 strings) Co-authored-by: Eldred HABERT Co-authored-by: Glandos Co-authored-by: Weblate Translate-URL: https://weblate.nheko.im/projects/nheko/nheko-master/fr/ Translation: Nheko/nheko --- resources/langs/nheko_fr.ts | 296 ++++++++++++++++++------------------ 1 file changed, 148 insertions(+), 148 deletions(-) diff --git a/resources/langs/nheko_fr.ts b/resources/langs/nheko_fr.ts index b8ea30be..38bf15b5 100644 --- a/resources/langs/nheko_fr.ts +++ b/resources/langs/nheko_fr.ts @@ -96,12 +96,12 @@ Unknown microphone: %1 - Microphone inconnu  : %1 + Microphone inconnu : %1 Unknown camera: %1 - Caméra inconnue  : %1 + Caméra inconnue : %1 @@ -111,7 +111,7 @@ No microphone found. - Pas de microphone trouvé. + Aucun microphone trouvé. @@ -119,7 +119,7 @@ Entire screen - L'écran complet + Tout l'écran @@ -133,7 +133,7 @@ Invited user: %1 - %1 a été invité(e) + Utilisateur %1 invité(e) @@ -199,7 +199,7 @@ Failed to ban %1 in %2: %3 - L'utilisateur %1 n'a pas pu être banni de %2 : %3 + Échec du bannissement de %1 de %2 : %3 @@ -229,7 +229,7 @@ Do you really want to start a private chat with %1? - Voulez-vous vraimer commencer une discussion privée avec %1 ? + Voulez-vous vraiment commencer une discussion privée avec %1 ? @@ -267,7 +267,7 @@ Please try to login again: %1 - Veuillez vous reconnecter : %1 + Veuillez re-tenter vous reconnecter : %1 @@ -297,7 +297,7 @@ Failed to kick %1 from %2: %3 - Échec de l'expulsion de %1 depuis %2  : %3 + Échec de l'expulsion de %1 de %2  : %3 @@ -305,7 +305,7 @@ Hide rooms with this tag or from this space by default. - + Cacher par défaut les salons avec cette étiquette ou de cet espace. @@ -313,42 +313,42 @@ All rooms - Tous les salons + Tous les salons Shows all rooms without filtering. - + Montre tous les salons sans filtrer. Favourites - + Favoris Rooms you have favourited. - + Vos salons favoris. Low Priority - Basse priorité + Priorité basse Rooms with low priority. - + Salons à priorité basse. Server Notices - Notifications du serveur + Notifications du serveur Messages from your server or administrator. - + Messages de votre serveur ou administrateur. @@ -361,12 +361,12 @@ Enter your recovery key or passphrase to decrypt your secrets: - Entrez votre clé de récupération ou phrase de passe pour déchiffrer vos secrets  : + Entrez votre clé de récupération ou phrase de passe pour déchiffrer vos secrets : Enter your recovery key or passphrase called %1 to decrypt your secrets: - Entrez votre clé de récupération ou votre phrase de passe nommée %1 pour déchiffrer vos secrets  : + Entrez votre clé de récupération ou votre phrase de passe nommée %1 pour déchiffrer vos secrets : @@ -483,17 +483,17 @@ Please verify the following emoji. You should see the same emoji on both sides. If they differ, please press 'They do not match!' to abort verification! - Veuillez vérifier les émoji suivantes. Vous devriez voir les mêmes émoji des deux côtés. Si celles-ci diffèrent, veuillez choisir « Elles sont différentes ! » pour annuler la vérification ! + Veuillez vérifier les émoji suivants. Vous devriez voir les mêmes émoji des deux côtés. S'ils diffèrent, veuillez choisir « Ils sont différents ! » pour annuler la vérification ! They do not match! - Elles sont différentes ! + Ils sont différents ! They match! - Elles sont identiques ! + Ils sont identiques ! @@ -501,42 +501,42 @@ There is no key to unlock this message. We requested the key automatically, but you can try requesting it again if you are impatient. - + Il n'y a pas de clé pour déverrouiller ce message. Nous avons demandé la clé automatiquement, mais vous pouvez tenter de la demander à nouveau si vous êtes impatient. This message couldn't be decrypted, because we only have a key for newer messages. You can try requesting access to this message. - + Ce message n'a pas pu être déchiffré, car nous n'avons une clef que pour des messages plus récents. Vous pouvez demander l'accès à ce message. There was an internal error reading the decryption key from the database. - + Une erreur interne s'est produite durant la lecture de la clef de déchiffrement depuis la base de données. There was an error decrypting this message. - + Une erreur s'est produite durant le déchiffrement de ce message. The message couldn't be parsed. - + Le message n'a pas pu être traité. The encryption key was reused! Someone is possibly trying to insert false messages into this chat! - + La clef de chiffrement a été réutilisée ! Quelqu'un essaye peut-être d'insérer de faux messages dans ce chat ! Unknown decryption error - + Erreur de déchiffrement inconnue Request key - + Demander la clef @@ -549,17 +549,17 @@ Encrypted by a verified device - + Chiffré par un appareil vérifié Encrypted by an unverified device, but you have trusted that user so far. - + Chiffré par un appareil non vérifié, mais vous avez déjà fait confiance à ce contact. Encrypted by an unverified device or the key is from an untrusted source like the key backup. - + Chiffré par un appareil non vérifié, ou la clef provient d'une source non sûre comme la sauvegarde des clefs. @@ -601,7 +601,7 @@ Forward Message - + Transférer le message @@ -609,74 +609,74 @@ Editing image pack - + Modification du paquet d'images Add images - + Ajouter des images Stickers (*.png *.webp *.gif) - + Autocollants (*.png *.webp *.gif) State key - + Clef d'état Packname - + Nom de paquet Attribution - + Attribution Use as Emoji - + Utiliser en tant qu'émoji Use as Sticker - + Utiliser en tant qu'autocollant Shortcode - + Raccourci Body - + Corps Remove from pack - + Retirer du paquet Remove - + Retirer Cancel - Annuler + Annuler Save - + Sauvegarder @@ -684,52 +684,52 @@ Image pack settings - + Paramètres des paquets d'images Create account pack - + Créer un paquet de compte New room pack - + Nouveau paquet de salle Private pack - + Paquet privé Pack from this room - + Paquet de cette salle Globally enabled pack - + Paquet activé partout Enable globally - + Activer partout Enables this pack to be used in all rooms - + Permet d'utiliser ce paquet dans tous les salons Edit - Modifier + Modifier Close - Fermer + Fermer @@ -755,33 +755,33 @@ Invite users to %1 - + Inviter des utilisateurs dans %1 User ID to invite - Identifiant d'utilisateur à inviter + Identifiant de l'utilisateur à inviter @joe:matrix.org Example user id. The name 'joe' can be localized however you want. - + @jean:matrix.org Add - + Ajouter Invite - + Inviter Cancel - Annuler + Annuler @@ -794,7 +794,7 @@ e.g @joe:matrix.org - ex : @joe:matrix.org + p. ex : @jean:matrix.org @@ -825,7 +825,7 @@ Si Nheko n'arrive pas à trouver votre serveur, il vous proposera de l&apos A name for this device, which will be shown to others, when verifying your devices. If none is provided a default is used. - Un nom pour cet appareil, qui sera montré aux autres utilisateurs lorsque ceux-ci le vérifieront. Si aucun n'est fourni, un nom par défaut est utilisé. + Un nom pour cet appareil, qui sera montré aux autres utilisateurs lorsque ceux-ci vérifient vos appareils. Si aucun n'est fourni, un nom par défaut est utilisé. @@ -835,14 +835,14 @@ Si Nheko n'arrive pas à trouver votre serveur, il vous proposera de l&apos server.my:8787 - mon.serveur.fr:8787 + monserveur.example.com:8787 The address that can be used to contact you homeservers client API. Example: https://server.my:8787 L'adresse qui peut être utilisée pour joindre l'API client de votre serveur. -Exemple : https ://monserveur.example.com :8787 +Exemple : https ://monserveur.example.com:8787 @@ -855,12 +855,12 @@ Exemple : https ://monserveur.example.com :8787 You have entered an invalid Matrix ID e.g @joe:matrix.org - Vous avez entré un identifiant Matrix invalide (exemple correct : @moi :mon.serveur.fr) + Vous avez entré un identifiant Matrix invalide exemple correct : @moi:monserveur.example.com) Autodiscovery failed. Received malformed response. - Échec de la découverte automatique. Réponse mal formatée reçue. + Échec de la découverte automatique. Réponse mal formée reçue. @@ -870,7 +870,7 @@ Exemple : https ://monserveur.example.com :8787 The required endpoints were not found. Possibly not a Matrix server. - Les chemins requis n'ont pas été trouvés. Possible qu'il ne s'agisse pas d'un serveur Matrix. + Les endpoints requis n'ont pas été trouvés. Ce n'est peut-être pas un serveur Matrix. @@ -924,7 +924,7 @@ Exemple : https ://monserveur.example.com :8787 topic changed to: %1 - sujet changé pour : %1 + sujet changé en : %1 @@ -934,7 +934,7 @@ Exemple : https ://monserveur.example.com :8787 %1 changed the room avatar - + %1 a changé l'avatar du salon @@ -974,7 +974,7 @@ Exemple : https ://monserveur.example.com :8787 Allow them in - + Les laisser entrer @@ -1002,7 +1002,7 @@ Exemple : https ://monserveur.example.com :8787 Stickers - + Autocollants @@ -1017,7 +1017,7 @@ Exemple : https ://monserveur.example.com :8787 You don't have permission to send messages in this room - + Vous n'avez pas l'autorisation d'envoyer des messages dans ce salon @@ -1045,72 +1045,72 @@ Exemple : https ://monserveur.example.com :8787 &Copy - + &Copier Copy &link location - + Copier l'adresse du &lien Re&act - + Ré&agir Repl&y - + &Y répondre &Edit - + &Editer Read receip&ts - + Accusés de lec&ture &Forward - + &Faire suivre &Mark as read - + &Marquer comme lu View raw message - Voir le message brut + Voir le message brut View decrypted raw message - Voir le message déchiffré brut + Voir le message déchiffré brut Remo&ve message - + Enle&ver le message &Save as - + Enregistrer &sous &Open in external program - + &Ouvrir dans un programme externe Copy link to eve&nt - + Copier le lien vers l'évène&nement @@ -1128,12 +1128,12 @@ Exemple : https ://monserveur.example.com :8787 To allow other users to see, which of your devices actually belong to you, you can verify them. This also allows key backup to work automatically. Verify %1 now? - Pour permettre aux autres utilisateurs de vérifier quels appareils de votre compte sont sous votre contrôle, vous pouvez vérifier ceux-ci. Cela permet également à ces appareils de sauvegarder vos clés de chiffrement automatiquement. Vérifier %1 maintenant ? + Pour permettre aux autres utilisateurs de vérifier quels appareils de votre compte sont réellement les vôtres, vous pouvez les vérifier. Cela permet également à la sauvegarde des clés de fonctionner automatiquement. Vérifier %1 maintenant ? To ensure that no malicious user can eavesdrop on your encrypted communications you can verify the other party. - Pour vous assurer que personne n'intercepte vos communications chiffrées, vous pouvez vérifier le correspondant. + Pour vous assurer que personne ne puisse intercepter vos communications chiffrées, vous pouvez vérifier le correspondant. @@ -1196,7 +1196,7 @@ Exemple : https ://monserveur.example.com :8787 %1: %2 Format a normal message in a notification. %1 is the sender, %2 the message - %1  : %2 + %1 : %2 @@ -1207,7 +1207,7 @@ Exemple : https ://monserveur.example.com :8787 %1 replied to a message - %1 a répondu a un message + %1 a répondu à un message @@ -1261,7 +1261,7 @@ Exemple : https ://monserveur.example.com :8787 Create a unique profile, which allows you to log into several accounts at the same time and start multiple instances of nheko. - Créer un profil unique, vous permettant de vous connecter simultanément à plusieurs comptes et à lancer plusieurs instances de nheko. + Créer un profil unique, vous permettant de vous connecter simultanément à plusieurs comptes et de lancer plusieurs instances de nheko. @@ -1279,7 +1279,7 @@ Exemple : https ://monserveur.example.com :8787 Read receipts - Accusés de lecture + Accusés de lecture @@ -1287,7 +1287,7 @@ Exemple : https ://monserveur.example.com :8787 Yesterday, %1 - + Hier, %1 @@ -1301,7 +1301,7 @@ Exemple : https ://monserveur.example.com :8787 The username must not be empty, and must contain only the characters a-z, 0-9, ., _, =, -, and /. - Le nom d'utilisateur ne doit pas être vide, et ne peut contenir que les caractères a à z, 0 à 9, et « . _ = - / ». + Le nom d'utilisateur ne doit pas être vide, et ne peut contenir que les caractères a-z, 0-9, ., _, =, -, et /. @@ -1336,42 +1336,42 @@ Exemple : https ://monserveur.example.com :8787 No supported registration flows! - Pas de méthode d'inscription supportée ! + Aucune méthode d'inscription supportée ! Registration token - + Jeton d'enregistrement Please enter a valid registration token. - + Veuillez entrer un jeton d'enregistrement valide. Autodiscovery failed. Received malformed response. - Échec de la découverte automatique. Réponse mal formatée reçue. + Échec de la découverte automatique. Réponse mal formée reçue. Autodiscovery failed. Unknown error when requesting .well-known. - Échec de la découverte automatique. Erreur inconnue lors de la demande de .well-known. + Échec de la découverte automatique. Erreur inconnue lors de la demande de .well-known. The required endpoints were not found. Possibly not a Matrix server. - Les chemins requis n'ont pas été trouvés. Possible qu'il ne s'agisse pas d'un serveur Matrix. + Les endpoints requis n'ont pas été trouvés. Ce n'est peut-être pas un serveur Matrix. Received malformed response. Make sure the homeserver domain is valid. - Réponse mal formée reçue. Vérifiez que le nom de domaine du serveur est valide. + Réponse mal formée reçue. Vérifiez que le nom de domaine du serveur est valide. An unknown error occured. Make sure the homeserver domain is valid. - Une erreur inconnue est survenue. Vérifiez que le nom de domaine du serveur est valide. + Une erreur inconnue est survenue. Vérifiez que le nom de domaine du serveur est valide. @@ -1407,12 +1407,12 @@ Exemple : https ://monserveur.example.com :8787 Explore Public Rooms - + Explorer les salons publics Search for public rooms - + Rechercher des salons publics @@ -1428,102 +1428,102 @@ Exemple : https ://monserveur.example.com :8787 New tag - + Nouvelle étiquette Enter the tag you want to use: - + Entrez l'étiquette que vous voulez utiliser : Leave Room - + Quitter le salon Are you sure you want to leave this room? - + Êtes-vous sûr de vouloir quitter ce salon ? Leave room - Quitter le salon + Quitter le salon Tag room as: - Étiqueter le salon comme : + Étiqueter le salon comme : Favourite - Favori + Favori Low priority - + Priorité basse Server notice - + Notification du serveur Create new tag... - + Créer une nouvelle étiquette… Status Message - + Message de statut Enter your status message: - + Entrez votre message de statut : Profile settings - + Paramètres de profil Set status message - + Changer le message de statut Logout - Se déconnecter + Déconnexion Start a new chat - Commencer une discussion + Commencer une nouvelle discussion Join a room - Rejoindre un salon + Rejoindre un salon Create a new room - + Créer un nouveau salon Room directory - Annuaire des salons + Annuaire des salons User settings - Paramètres utilisateur + Paramètres utilisateur @@ -1531,41 +1531,41 @@ Exemple : https ://monserveur.example.com :8787 Members of %1 - + Membres de %1 %n people in %1 Summary above list of members - - - + + %n personne dans %1 + %n personnes dans %1 Invite more people - + Inviter plus de personnes This room is not encrypted! - + Ce salon n'est pas chiffré ! This user is verified. - + Cet utilisateur est vérifié. This user isn't verified, but is still using the same master key from the first time you met. - + Cet utilisateur n'est pas vérifié, mais utilise toujours la même clef maîtresse que la première fois que vous vous êtes rencontrés. This user has unverified devices! - + Cet utilisateur a des appareils non vérifiés ! @@ -1608,7 +1608,7 @@ Exemple : https ://monserveur.example.com :8787 Room access - + Accès au salon @@ -1628,12 +1628,12 @@ Exemple : https ://monserveur.example.com :8787 By knocking - + En toquant Restricted by membership in other rooms - + Restreint par l'appartenance à d'autre salons @@ -1649,22 +1649,22 @@ Exemple : https ://monserveur.example.com :8787 Encryption is currently experimental and things might break unexpectedly. <br> Please take note that it can't be disabled afterwards. - Le chiffrement actuellement expérimental et des comportements inattendus peuvent être rencontrés. <br>Veuillez noter qu'il n'est pas possible de le désactiver par la suite. + Le chiffrement est actuellement expérimental et des comportements inattendus peuvent apparaître.<br>Veuillez noter qu'il n'est pas possible de le désactiver par la suite. Sticker & Emote Settings - + Paramètres des autocollants & emotes Change - + Modifier Change what packs are enabled, remove packs or create new ones - + Modifier quels paquets sont activés, retirer des paquets ou bien en créer de nouveaux @@ -1699,18 +1699,18 @@ Exemple : https ://monserveur.example.com :8787 The selected file is not an image - Le fichier sélectionné n'est pas une image + Le fichier sélectionné n'est pas une image Error while reading file: %1 - Erreur lors de la lecture du fichier  : %1 + Erreur lors de la lecture du fichier : %1 Failed to upload image: %s - Échec de l'envoi de l'image  : %s + Échec de l’envoi de l'image : %s From f7560cd998067a05f24dc3945f587d8a70053227 Mon Sep 17 00:00:00 2001 From: Nicolas Werner Date: Fri, 3 Sep 2021 01:33:21 +0200 Subject: [PATCH 080/232] Fix z value of avatar in timeline --- resources/qml/MessageView.qml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/resources/qml/MessageView.qml b/resources/qml/MessageView.qml index 6446e61b..9e9ab66e 100644 --- a/resources/qml/MessageView.qml +++ b/resources/qml/MessageView.qml @@ -412,6 +412,8 @@ ScrollView { Loader { id: section + z: 4 + property int parentWidth: parent.width property string userId: wrapper.userId property string previousMessageUserId: wrapper.previousMessageUserId From 6bd62f8283c14d456791e2b26fcc5744abaf5e72 Mon Sep 17 00:00:00 2001 From: Thulinma Date: Fri, 3 Sep 2021 01:55:47 +0200 Subject: [PATCH 081/232] Make desktop alerting independent from notification support in the homeserver. --- src/ChatPage.cpp | 25 +++++++++++++++++-------- 1 file changed, 17 insertions(+), 8 deletions(-) diff --git a/src/ChatPage.cpp b/src/ChatPage.cpp index 038c3e3c..80d06ae2 100644 --- a/src/ChatPage.cpp +++ b/src/ChatPage.cpp @@ -189,13 +189,26 @@ ChatPage::ChatPage(QSharedPointer userSettings, QWidget *parent) connect(this, &ChatPage::syncUI, this, [this](const mtx::responses::Rooms &rooms) { view_manager_->sync(rooms); - bool hasNotifications = false; + static unsigned int prevNotificationCount = 0; + unsigned int notificationCount = 0; for (const auto &room : rooms.join) { - if (room.second.unread_notifications.notification_count > 0) - hasNotifications = true; + notificationCount += room.second.unread_notifications.notification_count; } - if (hasNotifications && userSettings_->hasNotifications()) + // HACK: If we had less notifications last time we checked, send an alert if the + // user wanted one. Technically, this may cause an alert to be missed if new ones + // come in while you are reading old ones. Since the window is almost certainly open + // in this edge case, that's probably a non-issue. + // TODO: Replace this once we have proper pushrules support. This is a horrible hack + if (prevNotificationCount < notificationCount) { + if (userSettings_->hasAlertOnNotification()) + QApplication::alert(this); + } + prevNotificationCount = notificationCount; + + // No need to check amounts for this section, as this function internally checks for + // duplicates. + if (notificationCount && userSettings_->hasNotifications()) http::client()->notifications( 5, "", @@ -462,10 +475,6 @@ ChatPage::sendNotifications(const mtx::responses::Notifications &res) if (isRoomActive(room_id)) continue; - if (userSettings_->hasAlertOnNotification()) { - QApplication::alert(this); - } - if (userSettings_->hasDesktopNotifications()) { auto info = cache::singleRoomInfo(item.room_id); From bf9601018d4b36b654b0febc4af327404541e527 Mon Sep 17 00:00:00 2001 From: Nicolas Werner Date: Fri, 3 Sep 2021 12:34:41 +0200 Subject: [PATCH 082/232] Fix room ping permission checked incorrectly --- resources/qml/NotificationWarning.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/qml/NotificationWarning.qml b/resources/qml/NotificationWarning.qml index b606581b..95ca210b 100644 --- a/resources/qml/NotificationWarning.qml +++ b/resources/qml/NotificationWarning.qml @@ -15,7 +15,7 @@ Item { Rectangle { id: warningRect - visible: (room && room.permissions.canPingRoom && room.input.containsAtRoom) + visible: (room && room.permissions.canPingRoom() && room.input.containsAtRoom) color: Nheko.colors.base anchors.fill: parent z: 3 From 99f3296a644b50d4189a5cfd7785f2c53acafacf Mon Sep 17 00:00:00 2001 From: Nicolas Werner Date: Fri, 3 Sep 2021 15:08:16 +0200 Subject: [PATCH 083/232] Make error case with unverified master key more descriptive --- src/Cache.cpp | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/src/Cache.cpp b/src/Cache.cpp index a7fe473f..5842f536 100644 --- a/src/Cache.cpp +++ b/src/Cache.cpp @@ -4442,13 +4442,19 @@ Cache::verificationStatus_(const std::string &user_id, lmdb::txn &txn) // Update verified devices count to count without cross-signing updateUnverifiedDevices(theirKeys->device_keys); - if (!mtx::crypto::ed25519_verify_signature( - olm::client()->identity_keys().ed25519, - json(ourKeys->master_keys), - ourKeys->master_keys.signatures.at(local_user) - .at("ed25519:" + http::client()->device_id()))) { - verification_storage.status[user_id] = status; - return status; + { + auto &mk = ourKeys->master_keys; + std::string dev_id = "ed25519:" + http::client()->device_id(); + if (!mk.signatures.count(local_user) || + !mk.signatures.at(local_user).count(dev_id) || + !mtx::crypto::ed25519_verify_signature( + olm::client()->identity_keys().ed25519, + json(mk), + mk.signatures.at(local_user).at(dev_id))) { + nhlog::crypto()->debug("We have not verified our own master key"); + verification_storage.status[user_id] = status; + return status; + } } auto master_keys = ourKeys->master_keys.keys; From 764bd203d1984133bda66e6c2f3044feae90a173 Mon Sep 17 00:00:00 2001 From: Weblate Date: Fri, 3 Sep 2021 12:33:11 -0400 Subject: [PATCH 084/232] Translated using Weblate (French) Currently translated at 100.0% (560 of 560 strings) Translated using Weblate (French) Currently translated at 100.0% (560 of 560 strings) Co-authored-by: Eldred HABERT Co-authored-by: Glandos Translate-URL: https://weblate.nheko.im/projects/nheko/nheko-master/fr/ Translation: Nheko/nheko --- resources/langs/nheko_fr.ts | 152 ++++++++++++++++++------------------ 1 file changed, 76 insertions(+), 76 deletions(-) diff --git a/resources/langs/nheko_fr.ts b/resources/langs/nheko_fr.ts index 38bf15b5..e4c6bfcb 100644 --- a/resources/langs/nheko_fr.ts +++ b/resources/langs/nheko_fr.ts @@ -1710,7 +1710,7 @@ Exemple : https ://monserveur.example.com:8787 Failed to upload image: %s - Échec de l’envoi de l'image : %s + Échec de l'envoi de l'image : %s @@ -1718,17 +1718,17 @@ Exemple : https ://monserveur.example.com:8787 Pending invite. - + Invitation en attente. Previewing this room - + Prévisualisation du salon No preview available - + Aucune prévisualisation disponible @@ -1741,12 +1741,12 @@ Exemple : https ://monserveur.example.com:8787 Window: - Fenêtre  : + Fenêtre : Frame rate: - Fréquence d'images  : + Fréquence d'images : @@ -1782,7 +1782,7 @@ Exemple : https ://monserveur.example.com:8787 Cancel - Annuler + Annuler @@ -1790,12 +1790,12 @@ Exemple : https ://monserveur.example.com:8787 Failed to connect to secret storage - + Échec de la connexion au stockage des secrets Nheko could not connect to the secure storage to save encryption secrets to. This can have multiple reasons. Check if your D-Bus service is running and you have configured a service like KWallet, Gnome Secrets or the equivalent for your platform. If you are having trouble, feel free to open an issue here: https://github.com/Nheko-Reborn/nheko/issues - + Nheko n'a pas pu se connecter au stockage des secrets pour y stocker les secrets de chiffrement. Plusieurs causes sont possibles. Vérifiez si votre service D-Bus est en cours d'exécution et si vous avez configuré un service comme KWallet, Gnome Secrets, ou un équivalent pour votre plate-forme. En cas de difficulté, n'hésitez pas à ouvrir un ticket ici (en anglais) : https://github.com/Nheko-Reborn/nheko/issues @@ -1804,22 +1804,22 @@ Exemple : https ://monserveur.example.com:8787 Failed to update image pack: %1 - + Échec de la mise à jour du paquet d'images : %1 Failed to delete old image pack: %1 - + Échec de l'effacement de l'ancien paquet d'images : %1 Failed to open image: %1 - + Échec de l'ouverture de l'image : %1 Failed to upload image: %1 - + Échec de l'envoi de l'image : %1 @@ -1850,7 +1850,7 @@ Exemple : https ://monserveur.example.com:8787 Search - Chercher + Rechercher @@ -1863,7 +1863,7 @@ Exemple : https ://monserveur.example.com:8787 Verification successful! Both sides verified their devices! - Vérification réussie ! Les deux côtés ont vérifié leur appareil ! + Vérification réussie ! Les deux côtés ont vérifié leur appareil ! @@ -1916,7 +1916,7 @@ Exemple : https ://monserveur.example.com:8787 %1 opened the room to the public. - %1 a rendu le salon ouvert au public. + %1 a ouvert le salon au public. @@ -1926,12 +1926,12 @@ Exemple : https ://monserveur.example.com:8787 %1 allowed to join this room by knocking. - + %1 a permis de rejoindre ce salon en toquant. %1 allowed members of the following rooms to automatically join this room: %2 - + %1 a permis aux membres des salons suivants de rejoindre automatiquement ce salon : %2 @@ -1961,7 +1961,7 @@ Exemple : https ://monserveur.example.com:8787 %1 set the room history visible to members since they joined the room. - %1 a rendu l'historique du salon visible à partir de l'instant où un membre le rejoint. + %1 a rendu l'historique du salon visible aux membres à partir de l'instant où ils le rejoignent. @@ -1981,7 +1981,7 @@ Exemple : https ://monserveur.example.com:8787 %1 changed some profile info. - %1 a changé ses informations de profil. + %1 a changé des informations de profil. @@ -1991,7 +1991,7 @@ Exemple : https ://monserveur.example.com:8787 %1 joined via authorisation from %2's server. - + %1 a rejoint via une autorisation de la part du serveur de %2. @@ -2026,12 +2026,12 @@ Exemple : https ://monserveur.example.com:8787 Reason: %1 - + Raison : %1 %1 redacted their knock. - %1 ne frappe plus au salon. + %1 a arrêté de toquer. @@ -2041,17 +2041,17 @@ Exemple : https ://monserveur.example.com:8787 %1 has changed their avatar and changed their display name to %2. - + %1 a changé son avatar et changé son surnom en %2. %1 has changed their display name to %2. - + %1 a changé son surnom en %2. Rejected the knock from %1. - %1 a été rejeté après avoir frappé au salon. + %1 a été rejeté après avoir toqué. @@ -2062,7 +2062,7 @@ Exemple : https ://monserveur.example.com:8787 %1 knocked. - %1 a frappé au salon. + %1 a toqué. @@ -2083,27 +2083,27 @@ Exemple : https ://monserveur.example.com:8787 %1 member(s) - %1 membre(s) + %1 membre(s) join the conversation - + rejoindre la conversation accept invite - + accepter l'invitation decline invite - + décliner l'invitation Back to room list - Revenir à la liste des salons + Revenir à la liste des salons @@ -2111,7 +2111,7 @@ Exemple : https ://monserveur.example.com:8787 No encrypted private chat found with this user. Create an encrypted private chat with this user and try again. - Pas de discussion privée et chiffrée trouvée avec cet utilisateur. Créez-en une et réessayez. + Aucune discussion privée chiffrée trouvée avec cet utilisateur. Créez-en une et réessayez. @@ -2129,22 +2129,22 @@ Exemple : https ://monserveur.example.com:8787 This room is not encrypted! - + Ce salon n'est pas chiffré ! This room contains only verified devices. - + Ce salon ne contient que des appareils vérifiés. This rooms contain verified devices and devices which have never changed their master key. - + Ce salon contient des appareils vérifiés et des appareils qui n'ont jamais changé leur clef maîtresse. This room contains unverified devices! - + Ce salon contient des appareils non vérifiés ! @@ -2226,7 +2226,7 @@ Exemple : https ://monserveur.example.com:8787 Select an avatar - Sélectionner un avatar + Sélectionnez un avatar @@ -2258,7 +2258,7 @@ Exemple : https ://monserveur.example.com:8787 Minimize to tray - Réduire à la barre des tâches + Réduire dans la barre des tâches @@ -2293,7 +2293,7 @@ Exemple : https ://monserveur.example.com:8787 Cross Signing Keys - Clés d'auto-vérification (Cross-Signing) + Clés de signature croisée (Cross-Signing) @@ -2308,12 +2308,12 @@ Exemple : https ://monserveur.example.com:8787 Keep the application running in the background after closing the client window. - Conserver l'application en arrière plan après la fermeture de la fenêtre du client. + Conserver l'application en arrière-plan après avoir fermé la fenêtre du client. Start the application in the background without showing the client window. - Démarrer l'application en arrière plan sans montrer la fenêtre du client. + Démarrer l'application en arrière-plan sans montrer la fenêtre du client. @@ -2325,7 +2325,7 @@ OFF – carré, ON – cercle. Show a column containing groups and tags next to the room list. - Affiche une colonne contenant les groupes et tags à côté de la liste des salons. + Affiche une colonne contenant les groupes et étiquettes à côté de la liste des salons. @@ -2364,7 +2364,7 @@ Set to 0 to blur immediately after focus loss. Max value of 1 hour (3600 seconds Temps d'attente (en secondes) avant le floutage des conversations lorsque la fenêtre n'est plus active. Régler à 0 pour flouter immédiatement lorsque la fenêtre n'est plus au premier plan. -Valeur maximale de une heure (3600 secondes). +Valeur maximale d'une heure (3600 secondes). @@ -2374,12 +2374,12 @@ Valeur maximale de une heure (3600 secondes). Show buttons to quickly reply, react or access additional options next to each message. - Montre les boutons de réponse, réaction ou options additionnelles près de chaque message. + Montre les boutons de réponse, réaction et options additionnelles près de chaque message. Limit width of timeline - Limiter la largeur de l'historique + Limiter la largeur de la discussion @@ -2409,9 +2409,9 @@ Ceci activera ou désactivera également l'envoi de notifications similaire If this is off, the list of rooms will only be sorted by the timestamp of the last message in a room. If this is on, rooms which have active notifications (the small circle with a number in it) will be sorted on top. Rooms, that you have muted, will still be sorted by timestamp, since you don't seem to consider them as important as the other rooms. Montre les salons qui contiennent de nouveaux messages en premier. -Si non activé, la liste des salons sera uniquement trié en fonction de la date du dernier message. +Si non activé, la liste des salons sera uniquement triée en fonction de la date du dernier message. Si activé, les salons qui ont des notifications actives (le petit cercle avec un chiffre dedans) seront affichés en premier. -Les salons que vous avez rendu silencieux seront toujours triés par date du dernier message, car ceux-ci sont considérés comme moins importants. +Les salons que vous avez rendu silencieux seront toujours triés par date du dernier message, puisqu'ils sont considérés comme moins importants. @@ -2440,17 +2440,17 @@ Lorsque désactivé, tous les messages sont envoyés en texte brut. Play animated images only on hover - + Ne jouer les images animées que quand survolées Desktop notifications - Notifier sur le bureau + Notifications sur le bureau Notify about received message when the client is not currently focused. - Notifie des messages reçus lorsque la fenêtre du client n'est pas focalisée. + Notifie des messages reçus lorsque la fenêtre du client n'est pas active. @@ -2477,22 +2477,22 @@ Cela met l'application en évidence dans la barre des tâches. Large Emoji in timeline - Grandes émoticônes dans la discussion + Grands emojis dans la discussion Make font size larger if messages with only a few emojis are displayed. - Augmente la taille de la police lors de l'affichage de messages contenant uniquement quelques emojis. + Augmente la taille de la police des messages contenant uniquement quelques emojis. Send encrypted messages to verified users only - + N'envoyer des messages chiffrés qu'aux utilisateurs vérifiés Requires a user to be verified to send encrypted messages to them. This improves safety but makes E2EE more tedious. - + Requiert qu'un utilisateur soit vérifié pour lui envoyer des messages chiffrés. La sécurité en est améliorée, mais le chiffrement de bout en bout devient plus fastidieux. @@ -2502,27 +2502,27 @@ Cela met l'application en évidence dans la barre des tâches. Automatically replies to key requests from other users, if they are verified, even if that device shouldn't have access to those keys otherwise. - + Répond automatiquement aux requêtes de clefs des autres utilisateurs, s'ils sont vérifiés, même si cet appareil ne devrait pas avoir accès à ces clefs autrement. Online Key Backup - + Sauvegarde des clefs en ligne Download message encryption keys from and upload to the encrypted online key backup. - + Télécharge les clefs de chiffrement de message depuis et envoie vers la sauvegarde chiffrée en ligne de clefs. Enable online key backup - + Activer la sauvegarde de clefs en ligne The Nheko authors recommend not enabling online key backup until symmetric online key backup is available. Enable anyway? - + Les auteurs de Nheko ne recommandent pas d'activer la sauvegarde en ligne de clefs jusqu'à ce que la sauvegarde symétrique en ligne de clefs soit disponible. Activer quand même ? @@ -2542,7 +2542,7 @@ Cela met l'application en évidence dans la barre des tâches. Change the scale factor of the whole user interface. - Agrandit l'interface entière de ce facteur. + Agrandit l'interface entière par ce facteur. @@ -2642,7 +2642,7 @@ Cela met l'application en évidence dans la barre des tâches. Plays media like GIFs or WEBPs only when explicitly hovering over them. - + Joue les images comme les GIFs ou WEBPs uniquement quand la souris est au-dessus. @@ -2652,17 +2652,17 @@ Cela met l'application en évidence dans la barre des tâches. Will prevent text selection in the timeline to make touch scrolling easier. - Empêchera la sélection de texte dans la discussion pour faciliter le défilement tactile. + Empêche la sélection de texte dans la discussion pour faciliter le défilement tactile. Emoji Font Family - Nom de Police Emoji + Nom de police pour émoji Master signing key - Clé de signature de l'utilisateur + Clé de signature maîtresse de l'utilisateur @@ -2677,12 +2677,12 @@ Cela met l'application en évidence dans la barre des tâches. The key to verify other users. If it is cached, verifying a user will verify all their devices. - La clé utilisée pour vérifier d'autres utilisateurs. Si celle-ci est cachée, vérifier un utilisateur vérifiera tous ses appareils. + La clé utilisée pour vérifier d'autres utilisateurs. Si celle-ci est dans le cache, vérifier un utilisateur vérifiera tous ses appareils. Self signing key - Clé d'auto-vérification + Clé d'auto-vérification @@ -2692,12 +2692,12 @@ Cela met l'application en évidence dans la barre des tâches. Backup key - Clé de secours + Clé de secours The key to decrypt online key backups. If it is cached, you can enable online key backup to store encryption keys securely encrypted on the server. - La clé utilisée pour déchiffrer les sauvegardes de clé stockées en ligne. Si celle-ci est cachée, vous pouvez activer la sauvegarde de vos clés en ligne afin d'en conserver une copie chiffrée en toute sécurité sur le serveur. + La clé utilisée pour déchiffrer les sauvegardes de clé stockées en ligne. Si celle-ci est dans le cache, vous pouvez activer la sauvegarde de vos clés en ligne afin d'en conserver une copie chiffrée en toute sécurité sur le serveur. @@ -2712,7 +2712,7 @@ Cela met l'application en évidence dans la barre des tâches. Open Sessions File - Ouvrir fichier de sessions + Ouvrir le fichier de sessions @@ -2733,7 +2733,7 @@ Cela met l'application en évidence dans la barre des tâches. Enter the passphrase to decrypt the file: - Entrez la clé secrète pour déchiffrer le fichier  : + Entrez la phrase de passe pour déchiffrer le fichier : @@ -2744,7 +2744,7 @@ Cela met l'application en évidence dans la barre des tâches. Enter passphrase to encrypt your session keys: - Entrez une clé secrète pour chiffrer vos clés de session  : + Entrez une phrase de passe pour chiffrer vos clés de session : @@ -2859,7 +2859,7 @@ Cela met l'application en évidence dans la barre des tâches. Open Fallback in Browser - Ouvrir la solution de repli dans le navigateur + Ouvrir la solution de secours dans le navigateur @@ -2874,7 +2874,7 @@ Cela met l'application en évidence dans la barre des tâches. Open the fallback, follow the steps and confirm after completing them. - Ouvrez la solution de repli, suivez les étapes et confirmez après les avoir terminées. + Ouvrez la solution de secours, suivez les étapes et confirmez après les avoir terminées. @@ -3026,12 +3026,12 @@ Taille du média : %2 You: %1 - Vous  : %1 + Vous : %1 %1: %2 - %1  : %2 + %1 : %2 From 3b643c9c08279b029226907f5c9db2a822b726c4 Mon Sep 17 00:00:00 2001 From: Joe Donofry Date: Fri, 3 Sep 2021 18:53:31 +0000 Subject: [PATCH 085/232] Macos notarization --- .ci/macos/notarize.sh | 73 +++++++++++++++++++++++++++++++++++++++++++ .gitlab-ci.yml | 32 +++++++++++++++---- 2 files changed, 99 insertions(+), 6 deletions(-) create mode 100755 .ci/macos/notarize.sh diff --git a/.ci/macos/notarize.sh b/.ci/macos/notarize.sh new file mode 100755 index 00000000..ca8646be --- /dev/null +++ b/.ci/macos/notarize.sh @@ -0,0 +1,73 @@ +#!/bin/sh + +set -u + +# Modified version of script found at: +# https://forum.qt.io/topic/96652/how-to-notarize-qt-application-on-macos/18 + +# Add Qt binaries to path +PATH="/usr/local/opt/qt@5/bin/:${PATH}" + +security unlock-keychain -p "${RUNNER_USER_PW}" login.keychain + +( cd build || exit + # macdeployqt does not copy symlinks over. + # this specifically addresses icu4c issues but nothing else. + # We might not even need this any longer... + # ICU_LIB="$(brew --prefix icu4c)/lib" + # export ICU_LIB + # mkdir -p nheko.app/Contents/Frameworks + # find "${ICU_LIB}" -type l -name "*.dylib" -exec cp -a -n {} nheko.app/Contents/Frameworks/ \; || true + + macdeployqt nheko.app -dmg -always-overwrite -qmldir=../resources/qml/ -sign-for-notarization="${APPLE_DEV_IDENTITY}" + + user=$(id -nu) + chown "${user}" nheko.dmg +) + +NOTARIZE_SUBMIT_LOG=$(mktemp -t notarize-submit) +NOTARIZE_STATUS_LOG=$(mktemp -t notarize-status) + +finish() { + rm "$NOTARIZE_SUBMIT_LOG" "$NOTARIZE_STATUS_LOG" +} +trap finish EXIT + +dmgbuild -s .ci/macos/settings.json "Nheko" nheko.dmg +codesign -s "${APPLE_DEV_IDENTITY}" nheko.dmg +user=$(id -nu) +chown "${user}" nheko.dmg + +echo "--> Start Notarization process" +xcrun altool -t osx -f nheko.dmg --primary-bundle-id "io.github.nheko-reborn.nheko" --notarize-app -u "${APPLE_DEV_USER}" -p "${APPLE_DEV_PASS}" > "$NOTARIZE_SUBMIT_LOG" 2>&1 +requestUUID="$(awk -F ' = ' '/RequestUUID/ {print $2}' "$NOTARIZE_SUBMIT_LOG")" + +while sleep 60 && date; do + echo "--> Checking notarization status for ${requestUUID}" + + xcrun altool --notarization-info "${requestUUID}" -u "${APPLE_DEV_USER}" -p "${APPLE_DEV_PASS}" > "$NOTARIZE_STATUS_LOG" 2>&1 + + isSuccess=$(grep "success" "$NOTARIZE_STATUS_LOG") + isFailure=$(grep "invalid" "$NOTARIZE_STATUS_LOG") + + if [ -n "${isSuccess}" ]; then + echo "Notarization done!" + xcrun stapler staple -v nheko.dmg + echo "Stapler done!" + break + fi + if [ -n "${isFailure}" ]; then + echo "Notarization failed" + cat "$NOTARIZE_STATUS_LOG" 1>&2 + return 1 + fi + echo "Notarization not finished yet, sleep 1m then check again..." +done + +VERSION=${CI_COMMIT_SHORT_SHA} + +if [ -n "$VERSION" ]; then + mv nheko.dmg "nheko-${VERSION}.dmg" + mkdir artifacts + cp "nheko-${VERSION}.dmg" artifacts/ +fi \ No newline at end of file diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index cea6be7b..e82e72d6 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -55,7 +55,6 @@ build-macos: #- brew update #- brew reinstall --force python3 #- brew bundle --file=./.ci/macos/Brewfile --force --cleanup - - pip3 install dmgbuild - rm -rf ../.hunter && mv .hunter ../.hunter || true script: - export PATH=/usr/local/opt/qt@5/bin/:${PATH} @@ -72,19 +71,40 @@ build-macos: - cmake --build build after_script: - mv ../.hunter .hunter - - ./.ci/macos/deploy.sh - - ./.ci/upload-nightly-gitlab.sh artifacts/nheko-${CI_COMMIT_SHORT_SHA}.dmg artifacts: paths: - - artifacts/nheko-${CI_COMMIT_SHORT_SHA}.dmg - name: nheko-${CI_COMMIT_SHORT_SHA}-macos - expose_as: 'macos-dmg' + - build/nheko.app + name: nheko-${CI_COMMIT_SHORT_SHA}-macos-app + expose_as: 'macos-app' + public: false cache: key: "${CI_JOB_NAME}" paths: - .hunter/ - "${CCACHE_DIR}" +codesign-macos: + stage: deploy + tags: [macos] + before_script: + - pip3 install dmgbuild + script: + - export PATH=/usr/local/opt/qt@5/bin/:${PATH} + - ./.ci/macos/notarize.sh + after_script: + - ./.ci/upload-nightly-gitlab.sh artifacts/nheko-${CI_COMMIT_SHORT_SHA}.dmg + needs: + - build-macos + rules: + - if: '$CI_COMMIT_BRANCH == "master"' + - if : $CI_COMMIT_TAG + artifacts: + paths: + - artifacts/nheko-${CI_COMMIT_SHORT_SHA}.dmg + name: nheko-${CI_COMMIT_SHORT_SHA}-macos + expose_as: 'macos-dmg' + + build-flatpak-amd64: stage: build image: ubuntu:latest From 2ec6ee304f20bbcd03d7dea31f8fb00614d47155 Mon Sep 17 00:00:00 2001 From: Weblate Date: Fri, 3 Sep 2021 20:11:38 -0400 Subject: [PATCH 086/232] Translated using Weblate (Portuguese (Portugal)) Currently translated at 1.2% (7 of 560 strings) Co-authored-by: Gabriel R Translate-URL: https://weblate.nheko.im/projects/nheko/nheko-master/pt_PT/ Translation: Nheko/nheko --- resources/langs/nheko_pt_PT.ts | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/resources/langs/nheko_pt_PT.ts b/resources/langs/nheko_pt_PT.ts index bf63723f..40767990 100644 --- a/resources/langs/nheko_pt_PT.ts +++ b/resources/langs/nheko_pt_PT.ts @@ -50,7 +50,7 @@ Cancel - + Cancelar @@ -153,13 +153,13 @@ Room %1 created. - + Sala %1 criada. Confirm invite - + Confirmar convite @@ -169,12 +169,12 @@ Failed to invite %1 to %2: %3 - + Falha ao convidar %1 para %2: %3 Confirm kick - + Confirmar expulsão @@ -189,7 +189,7 @@ Confirm ban - + Confirmar banimento @@ -199,7 +199,7 @@ Failed to ban %1 in %2: %3 - + Falha ao banir %1 em %2: %3 From 17ed785c5b3fc72743205bd40b508cdab1c33b4f Mon Sep 17 00:00:00 2001 From: Weblate Date: Sat, 4 Sep 2021 10:40:41 -0400 Subject: [PATCH 087/232] Translated using Weblate (Portuguese (Portugal)) Currently translated at 7.1% (40 of 560 strings) Translated using Weblate (Portuguese (Portugal)) Currently translated at 7.1% (40 of 560 strings) Co-authored-by: Gabriel R Co-authored-by: Tnpod Co-authored-by: Weblate Translate-URL: https://weblate.nheko.im/projects/nheko/nheko-master/pt_PT/ Translation: Nheko/nheko --- resources/langs/nheko_pt_PT.ts | 66 +++++++++++++++++----------------- 1 file changed, 33 insertions(+), 33 deletions(-) diff --git a/resources/langs/nheko_pt_PT.ts b/resources/langs/nheko_pt_PT.ts index 40767990..b120de8c 100644 --- a/resources/langs/nheko_pt_PT.ts +++ b/resources/langs/nheko_pt_PT.ts @@ -6,7 +6,7 @@ Calling... - + A chamar... @@ -17,7 +17,7 @@ You are screen sharing - + Está a partilhar o seu ecrã @@ -40,7 +40,7 @@ Awaiting Confirmation - + A aguardar confirmação @@ -58,17 +58,17 @@ Video Call - + Videochamada Voice Call - + Chamada No microphone found. - + Nenhum microfone encontrado. @@ -76,42 +76,42 @@ Video Call - + Videochamada Voice Call - + Chamada Devices - + Dispositivos Accept - + Aceitar Unknown microphone: %1 - + Microfone desconhecido: %1 Unknown camera: %1 - + Câmara desconhecida: %1 Decline - + Recusar No microphone found. - + Nenhum microfone encontrado. @@ -119,7 +119,7 @@ Entire screen - + Ecrã inteiro @@ -127,28 +127,28 @@ Failed to invite user: %1 - + Falha ao convidar utilizador: %1 Invited user: %1 - + Utilizador convidado: %1 Migrating the cache to the current version failed. This can have different reasons. Please open an issue and try to use an older version in the mean time. Alternatively you can try deleting the cache manually. - + A migração da cache para a versão atual falhou, e existem várias razões possíveis. Por favor abra um problema ("issue") e experimente usar uma versão mais antiga entretanto. Alternativamente, pode tentar apagar a cache manualmente. Confirm join - + Confirmar entrada Do you really want to join %1? - + Tem a certeza que quer entrar em %1? @@ -164,7 +164,7 @@ Do you really want to invite %1 (%2)? - + Tem a certeza que quer convidar %1 (%2)? @@ -179,12 +179,12 @@ Do you really want to kick %1 (%2)? - + Tem a certeza que quer expulsar %1 (%2)? Kicked user: %1 - + Utilizador expulso: %1 @@ -194,7 +194,7 @@ Do you really want to ban %1 (%2)? - + Tem a certeza que quer banir %1 (%2)? @@ -204,12 +204,12 @@ Banned user: %1 - + Utilizador banido: %1 Confirm unban - + Confirmar perdão @@ -229,39 +229,39 @@ Do you really want to start a private chat with %1? - + Tem a certeza que quer começar uma conversa privada com %1? Cache migration failed! - + Falha ao migrar a cache! Incompatible cache version - + Versão da cache incompatível The cache on your disk is newer than this version of Nheko supports. Please update or clear your cache. - + A cache que existe no seu disco é mais recente do que esta versão do Nheko suporta. Por favor atualize-a ou apague-a. Failed to restore OLM account. Please login again. - + Falha ao restaurar a sua conta OLM. Por favor autentique-se novamente. Failed to restore save data. Please login again. - + Falha ao restaurar dados guardados. Por favor, autentique-se novamente. Failed to setup encryption keys. Server response: %1 %2. Please try again later. - + Falha ao estabelecer chaves encriptadas. Resposta do servidor: %1 %2. Tente novamente. From c122915c282cb2f0fdc5bed3645f399e66f50e3c Mon Sep 17 00:00:00 2001 From: tastytea Date: Sat, 4 Sep 2021 14:52:33 +0200 Subject: [PATCH 088/232] Decrease left margins on blockquotes to 1em. It is intentionally impossible to add borders to blockquotes via CSS: . Bug: https://github.com/Nheko-Reborn/nheko/issues/704 --- resources/qml/delegates/TextMessage.qml | 1 + 1 file changed, 1 insertion(+) diff --git a/resources/qml/delegates/TextMessage.qml b/resources/qml/delegates/TextMessage.qml index 0bc45a9c..c37314fd 100644 --- a/resources/qml/delegates/TextMessage.qml +++ b/resources/qml/delegates/TextMessage.qml @@ -29,6 +29,7 @@ MatrixText { border-collapse: collapse; border: 1px solid " + Nheko.colors.text + "; } + blockquote { margin-left: 1em; } " + formatted.replace("
", "
").replace("", "").replace("", "").replace("", "").replace("", "")
     width: parent ? parent.width : undefined

From e7564396fb674ea0826134141f115a6db25e6005 Mon Sep 17 00:00:00 2001
From: Thulinma 
Date: Sun, 5 Sep 2021 18:15:25 +0200
Subject: [PATCH 089/232] Improvements to user profiles: - Set a minimum width
 on the profile window (avatar size + margins) - Made avatar editing a
 separate button, so you can zoom in on your own avatars - Added hover text to
 avatar/displayname change buttons, which clarify where they will apply for
 global/room-specific profiles - Added display of room name for room-specific
 profiles, with hover text that explains what that means. - Added way to open
 global profile for users from their room-specific profiles (globe button next
 to room name)

---
 resources/qml/UserProfile.qml | 58 ++++++++++++++++++++++++++++++-----
 src/ui/UserProfile.cpp        | 10 +++++-
 src/ui/UserProfile.h          |  1 +
 3 files changed, 60 insertions(+), 9 deletions(-)

diff --git a/resources/qml/UserProfile.qml b/resources/qml/UserProfile.qml
index 95fd2bbe..b4a85c52 100644
--- a/resources/qml/UserProfile.qml
+++ b/resources/qml/UserProfile.qml
@@ -20,6 +20,7 @@ ApplicationWindow {
 
     height: 650
     width: 420
+    minimumWidth: 150
     minimumHeight: 420
     palette: Nheko.colors
     color: Nheko.colors.window
@@ -45,9 +46,23 @@ ApplicationWindow {
             height: 130
             width: 130
             displayName: profile.displayName
+            id: displayAvatar
             userid: profile.userid
             Layout.alignment: Qt.AlignHCenter
-            onClicked: profile.isSelf ? profile.changeAvatar() : TimelineManager.openImageOverlay(profile.avatarUrl, "")
+            onClicked: TimelineManager.openImageOverlay(profile.avatarUrl, "")
+
+            ImageButton {
+                hoverEnabled: true
+                ToolTip.visible: hovered
+                ToolTip.text: profile.isGlobalUserProfile ? qsTr("Change avatar globally.") : qsTr("Change avatar. Will only apply to this room.")
+                anchors.left: displayAvatar.left
+                anchors.top: displayAvatar.top
+                anchors.leftMargin: Nheko.paddingMedium
+                anchors.topMargin: Nheko.paddingMedium
+                visible: profile.isSelf
+                image: ":/icons/icons/ui/edit.png"
+                onClicked: profile.changeAvatar()
+            }
         }
 
         Spinner {
@@ -113,9 +128,12 @@ ApplicationWindow {
 
             ImageButton {
                 visible: profile.isSelf
-                anchors.leftMargin: 5
+                anchors.leftMargin: Nheko.paddingSmall
                 anchors.left: displayUsername.right
                 anchors.verticalCenter: displayUsername.verticalCenter
+                hoverEnabled: true
+                ToolTip.visible: hovered
+                ToolTip.text: profile.isGlobalUserProfile ? qsTr("Change display name globally.") : qsTr("Change display name. Will only apply to this room.")
                 image: displayUsername.isUsernameEditingAllowed ? ":/icons/icons/ui/checkmark.png" : ":/icons/icons/ui/edit.png"
                 onClicked: {
                     if (displayUsername.isUsernameEditingAllowed) {
@@ -137,6 +155,30 @@ ApplicationWindow {
             Layout.alignment: Qt.AlignHCenter
         }
 
+        MatrixText {
+            id: displayRoomname
+            text: qsTr("Room: %1").arg(profile.room.roomName)
+            Layout.alignment: Qt.AlignHCenter
+            visible: !profile.isGlobalUserProfile
+            ToolTip.text: qsTr("This is a room-specific profile. The user's name and avatar may be different from their global versions.")
+            ToolTip.visible: ma.hovered
+            HoverHandler {
+                id: ma
+            }
+
+            ImageButton {
+                anchors.leftMargin: Nheko.paddingSmall
+                anchors.left: displayRoomname.right
+                anchors.verticalCenter: displayRoomname.verticalCenter
+                image: ":/icons/icons/ui/world.png"
+                hoverEnabled: true
+                ToolTip.visible: hovered
+                ToolTip.text: qsTr("Open the global profile for this user.")
+                onClicked: profile.openGlobalProfile()
+                visible: !profile.isGlobalUserProfile
+            }
+        }
+
         Button {
             id: verifyUserButton
 
@@ -163,7 +205,7 @@ ApplicationWindow {
             //         right: 5
             //     }
             //     ToolTip.visible: hovered
-            //     ToolTip.text: qsTr("Ignore messages from this user")
+            //     ToolTip.text: qsTr("Ignore messages from this user.")
             //     onClicked : {
             //         profile.ignoreUser()
             //     }
@@ -176,7 +218,7 @@ ApplicationWindow {
                 image: ":/icons/icons/ui/black-bubble-speech.png"
                 hoverEnabled: true
                 ToolTip.visible: hovered
-                ToolTip.text: qsTr("Start a private chat")
+                ToolTip.text: qsTr("Start a private chat.")
                 onClicked: profile.startChat()
             }
 
@@ -184,18 +226,18 @@ ApplicationWindow {
                 image: ":/icons/icons/ui/round-remove-button.png"
                 hoverEnabled: true
                 ToolTip.visible: hovered
-                ToolTip.text: qsTr("Kick the user")
+                ToolTip.text: qsTr("Kick the user.")
                 onClicked: profile.kickUser()
-                visible: profile.room ? profile.room.permissions.canKick() : false
+                visible: !profile.isGlobalUserProfile && profile.room.permissions.canKick()
             }
 
             ImageButton {
                 image: ":/icons/icons/ui/do-not-disturb-rounded-sign.png"
                 hoverEnabled: true
                 ToolTip.visible: hovered
-                ToolTip.text: qsTr("Ban the user")
+                ToolTip.text: qsTr("Ban the user.")
                 onClicked: profile.banUser()
-                visible: profile.room ? profile.room.permissions.canBan() : false
+                visible: !profile.isGlobalUserProfile && profile.room.permissions.canBan()
             }
 
         }
diff --git a/src/ui/UserProfile.cpp b/src/ui/UserProfile.cpp
index 3d9c4b6a..a3f42671 100644
--- a/src/ui/UserProfile.cpp
+++ b/src/ui/UserProfile.cpp
@@ -409,7 +409,8 @@ UserProfile::getGlobalProfileData()
           userid_.toStdString(),
           [this](const mtx::responses::Profile &res, mtx::http::RequestErr err) {
                   if (err) {
-                          nhlog::net()->warn("failed to retrieve own profile info");
+                          nhlog::net()->warn("failed to retrieve profile info for {}",
+                                             userid_.toStdString());
                           return;
                   }
 
@@ -418,3 +419,10 @@ UserProfile::getGlobalProfileData()
                   emit avatarUrlChanged();
           });
 }
+
+void
+UserProfile::openGlobalProfile()
+{
+        UserProfile *userProfile = new UserProfile("", userid_, manager, model);
+        emit manager->openProfile(userProfile);
+}
diff --git a/src/ui/UserProfile.h b/src/ui/UserProfile.h
index 721d7230..fd8772d5 100644
--- a/src/ui/UserProfile.h
+++ b/src/ui/UserProfile.h
@@ -125,6 +125,7 @@ public:
         Q_INVOKABLE void startChat();
         Q_INVOKABLE void changeUsername(QString username);
         Q_INVOKABLE void changeAvatar();
+        Q_INVOKABLE void openGlobalProfile();
 
 signals:
         void userStatusChanged();

From 25255796100632a9a65cf1c2895dfa3f79a5e291 Mon Sep 17 00:00:00 2001
From: Thulinma 
Date: Sun, 5 Sep 2021 23:00:29 +0200
Subject: [PATCH 090/232] Fixed duplicate messages appearing in timeline if
 server sent them

---
 src/Cache.cpp | 59 +++++++++++++++++++++++++++++++--------------------
 1 file changed, 36 insertions(+), 23 deletions(-)

diff --git a/src/Cache.cpp b/src/Cache.cpp
index 5842f536..5b43192c 100644
--- a/src/Cache.cpp
+++ b/src/Cache.cpp
@@ -2999,24 +2999,31 @@ Cache::saveTimelineMessages(lmdb::txn &txn,
                         eventsDb.put(txn, redaction->redacts, event.dump());
                         eventsDb.put(txn, redaction->event_id, json(*redaction).dump());
                 } else {
-                        eventsDb.put(txn, event_id, event.dump());
-
-                        ++index;
-
                         first = false;
 
-                        nhlog::db()->debug("saving '{}'", orderEntry.dump());
+                        // This check protects against duplicates in the timeline. If the event_id
+                        // is already in the DB, we skip putting it (again) in ordered DBs, and only
+                        // update the event itself and its relations.
+                        std::string_view unused_read;
+                        if (!eventsDb.get(txn, event_id, unused_read)) {
+                                ++index;
 
-                        cursor.put(lmdb::to_sv(index), orderEntry.dump(), MDB_APPEND);
-                        evToOrderDb.put(txn, event_id, lmdb::to_sv(index));
+                                nhlog::db()->debug("saving '{}'", orderEntry.dump());
 
-                        // TODO(Nico): Allow blacklisting more event types in UI
-                        if (!isHiddenEvent(txn, e, room_id)) {
-                                ++msgIndex;
-                                msgCursor.put(lmdb::to_sv(msgIndex), event_id, MDB_APPEND);
+                                cursor.put(lmdb::to_sv(index), orderEntry.dump(), MDB_APPEND);
+                                evToOrderDb.put(txn, event_id, lmdb::to_sv(index));
 
-                                msg2orderDb.put(txn, event_id, lmdb::to_sv(msgIndex));
+                                // TODO(Nico): Allow blacklisting more event types in UI
+                                if (!isHiddenEvent(txn, e, room_id)) {
+                                        ++msgIndex;
+                                        msgCursor.put(lmdb::to_sv(msgIndex), event_id, MDB_APPEND);
+
+                                        msg2orderDb.put(txn, event_id, lmdb::to_sv(msgIndex));
+                                }
+                        } else {
+                                nhlog::db()->warn("duplicate event '{}'", orderEntry.dump());
                         }
+                        eventsDb.put(txn, event_id, event.dump());
 
                         auto relations = mtx::accessors::relations(e);
                         if (!relations.relations.empty()) {
@@ -3078,23 +3085,29 @@ Cache::saveOldMessages(const std::string &room_id, const mtx::responses::Message
                 auto event                = mtx::accessors::serialize_event(e);
                 event_id_val              = event["event_id"].get();
                 std::string_view event_id = event_id_val;
-                eventsDb.put(txn, event_id, event.dump());
 
-                --index;
+                // This check protects against duplicates in the timeline. If the event_id is
+                // already in the DB, we skip putting it (again) in ordered DBs, and only update the
+                // event itself and its relations.
+                std::string_view unused_read;
+                if (!eventsDb.get(txn, event_id, unused_read)) {
+                        --index;
 
-                json orderEntry        = json::object();
-                orderEntry["event_id"] = event_id_val;
+                        json orderEntry        = json::object();
+                        orderEntry["event_id"] = event_id_val;
 
-                orderDb.put(txn, lmdb::to_sv(index), orderEntry.dump());
-                evToOrderDb.put(txn, event_id, lmdb::to_sv(index));
+                        orderDb.put(txn, lmdb::to_sv(index), orderEntry.dump());
+                        evToOrderDb.put(txn, event_id, lmdb::to_sv(index));
 
-                // TODO(Nico): Allow blacklisting more event types in UI
-                if (!isHiddenEvent(txn, e, room_id)) {
-                        --msgIndex;
-                        order2msgDb.put(txn, lmdb::to_sv(msgIndex), event_id);
+                        // TODO(Nico): Allow blacklisting more event types in UI
+                        if (!isHiddenEvent(txn, e, room_id)) {
+                                --msgIndex;
+                                order2msgDb.put(txn, lmdb::to_sv(msgIndex), event_id);
 
-                        msg2orderDb.put(txn, event_id, lmdb::to_sv(msgIndex));
+                                msg2orderDb.put(txn, event_id, lmdb::to_sv(msgIndex));
+                        }
                 }
+                eventsDb.put(txn, event_id, event.dump());
 
                 auto relations = mtx::accessors::relations(e);
                 if (!relations.relations.empty()) {

From e035d1407a80efc54ae46f9662dcc4fde9196df4 Mon Sep 17 00:00:00 2001
From: Nicolas Werner 
Date: Sun, 5 Sep 2021 23:15:44 +0200
Subject: [PATCH 091/232] Conduit does not send count, if it didn't change

---
 src/ChatPage.cpp | 45 ++++++++++++++++++++++-----------------------
 1 file changed, 22 insertions(+), 23 deletions(-)

diff --git a/src/ChatPage.cpp b/src/ChatPage.cpp
index 80d06ae2..437bd658 100644
--- a/src/ChatPage.cpp
+++ b/src/ChatPage.cpp
@@ -939,33 +939,32 @@ ChatPage::currentPresence() const
 void
 ChatPage::ensureOneTimeKeyCount(const std::map &counts)
 {
-        uint16_t count = 0;
-        if (auto c = counts.find(mtx::crypto::SIGNED_CURVE25519); c != counts.end())
-                count = c->second;
+        if (auto count = counts.find(mtx::crypto::SIGNED_CURVE25519); c != counts.end()) {
+                if (count < MAX_ONETIME_KEYS) {
+                        const int nkeys = MAX_ONETIME_KEYS - count;
 
-        if (count < MAX_ONETIME_KEYS) {
-                const int nkeys = MAX_ONETIME_KEYS - count;
+                        nhlog::crypto()->info(
+                          "uploading {} {} keys", nkeys, mtx::crypto::SIGNED_CURVE25519);
+                        olm::client()->generate_one_time_keys(nkeys);
 
-                nhlog::crypto()->info(
-                  "uploading {} {} keys", nkeys, mtx::crypto::SIGNED_CURVE25519);
-                olm::client()->generate_one_time_keys(nkeys);
+                        http::client()->upload_keys(
+                          olm::client()->create_upload_keys_request(),
+                          [](const mtx::responses::UploadKeys &, mtx::http::RequestErr err) {
+                                  if (err) {
+                                          nhlog::crypto()->warn(
+                                            "failed to update one-time keys: {} {} {}",
+                                            err->matrix_error.error,
+                                            static_cast(err->status_code),
+                                            static_cast(err->error_code));
 
-                http::client()->upload_keys(
-                  olm::client()->create_upload_keys_request(),
-                  [](const mtx::responses::UploadKeys &, mtx::http::RequestErr err) {
-                          if (err) {
-                                  nhlog::crypto()->warn("failed to update one-time keys: {} {} {}",
-                                                        err->matrix_error.error,
-                                                        static_cast(err->status_code),
-                                                        static_cast(err->error_code));
+                                          if (err->status_code < 400 || err->status_code >= 500)
+                                                  return;
+                                  }
 
-                                  if (err->status_code < 400 || err->status_code >= 500)
-                                          return;
-                          }
-
-                          // mark as published anyway, otherwise we may end up in a loop.
-                          olm::mark_keys_as_published();
-                  });
+                                  // mark as published anyway, otherwise we may end up in a loop.
+                                  olm::mark_keys_as_published();
+                          });
+                }
         }
 }
 

From 70e20f5d1005bf77ac3ce2e9d04a8699a7a0819f Mon Sep 17 00:00:00 2001
From: Nicolas Werner 
Date: Mon, 6 Sep 2021 00:07:14 +0200
Subject: [PATCH 092/232] Fix key count updates on conduit

---
 src/ChatPage.cpp | 43 ++++++++++++++++++++++++++++++++++++++++---
 src/ChatPage.h   |  1 +
 2 files changed, 41 insertions(+), 3 deletions(-)

diff --git a/src/ChatPage.cpp b/src/ChatPage.cpp
index 437bd658..5d2117cc 100644
--- a/src/ChatPage.cpp
+++ b/src/ChatPage.cpp
@@ -434,6 +434,7 @@ ChatPage::loadStateFromCache()
 
         getProfileInfo();
         getBackupVersion();
+        verifyOneTimeKeyCountAfterStartup();
 
         emit contentLoaded();
 
@@ -936,12 +937,48 @@ ChatPage::currentPresence() const
         }
 }
 
+void
+ChatPage::verifyOneTimeKeyCountAfterStartup()
+{
+        http::client()->upload_keys(
+          olm::client()->create_upload_keys_request(),
+          [this](const mtx::responses::UploadKeys &res, mtx::http::RequestErr err) {
+                  if (err) {
+                          nhlog::crypto()->warn("failed to update one-time keys: {} {} {}",
+                                                err->matrix_error.error,
+                                                static_cast(err->status_code),
+                                                static_cast(err->error_code));
+
+                          if (err->status_code < 400 || err->status_code >= 500)
+                                  return;
+                  }
+
+                  std::map key_counts;
+                  auto count = 0;
+                  if (auto c = res.one_time_key_counts.find(mtx::crypto::SIGNED_CURVE25519);
+                      c == res.one_time_key_counts.end()) {
+                          key_counts[mtx::crypto::SIGNED_CURVE25519] = 0;
+                  } else {
+                          key_counts[mtx::crypto::SIGNED_CURVE25519] = c->second;
+                          count                                      = c->second;
+                  }
+
+                  nhlog::crypto()->info(
+                    "Fetched server key count {} {}", count, mtx::crypto::SIGNED_CURVE25519);
+
+                  ensureOneTimeKeyCount(key_counts);
+          });
+}
+
 void
 ChatPage::ensureOneTimeKeyCount(const std::map &counts)
 {
-        if (auto count = counts.find(mtx::crypto::SIGNED_CURVE25519); c != counts.end()) {
-                if (count < MAX_ONETIME_KEYS) {
-                        const int nkeys = MAX_ONETIME_KEYS - count;
+        if (auto count = counts.find(mtx::crypto::SIGNED_CURVE25519); count != counts.end()) {
+                nhlog::crypto()->debug(
+                  "Updated server key count {} {}", count->second, mtx::crypto::SIGNED_CURVE25519);
+
+                if (count->second < MAX_ONETIME_KEYS) {
+                        const int nkeys = MAX_ONETIME_KEYS - count->second;
 
                         nhlog::crypto()->info(
                           "uploading {} {} keys", nkeys, mtx::crypto::SIGNED_CURVE25519);
diff --git a/src/ChatPage.h b/src/ChatPage.h
index d79bee46..9cbf2a03 100644
--- a/src/ChatPage.h
+++ b/src/ChatPage.h
@@ -181,6 +181,7 @@ private:
         void startInitialSync();
         void tryInitialSync();
         void trySync();
+        void verifyOneTimeKeyCountAfterStartup();
         void ensureOneTimeKeyCount(const std::map &counts);
         void getProfileInfo();
         void getBackupVersion();

From 6490ee3a34c914a4c59125d947afd5b7fc01810d Mon Sep 17 00:00:00 2001
From: Nicolas Werner 
Date: Mon, 6 Sep 2021 00:32:09 +0200
Subject: [PATCH 093/232] Add workaround for broken key counts

---
 src/ChatPage.cpp | 17 +++++++++++++++++
 1 file changed, 17 insertions(+)

diff --git a/src/ChatPage.cpp b/src/ChatPage.cpp
index 5d2117cc..c5c27964 100644
--- a/src/ChatPage.cpp
+++ b/src/ChatPage.cpp
@@ -1001,6 +1001,23 @@ ChatPage::ensureOneTimeKeyCount(const std::map &counts)
                                   // mark as published anyway, otherwise we may end up in a loop.
                                   olm::mark_keys_as_published();
                           });
+                } else if (count->second > 2 * MAX_ONETIME_KEYS) {
+                        nhlog::crypto()->warn("too many one-time keys, deleting 1");
+                        mtx::requests::ClaimKeys req;
+                        req.one_time_keys[http::client()->user_id().to_string()]
+                                         [http::client()->device_id()] =
+                          std::string(mtx::crypto::SIGNED_CURVE25519);
+                        http::client()->claim_keys(
+                          req, [](const mtx::responses::ClaimKeys &, mtx::http::RequestErr err) {
+                                  if (err)
+                                          nhlog::crypto()->warn(
+                                            "failed to clear 1 one-time key: {} {} {}",
+                                            err->matrix_error.error,
+                                            static_cast(err->status_code),
+                                            static_cast(err->error_code));
+                                  else
+                                          nhlog::crypto()->info("cleared 1 one-time key");
+                          });
                 }
         }
 }

From 6d35ef2f2fd04ef2f5ae15971f63ec632f1badbe Mon Sep 17 00:00:00 2001
From: Weblate 
Date: Sun, 5 Sep 2021 18:41:01 -0400
Subject: [PATCH 094/232] Translated using Weblate (Portuguese (Portugal))

Currently translated at 7.1% (40 of 560 strings)

Co-authored-by: Tnpod 
Translate-URL: https://weblate.nheko.im/projects/nheko/nheko-master/pt_PT/
Translation: Nheko/nheko
---
 resources/langs/nheko_pt_PT.ts | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/resources/langs/nheko_pt_PT.ts b/resources/langs/nheko_pt_PT.ts
index b120de8c..00521ca2 100644
--- a/resources/langs/nheko_pt_PT.ts
+++ b/resources/langs/nheko_pt_PT.ts
@@ -261,7 +261,7 @@
     
         
         Failed to setup encryption keys. Server response: %1 %2. Please try again later.
-        Falha ao estabelecer chaves encriptadas. Resposta do servidor: %1 %2. Tente novamente.
+        Falha ao estabelecer chaves encriptadas. Resposta do servidor: %1 %2. Tente novamente mais tarde.
     
     
         

From 80fa3e801f36566c8127c43ecdbbb9410b3bada2 Mon Sep 17 00:00:00 2001
From: Nicolas Werner 
Date: Mon, 6 Sep 2021 01:41:04 +0200
Subject: [PATCH 095/232] Fix duplicate check possibly leaving large gaps if
 initial state was not in timeline

---
 src/Cache.cpp | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/src/Cache.cpp b/src/Cache.cpp
index 5b43192c..d009c0d3 100644
--- a/src/Cache.cpp
+++ b/src/Cache.cpp
@@ -3005,7 +3005,7 @@ Cache::saveTimelineMessages(lmdb::txn &txn,
                         // is already in the DB, we skip putting it (again) in ordered DBs, and only
                         // update the event itself and its relations.
                         std::string_view unused_read;
-                        if (!eventsDb.get(txn, event_id, unused_read)) {
+                        if (!evToOrderDb.get(txn, event_id, unused_read)) {
                                 ++index;
 
                                 nhlog::db()->debug("saving '{}'", orderEntry.dump());
@@ -3090,7 +3090,7 @@ Cache::saveOldMessages(const std::string &room_id, const mtx::responses::Message
                 // already in the DB, we skip putting it (again) in ordered DBs, and only update the
                 // event itself and its relations.
                 std::string_view unused_read;
-                if (!eventsDb.get(txn, event_id, unused_read)) {
+                if (!evToOrderDb.get(txn, event_id, unused_read)) {
                         --index;
 
                         json orderEntry        = json::object();

From 54723b061959c2c8ceb699ede4ce3f64d87c4181 Mon Sep 17 00:00:00 2001
From: Weblate 
Date: Mon, 6 Sep 2021 22:28:54 -0400
Subject: [PATCH 096/232] Translated using Weblate (Portuguese (Portugal))

Currently translated at 15.7% (88 of 560 strings)

Co-authored-by: Tnpod 
Translate-URL: https://weblate.nheko.im/projects/nheko/nheko-master/pt_PT/
Translation: Nheko/nheko
---
 resources/langs/nheko_pt_PT.ts | 96 +++++++++++++++++-----------------
 1 file changed, 48 insertions(+), 48 deletions(-)

diff --git a/resources/langs/nheko_pt_PT.ts b/resources/langs/nheko_pt_PT.ts
index 00521ca2..39a88da9 100644
--- a/resources/langs/nheko_pt_PT.ts
+++ b/resources/langs/nheko_pt_PT.ts
@@ -12,7 +12,7 @@
         
         
         Connecting...
-        
+        A ligar...
     
     
         
@@ -32,7 +32,7 @@
     
         
         Mute Mic
-        
+        Silenciar microfone
     
 
 
@@ -45,7 +45,7 @@
     
         
         Waiting for other side to complete verification.
-        
+        A aguardar que o outro lado complete a verificação.
     
     
         
@@ -214,17 +214,17 @@
     
         
         Do you really want to unban %1 (%2)?
-        
+        Tem a certeza que quer perdoar %1 (%2)?
     
     
         
         Failed to unban %1 in %2: %3
-        
+        Falha ao perdoar %1 em %2: %3
     
     
         
         Unbanned user: %1
-        
+        Utilizador perdoado: %1
     
     
         
@@ -267,37 +267,37 @@
         
         
         Please try to login again: %1
-        
+        Por favor, tente autenticar-se novamente: %1
     
     
         
         Failed to join room: %1
-        
+        Falha ao entrar em sala: %1
     
     
         
         You joined the room
-        
+        Entrou na sala
     
     
         
         Failed to remove invite: %1
-        
+        Falha ao remover convite: %1
     
     
         
         Room creation failed: %1
-        
+        Falha ao criar sala: %1
     
     
         
         Failed to leave room: %1
-        
+        Falha ao sair da sala: %1
     
     
         
         Failed to kick %1 from %2: %3
-        
+        Falha ao expulsar %1 de %2: %3
     
 
 
@@ -305,7 +305,7 @@
     
         
         Hide rooms with this tag or from this space by default.
-        
+        Ocultar salas com esta etiqueta ou  pertencentes a este espaço por defeito.
     
 
 
@@ -313,42 +313,42 @@
     
         
         All rooms
-        
+        Todas as salas
     
     
         
         Shows all rooms without filtering.
-        
+        Mostra todas as salas sem filtros.
     
     
         
         Favourites
-        
+        Favoritos
     
     
         
         Rooms you have favourited.
-        
+        Salas favoritas.
     
     
         
         Low Priority
-        
+        Prioridade baixa
     
     
         
         Rooms with low priority.
-        
+        Salas com prioridade baixa.
     
     
         
         Server Notices
-        
+        Avisos do servidor
     
     
         
         Messages from your server or administrator.
-        
+        Mensagens do seu servidor ou administrador.
     
 
 
@@ -356,27 +356,27 @@
     
         
         Decrypt secrets
-        
+        Desencriptar segredos
     
     
         
         Enter your recovery key or passphrase to decrypt your secrets:
-        
+        Insira a sua chave de recuperação ou palavra-passe para desencriptar os seus segredos:
     
     
         
         Enter your recovery key or passphrase called %1 to decrypt your secrets:
-        
+        Insira a sua chave de recuperação ou palavra-passe chamada %1 para desencriptar os seus segredos:
     
     
         
         Decryption failed
-        
+        Falha ao desencriptar
     
     
         
         Failed to decrypt secrets with the provided recovery key or passphrase
-        
+        Falha ao desencriptada segredos com a chave ou palavra-passe dada
     
 
 
@@ -384,22 +384,22 @@
     
         
         Verification Code
-        
+        Código de verificação
     
     
         
         Please verify the following digits. You should see the same numbers on both sides. If they differ, please press 'They do not match!' to abort verification!
-        
+        Por favor verifique os seguintes dígitos.  Deve ver os mesmos em ambos os lados. Se forem diferentes, carregue em "Não coincidem!" para abortar a verificação!
     
     
         
         They do not match!
-        
+        Não coincidem!
     
     
         
         They match!
-        
+        Coincidem!
     
 
 
@@ -407,22 +407,22 @@
     
         
         Apply
-        
+        Aplicar
     
     
         
         Cancel
-        
+        Cancelar
     
     
         
         Name
-        
+        Nome
     
     
         
         Topic
-        
+        Tópico
     
 
 
@@ -430,47 +430,47 @@
     
         
         Search
-        
+        Procurar
     
     
         
         People
-        
+        Pessoas
     
     
         
         Nature
-        
+        Natureza
     
     
         
         Food
-        
+        Comida
     
     
         
         Activity
-        
+        Actividades
     
     
         
         Travel
-        
+        Viagem
     
     
         
         Objects
-        
+        Objetos
     
     
         
         Symbols
-        
+        Símbolos
     
     
         
         Flags
-        
+        Bandeiras
     
 
 
@@ -478,22 +478,22 @@
     
         
         Verification Code
-        
+        Código de verificação
     
     
         
         Please verify the following emoji. You should see the same emoji on both sides. If they differ, please press 'They do not match!' to abort verification!
-        
+        Por favor verifique os seguintes emojis. Deve ver os mesmos em ambos os lados. Se não coincidirem, carregue em "Não coincidem!" para abortar a verificação!
     
     
         
         They do not match!
-        
+        Não coincidem!
     
     
         
         They match!
-        
+        Coincidem!
     
 
 

From a39cb537ae157c8f84f5227049df6b5ace409152 Mon Sep 17 00:00:00 2001
From: Thulinma 
Date: Tue, 7 Sep 2021 02:34:32 +0200
Subject: [PATCH 097/232] More profile improvements: - Now scrolls entire
 profile instead of only device list, improving the experience on smaller
 screens - Fixed centering of room name - Allow profile to be sized smaller to
 match the new scrolling behavior - Silenced warning about room being null for
 global profiles - Matrix URLs now open global profiles instead of
 room-specific profiles if the user is not in the currently opened room -
 Opening global profile from room specific profile now uses
 openGlobalUserProfile function instead of reinventing the wheel

---
 resources/qml/UserProfile.qml | 518 +++++++++++++++++-----------------
 src/ChatPage.cpp              |   7 +-
 src/ui/UserProfile.cpp        |   3 +-
 3 files changed, 273 insertions(+), 255 deletions(-)

diff --git a/resources/qml/UserProfile.qml b/resources/qml/UserProfile.qml
index b4a85c52..f57a9441 100644
--- a/resources/qml/UserProfile.qml
+++ b/resources/qml/UserProfile.qml
@@ -21,7 +21,7 @@ ApplicationWindow {
     height: 650
     width: 420
     minimumWidth: 150
-    minimumHeight: 420
+    minimumHeight: 150
     palette: Nheko.colors
     color: Nheko.colors.window
     title: profile.isGlobalUserProfile ? qsTr("Global User Profile") : qsTr("Room User Profile")
@@ -34,279 +34,293 @@ ApplicationWindow {
         onActivated: userProfileDialog.close()
     }
 
-    ColumnLayout {
-        id: contentL
 
+    ListView {
+
+        ScrollHelper {
+            flickable: parent
+            anchors.fill: parent
+            enabled: !Settings.mobileMode
+        }
+
+        header: ColumnLayout {
+            id: contentL
+            width: devicelist.width
+
+            spacing: 10
+
+            Avatar {
+                url: profile.avatarUrl.replace("mxc://", "image://MxcImage/")
+                height: 130
+                width: 130
+                displayName: profile.displayName
+                id: displayAvatar
+                userid: profile.userid
+                Layout.alignment: Qt.AlignHCenter
+                onClicked: TimelineManager.openImageOverlay(profile.avatarUrl, "")
+
+                ImageButton {
+                    hoverEnabled: true
+                    ToolTip.visible: hovered
+                    ToolTip.text: profile.isGlobalUserProfile ? qsTr("Change avatar globally.") : qsTr("Change avatar. Will only apply to this room.")
+                    anchors.left: displayAvatar.left
+                    anchors.top: displayAvatar.top
+                    anchors.leftMargin: Nheko.paddingMedium
+                    anchors.topMargin: Nheko.paddingMedium
+                    visible: profile.isSelf
+                    image: ":/icons/icons/ui/edit.png"
+                    onClicked: profile.changeAvatar()
+                }
+            }
+
+            Spinner {
+                Layout.alignment: Qt.AlignHCenter
+                running: profile.isLoading
+                visible: profile.isLoading
+                foreground: Nheko.colors.mid
+            }
+
+            Text {
+                id: errorText
+
+                color: "red"
+                visible: opacity > 0
+                opacity: 0
+                Layout.alignment: Qt.AlignHCenter
+            }
+
+            SequentialAnimation {
+                id: hideErrorAnimation
+
+                running: false
+
+                PauseAnimation {
+                    duration: 4000
+                }
+
+                NumberAnimation {
+                    target: errorText
+                    property: 'opacity'
+                    to: 0
+                    duration: 1000
+                }
+
+            }
+
+            Connections {
+                function onDisplayError(errorMessage) {
+                    errorText.text = errorMessage;
+                    errorText.opacity = 1;
+                    hideErrorAnimation.restart();
+                }
+
+                target: profile
+            }
+
+            TextInput {
+                id: displayUsername
+
+                property bool isUsernameEditingAllowed
+
+                readOnly: !isUsernameEditingAllowed
+                text: profile.displayName
+                font.pixelSize: 20
+                color: TimelineManager.userColor(profile.userid, Nheko.colors.window)
+                font.bold: true
+                Layout.alignment: Qt.AlignHCenter
+                selectByMouse: true
+                onAccepted: {
+                    profile.changeUsername(displayUsername.text);
+                    displayUsername.isUsernameEditingAllowed = false;
+                }
+
+                ImageButton {
+                    visible: profile.isSelf
+                    anchors.leftMargin: Nheko.paddingSmall
+                    anchors.left: displayUsername.right
+                    anchors.verticalCenter: displayUsername.verticalCenter
+                    hoverEnabled: true
+                    ToolTip.visible: hovered
+                    ToolTip.text: profile.isGlobalUserProfile ? qsTr("Change display name globally.") : qsTr("Change display name. Will only apply to this room.")
+                    image: displayUsername.isUsernameEditingAllowed ? ":/icons/icons/ui/checkmark.png" : ":/icons/icons/ui/edit.png"
+                    onClicked: {
+                        if (displayUsername.isUsernameEditingAllowed) {
+                            profile.changeUsername(displayUsername.text);
+                            displayUsername.isUsernameEditingAllowed = false;
+                        } else {
+                            displayUsername.isUsernameEditingAllowed = true;
+                            displayUsername.focus = true;
+                            displayUsername.selectAll();
+                        }
+                    }
+                }
+
+            }
+
+            MatrixText {
+                text: profile.userid
+                Layout.alignment: Qt.AlignHCenter
+            }
+
+
+            RowLayout {
+                visible: !profile.isGlobalUserProfile
+                Layout.alignment: Qt.AlignHCenter
+                spacing: Nheko.paddingSmall
+                MatrixText {
+                    id: displayRoomname
+                    text: qsTr("Room: %1").arg(profile.room?profile.room.roomName:"")
+                    ToolTip.text: qsTr("This is a room-specific profile. The user's name and avatar may be different from their global versions.")
+                    ToolTip.visible: ma.hovered
+                    HoverHandler {
+                        id: ma
+                    }
+                }
+
+                ImageButton {
+                    image: ":/icons/icons/ui/world.png"
+                    hoverEnabled: true
+                    ToolTip.visible: hovered
+                    ToolTip.text: qsTr("Open the global profile for this user.")
+                    onClicked: profile.openGlobalProfile()
+                }
+            }
+
+            Button {
+                id: verifyUserButton
+
+                text: qsTr("Verify")
+                Layout.alignment: Qt.AlignHCenter
+                enabled: profile.userVerified != Crypto.Verified
+                visible: profile.userVerified != Crypto.Verified && !profile.isSelf && profile.userVerificationEnabled
+                onClicked: profile.verify()
+            }
+
+            Image {
+                Layout.preferredHeight: 16
+                Layout.preferredWidth: 16
+                source: "image://colorimage/:/icons/icons/ui/lock.png?" + ((profile.userVerified == Crypto.Verified) ? "green" : Nheko.colors.buttonText)
+                visible: profile.userVerified != Crypto.Unverified
+                Layout.alignment: Qt.AlignHCenter
+            }
+
+            RowLayout {
+                // ImageButton{
+                //     image:":/icons/icons/ui/volume-off-indicator.png"
+                //     Layout.margins: {
+                //         left: 5
+                //         right: 5
+                //     }
+                //     ToolTip.visible: hovered
+                //     ToolTip.text: qsTr("Ignore messages from this user.")
+                //     onClicked : {
+                //         profile.ignoreUser()
+                //     }
+                // }
+
+                Layout.alignment: Qt.AlignHCenter
+                Layout.bottomMargin: 10
+                spacing: Nheko.paddingSmall
+
+                ImageButton {
+                    image: ":/icons/icons/ui/black-bubble-speech.png"
+                    hoverEnabled: true
+                    ToolTip.visible: hovered
+                    ToolTip.text: qsTr("Start a private chat.")
+                    onClicked: profile.startChat()
+                }
+
+                ImageButton {
+                    image: ":/icons/icons/ui/round-remove-button.png"
+                    hoverEnabled: true
+                    ToolTip.visible: hovered
+                    ToolTip.text: qsTr("Kick the user.")
+                    onClicked: profile.kickUser()
+                    visible: !profile.isGlobalUserProfile && profile.room.permissions.canKick()
+                }
+
+                ImageButton {
+                    image: ":/icons/icons/ui/do-not-disturb-rounded-sign.png"
+                    hoverEnabled: true
+                    ToolTip.visible: hovered
+                    ToolTip.text: qsTr("Ban the user.")
+                    onClicked: profile.banUser()
+                    visible: !profile.isGlobalUserProfile && profile.room.permissions.canBan()
+                }
+
+            }
+        }
+
+        id: devicelist
+        Layout.fillHeight: true
+        Layout.fillWidth: true
+        clip: true
+        spacing: 8
+        boundsBehavior: Flickable.StopAtBounds
+        model: profile.deviceList
         anchors.fill: parent
         anchors.margins: 10
-        spacing: 10
 
-        Avatar {
-            url: profile.avatarUrl.replace("mxc://", "image://MxcImage/")
-            height: 130
-            width: 130
-            displayName: profile.displayName
-            id: displayAvatar
-            userid: profile.userid
-            Layout.alignment: Qt.AlignHCenter
-            onClicked: TimelineManager.openImageOverlay(profile.avatarUrl, "")
 
-            ImageButton {
-                hoverEnabled: true
-                ToolTip.visible: hovered
-                ToolTip.text: profile.isGlobalUserProfile ? qsTr("Change avatar globally.") : qsTr("Change avatar. Will only apply to this room.")
-                anchors.left: displayAvatar.left
-                anchors.top: displayAvatar.top
-                anchors.leftMargin: Nheko.paddingMedium
-                anchors.topMargin: Nheko.paddingMedium
-                visible: profile.isSelf
-                image: ":/icons/icons/ui/edit.png"
-                onClicked: profile.changeAvatar()
-            }
-        }
+        delegate: RowLayout {
+            width: devicelist.width
+            spacing: 4
 
-        Spinner {
-            Layout.alignment: Qt.AlignHCenter
-            running: profile.isLoading
-            visible: profile.isLoading
-            foreground: Nheko.colors.mid
-        }
+            ColumnLayout {
+                spacing: 0
 
-        Text {
-            id: errorText
+                Text {
+                    Layout.fillWidth: true
+                    Layout.alignment: Qt.AlignLeft
+                    elide: Text.ElideRight
+                    font.bold: true
+                    color: Nheko.colors.text
+                    text: model.deviceId
+                }
 
-            color: "red"
-            visible: opacity > 0
-            opacity: 0
-            Layout.alignment: Qt.AlignHCenter
-        }
+                Text {
+                    Layout.fillWidth: true
+                    Layout.alignment: Qt.AlignRight
+                    elide: Text.ElideRight
+                    color: Nheko.colors.text
+                    text: model.deviceName
+                }
 
-        SequentialAnimation {
-            id: hideErrorAnimation
-
-            running: false
-
-            PauseAnimation {
-                duration: 4000
             }
 
-            NumberAnimation {
-                target: errorText
-                property: 'opacity'
-                to: 0
-                duration: 1000
+            Image {
+                Layout.preferredHeight: 16
+                Layout.preferredWidth: 16
+                source: ((model.verificationStatus == VerificationStatus.VERIFIED) ? "image://colorimage/:/icons/icons/ui/lock.png?green" : ((model.verificationStatus == VerificationStatus.UNVERIFIED) ? "image://colorimage/:/icons/icons/ui/unlock.png?yellow" : "image://colorimage/:/icons/icons/ui/unlock.png?red"))
             }
 
-        }
+            Button {
+                id: verifyButton
 
-        Connections {
-            function onDisplayError(errorMessage) {
-                errorText.text = errorMessage;
-                errorText.opacity = 1;
-                hideErrorAnimation.restart();
-            }
-
-            target: profile
-        }
-
-        TextInput {
-            id: displayUsername
-
-            property bool isUsernameEditingAllowed
-
-            readOnly: !isUsernameEditingAllowed
-            text: profile.displayName
-            font.pixelSize: 20
-            color: TimelineManager.userColor(profile.userid, Nheko.colors.window)
-            font.bold: true
-            Layout.alignment: Qt.AlignHCenter
-            selectByMouse: true
-            onAccepted: {
-                profile.changeUsername(displayUsername.text);
-                displayUsername.isUsernameEditingAllowed = false;
-            }
-
-            ImageButton {
-                visible: profile.isSelf
-                anchors.leftMargin: Nheko.paddingSmall
-                anchors.left: displayUsername.right
-                anchors.verticalCenter: displayUsername.verticalCenter
-                hoverEnabled: true
-                ToolTip.visible: hovered
-                ToolTip.text: profile.isGlobalUserProfile ? qsTr("Change display name globally.") : qsTr("Change display name. Will only apply to this room.")
-                image: displayUsername.isUsernameEditingAllowed ? ":/icons/icons/ui/checkmark.png" : ":/icons/icons/ui/edit.png"
+                visible: (!profile.userVerificationEnabled && !profile.isSelf) || (profile.isSelf && (model.verificationStatus != VerificationStatus.VERIFIED || !profile.userVerificationEnabled))
+                text: (model.verificationStatus != VerificationStatus.VERIFIED) ? qsTr("Verify") : qsTr("Unverify")
                 onClicked: {
-                    if (displayUsername.isUsernameEditingAllowed) {
-                        profile.changeUsername(displayUsername.text);
-                        displayUsername.isUsernameEditingAllowed = false;
-                    } else {
-                        displayUsername.isUsernameEditingAllowed = true;
-                        displayUsername.focus = true;
-                        displayUsername.selectAll();
-                    }
+                    if (model.verificationStatus == VerificationStatus.VERIFIED)
+                        profile.unverify(model.deviceId);
+                    else
+                        profile.verify(model.deviceId);
                 }
             }
 
         }
-
-        MatrixText {
-            text: profile.userid
-            font.pixelSize: 15
-            Layout.alignment: Qt.AlignHCenter
-        }
-
-        MatrixText {
-            id: displayRoomname
-            text: qsTr("Room: %1").arg(profile.room.roomName)
-            Layout.alignment: Qt.AlignHCenter
-            visible: !profile.isGlobalUserProfile
-            ToolTip.text: qsTr("This is a room-specific profile. The user's name and avatar may be different from their global versions.")
-            ToolTip.visible: ma.hovered
-            HoverHandler {
-                id: ma
-            }
-
-            ImageButton {
-                anchors.leftMargin: Nheko.paddingSmall
-                anchors.left: displayRoomname.right
-                anchors.verticalCenter: displayRoomname.verticalCenter
-                image: ":/icons/icons/ui/world.png"
-                hoverEnabled: true
-                ToolTip.visible: hovered
-                ToolTip.text: qsTr("Open the global profile for this user.")
-                onClicked: profile.openGlobalProfile()
-                visible: !profile.isGlobalUserProfile
+        footerPositioning: ListView.OverlayFooter
+        footer: DialogButtonBox {
+            z: 2
+            width: devicelist.width
+            alignment: Qt.AlignRight
+            standardButtons: DialogButtonBox.Ok
+            onAccepted: userProfileDialog.close()
+            background: Rectangle {
+                anchors.fill: parent
+                color: Nheko.colors.window
             }
         }
 
-        Button {
-            id: verifyUserButton
-
-            text: qsTr("Verify")
-            Layout.alignment: Qt.AlignHCenter
-            enabled: profile.userVerified != Crypto.Verified
-            visible: profile.userVerified != Crypto.Verified && !profile.isSelf && profile.userVerificationEnabled
-            onClicked: profile.verify()
-        }
-
-        Image {
-            Layout.preferredHeight: 16
-            Layout.preferredWidth: 16
-            source: "image://colorimage/:/icons/icons/ui/lock.png?" + ((profile.userVerified == Crypto.Verified) ? "green" : Nheko.colors.buttonText)
-            visible: profile.userVerified != Crypto.Unverified
-            Layout.alignment: Qt.AlignHCenter
-        }
-
-        RowLayout {
-            // ImageButton{
-            //     image:":/icons/icons/ui/volume-off-indicator.png"
-            //     Layout.margins: {
-            //         left: 5
-            //         right: 5
-            //     }
-            //     ToolTip.visible: hovered
-            //     ToolTip.text: qsTr("Ignore messages from this user.")
-            //     onClicked : {
-            //         profile.ignoreUser()
-            //     }
-            // }
-
-            Layout.alignment: Qt.AlignHCenter
-            spacing: 8
-
-            ImageButton {
-                image: ":/icons/icons/ui/black-bubble-speech.png"
-                hoverEnabled: true
-                ToolTip.visible: hovered
-                ToolTip.text: qsTr("Start a private chat.")
-                onClicked: profile.startChat()
-            }
-
-            ImageButton {
-                image: ":/icons/icons/ui/round-remove-button.png"
-                hoverEnabled: true
-                ToolTip.visible: hovered
-                ToolTip.text: qsTr("Kick the user.")
-                onClicked: profile.kickUser()
-                visible: !profile.isGlobalUserProfile && profile.room.permissions.canKick()
-            }
-
-            ImageButton {
-                image: ":/icons/icons/ui/do-not-disturb-rounded-sign.png"
-                hoverEnabled: true
-                ToolTip.visible: hovered
-                ToolTip.text: qsTr("Ban the user.")
-                onClicked: profile.banUser()
-                visible: !profile.isGlobalUserProfile && profile.room.permissions.canBan()
-            }
-
-        }
-
-        ListView {
-            id: devicelist
-
-            Layout.fillHeight: true
-            Layout.minimumHeight: 200
-            Layout.fillWidth: true
-            clip: true
-            spacing: 8
-            boundsBehavior: Flickable.StopAtBounds
-            model: profile.deviceList
-
-            delegate: RowLayout {
-                width: devicelist.width
-                spacing: 4
-
-                ColumnLayout {
-                    spacing: 0
-
-                    Text {
-                        Layout.fillWidth: true
-                        Layout.alignment: Qt.AlignLeft
-                        elide: Text.ElideRight
-                        font.bold: true
-                        color: Nheko.colors.text
-                        text: model.deviceId
-                    }
-
-                    Text {
-                        Layout.fillWidth: true
-                        Layout.alignment: Qt.AlignRight
-                        elide: Text.ElideRight
-                        color: Nheko.colors.text
-                        text: model.deviceName
-                    }
-
-                }
-
-                Image {
-                    Layout.preferredHeight: 16
-                    Layout.preferredWidth: 16
-                    source: ((model.verificationStatus == VerificationStatus.VERIFIED) ? "image://colorimage/:/icons/icons/ui/lock.png?green" : ((model.verificationStatus == VerificationStatus.UNVERIFIED) ? "image://colorimage/:/icons/icons/ui/unlock.png?yellow" : "image://colorimage/:/icons/icons/ui/unlock.png?red"))
-                }
-
-                Button {
-                    id: verifyButton
-
-                    visible: (!profile.userVerificationEnabled && !profile.isSelf) || (profile.isSelf && (model.verificationStatus != VerificationStatus.VERIFIED || !profile.userVerificationEnabled))
-                    text: (model.verificationStatus != VerificationStatus.VERIFIED) ? qsTr("Verify") : qsTr("Unverify")
-                    onClicked: {
-                        if (model.verificationStatus == VerificationStatus.VERIFIED)
-                            profile.unverify(model.deviceId);
-                        else
-                            profile.verify(model.deviceId);
-                    }
-                }
-
-            }
-
-        }
-
-    }
-
-    footer: DialogButtonBox {
-        standardButtons: DialogButtonBox.Ok
-        onAccepted: userProfileDialog.close()
     }
 
 }
diff --git a/src/ChatPage.cpp b/src/ChatPage.cpp
index c5c27964..a07a9654 100644
--- a/src/ChatPage.cpp
+++ b/src/ChatPage.cpp
@@ -1279,8 +1279,13 @@ ChatPage::handleMatrixUri(const QByteArray &uri)
 
         if (sigil1 == "u") {
                 if (action.isEmpty()) {
-                        if (auto t = view_manager_->rooms()->currentRoom())
+                        auto t = view_manager_->rooms()->currentRoom();
+                        if (t &&
+                            cache::isRoomMember(mxid1.toStdString(), t->roomId().toStdString())) {
                                 t->openUserProfile(mxid1);
+                                return;
+                        }
+                        emit view_manager_->openGlobalUserProfile(mxid1);
                 } else if (action == "chat") {
                         this->startChat(mxid1);
                 }
diff --git a/src/ui/UserProfile.cpp b/src/ui/UserProfile.cpp
index a3f42671..fbd0f4f7 100644
--- a/src/ui/UserProfile.cpp
+++ b/src/ui/UserProfile.cpp
@@ -423,6 +423,5 @@ UserProfile::getGlobalProfileData()
 void
 UserProfile::openGlobalProfile()
 {
-        UserProfile *userProfile = new UserProfile("", userid_, manager, model);
-        emit manager->openProfile(userProfile);
+        emit manager->openGlobalUserProfile(userid_);
 }

From c7e884c4543717ff761adb87f93fc8ebb26018ca Mon Sep 17 00:00:00 2001
From: Nicolas Werner 
Date: Wed, 8 Sep 2021 01:28:49 +0200
Subject: [PATCH 098/232] Recommend version 0.12 of QtKeychain

See https://github.com/Nheko-Reborn/nheko/issues/715
---
 README.md | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/README.md b/README.md
index 61dfdfc3..b5ed4966 100644
--- a/README.md
+++ b/README.md
@@ -171,7 +171,7 @@ choco install nheko-reborn
     - Voice call support: dtls, opus, rtpmanager, srtp, webrtc
     - Video call support (optional): compositor, opengl, qmlgl, rtp, vpx
     - [libnice](https://gitlab.freedesktop.org/libnice/libnice)
-- [qtkeychain](https://github.com/frankosterfeld/qtkeychain)
+- [qtkeychain](https://github.com/frankosterfeld/qtkeychain) (You need at least version 0.12 for proper Gnome Keychain support)
 - A compiler that supports C++ 17:
     - Clang 6 (tested on Travis CI)
     - GCC 7 (tested on Travis CI)

From 495a4334dff6b1eb2dd2bc2d75dfd8ef219f5950 Mon Sep 17 00:00:00 2001
From: Nicolas Werner 
Date: Wed, 8 Sep 2021 12:47:44 +0200
Subject: [PATCH 099/232] Fix day separator when loading older messages

fixes #632
---
 src/timeline/EventStore.cpp    |  3 +++
 src/timeline/TimelineModel.cpp | 19 +++++++------------
 2 files changed, 10 insertions(+), 12 deletions(-)

diff --git a/src/timeline/EventStore.cpp b/src/timeline/EventStore.cpp
index 8860bc75..881fd5bb 100644
--- a/src/timeline/EventStore.cpp
+++ b/src/timeline/EventStore.cpp
@@ -71,11 +71,14 @@ EventStore::EventStore(std::string room_id, QObject *)
                           fetchMore();
                   else {
                           if (this->last != std::numeric_limits::max()) {
+                                  auto oldFirst = this->first;
                                   emit beginInsertRows(toExternalIdx(newFirst),
                                                        toExternalIdx(this->first - 1));
                                   this->first = newFirst;
                                   emit endInsertRows();
                                   emit fetchedMore();
+                                  emit dataChanged(toExternalIdx(oldFirst),
+                                                   toExternalIdx(oldFirst));
                           } else {
                                   auto range = cache::client()->getTimelineRange(room_id_);
 
diff --git a/src/timeline/TimelineModel.cpp b/src/timeline/TimelineModel.cpp
index 1e369b46..e5e9d9bf 100644
--- a/src/timeline/TimelineModel.cpp
+++ b/src/timeline/TimelineModel.cpp
@@ -354,18 +354,13 @@ TimelineModel::TimelineModel(TimelineViewManager *manager, QString room_id, QObj
                 Qt::QueuedConnection);
         connect(this, &TimelineModel::addPendingMessageToStore, &events, &EventStore::addPending);
 
-        connect(
-          &events,
-          &EventStore::dataChanged,
-          this,
-          [this](int from, int to) {
-                  relatedEventCacheBuster++;
-                  nhlog::ui()->debug(
-                    "data changed {} to {}", events.size() - to - 1, events.size() - from - 1);
-                  emit dataChanged(index(events.size() - to - 1, 0),
-                                   index(events.size() - from - 1, 0));
-          },
-          Qt::QueuedConnection);
+        connect(&events, &EventStore::dataChanged, this, [this](int from, int to) {
+                relatedEventCacheBuster++;
+                nhlog::ui()->debug(
+                  "data changed {} to {}", events.size() - to - 1, events.size() - from - 1);
+                emit dataChanged(index(events.size() - to - 1, 0),
+                                 index(events.size() - from - 1, 0));
+        });
 
         connect(&events, &EventStore::beginInsertRows, this, [this](int from, int to) {
                 int first = events.size() - to;

From f9a334233fa03e4705fb34a02fce9da8cd0dceb6 Mon Sep 17 00:00:00 2001
From: Nicolas Werner 
Date: Thu, 9 Sep 2021 21:20:34 +0200
Subject: [PATCH 100/232] Don't allow images in username change messages and
 user completer

Sorry, no fun for you!
---
 src/UsersModel.cpp             | 7 ++++---
 src/timeline/TimelineModel.cpp | 3 ++-
 2 files changed, 6 insertions(+), 4 deletions(-)

diff --git a/src/UsersModel.cpp b/src/UsersModel.cpp
index c4379668..13b05f0e 100644
--- a/src/UsersModel.cpp
+++ b/src/UsersModel.cpp
@@ -42,21 +42,22 @@ UsersModel::data(const QModelIndex &index, int role) const
                 case CompletionModel::CompletionRole:
                         if (UserSettings::instance()->markdown())
                                 return QString("[%1](https://matrix.to/#/%2)")
-                                  .arg(displayNames[index.row()])
+                                  .arg(displayNames[index.row()].toHtmlEscaped())
                                   .arg(QString(QUrl::toPercentEncoding(userids[index.row()])));
                         else
                                 return displayNames[index.row()];
                 case CompletionModel::SearchRole:
+                        return displayNames[index.row()];
                 case Qt::DisplayRole:
                 case Roles::DisplayName:
-                        return displayNames[index.row()];
+                        return displayNames[index.row()].toHtmlEscaped();
                 case CompletionModel::SearchRole2:
                         return userids[index.row()];
                 case Roles::AvatarUrl:
                         return cache::avatarUrl(QString::fromStdString(room_id),
                                                 QString::fromStdString(roomMembers_[index.row()]));
                 case Roles::UserID:
-                        return userids[index.row()];
+                        return userids[index.row()].toHtmlEscaped();
                 }
         }
         return {};
diff --git a/src/timeline/TimelineModel.cpp b/src/timeline/TimelineModel.cpp
index e5e9d9bf..78409e1d 100644
--- a/src/timeline/TimelineModel.cpp
+++ b/src/timeline/TimelineModel.cpp
@@ -1858,7 +1858,8 @@ TimelineModel::formatMemberEvent(QString id)
                 break;
         case Membership::Join:
                 if (prevEvent && prevEvent->content.membership == Membership::Join) {
-                        QString oldName = QString::fromStdString(prevEvent->content.display_name);
+                        QString oldName = utils::replaceEmoji(
+                          QString::fromStdString(prevEvent->content.display_name).toHtmlEscaped());
 
                         bool displayNameChanged =
                           prevEvent->content.display_name != event->content.display_name;

From 4cccb98529f81b2e61d3ee4665982ec9aa938f4d Mon Sep 17 00:00:00 2001
From: Weblate 
Date: Fri, 10 Sep 2021 11:30:41 -0400
Subject: [PATCH 101/232] Translated using Weblate (Portuguese (Portugal))

Currently translated at 15.8% (89 of 560 strings)

Co-authored-by: Xenovox 
Translate-URL: https://weblate.nheko.im/projects/nheko/nheko-master/pt_PT/
Translation: Nheko/nheko
---
 resources/langs/nheko_pt_PT.ts | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/resources/langs/nheko_pt_PT.ts b/resources/langs/nheko_pt_PT.ts
index 39a88da9..77639d92 100644
--- a/resources/langs/nheko_pt_PT.ts
+++ b/resources/langs/nheko_pt_PT.ts
@@ -22,7 +22,7 @@
     
         
         Hide/Show Picture-in-Picture
-        
+        Mostrar/Ocultar Picture-in-Picture
     
     
         

From 1a8b7752ad8c5fb9cfb7d6da6173cfe244145db6 Mon Sep 17 00:00:00 2001
From: Weblate 
Date: Fri, 10 Sep 2021 11:32:03 -0400
Subject: [PATCH 102/232] Translated using Weblate (Portuguese (Portugal))

Currently translated at 16.0% (90 of 560 strings)

Co-authored-by: Xenovox 
Translate-URL: https://weblate.nheko.im/projects/nheko/nheko-master/pt_PT/
Translation: Nheko/nheko
---
 resources/langs/nheko_pt_PT.ts | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/resources/langs/nheko_pt_PT.ts b/resources/langs/nheko_pt_PT.ts
index 77639d92..61c58269 100644
--- a/resources/langs/nheko_pt_PT.ts
+++ b/resources/langs/nheko_pt_PT.ts
@@ -501,7 +501,7 @@
     
         
         There is no key to unlock this message. We requested the key automatically, but you can try requesting it again if you are impatient.
-        
+        Não há nenhuma chave para desbloquear esta mensagem. Nós pedimos a chave automaticamente, mas pode tentar pedi-la outra vez se estiver impaciente.
     
     
         

From ef22995e6264175d4ef2622d2dfa443c4051005a Mon Sep 17 00:00:00 2001
From: Weblate 
Date: Fri, 10 Sep 2021 11:34:54 -0400
Subject: [PATCH 103/232] Translated using Weblate (Portuguese (Portugal))

Currently translated at 16.7% (94 of 560 strings)

Translated using Weblate (Portuguese (Portugal))

Currently translated at 16.7% (94 of 560 strings)

Co-authored-by: Tnpod 
Co-authored-by: Weblate 
Co-authored-by: Xenovox 
Translate-URL: https://weblate.nheko.im/projects/nheko/nheko-master/pt_PT/
Translation: Nheko/nheko
---
 resources/langs/nheko_pt_PT.ts | 10 +++++-----
 1 file changed, 5 insertions(+), 5 deletions(-)

diff --git a/resources/langs/nheko_pt_PT.ts b/resources/langs/nheko_pt_PT.ts
index 61c58269..3d45b8e2 100644
--- a/resources/langs/nheko_pt_PT.ts
+++ b/resources/langs/nheko_pt_PT.ts
@@ -501,22 +501,22 @@
     
         
         There is no key to unlock this message. We requested the key automatically, but you can try requesting it again if you are impatient.
-        Não há nenhuma chave para desbloquear esta mensagem. Nós pedimos a chave automaticamente, mas pode tentar pedi-la outra vez se estiver impaciente.
+        Não existe nenhuma chave para desbloquear esta mensagem. Nós pedimos a chave automaticamente, mas pode tentar pedi-la outra vez se estiver impaciente.
     
     
         
         This message couldn't be decrypted, because we only have a key for newer messages. You can try requesting access to this message.
-        
+        Esta mensagem não pôde ser desencriptada, porque apenas temos uma chave para mensagens mais recentes. Pode tentar solicitar acesso a esta mensagem.
     
     
         
         There was an internal error reading the decryption key from the database.
-        
+        Ocorreu um erro interno ao ler a chave de desencriptação da base de dados.
     
     
         
         There was an error decrypting this message.
-        
+        Ocorreu um erro ao desencriptar esta mensagem.
     
     
         
@@ -526,7 +526,7 @@
     
         
         The encryption key was reused! Someone is possibly trying to insert false messages into this chat!
-        
+        Esta chave de encriptação foi reutilizada! É possível que alguém esteja a tentar inserir mensagens falsas neste chat!
     
     
         

From 649f591903e7a9fea0f6e181560bbd1e9c47338f Mon Sep 17 00:00:00 2001
From: Weblate 
Date: Sat, 11 Sep 2021 02:01:09 -0400
Subject: [PATCH 104/232] Translated using Weblate (Portuguese (Portugal))

Currently translated at 17.6% (99 of 560 strings)

Translated using Weblate (Portuguese (Portugal))

Currently translated at 17.6% (99 of 560 strings)

Co-authored-by: Tnpod 
Co-authored-by: Xenovox 
Translate-URL: https://weblate.nheko.im/projects/nheko/nheko-master/pt_PT/
Translation: Nheko/nheko
---
 resources/langs/nheko_pt_PT.ts | 12 ++++++------
 1 file changed, 6 insertions(+), 6 deletions(-)

diff --git a/resources/langs/nheko_pt_PT.ts b/resources/langs/nheko_pt_PT.ts
index 3d45b8e2..b976b64a 100644
--- a/resources/langs/nheko_pt_PT.ts
+++ b/resources/langs/nheko_pt_PT.ts
@@ -526,17 +526,17 @@
     
         
         The encryption key was reused! Someone is possibly trying to insert false messages into this chat!
-        Esta chave de encriptação foi reutilizada! É possível que alguém esteja a tentar inserir mensagens falsas neste chat!
+        Esta chave de encriptação foi reutilizada! É possível que alguém esteja a tentar inserir mensagens falsas nesta conversa!
     
     
         
         Unknown decryption error
-        
+        Erro de desencriptação desconhecido
     
     
         
         Request key
-        
+        Solicitar chave
     
 
 
@@ -544,17 +544,17 @@
     
         
         This message is not encrypted!
-        
+        Esta mensagem não está encriptada!
     
     
         
         Encrypted by a verified device
-        
+        Encriptado por um dispositivo verificado.
     
     
         
         Encrypted by an unverified device, but you have trusted that user so far.
-        
+        Encriptado por um dispositivo não verificado, mas até agora tem confiado neste utilizador.
     
     
         

From 7bb1b1c6505977cb2aadc0a83542450d0a9ccfdb Mon Sep 17 00:00:00 2001
From: Weblate 
Date: Sat, 11 Sep 2021 02:01:09 -0400
Subject: [PATCH 105/232] Translated using Weblate (Malayalam)

Currently translated at 17.1% (96 of 560 strings)

Co-authored-by: vachan-maker 
Translate-URL: https://weblate.nheko.im/projects/nheko/nheko-master/ml/
Translation: Nheko/nheko
---
 resources/langs/nheko_ml.ts | 94 ++++++++++++++++++-------------------
 1 file changed, 47 insertions(+), 47 deletions(-)

diff --git a/resources/langs/nheko_ml.ts b/resources/langs/nheko_ml.ts
index 2d8ddab6..c931cc34 100644
--- a/resources/langs/nheko_ml.ts
+++ b/resources/langs/nheko_ml.ts
@@ -17,7 +17,7 @@
     
         
         You are screen sharing
-        
+        നിങ്ങൾ സ്ക്രീൻ പങ്കിടുന്നു
     
     
         
@@ -91,7 +91,7 @@
     
         
         Accept
-        
+        സ്വീകരിക്കുക
     
     
         
@@ -119,7 +119,7 @@
     
         
         Entire screen
-        
+        മുഴുവൻ സ്ക്രീൻ
     
 
 
@@ -143,12 +143,12 @@
     
         
         Confirm join
-        
+        ചേരുന്നത് ഉറപ്പാക്കുക
     
     
         
         Do you really want to join %1?
-        
+        നിങ്ങൾക്ക് %1 -ൽ ചേരാൻ ആഗ്രഹം ഉണ്ടോ?
     
     
         
@@ -174,7 +174,7 @@
     
         
         Confirm kick
-        
+        പുറത്താക്കൽ ഉറപ്പാക്കുക
     
     
         
@@ -184,7 +184,7 @@
     
         
         Kicked user: %1
-        
+        ഉപയോക്താവിനെ പുറത്താക്കി: %1
     
     
         
@@ -267,7 +267,7 @@
         
         
         Please try to login again: %1
-        
+        ദയവായി വീണ്ടും ലോഗിൻ ചെയ്യാൻ നോക്കുക: %1
     
     
         
@@ -287,7 +287,7 @@
     
         
         Room creation failed: %1
-        
+        മുറി സൃഷ്ടിക്കുന്നത് പരാജയപ്പെട്ടു: %1
     
     
         
@@ -323,7 +323,7 @@
     
         
         Favourites
-        
+        പ്രിയപ്പെട്ടവ
     
     
         
@@ -384,7 +384,7 @@
     
         
         Verification Code
-        
+        ഉറപ്പാക്കൽ കോഡ്
     
     
         
@@ -450,17 +450,17 @@
     
         
         Activity
-        
+        പ്രവർത്തനം
     
     
         
         Travel
-        
+        യാത്ര
     
     
         
         Objects
-        
+        സാധനങ്ങൾ
     
     
         
@@ -470,7 +470,7 @@
     
         
         Flags
-        
+        പതാകകൾ
     
 
 
@@ -478,7 +478,7 @@
     
         
         Verification Code
-        
+        ഉറപ്പാക്കൽ കോഡ്
     
     
         
@@ -536,7 +536,7 @@
     
         
         Request key
-        
+        കീ അഭ്യർ
     
 
 
@@ -567,7 +567,7 @@
     
         
         Verification failed
-        
+        ഉറപ്പാക്കൽ പരാജയപ്പെട്ടു
     
     
         
@@ -614,12 +614,12 @@
     
         
         Add images
-        
+        ചിത്രങ്ങൾ ചേർക്കുക
     
     
         
         Stickers (*.png *.webp *.gif)
-        
+        സ്റ്റിക്കറുകൾ(*.png *.webp *.gif)
     
     
         
@@ -629,7 +629,7 @@
     
         
         Packname
-        
+        പാക്കിന്റെ പേര്
     
     
         
@@ -640,13 +640,13 @@
         
         
         Use as Emoji
-        
+        ഇമോജി ആയി ഉപയോഗിക്കുക
     
     
         
         
         Use as Sticker
-        
+        സ്റ്റിക്കറായി ഉപയോഗിക്കുക
     
     
         
@@ -666,7 +666,7 @@
     
         
         Remove
-        
+        നീക്കം ചെയ്യുക
     
     
         
@@ -676,7 +676,7 @@
     
         
         Save
-        
+        സംരക്ഷിക്കുക
     
 
 
@@ -689,12 +689,12 @@
     
         
         Create account pack
-        
+        അക്കൗണ്ട് പാക്ക് സൃഷ്ടിക്കുക
     
     
         
         New room pack
-        
+        പുതിയ മുറി പാക്ക്
     
     
         
@@ -742,7 +742,7 @@
     
         
         All Files (*)
-        
+        എല്ലാ ഫയലുകളും (*)
     
     
         
@@ -755,7 +755,7 @@
     
         
         Invite users to %1
-        
+        %1 - ലേക്ക് ഉപയോക്താക്കളെ ക്ഷണിക്കുക
     
     
         
@@ -766,17 +766,17 @@
         
         @joe:matrix.org
         Example user id. The name 'joe' can be localized however you want.
-        
+        @joe:matrix.org
     
     
         
         Add
-        
+        ചേർക്കുക
     
     
         
         Invite
-        
+        ക്ഷണിക്കുക
     
     
         
@@ -794,7 +794,7 @@
     
         
         e.g @joe:matrix.org
-        
+        ഉദാ. @joe:matrix.org
     
     
         
@@ -843,7 +843,7 @@ Example: https://server.my:8787
     
         
         LOGIN
-        
+        പ്രവേശിക്കുക
     
     
         
@@ -881,7 +881,7 @@ Example: https://server.my:8787
     
         
         SSO LOGIN
-        
+        എസ് എസ് ഓ ലോഗിൻ
     
     
         
@@ -909,7 +909,7 @@ Example: https://server.my:8787
     
         
         removed room name
-        
+        മുറിയുടെ പേര് നീക്കം ചെയ്തു
     
     
         
@@ -919,7 +919,7 @@ Example: https://server.my:8787
     
         
         removed topic
-        
+        വിഷയം നീക്കം ചെയ്തു
     
     
         
@@ -954,7 +954,7 @@ Example: https://server.my:8787
     
         
         Allow them in
-        
+        ഇവരെ അനുവദിക്കുക
     
     
         
@@ -988,7 +988,7 @@ Example: https://server.my:8787
     
         
         Send a file
-        
+        ഒരു ഫയൽ അയയ്ക്കുക
     
     
         
@@ -998,7 +998,7 @@ Example: https://server.my:8787
     
         
         Stickers
-        
+        സ്റ്റിക്കറുകൾ
     
     
         
@@ -1021,7 +1021,7 @@ Example: https://server.my:8787
     
         
         Edit
-        
+        തിരുത്തുക
     
     
         
@@ -1031,7 +1031,7 @@ Example: https://server.my:8787
     
         
         Reply
-        
+        മറുപടി നൽകുക
     
     
         
@@ -1076,7 +1076,7 @@ Example: https://server.my:8787
     
         
         &Mark as read
-        
+        &വായിച്ചതായി കാണിക്കുക
     
     
         
@@ -1154,7 +1154,7 @@ Example: https://server.my:8787
     
         
         Deny
-        
+        നിരസിക്കുക
     
     
         
@@ -1164,7 +1164,7 @@ Example: https://server.my:8787
     
         
         Accept
-        
+        സ്വീകരിക്കുക
     
 
 
@@ -1180,7 +1180,7 @@ Example: https://server.my:8787
         
         * %1 %2
         Format an emote message in a notification, %1 is the sender, %2 the message
-        
+        * %1 %2
     
     
         
@@ -1192,7 +1192,7 @@ Example: https://server.my:8787
         
         %1: %2
         Format a normal message in a notification. %1 is the sender, %2 the message
-        
+        %1: %2
     
     
         

From 45b5629fe43d036d27db9bc0f100a1ad8cffc027 Mon Sep 17 00:00:00 2001
From: Thulinma 
Date: Sat, 11 Sep 2021 17:33:09 +0200
Subject: [PATCH 106/232] Fix a few more HTML injections

---
 src/RoomsModel.cpp      | 2 +-
 src/timeline/Reaction.h | 4 ++--
 2 files changed, 3 insertions(+), 3 deletions(-)

diff --git a/src/RoomsModel.cpp b/src/RoomsModel.cpp
index 80f13756..656a0deb 100644
--- a/src/RoomsModel.cpp
+++ b/src/RoomsModel.cpp
@@ -77,7 +77,7 @@ RoomsModel::data(const QModelIndex &index, int role) const
                         return QString::fromStdString(
                           roomInfos.at(roomids[index.row()]).avatar_url);
                 case Roles::RoomID:
-                        return roomids[index.row()];
+                        return roomids[index.row()].toHtmlEscaped();
                 }
         }
         return {};
diff --git a/src/timeline/Reaction.h b/src/timeline/Reaction.h
index 47dac617..788e9ced 100644
--- a/src/timeline/Reaction.h
+++ b/src/timeline/Reaction.h
@@ -16,8 +16,8 @@ struct Reaction
         Q_PROPERTY(int count READ count)
 
 public:
-        QString key() const { return key_; }
-        QString users() const { return users_; }
+        QString key() const { return key_.toHtmlEscaped(); }
+        QString users() const { return users_.toHtmlEscaped(); }
         QString selfReactedEvent() const { return selfReactedEvent_; }
         int count() const { return count_; }
 

From d2e193ff78c491f7108476b00340aea97f4feed3 Mon Sep 17 00:00:00 2001
From: Loren Burkholder 
Date: Fri, 25 Dec 2020 09:14:00 -0500
Subject: [PATCH 107/232] Add jdenticon support

---
 CMakeLists.txt                       |  2 +
 resources/qml/Avatar.qml             |  9 +++-
 src/JdenticonProvider.cpp            | 68 ++++++++++++++++++++++++++++
 src/JdenticonProvider.h              | 59 ++++++++++++++++++++++++
 src/MainWindow.cpp                   | 28 +-----------
 src/MainWindow.h                     |  2 -
 src/UserSettingsPage.cpp             | 23 ++++++++++
 src/UserSettingsPage.h               |  8 ++++
 src/timeline/TimelineViewManager.cpp |  3 ++
 src/timeline/TimelineViewManager.h   |  2 +
 src/ui/Theme.h                       |  3 +-
 11 files changed, 176 insertions(+), 31 deletions(-)
 create mode 100644 src/JdenticonProvider.cpp
 create mode 100644 src/JdenticonProvider.h

diff --git a/CMakeLists.txt b/CMakeLists.txt
index 20ef5cab..1b6c08b7 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -345,6 +345,7 @@ set(SRC_FILES
 	src/DeviceVerificationFlow.cpp
 	src/EventAccessors.cpp
 	src/InviteesModel.cpp
+	src/JdenticonProvider.cpp
 	src/Logging.cpp
 	src/LoginPage.cpp
 	src/MainWindow.cpp
@@ -557,6 +558,7 @@ qt5_wrap_cpp(MOC_HEADERS
 	src/DeviceVerificationFlow.h
 	src/ImagePackListModel.h
 	src/InviteesModel.h
+	src/JdenticonProvider.h
 	src/LoginPage.h
 	src/MainWindow.h
 	src/MemberList.h
diff --git a/resources/qml/Avatar.qml b/resources/qml/Avatar.qml
index ab067eee..7bbdeba3 100644
--- a/resources/qml/Avatar.qml
+++ b/resources/qml/Avatar.qml
@@ -35,10 +35,17 @@ Rectangle {
         font.pixelSize: avatar.height / 2
         verticalAlignment: Text.AlignVCenter
         horizontalAlignment: Text.AlignHCenter
-        visible: img.status != Image.Ready
+        visible: img.status != Image.Ready && !Settings.useIdenticon
         color: Nheko.colors.text
     }
 
+    Image {
+        id: identicon
+        anchors.fill: parent
+        visible: img.status != Image.Ready && Settings.useIdenticon
+        source: "image://jdenticon/" + userid
+    }
+
     Image {
         id: img
 
diff --git a/src/JdenticonProvider.cpp b/src/JdenticonProvider.cpp
new file mode 100644
index 00000000..4be972dc
--- /dev/null
+++ b/src/JdenticonProvider.cpp
@@ -0,0 +1,68 @@
+#include "JdenticonProvider.h"
+
+#include 
+#include 
+#include 
+#include 
+#include 
+
+#include 
+
+#include "Cache.h"
+#include "Logging.h"
+#include "MatrixClient.h"
+#include "Utils.h"
+#include "jdenticoninterface.h"
+
+JdenticonResponse::JdenticonResponse(const QString &key, const QSize &requestedSize)
+  : m_key(key)
+  , m_requestedSize(requestedSize.isValid() ? requestedSize : QSize(100, 100))
+  , m_pixmap{m_requestedSize}
+  , jdenticonInterface_{Jdenticon::getJdenticonInterface()}
+{
+        setAutoDelete(false);
+}
+
+void
+JdenticonResponse::run()
+{
+        m_pixmap.fill(Qt::transparent);
+        QPainter painter{&m_pixmap};
+        QSvgRenderer renderer{
+          jdenticonInterface_->generate(m_key, m_requestedSize.width()).toUtf8()};
+        //        m_image = QImage::fromData(jdenticonInterface_->generate(m_key,
+        //        size->width()).toUtf8());
+        renderer.render(&painter);
+
+        emit finished();
+}
+
+namespace Jdenticon {
+JdenticonInterface *
+getJdenticonInterface()
+{
+        static JdenticonInterface *interface = nullptr;
+
+        if (interface == nullptr) {
+                QDir pluginsDir(qApp->applicationDirPath());
+
+                bool plugins = pluginsDir.cd("plugins");
+                if (plugins) {
+                        for (QString fileName : pluginsDir.entryList(QDir::Files)) {
+                                QPluginLoader pluginLoader(pluginsDir.absoluteFilePath(fileName));
+                                QObject *plugin = pluginLoader.instance();
+                                if (plugin) {
+                                        interface = qobject_cast(plugin);
+                                        if (interface) {
+                                                nhlog::ui()->info("Loaded jdenticon plugin.");
+                                                break;
+                                        }
+                                }
+                        }
+                } else
+                        nhlog::ui()->info("jdenticon plugin not found.");
+        }
+
+        return interface;
+}
+}
diff --git a/src/JdenticonProvider.h b/src/JdenticonProvider.h
new file mode 100644
index 00000000..053842bb
--- /dev/null
+++ b/src/JdenticonProvider.h
@@ -0,0 +1,59 @@
+#pragma once
+
+#include 
+#include 
+#include 
+#include 
+
+#include 
+
+#include 
+
+#include "jdenticoninterface.h"
+
+namespace Jdenticon {
+JdenticonInterface *
+getJdenticonInterface();
+}
+
+class JdenticonResponse
+  : public QQuickImageResponse
+  , public QRunnable
+{
+public:
+        JdenticonResponse(const QString &key, const QSize &requestedSize);
+
+        QQuickTextureFactory *textureFactory() const override
+        {
+                return QQuickTextureFactory::textureFactoryForImage(m_pixmap.toImage());
+        }
+
+        void run() override;
+
+        QString m_key;
+        QSize m_requestedSize;
+        QPixmap m_pixmap;
+        JdenticonInterface *jdenticonInterface_ = nullptr;
+};
+
+class JdenticonProvider
+  : public QObject
+  , public QQuickAsyncImageProvider
+{
+        Q_OBJECT
+
+public:
+        static bool isAvailable() { return Jdenticon::getJdenticonInterface() != nullptr; }
+
+public slots:
+        QQuickImageResponse *requestImageResponse(const QString &key,
+                                                  const QSize &requestedSize) override
+        {
+                JdenticonResponse *response = new JdenticonResponse(key, requestedSize);
+                pool.start(response);
+                return response;
+        }
+
+private:
+        QThreadPool pool;
+};
diff --git a/src/MainWindow.cpp b/src/MainWindow.cpp
index 7eadc6df..b423304f 100644
--- a/src/MainWindow.cpp
+++ b/src/MainWindow.cpp
@@ -16,6 +16,7 @@
 #include "Cache_p.h"
 #include "ChatPage.h"
 #include "Config.h"
+#include "JdenticonProvider.h"
 #include "Logging.h"
 #include "LoginPage.h"
 #include "MainWindow.h"
@@ -152,10 +153,6 @@ MainWindow::MainWindow(QWidget *parent)
                         showChatPage();
                 }
         });
-
-        if (loadJdenticonPlugin()) {
-                nhlog::ui()->info("loaded jdenticon.");
-        }
 }
 
 void
@@ -428,29 +425,6 @@ MainWindow::showDialog(QWidget *dialog)
         dialog->show();
 }
 
-bool
-MainWindow::loadJdenticonPlugin()
-{
-        QDir pluginsDir(qApp->applicationDirPath());
-
-        bool plugins = pluginsDir.cd("plugins");
-        if (plugins) {
-                foreach (QString fileName, pluginsDir.entryList(QDir::Files)) {
-                        QPluginLoader pluginLoader(pluginsDir.absoluteFilePath(fileName));
-                        QObject *plugin = pluginLoader.instance();
-                        if (plugin) {
-                                jdenticonInteface_ = qobject_cast(plugin);
-                                if (jdenticonInteface_) {
-                                        nhlog::ui()->info("Found jdenticon plugin.");
-                                        return true;
-                                }
-                        }
-                }
-        }
-
-        nhlog::ui()->info("jdenticon plugin not found.");
-        return false;
-}
 void
 MainWindow::showWelcomePage()
 {
diff --git a/src/MainWindow.h b/src/MainWindow.h
index d423af9f..18bcfe3d 100644
--- a/src/MainWindow.h
+++ b/src/MainWindow.h
@@ -137,6 +137,4 @@ private:
         //! Overlay modal used to project other widgets.
         OverlayModal *modal_       = nullptr;
         LoadingIndicator *spinner_ = nullptr;
-
-        JdenticonInterface *jdenticonInteface_ = nullptr;
 };
diff --git a/src/UserSettingsPage.cpp b/src/UserSettingsPage.cpp
index af32344c..bfe5232b 100644
--- a/src/UserSettingsPage.cpp
+++ b/src/UserSettingsPage.cpp
@@ -86,6 +86,7 @@ UserSettings::load(std::optional profile)
         theme_                = settings.value("user/theme", defaultTheme_).toString();
         font_                 = settings.value("user/font_family", "default").toString();
         avatarCircles_        = settings.value("user/avatar_circles", true).toBool();
+        useIdenticon_         = settings.value("user/use_identicon", true).toBool();
         decryptSidebar_       = settings.value("user/decrypt_sidebar", true).toBool();
         privacyScreen_        = settings.value("user/privacy_screen", false).toBool();
         privacyScreenTimeout_ = settings.value("user/privacy_screen_timeout", 0).toInt();
@@ -596,6 +597,15 @@ UserSettings::setDisableCertificateValidation(bool disabled)
         disableCertificateValidation_ = disabled;
         http::client()->verify_certificates(!disabled);
         emit disableCertificateValidationChanged(disabled);
+}
+
+void
+UserSettings::setUseIdenticon(bool state)
+{
+        if (state == useIdenticon_)
+                return;
+        useIdenticon_ = state;
+        emit useIdenticonChanged(useIdenticon_);
         save();
 }
 
@@ -674,6 +684,7 @@ UserSettings::save()
         settings.setValue("screen_share_hide_cursor", screenShareHideCursor_);
         settings.setValue("use_stun_server", useStunServer_);
         settings.setValue("currentProfile", profile_);
+        settings.setValue("use_identicon", useIdenticon_);
 
         settings.endGroup(); // user
 
@@ -746,6 +757,7 @@ UserSettingsPage::UserSettingsPage(QSharedPointer settings, QWidge
         trayToggle_                     = new Toggle{this};
         startInTrayToggle_              = new Toggle{this};
         avatarCircles_                  = new Toggle{this};
+        useIdenticon_                   = new Toggle{this};
         decryptSidebar_                 = new Toggle(this);
         privacyScreen_                  = new Toggle{this};
         onlyShareKeysWithVerifiedUsers_ = new Toggle(this);
@@ -779,6 +791,7 @@ UserSettingsPage::UserSettingsPage(QSharedPointer settings, QWidge
         trayToggle_->setChecked(settings_->tray());
         startInTrayToggle_->setChecked(settings_->startInTray());
         avatarCircles_->setChecked(settings_->avatarCircles());
+        useIdenticon_->setChecked(settings_->useIdenticon());
         decryptSidebar_->setChecked(settings_->decryptSidebar());
         privacyScreen_->setChecked(settings_->privacyScreen());
         onlyShareKeysWithVerifiedUsers_->setChecked(settings_->onlyShareKeysWithVerifiedUsers());
@@ -941,6 +954,12 @@ UserSettingsPage::UserSettingsPage(QSharedPointer settings, QWidge
         boxWrap(tr("Circular Avatars"),
                 avatarCircles_,
                 tr("Change the appearance of user avatars in chats.\nOFF - square, ON - Circle."));
+        if (JdenticonProvider::isAvailable())
+                boxWrap(
+                  tr("Use identicons"),
+                  useIdenticon_,
+                  tr(
+                    "Display an identicon instead of a letter when a user has not set an avatar."));
         boxWrap(tr("Group's sidebar"),
                 groupViewToggle_,
                 tr("Show a column containing groups and tags next to the room list."));
@@ -1263,6 +1282,10 @@ UserSettingsPage::UserSettingsPage(QSharedPointer settings, QWidge
                 settings_->setAvatarCircles(enabled);
         });
 
+        connect(useIdenticon_, &Toggle::toggled, this, [this](bool enabled) {
+                settings_->setUseIdenticon(enabled);
+        });
+
         connect(markdown_, &Toggle::toggled, this, [this](bool enabled) {
                 settings_->setMarkdown(enabled);
         });
diff --git a/src/UserSettingsPage.h b/src/UserSettingsPage.h
index 93b53211..bcd9439b 100644
--- a/src/UserSettingsPage.h
+++ b/src/UserSettingsPage.h
@@ -12,6 +12,7 @@
 #include 
 #include 
 
+#include "JdenticonProvider.h"
 #include 
 
 class Toggle;
@@ -105,6 +106,8 @@ class UserSettings : public QObject
         Q_PROPERTY(QString homeserver READ homeserver WRITE setHomeserver NOTIFY homeserverChanged)
         Q_PROPERTY(bool disableCertificateValidation READ disableCertificateValidation WRITE
                      setDisableCertificateValidation NOTIFY disableCertificateValidationChanged)
+        Q_PROPERTY(
+          bool useIdenticon READ useIdenticon WRITE setUseIdenticon NOTIFY useIdenticonChanged)
 
         UserSettings();
 
@@ -172,6 +175,7 @@ public:
         void setHomeserver(QString homeserver);
         void setDisableCertificateValidation(bool disabled);
         void setHiddenTags(QStringList hiddenTags);
+        void setUseIdenticon(bool state);
 
         QString theme() const { return !theme_.isEmpty() ? theme_ : defaultTheme_; }
         bool messageHoverHighlight() const { return messageHoverHighlight_; }
@@ -230,6 +234,7 @@ public:
         QString homeserver() const { return homeserver_; }
         bool disableCertificateValidation() const { return disableCertificateValidation_; }
         QStringList hiddenTags() const { return hiddenTags_; }
+        bool useIdenticon() const { return useIdenticon_ && JdenticonProvider::isAvailable(); }
 
 signals:
         void groupViewStateChanged(bool state);
@@ -277,6 +282,7 @@ signals:
         void deviceIdChanged(QString deviceId);
         void homeserverChanged(QString homeserver);
         void disableCertificateValidationChanged(bool disabled);
+        void useIdenticonChanged(bool state);
 
 private:
         // Default to system theme if QT_QPA_PLATFORMTHEME var is set.
@@ -330,6 +336,7 @@ private:
         QString deviceId_;
         QString homeserver_;
         QStringList hiddenTags_;
+        bool useIdenticon_;
 
         QSettings settings;
 
@@ -391,6 +398,7 @@ private:
         Toggle *desktopNotifications_;
         Toggle *alertOnNotification_;
         Toggle *avatarCircles_;
+        Toggle *useIdenticon_;
         Toggle *useStunServer_;
         Toggle *decryptSidebar_;
         Toggle *privacyScreen_;
diff --git a/src/timeline/TimelineViewManager.cpp b/src/timeline/TimelineViewManager.cpp
index 681cbe09..ea231b03 100644
--- a/src/timeline/TimelineViewManager.cpp
+++ b/src/timeline/TimelineViewManager.cpp
@@ -141,6 +141,7 @@ TimelineViewManager::TimelineViewManager(CallManager *callManager, ChatPage *par
   , imgProvider(new MxcImageProvider())
   , colorImgProvider(new ColorImageProvider())
   , blurhashProvider(new BlurhashProvider())
+  , jdenticonProvider(new JdenticonProvider())
   , callManager_(callManager)
   , rooms_(new RoomlistModel(this))
   , communities_(new CommunitiesModel(this))
@@ -310,6 +311,8 @@ TimelineViewManager::TimelineViewManager(CallManager *callManager, ChatPage *par
         view->engine()->addImageProvider("MxcImage", imgProvider);
         view->engine()->addImageProvider("colorimage", colorImgProvider);
         view->engine()->addImageProvider("blurhash", blurhashProvider);
+        if (JdenticonProvider::isAvailable())
+                view->engine()->addImageProvider("jdenticon", jdenticonProvider);
         view->setSource(QUrl("qrc:///qml/Root.qml"));
 
         connect(parent, &ChatPage::themeChanged, this, &TimelineViewManager::updateColorPalette);
diff --git a/src/timeline/TimelineViewManager.h b/src/timeline/TimelineViewManager.h
index 4dd5e996..8991de55 100644
--- a/src/timeline/TimelineViewManager.h
+++ b/src/timeline/TimelineViewManager.h
@@ -18,6 +18,7 @@
 
 #include "Cache.h"
 #include "CallManager.h"
+#include "JdenticonProvider.h"
 #include "Logging.h"
 #include "TimelineModel.h"
 #include "Utils.h"
@@ -141,6 +142,7 @@ private:
         MxcImageProvider *imgProvider;
         ColorImageProvider *colorImgProvider;
         BlurhashProvider *blurhashProvider;
+        JdenticonProvider *jdenticonProvider;
 
         CallManager *callManager_ = nullptr;
 
diff --git a/src/ui/Theme.h b/src/ui/Theme.h
index b5bcd4dd..254fbadf 100644
--- a/src/ui/Theme.h
+++ b/src/ui/Theme.h
@@ -11,7 +11,8 @@ namespace ui {
 enum class AvatarType
 {
         Image,
-        Letter
+        Letter,
+        Jdenticon
 };
 
 // Default font size.

From 4e4a9c6e0c2095d3d1a2fa532a2274f50506ffc8 Mon Sep 17 00:00:00 2001
From: Loren Burkholder 
Date: Mon, 25 Jan 2021 19:27:29 -0500
Subject: [PATCH 108/232] Fix braces; make lint

---
 src/JdenticonProvider.cpp | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/src/JdenticonProvider.cpp b/src/JdenticonProvider.cpp
index 4be972dc..3ec350a7 100644
--- a/src/JdenticonProvider.cpp
+++ b/src/JdenticonProvider.cpp
@@ -59,8 +59,9 @@ getJdenticonInterface()
                                         }
                                 }
                         }
-                } else
+                } else {
                         nhlog::ui()->info("jdenticon plugin not found.");
+                }
         }
 
         return interface;

From 0e931456ee8811f58c91311a77ebf4b039d26fe8 Mon Sep 17 00:00:00 2001
From: Loren Burkholder 
Date: Mon, 25 Jan 2021 20:15:40 -0500
Subject: [PATCH 109/232] Only set identicon source if used

---
 resources/qml/Avatar.qml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/resources/qml/Avatar.qml b/resources/qml/Avatar.qml
index 7bbdeba3..91245d52 100644
--- a/resources/qml/Avatar.qml
+++ b/resources/qml/Avatar.qml
@@ -43,7 +43,7 @@ Rectangle {
         id: identicon
         anchors.fill: parent
         visible: img.status != Image.Ready && Settings.useIdenticon
-        source: "image://jdenticon/" + userid
+        source: Settings.useIdenticon ? "image://jdenticon/" + userid : ""
     }
 
     Image {

From 069115ba5fd03798e80166c115c55f8c4b3d17b7 Mon Sep 17 00:00:00 2001
From: Loren Burkholder 
Date: Fri, 26 Mar 2021 19:15:22 -0400
Subject: [PATCH 110/232] Don't add toggle for jdenticon if the plugin can't be
 found

---
 src/UserSettingsPage.cpp | 14 +++++++++-----
 1 file changed, 9 insertions(+), 5 deletions(-)

diff --git a/src/UserSettingsPage.cpp b/src/UserSettingsPage.cpp
index bfe5232b..7df9d55d 100644
--- a/src/UserSettingsPage.cpp
+++ b/src/UserSettingsPage.cpp
@@ -757,7 +757,6 @@ UserSettingsPage::UserSettingsPage(QSharedPointer settings, QWidge
         trayToggle_                     = new Toggle{this};
         startInTrayToggle_              = new Toggle{this};
         avatarCircles_                  = new Toggle{this};
-        useIdenticon_                   = new Toggle{this};
         decryptSidebar_                 = new Toggle(this);
         privacyScreen_                  = new Toggle{this};
         onlyShareKeysWithVerifiedUsers_ = new Toggle(this);
@@ -791,7 +790,6 @@ UserSettingsPage::UserSettingsPage(QSharedPointer settings, QWidge
         trayToggle_->setChecked(settings_->tray());
         startInTrayToggle_->setChecked(settings_->startInTray());
         avatarCircles_->setChecked(settings_->avatarCircles());
-        useIdenticon_->setChecked(settings_->useIdenticon());
         decryptSidebar_->setChecked(settings_->decryptSidebar());
         privacyScreen_->setChecked(settings_->privacyScreen());
         onlyShareKeysWithVerifiedUsers_->setChecked(settings_->onlyShareKeysWithVerifiedUsers());
@@ -811,6 +809,11 @@ UserSettingsPage::UserSettingsPage(QSharedPointer settings, QWidge
         useStunServer_->setChecked(settings_->useStunServer());
         mobileMode_->setChecked(settings_->mobileMode());
 
+        if (JdenticonProvider::isAvailable()) {
+                useIdenticon_ = new Toggle{this};
+                useIdenticon_->setChecked(settings_->useIdenticon());
+        }
+
         if (!settings_->tray()) {
                 startInTrayToggle_->setState(false);
                 startInTrayToggle_->setDisabled(true);
@@ -1282,9 +1285,10 @@ UserSettingsPage::UserSettingsPage(QSharedPointer settings, QWidge
                 settings_->setAvatarCircles(enabled);
         });
 
-        connect(useIdenticon_, &Toggle::toggled, this, [this](bool enabled) {
-                settings_->setUseIdenticon(enabled);
-        });
+        if (JdenticonProvider::isAvailable())
+                connect(useIdenticon_, &Toggle::toggled, this, [this](bool enabled) {
+                        settings_->setUseIdenticon(enabled);
+                });
 
         connect(markdown_, &Toggle::toggled, this, [this](bool enabled) {
                 settings_->setMarkdown(enabled);

From ebe80a600674be95eedd30d3ccb70ca2171b8d40 Mon Sep 17 00:00:00 2001
From: Loren Burkholder 
Date: Fri, 26 Mar 2021 19:22:51 -0400
Subject: [PATCH 111/232] Fix typo

---
 src/CacheStructs.h | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/CacheStructs.h b/src/CacheStructs.h
index 4a5c5c76..5f4d392a 100644
--- a/src/CacheStructs.h
+++ b/src/CacheStructs.h
@@ -93,7 +93,7 @@ to_json(nlohmann::json &j, const RoomInfo &info);
 void
 from_json(const nlohmann::json &j, RoomInfo &info);
 
-//! Basic information per member;
+//! Basic information per member.
 struct MemberInfo
 {
         std::string name;

From 651d620afdd9f3e3b43a05b849d60aedd0d8a0c5 Mon Sep 17 00:00:00 2001
From: Loren Burkholder 
Date: Thu, 12 Aug 2021 19:28:11 -0400
Subject: [PATCH 112/232] Remove unnecessary stuff

---
 src/JdenticonProvider.h | 2 --
 src/MainWindow.h        | 2 --
 2 files changed, 4 deletions(-)

diff --git a/src/JdenticonProvider.h b/src/JdenticonProvider.h
index 053842bb..98dd9c8e 100644
--- a/src/JdenticonProvider.h
+++ b/src/JdenticonProvider.h
@@ -7,8 +7,6 @@
 
 #include 
 
-#include 
-
 #include "jdenticoninterface.h"
 
 namespace Jdenticon {
diff --git a/src/MainWindow.h b/src/MainWindow.h
index 18bcfe3d..d9ffb9b1 100644
--- a/src/MainWindow.h
+++ b/src/MainWindow.h
@@ -106,8 +106,6 @@ signals:
         void reload();
 
 private:
-        bool loadJdenticonPlugin();
-
         void showDialog(QWidget *dialog);
         bool hasActiveUser();
         void restoreWindowSize();

From 7a200d7e7756f82fd0d7e9ee803c5ec39e5370f0 Mon Sep 17 00:00:00 2001
From: Loren Burkholder 
Date: Thu, 12 Aug 2021 20:49:25 -0400
Subject: [PATCH 113/232] Add licenses

---
 src/JdenticonProvider.cpp | 4 ++++
 src/JdenticonProvider.h   | 4 ++++
 2 files changed, 8 insertions(+)

diff --git a/src/JdenticonProvider.cpp b/src/JdenticonProvider.cpp
index 3ec350a7..fd7ee137 100644
--- a/src/JdenticonProvider.cpp
+++ b/src/JdenticonProvider.cpp
@@ -1,3 +1,7 @@
+// SPDX-FileCopyrightText: 2021 Nheko Contributors
+//
+// SPDX-License-Identifier: GPL-3.0-or-later
+
 #include "JdenticonProvider.h"
 
 #include 
diff --git a/src/JdenticonProvider.h b/src/JdenticonProvider.h
index 98dd9c8e..bd722f2d 100644
--- a/src/JdenticonProvider.h
+++ b/src/JdenticonProvider.h
@@ -1,3 +1,7 @@
+// SPDX-FileCopyrightText: 2021 Nheko Contributors
+//
+// SPDX-License-Identifier: GPL-3.0-or-later
+
 #pragma once
 
 #include 

From 1fdecdcc213fe91711649243a315ebd11809f0f9 Mon Sep 17 00:00:00 2001
From: Loren Burkholder 
Date: Fri, 13 Aug 2021 20:05:26 -0400
Subject: [PATCH 114/232] Get direct chat jdenticons to line up

---
 resources/qml/RoomList.qml     |  3 +++
 resources/qml/TopBar.qml       |  1 +
 src/timeline/RoomlistModel.cpp |  6 ++++++
 src/timeline/RoomlistModel.h   |  2 ++
 src/timeline/TimelineModel.cpp | 13 +++++++++++++
 src/timeline/TimelineModel.h   |  4 ++++
 6 files changed, 29 insertions(+)

diff --git a/resources/qml/RoomList.qml b/resources/qml/RoomList.qml
index a0009174..fcb2644e 100644
--- a/resources/qml/RoomList.qml
+++ b/resources/qml/RoomList.qml
@@ -143,6 +143,8 @@ Page {
             required property int notificationCount
             required property bool hasLoudNotification
             required property bool hasUnreadMessages
+            required property int roomMemberCount
+            required property string directChatAvatarMxid
 
             color: background
             height: avatarSize + 2 * Nheko.paddingMedium
@@ -237,6 +239,7 @@ Page {
                     width: avatarSize
                     url: avatarUrl.replace("mxc://", "image://MxcImage/")
                     displayName: roomName
+                    userid: roomMemberCount < 3 ?  directChatAvatarMxid : roomId
 
                     Rectangle {
                         id: collapsedNotificationBubble
diff --git a/resources/qml/TopBar.qml b/resources/qml/TopBar.qml
index 7f67c028..0b50f7c6 100644
--- a/resources/qml/TopBar.qml
+++ b/resources/qml/TopBar.qml
@@ -65,6 +65,7 @@ Rectangle {
             width: Nheko.avatarSize
             height: Nheko.avatarSize
             url: avatarUrl.replace("mxc://", "image://MxcImage/")
+            userid: room.roomMemberCount < 3 ? room.directChatAvatarMxid : room.roomId
             displayName: roomName
             onClicked: {
                 if (room)
diff --git a/src/timeline/RoomlistModel.cpp b/src/timeline/RoomlistModel.cpp
index 942a4b05..88411236 100644
--- a/src/timeline/RoomlistModel.cpp
+++ b/src/timeline/RoomlistModel.cpp
@@ -76,6 +76,8 @@ RoomlistModel::roleNames() const
           {IsSpace, "isSpace"},
           {Tags, "tags"},
           {ParentSpaces, "parentSpaces"},
+          {RoomMemberCount, "roomMemberCount"},
+          {DirectChatAvatarMxid, "directChatAvatarMxid"},
         };
 }
 
@@ -129,6 +131,10 @@ RoomlistModel::data(const QModelIndex &index, int role) const
                                         list.push_back(QString::fromStdString(t));
                                 return list;
                         }
+                        case Roles::RoomMemberCount:
+                                return room->roomMemberCount();
+                        case Roles::DirectChatAvatarMxid:
+                                return room->directChatAvatarMxid();
                         default:
                                 return {};
                         }
diff --git a/src/timeline/RoomlistModel.h b/src/timeline/RoomlistModel.h
index 6ac6da18..aca74ea1 100644
--- a/src/timeline/RoomlistModel.h
+++ b/src/timeline/RoomlistModel.h
@@ -65,6 +65,8 @@ public:
                 IsPreviewFetched,
                 Tags,
                 ParentSpaces,
+                RoomMemberCount,
+                DirectChatAvatarMxid,
         };
 
         RoomlistModel(TimelineViewManager *parent = nullptr);
diff --git a/src/timeline/TimelineModel.cpp b/src/timeline/TimelineModel.cpp
index 78409e1d..4daf44f3 100644
--- a/src/timeline/TimelineModel.cpp
+++ b/src/timeline/TimelineModel.cpp
@@ -2073,3 +2073,16 @@ TimelineModel::roomMemberCount() const
 {
         return (int)cache::client()->memberCount(room_id_.toStdString());
 }
+
+QString
+TimelineModel::directChatAvatarMxid() const
+{
+        if (roomMemberCount() < 3) {
+                QString id;
+                for (auto member : cache::getMembers(room_id_.toStdString()))
+                        if (member.user_id != UserSettings::instance()->userId())
+                                id = member.user_id;
+                return id;
+        } else
+                return "";
+}
diff --git a/src/timeline/TimelineModel.h b/src/timeline/TimelineModel.h
index 417fbb7f..8c34fc1d 100644
--- a/src/timeline/TimelineModel.h
+++ b/src/timeline/TimelineModel.h
@@ -176,6 +176,8 @@ class TimelineModel : public QAbstractListModel
         Q_PROPERTY(bool isEncrypted READ isEncrypted NOTIFY encryptionChanged)
         Q_PROPERTY(bool isSpace READ isSpace CONSTANT)
         Q_PROPERTY(int trustlevel READ trustlevel NOTIFY trustlevelChanged)
+        Q_PROPERTY(
+          QString directChatAvatarMxid READ directChatAvatarMxid NOTIFY directChatAvatarMxidChanged)
         Q_PROPERTY(InputBar *input READ input CONSTANT)
         Q_PROPERTY(Permissions *permissions READ permissions NOTIFY permissionsChanged)
 
@@ -292,6 +294,7 @@ public:
         bool isEncrypted() const { return isEncrypted_; }
         crypto::Trust trustlevel() const;
         int roomMemberCount() const;
+        QString directChatAvatarMxid() const;
 
         std::optional eventById(const QString &id)
         {
@@ -391,6 +394,7 @@ signals:
         void roomTopicChanged();
         void roomAvatarUrlChanged();
         void roomMemberCountChanged();
+        void directChatAvatarMxidChanged();
         void permissionsChanged();
         void forwardToRoom(mtx::events::collections::TimelineEvents *e, QString roomId);
 

From 350d1977af35ee9cc59f4912593c4083a64dc7d1 Mon Sep 17 00:00:00 2001
From: Loren Burkholder 
Date: Fri, 13 Aug 2021 20:05:51 -0400
Subject: [PATCH 115/232] Add some fancy effects to jdenticon

---
 resources/qml/Avatar.qml  | 14 +++++++++++++-
 src/JdenticonProvider.cpp | 18 +++++++++++++++---
 src/JdenticonProvider.h   | 28 +++++++++++++++++++++++++---
 3 files changed, 53 insertions(+), 7 deletions(-)

diff --git a/resources/qml/Avatar.qml b/resources/qml/Avatar.qml
index 91245d52..1b7497c1 100644
--- a/resources/qml/Avatar.qml
+++ b/resources/qml/Avatar.qml
@@ -43,7 +43,19 @@ Rectangle {
         id: identicon
         anchors.fill: parent
         visible: img.status != Image.Ready && Settings.useIdenticon
-        source: Settings.useIdenticon ? "image://jdenticon/" + userid : ""
+        source: Settings.useIdenticon ? "image://jdenticon/" + userid + "?radius=" + radius : ""
+        layer.enabled: true
+
+        MouseArea {
+            anchors.fill: parent
+
+            Ripple {
+                rippleTarget: parent
+                color: Qt.rgba(Nheko.colors.alternateBase.r, Nheko.colors.alternateBase.g, Nheko.colors.alternateBase.b, 0.5)
+            }
+
+        }
+
     }
 
     Image {
diff --git a/src/JdenticonProvider.cpp b/src/JdenticonProvider.cpp
index fd7ee137..5495b2d3 100644
--- a/src/JdenticonProvider.cpp
+++ b/src/JdenticonProvider.cpp
@@ -7,6 +7,7 @@
 #include 
 #include 
 #include 
+#include 
 #include 
 #include 
 
@@ -18,8 +19,13 @@
 #include "Utils.h"
 #include "jdenticoninterface.h"
 
-JdenticonResponse::JdenticonResponse(const QString &key, const QSize &requestedSize)
+JdenticonResponse::JdenticonResponse(const QString &key,
+                                     bool crop,
+                                     double radius,
+                                     const QSize &requestedSize)
   : m_key(key)
+  , m_crop{crop}
+  , m_radius{radius}
   , m_requestedSize(requestedSize.isValid() ? requestedSize : QSize(100, 100))
   , m_pixmap{m_requestedSize}
   , jdenticonInterface_{Jdenticon::getJdenticonInterface()}
@@ -32,10 +38,16 @@ JdenticonResponse::run()
 {
         m_pixmap.fill(Qt::transparent);
         QPainter painter{&m_pixmap};
+        painter.setRenderHint(QPainter::Antialiasing, true);
+        painter.setRenderHint(QPainter::SmoothPixmapTransform, true);
+
+        QPainterPath ppath;
+        ppath.addRoundedRect(m_pixmap.rect(), m_radius, m_radius);
+
+        painter.setClipPath(ppath);
+
         QSvgRenderer renderer{
           jdenticonInterface_->generate(m_key, m_requestedSize.width()).toUtf8()};
-        //        m_image = QImage::fromData(jdenticonInterface_->generate(m_key,
-        //        size->width()).toUtf8());
         renderer.render(&painter);
 
         emit finished();
diff --git a/src/JdenticonProvider.h b/src/JdenticonProvider.h
index bd722f2d..2497687f 100644
--- a/src/JdenticonProvider.h
+++ b/src/JdenticonProvider.h
@@ -23,7 +23,7 @@ class JdenticonResponse
   , public QRunnable
 {
 public:
-        JdenticonResponse(const QString &key, const QSize &requestedSize);
+        JdenticonResponse(const QString &key, bool crop, double radius, const QSize &requestedSize);
 
         QQuickTextureFactory *textureFactory() const override
         {
@@ -33,6 +33,8 @@ public:
         void run() override;
 
         QString m_key;
+        bool m_crop;
+        double m_radius;
         QSize m_requestedSize;
         QPixmap m_pixmap;
         JdenticonInterface *jdenticonInterface_ = nullptr;
@@ -48,10 +50,30 @@ public:
         static bool isAvailable() { return Jdenticon::getJdenticonInterface() != nullptr; }
 
 public slots:
-        QQuickImageResponse *requestImageResponse(const QString &key,
+        QQuickImageResponse *requestImageResponse(const QString &id,
                                                   const QSize &requestedSize) override
         {
-                JdenticonResponse *response = new JdenticonResponse(key, requestedSize);
+                auto id_      = id;
+                bool crop     = true;
+                double radius = 0;
+
+                auto queryStart = id.lastIndexOf('?');
+                if (queryStart != -1) {
+                        id_            = id.left(queryStart);
+                        auto query     = id.midRef(queryStart + 1);
+                        auto queryBits = query.split('&');
+
+                        for (auto b : queryBits) {
+                                if (b == "scale") {
+                                        crop = false;
+                                } else if (b.startsWith("radius=")) {
+                                        radius = b.mid(7).toDouble();
+                                }
+                        }
+                }
+
+                JdenticonResponse *response =
+                  new JdenticonResponse(id_, crop, radius, requestedSize);
                 pool.start(response);
                 return response;
         }

From c991f20284620ca0483b00b828c3ae574a14a2ef Mon Sep 17 00:00:00 2001
From: Loren Burkholder 
Date: Mon, 30 Aug 2021 08:00:35 -0400
Subject: [PATCH 116/232] Make sure jdenticon toggle is always initialized

---
 src/UserSettingsPage.cpp | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/src/UserSettingsPage.cpp b/src/UserSettingsPage.cpp
index 7df9d55d..60ba18aa 100644
--- a/src/UserSettingsPage.cpp
+++ b/src/UserSettingsPage.cpp
@@ -813,6 +813,8 @@ UserSettingsPage::UserSettingsPage(QSharedPointer settings, QWidge
                 useIdenticon_ = new Toggle{this};
                 useIdenticon_->setChecked(settings_->useIdenticon());
         }
+        else
+            useIdenticon_ = nullptr;
 
         if (!settings_->tray()) {
                 startInTrayToggle_->setState(false);

From 1ac4f3a97b7920f310c4523aec2dfb6758c2bd24 Mon Sep 17 00:00:00 2001
From: Loren Burkholder 
Date: Mon, 30 Aug 2021 08:01:26 -0400
Subject: [PATCH 117/232] Remove unused struct

---
 src/ui/Theme.h | 7 -------
 1 file changed, 7 deletions(-)

diff --git a/src/ui/Theme.h b/src/ui/Theme.h
index 254fbadf..cc39714b 100644
--- a/src/ui/Theme.h
+++ b/src/ui/Theme.h
@@ -8,13 +8,6 @@
 #include 
 
 namespace ui {
-enum class AvatarType
-{
-        Image,
-        Letter,
-        Jdenticon
-};
-
 // Default font size.
 const int FontSize = 16;
 

From 350fc593ed0a6b36f63a2fabc3918d346597c7c5 Mon Sep 17 00:00:00 2001
From: Loren Burkholder 
Date: Mon, 30 Aug 2021 20:08:47 -0400
Subject: [PATCH 118/232] Use better id loading methodology

---
 resources/qml/Avatar.qml       | 3 ++-
 resources/qml/RoomList.qml     | 5 +++--
 resources/qml/TopBar.qml       | 3 ++-
 src/timeline/RoomlistModel.cpp | 6 +++---
 src/timeline/RoomlistModel.h   | 2 +-
 src/timeline/TimelineModel.cpp | 6 ++++++
 src/timeline/TimelineModel.h   | 3 +++
 7 files changed, 20 insertions(+), 8 deletions(-)

diff --git a/resources/qml/Avatar.qml b/resources/qml/Avatar.qml
index 1b7497c1..bf936bff 100644
--- a/resources/qml/Avatar.qml
+++ b/resources/qml/Avatar.qml
@@ -12,6 +12,7 @@ Rectangle {
 
     property string url
     property string userid
+    property string roomid
     property string displayName
     property alias textColor: label.color
     property bool crop: true
@@ -43,7 +44,7 @@ Rectangle {
         id: identicon
         anchors.fill: parent
         visible: img.status != Image.Ready && Settings.useIdenticon
-        source: Settings.useIdenticon ? "image://jdenticon/" + userid + "?radius=" + radius : ""
+        source: Settings.useIdenticon ? ("image://jdenticon/" + (userid !== "" ? userid : roomid) + "?radius=" + (Settings.avatarCircles ? 100 : 25) + ((avatar.crop) ? "" : "&scale")) : ""
         layer.enabled: true
 
         MouseArea {
diff --git a/resources/qml/RoomList.qml b/resources/qml/RoomList.qml
index fcb2644e..3b7fa84e 100644
--- a/resources/qml/RoomList.qml
+++ b/resources/qml/RoomList.qml
@@ -143,7 +143,7 @@ Page {
             required property int notificationCount
             required property bool hasLoudNotification
             required property bool hasUnreadMessages
-            required property int roomMemberCount
+            required property bool isDirect
             required property string directChatAvatarMxid
 
             color: background
@@ -239,7 +239,8 @@ Page {
                     width: avatarSize
                     url: avatarUrl.replace("mxc://", "image://MxcImage/")
                     displayName: roomName
-                    userid: roomMemberCount < 3 ?  directChatAvatarMxid : roomId
+                    userid: isDirect ? directChatAvatarMxid : undefined
+                    roomid: roomId
 
                     Rectangle {
                         id: collapsedNotificationBubble
diff --git a/resources/qml/TopBar.qml b/resources/qml/TopBar.qml
index 0b50f7c6..78f81b7f 100644
--- a/resources/qml/TopBar.qml
+++ b/resources/qml/TopBar.qml
@@ -65,7 +65,8 @@ Rectangle {
             width: Nheko.avatarSize
             height: Nheko.avatarSize
             url: avatarUrl.replace("mxc://", "image://MxcImage/")
-            userid: room.roomMemberCount < 3 ? room.directChatAvatarMxid : room.roomId
+            roomid: room.roomId
+            userid: room.isDirect ? room.directChatAvatarMxid : undefined
             displayName: roomName
             onClicked: {
                 if (room)
diff --git a/src/timeline/RoomlistModel.cpp b/src/timeline/RoomlistModel.cpp
index 88411236..d12fb426 100644
--- a/src/timeline/RoomlistModel.cpp
+++ b/src/timeline/RoomlistModel.cpp
@@ -76,7 +76,7 @@ RoomlistModel::roleNames() const
           {IsSpace, "isSpace"},
           {Tags, "tags"},
           {ParentSpaces, "parentSpaces"},
-          {RoomMemberCount, "roomMemberCount"},
+          {IsDirect, "isDirect"},
           {DirectChatAvatarMxid, "directChatAvatarMxid"},
         };
 }
@@ -131,8 +131,8 @@ RoomlistModel::data(const QModelIndex &index, int role) const
                                         list.push_back(QString::fromStdString(t));
                                 return list;
                         }
-                        case Roles::RoomMemberCount:
-                                return room->roomMemberCount();
+                        case Roles::IsDirect:
+                                return room->isDirect();
                         case Roles::DirectChatAvatarMxid:
                                 return room->directChatAvatarMxid();
                         default:
diff --git a/src/timeline/RoomlistModel.h b/src/timeline/RoomlistModel.h
index aca74ea1..57669087 100644
--- a/src/timeline/RoomlistModel.h
+++ b/src/timeline/RoomlistModel.h
@@ -65,7 +65,7 @@ public:
                 IsPreviewFetched,
                 Tags,
                 ParentSpaces,
-                RoomMemberCount,
+                IsDirect,
                 DirectChatAvatarMxid,
         };
 
diff --git a/src/timeline/TimelineModel.cpp b/src/timeline/TimelineModel.cpp
index 4daf44f3..0e1e8f5d 100644
--- a/src/timeline/TimelineModel.cpp
+++ b/src/timeline/TimelineModel.cpp
@@ -817,6 +817,12 @@ TimelineModel::syncState(const mtx::responses::State &s)
                         emit roomAvatarUrlChanged();
                         emit roomNameChanged();
                         emit roomMemberCountChanged();
+
+                        if (roomMemberCount() <= 2)
+                        {
+                            emit isDirectChanged();
+                            emit directChatAvatarMxidChanged();
+                        }
                 } else if (std::holds_alternative>(e)) {
                         this->isEncrypted_ = cache::isRoomEncrypted(room_id_.toStdString());
                         emit encryptionChanged();
diff --git a/src/timeline/TimelineModel.h b/src/timeline/TimelineModel.h
index 8c34fc1d..03e33066 100644
--- a/src/timeline/TimelineModel.h
+++ b/src/timeline/TimelineModel.h
@@ -176,6 +176,7 @@ class TimelineModel : public QAbstractListModel
         Q_PROPERTY(bool isEncrypted READ isEncrypted NOTIFY encryptionChanged)
         Q_PROPERTY(bool isSpace READ isSpace CONSTANT)
         Q_PROPERTY(int trustlevel READ trustlevel NOTIFY trustlevelChanged)
+        Q_PROPERTY(bool isDirect READ isDirect NOTIFY isDirectChanged)
         Q_PROPERTY(
           QString directChatAvatarMxid READ directChatAvatarMxid NOTIFY directChatAvatarMxidChanged)
         Q_PROPERTY(InputBar *input READ input CONSTANT)
@@ -294,6 +295,7 @@ public:
         bool isEncrypted() const { return isEncrypted_; }
         crypto::Trust trustlevel() const;
         int roomMemberCount() const;
+        bool isDirect() const { return roomMemberCount() <= 2; } // TODO: handle invites
         QString directChatAvatarMxid() const;
 
         std::optional eventById(const QString &id)
@@ -394,6 +396,7 @@ signals:
         void roomTopicChanged();
         void roomAvatarUrlChanged();
         void roomMemberCountChanged();
+        void isDirectChanged();
         void directChatAvatarMxidChanged();
         void permissionsChanged();
         void forwardToRoom(mtx::events::collections::TimelineEvents *e, QString roomId);

From dcdf00dcc5785be21514e6eb4cebe78f39691e7f Mon Sep 17 00:00:00 2001
From: Loren Burkholder 
Date: Mon, 30 Aug 2021 20:19:17 -0400
Subject: [PATCH 119/232] Finish fixing rounded avatars

---
 src/JdenticonProvider.cpp | 34 +++++++++++++++++++++++++++-------
 1 file changed, 27 insertions(+), 7 deletions(-)

diff --git a/src/JdenticonProvider.cpp b/src/JdenticonProvider.cpp
index 5495b2d3..c76e2b23 100644
--- a/src/JdenticonProvider.cpp
+++ b/src/JdenticonProvider.cpp
@@ -19,6 +19,25 @@
 #include "Utils.h"
 #include "jdenticoninterface.h"
 
+static QPixmap
+clipRadius(QPixmap img, double radius)
+{
+        QPixmap out(img.size());
+        out.fill(Qt::transparent);
+
+        QPainter painter(&out);
+        painter.setRenderHint(QPainter::Antialiasing, true);
+        painter.setRenderHint(QPainter::SmoothPixmapTransform, true);
+
+        QPainterPath ppath;
+        ppath.addRoundedRect(img.rect(), radius, radius, Qt::SizeMode::RelativeSize);
+
+        painter.setClipPath(ppath);
+        painter.drawPixmap(img.rect(), img);
+
+        return out;
+}
+
 JdenticonResponse::JdenticonResponse(const QString &key,
                                      bool crop,
                                      double radius,
@@ -37,19 +56,20 @@ void
 JdenticonResponse::run()
 {
         m_pixmap.fill(Qt::transparent);
-        QPainter painter{&m_pixmap};
+
+        QPainter painter;
+        painter.begin(&m_pixmap);
         painter.setRenderHint(QPainter::Antialiasing, true);
         painter.setRenderHint(QPainter::SmoothPixmapTransform, true);
 
-        QPainterPath ppath;
-        ppath.addRoundedRect(m_pixmap.rect(), m_radius, m_radius);
-
-        painter.setClipPath(ppath);
-
         QSvgRenderer renderer{
           jdenticonInterface_->generate(m_key, m_requestedSize.width()).toUtf8()};
         renderer.render(&painter);
 
+        painter.end();
+
+        m_pixmap = clipRadius(m_pixmap, m_radius);
+
         emit finished();
 }
 
@@ -64,7 +84,7 @@ getJdenticonInterface()
 
                 bool plugins = pluginsDir.cd("plugins");
                 if (plugins) {
-                        for (QString fileName : pluginsDir.entryList(QDir::Files)) {
+                        for (const QString &fileName : pluginsDir.entryList(QDir::Files)) {
                                 QPluginLoader pluginLoader(pluginsDir.absoluteFilePath(fileName));
                                 QObject *plugin = pluginLoader.instance();
                                 if (plugin) {

From a23c586cde75ebaa7583017f96a73e5cf9e7d348 Mon Sep 17 00:00:00 2001
From: Loren Burkholder 
Date: Tue, 31 Aug 2021 17:58:00 -0400
Subject: [PATCH 120/232] make lint

---
 src/UserSettingsPage.cpp       | 5 ++---
 src/timeline/TimelineModel.cpp | 7 +++----
 2 files changed, 5 insertions(+), 7 deletions(-)

diff --git a/src/UserSettingsPage.cpp b/src/UserSettingsPage.cpp
index 60ba18aa..d79cadbe 100644
--- a/src/UserSettingsPage.cpp
+++ b/src/UserSettingsPage.cpp
@@ -812,9 +812,8 @@ UserSettingsPage::UserSettingsPage(QSharedPointer settings, QWidge
         if (JdenticonProvider::isAvailable()) {
                 useIdenticon_ = new Toggle{this};
                 useIdenticon_->setChecked(settings_->useIdenticon());
-        }
-        else
-            useIdenticon_ = nullptr;
+        } else
+                useIdenticon_ = nullptr;
 
         if (!settings_->tray()) {
                 startInTrayToggle_->setState(false);
diff --git a/src/timeline/TimelineModel.cpp b/src/timeline/TimelineModel.cpp
index 0e1e8f5d..d5c39bbe 100644
--- a/src/timeline/TimelineModel.cpp
+++ b/src/timeline/TimelineModel.cpp
@@ -818,10 +818,9 @@ TimelineModel::syncState(const mtx::responses::State &s)
                         emit roomNameChanged();
                         emit roomMemberCountChanged();
 
-                        if (roomMemberCount() <= 2)
-                        {
-                            emit isDirectChanged();
-                            emit directChatAvatarMxidChanged();
+                        if (roomMemberCount() <= 2) {
+                                emit isDirectChanged();
+                                emit directChatAvatarMxidChanged();
                         }
                 } else if (std::holds_alternative>(e)) {
                         this->isEncrypted_ = cache::isRoomEncrypted(room_id_.toStdString());

From 3a86d44c1ebe2181dd274d0f9d72723c82ed205a Mon Sep 17 00:00:00 2001
From: Loren Burkholder 
Date: Tue, 31 Aug 2021 21:53:12 -0400
Subject: [PATCH 121/232] Finish getting all avatars jdenticonified

---
 resources/qml/CommunitiesList.qml                 | 1 +
 resources/qml/Completer.qml                       | 3 +++
 resources/qml/RoomDirectory.qml                   | 1 +
 resources/qml/RoomMembers.qml                     | 1 +
 resources/qml/RoomSettings.qml                    | 1 +
 resources/qml/TimelineView.qml                    | 2 ++
 resources/qml/components/AvatarListTile.qml       | 2 ++
 resources/qml/dialogs/ImagePackEditorDialog.qml   | 1 +
 resources/qml/dialogs/ImagePackSettingsDialog.qml | 1 +
 resources/qml/voip/ActiveCallBar.qml              | 1 +
 resources/qml/voip/PlaceCall.qml                  | 1 +
 11 files changed, 15 insertions(+)

diff --git a/resources/qml/CommunitiesList.qml b/resources/qml/CommunitiesList.qml
index 491913be..121eb7db 100644
--- a/resources/qml/CommunitiesList.qml
+++ b/resources/qml/CommunitiesList.qml
@@ -130,6 +130,7 @@ Page {
                         else
                             return "image://colorimage/" + model.avatarUrl + "?" + communityItem.unimportantText;
                     }
+                    roomid: model.roomid
                     displayName: model.displayName
                     color: communityItem.background
                 }
diff --git a/resources/qml/Completer.qml b/resources/qml/Completer.qml
index 00fc3216..6bde67fa 100644
--- a/resources/qml/Completer.qml
+++ b/resources/qml/Completer.qml
@@ -139,6 +139,7 @@ Popup {
                             height: popup.avatarHeight
                             width: popup.avatarWidth
                             displayName: model.displayName
+                            userid: model.userid
                             url: model.avatarUrl.replace("mxc://", "image://MxcImage/")
                             onClicked: popup.completionClicked(completer.completionAt(model.index))
                         }
@@ -194,6 +195,7 @@ Popup {
                             height: popup.avatarHeight
                             width: popup.avatarWidth
                             displayName: model.roomName
+                            roomid: model.roomid
                             url: model.avatarUrl.replace("mxc://", "image://MxcImage/")
                             onClicked: {
                                 popup.completionClicked(completer.completionAt(model.index));
@@ -225,6 +227,7 @@ Popup {
                             height: popup.avatarHeight
                             width: popup.avatarWidth
                             displayName: model.roomName
+                            roomid: model.roomid
                             url: model.avatarUrl.replace("mxc://", "image://MxcImage/")
                             onClicked: popup.completionClicked(completer.completionAt(model.index))
                         }
diff --git a/resources/qml/RoomDirectory.qml b/resources/qml/RoomDirectory.qml
index 2d7b3a34..b51c7bbc 100644
--- a/resources/qml/RoomDirectory.qml
+++ b/resources/qml/RoomDirectory.qml
@@ -65,6 +65,7 @@ ApplicationWindow {
                     width: avatarSize
                     height: avatarSize
                     url: model.avatarUrl.replace("mxc://", "image://MxcImage/")
+                    roomid: model.roomid
                     displayName: model.name
                 }
 
diff --git a/resources/qml/RoomMembers.qml b/resources/qml/RoomMembers.qml
index 62175bf0..3376a4b6 100644
--- a/resources/qml/RoomMembers.qml
+++ b/resources/qml/RoomMembers.qml
@@ -39,6 +39,7 @@ ApplicationWindow {
 
             width: 130
             height: width
+            roomid: members.roomId
             displayName: members.roomName
             Layout.alignment: Qt.AlignHCenter
             url: members.avatarUrl.replace("mxc://", "image://MxcImage/")
diff --git a/resources/qml/RoomSettings.qml b/resources/qml/RoomSettings.qml
index a70cd71a..152567c8 100644
--- a/resources/qml/RoomSettings.qml
+++ b/resources/qml/RoomSettings.qml
@@ -38,6 +38,7 @@ ApplicationWindow {
 
         Avatar {
             url: roomSettings.roomAvatarUrl.replace("mxc://", "image://MxcImage/")
+            roomid: roomSettings.roomid
             displayName: roomSettings.roomName
             height: 130
             width: 130
diff --git a/resources/qml/TimelineView.qml b/resources/qml/TimelineView.qml
index f12060f2..b38df44f 100644
--- a/resources/qml/TimelineView.qml
+++ b/resources/qml/TimelineView.qml
@@ -137,6 +137,7 @@ Item {
     ColumnLayout {
         id: preview
 
+        property string roomid: room ? room.roomid : (roomPreview ? roomPreview.roomid : "")
         property string roomName: room ? room.roomName : (roomPreview ? roomPreview.roomName : "")
         property string roomTopic: room ? room.roomTopic : (roomPreview ? roomPreview.roomTopic : "")
         property string avatarUrl: room ? room.roomAvatarUrl : (roomPreview ? roomPreview.roomAvatarUrl : "")
@@ -153,6 +154,7 @@ Item {
 
         Avatar {
             url: parent.avatarUrl.replace("mxc://", "image://MxcImage/")
+            roomid: parent.roomid
             displayName: parent.roomName
             height: 130
             width: 130
diff --git a/resources/qml/components/AvatarListTile.qml b/resources/qml/components/AvatarListTile.qml
index 36c26a97..853266c6 100644
--- a/resources/qml/components/AvatarListTile.qml
+++ b/resources/qml/components/AvatarListTile.qml
@@ -23,6 +23,8 @@ Rectangle {
     required property int index
     required property int selectedIndex
     property bool crop: true
+    property alias roomid: avatar.roomid
+    property alias userid: avatar.userid
 
     color: background
     height: avatarSize + 2 * Nheko.paddingMedium
diff --git a/resources/qml/dialogs/ImagePackEditorDialog.qml b/resources/qml/dialogs/ImagePackEditorDialog.qml
index e78213e0..9125e560 100644
--- a/resources/qml/dialogs/ImagePackEditorDialog.qml
+++ b/resources/qml/dialogs/ImagePackEditorDialog.qml
@@ -111,6 +111,7 @@ ApplicationWindow {
                     title: shortCode
                     subtitle: body
                     avatarUrl: url
+                    roomid: imagePack.roomid
                     selectedIndex: currentImageIndex
                     crop: false
 
diff --git a/resources/qml/dialogs/ImagePackSettingsDialog.qml b/resources/qml/dialogs/ImagePackSettingsDialog.qml
index b217abdd..4cc7bcd3 100644
--- a/resources/qml/dialogs/ImagePackSettingsDialog.qml
+++ b/resources/qml/dialogs/ImagePackSettingsDialog.qml
@@ -112,6 +112,7 @@ ApplicationWindow {
                             return qsTr("Globally enabled pack");
                     }
                     selectedIndex: currentPackIndex
+                    roomid: currentPack.roomid
 
                     TapHandler {
                         onSingleTapped: currentPackIndex = index
diff --git a/resources/qml/voip/ActiveCallBar.qml b/resources/qml/voip/ActiveCallBar.qml
index d44c5edf..c92b15c9 100644
--- a/resources/qml/voip/ActiveCallBar.qml
+++ b/resources/qml/voip/ActiveCallBar.qml
@@ -35,6 +35,7 @@ Rectangle {
             height: Nheko.avatarSize
             url: CallManager.callPartyAvatarUrl.replace("mxc://", "image://MxcImage/")
             displayName: CallManager.callParty
+            userid: CallManager.callParty
             onClicked: TimelineManager.openImageOverlay(room.avatarUrl(userid), room.data.eventId)
         }
 
diff --git a/resources/qml/voip/PlaceCall.qml b/resources/qml/voip/PlaceCall.qml
index 97932cc9..c733012c 100644
--- a/resources/qml/voip/PlaceCall.qml
+++ b/resources/qml/voip/PlaceCall.qml
@@ -79,6 +79,7 @@ Popup {
                 height: Nheko.avatarSize
                 url: room.roomAvatarUrl.replace("mxc://", "image://MxcImage/")
                 displayName: room.roomName
+                roomid: room.roomid
                 onClicked: TimelineManager.openImageOverlay(room.avatarUrl(userid), room.data.eventId)
             }
 

From 356723fe0644f6e05f934262ea80dca2d33330c3 Mon Sep 17 00:00:00 2001
From: Loren Burkholder 
Date: Sat, 4 Sep 2021 20:53:33 -0400
Subject: [PATCH 122/232] Use more descriptive property name

---
 resources/qml/RoomList.qml     | 4 ++--
 resources/qml/TopBar.qml       | 2 +-
 src/timeline/RoomlistModel.cpp | 6 +++---
 src/timeline/RoomlistModel.h   | 2 +-
 src/timeline/TimelineModel.cpp | 4 ++--
 src/timeline/TimelineModel.h   | 6 +++---
 6 files changed, 12 insertions(+), 12 deletions(-)

diff --git a/resources/qml/RoomList.qml b/resources/qml/RoomList.qml
index 3b7fa84e..addbf571 100644
--- a/resources/qml/RoomList.qml
+++ b/resources/qml/RoomList.qml
@@ -144,7 +144,7 @@ Page {
             required property bool hasLoudNotification
             required property bool hasUnreadMessages
             required property bool isDirect
-            required property string directChatAvatarMxid
+            required property string directChatOtherUserId
 
             color: background
             height: avatarSize + 2 * Nheko.paddingMedium
@@ -239,7 +239,7 @@ Page {
                     width: avatarSize
                     url: avatarUrl.replace("mxc://", "image://MxcImage/")
                     displayName: roomName
-                    userid: isDirect ? directChatAvatarMxid : undefined
+                    userid: isDirect ? directChatOtherUserId : ""
                     roomid: roomId
 
                     Rectangle {
diff --git a/resources/qml/TopBar.qml b/resources/qml/TopBar.qml
index 78f81b7f..a8a53a24 100644
--- a/resources/qml/TopBar.qml
+++ b/resources/qml/TopBar.qml
@@ -66,7 +66,7 @@ Rectangle {
             height: Nheko.avatarSize
             url: avatarUrl.replace("mxc://", "image://MxcImage/")
             roomid: room.roomId
-            userid: room.isDirect ? room.directChatAvatarMxid : undefined
+            userid: room.isDirect ? room.directChatOtherUserId : ""
             displayName: roomName
             onClicked: {
                 if (room)
diff --git a/src/timeline/RoomlistModel.cpp b/src/timeline/RoomlistModel.cpp
index d12fb426..094b0df6 100644
--- a/src/timeline/RoomlistModel.cpp
+++ b/src/timeline/RoomlistModel.cpp
@@ -77,7 +77,7 @@ RoomlistModel::roleNames() const
           {Tags, "tags"},
           {ParentSpaces, "parentSpaces"},
           {IsDirect, "isDirect"},
-          {DirectChatAvatarMxid, "directChatAvatarMxid"},
+          {DirectChatOtherUserId, "directChatOtherUserId"},
         };
 }
 
@@ -133,8 +133,8 @@ RoomlistModel::data(const QModelIndex &index, int role) const
                         }
                         case Roles::IsDirect:
                                 return room->isDirect();
-                        case Roles::DirectChatAvatarMxid:
-                                return room->directChatAvatarMxid();
+                        case Roles::DirectChatOtherUserId:
+                                return room->directChatOtherUserId();
                         default:
                                 return {};
                         }
diff --git a/src/timeline/RoomlistModel.h b/src/timeline/RoomlistModel.h
index 57669087..c0a87aee 100644
--- a/src/timeline/RoomlistModel.h
+++ b/src/timeline/RoomlistModel.h
@@ -66,7 +66,7 @@ public:
                 Tags,
                 ParentSpaces,
                 IsDirect,
-                DirectChatAvatarMxid,
+                DirectChatOtherUserId,
         };
 
         RoomlistModel(TimelineViewManager *parent = nullptr);
diff --git a/src/timeline/TimelineModel.cpp b/src/timeline/TimelineModel.cpp
index d5c39bbe..ca303040 100644
--- a/src/timeline/TimelineModel.cpp
+++ b/src/timeline/TimelineModel.cpp
@@ -820,7 +820,7 @@ TimelineModel::syncState(const mtx::responses::State &s)
 
                         if (roomMemberCount() <= 2) {
                                 emit isDirectChanged();
-                                emit directChatAvatarMxidChanged();
+                                emit directChatOtherUserIdChanged();
                         }
                 } else if (std::holds_alternative>(e)) {
                         this->isEncrypted_ = cache::isRoomEncrypted(room_id_.toStdString());
@@ -2080,7 +2080,7 @@ TimelineModel::roomMemberCount() const
 }
 
 QString
-TimelineModel::directChatAvatarMxid() const
+TimelineModel::directChatOtherUserId() const
 {
         if (roomMemberCount() < 3) {
                 QString id;
diff --git a/src/timeline/TimelineModel.h b/src/timeline/TimelineModel.h
index 03e33066..582c6b28 100644
--- a/src/timeline/TimelineModel.h
+++ b/src/timeline/TimelineModel.h
@@ -178,7 +178,7 @@ class TimelineModel : public QAbstractListModel
         Q_PROPERTY(int trustlevel READ trustlevel NOTIFY trustlevelChanged)
         Q_PROPERTY(bool isDirect READ isDirect NOTIFY isDirectChanged)
         Q_PROPERTY(
-          QString directChatAvatarMxid READ directChatAvatarMxid NOTIFY directChatAvatarMxidChanged)
+          QString directChatOtherUserId READ directChatOtherUserId NOTIFY directChatOtherUserIdChanged)
         Q_PROPERTY(InputBar *input READ input CONSTANT)
         Q_PROPERTY(Permissions *permissions READ permissions NOTIFY permissionsChanged)
 
@@ -296,7 +296,7 @@ public:
         crypto::Trust trustlevel() const;
         int roomMemberCount() const;
         bool isDirect() const { return roomMemberCount() <= 2; } // TODO: handle invites
-        QString directChatAvatarMxid() const;
+        QString directChatOtherUserId() const;
 
         std::optional eventById(const QString &id)
         {
@@ -397,7 +397,7 @@ signals:
         void roomAvatarUrlChanged();
         void roomMemberCountChanged();
         void isDirectChanged();
-        void directChatAvatarMxidChanged();
+        void directChatOtherUserIdChanged();
         void permissionsChanged();
         void forwardToRoom(mtx::events::collections::TimelineEvents *e, QString roomId);
 

From 17729ce662528be1949b3157513d9c4a1db987de Mon Sep 17 00:00:00 2001
From: Loren Burkholder 
Date: Sat, 4 Sep 2021 20:54:02 -0400
Subject: [PATCH 123/232] Fix jdenticons in various places

---
 resources/qml/dialogs/ImagePackEditorDialog.qml   | 4 +++-
 resources/qml/dialogs/ImagePackSettingsDialog.qml | 3 ++-
 resources/qml/voip/ActiveCallBar.qml              | 4 ++--
 resources/qml/voip/CallInvite.qml                 | 5 +++--
 resources/qml/voip/CallInviteBar.qml              | 5 +++--
 src/CallManager.cpp                               | 9 +++++++--
 src/CallManager.h                                 | 3 +++
 7 files changed, 23 insertions(+), 10 deletions(-)

diff --git a/resources/qml/dialogs/ImagePackEditorDialog.qml b/resources/qml/dialogs/ImagePackEditorDialog.qml
index 9125e560..f927a745 100644
--- a/resources/qml/dialogs/ImagePackEditorDialog.qml
+++ b/resources/qml/dialogs/ImagePackEditorDialog.qml
@@ -61,6 +61,7 @@ ApplicationWindow {
                 header: AvatarListTile {
                     title: imagePack.packname
                     avatarUrl: imagePack.avatarUrl
+                    userid: imagePack.packname
                     subtitle: imagePack.statekey
                     index: -1
                     selectedIndex: currentImageIndex
@@ -111,7 +112,6 @@ ApplicationWindow {
                     title: shortCode
                     subtitle: body
                     avatarUrl: url
-                    roomid: imagePack.roomid
                     selectedIndex: currentImageIndex
                     crop: false
 
@@ -143,6 +143,7 @@ ApplicationWindow {
                         Layout.columnSpan: 2
                         url: imagePack.avatarUrl.replace("mxc://", "image://MxcImage/")
                         displayName: imagePack.packname
+                        userid: imagePack.packname
                         height: 130
                         width: 130
                         crop: false
@@ -220,6 +221,7 @@ ApplicationWindow {
                         Layout.columnSpan: 2
                         url: imagePack.data(imagePack.index(currentImageIndex, 0), SingleImagePackModel.Url).replace("mxc://", "image://MxcImage/")
                         displayName: imagePack.data(imagePack.index(currentImageIndex, 0), SingleImagePackModel.ShortCode)
+                        userid: displayName
                         height: 130
                         width: 130
                         crop: false
diff --git a/resources/qml/dialogs/ImagePackSettingsDialog.qml b/resources/qml/dialogs/ImagePackSettingsDialog.qml
index 4cc7bcd3..4aee4a78 100644
--- a/resources/qml/dialogs/ImagePackSettingsDialog.qml
+++ b/resources/qml/dialogs/ImagePackSettingsDialog.qml
@@ -112,7 +112,7 @@ ApplicationWindow {
                             return qsTr("Globally enabled pack");
                     }
                     selectedIndex: currentPackIndex
-                    roomid: currentPack.roomid
+                    userid: displayName
 
                     TapHandler {
                         onSingleTapped: currentPackIndex = index
@@ -144,6 +144,7 @@ ApplicationWindow {
                     Avatar {
                         url: packinfo.avatarUrl.replace("mxc://", "image://MxcImage/")
                         displayName: packinfo.packName
+                        userid: packinfo.packName
                         height: 100
                         width: 100
                         Layout.alignment: Qt.AlignHCenter
diff --git a/resources/qml/voip/ActiveCallBar.qml b/resources/qml/voip/ActiveCallBar.qml
index c92b15c9..be698356 100644
--- a/resources/qml/voip/ActiveCallBar.qml
+++ b/resources/qml/voip/ActiveCallBar.qml
@@ -34,15 +34,15 @@ Rectangle {
             width: Nheko.avatarSize
             height: Nheko.avatarSize
             url: CallManager.callPartyAvatarUrl.replace("mxc://", "image://MxcImage/")
-            displayName: CallManager.callParty
             userid: CallManager.callParty
+            displayName: CallManager.callPartyDisplayName
             onClicked: TimelineManager.openImageOverlay(room.avatarUrl(userid), room.data.eventId)
         }
 
         Label {
             Layout.leftMargin: 8
             font.pointSize: fontMetrics.font.pointSize * 1.1
-            text: CallManager.callParty
+            text: CallManager.callPartyDisplayName
             color: "#000000"
         }
 
diff --git a/resources/qml/voip/CallInvite.qml b/resources/qml/voip/CallInvite.qml
index 253fa25c..1bd5eb26 100644
--- a/resources/qml/voip/CallInvite.qml
+++ b/resources/qml/voip/CallInvite.qml
@@ -40,7 +40,7 @@ Popup {
         Label {
             Layout.alignment: Qt.AlignCenter
             Layout.topMargin: msgView.height / 25
-            text: CallManager.callParty
+            text: CallManager.callPartyDisplayName
             font.pointSize: fontMetrics.font.pointSize * 2
             color: Nheko.colors.windowText
         }
@@ -50,7 +50,8 @@ Popup {
             width: msgView.height / 5
             height: msgView.height / 5
             url: CallManager.callPartyAvatarUrl.replace("mxc://", "image://MxcImage/")
-            displayName: CallManager.callParty
+            userid: CallManager.callParty
+            displayName: CallManager.callPartyDisplayName
         }
 
         ColumnLayout {
diff --git a/resources/qml/voip/CallInviteBar.qml b/resources/qml/voip/CallInviteBar.qml
index f6c1ecde..10f8367a 100644
--- a/resources/qml/voip/CallInviteBar.qml
+++ b/resources/qml/voip/CallInviteBar.qml
@@ -41,14 +41,15 @@ Rectangle {
             width: Nheko.avatarSize
             height: Nheko.avatarSize
             url: CallManager.callPartyAvatarUrl.replace("mxc://", "image://MxcImage/")
-            displayName: CallManager.callParty
+            userid: CallManager.callParty
+            displayName: CallManager.callPartyDisplayName
             onClicked: TimelineManager.openImageOverlay(room.avatarUrl(userid), room.data.eventId)
         }
 
         Label {
             Layout.leftMargin: 8
             font.pointSize: fontMetrics.font.pointSize * 1.1
-            text: CallManager.callParty
+            text: CallManager.callPartyDisplayName
             color: "#000000"
         }
 
diff --git a/src/CallManager.cpp b/src/CallManager.cpp
index 6d41f1c6..601c9d6b 100644
--- a/src/CallManager.cpp
+++ b/src/CallManager.cpp
@@ -206,7 +206,9 @@ CallManager::sendInvite(const QString &roomid, CallType callType, unsigned int w
         std::vector members(cache::getMembers(roomid.toStdString()));
         const RoomMember &callee =
           members.front().user_id == utils::localUser() ? members.back() : members.front();
-        callParty_          = callee.display_name.isEmpty() ? callee.user_id : callee.display_name;
+        callParty_ = callee.user_id;
+        callPartyDisplayName_ =
+          callee.display_name.isEmpty() ? callee.user_id : callee.display_name;
         callPartyAvatarUrl_ = QString::fromStdString(roomInfo.avatar_url);
         emit newInviteState();
         playRingtone(QUrl("qrc:/media/media/ringback.ogg"), true);
@@ -308,7 +310,9 @@ CallManager::handleEvent(const RoomEvent &callInviteEvent)
         std::vector members(cache::getMembers(callInviteEvent.room_id));
         const RoomMember &caller =
           members.front().user_id == utils::localUser() ? members.back() : members.front();
-        callParty_          = caller.display_name.isEmpty() ? caller.user_id : caller.display_name;
+        callParty_ = caller.user_id;
+        callPartyDisplayName_ =
+          caller.display_name.isEmpty() ? caller.user_id : caller.display_name;
         callPartyAvatarUrl_ = QString::fromStdString(roomInfo.avatar_url);
 
         haveCallInvite_ = true;
@@ -459,6 +463,7 @@ CallManager::clear()
 {
         roomid_.clear();
         callParty_.clear();
+        callPartyDisplayName_.clear();
         callPartyAvatarUrl_.clear();
         callid_.clear();
         callType_       = CallType::VOICE;
diff --git a/src/CallManager.h b/src/CallManager.h
index 1d973191..407b8366 100644
--- a/src/CallManager.h
+++ b/src/CallManager.h
@@ -32,6 +32,7 @@ class CallManager : public QObject
         Q_PROPERTY(webrtc::CallType callType READ callType NOTIFY newInviteState)
         Q_PROPERTY(webrtc::State callState READ callState NOTIFY newCallState)
         Q_PROPERTY(QString callParty READ callParty NOTIFY newInviteState)
+        Q_PROPERTY(QString callPartyDisplayName READ callPartyDisplayName NOTIFY newInviteState)
         Q_PROPERTY(QString callPartyAvatarUrl READ callPartyAvatarUrl NOTIFY newInviteState)
         Q_PROPERTY(bool isMicMuted READ isMicMuted NOTIFY micMuteChanged)
         Q_PROPERTY(bool haveLocalPiP READ haveLocalPiP NOTIFY newCallState)
@@ -48,6 +49,7 @@ public:
         webrtc::CallType callType() const { return callType_; }
         webrtc::State callState() const { return session_.state(); }
         QString callParty() const { return callParty_; }
+        QString callPartyDisplayName() const { return callPartyDisplayName_; }
         QString callPartyAvatarUrl() const { return callPartyAvatarUrl_; }
         bool isMicMuted() const { return session_.isMicMuted(); }
         bool haveLocalPiP() const { return session_.haveLocalPiP(); }
@@ -87,6 +89,7 @@ private:
         WebRTCSession &session_;
         QString roomid_;
         QString callParty_;
+        QString callPartyDisplayName_;
         QString callPartyAvatarUrl_;
         std::string callid_;
         const uint32_t timeoutms_  = 120000;

From 0e3f3f2b2014aa47a1075aac8075290ee5ed7ec5 Mon Sep 17 00:00:00 2001
From: Loren Burkholder 
Date: Sat, 4 Sep 2021 21:51:06 -0400
Subject: [PATCH 124/232] make lint

---
 src/timeline/TimelineModel.h | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/src/timeline/TimelineModel.h b/src/timeline/TimelineModel.h
index 582c6b28..46e146b3 100644
--- a/src/timeline/TimelineModel.h
+++ b/src/timeline/TimelineModel.h
@@ -177,8 +177,8 @@ class TimelineModel : public QAbstractListModel
         Q_PROPERTY(bool isSpace READ isSpace CONSTANT)
         Q_PROPERTY(int trustlevel READ trustlevel NOTIFY trustlevelChanged)
         Q_PROPERTY(bool isDirect READ isDirect NOTIFY isDirectChanged)
-        Q_PROPERTY(
-          QString directChatOtherUserId READ directChatOtherUserId NOTIFY directChatOtherUserIdChanged)
+        Q_PROPERTY(QString directChatOtherUserId READ directChatOtherUserId NOTIFY
+                     directChatOtherUserIdChanged)
         Q_PROPERTY(InputBar *input READ input CONSTANT)
         Q_PROPERTY(Permissions *permissions READ permissions NOTIFY permissionsChanged)
 

From f14762e6a550e9b5c92e7a2e8348fff20fd12ea0 Mon Sep 17 00:00:00 2001
From: Loren Burkholder 
Date: Mon, 6 Sep 2021 21:10:08 -0400
Subject: [PATCH 125/232] Always show jdenticon toggle (disable if no plugin)

---
 src/UserSettingsPage.cpp | 19 +++++++------------
 1 file changed, 7 insertions(+), 12 deletions(-)

diff --git a/src/UserSettingsPage.cpp b/src/UserSettingsPage.cpp
index d79cadbe..7b01b0b8 100644
--- a/src/UserSettingsPage.cpp
+++ b/src/UserSettingsPage.cpp
@@ -757,6 +757,7 @@ UserSettingsPage::UserSettingsPage(QSharedPointer settings, QWidge
         trayToggle_                     = new Toggle{this};
         startInTrayToggle_              = new Toggle{this};
         avatarCircles_                  = new Toggle{this};
+        useIdenticon_                   = new Toggle{this};
         decryptSidebar_                 = new Toggle(this);
         privacyScreen_                  = new Toggle{this};
         onlyShareKeysWithVerifiedUsers_ = new Toggle(this);
@@ -790,6 +791,7 @@ UserSettingsPage::UserSettingsPage(QSharedPointer settings, QWidge
         trayToggle_->setChecked(settings_->tray());
         startInTrayToggle_->setChecked(settings_->startInTray());
         avatarCircles_->setChecked(settings_->avatarCircles());
+        useIdenticon_->setChecked(settings_->useIdenticon());
         decryptSidebar_->setChecked(settings_->decryptSidebar());
         privacyScreen_->setChecked(settings_->privacyScreen());
         onlyShareKeysWithVerifiedUsers_->setChecked(settings_->onlyShareKeysWithVerifiedUsers());
@@ -809,12 +811,6 @@ UserSettingsPage::UserSettingsPage(QSharedPointer settings, QWidge
         useStunServer_->setChecked(settings_->useStunServer());
         mobileMode_->setChecked(settings_->mobileMode());
 
-        if (JdenticonProvider::isAvailable()) {
-                useIdenticon_ = new Toggle{this};
-                useIdenticon_->setChecked(settings_->useIdenticon());
-        } else
-                useIdenticon_ = nullptr;
-
         if (!settings_->tray()) {
                 startInTrayToggle_->setState(false);
                 startInTrayToggle_->setDisabled(true);
@@ -958,12 +954,9 @@ UserSettingsPage::UserSettingsPage(QSharedPointer settings, QWidge
         boxWrap(tr("Circular Avatars"),
                 avatarCircles_,
                 tr("Change the appearance of user avatars in chats.\nOFF - square, ON - Circle."));
-        if (JdenticonProvider::isAvailable())
-                boxWrap(
-                  tr("Use identicons"),
-                  useIdenticon_,
-                  tr(
-                    "Display an identicon instead of a letter when a user has not set an avatar."));
+        boxWrap(tr("Use identicons"),
+                useIdenticon_,
+                tr("Display an identicon instead of a letter when a user has not set an avatar."));
         boxWrap(tr("Group's sidebar"),
                 groupViewToggle_,
                 tr("Show a column containing groups and tags next to the room list."));
@@ -1290,6 +1283,8 @@ UserSettingsPage::UserSettingsPage(QSharedPointer settings, QWidge
                 connect(useIdenticon_, &Toggle::toggled, this, [this](bool enabled) {
                         settings_->setUseIdenticon(enabled);
                 });
+        else
+                useIdenticon_->setDisabled(true);
 
         connect(markdown_, &Toggle::toggled, this, [this](bool enabled) {
                 settings_->setMarkdown(enabled);

From 2147ce8556fb8fcad4c0ff6ee8a493f9e928875b Mon Sep 17 00:00:00 2001
From: Loren Burkholder 
Date: Mon, 6 Sep 2021 21:10:22 -0400
Subject: [PATCH 126/232] Only try loading plugin once

---
 src/JdenticonProvider.cpp | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)

diff --git a/src/JdenticonProvider.cpp b/src/JdenticonProvider.cpp
index c76e2b23..3b819c7c 100644
--- a/src/JdenticonProvider.cpp
+++ b/src/JdenticonProvider.cpp
@@ -78,8 +78,9 @@ JdenticonInterface *
 getJdenticonInterface()
 {
         static JdenticonInterface *interface = nullptr;
+        static bool interfaceExists{true};
 
-        if (interface == nullptr) {
+        if (interface == nullptr && interfaceExists) {
                 QDir pluginsDir(qApp->applicationDirPath());
 
                 bool plugins = pluginsDir.cd("plugins");
@@ -97,6 +98,7 @@ getJdenticonInterface()
                         }
                 } else {
                         nhlog::ui()->info("jdenticon plugin not found.");
+                        interfaceExists = false;
                 }
         }
 

From 094ddb48a2aaa698e6fcb3a80398d157f55c0574 Mon Sep 17 00:00:00 2001
From: Loren Burkholder 
Date: Mon, 6 Sep 2021 21:11:37 -0400
Subject: [PATCH 127/232] Don't bother with crop

---
 resources/qml/Avatar.qml | 2 +-
 src/JdenticonProvider.h  | 4 +---
 2 files changed, 2 insertions(+), 4 deletions(-)

diff --git a/resources/qml/Avatar.qml b/resources/qml/Avatar.qml
index bf936bff..1c05e05d 100644
--- a/resources/qml/Avatar.qml
+++ b/resources/qml/Avatar.qml
@@ -44,8 +44,8 @@ Rectangle {
         id: identicon
         anchors.fill: parent
         visible: img.status != Image.Ready && Settings.useIdenticon
-        source: Settings.useIdenticon ? ("image://jdenticon/" + (userid !== "" ? userid : roomid) + "?radius=" + (Settings.avatarCircles ? 100 : 25) + ((avatar.crop) ? "" : "&scale")) : ""
         layer.enabled: true
+        source: Settings.useIdenticon ? ("image://jdenticon/" + (userid !== "" ? userid : roomid) + "?radius=" + (Settings.avatarCircles ? 100 : 25)) : ""
 
         MouseArea {
             anchors.fill: parent
diff --git a/src/JdenticonProvider.h b/src/JdenticonProvider.h
index 2497687f..bcda29c8 100644
--- a/src/JdenticonProvider.h
+++ b/src/JdenticonProvider.h
@@ -64,9 +64,7 @@ public slots:
                         auto queryBits = query.split('&');
 
                         for (auto b : queryBits) {
-                                if (b == "scale") {
-                                        crop = false;
-                                } else if (b.startsWith("radius=")) {
+                                if (b.startsWith("radius=")) {
                                         radius = b.mid(7).toDouble();
                                 }
                         }

From bb8dbf2c2e2a50d90ce4d01773cfe4e4f8db48e0 Mon Sep 17 00:00:00 2001
From: Loren Burkholder 
Date: Mon, 6 Sep 2021 21:11:49 -0400
Subject: [PATCH 128/232] Use better visible check

---
 resources/qml/Avatar.qml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/resources/qml/Avatar.qml b/resources/qml/Avatar.qml
index 1c05e05d..24d26405 100644
--- a/resources/qml/Avatar.qml
+++ b/resources/qml/Avatar.qml
@@ -43,8 +43,8 @@ Rectangle {
     Image {
         id: identicon
         anchors.fill: parent
-        visible: img.status != Image.Ready && Settings.useIdenticon
         layer.enabled: true
+        visible: Settings.useIdenticon && img.status != Image.Ready
         source: Settings.useIdenticon ? ("image://jdenticon/" + (userid !== "" ? userid : roomid) + "?radius=" + (Settings.avatarCircles ? 100 : 25)) : ""
 
         MouseArea {

From 3b15bf52274d114dc228f8fbb5a4e64ea684f242 Mon Sep 17 00:00:00 2001
From: Loren Burkholder 
Date: Mon, 6 Sep 2021 21:11:57 -0400
Subject: [PATCH 129/232] Remove useless line

---
 resources/qml/Avatar.qml | 1 -
 1 file changed, 1 deletion(-)

diff --git a/resources/qml/Avatar.qml b/resources/qml/Avatar.qml
index 24d26405..5d2b583a 100644
--- a/resources/qml/Avatar.qml
+++ b/resources/qml/Avatar.qml
@@ -43,7 +43,6 @@ Rectangle {
     Image {
         id: identicon
         anchors.fill: parent
-        layer.enabled: true
         visible: Settings.useIdenticon && img.status != Image.Ready
         source: Settings.useIdenticon ? ("image://jdenticon/" + (userid !== "" ? userid : roomid) + "?radius=" + (Settings.avatarCircles ? 100 : 25)) : ""
 

From 8e5f91a5796f7a18569d9cce5a2057725a8e465d Mon Sep 17 00:00:00 2001
From: Loren Burkholder 
Date: Mon, 6 Sep 2021 21:14:45 -0400
Subject: [PATCH 130/232] Use better jdenticon codes/id settings

---
 resources/qml/dialogs/ImagePackEditorDialog.qml   | 6 +++---
 resources/qml/dialogs/ImagePackSettingsDialog.qml | 5 +++--
 2 files changed, 6 insertions(+), 5 deletions(-)

diff --git a/resources/qml/dialogs/ImagePackEditorDialog.qml b/resources/qml/dialogs/ImagePackEditorDialog.qml
index f927a745..c028f4a2 100644
--- a/resources/qml/dialogs/ImagePackEditorDialog.qml
+++ b/resources/qml/dialogs/ImagePackEditorDialog.qml
@@ -61,7 +61,7 @@ ApplicationWindow {
                 header: AvatarListTile {
                     title: imagePack.packname
                     avatarUrl: imagePack.avatarUrl
-                    userid: imagePack.packname
+                    roomid: imagePack.statekey
                     subtitle: imagePack.statekey
                     index: -1
                     selectedIndex: currentImageIndex
@@ -143,7 +143,7 @@ ApplicationWindow {
                         Layout.columnSpan: 2
                         url: imagePack.avatarUrl.replace("mxc://", "image://MxcImage/")
                         displayName: imagePack.packname
-                        userid: imagePack.packname
+                        roomid: imagePack.statekey
                         height: 130
                         width: 130
                         crop: false
@@ -221,7 +221,7 @@ ApplicationWindow {
                         Layout.columnSpan: 2
                         url: imagePack.data(imagePack.index(currentImageIndex, 0), SingleImagePackModel.Url).replace("mxc://", "image://MxcImage/")
                         displayName: imagePack.data(imagePack.index(currentImageIndex, 0), SingleImagePackModel.ShortCode)
-                        userid: displayName
+                        roomid: displayName
                         height: 130
                         width: 130
                         crop: false
diff --git a/resources/qml/dialogs/ImagePackSettingsDialog.qml b/resources/qml/dialogs/ImagePackSettingsDialog.qml
index 4aee4a78..ca09ff27 100644
--- a/resources/qml/dialogs/ImagePackSettingsDialog.qml
+++ b/resources/qml/dialogs/ImagePackSettingsDialog.qml
@@ -112,7 +112,7 @@ ApplicationWindow {
                             return qsTr("Globally enabled pack");
                     }
                     selectedIndex: currentPackIndex
-                    userid: displayName
+                    roomid: currentPack.statekey
 
                     TapHandler {
                         onSingleTapped: currentPackIndex = index
@@ -136,6 +136,7 @@ ApplicationWindow {
                     property string packName: currentPack ? currentPack.packname : ""
                     property string attribution: currentPack ? currentPack.attribution : ""
                     property string avatarUrl: currentPack ? currentPack.avatarUrl : ""
+                    property string statekey: currentPack ? currentPack.statekey : ""
 
                     anchors.fill: parent
                     anchors.margins: Nheko.paddingLarge
@@ -144,7 +145,7 @@ ApplicationWindow {
                     Avatar {
                         url: packinfo.avatarUrl.replace("mxc://", "image://MxcImage/")
                         displayName: packinfo.packName
-                        userid: packinfo.packName
+                        roomid: packinfo.statekey
                         height: 100
                         width: 100
                         Layout.alignment: Qt.AlignHCenter

From 87bff3493d9c884eff09d5653e5df29b3489616d Mon Sep 17 00:00:00 2001
From: Loren Burkholder 
Date: Mon, 6 Sep 2021 21:45:55 -0400
Subject: [PATCH 131/232] Add direct chat handling for previews and invites

---
 src/timeline/RoomlistModel.cpp | 12 ++++++++++++
 src/timeline/TimelineModel.h   |  2 +-
 2 files changed, 13 insertions(+), 1 deletion(-)

diff --git a/src/timeline/RoomlistModel.cpp b/src/timeline/RoomlistModel.cpp
index 094b0df6..a423090c 100644
--- a/src/timeline/RoomlistModel.cpp
+++ b/src/timeline/RoomlistModel.cpp
@@ -164,6 +164,13 @@ RoomlistModel::data(const QModelIndex &index, int role) const
                                 return false;
                         case Roles::Tags:
                                 return QStringList();
+                        case Roles::IsDirect:
+                                return room.member_count == 1;
+                        case Roles::DirectChatOtherUserId:
+                                // if this is a direct chat, the front member is correct; otherwise,
+                                // it won't be used anyway
+                                return QString::fromStdString(
+                                  cache::roomMembers(roomid.toStdString()).front());
                         default:
                                 return {};
                         }
@@ -196,6 +203,11 @@ RoomlistModel::data(const QModelIndex &index, int role) const
                                 return true;
                         case Roles::Tags:
                                 return QStringList();
+                        case Roles::IsDirect:
+                                return room.member_count == 1;
+                        case Roles::DirectChatOtherUserId:
+                                return QString::fromStdString(
+                                  cache::roomMembers(roomid.toStdString()).front());
                         default:
                                 return {};
                         }
diff --git a/src/timeline/TimelineModel.h b/src/timeline/TimelineModel.h
index 46e146b3..66e0622e 100644
--- a/src/timeline/TimelineModel.h
+++ b/src/timeline/TimelineModel.h
@@ -295,7 +295,7 @@ public:
         bool isEncrypted() const { return isEncrypted_; }
         crypto::Trust trustlevel() const;
         int roomMemberCount() const;
-        bool isDirect() const { return roomMemberCount() <= 2; } // TODO: handle invites
+        bool isDirect() const { return roomMemberCount() <= 2; }
         QString directChatOtherUserId() const;
 
         std::optional eventById(const QString &id)

From 96edc0bb7522fb6ada70c34bb503e3eb3eb1fa92 Mon Sep 17 00:00:00 2001
From: Loren Burkholder 
Date: Tue, 7 Sep 2021 20:38:39 -0400
Subject: [PATCH 132/232] Use correct form of roomId

---
 resources/qml/CommunitiesList.qml |  2 +-
 resources/qml/TimelineView.qml    |  4 ++--
 resources/qml/TopBar.qml          | 15 +++++++++------
 3 files changed, 12 insertions(+), 9 deletions(-)

diff --git a/resources/qml/CommunitiesList.qml b/resources/qml/CommunitiesList.qml
index 121eb7db..ff9b7da7 100644
--- a/resources/qml/CommunitiesList.qml
+++ b/resources/qml/CommunitiesList.qml
@@ -130,7 +130,7 @@ Page {
                         else
                             return "image://colorimage/" + model.avatarUrl + "?" + communityItem.unimportantText;
                     }
-                    roomid: model.roomid
+                    roomid: model.id
                     displayName: model.displayName
                     color: communityItem.background
                 }
diff --git a/resources/qml/TimelineView.qml b/resources/qml/TimelineView.qml
index b38df44f..91bbca5b 100644
--- a/resources/qml/TimelineView.qml
+++ b/resources/qml/TimelineView.qml
@@ -137,7 +137,7 @@ Item {
     ColumnLayout {
         id: preview
 
-        property string roomid: room ? room.roomid : (roomPreview ? roomPreview.roomid : "")
+        property string roomId: room ? room.roomId : (roomPreview ? roomPreview.roomId : "")
         property string roomName: room ? room.roomName : (roomPreview ? roomPreview.roomName : "")
         property string roomTopic: room ? room.roomTopic : (roomPreview ? roomPreview.roomTopic : "")
         property string avatarUrl: room ? room.roomAvatarUrl : (roomPreview ? roomPreview.roomAvatarUrl : "")
@@ -154,7 +154,7 @@ Item {
 
         Avatar {
             url: parent.avatarUrl.replace("mxc://", "image://MxcImage/")
-            roomid: parent.roomid
+            roomid: parent.roomId
             displayName: parent.roomName
             height: 130
             width: 130
diff --git a/resources/qml/TopBar.qml b/resources/qml/TopBar.qml
index a8a53a24..05c61d99 100644
--- a/resources/qml/TopBar.qml
+++ b/resources/qml/TopBar.qml
@@ -13,10 +13,13 @@ Rectangle {
 
     property bool showBackButton: false
     property string roomName: room ? room.roomName : qsTr("No room selected")
+    property string roomId: room ? room.roomId : ""
     property string avatarUrl: room ? room.roomAvatarUrl : ""
     property string roomTopic: room ? room.roomTopic : ""
     property bool isEncrypted: room ? room.isEncrypted : false
     property int trustlevel: room ? room.trustlevel : Crypto.Unverified
+    property bool isDirect: room ? room.isDirect : false
+    property string directChatOtherUserId: room ? room.directChatOtherUserId : ""
 
     Layout.fillWidth: true
     implicitHeight: topLayout.height + Nheko.paddingMedium * 2
@@ -65,12 +68,12 @@ Rectangle {
             width: Nheko.avatarSize
             height: Nheko.avatarSize
             url: avatarUrl.replace("mxc://", "image://MxcImage/")
-            roomid: room.roomId
-            userid: room.isDirect ? room.directChatOtherUserId : ""
+            roomid: roomId
+            userid: isDirect ? directChatOtherUserId : ""
             displayName: roomName
             onClicked: {
                 if (room)
-                    TimelineManager.openRoomSettings(room.roomId);
+                    TimelineManager.openRoomSettings(roomId);
 
             }
         }
@@ -137,7 +140,7 @@ Rectangle {
                 Platform.MenuItem {
                     visible: room ? room.permissions.canInvite() : false
                     text: qsTr("Invite users")
-                    onTriggered: TimelineManager.openInviteUsers(room.roomId)
+                    onTriggered: TimelineManager.openInviteUsers(roomId)
                 }
 
                 Platform.MenuItem {
@@ -147,12 +150,12 @@ Rectangle {
 
                 Platform.MenuItem {
                     text: qsTr("Leave room")
-                    onTriggered: TimelineManager.openLeaveRoomDialog(room.roomId)
+                    onTriggered: TimelineManager.openLeaveRoomDialog(roomId)
                 }
 
                 Platform.MenuItem {
                     text: qsTr("Settings")
-                    onTriggered: TimelineManager.openRoomSettings(room.roomId)
+                    onTriggered: TimelineManager.openRoomSettings(roomId)
                 }
 
             }

From fb53fc86b67efd17288df24dba72085018c29eac Mon Sep 17 00:00:00 2001
From: Loren Burkholder 
Date: Thu, 9 Sep 2021 21:31:23 -0400
Subject: [PATCH 133/232] Fix invites crashing the whole app

---
 src/Cache.cpp                  | 54 ++++++++++++++++++++++++++++++++++
 src/Cache.h                    |  4 +++
 src/Cache_p.h                  |  8 ++++-
 src/timeline/RoomlistModel.cpp |  5 +---
 4 files changed, 66 insertions(+), 5 deletions(-)

diff --git a/src/Cache.cpp b/src/Cache.cpp
index d009c0d3..6be61633 100644
--- a/src/Cache.cpp
+++ b/src/Cache.cpp
@@ -2614,6 +2614,12 @@ Cache::getInviteRoomName(lmdb::txn &txn, lmdb::dbi &statesdb, lmdb::dbi &members
         return QString("Empty Room");
 }
 
+RoomMember
+Cache::getDirectInviteMember(const std::string &room_id)
+{
+        return getMembersFromInvitedRoom(room_id, 0, 1).front();
+}
+
 QString
 Cache::getInviteRoomAvatarUrl(lmdb::txn &txn, lmdb::dbi &statesdb, lmdb::dbi &membersdb)
 {
@@ -2777,6 +2783,48 @@ Cache::getMembers(const std::string &room_id, std::size_t startIndex, std::size_
         return members;
 }
 
+std::vector
+Cache::getMembersFromInvitedRoom(const std::string &room_id,
+                                 std::size_t startIndex,
+                                 std::size_t len)
+{
+        auto txn    = ro_txn(env_);
+        auto db     = getInviteMembersDb(txn, room_id);
+        auto cursor = lmdb::cursor::open(txn, db);
+
+        std::size_t currentIndex = 0;
+
+        const auto endIndex = std::min(startIndex + len, db.size(txn));
+
+        std::vector members;
+
+        std::string_view user_id, user_data;
+        while (cursor.get(user_id, user_data, MDB_NEXT)) {
+                if (currentIndex < startIndex) {
+                        currentIndex += 1;
+                        continue;
+                }
+
+                if (currentIndex >= endIndex)
+                        break;
+
+                try {
+                        MemberInfo tmp = json::parse(user_data);
+                        members.emplace_back(
+                          RoomMember{QString::fromStdString(std::string(user_id)),
+                                     QString::fromStdString(tmp.name)});
+                } catch (const json::exception &e) {
+                        nhlog::db()->warn("{}", e.what());
+                }
+
+                currentIndex += 1;
+        }
+
+        cursor.close();
+
+        return members;
+}
+
 bool
 Cache::isRoomMember(const std::string &user_id, const std::string &room_id)
 {
@@ -4816,6 +4864,12 @@ getMembers(const std::string &room_id, std::size_t startIndex, std::size_t len)
         return instance_->getMembers(room_id, startIndex, len);
 }
 
+RoomMember
+getDirectInviteMember(const std::string &room_id)
+{
+        return instance_->getDirectInviteMember(room_id);
+}
+
 void
 saveState(const mtx::responses::Sync &res)
 {
diff --git a/src/Cache.h b/src/Cache.h
index 57a36d73..2c024722 100644
--- a/src/Cache.h
+++ b/src/Cache.h
@@ -84,6 +84,10 @@ getRoomAvatarUrl(lmdb::txn &txn, lmdb::dbi &statesdb, lmdb::dbi &membersdb);
 std::vector
 getMembers(const std::string &room_id, std::size_t startIndex = 0, std::size_t len = 30);
 
+//! Get the other person from an invite to a direct chat.
+RoomMember
+getDirectInviteMember(const std::string &room_id);
+
 bool
 isInitialized();
 
diff --git a/src/Cache_p.h b/src/Cache_p.h
index 6190413f..d4605048 100644
--- a/src/Cache_p.h
+++ b/src/Cache_p.h
@@ -109,6 +109,10 @@ public:
         std::vector getMembers(const std::string &room_id,
                                            std::size_t startIndex = 0,
                                            std::size_t len        = 30);
+
+        std::vector getMembersFromInvitedRoom(const std::string &room_id,
+                                                          std::size_t startIndex = 0,
+                                                          std::size_t len        = 30);
         size_t memberCount(const std::string &room_id);
 
         void saveState(const mtx::responses::Sync &res);
@@ -135,6 +139,9 @@ public:
         //! Retrieve all the user ids from a room.
         std::vector roomMembers(const std::string &room_id);
 
+        //! Get the other user from an invite to a direct chat.
+        RoomMember getDirectInviteMember(const std::string &room_id);
+
         //! Check if the given user has power leve greater than than
         //! lowest power level of the given events.
         bool hasEnoughPowerLevel(const std::vector &eventTypes,
@@ -313,7 +320,6 @@ public:
 
                 return get_skey(a).compare(get_skey(b));
         }
-
 signals:
         void newReadReceipts(const QString &room_id, const std::vector &event_ids);
         void roomReadStatus(const std::map &status);
diff --git a/src/timeline/RoomlistModel.cpp b/src/timeline/RoomlistModel.cpp
index a423090c..e1234895 100644
--- a/src/timeline/RoomlistModel.cpp
+++ b/src/timeline/RoomlistModel.cpp
@@ -167,10 +167,7 @@ RoomlistModel::data(const QModelIndex &index, int role) const
                         case Roles::IsDirect:
                                 return room.member_count == 1;
                         case Roles::DirectChatOtherUserId:
-                                // if this is a direct chat, the front member is correct; otherwise,
-                                // it won't be used anyway
-                                return QString::fromStdString(
-                                  cache::roomMembers(roomid.toStdString()).front());
+                                return cache::getDirectInviteMember(roomid.toStdString()).user_id;
                         default:
                                 return {};
                         }

From 0b8527eb1be7ae2048e0503eee96e46177c7d5ee Mon Sep 17 00:00:00 2001
From: Loren Burkholder 
Date: Thu, 9 Sep 2021 21:33:50 -0400
Subject: [PATCH 134/232] Don't try to check whether a preview is direct

---
 src/timeline/RoomlistModel.cpp | 5 ++---
 1 file changed, 2 insertions(+), 3 deletions(-)

diff --git a/src/timeline/RoomlistModel.cpp b/src/timeline/RoomlistModel.cpp
index e1234895..c29e1adf 100644
--- a/src/timeline/RoomlistModel.cpp
+++ b/src/timeline/RoomlistModel.cpp
@@ -201,10 +201,9 @@ RoomlistModel::data(const QModelIndex &index, int role) const
                         case Roles::Tags:
                                 return QStringList();
                         case Roles::IsDirect:
-                                return room.member_count == 1;
+                                return false;
                         case Roles::DirectChatOtherUserId:
-                                return QString::fromStdString(
-                                  cache::roomMembers(roomid.toStdString()).front());
+                                return QString{}; // should never be reached
                         default:
                                 return {};
                         }

From b9255803fb32aaf06700d47153bdc802881f1a66 Mon Sep 17 00:00:00 2001
From: Loren Burkholder 
Date: Sat, 11 Sep 2021 19:45:01 -0400
Subject: [PATCH 135/232] Streamline getting other user id from invited direct
 chat

---
 src/Cache.cpp                  | 16 ++++------------
 src/Cache.h                    |  7 +++----
 src/Cache_p.h                  |  6 +++---
 src/timeline/RoomlistModel.cpp |  6 +++++-
 4 files changed, 15 insertions(+), 20 deletions(-)

diff --git a/src/Cache.cpp b/src/Cache.cpp
index 6be61633..b1fd0ce0 100644
--- a/src/Cache.cpp
+++ b/src/Cache.cpp
@@ -2614,12 +2614,6 @@ Cache::getInviteRoomName(lmdb::txn &txn, lmdb::dbi &statesdb, lmdb::dbi &members
         return QString("Empty Room");
 }
 
-RoomMember
-Cache::getDirectInviteMember(const std::string &room_id)
-{
-        return getMembersFromInvitedRoom(room_id, 0, 1).front();
-}
-
 QString
 Cache::getInviteRoomAvatarUrl(lmdb::txn &txn, lmdb::dbi &statesdb, lmdb::dbi &membersdb)
 {
@@ -2784,9 +2778,7 @@ Cache::getMembers(const std::string &room_id, std::size_t startIndex, std::size_
 }
 
 std::vector
-Cache::getMembersFromInvitedRoom(const std::string &room_id,
-                                 std::size_t startIndex,
-                                 std::size_t len)
+Cache::getMembersFromInvite(const std::string &room_id, std::size_t startIndex, std::size_t len)
 {
         auto txn    = ro_txn(env_);
         auto db     = getInviteMembersDb(txn, room_id);
@@ -4864,10 +4856,10 @@ getMembers(const std::string &room_id, std::size_t startIndex, std::size_t len)
         return instance_->getMembers(room_id, startIndex, len);
 }
 
-RoomMember
-getDirectInviteMember(const std::string &room_id)
+std::vector
+getMembersFromInvite(const std::string &room_id, std::size_t startIndex, std::size_t len)
 {
-        return instance_->getDirectInviteMember(room_id);
+        return instance_->getMembersFromInvite(room_id, startIndex, len);
 }
 
 void
diff --git a/src/Cache.h b/src/Cache.h
index 2c024722..f8626430 100644
--- a/src/Cache.h
+++ b/src/Cache.h
@@ -83,10 +83,9 @@ getRoomAvatarUrl(lmdb::txn &txn, lmdb::dbi &statesdb, lmdb::dbi &membersdb);
 //! Retrieve member info from a room.
 std::vector
 getMembers(const std::string &room_id, std::size_t startIndex = 0, std::size_t len = 30);
-
-//! Get the other person from an invite to a direct chat.
-RoomMember
-getDirectInviteMember(const std::string &room_id);
+//! Retrive member info from an invite.
+std::vector
+getMembersFromInvite(const std::string &room_id, std::size_t start_index = 0, std::size_t len = 30);
 
 bool
 isInitialized();
diff --git a/src/Cache_p.h b/src/Cache_p.h
index d4605048..e98b1c34 100644
--- a/src/Cache_p.h
+++ b/src/Cache_p.h
@@ -110,9 +110,9 @@ public:
                                            std::size_t startIndex = 0,
                                            std::size_t len        = 30);
 
-        std::vector getMembersFromInvitedRoom(const std::string &room_id,
-                                                          std::size_t startIndex = 0,
-                                                          std::size_t len        = 30);
+        std::vector getMembersFromInvite(const std::string &room_id,
+                                                     std::size_t startIndex = 0,
+                                                     std::size_t len        = 30);
         size_t memberCount(const std::string &room_id);
 
         void saveState(const mtx::responses::Sync &res);
diff --git a/src/timeline/RoomlistModel.cpp b/src/timeline/RoomlistModel.cpp
index c29e1adf..afe53560 100644
--- a/src/timeline/RoomlistModel.cpp
+++ b/src/timeline/RoomlistModel.cpp
@@ -165,9 +165,13 @@ RoomlistModel::data(const QModelIndex &index, int role) const
                         case Roles::Tags:
                                 return QStringList();
                         case Roles::IsDirect:
+                                // The list of users from the room doesn't contain the invited
+                                // users, so we won't factor the invite into the count
                                 return room.member_count == 1;
                         case Roles::DirectChatOtherUserId:
-                                return cache::getDirectInviteMember(roomid.toStdString()).user_id;
+                                return cache::getMembersFromInvite(roomid.toStdString(), 0, 1)
+                                  .front()
+                                  .user_id;
                         default:
                                 return {};
                         }

From 1b82b8242b1290b3ce39f932597e8cd3854b5e82 Mon Sep 17 00:00:00 2001
From: Nicolas Werner 
Date: Sat, 11 Sep 2021 01:29:09 +0200
Subject: [PATCH 136/232] Keep identities for users cached

There is not really a reason to stop tracking them, just because the
server says so. We might still want to show a users profile, etc.
---
 src/Cache.cpp | 8 --------
 src/Cache_p.h | 3 ---
 2 files changed, 11 deletions(-)

diff --git a/src/Cache.cpp b/src/Cache.cpp
index d009c0d3..84e2ddc2 100644
--- a/src/Cache.cpp
+++ b/src/Cache.cpp
@@ -1572,7 +1572,6 @@ Cache::saveState(const mtx::responses::Sync &res)
         savePresence(txn, res.presence);
 
         markUserKeysOutOfDate(txn, userKeyCacheDb, res.device_lists.changed, currentBatchToken);
-        deleteUserKeys(txn, userKeyCacheDb, res.device_lists.left);
 
         removeLeftRooms(txn, res.rooms.leave);
 
@@ -4124,13 +4123,6 @@ Cache::updateUserKeys(const std::string &sync_token, const mtx::responses::Query
         }
 }
 
-void
-Cache::deleteUserKeys(lmdb::txn &txn, lmdb::dbi &db, const std::vector &user_ids)
-{
-        for (const auto &user_id : user_ids)
-                db.del(txn, user_id);
-}
-
 void
 Cache::markUserKeysOutOfDate(lmdb::txn &txn,
                              lmdb::dbi &db,
diff --git a/src/Cache_p.h b/src/Cache_p.h
index 6190413f..7780c80f 100644
--- a/src/Cache_p.h
+++ b/src/Cache_p.h
@@ -55,9 +55,6 @@ public:
                                    lmdb::dbi &db,
                                    const std::vector &user_ids,
                                    const std::string &sync_token);
-        void deleteUserKeys(lmdb::txn &txn,
-                            lmdb::dbi &db,
-                            const std::vector &user_ids);
         void query_keys(const std::string &user_id,
                         std::function cb);
 

From b2bc80bfa2681cf0691a01ad94c23b3430ba6542 Mon Sep 17 00:00:00 2001
From: Weblate 
Date: Mon, 13 Sep 2021 15:08:01 -0400
Subject: [PATCH 137/232] Translated using Weblate (Esperanto)

Currently translated at 90.5% (507 of 560 strings)

Co-authored-by: Tirifto 
Translate-URL: https://weblate.nheko.im/projects/nheko/nheko-master/eo/
Translation: Nheko/nheko
---
 resources/langs/nheko_eo.ts | 518 ++++++++++++++++++------------------
 1 file changed, 261 insertions(+), 257 deletions(-)

diff --git a/resources/langs/nheko_eo.ts b/resources/langs/nheko_eo.ts
index 654acd23..c7606650 100644
--- a/resources/langs/nheko_eo.ts
+++ b/resources/langs/nheko_eo.ts
@@ -119,7 +119,7 @@
     
         
         Entire screen
-        Tuta ekrano
+        Tuta ekrano
     
 
 
@@ -240,7 +240,7 @@
     
         
         Incompatible cache version
-        Neakorda versio de kaŝmemoro
+        Neakorda versio de kaŝmemoro
     
     
         
@@ -306,7 +306,7 @@
     
         
         Hide rooms with this tag or from this space by default.
-        
+        Implicite kaŝi ĉambrojn kun ĉi tiu etikedo aŭ de ĉi tiu aro.
     
 
 
@@ -314,42 +314,42 @@
     
         
         All rooms
-        Ĉiuj ĉambroj
+        Ĉiuj ĉambroj
     
     
         
         Shows all rooms without filtering.
-        
+        Montras ĉiujn ĉambrojn sen filtrado.
     
     
         
         Favourites
-        
+        Elstaraj
     
     
         
         Rooms you have favourited.
-        
+        Ĉambroj, kiujn vi elstarigis.
     
     
         
         Low Priority
-        Malalta prioritato
+        Malalta prioritato
     
     
         
         Rooms with low priority.
-        
+        Ĉambroj kun malalta prioritato.
     
     
         
         Server Notices
-        
+        Avizoj de servilo
     
     
         
         Messages from your server or administrator.
-        
+        Mesaĝoj de via servilo aŭ administranto.
     
 
 
@@ -367,7 +367,7 @@
     
         
         Enter your recovery key or passphrase called %1 to decrypt your secrets:
-        
+        Enigu vian rehavan ŝlosilon aŭ pasfrazon kun nomo %1 por malĉifri viajn sekretojn:
     
     
         
@@ -502,42 +502,42 @@
     
         
         There is no key to unlock this message. We requested the key automatically, but you can try requesting it again if you are impatient.
-        
+        Estas neniu ŝloslio por malŝlosi ĉi tiun mesaĝon. Ni petis ĝin memage, sed vi povas provi repeti ĝin, se vi rapidas.
     
     
         
         This message couldn't be decrypted, because we only have a key for newer messages. You can try requesting access to this message.
-        
+        Ne povis malĉifri ĉi tiun mesaĝon, ĉar ni havas nur ŝlosilon por pli novaj. Vi povas provi peti aliron al ĉi tiu mesaĝo.
     
     
         
         There was an internal error reading the decryption key from the database.
-        
+        Eraris interne legado de malĉifra ŝlosilo el la datumbazo.
     
     
         
         There was an error decrypting this message.
-        
+        Eraris malĉifrado de ĉi tiu mesaĝo.
     
     
         
         The message couldn't be parsed.
-        
+        Ne povis trakti la mesaĝon.
     
     
         
         The encryption key was reused! Someone is possibly trying to insert false messages into this chat!
-        
+        La ĉifra ŝlosilo estas reuzita! Eble iu provas enmeti falsitajn mesaĝojn en la babilon!
     
     
         
         Unknown decryption error
-        
+        Nekonata malĉifra eraro
     
     
         
         Request key
-        
+        Peti ŝlosilon
     
 
 
@@ -560,7 +560,7 @@
     
         
         Encrypted by an unverified device or the key is from an untrusted source like the key backup.
-        
+        Ĉifrita de nekontrolita aparato, aŭ per ŝlosilo de nefidata fonto, ekzemple la deponejo de ŝlosiloj.
     
 
 
@@ -602,7 +602,7 @@
     
         
         Forward Message
-        
+        Plusendi mesaĝon
     
 
 
@@ -615,12 +615,12 @@
     
         
         Add images
-        
+        Aldoni bildojn
     
     
         
         Stickers (*.png *.webp *.gif)
-        
+        Glumarkoj (*.png *.webp *.gif)
     
     
         
@@ -635,24 +635,24 @@
     
         
         Attribution
-        
+        Atribuo
     
     
         
         
         Use as Emoji
-        
+        Uzi kiel bildosignon
     
     
         
         
         Use as Sticker
-        
+        Uzi kiel glumarkon
     
     
         
         Shortcode
-        
+        Mallongigo
     
     
         
@@ -662,22 +662,22 @@
     
         
         Remove from pack
-        
+        Forigi de pako
     
     
         
         Remove
-        
+        Forigi
     
     
         
         Cancel
-        Nuligi
+        Nuligi
     
     
         
         Save
-        
+        Konservi
     
 
 
@@ -685,27 +685,27 @@
     
         
         Image pack settings
-        
+        Agordoj de bildopako
     
     
         
         Create account pack
-        
+        Krei kontan pakon
     
     
         
         New room pack
-        
+        Nova ĉambra pako
     
     
         
         Private pack
-        
+        Privata pako
     
     
         
         Pack from this room
-        
+        Pakoj el ĉi tiu ĉambro
     
     
         
@@ -725,12 +725,12 @@
     
         
         Edit
-        Redakti
+        Redakti
     
     
         
         Close
-        Fermi
+        Fermi
     
 
 
@@ -738,7 +738,7 @@
     
         
         Select a file
-        Elektu dosieron
+        Elektu dosieron
     
     
         
@@ -756,33 +756,33 @@
     
         
         Invite users to %1
-        
+        Invitu uzantojn al %1
     
     
         
         User ID to invite
-        
+        Identigilo de invitota uzanto
     
     
         
         @joe:matrix.org
         Example user id. The name 'joe' can be localized however you want.
-        
+        @tacuo:matrix.org
     
     
         
         Add
-        
+        Aldoni
     
     
         
         Invite
-        
+        Inviti
     
     
         
         Cancel
-        Nuligi
+        Nuligi
     
 
 
@@ -803,7 +803,8 @@
 You can also put your homeserver address there, if your server doesn't support .well-known lookup.
 Example: @user:server.my
 If Nheko fails to discover your homeserver, it will show you a field to enter the server manually.
-        Via saluta nomo. Identigilo de Matrikso devus komenciĝi per @ sekvata de la identigilo de uzanto. Post la identigilo, vi devas meti retnomon post :. Vi ankaŭ povas enmeti adreson de via hejmservilo, se via servilo ne subtenas bone-konatan trovmanieron.
+        Via saluta nomo. Matriksa identigilo devus komenciĝi per @ sekvata de la identigilo de uzanto. Post la identigilo, vi devas meti nomon de via servilo post :.
+Vi ankaŭ povas enmeti adreson de via hejmservilo, se via servilo ne subtenas bone-konatan trovmanieron.
 Ekzemplo: @uzanto:servilo.mia
 Se Nheko malsukcesas trovi vian hejmservilon, ĝi montros kampon por ĝia permana aldono.
     
@@ -863,12 +864,12 @@ Ekzemplo: https://servilo.mia:8787
     
         
         Autodiscovery failed. Received malformed response.
-        
+        Malsukcesis memaga trovado. Ricevis misformitan respondon.
     
     
         
         Autodiscovery failed. Unknown error when requesting .well-known.
-        
+        Malsukcesis memaga trovado. Okazis nekonata eraro dum petado. .well-known.
     
     
         
@@ -878,12 +879,12 @@ Ekzemplo: https://servilo.mia:8787
     
         
         Received malformed response. Make sure the homeserver domain is valid.
-        
+        Ricevis misformitan respondon. Certiĝu, ke retnomo de la hejmservilo estas valida.
     
     
         
         An unknown error occured. Make sure the homeserver domain is valid.
-        
+        Okazis nekonata eraro. Certiĝu, ke retnomo de la hejmservilo estas valida.
     
     
         
@@ -912,7 +913,7 @@ Ekzemplo: https://servilo.mia:8787
     
         
         Encryption enabled
-        
+        Ĉifrado estas ŝaltita
     
     
         
@@ -927,7 +928,7 @@ Ekzemplo: https://servilo.mia:8787
     
         
         topic changed to: %1
-        
+        temo ŝanĝiĝis al: %1
     
     
         
@@ -937,27 +938,27 @@ Ekzemplo: https://servilo.mia:8787
     
         
         %1 changed the room avatar
-        
+        %1 ŝanĝis bildon de la ĉambro
     
     
         
         %1 created and configured room: %2
-        
+        %1 kreis kaj agordis ĉambron: %2
     
     
         
         %1 placed a voice call.
-        %1 metis voĉvokon.
+        %1 voĉvokis.
     
     
         
         %1 placed a video call.
-        %1 metis vidvokon.
+        %1 vidvokis.
     
     
         
         %1 placed a call.
-        %1 metis vokon.
+        %1 vokis.
     
     
         
@@ -977,7 +978,7 @@ Ekzemplo: https://servilo.mia:8787
     
         
         Allow them in
-        
+        Enlasi ĝin
     
 
 
@@ -985,7 +986,7 @@ Ekzemplo: https://servilo.mia:8787
     
         
         Hang up
-        
+        Fini
     
     
         
@@ -995,7 +996,7 @@ Ekzemplo: https://servilo.mia:8787
     
         
         Send a file
-        Sendu dosieron
+        Sendi dosieron
     
     
         
@@ -1005,7 +1006,7 @@ Ekzemplo: https://servilo.mia:8787
     
         
         Stickers
-        
+        Glumarkoj
     
     
         
@@ -1048,72 +1049,72 @@ Ekzemplo: https://servilo.mia:8787
     
         
         &Copy
-        
+        &Kopii
     
     
         
         Copy &link location
-        
+        Kopii celon de &ligilo
     
     
         
         Re&act
-        
+        Re&agi
     
     
         
         Repl&y
-        
+        Re&spondi
     
     
         
         &Edit
-        
+        R&edakti
     
     
         
         Read receip&ts
-        
+        K&vitancoj
     
     
         
         &Forward
-        
+        &Plusendi
     
     
         
         &Mark as read
-        
+        &Marki legita
     
     
         
         View raw message
-        Vidi krudan mesaĝon
+        Vidi krudan mesaĝon
     
     
         
         View decrypted raw message
-        Vidi malĉifritan krudan mesaĝon
+        Vidi malĉifritan krudan mesaĝon
     
     
         
         Remo&ve message
-        
+        &Forigi mesaĝon
     
     
         
         &Save as
-        
+        Kon&servi kiel
     
     
         
         &Open in external program
-        
+        &Malfermi per aparta programo
     
     
         
         Copy link to eve&nt
-        
+        Kopii ligilon al oka&zo
     
 
 
@@ -1228,7 +1229,7 @@ Ekzemplo: https://servilo.mia:8787
     
         
         No microphone found.
-        Neniu mikrofono troviĝis.
+        Neniu mikrofono troviĝis.
     
     
         
@@ -1264,7 +1265,7 @@ Ekzemplo: https://servilo.mia:8787
     
         
         Create a unique profile, which allows you to log into several accounts at the same time and start multiple instances of nheko.
-        
+        Krei unikan profilon, kiu permesos al vi saluti kelkajn kontojn samtempe, kaj startigi plurajn nhekojn.
     
     
         
@@ -1282,7 +1283,7 @@ Ekzemplo: https://servilo.mia:8787
     
         
         Read receipts
-        Kvitancoj
+        Kvitancoj
     
 
 
@@ -1290,7 +1291,7 @@ Ekzemplo: https://servilo.mia:8787
     
         
         Yesterday, %1
-        
+        Hieraŭ, %1
     
 
 
@@ -1344,22 +1345,22 @@ Ekzemplo: https://servilo.mia:8787
     
         
         Registration token
-        
+        Registra peco
     
     
         
         Please enter a valid registration token.
-        
+        Bonvolu enigi validan registran pecon.
     
     
         
         Autodiscovery failed. Received malformed response.
-        
+        Malsukcesis memaga trovado. Ricevis misformitan respondon.
     
     
         
         Autodiscovery failed. Unknown error when requesting .well-known.
-        
+        Malsukcesis memaga trovado. Okazis nekonata eraro dum petado. .well-known.
     
     
         
@@ -1369,12 +1370,12 @@ Ekzemplo: https://servilo.mia:8787
     
         
         Received malformed response. Make sure the homeserver domain is valid.
-        
+        Ricevis misformitan respondon. Certiĝu, ke retnomo de la hejmservilo estas valida.
     
     
         
         An unknown error occured. Make sure the homeserver domain is valid.
-        
+        Okazis nekonata eraro. Certiĝu, ke retnomo de la hejmservilo estas valida.
     
     
         
@@ -1410,12 +1411,12 @@ Ekzemplo: https://servilo.mia:8787
     
         
         Explore Public Rooms
-        
+        Esplori publikajn ĉambrojn
     
     
         
         Search for public rooms
-        
+        Serĉi publikajn ĉambrojn
     
 
 
@@ -1431,52 +1432,52 @@ Ekzemplo: https://servilo.mia:8787
     
         
         New tag
-        
+        Nova etikedo
     
     
         
         Enter the tag you want to use:
-        
+        Enigu la etikedon, kiun vi volas uzi:
     
     
         
         Leave Room
-        
+        Eliri el ĉambro
     
     
         
         Are you sure you want to leave this room?
-        
+        Ĉu vi certas, ke vi volas eliri el ĉi tiu ĉambro?
     
     
         
         Leave room
-        Eliri el ĉambro
+        Eliri el ĉambro
     
     
         
         Tag room as:
-        Etikedi ĉambron:
+        Etikedi ĉambron:
     
     
         
         Favourite
-        Preferata
+        Elstara
     
     
         
         Low priority
-        
+        Malalta prioritato
     
     
         
         Server notice
-        
+        Avizo de servilo
     
     
         
         Create new tag...
-        
+        Krei novan etikedon…
     
     
         
@@ -1491,7 +1492,7 @@ Ekzemplo: https://servilo.mia:8787
     
         
         Profile settings
-        
+        Agordoj de profilo
     
     
         
@@ -1501,32 +1502,32 @@ Ekzemplo: https://servilo.mia:8787
     
         
         Logout
-        Adiaŭi
+        Adiaŭi
     
     
         
         Start a new chat
-        Komenci novan babilon
+        Komenci novan babilon
     
     
         
         Join a room
-        Aliĝi ĉambron
+        Aliĝi al ĉambro
     
     
         
         Create a new room
-        
+        Krei novan ĉambron
     
     
         
         Room directory
-        Ĉambra dosierujo
+        Katalogo de ĉambroj
     
     
         
         User settings
-        Agordoj de uzanto
+        Agordoj de uzanto
     
 
 
@@ -1534,41 +1535,41 @@ Ekzemplo: https://servilo.mia:8787
     
         
         Members of %1
-        
+        Anoj de %1
     
     
         
         %n people in %1
         Summary above list of members
-        
-            
-            
+        
+            %n persono en %1
+            %n personoj en %1
         
     
     
         
         Invite more people
-        
+        Inviti pliajn personojn
     
     
         
         This room is not encrypted!
-        
+        Ĉi tiu ĉambro ne estas ĉifrata!
     
     
         
         This user is verified.
-        
+        Ĉi tiu uzanto estas kontrolita.
     
     
         
         This user isn't verified, but is still using the same master key from the first time you met.
-        
+        Ĉi tiu uzanto ne estas kontrolita, sed ankoraŭ uzas la saman ĉefan ŝlosilon ekde kiam vi renkontiĝis.
     
     
         
         This user has unverified devices!
-        
+        Ĉi tiu uzanto havas nekontrolitajn aparatojn!
     
 
 
@@ -1611,7 +1612,7 @@ Ekzemplo: https://servilo.mia:8787
     
         
         Room access
-        
+        Aliro al ĉambro
     
     
         
@@ -1631,12 +1632,12 @@ Ekzemplo: https://servilo.mia:8787
     
         
         By knocking
-        
+        Per frapado
     
     
         
         Restricted by membership in other rooms
-        
+        Limigita de aneco en aliaj ĉambroj
     
     
         
@@ -1657,17 +1658,17 @@ Ekzemplo: https://servilo.mia:8787
     
         
         Sticker & Emote Settings
-        
+        Agordoj de glumarkoj kaj mienetoj
     
     
         
         Change
-        
+        Ŝanĝi
     
     
         
         Change what packs are enabled, remove packs or create new ones
-        
+        Ŝalti, forigi, aŭ krei novajn pakojn
     
     
         
@@ -1697,7 +1698,7 @@ Ekzemplo: https://servilo.mia:8787
     
         
         All Files (*)
-        Ĉiuj dosieroj (*)
+        Ĉiuj dosieroj (*)
     
     
         
@@ -1726,12 +1727,12 @@ Ekzemplo: https://servilo.mia:8787
     
         
         Previewing this room
-        
+        Antaŭrigardante ĉi tiun ĉambron
     
     
         
         No preview available
-        
+        Neniu antaŭrigardo disponeblas
     
 
 
@@ -1817,12 +1818,12 @@ Ekzemplo: https://servilo.mia:8787
     
         
         Failed to open image: %1
-        
+        Malsukcesis malfermi bildon: %1
     
     
         
         Failed to upload image: %1
-        
+        Malsukcesis alŝuti bildon: %1
     
 
 
@@ -1854,7 +1855,7 @@ Ekzemplo: https://servilo.mia:8787
     
         
         Search
-        Serĉu
+        Serĉu
     
 
 
@@ -1862,17 +1863,17 @@ Ekzemplo: https://servilo.mia:8787
     
         
         Successful Verification
-        
+        Sukcesis kontrolo
     
     
         
         Verification successful! Both sides verified their devices!
-        
+        Sukcesis kontrolo! Ambaŭ flankoj kontrolis siajn aparatojn!
     
     
         
         Close
-        Fermi
+        Fermi
     
 
 
@@ -1880,13 +1881,13 @@ Ekzemplo: https://servilo.mia:8787
     
         
         Message redaction failed: %1
-        
+        Malsukcesis redaktado de mesaĝo: %1
     
     
         
         
         Failed to encrypt event, sending aborted!
-        
+        Malsukcesis ĉifri okazon; sendado nuliĝis!
     
     
         
@@ -1930,12 +1931,12 @@ Ekzemplo: https://servilo.mia:8787
     
         
         %1 allowed to join this room by knocking.
-        
+        %1 permesis aliĝi al ĉi tiu ĉambro per frapado.
     
     
         
         %1 allowed members of the following rooms to automatically join this room: %2
-        
+        %1 permesis al anoj de la jenaj ĉambroj memage aliĝi al ĉi tiu ĉambro: %2
     
     
         
@@ -1970,7 +1971,7 @@ Ekzemplo: https://servilo.mia:8787
     
         
         %1 has changed the room's permissions.
-        
+        %1 ŝanĝis permesojn de la ĉambro.
     
     
         
@@ -1987,7 +1988,7 @@ Ekzemplo: https://servilo.mia:8787
     
         
         %1 changed some profile info.
-        
+        %1 ŝanĝis iujn informojn en profilo.
     
     
         
@@ -1997,17 +1998,17 @@ Ekzemplo: https://servilo.mia:8787
     
         
         %1 joined via authorisation from %2's server.
-        
+        %1 aliĝis per rajtigo de servilo de %2.
     
     
         
         %1 rejected their invite.
-        
+        %1 rifuzis sian inviton.
     
     
         
         Revoked the invite to %1.
-        
+        Nuligis la inviton por %1.
     
     
         
@@ -2017,27 +2018,27 @@ Ekzemplo: https://servilo.mia:8787
     
         
         Kicked %1.
-        
+        Forpelis uzanton %1.
     
     
         
         Unbanned %1.
-        
+        Malforbaris uzanton %1.
     
     
         
         %1 was banned.
-        
+        %1 estas forbarita.
     
     
         
         Reason: %1
-        
+        Kialo: %1
     
     
         
         %1 redacted their knock.
-        
+        %1 forigis sian frapon.
     
     
         
@@ -2047,17 +2048,17 @@ Ekzemplo: https://servilo.mia:8787
     
         
         %1 has changed their avatar and changed their display name to %2.
-        
+        %1 ŝanĝis sian profilbildon kaj sian prezentan nomon al %2.
     
     
         
         %1 has changed their display name to %2.
-        
+        %1 ŝanĝis sian prezentan nomon al %2.
     
     
         
         Rejected the knock from %1.
-        
+        Rifuzis la frapon de %1.
     
     
         
@@ -2068,7 +2069,7 @@ Ekzemplo: https://servilo.mia:8787
     
         
         %1 knocked.
-        
+        %1 frapis.
     
 
 
@@ -2084,32 +2085,32 @@ Ekzemplo: https://servilo.mia:8787
     
         
         No room open
-        
+        Neniu ĉambro estas malfermita
     
     
         
         %1 member(s)
-        %1 ĉambrano(j)
+        %1 ĉambrano(j)
     
     
         
         join the conversation
-        
+        aliĝi al interparolo
     
     
         
         accept invite
-        
+        akcepti inviton
     
     
         
         decline invite
-        
+        rifuzi inviton
     
     
         
         Back to room list
-        
+        Reen al listo de ĉambroj
     
 
 
@@ -2117,7 +2118,7 @@ Ekzemplo: https://servilo.mia:8787
     
         
         No encrypted private chat found with this user. Create an encrypted private chat with this user and try again.
-        
+        Neniu ĉifrita privata babilo kun ĉi tiu uzanto troviĝis. Kreu ĉifritan privatan babilon kun ĉi tiu uzanto kaj reprovu.
     
 
 
@@ -2125,47 +2126,47 @@ Ekzemplo: https://servilo.mia:8787
     
         
         Back to room list
-        
+        Reen al listo de ĉambroj
     
     
         
         No room selected
-        
+        Neniu ĉambro estas elektita
     
     
         
         This room is not encrypted!
-        
+        Ĉi tiu ĉambro ne estas ĉifrata!
     
     
         
         This room contains only verified devices.
-        
+        Ĉi tiu ĉambro enhavas nur kontrolitajn aparatojn.
     
     
         
         This rooms contain verified devices and devices which have never changed their master key.
-        
+        Ĉi tiu ĉambro enhavas kontrolitajn aparatojn kaj aparatojn, kiuj neniam ŝanĝis sian ĉefan ŝlosilon.
     
     
         
         This room contains unverified devices!
-        
+        Ĉi tiu ĉambro enhavas nekontrolitajn aparatojn!
     
     
         
         Room options
-        
+        Elektebloj de ĉambro
     
     
         
         Invite users
-        
+        Inviti uzantojn
     
     
         
         Members
-        Membroj
+        Anoj
     
     
         
@@ -2188,7 +2189,7 @@ Ekzemplo: https://servilo.mia:8787
     
         
         Quit
-        
+        Ĉesigi
     
 
 
@@ -2196,38 +2197,38 @@ Ekzemplo: https://servilo.mia:8787
     
         
         Global User Profile
-        
+        Ĉiea profilo de uzanto
     
     
         
         Room User Profile
-        
+        Ĉambra profilo de uzanto
     
     
         
         
         Verify
-        
+        Kontroli
     
     
         
         Ban the user
-        
+        Forbari la uzanton
     
     
         
         Start a private chat
-        
+        Komenci privatan babilon
     
     
         
         Kick the user
-        
+        Forpeli la uzanton
     
     
         
         Unverify
-        
+        Malkontroli
     
     
         
@@ -2237,7 +2238,7 @@ Ekzemplo: https://servilo.mia:8787
     
         
         All Files (*)
-        Ĉiuj dosieroj (*)
+        Ĉiuj dosieroj (*)
     
     
         
@@ -2256,7 +2257,7 @@ Ekzemplo: https://servilo.mia:8787
         
         
         Default
-        
+        Implicita
     
 
 
@@ -2264,32 +2265,32 @@ Ekzemplo: https://servilo.mia:8787
     
         
         Minimize to tray
-        
+        Etigi al plato
     
     
         
         Start in tray
-        
+        Komenci ete sur pleto
     
     
         
         Group's sidebar
-        
+        Flanka breto de grupoj
     
     
         
         Circular Avatars
-        
+        Rondaj profilbildoj
     
     
         
         profile: %1
-        
+        profilo: %1
     
     
         
         Default
-        
+        Implicita
     
     
         
@@ -2299,7 +2300,7 @@ Ekzemplo: https://servilo.mia:8787
     
         
         Cross Signing Keys
-        
+        Ŝlosiloj por delegaj subskriboj
     
     
         
@@ -2451,7 +2452,7 @@ Kun ĉi tio malŝaltita, ĉiuj mesaĝoj sendiĝas en plata teksto.
     
         
         Play animated images only on hover
-        
+        Ludi movbildojn nur sub musmontrilo
     
     
         
@@ -2466,53 +2467,54 @@ Kun ĉi tio malŝaltita, ĉiuj mesaĝoj sendiĝas en plata teksto.
     
         
         Alert on notification
-        
+        Atentigi pri sciigoj
     
     
         
         Show an alert when a message is received.
 This usually causes the application icon in the task bar to animate in some fashion.
-        
+        Atentigas je ricevo de mesaĝo.
+Ĉi tio kutime movbildigas la simbolbildon sur la pleto iumaniere.
     
     
         
         Highlight message on hover
-        
+        Emfazi mesaĝojn sub musmontrilo
     
     
         
         Change the background color of messages when you hover over them.
-        
+        Ŝanĝi fonkoloron de mesaĝoj sub musmontrilo.
     
     
         
         Large Emoji in timeline
-        
+        Grandaj bildosignoj en historio
     
     
         
         Make font size larger if messages with only a few emojis are displayed.
-        
+        Grandigi tiparon se montriĝas mesaĝoj kun nur kelkaj bildosignoj.
     
     
         
         Send encrypted messages to verified users only
-        
+        Sendi ĉifritajn mesaĝojn nur al kontrolitaj uzantoj
     
     
         
         Requires a user to be verified to send encrypted messages to them. This improves safety but makes E2EE more tedious.
-        
+        Postulas, ke uzanto estu kontrolita, por ke ĝi povu ricevi mesaĝojn. Ĉi tio plibonigas sekurecon, sed iom maloportunigas tutvojan ĉifradon.
     
     
         
         Share keys with verified users and devices
-        
+        Havigi ŝlosilojn al kontrolitaj uzantoj kaj aparatoj
     
     
         
         Automatically replies to key requests from other users, if they are verified, even if that device shouldn't have access to those keys otherwise.
-        
+        Memage respondas al petoj de aliaj uzantoj je ŝlosiloj, se tiuj uzantoj estas kontrolitaj, eĉ se la aparato ne povus aliri tiujn ŝlosilojn alie.
     
     
         
@@ -2562,152 +2564,152 @@ This usually causes the application icon in the task bar to animate in some fash
     
         
         Font Family
-        
+        Tiparo
     
     
         
         Theme
-        
+        Haŭto
     
     
         
         Ringtone
-        
+        Sonoro
     
     
         
         Set the notification sound to play when a call invite arrives
-        
+        Agordi sciigan sonon, kiu aŭdiĝos je invito al voko
     
     
         
         Microphone
-        
+        Mikrofono
     
     
         
         Camera
-        
+        Filmilo
     
     
         
         Camera resolution
-        
+        Distingumo de filmilo
     
     
         
         Camera frame rate
-        
+        Filmerrapido de filmilo
     
     
         
         Allow fallback call assist server
-        
+        Permesi repaŝan asistan servilon por vokoj
     
     
         
         Will use turn.matrix.org as assist when your home server does not offer one.
-        
+        Uzos la servilon turn.matrix.org kiel asistanton, kiam via hejma servilo ne disponigos propran.
     
     
         
         Device ID
-        
+        Identigilo de aparato
     
     
         
         Device Fingerprint
-        
+        Fingrospuro de aparato
     
     
         
         Session Keys
-        
+        Ŝlosiloj de salutaĵo
     
     
         
         IMPORT
-        
+        ENPORTI
     
     
         
         EXPORT
-        
+        ELPORTI
     
     
         
         ENCRYPTION
-        
+        ĈIFRADO
     
     
         
         GENERAL
-        
+        ĜENERALAJ
     
     
         
         INTERFACE
-        
+        FASADO
     
     
         
         Plays media like GIFs or WEBPs only when explicitly hovering over them.
-        
+        Ludas vidaŭdaĵojn kiel GIF-ojn aŭ WEBP-ojn nur sub musmontrilo.
     
     
         
         Touchscreen mode
-        
+        Tuŝekrana reĝimo
     
     
         
         Will prevent text selection in the timeline to make touch scrolling easier.
-        
+        Malhelpos elkton de teksto en historio por faciligi rulumadon per tuŝoj.
     
     
         
         Emoji Font Family
-        
+        Bildosigna tiparo
     
     
         
         Master signing key
-        
+        Ĉefa subskriba ŝlosilo
     
     
         
         Your most important key. You don't need to have it cached, since not caching it makes it less likely it can be stolen and it is only needed to rotate your other signing keys.
-        
+        Via plej grava ŝlosilo. Vi ne bezonas kaŝmemori ĝin, ĉar de kaŝmemoro ĝi povus esti pli facile ŝtelebla, kaj vi bezonas ĝin nur por ŝanĝado de aliaj viaj subskribaj ŝlosiloj.
     
     
         
         User signing key
-        
+        Uzanto-subskriba ŝlosilo
     
     
         
         The key to verify other users. If it is cached, verifying a user will verify all their devices.
-        
+        Ŝlosilo por kontrolado de aliaj uzantoj. Se ĝi estas kaŝmemorata, kontrolo de uzanto kontrolos ankaŭ ĉiujn ĝiajn aparatojn.
     
     
         
         Self signing key
-        
+        Mem-subskriba ŝlosilo
     
     
         
         The key to verify your own devices. If it is cached, verifying one of your devices will mark it verified for all your other devices and for users, that have verified you.
-        
+        La ŝlosilo por kontrolado de viaj propraj aparatoj. Se ĝi estas kaŝmemorata, kontrolo de unu el viaj aparatoj markos ĝin kontrolita por aliaj viaj aparatoj, kaj por uzantoj, kiuj vin kontrolis.
     
     
         
         Backup key
-        
+        Savkopia ŝlosilo
     
     
         
         The key to decrypt online key backups. If it is cached, you can enable online key backup to store encryption keys securely encrypted on the server.
-        
+        La ŝlosilo por malĉifrado de enretaj savkopioj de ŝlosiloj. Se ĝi estas kaŝmemorata, vi povas ŝalti enretan savkopiadon de ŝlosiloj por deponi ŝlosilojn sekure ĉifritajn al la servilo.
     
     
         
@@ -2732,7 +2734,7 @@ This usually causes the application icon in the task bar to animate in some fash
         
         
         Error
-        
+        Eraro
     
     
         
@@ -2743,23 +2745,23 @@ This usually causes the application icon in the task bar to animate in some fash
     
         
         Enter the passphrase to decrypt the file:
-        
+        Enigu pasfrazon por malĉifri la dosieron:
     
     
         
         
         The password cannot be empty
-        
+        La pasvorto ne povas esti malplena
     
     
         
         Enter passphrase to encrypt your session keys:
-        
+        Enigu pasfrazon por ĉifri ŝlosilojn de via salutaĵo:
     
     
         
         File to save the exported session keys
-        
+        Dosiero, kien konserviĝos la elportitaj ŝloslioj de salutaĵo
     
 
 
@@ -2767,27 +2769,27 @@ This usually causes the application icon in the task bar to animate in some fash
     
         
         Waiting for other party…
-        
+        Atendante la aliulon…
     
     
         
         Waiting for other side to accept the verification request.
-        
+        Atendante, ĝis la aliulo akceptos la kontrolpeton.
     
     
         
         Waiting for other side to continue the verification process.
-        
+        Atendante, ĝis la aliulo finos la kontrolon.
     
     
         
         Waiting for other side to complete the verification process.
-        
+        Atendante, ĝis la aliulo finos la kontrolon.
     
     
         
         Cancel
-        Nuligi
+        Nuligi
     
 
 
@@ -2832,7 +2834,7 @@ This usually causes the application icon in the task bar to animate in some fash
     
         
         Cancel
-        Nuligi
+        Nuligi
     
     
         
@@ -2842,17 +2844,17 @@ This usually causes the application icon in the task bar to animate in some fash
     
         
         Topic
-        Temo
+        Temo
     
     
         
         Alias
-        
+        Kromnomo
     
     
         
         Room Visibility
-        
+        Videbleco de ĉambro
     
     
         
@@ -2875,12 +2877,12 @@ This usually causes the application icon in the task bar to animate in some fash
     
         
         Cancel
-        Nuligi
+        Nuligi
     
     
         
         Confirm
-        
+        Konfirmi
     
     
         
@@ -2898,12 +2900,12 @@ This usually causes the application icon in the task bar to animate in some fash
     
         
         Cancel
-        Nuligi
+        Nuligi
     
     
         
         Room ID or alias
-        
+        Identigilo aŭ kromnomo de ĉambro
     
 
 
@@ -2911,12 +2913,12 @@ This usually causes the application icon in the task bar to animate in some fash
     
         
         Cancel
-        Nuligi
+        Nuligi
     
     
         
         Are you sure you want to leave?
-        
+        Ĉu vi certas, ke vi volas foriri?
     
 
 
@@ -2924,7 +2926,7 @@ This usually causes the application icon in the task bar to animate in some fash
     
         
         Cancel
-        Nuligi
+        Nuligi
     
     
         
@@ -2937,19 +2939,21 @@ This usually causes the application icon in the task bar to animate in some fash
     
         
         Upload
-        
+        Alŝuti
     
     
         
         Cancel
-        Nuligi
+        Nuligi
     
     
         
         Media type: %1
 Media size: %2
 
-        
+        Speco de vidaŭdaĵo: %1
+Grandeco de vidaŭdaĵo: %2
+
     
 
 
@@ -2957,12 +2961,12 @@ Media size: %2
     
         
         Cancel
-        Nuligi
+        Nuligi
     
     
         
         Confirm
-        
+        Konfirmi
     
     
         
@@ -3025,12 +3029,12 @@ Media size: %2
     
         
         You sent a notification
-        
+        Vi sendis sciigon
     
     
         
         %1 sent a notification
-        
+        %1 sendis sciigon
     
     
         
@@ -3045,42 +3049,42 @@ Media size: %2
     
         
         You sent an encrypted message
-        
+        Vi sendis ĉifritan mesaĝon
     
     
         
         %1 sent an encrypted message
-        %1 sendis ĉifritan mesaĝon
+        %1 sendis ĉifritan mesaĝon
     
     
         
         You placed a call
-        
+        Vi vokis
     
     
         
         %1 placed a call
-        
+        %1 vokis
     
     
         
         You answered a call
-        
+        Vi respondis vokon
     
     
         
         %1 answered a call
-        
+        %1 respondis vokon
     
     
         
         You ended a call
-        
+        Vi finis vokon
     
     
         
         %1 ended a call
-        
+        %1 finis vokon
     
 
 

From 09a40c9ad10e03ba0e24c47e9c895982aaf9beb7 Mon Sep 17 00:00:00 2001
From: Weblate 
Date: Mon, 13 Sep 2021 16:24:23 -0400
Subject: [PATCH 138/232] Translated using Weblate (Esperanto)

Currently translated at 100.0% (560 of 560 strings)

Co-authored-by: Tirifto 
Translate-URL: https://weblate.nheko.im/projects/nheko/nheko-master/eo/
Translation: Nheko/nheko
---
 resources/langs/nheko_eo.ts | 108 ++++++++++++++++++------------------
 1 file changed, 54 insertions(+), 54 deletions(-)

diff --git a/resources/langs/nheko_eo.ts b/resources/langs/nheko_eo.ts
index c7606650..4876359a 100644
--- a/resources/langs/nheko_eo.ts
+++ b/resources/langs/nheko_eo.ts
@@ -22,7 +22,7 @@
     
         
         Hide/Show Picture-in-Picture
-        Kaŝi/Montri bildon en bildo
+        Kaŝi/Montri «bildon en bildo»
     
     
         
@@ -133,7 +133,7 @@
         
         
         Invited user: %1
-        Invitita uzanto: %1
+        Invitita uzanto: %1
     
     
         
@@ -250,14 +250,14 @@
     
         
         Failed to restore OLM account. Please login again.
-        
+        Malsukcesis rehavi konton je OLM. Bonvolu resaluti.
     
     
         
         
         
         Failed to restore save data. Please login again.
-        
+        Malsukcesis rehavi konservitajn datumojn. Bonvolu resaluti.
     
     
         
@@ -610,7 +610,7 @@
     
         
         Editing image pack
-        
+        Redaktado de bildopako
     
     
         
@@ -625,12 +625,12 @@
     
         
         State key
-        
+        Identigilo (stata ŝlosilo)
     
     
         
         Packname
-        
+        Nomo de pako
     
     
         
@@ -657,7 +657,7 @@
     
         
         Body
-        
+        Korpo
     
     
         
@@ -710,17 +710,17 @@
     
         
         Globally enabled pack
-        
+        Ĉie ŝaltita pako
     
     
         
         Enable globally
-        
+        Ŝalti ĉie
     
     
         
         Enables this pack to be used in all rooms
-        
+        Ŝaltas ĉi tiun pakon por uzo en ĉiuj ĉambroj
     
     
         
@@ -874,7 +874,7 @@ Ekzemplo: https://servilo.mia:8787
     
         
         The required endpoints were not found. Possibly not a Matrix server.
-        
+        La bezonataj konektaj lokoj ne troviĝis. Eble tio ne estas Matriksa servilo.
     
     
         
@@ -923,7 +923,7 @@ Ekzemplo: https://servilo.mia:8787
     
         
         removed room name
-        
+        forigis nomon de ĉambro
     
     
         
@@ -933,7 +933,7 @@ Ekzemplo: https://servilo.mia:8787
     
         
         removed topic
-        
+        forigis temon
     
     
         
@@ -1365,7 +1365,7 @@ Ekzemplo: https://servilo.mia:8787
     
         
         The required endpoints were not found. Possibly not a Matrix server.
-        
+        La bezonataj konektaj lokoj ne troviĝis. Eble tio ne estas Matriksa servilo.
     
     
         
@@ -1424,7 +1424,7 @@ Ekzemplo: https://servilo.mia:8787
     
         
         no version stored
-        
+        neniu versio konservita
     
 
 
@@ -1482,12 +1482,12 @@ Ekzemplo: https://servilo.mia:8787
     
         
         Status Message
-        
+        Statmesaĝo
     
     
         
         Enter your status message:
-        
+        Enigu vian statmesaĝon:
     
     
         
@@ -1497,7 +1497,7 @@ Ekzemplo: https://servilo.mia:8787
     
         
         Set status message
-        
+        Meti statmesaĝon
     
     
         
@@ -1722,7 +1722,7 @@ Ekzemplo: https://servilo.mia:8787
     
         
         Pending invite.
-        
+        Atendanta invito.
     
     
         
@@ -1755,18 +1755,18 @@ Ekzemplo: https://servilo.mia:8787
     
         
         Include your camera picture-in-picture
-        
+        Enigi vian filmilon en la filmon
     
     
         
         Request remote camera
-        
+        Peti foran filmilon
     
     
         
         
         View your callee's camera like a regular video call
-        
+        Vidi la filmilon de via vokato kiel en ordinara vidvoko
     
     
         
@@ -1776,12 +1776,12 @@ Ekzemplo: https://servilo.mia:8787
     
         
         Share
-        
+        Vidigi
     
     
         
         Preview
-        
+        Antaŭrigardi
     
     
         
@@ -1794,12 +1794,12 @@ Ekzemplo: https://servilo.mia:8787
     
         
         Failed to connect to secret storage
-        
+        Malsukcesis konektiĝi al sekreta deponejo
     
     
         
         Nheko could not connect to the secure storage to save encryption secrets to. This can have multiple reasons. Check if your D-Bus service is running and you have configured a service like KWallet, Gnome Secrets or the equivalent for your platform. If you are having trouble, feel free to open an issue here: https://github.com/Nheko-Reborn/nheko/issues
-        
+        Nheko ne povis konektiĝi al la sekreta deponejo por konservi ĉifrajn sekretojn. Tio povas havi diversajn kialojn. Kontrolu, ĉu la servo «D-Bus» funkcias, kaj ĉu vi agordis kroman servon kiel «KWallet», «Gnome Secrets», aŭ similan servon por via sistemo. Se daŭras problemoj, laŭplaĉe raportu ilin tie ĉi: https://github.com/Nheko-Reborn/nheko/issues
     
 
 
@@ -1808,12 +1808,12 @@ Ekzemplo: https://servilo.mia:8787
         
         
         Failed to update image pack: %1
-        
+        Malsukcesis ĝisdatigi bildopakon: %1
     
     
         
         Failed to delete old image pack: %1
-        
+        Malsukcesis forigi malnovan bildopakon: %1
     
     
         
@@ -2189,7 +2189,7 @@ Ekzemplo: https://servilo.mia:8787
     
         
         Quit
-        Ĉesigi
+        Ĉesigi
     
 
 
@@ -2371,7 +2371,7 @@ Set to 0 to blur immediately after focus loss. Max value of 1 hour (3600 seconds
         Tempo de atendo (en sekundoj) ekde senfokusiĝo
 de la fenestro, post kiu la enhavo malklariĝos.
 Agordu al 0 por malklarigi enhavon tuj post senfokusiĝo.
-Maksimuma valoro estas 1 horo (3600 sekundoj).
+Maksimuma valoro estas 1 horo (3600 sekundoj)
     
     
         
@@ -2519,52 +2519,52 @@ This usually causes the application icon in the task bar to animate in some fash
     
         
         Online Key Backup
-        
+        Enreta savkopiado de ŝlosiloj
     
     
         
         Download message encryption keys from and upload to the encrypted online key backup.
-        
+        Elŝutu ĉifrajn ŝlosilojn por mesaĝoj de la ĉifrita enreta deponejo de ŝlosiloj, aŭ alŝutu ilin tien.
     
     
         
         Enable online key backup
-        
+        Ŝalti enretan savkopiadon de ŝlosiloj
     
     
         
         The Nheko authors recommend not enabling online key backup until symmetric online key backup is available. Enable anyway?
-        
+        Aŭtoroj de Nheko rekomendas ne ŝalti enretan savkopiadon de ŝlosiloj, almenaŭ ĝis simetria enreta savkopiado estos disponebla. Ĉu vi tamen volas ĝin ŝalti?
     
     
         
         CACHED
-        
+        KAŜMEMORITA
     
     
         
         NOT CACHED
-        
+        NE KAŜMEMORITA
     
     
         
         Scale factor
-        
+        Skala obligo
     
     
         
         Change the scale factor of the whole user interface.
-        
+        Ŝanĝas skalan obligon de la tuta fasado.
     
     
         
         Font size
-        
+        Tipara grando
     
     
         
         Font Family
-        Tiparo
+        Tipara familio
     
     
         
@@ -2619,7 +2619,7 @@ This usually causes the application icon in the task bar to animate in some fash
     
         
         Device Fingerprint
-        Fingrospuro de aparato
+        Fingrospuro de aparato
     
     
         
@@ -2669,7 +2669,7 @@ This usually causes the application icon in the task bar to animate in some fash
     
         
         Emoji Font Family
-        Bildosigna tiparo
+        Bildosigna tiparo
     
     
         
@@ -2714,7 +2714,7 @@ This usually causes the application icon in the task bar to animate in some fash
     
         
         Select a file
-        Elektu dosieron
+        Elektu dosieron
     
     
         
@@ -2724,7 +2724,7 @@ This usually causes the application icon in the task bar to animate in some fash
     
         
         Open Sessions File
-        
+        Malfermi dosieron kun salutaĵoj
     
     
         
@@ -2740,7 +2740,7 @@ This usually causes the application icon in the task bar to animate in some fash
         
         
         File Password
-        
+        Pasvorto de dosiero
     
     
         
@@ -2859,12 +2859,12 @@ This usually causes the application icon in the task bar to animate in some fash
     
         
         Room Preset
-        
+        Antaŭagordo de ĉambro
     
     
         
         Direct Chat
-        
+        Individua ĉambro
     
 
 
@@ -2872,7 +2872,7 @@ This usually causes the application icon in the task bar to animate in some fash
     
         
         Open Fallback in Browser
-        
+        Iri al foliumilo por la alternativa metodo
     
     
         
@@ -2887,7 +2887,7 @@ This usually causes the application icon in the task bar to animate in some fash
     
         
         Open the fallback, follow the steps and confirm after completing them.
-        
+        Iru al la alternativa metodo, sekvu la paŝojn, kaj fininte ilin, konfirmu.
     
 
 
@@ -2971,7 +2971,7 @@ Grandeco de vidaŭdaĵo: %2
     
         
         Solve the reCAPTCHA and press the confirm button
-        
+        Solvu la kontrolon de homeco de «reCAPTCHA» kaj premu la konfirman butonon
     
 
 
@@ -2979,12 +2979,12 @@ Grandeco de vidaŭdaĵo: %2
     
         
         You sent an audio clip
-        
+        Vi sendis sonmesaĝon
     
     
         
         %1 sent an audio clip
-        
+        %1 sendis sonmesaĝon
     
     
         
@@ -3092,7 +3092,7 @@ Grandeco de vidaŭdaĵo: %2
     
         
         Unknown Message Type
-        
+        Nekonata tipo de mesaĝo
     
 
 

From ac88d0bfed2724ed5077d554a4739ebac80929e6 Mon Sep 17 00:00:00 2001
From: Nicolas Werner 
Date: Mon, 13 Sep 2021 22:56:52 +0200
Subject: [PATCH 139/232] Update translations

---
 resources/langs/nheko_cs.ts    | 110 +++++++++++-----
 resources/langs/nheko_de.ts    | 118 ++++++++++++-----
 resources/langs/nheko_el.ts    | 110 +++++++++++-----
 resources/langs/nheko_en.ts    | 118 ++++++++++++-----
 resources/langs/nheko_eo.ts    | 118 ++++++++++++-----
 resources/langs/nheko_es.ts    | 110 +++++++++++-----
 resources/langs/nheko_et.ts    | 118 ++++++++++++-----
 resources/langs/nheko_fi.ts    | 118 ++++++++++++-----
 resources/langs/nheko_fr.ts    | 228 ++++++++++++++++++++-------------
 resources/langs/nheko_hu.ts    | 118 ++++++++++++-----
 resources/langs/nheko_it.ts    | 110 +++++++++++-----
 resources/langs/nheko_ja.ts    | 110 +++++++++++-----
 resources/langs/nheko_ml.ts    | 122 ++++++++++++------
 resources/langs/nheko_nl.ts    | 118 ++++++++++++-----
 resources/langs/nheko_pl.ts    | 110 +++++++++++-----
 resources/langs/nheko_pt_BR.ts | 110 +++++++++++-----
 resources/langs/nheko_pt_PT.ts | 144 ++++++++++++++-------
 resources/langs/nheko_ro.ts    | 110 +++++++++++-----
 resources/langs/nheko_ru.ts    | 118 ++++++++++++-----
 resources/langs/nheko_si.ts    | 110 +++++++++++-----
 resources/langs/nheko_sv.ts    | 118 ++++++++++++-----
 resources/langs/nheko_zh_CN.ts | 110 +++++++++++-----
 22 files changed, 1878 insertions(+), 778 deletions(-)

diff --git a/resources/langs/nheko_cs.ts b/resources/langs/nheko_cs.ts
index 7af17b73..af2a89ac 100644
--- a/resources/langs/nheko_cs.ts
+++ b/resources/langs/nheko_cs.ts
@@ -131,17 +131,17 @@
     
     
         
-        
+        
         Invited user: %1
         
     
     
-        
+        
         Migrating the cache to the current version failed. This can have different reasons. Please open an issue and try to use an older version in the mean time. Alternatively you can try deleting the cache manually.
         
     
     
-        
+        
         Confirm join
         
     
@@ -157,12 +157,12 @@
     
     
         
-        
+        
         Confirm invite
         
     
     
-        
+        
         Do you really want to invite %1 (%2)?
         
     
@@ -227,12 +227,12 @@
         
     
     
-        
+        
         Do you really want to start a private chat with %1?
         
     
     
-        
+        
         Cache migration failed!
         
     
@@ -259,7 +259,7 @@
         
     
     
-        
+        
         Failed to setup encryption keys. Server response: %1 %2. Please try again later.
         
     
@@ -354,7 +354,7 @@
 
     CrossSigningSecrets
     
-        
+        
         Decrypt secrets
         
     
@@ -735,7 +735,7 @@
 
     InputBar
     
-        
+        
         Select a file
         
     
@@ -1039,17 +1039,19 @@ Example: https://server.my:8787
         
     
     
-        
+        
+        
         &Copy
         
     
     
-        
+        
+        
         Copy &link location
         
     
     
-        
+        
         Re&act
         
     
@@ -1108,6 +1110,11 @@ Example: https://server.my:8787
         Copy link to eve&nt
         
     
+    
+        
+        &Go to reply
+        
+    
 
 
     NewVerificationRequest
@@ -1167,6 +1174,14 @@ Example: https://server.my:8787
         
     
 
+
+    NotificationWarning
+    
+        
+        You will be pinging the whole room
+        
+    
+
 
     NotificationsManager
     
@@ -1414,7 +1429,7 @@ Example: https://server.my:8787
 
     RoomInfo
     
-        
+        
         no version stored
         
     
@@ -1785,7 +1800,7 @@ Example: https://server.my:8787
 
     SecretStorage
     
-        
+        
         Failed to connect to secret storage
         
     
@@ -1871,7 +1886,7 @@ Example: https://server.my:8787
 
     TimelineModel
     
-        
+        
         Message redaction failed: %1
         
     
@@ -1972,7 +1987,7 @@ Example: https://server.my:8787
         
     
     
-        
+        
         %1 changed their avatar.
         
     
@@ -2032,12 +2047,12 @@ Example: https://server.my:8787
         
     
     
-        
+        
         You joined this room.
         
     
     
-        
+        
         %1 has changed their avatar and changed their display name to %2.
         
     
@@ -2079,7 +2094,7 @@ Example: https://server.my:8787
         
     
     
-        
+        
         %1 member(s)
         
     
@@ -2186,7 +2201,7 @@ Example: https://server.my:8787
 
     UserProfile
     
-        
+        
         Global User Profile
         
     
@@ -2196,28 +2211,63 @@ Example: https://server.my:8787
         
     
     
-        
-        
-        Verify
+        
+        Change avatar globally.
         
     
     
-        
-        Ban the user
+        
+        Change avatar. Will only apply to this room.
         
     
     
-        
-        Start a private chat
+        
+        Change display name globally.
+        
+    
+    
+        
+        Change display name. Will only apply to this room.
+        
+    
+    
+        
+        Room: %1
+        
+    
+    
+        
+        This is a room-specific profile. The user's name and avatar may be different from their global versions.
+        
+    
+    
+        
+        Open the global profile for this user.
         
     
     
         
-        Kick the user
+        
+        Verify
         
     
     
-        
+        
+        Start a private chat.
+        
+    
+    
+        
+        Kick the user.
+        
+    
+    
+        
+        Ban the user.
+        
+    
+    
+        
         Unverify
         
     
diff --git a/resources/langs/nheko_de.ts b/resources/langs/nheko_de.ts
index cd6d1df8..cc8ce50e 100644
--- a/resources/langs/nheko_de.ts
+++ b/resources/langs/nheko_de.ts
@@ -131,17 +131,17 @@
     
     
         
-        
+        
         Invited user: %1
         Eingeladener Benutzer: %1
     
     
-        
+        
         Migrating the cache to the current version failed. This can have different reasons. Please open an issue and try to use an older version in the mean time. Alternatively you can try deleting the cache manually.
         Das Migrieren des Caches auf die aktuelle Version ist fehlgeschlagen. Das kann verschiedene Gründe als Ursache haben. Bitte melde den Fehler und verwende in der Zwischenzeit eine ältere Version. Alternativ kannst du den Cache manuell löschen.
     
     
-        
+        
         Confirm join
         Beitritt bestätigen
     
@@ -157,12 +157,12 @@
     
     
         
-        
+        
         Confirm invite
         Einladung bestätigen
     
     
-        
+        
         Do you really want to invite %1 (%2)?
         Nutzer %1 (%2) wirklich einladen?
     
@@ -227,12 +227,12 @@
         Verbannung aufgehoben: %1
     
     
-        
+        
         Do you really want to start a private chat with %1?
         Möchtest du wirklich eine private Konversation mit %1 beginnen?
     
     
-        
+        
         Cache migration failed!
         Migration des Caches fehlgeschlagen!
     
@@ -259,7 +259,7 @@
         Gespeicherte Nachrichten konnten nicht wiederhergestellt werden. Bitte melde Dich erneut an.
     
     
-        
+        
         Failed to setup encryption keys. Server response: %1 %2. Please try again later.
         Fehler beim Setup der Verschlüsselungsschlüssel. Servermeldung: %1 %2. Bitte versuche es später erneut.
     
@@ -354,7 +354,7 @@
 
     CrossSigningSecrets
     
-        
+        
         Decrypt secrets
         Geheimnisse entschlüsseln
     
@@ -735,7 +735,7 @@
 
     InputBar
     
-        
+        
         Select a file
         Datei auswählen
     
@@ -1043,17 +1043,19 @@ Beispiel: https://mein.server:8787
         Optionen
     
     
-        
+        
+        
         &Copy
         &Kopieren
     
     
-        
+        
+        
         Copy &link location
         Kopiere &Link
     
     
-        
+        
         Re&act
         Re&agieren
     
@@ -1112,6 +1114,11 @@ Beispiel: https://mein.server:8787
         Copy link to eve&nt
         Link &zu diesem Event kopieren
     
+    
+        
+        &Go to reply
+        &Gehe zur Antwort
+    
 
 
     NewVerificationRequest
@@ -1171,6 +1178,14 @@ Beispiel: https://mein.server:8787
         Akzeptieren
     
 
+
+    NotificationWarning
+    
+        
+        You will be pinging the whole room
+        Du wirst den ganzen Raum benachrichtigen
+    
+
 
     NotificationsManager
     
@@ -1418,7 +1433,7 @@ Beispiel: https://mein.server:8787
 
     RoomInfo
     
-        
+        
         no version stored
         keine Version gespeichert
     
@@ -1788,7 +1803,7 @@ Beispiel: https://mein.server:8787
 
     SecretStorage
     
-        
+        
         Failed to connect to secret storage
         Verbindung zum kryptografischen Speicher fehlgeschlagen
     
@@ -1874,7 +1889,7 @@ Beispiel: https://mein.server:8787
 
     TimelineModel
     
-        
+        
         Message redaction failed: %1
         Nachricht zurückziehen fehlgeschlagen: %1
     
@@ -1974,7 +1989,7 @@ Beispiel: https://mein.server:8787
         %1 wurde eingeladen.
     
     
-        
+        
         %1 changed their avatar.
         %1 hat den Avatar geändert.
     
@@ -2034,12 +2049,12 @@ Beispiel: https://mein.server:8787
         %1 hat das Anklopfen zurückgezogen.
     
     
-        
+        
         You joined this room.
         Du bist dem Raum beigetreten.
     
     
-        
+        
         %1 has changed their avatar and changed their display name to %2.
         %1 hat den eigenen Avatar und Namen geändert zu %2.
     
@@ -2081,7 +2096,7 @@ Beispiel: https://mein.server:8787
         Kein Raum geöffnet
     
     
-        
+        
         %1 member(s)
         %1 Teilnehmer
     
@@ -2188,7 +2203,7 @@ Beispiel: https://mein.server:8787
 
     UserProfile
     
-        
+        
         Global User Profile
         Globales Nutzerprofil
     
@@ -2198,28 +2213,63 @@ Beispiel: https://mein.server:8787
         Raumspezifisches Nutzerprofil
     
     
-        
-        
+        
+        Change avatar globally.
+        Ändere das Profilbild in allen Räumen.
+    
+    
+        
+        Change avatar. Will only apply to this room.
+        Ändere das Profilbild nur in diesem Raum.
+    
+    
+        
+        Change display name globally.
+        Ändere den Anzeigenamen in allen Räumen.
+    
+    
+        
+        Change display name. Will only apply to this room.
+        Ändere den Anzeigenamen nur in diesem Raum.
+    
+    
+        
+        Room: %1
+        Raum: %1
+    
+    
+        
+        This is a room-specific profile. The user's name and avatar may be different from their global versions.
+        Dies ist das raumspezifische Nutzerprofil. Der Anzeigename und das Profilbild kann sich von dem globalen Profil unterscheiden.
+    
+    
+        
+        Open the global profile for this user.
+        Öffne das globale Profil des Nutzers.
+    
+    
+        
+        
         Verify
         Verifizieren
     
     
-        
-        Ban the user
-        Banne den Nutzer
-    
-    
-        
-        Start a private chat
-        Starte eine private Konservation
+        
+        Start a private chat.
+        Starte eine private Unterhaltung.
     
     
         
-        Kick the user
-        Kicke den Nutzer
+        Kick the user.
+        Benutzer aus dem Raum werfen.
     
     
-        
+        
+        Ban the user.
+        Benutzer aus dem Raum verbannen.
+    
+    
+        
         Unverify
         Verifizierung zurückziehen
     
diff --git a/resources/langs/nheko_el.ts b/resources/langs/nheko_el.ts
index 928e0a93..04d688d7 100644
--- a/resources/langs/nheko_el.ts
+++ b/resources/langs/nheko_el.ts
@@ -131,17 +131,17 @@
     
     
         
-        
+        
         Invited user: %1
         
     
     
-        
+        
         Migrating the cache to the current version failed. This can have different reasons. Please open an issue and try to use an older version in the mean time. Alternatively you can try deleting the cache manually.
         
     
     
-        
+        
         Confirm join
         
     
@@ -157,12 +157,12 @@
     
     
         
-        
+        
         Confirm invite
         
     
     
-        
+        
         Do you really want to invite %1 (%2)?
         
     
@@ -227,12 +227,12 @@
         
     
     
-        
+        
         Do you really want to start a private chat with %1?
         
     
     
-        
+        
         Cache migration failed!
         
     
@@ -259,7 +259,7 @@
         
     
     
-        
+        
         Failed to setup encryption keys. Server response: %1 %2. Please try again later.
         
     
@@ -354,7 +354,7 @@
 
     CrossSigningSecrets
     
-        
+        
         Decrypt secrets
         
     
@@ -735,7 +735,7 @@
 
     InputBar
     
-        
+        
         Select a file
         Διάλεξε ένα αρχείο
     
@@ -1039,17 +1039,19 @@ Example: https://server.my:8787
         
     
     
-        
+        
+        
         &Copy
         
     
     
-        
+        
+        
         Copy &link location
         
     
     
-        
+        
         Re&act
         
     
@@ -1108,6 +1110,11 @@ Example: https://server.my:8787
         Copy link to eve&nt
         
     
+    
+        
+        &Go to reply
+        
+    
 
 
     NewVerificationRequest
@@ -1167,6 +1174,14 @@ Example: https://server.my:8787
         Αποδοχή
     
 
+
+    NotificationWarning
+    
+        
+        You will be pinging the whole room
+        
+    
+
 
     NotificationsManager
     
@@ -1414,7 +1429,7 @@ Example: https://server.my:8787
 
     RoomInfo
     
-        
+        
         no version stored
         
     
@@ -1784,7 +1799,7 @@ Example: https://server.my:8787
 
     SecretStorage
     
-        
+        
         Failed to connect to secret storage
         
     
@@ -1870,7 +1885,7 @@ Example: https://server.my:8787
 
     TimelineModel
     
-        
+        
         Message redaction failed: %1
         
     
@@ -1970,7 +1985,7 @@ Example: https://server.my:8787
         
     
     
-        
+        
         %1 changed their avatar.
         
     
@@ -2030,12 +2045,12 @@ Example: https://server.my:8787
         
     
     
-        
+        
         You joined this room.
         
     
     
-        
+        
         %1 has changed their avatar and changed their display name to %2.
         
     
@@ -2077,7 +2092,7 @@ Example: https://server.my:8787
         
     
     
-        
+        
         %1 member(s)
         
     
@@ -2184,7 +2199,7 @@ Example: https://server.my:8787
 
     UserProfile
     
-        
+        
         Global User Profile
         
     
@@ -2194,28 +2209,63 @@ Example: https://server.my:8787
         
     
     
-        
-        
-        Verify
+        
+        Change avatar globally.
         
     
     
-        
-        Ban the user
+        
+        Change avatar. Will only apply to this room.
         
     
     
-        
-        Start a private chat
+        
+        Change display name globally.
+        
+    
+    
+        
+        Change display name. Will only apply to this room.
+        
+    
+    
+        
+        Room: %1
+        
+    
+    
+        
+        This is a room-specific profile. The user's name and avatar may be different from their global versions.
+        
+    
+    
+        
+        Open the global profile for this user.
         
     
     
         
-        Kick the user
+        
+        Verify
         
     
     
-        
+        
+        Start a private chat.
+        
+    
+    
+        
+        Kick the user.
+        
+    
+    
+        
+        Ban the user.
+        
+    
+    
+        
         Unverify
         
     
diff --git a/resources/langs/nheko_en.ts b/resources/langs/nheko_en.ts
index 59ba7f5f..5ab3dfb7 100644
--- a/resources/langs/nheko_en.ts
+++ b/resources/langs/nheko_en.ts
@@ -131,17 +131,17 @@
     
     
         
-        
+        
         Invited user: %1
         Invited user: %1
     
     
-        
+        
         Migrating the cache to the current version failed. This can have different reasons. Please open an issue and try to use an older version in the mean time. Alternatively you can try deleting the cache manually.
         Migrating the cache to the current version failed. This can have different reasons. Please open an issue and try to use an older version in the mean time. Alternatively you can try deleting the cache manually.
     
     
-        
+        
         Confirm join
         Confirm join
     
@@ -157,12 +157,12 @@
     
     
         
-        
+        
         Confirm invite
         Confirm invite
     
     
-        
+        
         Do you really want to invite %1 (%2)?
         Do you really want to invite %1 (%2)?
     
@@ -227,12 +227,12 @@
         Unbanned user: %1
     
     
-        
+        
         Do you really want to start a private chat with %1?
         Do you really want to start a private chat with %1?
     
     
-        
+        
         Cache migration failed!
         Cache migration failed!
     
@@ -259,7 +259,7 @@
         Failed to restore save data. Please login again.
     
     
-        
+        
         Failed to setup encryption keys. Server response: %1 %2. Please try again later.
         Failed to setup encryption keys. Server response: %1 %2. Please try again later.
     
@@ -354,7 +354,7 @@
 
     CrossSigningSecrets
     
-        
+        
         Decrypt secrets
         Decrypt secrets
     
@@ -735,7 +735,7 @@
 
     InputBar
     
-        
+        
         Select a file
         Select a file
     
@@ -1043,17 +1043,19 @@ Example: https://server.my:8787
         Options
     
     
-        
+        
+        
         &Copy
         &Copy
     
     
-        
+        
+        
         Copy &link location
         Copy &link location
     
     
-        
+        
         Re&act
         Re&act
     
@@ -1112,6 +1114,11 @@ Example: https://server.my:8787
         Copy link to eve&nt
         Copy link to eve&nt
     
+    
+        
+        &Go to reply
+        &Go to reply
+    
 
 
     NewVerificationRequest
@@ -1171,6 +1178,14 @@ Example: https://server.my:8787
         Accept
     
 
+
+    NotificationWarning
+    
+        
+        You will be pinging the whole room
+        You will be pinging the whole room
+    
+
 
     NotificationsManager
     
@@ -1418,7 +1433,7 @@ Example: https://server.my:8787
 
     RoomInfo
     
-        
+        
         no version stored
         no version stored
     
@@ -1788,7 +1803,7 @@ Example: https://server.my:8787
 
     SecretStorage
     
-        
+        
         Failed to connect to secret storage
         Failed to connect to secret storage
     
@@ -1874,7 +1889,7 @@ Example: https://server.my:8787
 
     TimelineModel
     
-        
+        
         Message redaction failed: %1
         Message redaction failed: %1
     
@@ -1974,7 +1989,7 @@ Example: https://server.my:8787
         %1 was invited.
     
     
-        
+        
         %1 changed their avatar.
         %1 changed their avatar.
     
@@ -2034,12 +2049,12 @@ Example: https://server.my:8787
         %1 redacted their knock.
     
     
-        
+        
         You joined this room.
         You joined this room.
     
     
-        
+        
         %1 has changed their avatar and changed their display name to %2.
         %1 has changed their avatar and changed their display name to %2.
     
@@ -2081,7 +2096,7 @@ Example: https://server.my:8787
         No room open
     
     
-        
+        
         %1 member(s)
         %1 member(s)
     
@@ -2188,7 +2203,7 @@ Example: https://server.my:8787
 
     UserProfile
     
-        
+        
         Global User Profile
         Global User Profile
     
@@ -2198,28 +2213,63 @@ Example: https://server.my:8787
         Room User Profile
     
     
-        
-        
+        
+        Change avatar globally.
+        Change avatar globally.
+    
+    
+        
+        Change avatar. Will only apply to this room.
+        Change avatar. Will only apply to this room.
+    
+    
+        
+        Change display name globally.
+        Change display name globally.
+    
+    
+        
+        Change display name. Will only apply to this room.
+        Change display name. Will only apply to this room.
+    
+    
+        
+        Room: %1
+        Room: %1
+    
+    
+        
+        This is a room-specific profile. The user's name and avatar may be different from their global versions.
+        This is a room-specific profile. The user's name and avatar may be different from their global versions.
+    
+    
+        
+        Open the global profile for this user.
+        Open the global profile for this user.
+    
+    
+        
+        
         Verify
         Verify
     
     
-        
-        Ban the user
-        Ban the user
-    
-    
-        
-        Start a private chat
-        Start a private chat
+        
+        Start a private chat.
+        Start a private chat.
     
     
         
-        Kick the user
-        Kick the user
+        Kick the user.
+        Kick the user.
     
     
-        
+        
+        Ban the user.
+        Ban the user.
+    
+    
+        
         Unverify
         Unverify
     
diff --git a/resources/langs/nheko_eo.ts b/resources/langs/nheko_eo.ts
index 4876359a..7e2d8d4e 100644
--- a/resources/langs/nheko_eo.ts
+++ b/resources/langs/nheko_eo.ts
@@ -131,17 +131,17 @@
     
     
         
-        
+        
         Invited user: %1
         Invitita uzanto: %1
     
     
-        
+        
         Migrating the cache to the current version failed. This can have different reasons. Please open an issue and try to use an older version in the mean time. Alternatively you can try deleting the cache manually.
         Malsukcesis migrado de kaŝmemoro al nuna versio. Tio povas havi diversajn kialojn. Bonvolu raporti eraron kaj dume provi malpli novan version. Alternative, vi povas provi forigi la kaŝmemoron permane.
     
     
-        
+        
         Confirm join
         Konfirmu aliĝon
     
@@ -158,12 +158,12 @@
     
     
         
-        
+        
         Confirm invite
         Konfirmu inviton
     
     
-        
+        
         Do you really want to invite %1 (%2)?
         Ĉu vi certe volas inviti uzanton %1 (%2)?
     
@@ -228,12 +228,12 @@
         Malforbaris uzanton: %1
     
     
-        
+        
         Do you really want to start a private chat with %1?
         Ĉu vi certe volas komenci privatan babilon kun %1?
     
     
-        
+        
         Cache migration failed!
         Malsukcesis migrado de kaŝmemoro!
     
@@ -260,7 +260,7 @@
         Malsukcesis rehavi konservitajn datumojn. Bonvolu resaluti.
     
     
-        
+        
         Failed to setup encryption keys. Server response: %1 %2. Please try again later.
         Malsukcesis agordi ĉifrajn ŝlosilojn. Respondo de servilo: %1 %2. Bonvolu reprovi poste.
     
@@ -355,7 +355,7 @@
 
     CrossSigningSecrets
     
-        
+        
         Decrypt secrets
         Malĉifri sekretojn
     
@@ -736,7 +736,7 @@
 
     InputBar
     
-        
+        
         Select a file
         Elektu dosieron
     
@@ -1047,17 +1047,19 @@ Ekzemplo: https://servilo.mia:8787
         Elektebloj
     
     
-        
+        
+        
         &Copy
         &Kopii
     
     
-        
+        
+        
         Copy &link location
         Kopii celon de &ligilo
     
     
-        
+        
         Re&act
         Re&agi
     
@@ -1116,6 +1118,11 @@ Ekzemplo: https://servilo.mia:8787
         Copy link to eve&nt
         Kopii ligilon al oka&zo
     
+    
+        
+        &Go to reply
+        
+    
 
 
     NewVerificationRequest
@@ -1175,6 +1182,14 @@ Ekzemplo: https://servilo.mia:8787
         Akcepti
     
 
+
+    NotificationWarning
+    
+        
+        You will be pinging the whole room
+        
+    
+
 
     NotificationsManager
     
@@ -1422,7 +1437,7 @@ Ekzemplo: https://servilo.mia:8787
 
     RoomInfo
     
-        
+        
         no version stored
         neniu versio konservita
     
@@ -1792,7 +1807,7 @@ Ekzemplo: https://servilo.mia:8787
 
     SecretStorage
     
-        
+        
         Failed to connect to secret storage
         Malsukcesis konektiĝi al sekreta deponejo
     
@@ -1879,7 +1894,7 @@ Ekzemplo: https://servilo.mia:8787
 
     TimelineModel
     
-        
+        
         Message redaction failed: %1
         Malsukcesis redaktado de mesaĝo: %1
     
@@ -1980,7 +1995,7 @@ Ekzemplo: https://servilo.mia:8787
         %1 estis invitita.
     
     
-        
+        
         %1 changed their avatar.
         %1 ŝanĝis sian avataron.
         %1 ŝanĝis sian profilbildon.
@@ -2041,12 +2056,12 @@ Ekzemplo: https://servilo.mia:8787
         %1 forigis sian frapon.
     
     
-        
+        
         You joined this room.
         Vi aliĝis ĉi tiun ĉambron.
     
     
-        
+        
         %1 has changed their avatar and changed their display name to %2.
         %1 ŝanĝis sian profilbildon kaj sian prezentan nomon al %2.
     
@@ -2088,7 +2103,7 @@ Ekzemplo: https://servilo.mia:8787
         Neniu ĉambro estas malfermita
     
     
-        
+        
         %1 member(s)
         %1 ĉambrano(j)
     
@@ -2195,7 +2210,7 @@ Ekzemplo: https://servilo.mia:8787
 
     UserProfile
     
-        
+        
         Global User Profile
         Ĉiea profilo de uzanto
     
@@ -2205,28 +2220,63 @@ Ekzemplo: https://servilo.mia:8787
         Ĉambra profilo de uzanto
     
     
-        
-        
+        
+        Change avatar globally.
+        
+    
+    
+        
+        Change avatar. Will only apply to this room.
+        
+    
+    
+        
+        Change display name globally.
+        
+    
+    
+        
+        Change display name. Will only apply to this room.
+        
+    
+    
+        
+        Room: %1
+        
+    
+    
+        
+        This is a room-specific profile. The user's name and avatar may be different from their global versions.
+        
+    
+    
+        
+        Open the global profile for this user.
+        
+    
+    
+        
+        
         Verify
         Kontroli
     
     
-        
-        Ban the user
-        Forbari la uzanton
-    
-    
-        
-        Start a private chat
-        Komenci privatan babilon
+        
+        Start a private chat.
+        
     
     
         
-        Kick the user
-        Forpeli la uzanton
+        Kick the user.
+        
     
     
-        
+        
+        Ban the user.
+        
+    
+    
+        
         Unverify
         Malkontroli
     
diff --git a/resources/langs/nheko_es.ts b/resources/langs/nheko_es.ts
index 4cd55927..ed159928 100644
--- a/resources/langs/nheko_es.ts
+++ b/resources/langs/nheko_es.ts
@@ -131,17 +131,17 @@
     
     
         
-        
+        
         Invited user: %1
         Usuario invitado: %1
     
     
-        
+        
         Migrating the cache to the current version failed. This can have different reasons. Please open an issue and try to use an older version in the mean time. Alternatively you can try deleting the cache manually.
         La migración de la caché a la versión actual ha fallado. Esto puede deberse a distintos motivos. Por favor, reporte el incidente y mientras tanto intente usar una versión anterior. También puede probar a borrar la caché manualmente.
     
     
-        
+        
         Confirm join
         
     
@@ -157,12 +157,12 @@
     
     
         
-        
+        
         Confirm invite
         Confirmar invitación
     
     
-        
+        
         Do you really want to invite %1 (%2)?
         
     
@@ -227,12 +227,12 @@
         
     
     
-        
+        
         Do you really want to start a private chat with %1?
         
     
     
-        
+        
         Cache migration failed!
         
     
@@ -259,7 +259,7 @@
         
     
     
-        
+        
         Failed to setup encryption keys. Server response: %1 %2. Please try again later.
         
     
@@ -354,7 +354,7 @@
 
     CrossSigningSecrets
     
-        
+        
         Decrypt secrets
         
     
@@ -735,7 +735,7 @@
 
     InputBar
     
-        
+        
         Select a file
         
     
@@ -1039,17 +1039,19 @@ Example: https://server.my:8787
         
     
     
-        
+        
+        
         &Copy
         
     
     
-        
+        
+        
         Copy &link location
         
     
     
-        
+        
         Re&act
         
     
@@ -1108,6 +1110,11 @@ Example: https://server.my:8787
         Copy link to eve&nt
         
     
+    
+        
+        &Go to reply
+        
+    
 
 
     NewVerificationRequest
@@ -1167,6 +1174,14 @@ Example: https://server.my:8787
         Aceptar
     
 
+
+    NotificationWarning
+    
+        
+        You will be pinging the whole room
+        
+    
+
 
     NotificationsManager
     
@@ -1414,7 +1429,7 @@ Example: https://server.my:8787
 
     RoomInfo
     
-        
+        
         no version stored
         
     
@@ -1784,7 +1799,7 @@ Example: https://server.my:8787
 
     SecretStorage
     
-        
+        
         Failed to connect to secret storage
         
     
@@ -1870,7 +1885,7 @@ Example: https://server.my:8787
 
     TimelineModel
     
-        
+        
         Message redaction failed: %1
         
     
@@ -1970,7 +1985,7 @@ Example: https://server.my:8787
         
     
     
-        
+        
         %1 has changed their avatar and changed their display name to %2.
         
     
@@ -2040,12 +2055,12 @@ Example: https://server.my:8787
         
     
     
-        
+        
         You joined this room.
         Te has unido a esta sala.
     
     
-        
+        
         Rejected the knock from %1.
         
     
@@ -2077,7 +2092,7 @@ Example: https://server.my:8787
         
     
     
-        
+        
         %1 member(s)
         
     
@@ -2184,7 +2199,7 @@ Example: https://server.my:8787
 
     UserProfile
     
-        
+        
         Global User Profile
         
     
@@ -2194,28 +2209,63 @@ Example: https://server.my:8787
         
     
     
-        
-        
-        Verify
+        
+        Change avatar globally.
         
     
     
-        
-        Ban the user
+        
+        Change avatar. Will only apply to this room.
         
     
     
-        
-        Start a private chat
+        
+        Change display name globally.
+        
+    
+    
+        
+        Change display name. Will only apply to this room.
+        
+    
+    
+        
+        Room: %1
+        
+    
+    
+        
+        This is a room-specific profile. The user's name and avatar may be different from their global versions.
+        
+    
+    
+        
+        Open the global profile for this user.
         
     
     
         
-        Kick the user
+        
+        Verify
         
     
     
-        
+        
+        Start a private chat.
+        
+    
+    
+        
+        Kick the user.
+        
+    
+    
+        
+        Ban the user.
+        
+    
+    
+        
         Unverify
         
     
diff --git a/resources/langs/nheko_et.ts b/resources/langs/nheko_et.ts
index 6672c48c..1673236a 100644
--- a/resources/langs/nheko_et.ts
+++ b/resources/langs/nheko_et.ts
@@ -131,17 +131,17 @@
     
     
         
-        
+        
         Invited user: %1
         Kutsutud kasutaja: %1
     
     
-        
+        
         Migrating the cache to the current version failed. This can have different reasons. Please open an issue and try to use an older version in the mean time. Alternatively you can try deleting the cache manually.
         Puhverdatud andmete muutmine sobivaks rakenduse praeguse versiooniga ei õnnestunud. Sellel võib olla erinevaid põhjuseid. Palun saada meile veateade ja seni kasuta vanemat rakenduse versiooni. Aga kui sa soovid proovida, siis kustuta puhverdatud andmed käsitsi.
     
     
-        
+        
         Confirm join
         Kinnita liitumine
     
@@ -157,12 +157,12 @@
     
     
         
-        
+        
         Confirm invite
         Kinnita kutse
     
     
-        
+        
         Do you really want to invite %1 (%2)?
         Kas sa tõesti soovid saata kutset kasutajale %1 (%2)?
     
@@ -227,12 +227,12 @@
         Suhtluskeeld eemaldatud: %1
     
     
-        
+        
         Do you really want to start a private chat with %1?
         Kas sa kindlasti soovid alustada otsevestlust kasutajaga %1?
     
     
-        
+        
         Cache migration failed!
         Puhvri versiooniuuendus ebaõnnestus!
     
@@ -259,7 +259,7 @@
         Salvestatud andmete taastamine ei õnnestunud. Palun logi uuesti sisse.
     
     
-        
+        
         Failed to setup encryption keys. Server response: %1 %2. Please try again later.
         Krüptovõtmete kasutusele võtmine ei õnnestunud. Koduserveri vastus päringule: %1 %2. Palun proovi hiljem uuesti.
     
@@ -354,7 +354,7 @@
 
     CrossSigningSecrets
     
-        
+        
         Decrypt secrets
         Dekrüpti andmed
     
@@ -735,7 +735,7 @@
 
     InputBar
     
-        
+        
         Select a file
         Vali fail
     
@@ -1043,17 +1043,19 @@ Näiteks: https://server.minu:8787
         Valikud
     
     
-        
+        
+        
         &Copy
         &Kopeeri
     
     
-        
+        
+        
         Copy &link location
         Kopeeri &lingi asukoht
     
     
-        
+        
         Re&act
         Re&ageeri
     
@@ -1112,6 +1114,11 @@ Näiteks: https://server.minu:8787
         Copy link to eve&nt
         Kopeeri sündmuse li&nk
     
+    
+        
+        &Go to reply
+        
+    
 
 
     NewVerificationRequest
@@ -1171,6 +1178,14 @@ Näiteks: https://server.minu:8787
         Nõustu
     
 
+
+    NotificationWarning
+    
+        
+        You will be pinging the whole room
+        
+    
+
 
     NotificationsManager
     
@@ -1418,7 +1433,7 @@ Näiteks: https://server.minu:8787
 
     RoomInfo
     
-        
+        
         no version stored
         salvestatud versiooni ei leidu
     
@@ -1788,7 +1803,7 @@ Näiteks: https://server.minu:8787
 
     SecretStorage
     
-        
+        
         Failed to connect to secret storage
         Ühenduse loomine võtmehoidlaga ei õnnestunud
     
@@ -1874,7 +1889,7 @@ Näiteks: https://server.minu:8787
 
     TimelineModel
     
-        
+        
         Message redaction failed: %1
         Sõnumi ümbersõnastamine ebaõnnestus: %1
     
@@ -1974,7 +1989,7 @@ Näiteks: https://server.minu:8787
         %1 sai kutse.
     
     
-        
+        
         %1 changed their avatar.
         %1 muutis oma tunnuspilti.
     
@@ -2034,12 +2049,12 @@ Näiteks: https://server.minu:8787
         %1 muutis oma koputust jututoa uksele.
     
     
-        
+        
         You joined this room.
         Sa liitusid jututoaga.
     
     
-        
+        
         %1 has changed their avatar and changed their display name to %2.
         %1 muutis oma tunnuspilti ja seadistas uueks kuvatavaks nimeks %2.
     
@@ -2081,7 +2096,7 @@ Näiteks: https://server.minu:8787
         Ühtegi jututuba pole avatud
     
     
-        
+        
         %1 member(s)
         %1 liige(t)
     
@@ -2188,7 +2203,7 @@ Näiteks: https://server.minu:8787
 
     UserProfile
     
-        
+        
         Global User Profile
         Üldine kasutajaprofiil
     
@@ -2198,28 +2213,63 @@ Näiteks: https://server.minu:8787
         Kasutajaprofiil jututoas
     
     
-        
-        
+        
+        Change avatar globally.
+        
+    
+    
+        
+        Change avatar. Will only apply to this room.
+        
+    
+    
+        
+        Change display name globally.
+        
+    
+    
+        
+        Change display name. Will only apply to this room.
+        
+    
+    
+        
+        Room: %1
+        
+    
+    
+        
+        This is a room-specific profile. The user's name and avatar may be different from their global versions.
+        
+    
+    
+        
+        Open the global profile for this user.
+        
+    
+    
+        
+        
         Verify
         Verifitseeri
     
     
-        
-        Ban the user
-        Sea kasutajale suhtluskeeld
-    
-    
-        
-        Start a private chat
-        Alusta privaatset vestlust
+        
+        Start a private chat.
+        
     
     
         
-        Kick the user
-        Müksa kasutaja välja
+        Kick the user.
+        
     
     
-        
+        
+        Ban the user.
+        
+    
+    
+        
         Unverify
         Võta verifitseerimine tagasi
     
diff --git a/resources/langs/nheko_fi.ts b/resources/langs/nheko_fi.ts
index cadf9497..e4f006e7 100644
--- a/resources/langs/nheko_fi.ts
+++ b/resources/langs/nheko_fi.ts
@@ -131,17 +131,17 @@
     
     
         
-        
+        
         Invited user: %1
         Kutsuttu käyttäjä: %1
     
     
-        
+        
         Migrating the cache to the current version failed. This can have different reasons. Please open an issue and try to use an older version in the mean time. Alternatively you can try deleting the cache manually.
         Välimuistin tuominen nykyiseen versioon epäonnistui. Tällä voi olla eri syitä. Luo vikailmoitus ja yritä sillä aikaa käyttää vanhempaa versiota. Voit myös vaihtoehtoisesti koettaa tyhjentää välimuistin käsin.
     
     
-        
+        
         Confirm join
         Vahvista liittyminen
     
@@ -157,12 +157,12 @@
     
     
         
-        
+        
         Confirm invite
         Vahvista kutsu
     
     
-        
+        
         Do you really want to invite %1 (%2)?
         Haluatko kutsua %1 (%2)?
     
@@ -227,12 +227,12 @@
         Purettiin porttikielto käyttäjältä %1
     
     
-        
+        
         Do you really want to start a private chat with %1?
         Haluatko luoda yksityisen keskustelun käyttäjän %1 kanssa?
     
     
-        
+        
         Cache migration failed!
         Välimuistin siirto epäonnistui!
     
@@ -259,7 +259,7 @@
         Tallennettujen tietojen palauttaminen epäonnistui. Ole hyvä ja kirjaudu sisään uudelleen.
     
     
-        
+        
         Failed to setup encryption keys. Server response: %1 %2. Please try again later.
         Salausavainten lähetys epäonnistui. Palvelimen vastaus: %1 %2. Ole hyvä ja yritä uudelleen myöhemmin.
     
@@ -354,7 +354,7 @@
 
     CrossSigningSecrets
     
-        
+        
         Decrypt secrets
         Salaisuuksien salauksen purku
     
@@ -735,7 +735,7 @@
 
     InputBar
     
-        
+        
         Select a file
         Valitse tiedosto
     
@@ -1043,17 +1043,19 @@ Esimerkki: https://server.my:8787
         Asetukset
     
     
-        
+        
+        
         &Copy
         &Kopioi
     
     
-        
+        
+        
         Copy &link location
         Kopioi &linkki sijainti
     
     
-        
+        
         Re&act
         Re&act
     
@@ -1112,6 +1114,11 @@ Esimerkki: https://server.my:8787
         Copy link to eve&nt
         Kopioi linkki tapaht&umaan
     
+    
+        
+        &Go to reply
+        
+    
 
 
     NewVerificationRequest
@@ -1171,6 +1178,14 @@ Esimerkki: https://server.my:8787
         Hyväksy
     
 
+
+    NotificationWarning
+    
+        
+        You will be pinging the whole room
+        
+    
+
 
     NotificationsManager
     
@@ -1418,7 +1433,7 @@ Esimerkki: https://server.my:8787
 
     RoomInfo
     
-        
+        
         no version stored
         ei tallennettua versiota
     
@@ -1788,7 +1803,7 @@ Esimerkki: https://server.my:8787
 
     SecretStorage
     
-        
+        
         Failed to connect to secret storage
         
     
@@ -1874,7 +1889,7 @@ Esimerkki: https://server.my:8787
 
     TimelineModel
     
-        
+        
         Message redaction failed: %1
         Viestin muokkaus epäonnistui: %1
     
@@ -1974,7 +1989,7 @@ Esimerkki: https://server.my:8787
         &1 kutsuttiin.
     
     
-        
+        
         %1 changed their avatar.
         %1 muutti avatariaan.
     
@@ -2034,12 +2049,12 @@ Esimerkki: https://server.my:8787
         %1 perui koputuksensa.
     
     
-        
+        
         You joined this room.
         Sinä liityit tähän huoneeseen.
     
     
-        
+        
         %1 has changed their avatar and changed their display name to %2.
         %1 vaihtoi avatariaan ja vaihtoi näyttönimekseen %2.
     
@@ -2081,7 +2096,7 @@ Esimerkki: https://server.my:8787
         Ei avointa huonetta
     
     
-        
+        
         %1 member(s)
         %1 jäsentä
     
@@ -2188,7 +2203,7 @@ Esimerkki: https://server.my:8787
 
     UserProfile
     
-        
+        
         Global User Profile
         Yleinen käyttäjäprofiili
     
@@ -2198,28 +2213,63 @@ Esimerkki: https://server.my:8787
         Huoneen käyttäjäprofiili
     
     
-        
-        
+        
+        Change avatar globally.
+        
+    
+    
+        
+        Change avatar. Will only apply to this room.
+        
+    
+    
+        
+        Change display name globally.
+        
+    
+    
+        
+        Change display name. Will only apply to this room.
+        
+    
+    
+        
+        Room: %1
+        
+    
+    
+        
+        This is a room-specific profile. The user's name and avatar may be different from their global versions.
+        
+    
+    
+        
+        Open the global profile for this user.
+        
+    
+    
+        
+        
         Verify
         Vahvista
     
     
-        
-        Ban the user
-        Anna käyttäjälle porttikielto
-    
-    
-        
-        Start a private chat
-        Aloita yksityinen keskustelu
+        
+        Start a private chat.
+        
     
     
         
-        Kick the user
-        Potki käyttäjä
+        Kick the user.
+        
     
     
-        
+        
+        Ban the user.
+        
+    
+    
+        
         Unverify
         Peru vahvistus
     
diff --git a/resources/langs/nheko_fr.ts b/resources/langs/nheko_fr.ts
index e4c6bfcb..b584fa3c 100644
--- a/resources/langs/nheko_fr.ts
+++ b/resources/langs/nheko_fr.ts
@@ -96,12 +96,12 @@
     
         
         Unknown microphone: %1
-        Microphone inconnu : %1
+        Microphone inconnu : %1
     
     
         
         Unknown camera: %1
-        Caméra inconnue : %1
+        Caméra inconnue : %1
     
     
         
@@ -131,17 +131,17 @@
     
     
         
-        
+        
         Invited user: %1
         Utilisateur %1 invité(e)
     
     
-        
+        
         Migrating the cache to the current version failed. This can have different reasons. Please open an issue and try to use an older version in the mean time. Alternatively you can try deleting the cache manually.
         La migration du cache vers la version actuelle a échoué. Cela peut arriver pour différentes raisons. Signalez le problème et essayez d'utiliser une ancienne version en attendant. Vous pouvez également supprimer le cache manuellement.
     
     
-        
+        
         Confirm join
         Confirmez la participation
     
@@ -157,19 +157,19 @@
     
     
         
-        
+        
         Confirm invite
         Confirmer l'invitation
     
     
-        
+        
         Do you really want to invite %1 (%2)?
-        Voulez-vous vraiment inviter %1 (%2) ?
+        Voulez-vous vraiment inviter %1 (%2) ?
     
     
         
         Failed to invite %1 to %2: %3
-        Échec de l'invitation de %1 dans %2 : %3
+        Échec de l'invitation de %1 dans %2 : %3
     
     
         
@@ -179,7 +179,7 @@
     
         
         Do you really want to kick %1 (%2)?
-        Voulez-vous vraiment expulser %1 (%2) ?
+        Voulez-vous vraiment expulser %1 (%2) ?
     
     
         
@@ -194,7 +194,7 @@
     
         
         Do you really want to ban %1 (%2)?
-        Voulez-vous vraiment bannir %1 (%2) ?
+        Voulez-vous vraiment bannir %1 (%2) ?
     
     
         
@@ -214,12 +214,12 @@
     
         
         Do you really want to unban %1 (%2)?
-        Voulez-vous vraiment annuler le bannissement de %1 (%2) ?
+        Voulez-vous vraiment annuler le bannissement de %1 (%2) ?
     
     
         
         Failed to unban %1 in %2: %3
-        Échec de l'annulation du bannissement de %1 dans %2 : %3
+        Échec de l'annulation du bannissement de %1 dans %2 : %3
     
     
         
@@ -227,14 +227,14 @@
         %1 n'est plus banni(e)
     
     
-        
+        
         Do you really want to start a private chat with %1?
         Voulez-vous vraiment commencer une discussion privée avec %1 ?
     
     
-        
+        
         Cache migration failed!
-        Échec de la migration du cache !
+        Échec de la migration du cache !
     
     
         
@@ -259,20 +259,20 @@
         Échec de la restauration des données sauvegardées. Veuillez vous reconnecter.
     
     
-        
+        
         Failed to setup encryption keys. Server response: %1 %2. Please try again later.
-        Échec de la configuration des clés de chiffrement. Réponse du serveur : %1 %2. Veuillez réessayer plus tard.
+        Échec de la configuration des clés de chiffrement. Réponse du serveur : %1 %2. Veuillez réessayer plus tard.
     
     
         
         
         Please try to login again: %1
-        Veuillez re-tenter vous reconnecter : %1
+        Veuillez re-tenter vous reconnecter : %1
     
     
         
         Failed to join room: %1
-        Impossible de rejoindre le salon : %1
+        Impossible de rejoindre le salon : %1
     
     
         
@@ -282,22 +282,22 @@
     
         
         Failed to remove invite: %1
-        Impossible de supprimer l'invitation : %1
+        Impossible de supprimer l'invitation : %1
     
     
         
         Room creation failed: %1
-        Échec de la création du salon : %1
+        Échec de la création du salon : %1
     
     
         
         Failed to leave room: %1
-        Impossible de quitter le salon : %1
+        Impossible de quitter le salon : %1
     
     
         
         Failed to kick %1 from %2: %3
-        Échec de l'expulsion de %1 de %2  : %3
+        Échec de l'expulsion de %1 de %2  : %3
     
 
 
@@ -354,19 +354,19 @@
 
     CrossSigningSecrets
     
-        
+        
         Decrypt secrets
         Déchiffrer les secrets
     
     
         
         Enter your recovery key or passphrase to decrypt your secrets:
-        Entrez votre clé de récupération ou phrase de passe pour déchiffrer vos secrets :
+        Entrez votre clé de récupération ou phrase de passe pour déchiffrer vos secrets :
     
     
         
         Enter your recovery key or passphrase called %1 to decrypt your secrets:
-        Entrez votre clé de récupération ou votre phrase de passe nommée %1 pour déchiffrer vos secrets :
+        Entrez votre clé de récupération ou votre phrase de passe nommée %1 pour déchiffrer vos secrets :
     
     
         
@@ -389,17 +389,17 @@
     
         
         Please verify the following digits. You should see the same numbers on both sides. If they differ, please press 'They do not match!' to abort verification!
-        Veuillez vérifier les chiffres suivants. Vous devriez voir les mêmes chiffres des deux côtés. Si ceux-ci diffèrent, veuillez choisir « Ils sont différents ! » pour annuler la vérification !
+        Veuillez vérifier les chiffres suivants. Vous devriez voir les mêmes chiffres des deux côtés. Si ceux-ci diffèrent, veuillez choisir « Ils sont différents ! » pour annuler la vérification !
     
     
         
         They do not match!
-        Ils sont différents !
+        Ils sont différents !
     
     
         
         They match!
-        Ils sont identiques !
+        Ils sont identiques !
     
 
 
@@ -483,17 +483,17 @@
     
         
         Please verify the following emoji. You should see the same emoji on both sides. If they differ, please press 'They do not match!' to abort verification!
-        Veuillez vérifier les émoji suivants. Vous devriez voir les mêmes émoji des deux côtés. S'ils diffèrent, veuillez choisir « Ils sont différents ! » pour annuler la vérification !
+        Veuillez vérifier les émoji suivants. Vous devriez voir les mêmes émoji des deux côtés. S'ils diffèrent, veuillez choisir « Ils sont différents ! » pour annuler la vérification !
     
     
         
         They do not match!
-        Ils sont différents !
+        Ils sont différents !
     
     
         
         They match!
-        Ils sont identiques !
+        Ils sont identiques !
     
 
 
@@ -544,7 +544,7 @@
     
         
         This message is not encrypted!
-        Ce message n'est pas chiffré !
+        Ce message n'est pas chiffré !
     
     
         
@@ -577,7 +577,7 @@
     
         
         Key mismatch detected!
-        Clés non correspondantes détectées !
+        Clés non correspondantes détectées !
     
     
         
@@ -735,7 +735,7 @@
 
     InputBar
     
-        
+        
         Select a file
         Sélectionnez un fichier
     
@@ -802,9 +802,9 @@
 You can also put your homeserver address there, if your server doesn't support .well-known lookup.
 Example: @user:server.my
 If Nheko fails to discover your homeserver, it will show you a field to enter the server manually.
-        Votre nom de connexion. Un mxid doit commencer par un « @ » suivi de l'identifiant. L'identifiant doit être suivi du nom de serveur, séparé de celui-ci par « : ».
+        Votre nom de connexion. Un mxid doit commencer par un « @ » suivi de l'identifiant. L'identifiant doit être suivi du nom de serveur, séparé de celui-ci par « : ».
 Vous pouvez également spécifier l'adresse de votre serveur ici, si votre serveur ne supporte pas l'identification .well-known.
-Exemple : @utilisateur :monserveur.example.com
+Exemple : @utilisateur :monserveur.example.com
 Si Nheko n'arrive pas à trouver votre serveur, il vous proposera de l'indiquer manuellement.
     
     
@@ -842,7 +842,7 @@ Si Nheko n'arrive pas à trouver votre serveur, il vous proposera de l&apos
         The address that can be used to contact you homeservers client API.
 Example: https://server.my:8787
         L'adresse qui peut être utilisée pour joindre l'API client de votre serveur.
-Exemple : https ://monserveur.example.com:8787
+Exemple : https ://monserveur.example.com:8787
     
     
         
@@ -855,7 +855,7 @@ Exemple : https ://monserveur.example.com:8787
         
         
         You have entered an invalid Matrix ID  e.g @joe:matrix.org
-        Vous avez entré un identifiant Matrix invalide  exemple correct : @moi:monserveur.example.com)
+        Vous avez entré un identifiant Matrix invalide  exemple correct : @moi:monserveur.example.com)
     
     
         
@@ -914,7 +914,7 @@ Exemple : https ://monserveur.example.com:8787
     
         
         room name changed to: %1
-        nom du salon changé en : %1
+        nom du salon changé en : %1
     
     
         
@@ -924,7 +924,7 @@ Exemple : https ://monserveur.example.com:8787
     
         
         topic changed to: %1
-        sujet changé en : %1
+        sujet changé en : %1
     
     
         
@@ -939,7 +939,7 @@ Exemple : https ://monserveur.example.com:8787
     
         
         %1 created and configured room: %2
-        %1 a créé et configuré le salon : %2
+        %1 a créé et configuré le salon : %2
     
     
         
@@ -1043,17 +1043,19 @@ Exemple : https ://monserveur.example.com:8787
         Options
     
     
-        
+        
+        
         &Copy
         &Copier
     
     
-        
+        
+        
         Copy &link location
         Copier l'adresse du &lien
     
     
-        
+        
         Re&act
         Ré&agir
     
@@ -1112,6 +1114,11 @@ Exemple : https ://monserveur.example.com:8787
         Copy link to eve&nt
         Copier le lien vers l'évène&nement
     
+    
+        
+        &Go to reply
+        
+    
 
 
     NewVerificationRequest
@@ -1128,7 +1135,7 @@ Exemple : https ://monserveur.example.com:8787
     
         
         To allow other users to see, which of your devices actually belong to you, you can verify them. This also allows key backup to work automatically. Verify %1 now?
-        Pour permettre aux autres utilisateurs de vérifier quels appareils de votre compte sont réellement les vôtres, vous pouvez les vérifier. Cela permet également à la sauvegarde des clés de fonctionner automatiquement. Vérifier %1 maintenant ?
+        Pour permettre aux autres utilisateurs de vérifier quels appareils de votre compte sont réellement les vôtres, vous pouvez les vérifier. Cela permet également à la sauvegarde des clés de fonctionner automatiquement. Vérifier %1 maintenant ?
     
     
         
@@ -1171,6 +1178,14 @@ Exemple : https ://monserveur.example.com:8787
         Accepter
     
 
+
+    NotificationWarning
+    
+        
+        You will be pinging the whole room
+        
+    
+
 
     NotificationsManager
     
@@ -1196,7 +1211,7 @@ Exemple : https ://monserveur.example.com:8787
         
         %1: %2
         Format a normal message in a notification. %1 is the sender, %2 the message
-        %1 : %2
+        %1 : %2
     
     
         
@@ -1220,7 +1235,7 @@ Exemple : https ://monserveur.example.com:8787
     
         
         Place a call to %1?
-        Appeler %1 ?
+        Appeler %1 ?
     
     
         
@@ -1253,7 +1268,7 @@ Exemple : https ://monserveur.example.com:8787
     
         
         unimplemented event: 
-        Évènement non implémenté : 
+        Évènement non implémenté : 
     
 
 
@@ -1336,7 +1351,7 @@ Exemple : https ://monserveur.example.com:8787
     
         
         No supported registration flows!
-        Aucune méthode d'inscription supportée !
+        Aucune méthode d'inscription supportée !
     
     
         
@@ -1418,7 +1433,7 @@ Exemple : https ://monserveur.example.com:8787
 
     RoomInfo
     
-        
+        
         no version stored
         pas de version enregistrée
     
@@ -1453,7 +1468,7 @@ Exemple : https ://monserveur.example.com:8787
     
         
         Tag room as:
-        Étiqueter le salon comme :
+        Étiqueter le salon comme :
     
     
         
@@ -1684,7 +1699,7 @@ Exemple : https ://monserveur.example.com:8787
     
         
         Failed to enable encryption: %1
-        Échec de l'activation du chiffrement : %1
+        Échec de l'activation du chiffrement : %1
     
     
         
@@ -1704,13 +1719,13 @@ Exemple : https ://monserveur.example.com:8787
     
         
         Error while reading file: %1
-        Erreur lors de la lecture du fichier : %1
+        Erreur lors de la lecture du fichier : %1
     
     
         
         
         Failed to upload image: %s
-        Échec de l'envoi de l'image : %s
+        Échec de l'envoi de l'image : %s
     
 
 
@@ -1736,17 +1751,17 @@ Exemple : https ://monserveur.example.com:8787
     
         
         Share desktop with %1?
-        Partager le bureau avec %1  ?
+        Partager le bureau avec %1  ?
     
     
         
         Window:
-        Fenêtre :
+        Fenêtre :
     
     
         
         Frame rate:
-        Fréquence d'images :
+        Fréquence d'images :
     
     
         
@@ -1788,7 +1803,7 @@ Exemple : https ://monserveur.example.com:8787
 
     SecretStorage
     
-        
+        
         Failed to connect to secret storage
         Échec de la connexion au stockage des secrets
     
@@ -1863,7 +1878,7 @@ Exemple : https ://monserveur.example.com:8787
     
         
         Verification successful! Both sides verified their devices!
-        Vérification réussie !  Les deux côtés ont vérifié leur appareil !
+        Vérification réussie !  Les deux côtés ont vérifié leur appareil !
     
     
         
@@ -1874,15 +1889,15 @@ Exemple : https ://monserveur.example.com:8787
 
     TimelineModel
     
-        
+        
         Message redaction failed: %1
-        Échec de la suppression du message : %1
+        Échec de la suppression du message : %1
     
     
         
         
         Failed to encrypt event, sending aborted!
-        Échec du chiffrement de l'évènement, envoi abandonné !
+        Échec du chiffrement de l'évènement, envoi abandonné !
     
     
         
@@ -1974,7 +1989,7 @@ Exemple : https ://monserveur.example.com:8787
         %1 a été invité(e).
     
     
-        
+        
         %1 changed their avatar.
         %1 a changé son avatar.
     
@@ -2034,12 +2049,12 @@ Exemple : https ://monserveur.example.com:8787
         %1 a arrêté de toquer.
     
     
-        
+        
         You joined this room.
         Vous avez rejoint ce salon.
     
     
-        
+        
         %1 has changed their avatar and changed their display name to %2.
         %1 a changé son avatar et changé son surnom en %2.
     
@@ -2057,7 +2072,7 @@ Exemple : https ://monserveur.example.com:8787
         
         %1 left after having already left!
         This is a leave event after the user already left and shouldn't happen apart from state resets
-        %1 a quitté le salon après l'avoir déjà quitté !
+        %1 a quitté le salon après l'avoir déjà quitté !
     
     
         
@@ -2081,7 +2096,7 @@ Exemple : https ://monserveur.example.com:8787
         Aucun salon ouvert
     
     
-        
+        
         %1 member(s)
         %1 membre(s)
     
@@ -2188,7 +2203,7 @@ Exemple : https ://monserveur.example.com:8787
 
     UserProfile
     
-        
+        
         Global User Profile
         Profil général de l'utilisateur
     
@@ -2198,28 +2213,63 @@ Exemple : https ://monserveur.example.com:8787
         Profil utilisateur spécifique au salon
     
     
-        
-        
+        
+        Change avatar globally.
+        
+    
+    
+        
+        Change avatar. Will only apply to this room.
+        
+    
+    
+        
+        Change display name globally.
+        
+    
+    
+        
+        Change display name. Will only apply to this room.
+        
+    
+    
+        
+        Room: %1
+        
+    
+    
+        
+        This is a room-specific profile. The user's name and avatar may be different from their global versions.
+        
+    
+    
+        
+        Open the global profile for this user.
+        
+    
+    
+        
+        
         Verify
         Vérifier
     
     
-        
-        Ban the user
-        Bannir l'utilisateur
-    
-    
-        
-        Start a private chat
-        Créer une nouvelle discussion privée
+        
+        Start a private chat.
+        
     
     
         
-        Kick the user
-        Expulser l'utilisateur
+        Kick the user.
+        
     
     
-        
+        
+        Ban the user.
+        
+    
+    
+        
         Unverify
         Dé-vérifier
     
@@ -2241,7 +2291,7 @@ Exemple : https ://monserveur.example.com:8787
     
         
         Error while reading file: %1
-        Erreur lors de la lecture du fichier  : %1
+        Erreur lors de la lecture du fichier  : %1
     
 
 
@@ -2278,7 +2328,7 @@ Exemple : https ://monserveur.example.com:8787
     
         
         profile: %1
-        profil : %1
+        profil : %1
     
     
         
@@ -2733,7 +2783,7 @@ Cela met l'application en évidence dans la barre des tâches.
         
         Enter the passphrase to decrypt the file:
-        Entrez la phrase de passe pour déchiffrer le fichier :
+        Entrez la phrase de passe pour déchiffrer le fichier :
     
     
         
@@ -2744,7 +2794,7 @@ Cela met l'application en évidence dans la barre des tâches.
         
         Enter passphrase to encrypt your session keys:
-        Entrez une phrase de passe pour chiffrer vos clés de session :
+        Entrez une phrase de passe pour chiffrer vos clés de session :
     
     
         
@@ -3026,12 +3076,12 @@ Taille du média : %2
     
         
         You: %1
-        Vous : %1
+        Vous : %1
     
     
         
         %1: %2
-        %1 : %2
+        %1 : %2
     
     
         
diff --git a/resources/langs/nheko_hu.ts b/resources/langs/nheko_hu.ts
index 3626eff2..131f36eb 100644
--- a/resources/langs/nheko_hu.ts
+++ b/resources/langs/nheko_hu.ts
@@ -131,17 +131,17 @@
     
     
         
-        
+        
         Invited user: %1
         A felhasználó meg lett hívva: %1
     
     
-        
+        
         Migrating the cache to the current version failed. This can have different reasons. Please open an issue and try to use an older version in the mean time. Alternatively you can try deleting the cache manually.
         A gyorsítótár átvitele a jelenlegi verzióhoz nem sikerült. Ennek több oka is lehet. Kérlek, írj egy hibajelentést és egyelőre próbálj meg egy régebbi verziót használni! Alternatív megoldásként megprobálhatod eltávolítani a gyorsítótárat kézzel.
     
     
-        
+        
         Confirm join
         Csatlakozás megerősítése
     
@@ -157,12 +157,12 @@
     
     
         
-        
+        
         Confirm invite
         Meghívás megerősítése
     
     
-        
+        
         Do you really want to invite %1 (%2)?
         Biztos, hogy meg akarod hívni a következő felhasználót: %1 (%2)?
     
@@ -227,12 +227,12 @@
         Kitiltás feloldva a felhasználónak: %1
     
     
-        
+        
         Do you really want to start a private chat with %1?
         Biztosan privát csevegést akarsz indítani %1 felhasználóval?
     
     
-        
+        
         Cache migration failed!
         Gyorsítótár migráció nem sikerült!
     
@@ -259,7 +259,7 @@
         Nem sikerült visszaállítani a mentési adatot. Kérlek, jelentkezz be ismét!
     
     
-        
+        
         Failed to setup encryption keys. Server response: %1 %2. Please try again later.
         Nem sikerült beállítani a titkosítási kulcsokat. Válasz a szervertől: %1 %2. Kérlek, próbáld újra később!
     
@@ -354,7 +354,7 @@
 
     CrossSigningSecrets
     
-        
+        
         Decrypt secrets
         Titkos tároló feloldása
     
@@ -735,7 +735,7 @@
 
     InputBar
     
-        
+        
         Select a file
         Fájl kiválasztása
     
@@ -1043,17 +1043,19 @@ Példa: https://szerver.em:8787
         Műveletek
     
     
-        
+        
+        
         &Copy
         
     
     
-        
+        
+        
         Copy &link location
         
     
     
-        
+        
         Re&act
         
     
@@ -1112,6 +1114,11 @@ Példa: https://szerver.em:8787
         Copy link to eve&nt
         
     
+    
+        
+        &Go to reply
+        
+    
 
 
     NewVerificationRequest
@@ -1171,6 +1178,14 @@ Példa: https://szerver.em:8787
         Elfogadás
     
 
+
+    NotificationWarning
+    
+        
+        You will be pinging the whole room
+        
+    
+
 
     NotificationsManager
     
@@ -1418,7 +1433,7 @@ Példa: https://szerver.em:8787
 
     RoomInfo
     
-        
+        
         no version stored
         nincs tárolva verzió
     
@@ -1787,7 +1802,7 @@ Példa: https://szerver.em:8787
 
     SecretStorage
     
-        
+        
         Failed to connect to secret storage
         
     
@@ -1873,7 +1888,7 @@ Példa: https://szerver.em:8787
 
     TimelineModel
     
-        
+        
         Message redaction failed: %1
         Az üzenet visszavonása nem sikerült: %1
     
@@ -1972,7 +1987,7 @@ Példa: https://szerver.em:8787
         %1 meg lett hívva.
     
     
-        
+        
         %1 changed their avatar.
         %1 megváltoztatta a profilképét.
     
@@ -2032,12 +2047,12 @@ Példa: https://szerver.em:8787
         %1 visszavonta a kopogását.
     
     
-        
+        
         You joined this room.
         Csatlakoztál ehhez a szobához.
     
     
-        
+        
         %1 has changed their avatar and changed their display name to %2.
         
     
@@ -2079,7 +2094,7 @@ Példa: https://szerver.em:8787
         Nincs nyitott szoba
     
     
-        
+        
         %1 member(s)
         %1 tag
     
@@ -2186,7 +2201,7 @@ Példa: https://szerver.em:8787
 
     UserProfile
     
-        
+        
         Global User Profile
         Globális felhasználói profil
     
@@ -2196,28 +2211,63 @@ Példa: https://szerver.em:8787
         Szobai felhasználói profil
     
     
-        
-        
+        
+        Change avatar globally.
+        
+    
+    
+        
+        Change avatar. Will only apply to this room.
+        
+    
+    
+        
+        Change display name globally.
+        
+    
+    
+        
+        Change display name. Will only apply to this room.
+        
+    
+    
+        
+        Room: %1
+        
+    
+    
+        
+        This is a room-specific profile. The user's name and avatar may be different from their global versions.
+        
+    
+    
+        
+        Open the global profile for this user.
+        
+    
+    
+        
+        
         Verify
         Hitelesítés
     
     
-        
-        Ban the user
-        A felhasználó tiltása
-    
-    
-        
-        Start a private chat
-        Privát csevegés indítása
+        
+        Start a private chat.
+        
     
     
         
-        Kick the user
-        A felhasználó kirúgása
+        Kick the user.
+        
     
     
-        
+        
+        Ban the user.
+        
+    
+    
+        
         Unverify
         Hitelesítés visszavonása
     
diff --git a/resources/langs/nheko_it.ts b/resources/langs/nheko_it.ts
index 1ecd3358..dee40ec3 100644
--- a/resources/langs/nheko_it.ts
+++ b/resources/langs/nheko_it.ts
@@ -131,17 +131,17 @@
     
     
         
-        
+        
         Invited user: %1
         Invitato utente: %1
     
     
-        
+        
         Migrating the cache to the current version failed. This can have different reasons. Please open an issue and try to use an older version in the mean time. Alternatively you can try deleting the cache manually.
         Migrazione della cache alla versione corrente fallita. Questo può avere diverse cause. Per favore apri una issue e nel frattempo prova ad usare una versione più vecchia. In alternativa puoi provare a cancellare la cache manualmente.
     
     
-        
+        
         Confirm join
         Conferma collegamento
     
@@ -157,12 +157,12 @@
     
     
         
-        
+        
         Confirm invite
         Conferma Invito
     
     
-        
+        
         Do you really want to invite %1 (%2)?
         Vuoi davvero inviare %1 (%2)?
     
@@ -227,12 +227,12 @@
         Rimosso il ban dall'utente: %1
     
     
-        
+        
         Do you really want to start a private chat with %1?
         Sei sicuro di voler avviare una chat privata con %1?
     
     
-        
+        
         Cache migration failed!
         Migrazione della cache fallita!
     
@@ -259,7 +259,7 @@
         Impossibile ripristinare i dati salvati. Per favore accedi nuovamente.
     
     
-        
+        
         Failed to setup encryption keys. Server response: %1 %2. Please try again later.
         Impossibile configurare le chiavi crittografiche. Risposta del server: %1 %2. Per favore riprova in seguito.
     
@@ -354,7 +354,7 @@
 
     CrossSigningSecrets
     
-        
+        
         Decrypt secrets
         Decifra i segreti
     
@@ -735,7 +735,7 @@
 
     InputBar
     
-        
+        
         Select a file
         Seleziona un file
     
@@ -1043,17 +1043,19 @@ Esempio: https://server.mio:8787
         Opzioni
     
     
-        
+        
+        
         &Copy
         
     
     
-        
+        
+        
         Copy &link location
         
     
     
-        
+        
         Re&act
         
     
@@ -1112,6 +1114,11 @@ Esempio: https://server.mio:8787
         Copy link to eve&nt
         
     
+    
+        
+        &Go to reply
+        
+    
 
 
     NewVerificationRequest
@@ -1172,6 +1179,14 @@ Verificare %1 adesso?
         Accetta
     
 
+
+    NotificationWarning
+    
+        
+        You will be pinging the whole room
+        
+    
+
 
     NotificationsManager
     
@@ -1419,7 +1434,7 @@ Verificare %1 adesso?
 
     RoomInfo
     
-        
+        
         no version stored
         nessuna versione memorizzata
     
@@ -1789,7 +1804,7 @@ Verificare %1 adesso?
 
     SecretStorage
     
-        
+        
         Failed to connect to secret storage
         
     
@@ -1875,7 +1890,7 @@ Verificare %1 adesso?
 
     TimelineModel
     
-        
+        
         Message redaction failed: %1
         Oscuramento del messaggio fallito: %1
     
@@ -1975,7 +1990,7 @@ Verificare %1 adesso?
         %1 è stato invitato.
     
     
-        
+        
         %1 changed their avatar.
         %1 ha cambiato il suo avatar.
     
@@ -2035,12 +2050,12 @@ Verificare %1 adesso?
         %1 ha oscurato la sua bussata.
     
     
-        
+        
         You joined this room.
         Sei entrato in questa stanza.
     
     
-        
+        
         %1 has changed their avatar and changed their display name to %2.
         
     
@@ -2082,7 +2097,7 @@ Verificare %1 adesso?
         Nessuna stanza aperta
     
     
-        
+        
         %1 member(s)
         
     
@@ -2189,7 +2204,7 @@ Verificare %1 adesso?
 
     UserProfile
     
-        
+        
         Global User Profile
         
     
@@ -2199,28 +2214,63 @@ Verificare %1 adesso?
         
     
     
-        
-        
-        Verify
+        
+        Change avatar globally.
         
     
     
-        
-        Ban the user
+        
+        Change avatar. Will only apply to this room.
         
     
     
-        
-        Start a private chat
+        
+        Change display name globally.
+        
+    
+    
+        
+        Change display name. Will only apply to this room.
+        
+    
+    
+        
+        Room: %1
+        
+    
+    
+        
+        This is a room-specific profile. The user's name and avatar may be different from their global versions.
+        
+    
+    
+        
+        Open the global profile for this user.
         
     
     
         
-        Kick the user
+        
+        Verify
         
     
     
-        
+        
+        Start a private chat.
+        
+    
+    
+        
+        Kick the user.
+        
+    
+    
+        
+        Ban the user.
+        
+    
+    
+        
         Unverify
         
     
diff --git a/resources/langs/nheko_ja.ts b/resources/langs/nheko_ja.ts
index 51451ede..36b87226 100644
--- a/resources/langs/nheko_ja.ts
+++ b/resources/langs/nheko_ja.ts
@@ -131,17 +131,17 @@
     
     
         
-        
+        
         Invited user: %1
         招待されたユーザー: %1
     
     
-        
+        
         Migrating the cache to the current version failed. This can have different reasons. Please open an issue and try to use an older version in the mean time. Alternatively you can try deleting the cache manually.
         
     
     
-        
+        
         Confirm join
         
     
@@ -157,12 +157,12 @@
     
     
         
-        
+        
         Confirm invite
         
     
     
-        
+        
         Do you really want to invite %1 (%2)?
         
     
@@ -227,12 +227,12 @@
         永久追放を解除されたユーザー: %1
     
     
-        
+        
         Do you really want to start a private chat with %1?
         
     
     
-        
+        
         Cache migration failed!
         
     
@@ -259,7 +259,7 @@
         セーブデータを復元できませんでした。もう一度ログインして下さい。
     
     
-        
+        
         Failed to setup encryption keys. Server response: %1 %2. Please try again later.
         暗号化鍵を設定できませんでした。サーバーの応答: %1 %2. 後でやり直して下さい。
     
@@ -354,7 +354,7 @@
 
     CrossSigningSecrets
     
-        
+        
         Decrypt secrets
         
     
@@ -735,7 +735,7 @@
 
     InputBar
     
-        
+        
         Select a file
         ファイルを選択
     
@@ -1039,17 +1039,19 @@ Example: https://server.my:8787
         オプション
     
     
-        
+        
+        
         &Copy
         
     
     
-        
+        
+        
         Copy &link location
         
     
     
-        
+        
         Re&act
         
     
@@ -1108,6 +1110,11 @@ Example: https://server.my:8787
         Copy link to eve&nt
         
     
+    
+        
+        &Go to reply
+        
+    
 
 
     NewVerificationRequest
@@ -1167,6 +1174,14 @@ Example: https://server.my:8787
         容認
     
 
+
+    NotificationWarning
+    
+        
+        You will be pinging the whole room
+        
+    
+
 
     NotificationsManager
     
@@ -1414,7 +1429,7 @@ Example: https://server.my:8787
 
     RoomInfo
     
-        
+        
         no version stored
         バージョンが保存されていません
     
@@ -1783,7 +1798,7 @@ Example: https://server.my:8787
 
     SecretStorage
     
-        
+        
         Failed to connect to secret storage
         
     
@@ -1869,7 +1884,7 @@ Example: https://server.my:8787
 
     TimelineModel
     
-        
+        
         Message redaction failed: %1
         メッセージを編集できませんでした: %1
     
@@ -1968,7 +1983,7 @@ Example: https://server.my:8787
         %1が招待されました。
     
     
-        
+        
         %1 changed their avatar.
         %1がアバターを変更しました。
     
@@ -2028,12 +2043,12 @@ Example: https://server.my:8787
         %1がノックを編集しました。
     
     
-        
+        
         You joined this room.
         
     
     
-        
+        
         %1 has changed their avatar and changed their display name to %2.
         
     
@@ -2075,7 +2090,7 @@ Example: https://server.my:8787
         部屋が開いていません
     
     
-        
+        
         %1 member(s)
         
     
@@ -2182,7 +2197,7 @@ Example: https://server.my:8787
 
     UserProfile
     
-        
+        
         Global User Profile
         
     
@@ -2192,28 +2207,63 @@ Example: https://server.my:8787
         
     
     
-        
-        
-        Verify
+        
+        Change avatar globally.
         
     
     
-        
-        Ban the user
+        
+        Change avatar. Will only apply to this room.
         
     
     
-        
-        Start a private chat
+        
+        Change display name globally.
+        
+    
+    
+        
+        Change display name. Will only apply to this room.
+        
+    
+    
+        
+        Room: %1
+        
+    
+    
+        
+        This is a room-specific profile. The user's name and avatar may be different from their global versions.
+        
+    
+    
+        
+        Open the global profile for this user.
         
     
     
         
-        Kick the user
+        
+        Verify
         
     
     
-        
+        
+        Start a private chat.
+        
+    
+    
+        
+        Kick the user.
+        
+    
+    
+        
+        Ban the user.
+        
+    
+    
+        
         Unverify
         
     
diff --git a/resources/langs/nheko_ml.ts b/resources/langs/nheko_ml.ts
index c931cc34..0edf7a4b 100644
--- a/resources/langs/nheko_ml.ts
+++ b/resources/langs/nheko_ml.ts
@@ -131,17 +131,17 @@
     
     
         
-        
+        
         Invited user: %1
         ക്ഷണിച്ച ഉപയോക്താവ്:% 1
     
     
-        
+        
         Migrating the cache to the current version failed. This can have different reasons. Please open an issue and try to use an older version in the mean time. Alternatively you can try deleting the cache manually.
         
     
     
-        
+        
         Confirm join
         ചേരുന്നത് ഉറപ്പാക്കുക
     
@@ -157,12 +157,12 @@
     
     
         
-        
+        
         Confirm invite
         ക്ഷണം ഉറപ്പാക്കു
     
     
-        
+        
         Do you really want to invite %1 (%2)?
         
     
@@ -227,12 +227,12 @@
         
     
     
-        
+        
         Do you really want to start a private chat with %1?
         
     
     
-        
+        
         Cache migration failed!
         
     
@@ -259,7 +259,7 @@
         
     
     
-        
+        
         Failed to setup encryption keys. Server response: %1 %2. Please try again later.
         
     
@@ -354,7 +354,7 @@
 
     CrossSigningSecrets
     
-        
+        
         Decrypt secrets
         
     
@@ -724,7 +724,7 @@
     
         
         Edit
-        
+        തിരുത്തുക
     
     
         
@@ -735,7 +735,7 @@
 
     InputBar
     
-        
+        
         Select a file
         ഒരു ഫയൽ തിരഞ്ഞെടുക്കുക
     
@@ -1039,17 +1039,19 @@ Example: https://server.my:8787
         
     
     
-        
+        
+        
         &Copy
         
     
     
-        
+        
+        
         Copy &link location
         
     
     
-        
+        
         Re&act
         
     
@@ -1108,6 +1110,11 @@ Example: https://server.my:8787
         Copy link to eve&nt
         
     
+    
+        
+        &Go to reply
+        
+    
 
 
     NewVerificationRequest
@@ -1167,6 +1174,14 @@ Example: https://server.my:8787
         സ്വീകരിക്കുക
     
 
+
+    NotificationWarning
+    
+        
+        You will be pinging the whole room
+        
+    
+
 
     NotificationsManager
     
@@ -1414,7 +1429,7 @@ Example: https://server.my:8787
 
     RoomInfo
     
-        
+        
         no version stored
         
     
@@ -1690,7 +1705,7 @@ Example: https://server.my:8787
     
         
         All Files (*)
-        
+        എല്ലാ ഫയലുകളും (*)
     
     
         
@@ -1784,7 +1799,7 @@ Example: https://server.my:8787
 
     SecretStorage
     
-        
+        
         Failed to connect to secret storage
         
     
@@ -1870,7 +1885,7 @@ Example: https://server.my:8787
 
     TimelineModel
     
-        
+        
         Message redaction failed: %1
         
     
@@ -1970,7 +1985,7 @@ Example: https://server.my:8787
         
     
     
-        
+        
         %1 changed their avatar.
         
     
@@ -2030,12 +2045,12 @@ Example: https://server.my:8787
         
     
     
-        
+        
         You joined this room.
         നിങ്ങൾ ഈ മുറിയിൽ ചേർന്നു.
     
     
-        
+        
         %1 has changed their avatar and changed their display name to %2.
         
     
@@ -2077,7 +2092,7 @@ Example: https://server.my:8787
         
     
     
-        
+        
         %1 member(s)
         
     
@@ -2184,7 +2199,7 @@ Example: https://server.my:8787
 
     UserProfile
     
-        
+        
         Global User Profile
         
     
@@ -2194,28 +2209,63 @@ Example: https://server.my:8787
         
     
     
-        
-        
-        Verify
+        
+        Change avatar globally.
         
     
     
-        
-        Ban the user
+        
+        Change avatar. Will only apply to this room.
         
     
     
-        
-        Start a private chat
+        
+        Change display name globally.
+        
+    
+    
+        
+        Change display name. Will only apply to this room.
+        
+    
+    
+        
+        Room: %1
+        
+    
+    
+        
+        This is a room-specific profile. The user's name and avatar may be different from their global versions.
+        
+    
+    
+        
+        Open the global profile for this user.
         
     
     
         
-        Kick the user
+        
+        Verify
         
     
     
-        
+        
+        Start a private chat.
+        
+    
+    
+        
+        Kick the user.
+        
+    
+    
+        
+        Ban the user.
+        
+    
+    
+        
         Unverify
         
     
@@ -2227,7 +2277,7 @@ Example: https://server.my:8787
     
         
         All Files (*)
-        
+        എല്ലാ ഫയലുകളും (*)
     
     
         
@@ -2690,7 +2740,7 @@ This usually causes the application icon in the task bar to animate in some fash
     
         
         All Files (*)
-        
+        എല്ലാ ഫയലുകളും (*)
     
     
         
@@ -2783,7 +2833,7 @@ This usually causes the application icon in the task bar to animate in some fash
     
         
         LOGIN
-        
+        പ്രവേശിക്കുക
     
 
 
@@ -3012,7 +3062,7 @@ Media size: %2
     
         
         %1: %2
-        
+        %1: %2
     
     
         
diff --git a/resources/langs/nheko_nl.ts b/resources/langs/nheko_nl.ts
index 18a69994..17788714 100644
--- a/resources/langs/nheko_nl.ts
+++ b/resources/langs/nheko_nl.ts
@@ -131,17 +131,17 @@
     
     
         
-        
+        
         Invited user: %1
         Gebruiker uitgenodigd: %1
     
     
-        
+        
         Migrating the cache to the current version failed. This can have different reasons. Please open an issue and try to use an older version in the mean time. Alternatively you can try deleting the cache manually.
         Het migreren can de cache naar de huidige versie is mislukt. Dit kan verscheidene redenen hebben. Maak a.u.b een issue aan en probeer in de tussentijd een oudere versie. Je kan ook proberen de cache handmatig te verwijderen.
     
     
-        
+        
         Confirm join
         Bevestig deelname
     
@@ -157,12 +157,12 @@
     
     
         
-        
+        
         Confirm invite
         Bevestig uitnodiging
     
     
-        
+        
         Do you really want to invite %1 (%2)?
         Weet je zeker dat je %1 (%2) wil uitnodigen?
     
@@ -227,12 +227,12 @@
         Toegelaten gebruiker: %1
     
     
-        
+        
         Do you really want to start a private chat with %1?
         Weet je zeker dat je een privé chat wil beginnen met %1?
     
     
-        
+        
         Cache migration failed!
         Migreren van de cache is mislukt!
     
@@ -259,7 +259,7 @@
         Opgeslagen gegevens herstellen mislukt. Log a.u.b. opnieuw in.
     
     
-        
+        
         Failed to setup encryption keys. Server response: %1 %2. Please try again later.
         Instellen van de versleuteling is mislukt. Bericht van server: %1 %2. Probeer het a.u.b. later nog eens.
     
@@ -354,7 +354,7 @@
 
     CrossSigningSecrets
     
-        
+        
         Decrypt secrets
         Ontsleutel geheimen
     
@@ -735,7 +735,7 @@
 
     InputBar
     
-        
+        
         Select a file
         Selecteer een bestand
     
@@ -1043,17 +1043,19 @@ Voorbeeld: https://mijnserver.nl:8787
         Opties
     
     
-        
+        
+        
         &Copy
         &Kopiëren
     
     
-        
+        
+        
         Copy &link location
         Kopieer &link
     
     
-        
+        
         Re&act
         Re&ageren
     
@@ -1112,6 +1114,11 @@ Voorbeeld: https://mijnserver.nl:8787
         Copy link to eve&nt
         Kopieer link naar gebeurte&nis
     
+    
+        
+        &Go to reply
+        
+    
 
 
     NewVerificationRequest
@@ -1171,6 +1178,14 @@ Voorbeeld: https://mijnserver.nl:8787
         Accepteren
     
 
+
+    NotificationWarning
+    
+        
+        You will be pinging the whole room
+        
+    
+
 
     NotificationsManager
     
@@ -1418,7 +1433,7 @@ Voorbeeld: https://mijnserver.nl:8787
 
     RoomInfo
     
-        
+        
         no version stored
         geen versie opgeslagen
     
@@ -1788,7 +1803,7 @@ Voorbeeld: https://mijnserver.nl:8787
 
     SecretStorage
     
-        
+        
         Failed to connect to secret storage
         Verbinden met geheimopslag mislukt
     
@@ -1874,7 +1889,7 @@ Voorbeeld: https://mijnserver.nl:8787
 
     TimelineModel
     
-        
+        
         Message redaction failed: %1
         Bericht intrekken mislukt: %1
     
@@ -1974,7 +1989,7 @@ Voorbeeld: https://mijnserver.nl:8787
         %1 is uitgenodigd.
     
     
-        
+        
         %1 changed their avatar.
         %1 is van avatar veranderd.
     
@@ -2034,12 +2049,12 @@ Voorbeeld: https://mijnserver.nl:8787
         %1 heeft het aankloppen ingetrokken.
     
     
-        
+        
         You joined this room.
         Je neemt nu deel aan deze kamer.
     
     
-        
+        
         %1 has changed their avatar and changed their display name to %2.
         %1 is van avatar veranderd en heet nu %2.
     
@@ -2081,7 +2096,7 @@ Voorbeeld: https://mijnserver.nl:8787
         Geen kamer open
     
     
-        
+        
         %1 member(s)
         %1 deelnemer(s)
     
@@ -2188,7 +2203,7 @@ Voorbeeld: https://mijnserver.nl:8787
 
     UserProfile
     
-        
+        
         Global User Profile
         Globaal gebruikersprofiel
     
@@ -2198,28 +2213,63 @@ Voorbeeld: https://mijnserver.nl:8787
         Kamerspecifiek gebruikersprofiel
     
     
-        
-        
+        
+        Change avatar globally.
+        
+    
+    
+        
+        Change avatar. Will only apply to this room.
+        
+    
+    
+        
+        Change display name globally.
+        
+    
+    
+        
+        Change display name. Will only apply to this room.
+        
+    
+    
+        
+        Room: %1
+        
+    
+    
+        
+        This is a room-specific profile. The user's name and avatar may be different from their global versions.
+        
+    
+    
+        
+        Open the global profile for this user.
+        
+    
+    
+        
+        
         Verify
         Verifiëren
     
     
-        
-        Ban the user
-        Verban de gebruiker
-    
-    
-        
-        Start a private chat
-        Begin een privéchat
+        
+        Start a private chat.
+        
     
     
         
-        Kick the user
-        Verwijder de gebruiker
+        Kick the user.
+        
     
     
-        
+        
+        Ban the user.
+        
+    
+    
+        
         Unverify
         On-verifiëren
     
diff --git a/resources/langs/nheko_pl.ts b/resources/langs/nheko_pl.ts
index c0058c9b..44f47419 100644
--- a/resources/langs/nheko_pl.ts
+++ b/resources/langs/nheko_pl.ts
@@ -131,17 +131,17 @@
     
     
         
-        
+        
         Invited user: %1
         
     
     
-        
+        
         Migrating the cache to the current version failed. This can have different reasons. Please open an issue and try to use an older version in the mean time. Alternatively you can try deleting the cache manually.
         
     
     
-        
+        
         Confirm join
         
     
@@ -157,12 +157,12 @@
     
     
         
-        
+        
         Confirm invite
         
     
     
-        
+        
         Do you really want to invite %1 (%2)?
         Czy na pewno chcesz zaprosić %1 (%2)?
     
@@ -227,12 +227,12 @@
         Odblokowano użytkownika: %1
     
     
-        
+        
         Do you really want to start a private chat with %1?
         
     
     
-        
+        
         Cache migration failed!
         Nie udało się przenieść pamięci podręcznej!
     
@@ -259,7 +259,7 @@
         Nie udało się przywrócić zapisanych danych. Spróbuj zalogować się ponownie.
     
     
-        
+        
         Failed to setup encryption keys. Server response: %1 %2. Please try again later.
         Nie udało się ustawić kluczy szyfrujących. Odpowiedź serwera: %1 %2. Spróbuj ponownie później.
     
@@ -354,7 +354,7 @@
 
     CrossSigningSecrets
     
-        
+        
         Decrypt secrets
         
     
@@ -735,7 +735,7 @@
 
     InputBar
     
-        
+        
         Select a file
         Wybierz plik
     
@@ -1041,17 +1041,19 @@ Example: https://server.my:8787
         
     
     
-        
+        
+        
         &Copy
         
     
     
-        
+        
+        
         Copy &link location
         
     
     
-        
+        
         Re&act
         
     
@@ -1110,6 +1112,11 @@ Example: https://server.my:8787
         Copy link to eve&nt
         
     
+    
+        
+        &Go to reply
+        
+    
 
 
     NewVerificationRequest
@@ -1169,6 +1176,14 @@ Example: https://server.my:8787
         Akceptuj
     
 
+
+    NotificationWarning
+    
+        
+        You will be pinging the whole room
+        
+    
+
 
     NotificationsManager
     
@@ -1416,7 +1431,7 @@ Example: https://server.my:8787
 
     RoomInfo
     
-        
+        
         no version stored
         
     
@@ -1787,7 +1802,7 @@ Example: https://server.my:8787
 
     SecretStorage
     
-        
+        
         Failed to connect to secret storage
         
     
@@ -1873,7 +1888,7 @@ Example: https://server.my:8787
 
     TimelineModel
     
-        
+        
         Message redaction failed: %1
         Redagowanie wiadomości nie powiodło się: %1
     
@@ -1974,7 +1989,7 @@ Example: https://server.my:8787
         
     
     
-        
+        
         %1 changed their avatar.
         
     
@@ -2034,12 +2049,12 @@ Example: https://server.my:8787
         
     
     
-        
+        
         You joined this room.
         Dołączyłeś(-łaś) do tego pokoju.
     
     
-        
+        
         %1 has changed their avatar and changed their display name to %2.
         
     
@@ -2081,7 +2096,7 @@ Example: https://server.my:8787
         
     
     
-        
+        
         %1 member(s)
         
     
@@ -2188,7 +2203,7 @@ Example: https://server.my:8787
 
     UserProfile
     
-        
+        
         Global User Profile
         
     
@@ -2198,28 +2213,63 @@ Example: https://server.my:8787
         
     
     
-        
-        
-        Verify
+        
+        Change avatar globally.
         
     
     
-        
-        Ban the user
+        
+        Change avatar. Will only apply to this room.
         
     
     
-        
-        Start a private chat
+        
+        Change display name globally.
+        
+    
+    
+        
+        Change display name. Will only apply to this room.
+        
+    
+    
+        
+        Room: %1
+        
+    
+    
+        
+        This is a room-specific profile. The user's name and avatar may be different from their global versions.
+        
+    
+    
+        
+        Open the global profile for this user.
         
     
     
         
-        Kick the user
+        
+        Verify
         
     
     
-        
+        
+        Start a private chat.
+        
+    
+    
+        
+        Kick the user.
+        
+    
+    
+        
+        Ban the user.
+        
+    
+    
+        
         Unverify
         
     
diff --git a/resources/langs/nheko_pt_BR.ts b/resources/langs/nheko_pt_BR.ts
index ebf55897..0d067bc7 100644
--- a/resources/langs/nheko_pt_BR.ts
+++ b/resources/langs/nheko_pt_BR.ts
@@ -131,17 +131,17 @@
     
     
         
-        
+        
         Invited user: %1
         Usuário convidado: %1
     
     
-        
+        
         Migrating the cache to the current version failed. This can have different reasons. Please open an issue and try to use an older version in the mean time. Alternatively you can try deleting the cache manually.
         
     
     
-        
+        
         Confirm join
         Confirmar entrada
     
@@ -157,12 +157,12 @@
     
     
         
-        
+        
         Confirm invite
         Confirmar convite
     
     
-        
+        
         Do you really want to invite %1 (%2)?
         
     
@@ -227,12 +227,12 @@
         Usuário desbanido: %1
     
     
-        
+        
         Do you really want to start a private chat with %1?
         
     
     
-        
+        
         Cache migration failed!
         Migração do cache falhou!
     
@@ -259,7 +259,7 @@
         Falha ao restaurar dados salvos. Por favor faça login novamente.
     
     
-        
+        
         Failed to setup encryption keys. Server response: %1 %2. Please try again later.
         
     
@@ -354,7 +354,7 @@
 
     CrossSigningSecrets
     
-        
+        
         Decrypt secrets
         
     
@@ -735,7 +735,7 @@
 
     InputBar
     
-        
+        
         Select a file
         
     
@@ -1039,17 +1039,19 @@ Example: https://server.my:8787
         
     
     
-        
+        
+        
         &Copy
         
     
     
-        
+        
+        
         Copy &link location
         
     
     
-        
+        
         Re&act
         
     
@@ -1108,6 +1110,11 @@ Example: https://server.my:8787
         Copy link to eve&nt
         
     
+    
+        
+        &Go to reply
+        
+    
 
 
     NewVerificationRequest
@@ -1167,6 +1174,14 @@ Example: https://server.my:8787
         Aceitar
     
 
+
+    NotificationWarning
+    
+        
+        You will be pinging the whole room
+        
+    
+
 
     NotificationsManager
     
@@ -1414,7 +1429,7 @@ Example: https://server.my:8787
 
     RoomInfo
     
-        
+        
         no version stored
         
     
@@ -1784,7 +1799,7 @@ Example: https://server.my:8787
 
     SecretStorage
     
-        
+        
         Failed to connect to secret storage
         
     
@@ -1870,7 +1885,7 @@ Example: https://server.my:8787
 
     TimelineModel
     
-        
+        
         Message redaction failed: %1
         
     
@@ -1970,7 +1985,7 @@ Example: https://server.my:8787
         
     
     
-        
+        
         %1 changed their avatar.
         
     
@@ -2030,12 +2045,12 @@ Example: https://server.my:8787
         
     
     
-        
+        
         You joined this room.
         Você entrou nessa sala.
     
     
-        
+        
         %1 has changed their avatar and changed their display name to %2.
         
     
@@ -2077,7 +2092,7 @@ Example: https://server.my:8787
         
     
     
-        
+        
         %1 member(s)
         
     
@@ -2184,7 +2199,7 @@ Example: https://server.my:8787
 
     UserProfile
     
-        
+        
         Global User Profile
         
     
@@ -2194,28 +2209,63 @@ Example: https://server.my:8787
         
     
     
-        
-        
-        Verify
+        
+        Change avatar globally.
         
     
     
-        
-        Ban the user
+        
+        Change avatar. Will only apply to this room.
         
     
     
-        
-        Start a private chat
+        
+        Change display name globally.
+        
+    
+    
+        
+        Change display name. Will only apply to this room.
+        
+    
+    
+        
+        Room: %1
+        
+    
+    
+        
+        This is a room-specific profile. The user's name and avatar may be different from their global versions.
+        
+    
+    
+        
+        Open the global profile for this user.
         
     
     
         
-        Kick the user
+        
+        Verify
         
     
     
-        
+        
+        Start a private chat.
+        
+    
+    
+        
+        Kick the user.
+        
+    
+    
+        
+        Ban the user.
+        
+    
+    
+        
         Unverify
         
     
diff --git a/resources/langs/nheko_pt_PT.ts b/resources/langs/nheko_pt_PT.ts
index b976b64a..831cadd5 100644
--- a/resources/langs/nheko_pt_PT.ts
+++ b/resources/langs/nheko_pt_PT.ts
@@ -131,17 +131,17 @@
     
     
         
-        
+        
         Invited user: %1
         Utilizador convidado: %1
     
     
-        
+        
         Migrating the cache to the current version failed. This can have different reasons. Please open an issue and try to use an older version in the mean time. Alternatively you can try deleting the cache manually.
         A migração da cache para a versão atual falhou, e existem várias razões possíveis. Por favor abra um problema ("issue") e experimente usar uma versão mais antiga entretanto. Alternativamente, pode tentar apagar a cache manualmente.
     
     
-        
+        
         Confirm join
         Confirmar entrada
     
@@ -157,12 +157,12 @@
     
     
         
-        
+        
         Confirm invite
         Confirmar convite
     
     
-        
+        
         Do you really want to invite %1 (%2)?
         Tem a certeza que quer convidar %1 (%2)?
     
@@ -227,12 +227,12 @@
         Utilizador perdoado: %1
     
     
-        
+        
         Do you really want to start a private chat with %1?
         Tem a certeza que quer começar uma conversa privada com %1?
     
     
-        
+        
         Cache migration failed!
         Falha ao migrar a cache!
     
@@ -259,7 +259,7 @@
         Falha ao restaurar dados guardados. Por favor, autentique-se novamente.
     
     
-        
+        
         Failed to setup encryption keys. Server response: %1 %2. Please try again later.
         Falha ao estabelecer chaves encriptadas. Resposta do servidor: %1 %2. Tente novamente mais tarde.
     
@@ -354,7 +354,7 @@
 
     CrossSigningSecrets
     
-        
+        
         Decrypt secrets
         Desencriptar segredos
     
@@ -671,7 +671,7 @@
     
         
         Cancel
-        
+        Cancelar
     
     
         
@@ -735,7 +735,7 @@
 
     InputBar
     
-        
+        
         Select a file
         
     
@@ -781,7 +781,7 @@
     
         
         Cancel
-        
+        Cancelar
     
 
 
@@ -1039,17 +1039,19 @@ Example: https://server.my:8787
         
     
     
-        
+        
+        
         &Copy
         
     
     
-        
+        
+        
         Copy &link location
         
     
     
-        
+        
         Re&act
         
     
@@ -1108,6 +1110,11 @@ Example: https://server.my:8787
         Copy link to eve&nt
         
     
+    
+        
+        &Go to reply
+        
+    
 
 
     NewVerificationRequest
@@ -1149,7 +1156,7 @@ Example: https://server.my:8787
     
         
         Cancel
-        
+        Cancelar
     
     
         
@@ -1164,6 +1171,14 @@ Example: https://server.my:8787
     
         
         Accept
+        Aceitar
+    
+
+
+    NotificationWarning
+    
+        
+        You will be pinging the whole room
         
     
 
@@ -1221,7 +1236,7 @@ Example: https://server.my:8787
     
         
         No microphone found.
-        
+        Nenhum microfone encontrado.
     
     
         
@@ -1241,7 +1256,7 @@ Example: https://server.my:8787
     
         
         Cancel
-        
+        Cancelar
     
 
 
@@ -1414,7 +1429,7 @@ Example: https://server.my:8787
 
     RoomInfo
     
-        
+        
         no version stored
         
     
@@ -1778,13 +1793,13 @@ Example: https://server.my:8787
     
         
         Cancel
-        
+        Cancelar
     
 
 
     SecretStorage
     
-        
+        
         Failed to connect to secret storage
         
     
@@ -1846,7 +1861,7 @@ Example: https://server.my:8787
     
         
         Search
-        
+        Procurar
     
 
 
@@ -1870,7 +1885,7 @@ Example: https://server.my:8787
 
     TimelineModel
     
-        
+        
         Message redaction failed: %1
         
     
@@ -1970,7 +1985,7 @@ Example: https://server.my:8787
         
     
     
-        
+        
         %1 changed their avatar.
         
     
@@ -2030,12 +2045,12 @@ Example: https://server.my:8787
         
     
     
-        
+        
         You joined this room.
         
     
     
-        
+        
         %1 has changed their avatar and changed their display name to %2.
         
     
@@ -2077,7 +2092,7 @@ Example: https://server.my:8787
         
     
     
-        
+        
         %1 member(s)
         
     
@@ -2184,7 +2199,7 @@ Example: https://server.my:8787
 
     UserProfile
     
-        
+        
         Global User Profile
         
     
@@ -2194,28 +2209,63 @@ Example: https://server.my:8787
         
     
     
-        
-        
-        Verify
+        
+        Change avatar globally.
         
     
     
-        
-        Ban the user
+        
+        Change avatar. Will only apply to this room.
         
     
     
-        
-        Start a private chat
+        
+        Change display name globally.
+        
+    
+    
+        
+        Change display name. Will only apply to this room.
+        
+    
+    
+        
+        Room: %1
+        
+    
+    
+        
+        This is a room-specific profile. The user's name and avatar may be different from their global versions.
+        
+    
+    
+        
+        Open the global profile for this user.
         
     
     
         
-        Kick the user
+        
+        Verify
         
     
     
-        
+        
+        Start a private chat.
+        
+    
+    
+        
+        Kick the user.
+        
+    
+    
+        
+        Ban the user.
+        
+    
+    
+        
         Unverify
         
     
@@ -2760,7 +2810,7 @@ This usually causes the application icon in the task bar to animate in some fash
     
         
         Cancel
-        
+        Cancelar
     
 
 
@@ -2804,17 +2854,17 @@ This usually causes the application icon in the task bar to animate in some fash
     
         
         Cancel
-        
+        Cancelar
     
     
         
         Name
-        
+        Nome
     
     
         
         Topic
-        
+        Tópico
     
     
         
@@ -2847,7 +2897,7 @@ This usually causes the application icon in the task bar to animate in some fash
     
         
         Cancel
-        
+        Cancelar
     
     
         
@@ -2870,7 +2920,7 @@ This usually causes the application icon in the task bar to animate in some fash
     
         
         Cancel
-        
+        Cancelar
     
     
         
@@ -2883,7 +2933,7 @@ This usually causes the application icon in the task bar to animate in some fash
     
         
         Cancel
-        
+        Cancelar
     
     
         
@@ -2896,7 +2946,7 @@ This usually causes the application icon in the task bar to animate in some fash
     
         
         Cancel
-        
+        Cancelar
     
     
         
@@ -2914,7 +2964,7 @@ This usually causes the application icon in the task bar to animate in some fash
     
         
         Cancel
-        
+        Cancelar
     
     
         
@@ -2929,7 +2979,7 @@ Media size: %2
     
         
         Cancel
-        
+        Cancelar
     
     
         
diff --git a/resources/langs/nheko_ro.ts b/resources/langs/nheko_ro.ts
index f04aff5d..c6c79d3f 100644
--- a/resources/langs/nheko_ro.ts
+++ b/resources/langs/nheko_ro.ts
@@ -131,17 +131,17 @@
     
     
         
-        
+        
         Invited user: %1
         Utilizator invitat: %1
     
     
-        
+        
         Migrating the cache to the current version failed. This can have different reasons. Please open an issue and try to use an older version in the mean time. Alternatively you can try deleting the cache manually.
         Nu s-a putut muta cache-ul pe versiunea curentă. Acest lucru poate avea diferite cauze. Vă rugăm să deschideți un issue și încercați să folosiți o versiune mai veche între timp. O altă opțiune ar fi să încercați să ștergeți cache-ul manual.
     
     
-        
+        
         Confirm join
         
     
@@ -157,12 +157,12 @@
     
     
         
-        
+        
         Confirm invite
         
     
     
-        
+        
         Do you really want to invite %1 (%2)?
         
     
@@ -227,12 +227,12 @@
         Utilizator dezinterzis: %1
     
     
-        
+        
         Do you really want to start a private chat with %1?
         
     
     
-        
+        
         Cache migration failed!
         Nu s-a putut migra cache-ul!
     
@@ -259,7 +259,7 @@
         Nu s-au putut restabili datele salvate. Vă rugăm să vă reconectați.
     
     
-        
+        
         Failed to setup encryption keys. Server response: %1 %2. Please try again later.
         Nu s-au putut stabili cheile. Răspunsul serverului: %1 %2. Vă rugăm încercați mai târziu.
     
@@ -354,7 +354,7 @@
 
     CrossSigningSecrets
     
-        
+        
         Decrypt secrets
         
     
@@ -735,7 +735,7 @@
 
     InputBar
     
-        
+        
         Select a file
         
     
@@ -1043,17 +1043,19 @@ Exemplu: https://serverul.meu:8787
         Opțiuni
     
     
-        
+        
+        
         &Copy
         
     
     
-        
+        
+        
         Copy &link location
         
     
     
-        
+        
         Re&act
         
     
@@ -1112,6 +1114,11 @@ Exemplu: https://serverul.meu:8787
         Copy link to eve&nt
         
     
+    
+        
+        &Go to reply
+        
+    
 
 
     NewVerificationRequest
@@ -1171,6 +1178,14 @@ Exemplu: https://serverul.meu:8787
         Acceptare
     
 
+
+    NotificationWarning
+    
+        
+        You will be pinging the whole room
+        
+    
+
 
     NotificationsManager
     
@@ -1418,7 +1433,7 @@ Exemplu: https://serverul.meu:8787
 
     RoomInfo
     
-        
+        
         no version stored
         nicio versiune stocată
     
@@ -1789,7 +1804,7 @@ Exemplu: https://serverul.meu:8787
 
     SecretStorage
     
-        
+        
         Failed to connect to secret storage
         
     
@@ -1875,7 +1890,7 @@ Exemplu: https://serverul.meu:8787
 
     TimelineModel
     
-        
+        
         Message redaction failed: %1
         Redactare mesaj eșuată: %1
     
@@ -1976,7 +1991,7 @@ Exemplu: https://serverul.meu:8787
         %1 a fost invitat(ă).
     
     
-        
+        
         %1 changed their avatar.
         %1 și-a schimbat avatarul.
     
@@ -2036,12 +2051,12 @@ Exemplu: https://serverul.meu:8787
         %1 și-a redactat ciocănitul.
     
     
-        
+        
         You joined this room.
         Te-ai alăturat camerei.
     
     
-        
+        
         %1 has changed their avatar and changed their display name to %2.
         
     
@@ -2083,7 +2098,7 @@ Exemplu: https://serverul.meu:8787
         Nicio cameră deschisă
     
     
-        
+        
         %1 member(s)
         
     
@@ -2190,7 +2205,7 @@ Exemplu: https://serverul.meu:8787
 
     UserProfile
     
-        
+        
         Global User Profile
         
     
@@ -2200,28 +2215,63 @@ Exemplu: https://serverul.meu:8787
         
     
     
-        
-        
-        Verify
+        
+        Change avatar globally.
         
     
     
-        
-        Ban the user
+        
+        Change avatar. Will only apply to this room.
         
     
     
-        
-        Start a private chat
+        
+        Change display name globally.
+        
+    
+    
+        
+        Change display name. Will only apply to this room.
+        
+    
+    
+        
+        Room: %1
+        
+    
+    
+        
+        This is a room-specific profile. The user's name and avatar may be different from their global versions.
+        
+    
+    
+        
+        Open the global profile for this user.
         
     
     
         
-        Kick the user
+        
+        Verify
         
     
     
-        
+        
+        Start a private chat.
+        
+    
+    
+        
+        Kick the user.
+        
+    
+    
+        
+        Ban the user.
+        
+    
+    
+        
         Unverify
         
     
diff --git a/resources/langs/nheko_ru.ts b/resources/langs/nheko_ru.ts
index 11f8982b..f7d0d847 100644
--- a/resources/langs/nheko_ru.ts
+++ b/resources/langs/nheko_ru.ts
@@ -131,17 +131,17 @@
     
     
         
-        
+        
         Invited user: %1
         Приглашенный пользователь: %1
     
     
-        
+        
         Migrating the cache to the current version failed. This can have different reasons. Please open an issue and try to use an older version in the mean time. Alternatively you can try deleting the cache manually.
         Миграция кэша для текущей версии не удалась. Это может происходить по разным причинам. Пожалуйста сообщите о проблеме и попробуйте временно использовать старую версию. Так-же вы можете попробовать удалить кэш самостоятельно.
     
     
-        
+        
         Confirm join
         Подтвердить вход
     
@@ -157,12 +157,12 @@
     
     
         
-        
+        
         Confirm invite
         Подтвердите приглашение
     
     
-        
+        
         Do you really want to invite %1 (%2)?
         Вы точно хотите пригласить %1 (%2)?
     
@@ -227,12 +227,12 @@
         Разблокированный пользователь: %1
     
     
-        
+        
         Do you really want to start a private chat with %1?
         Вы действительно хотите начать личную переписку с %1?
     
     
-        
+        
         Cache migration failed!
         Миграция кэша не удалась!
     
@@ -259,7 +259,7 @@
         Не удалось восстановить сохраненные данные. Пожалуйста, войдите снова.
     
     
-        
+        
         Failed to setup encryption keys. Server response: %1 %2. Please try again later.
         Не удалось настроить ключи шифрования. Ответ сервера:%1 %2. Пожалуйста, попробуйте позже.
     
@@ -354,7 +354,7 @@
 
     CrossSigningSecrets
     
-        
+        
         Decrypt secrets
         Расшифровать секреты
     
@@ -735,7 +735,7 @@
 
     InputBar
     
-        
+        
         Select a file
         Выберите файл
     
@@ -1043,17 +1043,19 @@ Example: https://server.my:8787
         Опции
     
     
-        
+        
+        
         &Copy
         
     
     
-        
+        
+        
         Copy &link location
         
     
     
-        
+        
         Re&act
         
     
@@ -1112,6 +1114,11 @@ Example: https://server.my:8787
         Copy link to eve&nt
         
     
+    
+        
+        &Go to reply
+        
+    
 
 
     NewVerificationRequest
@@ -1171,6 +1178,14 @@ Example: https://server.my:8787
         Принять
     
 
+
+    NotificationWarning
+    
+        
+        You will be pinging the whole room
+        
+    
+
 
     NotificationsManager
     
@@ -1418,7 +1433,7 @@ Example: https://server.my:8787
 
     RoomInfo
     
-        
+        
         no version stored
         нет сохраненной версии
     
@@ -1789,7 +1804,7 @@ Example: https://server.my:8787
 
     SecretStorage
     
-        
+        
         Failed to connect to secret storage
         
     
@@ -1875,7 +1890,7 @@ Example: https://server.my:8787
 
     TimelineModel
     
-        
+        
         Message redaction failed: %1
         Ошибка редактирования сообщения: %1
     
@@ -1976,7 +1991,7 @@ Example: https://server.my:8787
         %1 был приглашен.
     
     
-        
+        
         %1 changed their avatar.
         %1 поменял свой аватар.
     
@@ -2036,12 +2051,12 @@ Example: https://server.my:8787
         %1 отредактировал его "стук".
     
     
-        
+        
         You joined this room.
         Вы присоединились к этой комнате.
     
     
-        
+        
         %1 has changed their avatar and changed their display name to %2.
         
     
@@ -2083,7 +2098,7 @@ Example: https://server.my:8787
         Комната не выбрана
     
     
-        
+        
         %1 member(s)
         %1 участник(ов)
     
@@ -2190,7 +2205,7 @@ Example: https://server.my:8787
 
     UserProfile
     
-        
+        
         Global User Profile
         Глобальный Пользовательский Профиль
     
@@ -2200,28 +2215,63 @@ Example: https://server.my:8787
         Поользовательский Профиль в Комнате
     
     
-        
-        
+        
+        Change avatar globally.
+        
+    
+    
+        
+        Change avatar. Will only apply to this room.
+        
+    
+    
+        
+        Change display name globally.
+        
+    
+    
+        
+        Change display name. Will only apply to this room.
+        
+    
+    
+        
+        Room: %1
+        
+    
+    
+        
+        This is a room-specific profile. The user's name and avatar may be different from their global versions.
+        
+    
+    
+        
+        Open the global profile for this user.
+        
+    
+    
+        
+        
         Verify
         Верифицировать
     
     
-        
-        Ban the user
-        Заблокировать пользователя
-    
-    
-        
-        Start a private chat
-        Создать приватный чат
+        
+        Start a private chat.
+        
     
     
         
-        Kick the user
-        Выгнать пользователя
+        Kick the user.
+        
     
     
-        
+        
+        Ban the user.
+        
+    
+    
+        
         Unverify
         Отменить Верификацию
     
diff --git a/resources/langs/nheko_si.ts b/resources/langs/nheko_si.ts
index 84ae71ef..a5fee005 100644
--- a/resources/langs/nheko_si.ts
+++ b/resources/langs/nheko_si.ts
@@ -131,17 +131,17 @@
     
     
         
-        
+        
         Invited user: %1
         
     
     
-        
+        
         Migrating the cache to the current version failed. This can have different reasons. Please open an issue and try to use an older version in the mean time. Alternatively you can try deleting the cache manually.
         
     
     
-        
+        
         Confirm join
         
     
@@ -157,12 +157,12 @@
     
     
         
-        
+        
         Confirm invite
         
     
     
-        
+        
         Do you really want to invite %1 (%2)?
         
     
@@ -227,12 +227,12 @@
         
     
     
-        
+        
         Do you really want to start a private chat with %1?
         
     
     
-        
+        
         Cache migration failed!
         
     
@@ -259,7 +259,7 @@
         
     
     
-        
+        
         Failed to setup encryption keys. Server response: %1 %2. Please try again later.
         
     
@@ -354,7 +354,7 @@
 
     CrossSigningSecrets
     
-        
+        
         Decrypt secrets
         
     
@@ -735,7 +735,7 @@
 
     InputBar
     
-        
+        
         Select a file
         
     
@@ -1039,17 +1039,19 @@ Example: https://server.my:8787
         
     
     
-        
+        
+        
         &Copy
         
     
     
-        
+        
+        
         Copy &link location
         
     
     
-        
+        
         Re&act
         
     
@@ -1108,6 +1110,11 @@ Example: https://server.my:8787
         Copy link to eve&nt
         
     
+    
+        
+        &Go to reply
+        
+    
 
 
     NewVerificationRequest
@@ -1167,6 +1174,14 @@ Example: https://server.my:8787
         
     
 
+
+    NotificationWarning
+    
+        
+        You will be pinging the whole room
+        
+    
+
 
     NotificationsManager
     
@@ -1414,7 +1429,7 @@ Example: https://server.my:8787
 
     RoomInfo
     
-        
+        
         no version stored
         
     
@@ -1784,7 +1799,7 @@ Example: https://server.my:8787
 
     SecretStorage
     
-        
+        
         Failed to connect to secret storage
         
     
@@ -1870,7 +1885,7 @@ Example: https://server.my:8787
 
     TimelineModel
     
-        
+        
         Message redaction failed: %1
         
     
@@ -1970,7 +1985,7 @@ Example: https://server.my:8787
         
     
     
-        
+        
         %1 changed their avatar.
         
     
@@ -2030,12 +2045,12 @@ Example: https://server.my:8787
         
     
     
-        
+        
         You joined this room.
         
     
     
-        
+        
         %1 has changed their avatar and changed their display name to %2.
         
     
@@ -2077,7 +2092,7 @@ Example: https://server.my:8787
         
     
     
-        
+        
         %1 member(s)
         
     
@@ -2184,7 +2199,7 @@ Example: https://server.my:8787
 
     UserProfile
     
-        
+        
         Global User Profile
         
     
@@ -2194,28 +2209,63 @@ Example: https://server.my:8787
         
     
     
-        
-        
-        Verify
+        
+        Change avatar globally.
         
     
     
-        
-        Ban the user
+        
+        Change avatar. Will only apply to this room.
         
     
     
-        
-        Start a private chat
+        
+        Change display name globally.
+        
+    
+    
+        
+        Change display name. Will only apply to this room.
+        
+    
+    
+        
+        Room: %1
+        
+    
+    
+        
+        This is a room-specific profile. The user's name and avatar may be different from their global versions.
+        
+    
+    
+        
+        Open the global profile for this user.
         
     
     
         
-        Kick the user
+        
+        Verify
         
     
     
-        
+        
+        Start a private chat.
+        
+    
+    
+        
+        Kick the user.
+        
+    
+    
+        
+        Ban the user.
+        
+    
+    
+        
         Unverify
         
     
diff --git a/resources/langs/nheko_sv.ts b/resources/langs/nheko_sv.ts
index ff36093e..e20c2823 100644
--- a/resources/langs/nheko_sv.ts
+++ b/resources/langs/nheko_sv.ts
@@ -131,17 +131,17 @@
     
     
         
-        
+        
         Invited user: %1
         Bjöd in användare: %1
     
     
-        
+        
         Migrating the cache to the current version failed. This can have different reasons. Please open an issue and try to use an older version in the mean time. Alternatively you can try deleting the cache manually.
         Kunde inte migrera cachen till den nuvarande versionen. Detta kan bero på flera anledningar, vänligen rapportera problemet och prova en äldre version under tiden. Du kan också försöka att manuellt radera cachen.
     
     
-        
+        
         Confirm join
         
     
@@ -157,12 +157,12 @@
     
     
         
-        
+        
         Confirm invite
         Bekräfta inbjudan
     
     
-        
+        
         Do you really want to invite %1 (%2)?
         Är du säker på att du vill bjuda in %1 (%2)?
     
@@ -227,12 +227,12 @@
         Hävde bannlysningen av användare: %1
     
     
-        
+        
         Do you really want to start a private chat with %1?
         
     
     
-        
+        
         Cache migration failed!
         Cache-migration misslyckades!
     
@@ -259,7 +259,7 @@
         Kunde inte återställa sparad data. Vänligen logga in på nytt.
     
     
-        
+        
         Failed to setup encryption keys. Server response: %1 %2. Please try again later.
         Kunde inte sätta upp krypteringsnycklar. Svar från servern: %1 %2. Vänligen försök igen senare.
     
@@ -354,7 +354,7 @@
 
     CrossSigningSecrets
     
-        
+        
         Decrypt secrets
         Dekryptera hemliga nycklar
     
@@ -735,7 +735,7 @@
 
     InputBar
     
-        
+        
         Select a file
         Välj en fil
     
@@ -1043,17 +1043,19 @@ Exempel: https://server.my:8787
         Alternativ
     
     
-        
+        
+        
         &Copy
         
     
     
-        
+        
+        
         Copy &link location
         
     
     
-        
+        
         Re&act
         
     
@@ -1112,6 +1114,11 @@ Exempel: https://server.my:8787
         Copy link to eve&nt
         
     
+    
+        
+        &Go to reply
+        
+    
 
 
     NewVerificationRequest
@@ -1171,6 +1178,14 @@ Exempel: https://server.my:8787
         Godkänn
     
 
+
+    NotificationWarning
+    
+        
+        You will be pinging the whole room
+        
+    
+
 
     NotificationsManager
     
@@ -1418,7 +1433,7 @@ Exempel: https://server.my:8787
 
     RoomInfo
     
-        
+        
         no version stored
         ingen version lagrad
     
@@ -1788,7 +1803,7 @@ Exempel: https://server.my:8787
 
     SecretStorage
     
-        
+        
         Failed to connect to secret storage
         
     
@@ -1874,7 +1889,7 @@ Exempel: https://server.my:8787
 
     TimelineModel
     
-        
+        
         Message redaction failed: %1
         Kunde inte maskera meddelande: %1
     
@@ -1974,7 +1989,7 @@ Exempel: https://server.my:8787
         %1 blev inbjuden.
     
     
-        
+        
         %1 changed their avatar.
         %1 ändrade sin avatar.
     
@@ -2034,12 +2049,12 @@ Exempel: https://server.my:8787
         %1 maskerade sin knackning.
     
     
-        
+        
         You joined this room.
         Du gick med i detta rum.
     
     
-        
+        
         %1 has changed their avatar and changed their display name to %2.
         
     
@@ -2081,7 +2096,7 @@ Exempel: https://server.my:8787
         Inget rum öppet
     
     
-        
+        
         %1 member(s)
         
     
@@ -2188,7 +2203,7 @@ Exempel: https://server.my:8787
 
     UserProfile
     
-        
+        
         Global User Profile
         
     
@@ -2198,28 +2213,63 @@ Exempel: https://server.my:8787
         
     
     
-        
-        
+        
+        Change avatar globally.
+        
+    
+    
+        
+        Change avatar. Will only apply to this room.
+        
+    
+    
+        
+        Change display name globally.
+        
+    
+    
+        
+        Change display name. Will only apply to this room.
+        
+    
+    
+        
+        Room: %1
+        
+    
+    
+        
+        This is a room-specific profile. The user's name and avatar may be different from their global versions.
+        
+    
+    
+        
+        Open the global profile for this user.
+        
+    
+    
+        
+        
         Verify
         Bekräfta
     
     
-        
-        Ban the user
-        Bannlys användaren
-    
-    
-        
-        Start a private chat
-        Starta en privat chatt
+        
+        Start a private chat.
+        
     
     
         
-        Kick the user
-        Sparka ut användaren
+        Kick the user.
+        
     
     
-        
+        
+        Ban the user.
+        
+    
+    
+        
         Unverify
         
     
diff --git a/resources/langs/nheko_zh_CN.ts b/resources/langs/nheko_zh_CN.ts
index 4bdcfd0c..c813ff7e 100644
--- a/resources/langs/nheko_zh_CN.ts
+++ b/resources/langs/nheko_zh_CN.ts
@@ -131,17 +131,17 @@
     
     
         
-        
+        
         Invited user: %1
         邀请已发送: %1
     
     
-        
+        
         Migrating the cache to the current version failed. This can have different reasons. Please open an issue and try to use an older version in the mean time. Alternatively you can try deleting the cache manually.
         无法迁移缓存到目前版本,可能有多种原因引发此类问题。您可以新建一个议题并继续使用之前版本,或者您可以尝试手动删除缓存。
     
     
-        
+        
         Confirm join
         
     
@@ -157,12 +157,12 @@
     
     
         
-        
+        
         Confirm invite
         
     
     
-        
+        
         Do you really want to invite %1 (%2)?
         
     
@@ -227,12 +227,12 @@
         解禁用户: %1
     
     
-        
+        
         Do you really want to start a private chat with %1?
         
     
     
-        
+        
         Cache migration failed!
         缓存迁移失败!
     
@@ -259,7 +259,7 @@
         恢复保存的数据失败。请重新登录。
     
     
-        
+        
         Failed to setup encryption keys. Server response: %1 %2. Please try again later.
         设置密钥失败。服务器返回信息: %1 %2。请稍后再试。
     
@@ -354,7 +354,7 @@
 
     CrossSigningSecrets
     
-        
+        
         Decrypt secrets
         
     
@@ -735,7 +735,7 @@
 
     InputBar
     
-        
+        
         Select a file
         选择一个文件
     
@@ -1039,17 +1039,19 @@ Example: https://server.my:8787
         
     
     
-        
+        
+        
         &Copy
         
     
     
-        
+        
+        
         Copy &link location
         
     
     
-        
+        
         Re&act
         
     
@@ -1108,6 +1110,11 @@ Example: https://server.my:8787
         Copy link to eve&nt
         
     
+    
+        
+        &Go to reply
+        
+    
 
 
     NewVerificationRequest
@@ -1167,6 +1174,14 @@ Example: https://server.my:8787
         接受
     
 
+
+    NotificationWarning
+    
+        
+        You will be pinging the whole room
+        
+    
+
 
     NotificationsManager
     
@@ -1414,7 +1429,7 @@ Example: https://server.my:8787
 
     RoomInfo
     
-        
+        
         no version stored
         
     
@@ -1783,7 +1798,7 @@ Example: https://server.my:8787
 
     SecretStorage
     
-        
+        
         Failed to connect to secret storage
         
     
@@ -1869,7 +1884,7 @@ Example: https://server.my:8787
 
     TimelineModel
     
-        
+        
         Message redaction failed: %1
         删除消息失败:%1
     
@@ -1968,7 +1983,7 @@ Example: https://server.my:8787
         
     
     
-        
+        
         %1 changed their avatar.
         
     
@@ -2028,12 +2043,12 @@ Example: https://server.my:8787
         
     
     
-        
+        
         You joined this room.
         您已加入此房间
     
     
-        
+        
         %1 has changed their avatar and changed their display name to %2.
         
     
@@ -2075,7 +2090,7 @@ Example: https://server.my:8787
         
     
     
-        
+        
         %1 member(s)
         
     
@@ -2182,7 +2197,7 @@ Example: https://server.my:8787
 
     UserProfile
     
-        
+        
         Global User Profile
         
     
@@ -2192,28 +2207,63 @@ Example: https://server.my:8787
         
     
     
-        
-        
-        Verify
+        
+        Change avatar globally.
         
     
     
-        
-        Ban the user
+        
+        Change avatar. Will only apply to this room.
         
     
     
-        
-        Start a private chat
+        
+        Change display name globally.
+        
+    
+    
+        
+        Change display name. Will only apply to this room.
+        
+    
+    
+        
+        Room: %1
+        
+    
+    
+        
+        This is a room-specific profile. The user's name and avatar may be different from their global versions.
+        
+    
+    
+        
+        Open the global profile for this user.
         
     
     
         
-        Kick the user
+        
+        Verify
         
     
     
-        
+        
+        Start a private chat.
+        
+    
+    
+        
+        Kick the user.
+        
+    
+    
+        
+        Ban the user.
+        
+    
+    
+        
         Unverify
         
     

From f6b278dc851059cb2edf7542941fbd0503a6ae8b Mon Sep 17 00:00:00 2001
From: Nicolas Werner 
Date: Mon, 13 Sep 2021 23:17:03 +0200
Subject: [PATCH 140/232] Update translations

---
 resources/langs/nheko_cs.ts    | 2 +-
 resources/langs/nheko_de.ts    | 4 ++--
 resources/langs/nheko_el.ts    | 2 +-
 resources/langs/nheko_en.ts    | 4 ++--
 resources/langs/nheko_eo.ts    | 2 +-
 resources/langs/nheko_es.ts    | 2 +-
 resources/langs/nheko_et.ts    | 2 +-
 resources/langs/nheko_fi.ts    | 2 +-
 resources/langs/nheko_fr.ts    | 2 +-
 resources/langs/nheko_hu.ts    | 2 +-
 resources/langs/nheko_it.ts    | 2 +-
 resources/langs/nheko_ja.ts    | 2 +-
 resources/langs/nheko_ml.ts    | 2 +-
 resources/langs/nheko_nl.ts    | 2 +-
 resources/langs/nheko_pl.ts    | 2 +-
 resources/langs/nheko_pt_BR.ts | 2 +-
 resources/langs/nheko_pt_PT.ts | 2 +-
 resources/langs/nheko_ro.ts    | 2 +-
 resources/langs/nheko_ru.ts    | 2 +-
 resources/langs/nheko_si.ts    | 2 +-
 resources/langs/nheko_sv.ts    | 2 +-
 resources/langs/nheko_zh_CN.ts | 2 +-
 resources/qml/MessageView.qml  | 2 +-
 23 files changed, 25 insertions(+), 25 deletions(-)

diff --git a/resources/langs/nheko_cs.ts b/resources/langs/nheko_cs.ts
index af2a89ac..5b281e33 100644
--- a/resources/langs/nheko_cs.ts
+++ b/resources/langs/nheko_cs.ts
@@ -1112,7 +1112,7 @@ Example: https://server.my:8787
     
     
         
-        &Go to reply
+        &Go to quoted message
         
     
 
diff --git a/resources/langs/nheko_de.ts b/resources/langs/nheko_de.ts
index cc8ce50e..cb169f2b 100644
--- a/resources/langs/nheko_de.ts
+++ b/resources/langs/nheko_de.ts
@@ -1116,8 +1116,8 @@ Beispiel: https://mein.server:8787
     
     
         
-        &Go to reply
-        &Gehe zur Antwort
+        &Go to quoted message
+        &Gehe zur zitierten Nachricht
     
 
 
diff --git a/resources/langs/nheko_el.ts b/resources/langs/nheko_el.ts
index 04d688d7..3c21edbb 100644
--- a/resources/langs/nheko_el.ts
+++ b/resources/langs/nheko_el.ts
@@ -1112,7 +1112,7 @@ Example: https://server.my:8787
     
     
         
-        &Go to reply
+        &Go to quoted message
         
     
 
diff --git a/resources/langs/nheko_en.ts b/resources/langs/nheko_en.ts
index 5ab3dfb7..934c9e31 100644
--- a/resources/langs/nheko_en.ts
+++ b/resources/langs/nheko_en.ts
@@ -1116,8 +1116,8 @@ Example: https://server.my:8787
     
     
         
-        &Go to reply
-        &Go to reply
+        &Go to quoted message
+        &Go to quoted message
     
 
 
diff --git a/resources/langs/nheko_eo.ts b/resources/langs/nheko_eo.ts
index 7e2d8d4e..192e1a36 100644
--- a/resources/langs/nheko_eo.ts
+++ b/resources/langs/nheko_eo.ts
@@ -1120,7 +1120,7 @@ Ekzemplo: https://servilo.mia:8787
     
     
         
-        &Go to reply
+        &Go to quoted message
         
     
 
diff --git a/resources/langs/nheko_es.ts b/resources/langs/nheko_es.ts
index ed159928..333eece3 100644
--- a/resources/langs/nheko_es.ts
+++ b/resources/langs/nheko_es.ts
@@ -1112,7 +1112,7 @@ Example: https://server.my:8787
     
     
         
-        &Go to reply
+        &Go to quoted message
         
     
 
diff --git a/resources/langs/nheko_et.ts b/resources/langs/nheko_et.ts
index 1673236a..89fffc87 100644
--- a/resources/langs/nheko_et.ts
+++ b/resources/langs/nheko_et.ts
@@ -1116,7 +1116,7 @@ Näiteks: https://server.minu:8787
     
     
         
-        &Go to reply
+        &Go to quoted message
         
     
 
diff --git a/resources/langs/nheko_fi.ts b/resources/langs/nheko_fi.ts
index e4f006e7..eef0a03e 100644
--- a/resources/langs/nheko_fi.ts
+++ b/resources/langs/nheko_fi.ts
@@ -1116,7 +1116,7 @@ Esimerkki: https://server.my:8787
     
     
         
-        &Go to reply
+        &Go to quoted message
         
     
 
diff --git a/resources/langs/nheko_fr.ts b/resources/langs/nheko_fr.ts
index b584fa3c..b882dc3d 100644
--- a/resources/langs/nheko_fr.ts
+++ b/resources/langs/nheko_fr.ts
@@ -1116,7 +1116,7 @@ Exemple : https ://monserveur.example.com:8787
     
     
         
-        &Go to reply
+        &Go to quoted message
         
     
 
diff --git a/resources/langs/nheko_hu.ts b/resources/langs/nheko_hu.ts
index 131f36eb..dd9392c7 100644
--- a/resources/langs/nheko_hu.ts
+++ b/resources/langs/nheko_hu.ts
@@ -1116,7 +1116,7 @@ Példa: https://szerver.em:8787
     
     
         
-        &Go to reply
+        &Go to quoted message
         
     
 
diff --git a/resources/langs/nheko_it.ts b/resources/langs/nheko_it.ts
index dee40ec3..d4821cce 100644
--- a/resources/langs/nheko_it.ts
+++ b/resources/langs/nheko_it.ts
@@ -1116,7 +1116,7 @@ Esempio: https://server.mio:8787
     
     
         
-        &Go to reply
+        &Go to quoted message
         
     
 
diff --git a/resources/langs/nheko_ja.ts b/resources/langs/nheko_ja.ts
index 36b87226..b25805d4 100644
--- a/resources/langs/nheko_ja.ts
+++ b/resources/langs/nheko_ja.ts
@@ -1112,7 +1112,7 @@ Example: https://server.my:8787
     
     
         
-        &Go to reply
+        &Go to quoted message
         
     
 
diff --git a/resources/langs/nheko_ml.ts b/resources/langs/nheko_ml.ts
index 0edf7a4b..85826cc0 100644
--- a/resources/langs/nheko_ml.ts
+++ b/resources/langs/nheko_ml.ts
@@ -1112,7 +1112,7 @@ Example: https://server.my:8787
     
     
         
-        &Go to reply
+        &Go to quoted message
         
     
 
diff --git a/resources/langs/nheko_nl.ts b/resources/langs/nheko_nl.ts
index 17788714..ee0d5809 100644
--- a/resources/langs/nheko_nl.ts
+++ b/resources/langs/nheko_nl.ts
@@ -1116,7 +1116,7 @@ Voorbeeld: https://mijnserver.nl:8787
     
     
         
-        &Go to reply
+        &Go to quoted message
         
     
 
diff --git a/resources/langs/nheko_pl.ts b/resources/langs/nheko_pl.ts
index 44f47419..19163e2d 100644
--- a/resources/langs/nheko_pl.ts
+++ b/resources/langs/nheko_pl.ts
@@ -1114,7 +1114,7 @@ Example: https://server.my:8787
     
     
         
-        &Go to reply
+        &Go to quoted message
         
     
 
diff --git a/resources/langs/nheko_pt_BR.ts b/resources/langs/nheko_pt_BR.ts
index 0d067bc7..775667c0 100644
--- a/resources/langs/nheko_pt_BR.ts
+++ b/resources/langs/nheko_pt_BR.ts
@@ -1112,7 +1112,7 @@ Example: https://server.my:8787
     
     
         
-        &Go to reply
+        &Go to quoted message
         
     
 
diff --git a/resources/langs/nheko_pt_PT.ts b/resources/langs/nheko_pt_PT.ts
index 831cadd5..77e9b432 100644
--- a/resources/langs/nheko_pt_PT.ts
+++ b/resources/langs/nheko_pt_PT.ts
@@ -1112,7 +1112,7 @@ Example: https://server.my:8787
     
     
         
-        &Go to reply
+        &Go to quoted message
         
     
 
diff --git a/resources/langs/nheko_ro.ts b/resources/langs/nheko_ro.ts
index c6c79d3f..e1fa6bcc 100644
--- a/resources/langs/nheko_ro.ts
+++ b/resources/langs/nheko_ro.ts
@@ -1116,7 +1116,7 @@ Exemplu: https://serverul.meu:8787
     
     
         
-        &Go to reply
+        &Go to quoted message
         
     
 
diff --git a/resources/langs/nheko_ru.ts b/resources/langs/nheko_ru.ts
index f7d0d847..64762ab8 100644
--- a/resources/langs/nheko_ru.ts
+++ b/resources/langs/nheko_ru.ts
@@ -1116,7 +1116,7 @@ Example: https://server.my:8787
     
     
         
-        &Go to reply
+        &Go to quoted message
         
     
 
diff --git a/resources/langs/nheko_si.ts b/resources/langs/nheko_si.ts
index a5fee005..88607fb3 100644
--- a/resources/langs/nheko_si.ts
+++ b/resources/langs/nheko_si.ts
@@ -1112,7 +1112,7 @@ Example: https://server.my:8787
     
     
         
-        &Go to reply
+        &Go to quoted message
         
     
 
diff --git a/resources/langs/nheko_sv.ts b/resources/langs/nheko_sv.ts
index e20c2823..d0f27e50 100644
--- a/resources/langs/nheko_sv.ts
+++ b/resources/langs/nheko_sv.ts
@@ -1116,7 +1116,7 @@ Exempel: https://server.my:8787
     
     
         
-        &Go to reply
+        &Go to quoted message
         
     
 
diff --git a/resources/langs/nheko_zh_CN.ts b/resources/langs/nheko_zh_CN.ts
index c813ff7e..c3f03a6f 100644
--- a/resources/langs/nheko_zh_CN.ts
+++ b/resources/langs/nheko_zh_CN.ts
@@ -1112,7 +1112,7 @@ Example: https://server.my:8787
     
     
         
-        &Go to reply
+        &Go to quoted message
         
     
 
diff --git a/resources/qml/MessageView.qml b/resources/qml/MessageView.qml
index 4398766a..2d4fe3a7 100644
--- a/resources/qml/MessageView.qml
+++ b/resources/qml/MessageView.qml
@@ -679,7 +679,7 @@ ScrollView {
         Platform.MenuItem {
             visible: true
             enabled: visible
-            text: qsTr("&Go to reply")
+            text: qsTr("&Go to quoted message")
             onTriggered: chat.model.showEvent(eventId)
         }
     }

From d49fbae9896f5a8ab9c75be1e942721893e4f5d3 Mon Sep 17 00:00:00 2001
From: Weblate 
Date: Mon, 13 Sep 2021 17:20:24 -0400
Subject: [PATCH 141/232] Translated using Weblate (Dutch)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Currently translated at 100.0% (569 of 569 strings)

Co-authored-by: Jaron Viëtor 
Translate-URL: https://weblate.nheko.im/projects/nheko/nheko-master/nl/
Translation: Nheko/nheko
---
 resources/langs/nheko_nl.ts | 24 ++++++++++++------------
 1 file changed, 12 insertions(+), 12 deletions(-)

diff --git a/resources/langs/nheko_nl.ts b/resources/langs/nheko_nl.ts
index 17788714..3d60067f 100644
--- a/resources/langs/nheko_nl.ts
+++ b/resources/langs/nheko_nl.ts
@@ -1117,7 +1117,7 @@ Voorbeeld: https://mijnserver.nl:8787
     
         
         &Go to reply
-        
+        &Ga naar citaat
     
 
 
@@ -1183,7 +1183,7 @@ Voorbeeld: https://mijnserver.nl:8787
     
         
         You will be pinging the whole room
-        
+        Je gaat een melding versturen naar de hele kamer
     
 
 
@@ -2215,37 +2215,37 @@ Voorbeeld: https://mijnserver.nl:8787
     
         
         Change avatar globally.
-        
+        Verander avatar globaal.
     
     
         
         Change avatar. Will only apply to this room.
-        
+        Verander avatar. Heeft alleen effect op deze kamer.
     
     
         
         Change display name globally.
-        
+        Verander weergavenaam globaal.
     
     
         
         Change display name. Will only apply to this room.
-        
+        Verander weergavenaam. Heeft alleen effect op deze kamer.
     
     
         
         Room: %1
-        
+        Kamer: %1
     
     
         
         This is a room-specific profile. The user's name and avatar may be different from their global versions.
-        
+        Dit is een kamer-specifiek profiel. De weergavenaam en avatar kunnen verschillen van de globale versie.
     
     
         
         Open the global profile for this user.
-        
+        Open het globale profiel van deze gebruiker.
     
     
         
@@ -2256,17 +2256,17 @@ Voorbeeld: https://mijnserver.nl:8787
     
         
         Start a private chat.
-        
+        Begin een privéchat.
     
     
         
         Kick the user.
-        
+        Verwijder de gebruiker.
     
     
         
         Ban the user.
-        
+        Verban de gebruiker.
     
     
         

From 61f57b0e09ef00f9a84dc000b62029ef6b21775f Mon Sep 17 00:00:00 2001
From: Weblate 
Date: Mon, 13 Sep 2021 17:23:00 -0400
Subject: [PATCH 142/232] Update translation files

Updated by "Cleanup translation files" hook in Weblate.

Co-authored-by: Weblate 
Translate-URL: https://weblate.nheko.im/projects/nheko/nheko-master/
Translation: Nheko/nheko
---
 resources/langs/nheko_nl.ts | 5 -----
 1 file changed, 5 deletions(-)

diff --git a/resources/langs/nheko_nl.ts b/resources/langs/nheko_nl.ts
index 3d60067f..432af6c9 100644
--- a/resources/langs/nheko_nl.ts
+++ b/resources/langs/nheko_nl.ts
@@ -1114,11 +1114,6 @@ Voorbeeld: https://mijnserver.nl:8787
         Copy link to eve&nt
         Kopieer link naar gebeurte&nis
     
-    
-        
-        &Go to reply
-        &Ga naar citaat
-    
 
 
     NewVerificationRequest

From f032c75e6a16d50ea2a2fc29a374c55715c0f000 Mon Sep 17 00:00:00 2001
From: Nicolas Werner 
Date: Mon, 13 Sep 2021 23:45:09 +0200
Subject: [PATCH 143/232] Remove unused prototype

---
 src/Cache_p.h | 3 ---
 1 file changed, 3 deletions(-)

diff --git a/src/Cache_p.h b/src/Cache_p.h
index 43644d7e..ff2f31e5 100644
--- a/src/Cache_p.h
+++ b/src/Cache_p.h
@@ -136,9 +136,6 @@ public:
         //! Retrieve all the user ids from a room.
         std::vector roomMembers(const std::string &room_id);
 
-        //! Get the other user from an invite to a direct chat.
-        RoomMember getDirectInviteMember(const std::string &room_id);
-
         //! Check if the given user has power leve greater than than
         //! lowest power level of the given events.
         bool hasEnoughPowerLevel(const std::vector &eventTypes,

From 82b1cc4e5f5a13793b3347c69292be22292aa82c Mon Sep 17 00:00:00 2001
From: Guillaume Girol 
Date: Sat, 11 Sep 2021 12:00:00 +0000
Subject: [PATCH 144/232] add Alt+A keybinding to switch to next room with
 unread messages

---
 resources/qml/Root.qml         |  5 ++++
 src/timeline/RoomlistModel.cpp | 46 ++++++++++++++++++++++++++++++++++
 src/timeline/RoomlistModel.h   |  1 +
 3 files changed, 52 insertions(+)

diff --git a/resources/qml/Root.qml b/resources/qml/Root.qml
index cc7d32ea..4207f35b 100644
--- a/resources/qml/Root.qml
+++ b/resources/qml/Root.qml
@@ -120,6 +120,11 @@ Page {
         }
     }
 
+    Shortcut {
+        sequence: "Alt+A"
+        onActivated: Rooms.nextRoomWithActivity()
+    }
+
     Shortcut {
         sequence: "Ctrl+Down"
         onActivated: Rooms.nextRoom()
diff --git a/src/timeline/RoomlistModel.cpp b/src/timeline/RoomlistModel.cpp
index 942a4b05..0f44ec6c 100644
--- a/src/timeline/RoomlistModel.cpp
+++ b/src/timeline/RoomlistModel.cpp
@@ -899,6 +899,52 @@ FilteredRoomlistModel::toggleTag(QString roomid, QString tag, bool on)
         }
 }
 
+void
+FilteredRoomlistModel::nextRoomWithActivity()
+{
+        int roomWithMention       = -1;
+        int roomWithNotification  = -1;
+        int roomWithUnreadMessage = -1;
+        auto r                    = currentRoom();
+        int currentRoomIdx        = r ? roomidToIndex(r->roomId()) : -1;
+        // first look for mentions
+        for (int i = 0; i < (int)roomlistmodel->roomids.size(); i++) {
+                if (i == currentRoomIdx)
+                        continue;
+                if (this->data(index(i, 0), RoomlistModel::HasLoudNotification).toBool()) {
+                        roomWithMention = i;
+                        break;
+                }
+                if (roomWithNotification == -1 &&
+                    this->data(index(i, 0), RoomlistModel::NotificationCount).toInt() > 0) {
+                        roomWithNotification = i;
+                        // don't break, we must continue looking for rooms with mentions
+                }
+                if (roomWithNotification == -1 && roomWithUnreadMessage == -1 &&
+                    this->data(index(i, 0), RoomlistModel::HasUnreadMessages).toBool()) {
+                        roomWithUnreadMessage = i;
+                        // don't break, we must continue looking for rooms with mentions
+                }
+        }
+        QString targetRoomId = nullptr;
+        if (roomWithMention != -1) {
+                targetRoomId =
+                  this->data(index(roomWithMention, 0), RoomlistModel::RoomId).toString();
+                nhlog::ui()->debug("choosing {} for mentions", targetRoomId.toStdString());
+        } else if (roomWithNotification != -1) {
+                targetRoomId =
+                  this->data(index(roomWithNotification, 0), RoomlistModel::RoomId).toString();
+                nhlog::ui()->debug("choosing {} for notifications", targetRoomId.toStdString());
+        } else if (roomWithUnreadMessage != -1) {
+                targetRoomId =
+                  this->data(index(roomWithUnreadMessage, 0), RoomlistModel::RoomId).toString();
+                nhlog::ui()->debug("choosing {} for unread messages", targetRoomId.toStdString());
+        }
+        if (targetRoomId != nullptr) {
+                setCurrentRoom(targetRoomId);
+        }
+}
+
 void
 FilteredRoomlistModel::nextRoom()
 {
diff --git a/src/timeline/RoomlistModel.h b/src/timeline/RoomlistModel.h
index 6ac6da18..dbdd06c2 100644
--- a/src/timeline/RoomlistModel.h
+++ b/src/timeline/RoomlistModel.h
@@ -170,6 +170,7 @@ public slots:
         void setCurrentRoom(QString roomid) { roomlistmodel->setCurrentRoom(std::move(roomid)); }
         void resetCurrentRoom() { roomlistmodel->resetCurrentRoom(); }
 
+        void nextRoomWithActivity();
         void nextRoom();
         void previousRoom();
 

From f91a0267e659ed23529ac35d94782caa8850ec6e Mon Sep 17 00:00:00 2001
From: Nicolas Werner 
Date: Tue, 14 Sep 2021 16:10:04 +0200
Subject: [PATCH 145/232] Add workaround for crash on some jdenticon inputs

---
 src/JdenticonProvider.cpp | 11 ++++++++---
 1 file changed, 8 insertions(+), 3 deletions(-)

diff --git a/src/JdenticonProvider.cpp b/src/JdenticonProvider.cpp
index 3b819c7c..23b601fc 100644
--- a/src/JdenticonProvider.cpp
+++ b/src/JdenticonProvider.cpp
@@ -62,9 +62,14 @@ JdenticonResponse::run()
         painter.setRenderHint(QPainter::Antialiasing, true);
         painter.setRenderHint(QPainter::SmoothPixmapTransform, true);
 
-        QSvgRenderer renderer{
-          jdenticonInterface_->generate(m_key, m_requestedSize.width()).toUtf8()};
-        renderer.render(&painter);
+        try {
+                QSvgRenderer renderer{
+                  jdenticonInterface_->generate(m_key, m_requestedSize.width()).toUtf8()};
+                renderer.render(&painter);
+        } catch (std::exception &e) {
+                nhlog::ui()->error(
+                  "caught {} in jdenticonprovider, key '{}'", e.what(), m_key.toStdString());
+        }
 
         painter.end();
 

From c7545cb455fb89a360c0da18cfc54f39168dcfcc Mon Sep 17 00:00:00 2001
From: Joseph Donofry 
Date: Tue, 14 Sep 2021 18:39:57 -0400
Subject: [PATCH 146/232] Fix a few jdenticon bugs

---
 resources/qml/RoomSettings.qml                    | 2 +-
 resources/qml/dialogs/ImagePackSettingsDialog.qml | 3 ++-
 2 files changed, 3 insertions(+), 2 deletions(-)

diff --git a/resources/qml/RoomSettings.qml b/resources/qml/RoomSettings.qml
index 152567c8..6caf8790 100644
--- a/resources/qml/RoomSettings.qml
+++ b/resources/qml/RoomSettings.qml
@@ -38,7 +38,7 @@ ApplicationWindow {
 
         Avatar {
             url: roomSettings.roomAvatarUrl.replace("mxc://", "image://MxcImage/")
-            roomid: roomSettings.roomid
+            roomid: roomSettings.roomId
             displayName: roomSettings.roomName
             height: 130
             width: 130
diff --git a/resources/qml/dialogs/ImagePackSettingsDialog.qml b/resources/qml/dialogs/ImagePackSettingsDialog.qml
index ca09ff27..e48040c1 100644
--- a/resources/qml/dialogs/ImagePackSettingsDialog.qml
+++ b/resources/qml/dialogs/ImagePackSettingsDialog.qml
@@ -101,6 +101,7 @@ ApplicationWindow {
                     required property string displayName
                     required property bool fromAccountData
                     required property bool fromCurrentRoom
+                    required property string statekey
 
                     title: displayName
                     subtitle: {
@@ -112,7 +113,7 @@ ApplicationWindow {
                             return qsTr("Globally enabled pack");
                     }
                     selectedIndex: currentPackIndex
-                    roomid: currentPack.statekey
+                    roomid: statekey
 
                     TapHandler {
                         onSingleTapped: currentPackIndex = index

From 30aedd36a1d3359a0200f14cacebc67e7bcf14a5 Mon Sep 17 00:00:00 2001
From: Nicolas Werner 
Date: Wed, 15 Sep 2021 23:18:21 +0200
Subject: [PATCH 147/232] Reenable reuseItems for the message view

If this is still broken, we will need to disable it again.
---
 resources/qml/MessageView.qml               | 6 +++---
 resources/qml/delegates/Encrypted.qml       | 4 ++--
 resources/qml/delegates/MessageDelegate.qml | 2 +-
 3 files changed, 6 insertions(+), 6 deletions(-)

diff --git a/resources/qml/MessageView.qml b/resources/qml/MessageView.qml
index 2d4fe3a7..d80d274d 100644
--- a/resources/qml/MessageView.qml
+++ b/resources/qml/MessageView.qml
@@ -25,8 +25,8 @@ ScrollView {
 
         model: room
         // reuseItems still has a few bugs, see https://bugreports.qt.io/browse/QTBUG-95105 https://bugreports.qt.io/browse/QTBUG-95107
-        //onModelChanged: if (room) room.sendReset()
-        //reuseItems: true
+        onModelChanged: if (room) room.sendReset()
+        reuseItems: true
         boundsBehavior: Flickable.StopAtBounds
         pixelAligned: true
         spacing: 4
@@ -361,7 +361,7 @@ ScrollView {
 
             anchors.horizontalCenter: parent ? parent.horizontalCenter : undefined
             width: chat.delegateMaxWidth
-            height: section ? section.height + timelinerow.height : timelinerow.height
+            height: Math.max(section.active ? section.height + timelinerow.height : timelinerow.height, 10)
 
             Rectangle {
                 id: scrollHighlight
diff --git a/resources/qml/delegates/Encrypted.qml b/resources/qml/delegates/Encrypted.qml
index cd00a9d4..6616d3ce 100644
--- a/resources/qml/delegates/Encrypted.qml
+++ b/resources/qml/delegates/Encrypted.qml
@@ -3,11 +3,11 @@
 // SPDX-License-Identifier: GPL-3.0-or-later
 
 import ".."
+import QtQuick 2.15
 import QtQuick.Controls 2.1
-import QtQuick.Layouts 1.2
 import im.nheko 1.0
 
-ColumnLayout {
+Column {
     id: r
 
     required property int encryptionError
diff --git a/resources/qml/delegates/MessageDelegate.qml b/resources/qml/delegates/MessageDelegate.qml
index 4086a1a8..9f889106 100644
--- a/resources/qml/delegates/MessageDelegate.qml
+++ b/resources/qml/delegates/MessageDelegate.qml
@@ -34,7 +34,7 @@ Item {
     required property int encryptionError
     required property int relatedEventCacheBuster
 
-    height: chooser.child.height
+    height: Math.max(chooser.child.height, 20)
 
     DelegateChooser {
         id: chooser

From 5bff9df4ae3291598549fc0904725a1ec3c416e3 Mon Sep 17 00:00:00 2001
From: Nicolas Werner 
Date: Wed, 15 Sep 2021 23:38:01 +0200
Subject: [PATCH 148/232] Workaround for broken fetchMore() with reuseItems

---
 src/timeline/TimelineModel.cpp | 4 ++++
 1 file changed, 4 insertions(+)

diff --git a/src/timeline/TimelineModel.cpp b/src/timeline/TimelineModel.cpp
index ca303040..e03c32a7 100644
--- a/src/timeline/TimelineModel.cpp
+++ b/src/timeline/TimelineModel.cpp
@@ -717,6 +717,10 @@ TimelineModel::data(const QModelIndex &index, int role) const
         if (index.row() < 0 && index.row() >= rowCount())
                 return QVariant();
 
+        // HACK(Nico): fetchMore likes to break with dynamically sized delegates and reuseItems
+        if (index.row() + 1 == rowCount() && !m_paginationInProgress)
+                const_cast(this)->fetchMore(index);
+
         auto event = events.get(rowCount() - index.row() - 1);
 
         if (!event)

From 1d5bf56cf9dc8aea4aa849ef5f0f580c1eae4cdd Mon Sep 17 00:00:00 2001
From: Thulinma 
Date: Thu, 16 Sep 2021 01:41:55 +0200
Subject: [PATCH 149/232] Improvements for linking to events

- Fixes scrolling to an event not being reliable
- Adds new /goto command that can open URLs, go to events, or go to message indexes.
- Refactored ChatPage::handleMatrixUri() to contain the handling originally in Nheko::openLink(), and makes it return a boolean based on whether the URL was handled internally or not.
---
 resources/qml/MessageView.qml  |  5 ++-
 src/ChatPage.cpp               | 71 +++++++++++++++++++++++++++++-----
 src/ChatPage.h                 |  4 +-
 src/timeline/InputBar.cpp      | 17 ++++++++
 src/timeline/TimelineModel.cpp | 16 +++++++-
 src/ui/NhekoGlobalObject.cpp   | 44 +--------------------
 6 files changed, 101 insertions(+), 56 deletions(-)

diff --git a/resources/qml/MessageView.qml b/resources/qml/MessageView.qml
index d80d274d..e4e58845 100644
--- a/resources/qml/MessageView.qml
+++ b/resources/qml/MessageView.qml
@@ -20,6 +20,9 @@ ScrollView {
 
     ListView {
         id: chat
+        
+        displayMarginBeginning: height/2
+        displayMarginEnd: height/2
 
         property int delegateMaxWidth: ((Settings.timelineMaxWidth > 100 && Settings.timelineMaxWidth < parent.availableWidth) ? Settings.timelineMaxWidth : parent.availableWidth) - parent.padding * 2
 
@@ -276,7 +279,7 @@ ScrollView {
                         }
 
                         function onScrollToIndex(index) {
-                            chat.positionViewAtIndex(index, ListView.Visible);
+                            chat.positionViewAtIndex(index, ListView.Center);
                         }
 
                         target: chat.model
diff --git a/src/ChatPage.cpp b/src/ChatPage.cpp
index a07a9654..3887f8b8 100644
--- a/src/ChatPage.cpp
+++ b/src/ChatPage.cpp
@@ -1233,14 +1233,58 @@ mxidFromSegments(QStringRef sigil, QStringRef mxid)
         }
 }
 
-void
+bool
 ChatPage::handleMatrixUri(const QByteArray &uri)
 {
         nhlog::ui()->info("Received uri! {}", uri.toStdString());
         QUrl uri_{QString::fromUtf8(uri)};
 
+        // Convert matrix.to URIs to proper format
+        if (uri_.scheme() == "https" && uri_.host() == "matrix.to") {
+                QString p = uri_.fragment(QUrl::FullyEncoded);
+                if (p.startsWith("/"))
+                        p.remove(0, 1);
+
+                auto temp = p.split("?");
+                QString query;
+                if (temp.size() >= 2)
+                        query = QUrl::fromPercentEncoding(temp.takeAt(1).toUtf8());
+
+                temp            = temp.first().split("/");
+                auto identifier = QUrl::fromPercentEncoding(temp.takeFirst().toUtf8());
+                QString eventId = QUrl::fromPercentEncoding(temp.join('/').toUtf8());
+                if (!identifier.isEmpty()) {
+                        if (identifier.startsWith("@")) {
+                                QByteArray newUri =
+                                  "matrix:u/" + QUrl::toPercentEncoding(identifier.remove(0, 1));
+                                if (!query.isEmpty())
+                                        newUri.append("?" + query.toUtf8());
+                                return handleMatrixUri(QUrl::fromEncoded(newUri));
+                        } else if (identifier.startsWith("#")) {
+                                QByteArray newUri =
+                                  "matrix:r/" + QUrl::toPercentEncoding(identifier.remove(0, 1));
+                                if (!eventId.isEmpty())
+                                        newUri.append(
+                                          "/e/" + QUrl::toPercentEncoding(eventId.remove(0, 1)));
+                                if (!query.isEmpty())
+                                        newUri.append("?" + query.toUtf8());
+                                return handleMatrixUri(QUrl::fromEncoded(newUri));
+                        } else if (identifier.startsWith("!")) {
+                                QByteArray newUri = "matrix:roomid/" + QUrl::toPercentEncoding(
+                                                                         identifier.remove(0, 1));
+                                if (!eventId.isEmpty())
+                                        newUri.append(
+                                          "/e/" + QUrl::toPercentEncoding(eventId.remove(0, 1)));
+                                if (!query.isEmpty())
+                                        newUri.append("?" + query.toUtf8());
+                                return handleMatrixUri(QUrl::fromEncoded(newUri));
+                        }
+                }
+        }
+
+        // non-matrix URIs are not handled by us, return false
         if (uri_.scheme() != "matrix")
-                return;
+                return false;
 
         auto tempPath = uri_.path(QUrl::ComponentFormattingOption::FullyEncoded);
         if (tempPath.startsWith('/'))
@@ -1248,17 +1292,17 @@ ChatPage::handleMatrixUri(const QByteArray &uri)
         auto segments = tempPath.splitRef('/');
 
         if (segments.size() != 2 && segments.size() != 4)
-                return;
+                return false;
 
         auto sigil1 = segments[0];
         auto mxid1  = mxidFromSegments(sigil1, segments[1]);
         if (mxid1.isEmpty())
-                return;
+                return false;
 
         QString mxid2;
         if (segments.size() == 4 && segments[2] == "e") {
                 if (segments[3].isEmpty())
-                        return;
+                        return false;
                 else
                         mxid2 = "$" + QUrl::fromPercentEncoding(segments[3].toUtf8());
         }
@@ -1283,12 +1327,13 @@ ChatPage::handleMatrixUri(const QByteArray &uri)
                         if (t &&
                             cache::isRoomMember(mxid1.toStdString(), t->roomId().toStdString())) {
                                 t->openUserProfile(mxid1);
-                                return;
+                                return true;
                         }
                         emit view_manager_->openGlobalUserProfile(mxid1);
                 } else if (action == "chat") {
                         this->startChat(mxid1);
                 }
+                return true;
         } else if (sigil1 == "roomid") {
                 auto joined_rooms = cache::joinedRooms();
                 auto targetRoomId = mxid1.toStdString();
@@ -1298,13 +1343,15 @@ ChatPage::handleMatrixUri(const QByteArray &uri)
                                 view_manager_->rooms()->setCurrentRoom(mxid1);
                                 if (!mxid2.isEmpty())
                                         view_manager_->showEvent(mxid1, mxid2);
-                                return;
+                                return true;
                         }
                 }
 
                 if (action == "join" || action.isEmpty()) {
                         joinRoomVia(targetRoomId, vias);
+                        return true;
                 }
+                return false;
         } else if (sigil1 == "r") {
                 auto joined_rooms    = cache::joinedRooms();
                 auto targetRoomAlias = mxid1.toStdString();
@@ -1318,21 +1365,25 @@ ChatPage::handleMatrixUri(const QByteArray &uri)
                                         if (!mxid2.isEmpty())
                                                 view_manager_->showEvent(
                                                   QString::fromStdString(roomid), mxid2);
-                                        return;
+                                        return true;
                                 }
                         }
                 }
 
                 if (action == "join" || action.isEmpty()) {
                         joinRoomVia(mxid1.toStdString(), vias);
+                        return true;
                 }
+                return false;
         }
+        return false;
 }
 
-void
+bool
 ChatPage::handleMatrixUri(const QUrl &uri)
 {
-        handleMatrixUri(uri.toString(QUrl::ComponentFormattingOption::FullyEncoded).toUtf8());
+        return handleMatrixUri(
+          uri.toString(QUrl::ComponentFormattingOption::FullyEncoded).toUtf8());
 }
 
 bool
diff --git a/src/ChatPage.h b/src/ChatPage.h
index 9cbf2a03..66e4c6ab 100644
--- a/src/ChatPage.h
+++ b/src/ChatPage.h
@@ -78,8 +78,8 @@ public:
         QString currentRoom() const;
 
 public slots:
-        void handleMatrixUri(const QByteArray &uri);
-        void handleMatrixUri(const QUrl &uri);
+        bool handleMatrixUri(const QByteArray &uri);
+        bool handleMatrixUri(const QUrl &uri);
 
         void startChat(QString userid);
         void leaveRoom(const QString &room_id);
diff --git a/src/timeline/InputBar.cpp b/src/timeline/InputBar.cpp
index ece9db62..a6fbab78 100644
--- a/src/timeline/InputBar.cpp
+++ b/src/timeline/InputBar.cpp
@@ -630,6 +630,23 @@ InputBar::command(QString command, QString args)
                 notice(args, false);
         } else if (command == "rainbownotice") {
                 notice(args, true);
+        } else if (command == "goto") {
+                // Goto has three different modes:
+                // 1 - Going directly to a given event ID
+                if (args[0] == '$') {
+                        room->showEvent(args);
+                        return;
+                }
+                // 2 - Going directly to a given message index
+                if (args[0] >= '0' && args[0] <= '9') {
+                        room->showEvent(args);
+                        return;
+                }
+                // 3 - Matrix URI handler, as if you clicked the URI
+                if (ChatPage::instance()->handleMatrixUri(args)) {
+                        return;
+                }
+                nhlog::net()->error("Could not resolve goto: {}", args.toStdString());
         }
 }
 
diff --git a/src/timeline/TimelineModel.cpp b/src/timeline/TimelineModel.cpp
index e03c32a7..00f6d9df 100644
--- a/src/timeline/TimelineModel.cpp
+++ b/src/timeline/TimelineModel.cpp
@@ -1534,11 +1534,25 @@ void
 TimelineModel::showEvent(QString eventId)
 {
         using namespace std::chrono_literals;
-        if (idToIndex(eventId) != -1) {
+        // Direct to eventId
+        if (eventId[0] == '$') {
+                int idx = idToIndex(eventId);
+                if (idx == -1) {
+                        nhlog::ui()->warn("Scrolling to event id {}, failed - no known index",
+                                          eventId.toStdString());
+                        return;
+                }
                 eventIdToShow = eventId;
                 emit scrollTargetChanged();
                 showEventTimer.start(50ms);
+                return;
         }
+        // to message index
+        eventId       = indexToId(eventId.toInt());
+        eventIdToShow = eventId;
+        emit scrollTargetChanged();
+        showEventTimer.start(50ms);
+        return;
 }
 
 void
diff --git a/src/ui/NhekoGlobalObject.cpp b/src/ui/NhekoGlobalObject.cpp
index 9e0d706b..355f187b 100644
--- a/src/ui/NhekoGlobalObject.cpp
+++ b/src/ui/NhekoGlobalObject.cpp
@@ -57,48 +57,8 @@ void
 Nheko::openLink(QString link) const
 {
         QUrl url(link);
-        if (url.scheme() == "https" && url.host() == "matrix.to") {
-                // handle matrix.to links internally
-                QString p = url.fragment(QUrl::FullyEncoded);
-                if (p.startsWith("/"))
-                        p.remove(0, 1);
-
-                auto temp = p.split("?");
-                QString query;
-                if (temp.size() >= 2)
-                        query = QUrl::fromPercentEncoding(temp.takeAt(1).toUtf8());
-
-                temp            = temp.first().split("/");
-                auto identifier = QUrl::fromPercentEncoding(temp.takeFirst().toUtf8());
-                QString eventId = QUrl::fromPercentEncoding(temp.join('/').toUtf8());
-                if (!identifier.isEmpty()) {
-                        if (identifier.startsWith("@")) {
-                                QByteArray uri =
-                                  "matrix:u/" + QUrl::toPercentEncoding(identifier.remove(0, 1));
-                                if (!query.isEmpty())
-                                        uri.append("?" + query.toUtf8());
-                                ChatPage::instance()->handleMatrixUri(QUrl::fromEncoded(uri));
-                        } else if (identifier.startsWith("#")) {
-                                QByteArray uri =
-                                  "matrix:r/" + QUrl::toPercentEncoding(identifier.remove(0, 1));
-                                if (!eventId.isEmpty())
-                                        uri.append("/e/" +
-                                                   QUrl::toPercentEncoding(eventId.remove(0, 1)));
-                                if (!query.isEmpty())
-                                        uri.append("?" + query.toUtf8());
-                                ChatPage::instance()->handleMatrixUri(QUrl::fromEncoded(uri));
-                        } else if (identifier.startsWith("!")) {
-                                QByteArray uri = "matrix:roomid/" +
-                                                 QUrl::toPercentEncoding(identifier.remove(0, 1));
-                                if (!eventId.isEmpty())
-                                        uri.append("/e/" +
-                                                   QUrl::toPercentEncoding(eventId.remove(0, 1)));
-                                if (!query.isEmpty())
-                                        uri.append("?" + query.toUtf8());
-                                ChatPage::instance()->handleMatrixUri(QUrl::fromEncoded(uri));
-                        }
-                }
-        } else {
+        // Open externally if we couldn't handle it internally
+        if (!ChatPage::instance()->handleMatrixUri(url)) {
                 QDesktopServices::openUrl(url);
         }
 }

From 28a660757947d4d3d14255c4e2183c6352a34cab Mon Sep 17 00:00:00 2001
From: Weblate 
Date: Wed, 15 Sep 2021 23:42:33 -0400
Subject: [PATCH 150/232] Translated using Weblate (Estonian)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Currently translated at 100.0% (569 of 569 strings)

Co-authored-by: Priit Jõerüüt 
Translate-URL: https://weblate.nheko.im/projects/nheko/nheko-master/et/
Translation: Nheko/nheko
---
 resources/langs/nheko_et.ts | 24 ++++++++++++------------
 1 file changed, 12 insertions(+), 12 deletions(-)

diff --git a/resources/langs/nheko_et.ts b/resources/langs/nheko_et.ts
index 89fffc87..bd772b55 100644
--- a/resources/langs/nheko_et.ts
+++ b/resources/langs/nheko_et.ts
@@ -1117,7 +1117,7 @@ Näiteks: https://server.minu:8787
     
         
         &Go to quoted message
-        
+        &Vaata tsiteeritud sõnumit
     
 
 
@@ -1183,7 +1183,7 @@ Näiteks: https://server.minu:8787
     
         
         You will be pinging the whole room
-        
+        Sa saadad viitesõnumi tervele jututoale
     
 
 
@@ -2215,37 +2215,37 @@ Näiteks: https://server.minu:8787
     
         
         Change avatar globally.
-        
+        Muuda oma tunnuspilti kõikjal.
     
     
         
         Change avatar. Will only apply to this room.
-        
+        Muuda oma tunnuspilti vaid selles jututoas.
     
     
         
         Change display name globally.
-        
+        Muuda oma kuvatavat nime kõikjal.
     
     
         
         Change display name. Will only apply to this room.
-        
+        Muuda oma kuvatavat nime vaid selles jututoas.
     
     
         
         Room: %1
-        
+        Jututuba: %1
     
     
         
         This is a room-specific profile. The user's name and avatar may be different from their global versions.
-        
+        See kasutajaprofiil on vaid selle jututoa kohane. Kasutaja kuvatav nimi ja tunnuspilt võivad muudes jutubades olla teistsugused.
     
     
         
         Open the global profile for this user.
-        
+        Vaata selle kasutaja üldist profiili.
     
     
         
@@ -2256,17 +2256,17 @@ Näiteks: https://server.minu:8787
     
         
         Start a private chat.
-        
+        Alusta privaatset vestlust.
     
     
         
         Kick the user.
-        
+        Müksa kasutaja välja.
     
     
         
         Ban the user.
-        
+        Sea kasutajale suhtluskeeld.
     
     
         

From 9bef5367233af33202caa5b7d75cd3da00babba1 Mon Sep 17 00:00:00 2001
From: Weblate 
Date: Wed, 15 Sep 2021 23:42:33 -0400
Subject: [PATCH 151/232] Translated using Weblate (Finnish)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Currently translated at 84.0% (478 of 569 strings)

Co-authored-by: Priit Jõerüüt 
Translate-URL: https://weblate.nheko.im/projects/nheko/nheko-master/fi/
Translation: Nheko/nheko
---
 resources/langs/nheko_fi.ts | 34 +++++++++++++++++-----------------
 1 file changed, 17 insertions(+), 17 deletions(-)

diff --git a/resources/langs/nheko_fi.ts b/resources/langs/nheko_fi.ts
index eef0a03e..b67b1489 100644
--- a/resources/langs/nheko_fi.ts
+++ b/resources/langs/nheko_fi.ts
@@ -328,7 +328,7 @@
     
         
         Rooms you have favourited.
-        Suosikkihuoneesi
+        Suosikkihuoneesi.
     
     
         
@@ -619,7 +619,7 @@
     
         
         Stickers (*.png *.webp *.gif)
-        
+        Tarrat (*.png *.webp *.gif)
     
     
         
@@ -666,12 +666,12 @@
     
         
         Remove
-        
+        Poista
     
     
         
         Cancel
-        Peruuta
+        Peruuta
     
     
         
@@ -724,12 +724,12 @@
     
         
         Edit
-        Muokkaa
+        Muokkaa
     
     
         
         Close
-        Sulje
+        Sulje
     
 
 
@@ -760,7 +760,7 @@
     
         
         User ID to invite
-        Käyttäjätunnus kutsuttavaksi
+        Käyttäjätunnus kutsuttavaksi
     
     
         
@@ -781,7 +781,7 @@
     
         
         Cancel
-        Peruuta
+        Peruuta
     
 
 
@@ -1057,7 +1057,7 @@ Esimerkki: https://server.my:8787
     
         
         Re&act
-        Re&act
+        Rea&goi
     
     
         
@@ -1294,7 +1294,7 @@ Esimerkki: https://server.my:8787
     
         
         Read receipts
-        Lukukuittaukset
+        Lukukuittaukset
     
 
 
@@ -1865,7 +1865,7 @@ Esimerkki: https://server.my:8787
     
         
         Search
-        Hae
+        Hae
     
 
 
@@ -1956,7 +1956,7 @@ Esimerkki: https://server.my:8787
     
         
         %1 has closed the room to guest access.
-        %1 on sulkenut huoneen vierailta
+        %1 on sulkenut huoneen vierailta.
     
     
         
@@ -2256,17 +2256,17 @@ Esimerkki: https://server.my:8787
     
         
         Start a private chat.
-        
+        Aloita yksityinen keskustelu.
     
     
         
         Kick the user.
-        
+        Potki käyttäjä.
     
     
         
         Ban the user.
-        
+        Anna käyttäjälle porttikielto.
     
     
         
@@ -2370,7 +2370,7 @@ Esimerkki: https://server.my:8787
         Change the appearance of user avatars in chats.
 OFF - square, ON - Circle.
         Muuta käyttäjien avatarien ulkonäköä keskusteluissa.
-POIS PÄÄLTÄ - neliö, PÄÄLLÄ - pyöreä
+POIS PÄÄLTÄ - neliö, PÄÄLLÄ - pyöreä.
     
     
         
@@ -2615,7 +2615,7 @@ Tämä yleensä saa sovelluksen kuvakkeen liikkumaan jollain tapaa tehtäväpalk
     
         
         Set the notification sound to play when a call invite arrives
-        Aseta ilmoitusääni soimaan kun kutsu puheluun tulee.
+        Aseta ilmoitusääni soimaan kun kutsu puheluun tulee
     
     
         

From a6fcea1b141e1bbbe494b76a2d8973c4dc012a12 Mon Sep 17 00:00:00 2001
From: Nicolas Werner 
Date: Thu, 16 Sep 2021 15:56:58 +0200
Subject: [PATCH 152/232] bump mtxclient

fixes build with new libolm
---
 CMakeLists.txt                   | 2 +-
 io.github.NhekoReborn.Nheko.yaml | 2 +-
 2 files changed, 2 insertions(+), 2 deletions(-)

diff --git a/CMakeLists.txt b/CMakeLists.txt
index 1b6c08b7..85726fed 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -385,7 +385,7 @@ if(USE_BUNDLED_MTXCLIENT)
 	FetchContent_Declare(
 		MatrixClient
 		GIT_REPOSITORY https://github.com/Nheko-Reborn/mtxclient.git
-		GIT_TAG        ef741d7dceed11ccd46a553a4c886491aedc973b
+		GIT_TAG        b452a984b0fc522c21bb8df7d320bf13960974d0
 		)
 	set(BUILD_LIB_EXAMPLES OFF CACHE INTERNAL "")
 	set(BUILD_LIB_TESTS OFF CACHE INTERNAL "")
diff --git a/io.github.NhekoReborn.Nheko.yaml b/io.github.NhekoReborn.Nheko.yaml
index a1d96db7..4b0f7bf3 100644
--- a/io.github.NhekoReborn.Nheko.yaml
+++ b/io.github.NhekoReborn.Nheko.yaml
@@ -163,7 +163,7 @@ modules:
     buildsystem: cmake-ninja
     name: mtxclient
     sources:
-      - commit: ef741d7dceed11ccd46a553a4c886491aedc973b
+      - commit: b452a984b0fc522c21bb8df7d320bf13960974d0
         type: git
         url: https://github.com/Nheko-Reborn/mtxclient.git
   - config-opts:

From 22230ed0a904daa8aaf6a433141a0ef51286b6ac Mon Sep 17 00:00:00 2001
From: Nicolas Werner 
Date: Fri, 17 Sep 2021 12:02:54 +0200
Subject: [PATCH 153/232] Disable reuseItems again D:

---
 resources/qml/MessageView.qml | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/resources/qml/MessageView.qml b/resources/qml/MessageView.qml
index d80d274d..e6ac9662 100644
--- a/resources/qml/MessageView.qml
+++ b/resources/qml/MessageView.qml
@@ -25,8 +25,8 @@ ScrollView {
 
         model: room
         // reuseItems still has a few bugs, see https://bugreports.qt.io/browse/QTBUG-95105 https://bugreports.qt.io/browse/QTBUG-95107
-        onModelChanged: if (room) room.sendReset()
-        reuseItems: true
+        //onModelChanged: if (room) room.sendReset()
+        //reuseItems: true
         boundsBehavior: Flickable.StopAtBounds
         pixelAligned: true
         spacing: 4

From d499fffb7ee89d3a6290803be00ec9d664a75261 Mon Sep 17 00:00:00 2001
From: Patryk Cisek 
Date: Fri, 17 Sep 2021 03:12:56 -0700
Subject: [PATCH 154/232] Added a text field that allows choosing custom
 homeserver in "Room directory" dialog. (#727)

* Added a text field that allows choosing custom homeserver in "Room directory" dialog.

* Moved "Choose custom homeserver" text field to the right and shrinked it to 30% of "Room directory" dialog's width.

* Adding "server_name=" when needed when joining room.

When joining room that is hosted on a different homeserver than
the account is registered on, the request fails. In such scenario
the server has to be explicitly mentioned in a server_name URL
parameter. More info here:
https://matrix.org/docs/spec/client_server/r0.6.1#post-matrix-client-r0-join-roomidoralias

* Minor fix: intentation (4 spaces -> 8 spaces)

Co-authored-by: Patryk Cisek 
---
 resources/qml/RoomDirectory.qml | 11 +++++++++++
 src/RoomDirectoryModel.cpp      |  9 +++++++++
 2 files changed, 20 insertions(+)

diff --git a/resources/qml/RoomDirectory.qml b/resources/qml/RoomDirectory.qml
index b51c7bbc..54d405ff 100644
--- a/resources/qml/RoomDirectory.qml
+++ b/resources/qml/RoomDirectory.qml
@@ -192,6 +192,17 @@ ApplicationWindow {
             onTextChanged: searchTimer.restart()
         }
 
+        MatrixTextField {
+            id: chooseServer
+            Layout.minimumWidth: 0.3 * header.width
+            Layout.maximumWidth: 0.3 * header.width
+
+            padding: Nheko.paddingMedium
+            color: Nheko.colors.text
+            placeholderText: qsTr("Choose custom homeserver")
+            onTextChanged: publicRooms.setMatrixServer(text)
+        }
+
         Timer {
             id: searchTimer
 
diff --git a/src/RoomDirectoryModel.cpp b/src/RoomDirectoryModel.cpp
index de5d430a..cfa2b623 100644
--- a/src/RoomDirectoryModel.cpp
+++ b/src/RoomDirectoryModel.cpp
@@ -98,6 +98,15 @@ RoomDirectoryModel::getViasForRoom(const std::vector &aliases)
                        std::back_inserter(vias),
                        [](const auto &alias) { return alias.substr(alias.find(":") + 1); });
 
+        // When joining a room hosted on a homeserver other than the one the
+        // account has been registered on, the room's server has to be explicitly
+        // specified in the "server_name=..." URL parameter of the Matrix Join Room
+        // request. For more details consult the specs:
+        // https://matrix.org/docs/spec/client_server/r0.6.1#post-matrix-client-r0-join-roomidoralias
+        if (!server_.empty()) {
+                vias.push_back(server_);
+        }
+
         return vias;
 }
 

From cfca7157b98c9dc8e0852fe6484bc3f75008af7d Mon Sep 17 00:00:00 2001
From: Nicolas Werner 
Date: Sat, 18 Sep 2021 00:22:33 +0200
Subject: [PATCH 155/232] Change indentation to 4 spaces

---
 .clang-format                        |    6 +-
 src/AvatarProvider.cpp               |   69 +-
 src/AvatarProvider.h                 |    4 +-
 src/BlurhashProvider.cpp             |   56 +-
 src/BlurhashProvider.h               |   48 +-
 src/Cache.cpp                        | 6408 +++++++++++++-------------
 src/CacheCryptoStructs.h             |  152 +-
 src/CacheStructs.h                   |   86 +-
 src/Cache_p.h                        | 1172 +++--
 src/CallDevices.cpp                  |  441 +-
 src/CallDevices.h                    |   41 +-
 src/CallManager.cpp                  |  874 ++--
 src/CallManager.h                    |  151 +-
 src/ChatPage.cpp                     | 1925 ++++----
 src/ChatPage.h                       |  257 +-
 src/Clipboard.cpp                    |    7 +-
 src/Clipboard.h                      |   12 +-
 src/ColorImageProvider.cpp           |   26 +-
 src/ColorImageProvider.h             |    8 +-
 src/CombinedImagePackModel.cpp       |   84 +-
 src/CombinedImagePackModel.h         |   54 +-
 src/CompletionModelRoles.h           |    6 +-
 src/CompletionProxyModel.cpp         |  176 +-
 src/CompletionProxyModel.h           |  281 +-
 src/DeviceVerificationFlow.cpp       | 1243 +++--
 src/DeviceVerificationFlow.h         |  343 +-
 src/EventAccessors.cpp               |  489 +-
 src/EventAccessors.h                 |   14 +-
 src/ImagePackListModel.cpp           |   91 +-
 src/ImagePackListModel.h             |   40 +-
 src/InviteesModel.cpp                |   75 +-
 src/InviteesModel.h                  |   54 +-
 src/JdenticonProvider.cpp            |   96 +-
 src/JdenticonProvider.h              |   71 +-
 src/Logging.cpp                      |  103 +-
 src/LoginPage.cpp                    |  699 ++-
 src/LoginPage.h                      |  128 +-
 src/MainWindow.cpp                   |  474 +-
 src/MainWindow.h                     |  131 +-
 src/MatrixClient.cpp                 |   30 +-
 src/MemberList.cpp                   |  122 +-
 src/MemberList.h                     |   84 +-
 src/MxcImageProvider.cpp             |  414 +-
 src/MxcImageProvider.h               |   58 +-
 src/Olm.cpp                          | 2710 +++++------
 src/Olm.h                            |   32 +-
 src/ReadReceiptsModel.cpp            |  141 +-
 src/ReadReceiptsModel.h              |   68 +-
 src/RegisterPage.cpp                 |  705 ++-
 src/RegisterPage.h                   |  102 +-
 src/RoomDirectoryModel.cpp           |  252 +-
 src/RoomDirectoryModel.h             |   94 +-
 src/RoomsModel.cpp                   |  102 +-
 src/RoomsModel.h                     |   38 +-
 src/SSOHandler.cpp                   |   58 +-
 src/SSOHandler.h                     |   16 +-
 src/SingleImagePackModel.cpp         |  470 +-
 src/SingleImagePackModel.h           |  119 +-
 src/TrayIcon.cpp                     |  134 +-
 src/TrayIcon.h                       |   28 +-
 src/UserSettingsPage.cpp             | 2191 +++++----
 src/UserSettingsPage.h               |  703 ++-
 src/UsersModel.cpp                   |   72 +-
 src/UsersModel.h                     |   36 +-
 src/Utils.cpp                        | 1067 +++--
 src/Utils.h                          |  230 +-
 src/WebRTCSession.cpp                | 1564 +++----
 src/WebRTCSession.h                  |  130 +-
 src/WelcomePage.cpp                  |   98 +-
 src/WelcomePage.h                    |   14 +-
 src/dialogs/CreateRoom.cpp           |  202 +-
 src/dialogs/CreateRoom.h             |   28 +-
 src/dialogs/FallbackAuth.cpp         |   84 +-
 src/dialogs/FallbackAuth.h           |   14 +-
 src/dialogs/ImageOverlay.cpp         |  105 +-
 src/dialogs/ImageOverlay.h           |   24 +-
 src/dialogs/JoinRoom.cpp             |   66 +-
 src/dialogs/JoinRoom.h               |   16 +-
 src/dialogs/LeaveRoom.cpp            |   54 +-
 src/dialogs/LeaveRoom.h              |   10 +-
 src/dialogs/Logout.cpp               |   56 +-
 src/dialogs/Logout.h                 |   10 +-
 src/dialogs/PreviewUploadOverlay.cpp |  253 +-
 src/dialogs/PreviewUploadOverlay.h   |   40 +-
 src/dialogs/ReCaptcha.cpp            |   80 +-
 src/dialogs/ReCaptcha.h              |   14 +-
 src/emoji/EmojiModel.cpp             |   69 +-
 src/emoji/EmojiModel.h               |   26 +-
 src/emoji/MacHelper.h                |    4 +-
 src/emoji/Provider.h                 |   44 +-
 src/main.cpp                         |  391 +-
 src/notifications/Manager.cpp        |   48 +-
 src/notifications/Manager.h          |   82 +-
 src/notifications/ManagerLinux.cpp   |  357 +-
 src/notifications/ManagerMac.cpp     |   66 +-
 src/notifications/ManagerWin.cpp     |   88 +-
 src/timeline/CommunitiesModel.cpp    |  365 +-
 src/timeline/CommunitiesModel.h      |  102 +-
 src/timeline/DelegateChooser.cpp     |  113 +-
 src/timeline/DelegateChooser.h       |   88 +-
 src/timeline/EventStore.cpp          | 1347 +++---
 src/timeline/EventStore.h            |  220 +-
 src/timeline/InputBar.cpp            | 1142 +++--
 src/timeline/InputBar.h              |  162 +-
 src/timeline/Permissions.cpp         |   34 +-
 src/timeline/Permissions.h           |   24 +-
 src/timeline/Reaction.h              |   26 +-
 src/timeline/RoomlistModel.cpp       | 1567 +++----
 src/timeline/RoomlistModel.h         |  300 +-
 src/timeline/TimelineModel.cpp       | 2973 ++++++------
 src/timeline/TimelineModel.h         |  691 ++-
 src/timeline/TimelineViewManager.cpp |  925 ++--
 src/timeline/TimelineViewManager.h   |  164 +-
 src/ui/Badge.cpp                     |  168 +-
 src/ui/Badge.h                       |   72 +-
 src/ui/DropShadow.cpp                |  155 +-
 src/ui/DropShadow.h                  |   20 +-
 src/ui/FlatButton.cpp                |  659 ++-
 src/ui/FlatButton.h                  |  231 +-
 src/ui/FloatingButton.cpp            |  106 +-
 src/ui/FloatingButton.h              |   14 +-
 src/ui/InfoMessage.cpp               |   58 +-
 src/ui/InfoMessage.h                 |   52 +-
 src/ui/Label.cpp                     |   14 +-
 src/ui/Label.h                       |   22 +-
 src/ui/LoadingIndicator.cpp          |   64 +-
 src/ui/LoadingIndicator.h            |   30 +-
 src/ui/Menu.h                        |   16 +-
 src/ui/MxcAnimatedImage.cpp          |  267 +-
 src/ui/MxcAnimatedImage.h            |  124 +-
 src/ui/MxcMediaProxy.cpp             |  193 +-
 src/ui/MxcMediaProxy.h               |   95 +-
 src/ui/NhekoCursorShape.cpp          |   12 +-
 src/ui/NhekoCursorShape.h            |   16 +-
 src/ui/NhekoDropArea.cpp             |   16 +-
 src/ui/NhekoDropArea.h               |   30 +-
 src/ui/NhekoGlobalObject.cpp         |   58 +-
 src/ui/NhekoGlobalObject.h           |   66 +-
 src/ui/OverlayModal.cpp              |   46 +-
 src/ui/OverlayModal.h                |   26 +-
 src/ui/OverlayWidget.cpp             |   80 +-
 src/ui/OverlayWidget.h               |   12 +-
 src/ui/Painter.h                     |  218 +-
 src/ui/RaisedButton.cpp              |   96 +-
 src/ui/RaisedButton.h                |   20 +-
 src/ui/Ripple.cpp                    |   72 +-
 src/ui/Ripple.h                      |   98 +-
 src/ui/RippleOverlay.cpp             |   46 +-
 src/ui/RippleOverlay.h               |   38 +-
 src/ui/RoomSettings.cpp              |  797 ++--
 src/ui/RoomSettings.h                |  164 +-
 src/ui/SnackBar.cpp                  |  128 +-
 src/ui/SnackBar.h                    |   94 +-
 src/ui/TextField.cpp                 |  390 +-
 src/ui/TextField.h                   |  162 +-
 src/ui/TextLabel.cpp                 |  116 +-
 src/ui/TextLabel.h                   |   40 +-
 src/ui/Theme.cpp                     |  116 +-
 src/ui/Theme.h                       |   56 +-
 src/ui/ThemeManager.cpp              |   46 +-
 src/ui/ThemeManager.h                |   16 +-
 src/ui/ToggleButton.cpp              |  182 +-
 src/ui/ToggleButton.h                |  110 +-
 src/ui/UserProfile.cpp               |  454 +-
 src/ui/UserProfile.h                 |  189 +-
 165 files changed, 23146 insertions(+), 23975 deletions(-)

diff --git a/.clang-format b/.clang-format
index 059aee19..b5e2f017 100644
--- a/.clang-format
+++ b/.clang-format
@@ -1,14 +1,14 @@
 ---
 Language: Cpp
-Standard: Cpp11
-AccessModifierOffset: -8
+Standard: c++17
+AccessModifierOffset: -4
 AlignAfterOpenBracket: Align
 AlignConsecutiveAssignments: true
 AllowShortFunctionsOnASingleLine: true
 BasedOnStyle: Mozilla
 ColumnLimit: 100
 IndentCaseLabels: false
-IndentWidth: 8
+IndentWidth: 4
 KeepEmptyLinesAtTheStartOfBlocks: false
 PointerAlignment: Right
 Cpp11BracedListStyle: true
diff --git a/src/AvatarProvider.cpp b/src/AvatarProvider.cpp
index b9962cef..177bf903 100644
--- a/src/AvatarProvider.cpp
+++ b/src/AvatarProvider.cpp
@@ -22,45 +22,44 @@ namespace AvatarProvider {
 void
 resolve(QString avatarUrl, int size, QObject *receiver, AvatarCallback callback)
 {
-        const auto cacheKey = QString("%1_size_%2").arg(avatarUrl).arg(size);
+    const auto cacheKey = QString("%1_size_%2").arg(avatarUrl).arg(size);
 
-        QPixmap pixmap;
-        if (avatarUrl.isEmpty()) {
-                callback(pixmap);
-                return;
-        }
+    QPixmap pixmap;
+    if (avatarUrl.isEmpty()) {
+        callback(pixmap);
+        return;
+    }
 
-        if (avatar_cache.find(cacheKey, &pixmap)) {
-                callback(pixmap);
-                return;
-        }
+    if (avatar_cache.find(cacheKey, &pixmap)) {
+        callback(pixmap);
+        return;
+    }
 
-        MxcImageProvider::download(avatarUrl.remove(QStringLiteral("mxc://")),
-                                   QSize(size, size),
-                                   [callback, cacheKey, recv = QPointer(receiver)](
-                                     QString, QSize, QImage img, QString) {
-                                           if (!recv)
-                                                   return;
+    MxcImageProvider::download(avatarUrl.remove(QStringLiteral("mxc://")),
+                               QSize(size, size),
+                               [callback, cacheKey, recv = QPointer(receiver)](
+                                 QString, QSize, QImage img, QString) {
+                                   if (!recv)
+                                       return;
 
-                                           auto proxy = std::make_shared();
-                                           QObject::connect(proxy.get(),
-                                                            &AvatarProxy::avatarDownloaded,
-                                                            recv,
-                                                            [callback, cacheKey](QPixmap pm) {
-                                                                    if (!pm.isNull())
-                                                                            avatar_cache.insert(
-                                                                              cacheKey, pm);
-                                                                    callback(pm);
-                                                            });
+                                   auto proxy = std::make_shared();
+                                   QObject::connect(proxy.get(),
+                                                    &AvatarProxy::avatarDownloaded,
+                                                    recv,
+                                                    [callback, cacheKey](QPixmap pm) {
+                                                        if (!pm.isNull())
+                                                            avatar_cache.insert(cacheKey, pm);
+                                                        callback(pm);
+                                                    });
 
-                                           if (img.isNull()) {
-                                                   emit proxy->avatarDownloaded(QPixmap{});
-                                                   return;
-                                           }
+                                   if (img.isNull()) {
+                                       emit proxy->avatarDownloaded(QPixmap{});
+                                       return;
+                                   }
 
-                                           auto pm = QPixmap::fromImage(std::move(img));
-                                           emit proxy->avatarDownloaded(pm);
-                                   });
+                                   auto pm = QPixmap::fromImage(std::move(img));
+                                   emit proxy->avatarDownloaded(pm);
+                               });
 }
 
 void
@@ -70,8 +69,8 @@ resolve(const QString &room_id,
         QObject *receiver,
         AvatarCallback callback)
 {
-        auto avatarUrl = cache::avatarUrl(room_id, user_id);
+    auto avatarUrl = cache::avatarUrl(room_id, user_id);
 
-        resolve(std::move(avatarUrl), size, receiver, callback);
+    resolve(std::move(avatarUrl), size, receiver, callback);
 }
 }
diff --git a/src/AvatarProvider.h b/src/AvatarProvider.h
index 173a2fba..efd0f330 100644
--- a/src/AvatarProvider.h
+++ b/src/AvatarProvider.h
@@ -12,10 +12,10 @@ using AvatarCallback = std::function;
 
 class AvatarProxy : public QObject
 {
-        Q_OBJECT
+    Q_OBJECT
 
 signals:
-        void avatarDownloaded(QPixmap pm);
+    void avatarDownloaded(QPixmap pm);
 };
 
 namespace AvatarProvider {
diff --git a/src/BlurhashProvider.cpp b/src/BlurhashProvider.cpp
index aef618a2..e905474a 100644
--- a/src/BlurhashProvider.cpp
+++ b/src/BlurhashProvider.cpp
@@ -13,33 +13,33 @@
 void
 BlurhashResponse::run()
 {
-        if (m_requestedSize.width() < 0 || m_requestedSize.height() < 0) {
-                m_error = QStringLiteral("Blurhash needs size request");
-                emit finished();
-                return;
-        }
-        if (m_requestedSize.width() == 0 || m_requestedSize.height() == 0) {
-                m_image = QImage(m_requestedSize, QImage::Format_RGB32);
-                m_image.fill(QColor(0, 0, 0));
-                emit finished();
-                return;
-        }
-
-        auto decoded = blurhash::decode(QUrl::fromPercentEncoding(m_id.toUtf8()).toStdString(),
-                                        m_requestedSize.width(),
-                                        m_requestedSize.height());
-        if (decoded.image.empty()) {
-                m_error = QStringLiteral("Failed decode!");
-                emit finished();
-                return;
-        }
-
-        QImage image(decoded.image.data(),
-                     (int)decoded.width,
-                     (int)decoded.height,
-                     (int)decoded.width * 3,
-                     QImage::Format_RGB888);
-
-        m_image = image.copy();
+    if (m_requestedSize.width() < 0 || m_requestedSize.height() < 0) {
+        m_error = QStringLiteral("Blurhash needs size request");
         emit finished();
+        return;
+    }
+    if (m_requestedSize.width() == 0 || m_requestedSize.height() == 0) {
+        m_image = QImage(m_requestedSize, QImage::Format_RGB32);
+        m_image.fill(QColor(0, 0, 0));
+        emit finished();
+        return;
+    }
+
+    auto decoded = blurhash::decode(QUrl::fromPercentEncoding(m_id.toUtf8()).toStdString(),
+                                    m_requestedSize.width(),
+                                    m_requestedSize.height());
+    if (decoded.image.empty()) {
+        m_error = QStringLiteral("Failed decode!");
+        emit finished();
+        return;
+    }
+
+    QImage image(decoded.image.data(),
+                 (int)decoded.width,
+                 (int)decoded.height,
+                 (int)decoded.width * 3,
+                 QImage::Format_RGB888);
+
+    m_image = image.copy();
+    emit finished();
 }
diff --git a/src/BlurhashProvider.h b/src/BlurhashProvider.h
index ee89302c..1c8351f2 100644
--- a/src/BlurhashProvider.h
+++ b/src/BlurhashProvider.h
@@ -15,41 +15,41 @@ class BlurhashResponse
   , public QRunnable
 {
 public:
-        BlurhashResponse(const QString &id, const QSize &requestedSize)
+    BlurhashResponse(const QString &id, const QSize &requestedSize)
 
-          : m_id(id)
-          , m_requestedSize(requestedSize)
-        {
-                setAutoDelete(false);
-        }
+      : m_id(id)
+      , m_requestedSize(requestedSize)
+    {
+        setAutoDelete(false);
+    }
 
-        QQuickTextureFactory *textureFactory() const override
-        {
-                return QQuickTextureFactory::textureFactoryForImage(m_image);
-        }
-        QString errorString() const override { return m_error; }
+    QQuickTextureFactory *textureFactory() const override
+    {
+        return QQuickTextureFactory::textureFactoryForImage(m_image);
+    }
+    QString errorString() const override { return m_error; }
 
-        void run() override;
+    void run() override;
 
-        QString m_id, m_error;
-        QSize m_requestedSize;
-        QImage m_image;
+    QString m_id, m_error;
+    QSize m_requestedSize;
+    QImage m_image;
 };
 
 class BlurhashProvider
   : public QObject
   , public QQuickAsyncImageProvider
 {
-        Q_OBJECT
+    Q_OBJECT
 public slots:
-        QQuickImageResponse *requestImageResponse(const QString &id,
-                                                  const QSize &requestedSize) override
-        {
-                BlurhashResponse *response = new BlurhashResponse(id, requestedSize);
-                pool.start(response);
-                return response;
-        }
+    QQuickImageResponse *requestImageResponse(const QString &id,
+                                              const QSize &requestedSize) override
+    {
+        BlurhashResponse *response = new BlurhashResponse(id, requestedSize);
+        pool.start(response);
+        return response;
+    }
 
 private:
-        QThreadPool pool;
+    QThreadPool pool;
 };
diff --git a/src/Cache.cpp b/src/Cache.cpp
index 9ebc61b9..b124fe5e 100644
--- a/src/Cache.cpp
+++ b/src/Cache.cpp
@@ -97,55 +97,55 @@ std::unique_ptr instance_ = nullptr;
 
 struct RO_txn
 {
-        ~RO_txn() { txn.reset(); }
-        operator MDB_txn *() const noexcept { return txn.handle(); }
-        operator lmdb::txn &() noexcept { return txn; }
+    ~RO_txn() { txn.reset(); }
+    operator MDB_txn *() const noexcept { return txn.handle(); }
+    operator lmdb::txn &() noexcept { return txn; }
 
-        lmdb::txn &txn;
+    lmdb::txn &txn;
 };
 
 RO_txn
 ro_txn(lmdb::env &env)
 {
-        thread_local lmdb::txn txn     = lmdb::txn::begin(env, nullptr, MDB_RDONLY);
-        thread_local int reuse_counter = 0;
+    thread_local lmdb::txn txn     = lmdb::txn::begin(env, nullptr, MDB_RDONLY);
+    thread_local int reuse_counter = 0;
 
-        if (reuse_counter >= 100 || txn.env() != env.handle()) {
-                txn.abort();
-                txn           = lmdb::txn::begin(env, nullptr, MDB_RDONLY);
-                reuse_counter = 0;
-        } else if (reuse_counter > 0) {
-                try {
-                        txn.renew();
-                } catch (...) {
-                        txn.abort();
-                        txn           = lmdb::txn::begin(env, nullptr, MDB_RDONLY);
-                        reuse_counter = 0;
-                }
+    if (reuse_counter >= 100 || txn.env() != env.handle()) {
+        txn.abort();
+        txn           = lmdb::txn::begin(env, nullptr, MDB_RDONLY);
+        reuse_counter = 0;
+    } else if (reuse_counter > 0) {
+        try {
+            txn.renew();
+        } catch (...) {
+            txn.abort();
+            txn           = lmdb::txn::begin(env, nullptr, MDB_RDONLY);
+            reuse_counter = 0;
         }
-        reuse_counter++;
+    }
+    reuse_counter++;
 
-        return RO_txn{txn};
+    return RO_txn{txn};
 }
 
 template
 bool
 containsStateUpdates(const T &e)
 {
-        return std::visit([](const auto &ev) { return Cache::isStateEvent_; }, e);
+    return std::visit([](const auto &ev) { return Cache::isStateEvent_; }, e);
 }
 
 bool
 containsStateUpdates(const mtx::events::collections::StrippedEvents &e)
 {
-        using namespace mtx::events;
-        using namespace mtx::events::state;
+    using namespace mtx::events;
+    using namespace mtx::events::state;
 
-        return std::holds_alternative>(e) ||
-               std::holds_alternative>(e) ||
-               std::holds_alternative>(e) ||
-               std::holds_alternative>(e) ||
-               std::holds_alternative>(e);
+    return std::holds_alternative>(e) ||
+           std::holds_alternative>(e) ||
+           std::holds_alternative>(e) ||
+           std::holds_alternative>(e) ||
+           std::holds_alternative>(e);
 }
 
 bool
@@ -153,45 +153,45 @@ Cache::isHiddenEvent(lmdb::txn &txn,
                      mtx::events::collections::TimelineEvents e,
                      const std::string &room_id)
 {
-        using namespace mtx::events;
+    using namespace mtx::events;
 
-        // Always hide edits
-        if (mtx::accessors::relations(e).replaces())
-                return true;
+    // Always hide edits
+    if (mtx::accessors::relations(e).replaces())
+        return true;
 
-        if (auto encryptedEvent = std::get_if>(&e)) {
-                MegolmSessionIndex index;
-                index.room_id    = room_id;
-                index.session_id = encryptedEvent->content.session_id;
-                index.sender_key = encryptedEvent->content.sender_key;
+    if (auto encryptedEvent = std::get_if>(&e)) {
+        MegolmSessionIndex index;
+        index.room_id    = room_id;
+        index.session_id = encryptedEvent->content.session_id;
+        index.sender_key = encryptedEvent->content.sender_key;
 
-                auto result = olm::decryptEvent(index, *encryptedEvent, true);
-                if (!result.error)
-                        e = result.event.value();
-        }
+        auto result = olm::decryptEvent(index, *encryptedEvent, true);
+        if (!result.error)
+            e = result.event.value();
+    }
 
-        mtx::events::account_data::nheko_extensions::HiddenEvents hiddenEvents;
-        hiddenEvents.hidden_event_types = {
-          EventType::Reaction, EventType::CallCandidates, EventType::Unsupported};
+    mtx::events::account_data::nheko_extensions::HiddenEvents hiddenEvents;
+    hiddenEvents.hidden_event_types = {
+      EventType::Reaction, EventType::CallCandidates, EventType::Unsupported};
 
-        if (auto temp = getAccountData(txn, mtx::events::EventType::NhekoHiddenEvents, ""))
-                hiddenEvents =
-                  std::move(std::get>(*temp)
-                              .content);
-        if (auto temp = getAccountData(txn, mtx::events::EventType::NhekoHiddenEvents, room_id))
-                hiddenEvents =
-                  std::move(std::get>(*temp)
-                              .content);
+    if (auto temp = getAccountData(txn, mtx::events::EventType::NhekoHiddenEvents, ""))
+        hiddenEvents =
+          std::move(std::get>(*temp)
+                      .content);
+    if (auto temp = getAccountData(txn, mtx::events::EventType::NhekoHiddenEvents, room_id))
+        hiddenEvents =
+          std::move(std::get>(*temp)
+                      .content);
 
-        return std::visit(
-          [hiddenEvents](const auto &ev) {
-                  return std::any_of(hiddenEvents.hidden_event_types.begin(),
-                                     hiddenEvents.hidden_event_types.end(),
-                                     [ev](EventType type) { return type == ev.type; });
-          },
-          e);
+    return std::visit(
+      [hiddenEvents](const auto &ev) {
+          return std::any_of(hiddenEvents.hidden_event_types.begin(),
+                             hiddenEvents.hidden_event_types.end(),
+                             [ev](EventType type) { return type == ev.type; });
+      },
+      e);
 }
 
 Cache::Cache(const QString &userId, QObject *parent)
@@ -199,218 +199,210 @@ Cache::Cache(const QString &userId, QObject *parent)
   , env_{nullptr}
   , localUserId_{userId}
 {
-        setup();
-        connect(this, &Cache::userKeysUpdate, this, &Cache::updateUserKeys, Qt::QueuedConnection);
+    setup();
+    connect(this, &Cache::userKeysUpdate, this, &Cache::updateUserKeys, Qt::QueuedConnection);
 }
 
 void
 Cache::setup()
 {
-        auto settings = UserSettings::instance();
+    auto settings = UserSettings::instance();
 
-        nhlog::db()->debug("setting up cache");
+    nhlog::db()->debug("setting up cache");
 
-        // Previous location of the cache directory
-        auto oldCache = QString("%1/%2%3")
-                          .arg(QStandardPaths::writableLocation(QStandardPaths::CacheLocation))
-                          .arg(QString::fromUtf8(localUserId_.toUtf8().toHex()))
-                          .arg(QString::fromUtf8(settings->profile().toUtf8().toHex()));
+    // Previous location of the cache directory
+    auto oldCache = QString("%1/%2%3")
+                      .arg(QStandardPaths::writableLocation(QStandardPaths::CacheLocation))
+                      .arg(QString::fromUtf8(localUserId_.toUtf8().toHex()))
+                      .arg(QString::fromUtf8(settings->profile().toUtf8().toHex()));
 
-        cacheDirectory_ = QString("%1/%2%3")
-                            .arg(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation))
-                            .arg(QString::fromUtf8(localUserId_.toUtf8().toHex()))
-                            .arg(QString::fromUtf8(settings->profile().toUtf8().toHex()));
+    cacheDirectory_ = QString("%1/%2%3")
+                        .arg(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation))
+                        .arg(QString::fromUtf8(localUserId_.toUtf8().toHex()))
+                        .arg(QString::fromUtf8(settings->profile().toUtf8().toHex()));
 
-        bool isInitial = !QFile::exists(cacheDirectory_);
+    bool isInitial = !QFile::exists(cacheDirectory_);
 
-        // NOTE: If both cache directories exist it's better to do nothing: it
-        // could mean a previous migration failed or was interrupted.
-        bool needsMigration = isInitial && QFile::exists(oldCache);
+    // NOTE: If both cache directories exist it's better to do nothing: it
+    // could mean a previous migration failed or was interrupted.
+    bool needsMigration = isInitial && QFile::exists(oldCache);
 
-        if (needsMigration) {
-                nhlog::db()->info("found old state directory, migrating");
-                if (!QDir().rename(oldCache, cacheDirectory_)) {
-                        throw std::runtime_error(("Unable to migrate the old state directory (" +
-                                                  oldCache + ") to the new location (" +
-                                                  cacheDirectory_ + ")")
-                                                   .toStdString()
-                                                   .c_str());
-                }
-                nhlog::db()->info("completed state migration");
+    if (needsMigration) {
+        nhlog::db()->info("found old state directory, migrating");
+        if (!QDir().rename(oldCache, cacheDirectory_)) {
+            throw std::runtime_error(("Unable to migrate the old state directory (" + oldCache +
+                                      ") to the new location (" + cacheDirectory_ + ")")
+                                       .toStdString()
+                                       .c_str());
+        }
+        nhlog::db()->info("completed state migration");
+    }
+
+    env_ = lmdb::env::create();
+    env_.set_mapsize(DB_SIZE);
+    env_.set_max_dbs(MAX_DBS);
+
+    if (isInitial) {
+        nhlog::db()->info("initializing LMDB");
+
+        if (!QDir().mkpath(cacheDirectory_)) {
+            throw std::runtime_error(
+              ("Unable to create state directory:" + cacheDirectory_).toStdString().c_str());
+        }
+    }
+
+    try {
+        // NOTE(Nico): We may want to use (MDB_MAPASYNC | MDB_WRITEMAP) in the future, but
+        // it can really mess up our database, so we shouldn't. For now, hopefully
+        // NOMETASYNC is fast enough.
+        env_.open(cacheDirectory_.toStdString().c_str(), MDB_NOMETASYNC | MDB_NOSYNC);
+    } catch (const lmdb::error &e) {
+        if (e.code() != MDB_VERSION_MISMATCH && e.code() != MDB_INVALID) {
+            throw std::runtime_error("LMDB initialization failed" + std::string(e.what()));
         }
 
-        env_ = lmdb::env::create();
-        env_.set_mapsize(DB_SIZE);
-        env_.set_max_dbs(MAX_DBS);
+        nhlog::db()->warn("resetting cache due to LMDB version mismatch: {}", e.what());
 
-        if (isInitial) {
-                nhlog::db()->info("initializing LMDB");
+        QDir stateDir(cacheDirectory_);
 
-                if (!QDir().mkpath(cacheDirectory_)) {
-                        throw std::runtime_error(
-                          ("Unable to create state directory:" + cacheDirectory_)
-                            .toStdString()
-                            .c_str());
-                }
+        for (const auto &file : stateDir.entryList(QDir::NoDotAndDotDot)) {
+            if (!stateDir.remove(file))
+                throw std::runtime_error(("Unable to delete file " + file).toStdString().c_str());
         }
+        env_.open(cacheDirectory_.toStdString().c_str());
+    }
 
-        try {
-                // NOTE(Nico): We may want to use (MDB_MAPASYNC | MDB_WRITEMAP) in the future, but
-                // it can really mess up our database, so we shouldn't. For now, hopefully
-                // NOMETASYNC is fast enough.
-                env_.open(cacheDirectory_.toStdString().c_str(), MDB_NOMETASYNC | MDB_NOSYNC);
-        } catch (const lmdb::error &e) {
-                if (e.code() != MDB_VERSION_MISMATCH && e.code() != MDB_INVALID) {
-                        throw std::runtime_error("LMDB initialization failed" +
-                                                 std::string(e.what()));
-                }
+    auto txn          = lmdb::txn::begin(env_);
+    syncStateDb_      = lmdb::dbi::open(txn, SYNC_STATE_DB, MDB_CREATE);
+    roomsDb_          = lmdb::dbi::open(txn, ROOMS_DB, MDB_CREATE);
+    spacesChildrenDb_ = lmdb::dbi::open(txn, SPACES_CHILDREN_DB, MDB_CREATE | MDB_DUPSORT);
+    spacesParentsDb_  = lmdb::dbi::open(txn, SPACES_PARENTS_DB, MDB_CREATE | MDB_DUPSORT);
+    invitesDb_        = lmdb::dbi::open(txn, INVITES_DB, MDB_CREATE);
+    readReceiptsDb_   = lmdb::dbi::open(txn, READ_RECEIPTS_DB, MDB_CREATE);
+    notificationsDb_  = lmdb::dbi::open(txn, NOTIFICATIONS_DB, MDB_CREATE);
 
-                nhlog::db()->warn("resetting cache due to LMDB version mismatch: {}", e.what());
+    // Device management
+    devicesDb_    = lmdb::dbi::open(txn, DEVICES_DB, MDB_CREATE);
+    deviceKeysDb_ = lmdb::dbi::open(txn, DEVICE_KEYS_DB, MDB_CREATE);
 
-                QDir stateDir(cacheDirectory_);
+    // Session management
+    inboundMegolmSessionDb_  = lmdb::dbi::open(txn, INBOUND_MEGOLM_SESSIONS_DB, MDB_CREATE);
+    outboundMegolmSessionDb_ = lmdb::dbi::open(txn, OUTBOUND_MEGOLM_SESSIONS_DB, MDB_CREATE);
+    megolmSessionDataDb_     = lmdb::dbi::open(txn, MEGOLM_SESSIONS_DATA_DB, MDB_CREATE);
 
-                for (const auto &file : stateDir.entryList(QDir::NoDotAndDotDot)) {
-                        if (!stateDir.remove(file))
-                                throw std::runtime_error(
-                                  ("Unable to delete file " + file).toStdString().c_str());
-                }
-                env_.open(cacheDirectory_.toStdString().c_str());
-        }
+    // What rooms are encrypted
+    encryptedRooms_                      = lmdb::dbi::open(txn, ENCRYPTED_ROOMS_DB, MDB_CREATE);
+    [[maybe_unused]] auto verificationDb = getVerificationDb(txn);
+    [[maybe_unused]] auto userKeysDb     = getUserKeysDb(txn);
 
-        auto txn          = lmdb::txn::begin(env_);
-        syncStateDb_      = lmdb::dbi::open(txn, SYNC_STATE_DB, MDB_CREATE);
-        roomsDb_          = lmdb::dbi::open(txn, ROOMS_DB, MDB_CREATE);
-        spacesChildrenDb_ = lmdb::dbi::open(txn, SPACES_CHILDREN_DB, MDB_CREATE | MDB_DUPSORT);
-        spacesParentsDb_  = lmdb::dbi::open(txn, SPACES_PARENTS_DB, MDB_CREATE | MDB_DUPSORT);
-        invitesDb_        = lmdb::dbi::open(txn, INVITES_DB, MDB_CREATE);
-        readReceiptsDb_   = lmdb::dbi::open(txn, READ_RECEIPTS_DB, MDB_CREATE);
-        notificationsDb_  = lmdb::dbi::open(txn, NOTIFICATIONS_DB, MDB_CREATE);
+    txn.commit();
 
-        // Device management
-        devicesDb_    = lmdb::dbi::open(txn, DEVICES_DB, MDB_CREATE);
-        deviceKeysDb_ = lmdb::dbi::open(txn, DEVICE_KEYS_DB, MDB_CREATE);
-
-        // Session management
-        inboundMegolmSessionDb_  = lmdb::dbi::open(txn, INBOUND_MEGOLM_SESSIONS_DB, MDB_CREATE);
-        outboundMegolmSessionDb_ = lmdb::dbi::open(txn, OUTBOUND_MEGOLM_SESSIONS_DB, MDB_CREATE);
-        megolmSessionDataDb_     = lmdb::dbi::open(txn, MEGOLM_SESSIONS_DATA_DB, MDB_CREATE);
-
-        // What rooms are encrypted
-        encryptedRooms_                      = lmdb::dbi::open(txn, ENCRYPTED_ROOMS_DB, MDB_CREATE);
-        [[maybe_unused]] auto verificationDb = getVerificationDb(txn);
-        [[maybe_unused]] auto userKeysDb     = getUserKeysDb(txn);
-
-        txn.commit();
-
-        databaseReady_ = true;
+    databaseReady_ = true;
 }
 
 void
 Cache::setEncryptedRoom(lmdb::txn &txn, const std::string &room_id)
 {
-        nhlog::db()->info("mark room {} as encrypted", room_id);
+    nhlog::db()->info("mark room {} as encrypted", room_id);
 
-        encryptedRooms_.put(txn, room_id, "0");
+    encryptedRooms_.put(txn, room_id, "0");
 }
 
 bool
 Cache::isRoomEncrypted(const std::string &room_id)
 {
-        std::string_view unused;
+    std::string_view unused;
 
-        auto txn = ro_txn(env_);
-        auto res = encryptedRooms_.get(txn, room_id, unused);
+    auto txn = ro_txn(env_);
+    auto res = encryptedRooms_.get(txn, room_id, unused);
 
-        return res;
+    return res;
 }
 
 std::optional
 Cache::roomEncryptionSettings(const std::string &room_id)
 {
-        using namespace mtx::events;
-        using namespace mtx::events::state;
+    using namespace mtx::events;
+    using namespace mtx::events::state;
 
-        try {
-                auto txn      = ro_txn(env_);
-                auto statesdb = getStatesDb(txn, room_id);
-                std::string_view event;
-                bool res =
-                  statesdb.get(txn, to_string(mtx::events::EventType::RoomEncryption), event);
+    try {
+        auto txn      = ro_txn(env_);
+        auto statesdb = getStatesDb(txn, room_id);
+        std::string_view event;
+        bool res = statesdb.get(txn, to_string(mtx::events::EventType::RoomEncryption), event);
 
-                if (res) {
-                        try {
-                                StateEvent msg = json::parse(event);
+        if (res) {
+            try {
+                StateEvent msg = json::parse(event);
 
-                                return msg.content;
-                        } catch (const json::exception &e) {
-                                nhlog::db()->warn("failed to parse m.room.encryption event: {}",
-                                                  e.what());
-                                return Encryption{};
-                        }
-                }
-        } catch (lmdb::error &) {
+                return msg.content;
+            } catch (const json::exception &e) {
+                nhlog::db()->warn("failed to parse m.room.encryption event: {}", e.what());
+                return Encryption{};
+            }
         }
+    } catch (lmdb::error &) {
+    }
 
-        return std::nullopt;
+    return std::nullopt;
 }
 
 mtx::crypto::ExportedSessionKeys
 Cache::exportSessionKeys()
 {
-        using namespace mtx::crypto;
+    using namespace mtx::crypto;
 
-        ExportedSessionKeys keys;
+    ExportedSessionKeys keys;
 
-        auto txn    = ro_txn(env_);
-        auto cursor = lmdb::cursor::open(txn, inboundMegolmSessionDb_);
+    auto txn    = ro_txn(env_);
+    auto cursor = lmdb::cursor::open(txn, inboundMegolmSessionDb_);
 
-        std::string_view key, value;
-        while (cursor.get(key, value, MDB_NEXT)) {
-                ExportedSession exported;
-                MegolmSessionIndex index;
+    std::string_view key, value;
+    while (cursor.get(key, value, MDB_NEXT)) {
+        ExportedSession exported;
+        MegolmSessionIndex index;
 
-                auto saved_session =
-                  unpickle(std::string(value), pickle_secret_);
+        auto saved_session = unpickle(std::string(value), pickle_secret_);
 
-                try {
-                        index = nlohmann::json::parse(key).get();
-                } catch (const nlohmann::json::exception &e) {
-                        nhlog::db()->critical("failed to export megolm session: {}", e.what());
-                        continue;
-                }
-
-                exported.room_id     = index.room_id;
-                exported.sender_key  = index.sender_key;
-                exported.session_id  = index.session_id;
-                exported.session_key = export_session(saved_session.get(), -1);
-
-                keys.sessions.push_back(exported);
+        try {
+            index = nlohmann::json::parse(key).get();
+        } catch (const nlohmann::json::exception &e) {
+            nhlog::db()->critical("failed to export megolm session: {}", e.what());
+            continue;
         }
 
-        cursor.close();
+        exported.room_id     = index.room_id;
+        exported.sender_key  = index.sender_key;
+        exported.session_id  = index.session_id;
+        exported.session_key = export_session(saved_session.get(), -1);
 
-        return keys;
+        keys.sessions.push_back(exported);
+    }
+
+    cursor.close();
+
+    return keys;
 }
 
 void
 Cache::importSessionKeys(const mtx::crypto::ExportedSessionKeys &keys)
 {
-        for (const auto &s : keys.sessions) {
-                MegolmSessionIndex index;
-                index.room_id    = s.room_id;
-                index.session_id = s.session_id;
-                index.sender_key = s.sender_key;
+    for (const auto &s : keys.sessions) {
+        MegolmSessionIndex index;
+        index.room_id    = s.room_id;
+        index.session_id = s.session_id;
+        index.sender_key = s.sender_key;
 
-                GroupSessionData data{};
-                data.forwarding_curve25519_key_chain = s.forwarding_curve25519_key_chain;
-                if (s.sender_claimed_keys.count("ed25519"))
-                        data.sender_claimed_ed25519_key = s.sender_claimed_keys.at("ed25519");
+        GroupSessionData data{};
+        data.forwarding_curve25519_key_chain = s.forwarding_curve25519_key_chain;
+        if (s.sender_claimed_keys.count("ed25519"))
+            data.sender_claimed_ed25519_key = s.sender_claimed_keys.at("ed25519");
 
-                auto exported_session = mtx::crypto::import_session(s.session_key);
+        auto exported_session = mtx::crypto::import_session(s.session_key);
 
-                saveInboundMegolmSession(index, std::move(exported_session), data);
-                ChatPage::instance()->receivedSessionKey(index.room_id, index.session_id);
-        }
+        saveInboundMegolmSession(index, std::move(exported_session), data);
+        ChatPage::instance()->receivedSessionKey(index.room_id, index.session_id);
+    }
 }
 
 //
@@ -422,67 +414,64 @@ Cache::saveInboundMegolmSession(const MegolmSessionIndex &index,
                                 mtx::crypto::InboundGroupSessionPtr session,
                                 const GroupSessionData &data)
 {
-        using namespace mtx::crypto;
-        const auto key     = json(index).dump();
-        const auto pickled = pickle(session.get(), pickle_secret_);
+    using namespace mtx::crypto;
+    const auto key     = json(index).dump();
+    const auto pickled = pickle(session.get(), pickle_secret_);
 
-        auto txn = lmdb::txn::begin(env_);
+    auto txn = lmdb::txn::begin(env_);
 
-        std::string_view value;
-        if (inboundMegolmSessionDb_.get(txn, key, value)) {
-                auto oldSession =
-                  unpickle(std::string(value), pickle_secret_);
-                if (olm_inbound_group_session_first_known_index(session.get()) >
-                    olm_inbound_group_session_first_known_index(oldSession.get())) {
-                        nhlog::crypto()->warn(
-                          "Not storing inbound session with newer first known index");
-                        return;
-                }
+    std::string_view value;
+    if (inboundMegolmSessionDb_.get(txn, key, value)) {
+        auto oldSession = unpickle(std::string(value), pickle_secret_);
+        if (olm_inbound_group_session_first_known_index(session.get()) >
+            olm_inbound_group_session_first_known_index(oldSession.get())) {
+            nhlog::crypto()->warn("Not storing inbound session with newer first known index");
+            return;
         }
+    }
 
-        inboundMegolmSessionDb_.put(txn, key, pickled);
-        megolmSessionDataDb_.put(txn, key, json(data).dump());
-        txn.commit();
+    inboundMegolmSessionDb_.put(txn, key, pickled);
+    megolmSessionDataDb_.put(txn, key, json(data).dump());
+    txn.commit();
 }
 
 mtx::crypto::InboundGroupSessionPtr
 Cache::getInboundMegolmSession(const MegolmSessionIndex &index)
 {
-        using namespace mtx::crypto;
+    using namespace mtx::crypto;
 
-        try {
-                auto txn        = ro_txn(env_);
-                std::string key = json(index).dump();
-                std::string_view value;
+    try {
+        auto txn        = ro_txn(env_);
+        std::string key = json(index).dump();
+        std::string_view value;
 
-                if (inboundMegolmSessionDb_.get(txn, key, value)) {
-                        auto session =
-                          unpickle(std::string(value), pickle_secret_);
-                        return session;
-                }
-        } catch (std::exception &e) {
-                nhlog::db()->error("Failed to get inbound megolm session {}", e.what());
+        if (inboundMegolmSessionDb_.get(txn, key, value)) {
+            auto session = unpickle(std::string(value), pickle_secret_);
+            return session;
         }
+    } catch (std::exception &e) {
+        nhlog::db()->error("Failed to get inbound megolm session {}", e.what());
+    }
 
-        return nullptr;
+    return nullptr;
 }
 
 bool
 Cache::inboundMegolmSessionExists(const MegolmSessionIndex &index)
 {
-        using namespace mtx::crypto;
+    using namespace mtx::crypto;
 
-        try {
-                auto txn        = ro_txn(env_);
-                std::string key = json(index).dump();
-                std::string_view value;
+    try {
+        auto txn        = ro_txn(env_);
+        std::string key = json(index).dump();
+        std::string_view value;
 
-                return inboundMegolmSessionDb_.get(txn, key, value);
-        } catch (std::exception &e) {
-                nhlog::db()->error("Failed to get inbound megolm session {}", e.what());
-        }
+        return inboundMegolmSessionDb_.get(txn, key, value);
+    } catch (std::exception &e) {
+        nhlog::db()->error("Failed to get inbound megolm session {}", e.what());
+    }
 
-        return false;
+    return false;
 }
 
 void
@@ -490,42 +479,42 @@ Cache::updateOutboundMegolmSession(const std::string &room_id,
                                    const GroupSessionData &data_,
                                    mtx::crypto::OutboundGroupSessionPtr &ptr)
 {
-        using namespace mtx::crypto;
+    using namespace mtx::crypto;
 
-        if (!outboundMegolmSessionExists(room_id))
-                return;
+    if (!outboundMegolmSessionExists(room_id))
+        return;
 
-        GroupSessionData data = data_;
-        data.message_index    = olm_outbound_group_session_message_index(ptr.get());
-        MegolmSessionIndex index;
-        index.room_id    = room_id;
-        index.sender_key = olm::client()->identity_keys().ed25519;
-        index.session_id = mtx::crypto::session_id(ptr.get());
+    GroupSessionData data = data_;
+    data.message_index    = olm_outbound_group_session_message_index(ptr.get());
+    MegolmSessionIndex index;
+    index.room_id    = room_id;
+    index.sender_key = olm::client()->identity_keys().ed25519;
+    index.session_id = mtx::crypto::session_id(ptr.get());
 
-        // Save the updated pickled data for the session.
-        json j;
-        j["session"] = pickle(ptr.get(), pickle_secret_);
+    // Save the updated pickled data for the session.
+    json j;
+    j["session"] = pickle(ptr.get(), pickle_secret_);
 
-        auto txn = lmdb::txn::begin(env_);
-        outboundMegolmSessionDb_.put(txn, room_id, j.dump());
-        megolmSessionDataDb_.put(txn, json(index).dump(), json(data).dump());
-        txn.commit();
+    auto txn = lmdb::txn::begin(env_);
+    outboundMegolmSessionDb_.put(txn, room_id, j.dump());
+    megolmSessionDataDb_.put(txn, json(index).dump(), json(data).dump());
+    txn.commit();
 }
 
 void
 Cache::dropOutboundMegolmSession(const std::string &room_id)
 {
-        using namespace mtx::crypto;
+    using namespace mtx::crypto;
 
-        if (!outboundMegolmSessionExists(room_id))
-                return;
+    if (!outboundMegolmSessionExists(room_id))
+        return;
 
-        {
-                auto txn = lmdb::txn::begin(env_);
-                outboundMegolmSessionDb_.del(txn, room_id);
-                // don't delete session data, so that we can still share the session.
-                txn.commit();
-        }
+    {
+        auto txn = lmdb::txn::begin(env_);
+        outboundMegolmSessionDb_.del(txn, room_id);
+        // don't delete session data, so that we can still share the session.
+        txn.commit();
+    }
 }
 
 void
@@ -533,86 +522,86 @@ Cache::saveOutboundMegolmSession(const std::string &room_id,
                                  const GroupSessionData &data_,
                                  mtx::crypto::OutboundGroupSessionPtr &session)
 {
-        using namespace mtx::crypto;
-        const auto pickled = pickle(session.get(), pickle_secret_);
+    using namespace mtx::crypto;
+    const auto pickled = pickle(session.get(), pickle_secret_);
 
-        GroupSessionData data = data_;
-        data.message_index    = olm_outbound_group_session_message_index(session.get());
-        MegolmSessionIndex index;
-        index.room_id    = room_id;
-        index.sender_key = olm::client()->identity_keys().ed25519;
-        index.session_id = mtx::crypto::session_id(session.get());
+    GroupSessionData data = data_;
+    data.message_index    = olm_outbound_group_session_message_index(session.get());
+    MegolmSessionIndex index;
+    index.room_id    = room_id;
+    index.sender_key = olm::client()->identity_keys().ed25519;
+    index.session_id = mtx::crypto::session_id(session.get());
 
-        json j;
-        j["session"] = pickled;
+    json j;
+    j["session"] = pickled;
 
-        auto txn = lmdb::txn::begin(env_);
-        outboundMegolmSessionDb_.put(txn, room_id, j.dump());
-        megolmSessionDataDb_.put(txn, json(index).dump(), json(data).dump());
-        txn.commit();
+    auto txn = lmdb::txn::begin(env_);
+    outboundMegolmSessionDb_.put(txn, room_id, j.dump());
+    megolmSessionDataDb_.put(txn, json(index).dump(), json(data).dump());
+    txn.commit();
 }
 
 bool
 Cache::outboundMegolmSessionExists(const std::string &room_id) noexcept
 {
-        try {
-                auto txn = ro_txn(env_);
-                std::string_view value;
-                return outboundMegolmSessionDb_.get(txn, room_id, value);
-        } catch (std::exception &e) {
-                nhlog::db()->error("Failed to retrieve outbound Megolm Session: {}", e.what());
-                return false;
-        }
+    try {
+        auto txn = ro_txn(env_);
+        std::string_view value;
+        return outboundMegolmSessionDb_.get(txn, room_id, value);
+    } catch (std::exception &e) {
+        nhlog::db()->error("Failed to retrieve outbound Megolm Session: {}", e.what());
+        return false;
+    }
 }
 
 OutboundGroupSessionDataRef
 Cache::getOutboundMegolmSession(const std::string &room_id)
 {
-        try {
-                using namespace mtx::crypto;
+    try {
+        using namespace mtx::crypto;
 
-                auto txn = ro_txn(env_);
-                std::string_view value;
-                outboundMegolmSessionDb_.get(txn, room_id, value);
-                auto obj = json::parse(value);
+        auto txn = ro_txn(env_);
+        std::string_view value;
+        outboundMegolmSessionDb_.get(txn, room_id, value);
+        auto obj = json::parse(value);
 
-                OutboundGroupSessionDataRef ref{};
-                ref.session = unpickle(obj.at("session"), pickle_secret_);
+        OutboundGroupSessionDataRef ref{};
+        ref.session = unpickle(obj.at("session"), pickle_secret_);
 
-                MegolmSessionIndex index;
-                index.room_id    = room_id;
-                index.sender_key = olm::client()->identity_keys().ed25519;
-                index.session_id = mtx::crypto::session_id(ref.session.get());
+        MegolmSessionIndex index;
+        index.room_id    = room_id;
+        index.sender_key = olm::client()->identity_keys().ed25519;
+        index.session_id = mtx::crypto::session_id(ref.session.get());
 
-                if (megolmSessionDataDb_.get(txn, json(index).dump(), value)) {
-                        ref.data = nlohmann::json::parse(value).get();
-                }
-
-                return ref;
-        } catch (std::exception &e) {
-                nhlog::db()->error("Failed to retrieve outbound Megolm Session: {}", e.what());
-                return {};
+        if (megolmSessionDataDb_.get(txn, json(index).dump(), value)) {
+            ref.data = nlohmann::json::parse(value).get();
         }
+
+        return ref;
+    } catch (std::exception &e) {
+        nhlog::db()->error("Failed to retrieve outbound Megolm Session: {}", e.what());
+        return {};
+    }
 }
 
 std::optional
 Cache::getMegolmSessionData(const MegolmSessionIndex &index)
 {
-        try {
-                using namespace mtx::crypto;
+    try {
+        using namespace mtx::crypto;
 
-                auto txn = ro_txn(env_);
+        auto txn = ro_txn(env_);
 
-                std::string_view value;
-                if (megolmSessionDataDb_.get(txn, json(index).dump(), value)) {
-                        return nlohmann::json::parse(value).get();
-                }
-
-                return std::nullopt;
-        } catch (std::exception &e) {
-                nhlog::db()->error("Failed to retrieve Megolm Session Data: {}", e.what());
-                return std::nullopt;
+        std::string_view value;
+        if (megolmSessionDataDb_.get(txn, json(index).dump(), value)) {
+            return nlohmann::json::parse(value).get();
         }
+
+        return std::nullopt;
+    } catch (std::exception &e) {
+        nhlog::db()->error("Failed to retrieve Megolm Session Data: {}", e.what());
+        return std::nullopt;
+    }
 }
 //
 // OLM sessions.
@@ -623,1013 +612,972 @@ Cache::saveOlmSession(const std::string &curve25519,
                       mtx::crypto::OlmSessionPtr session,
                       uint64_t timestamp)
 {
-        using namespace mtx::crypto;
+    using namespace mtx::crypto;
 
-        auto txn = lmdb::txn::begin(env_);
-        auto db  = getOlmSessionsDb(txn, curve25519);
+    auto txn = lmdb::txn::begin(env_);
+    auto db  = getOlmSessionsDb(txn, curve25519);
 
-        const auto pickled    = pickle(session.get(), pickle_secret_);
-        const auto session_id = mtx::crypto::session_id(session.get());
+    const auto pickled    = pickle(session.get(), pickle_secret_);
+    const auto session_id = mtx::crypto::session_id(session.get());
 
-        StoredOlmSession stored_session;
-        stored_session.pickled_session = pickled;
-        stored_session.last_message_ts = timestamp;
+    StoredOlmSession stored_session;
+    stored_session.pickled_session = pickled;
+    stored_session.last_message_ts = timestamp;
 
-        db.put(txn, session_id, json(stored_session).dump());
+    db.put(txn, session_id, json(stored_session).dump());
 
-        txn.commit();
+    txn.commit();
 }
 
 std::optional
 Cache::getOlmSession(const std::string &curve25519, const std::string &session_id)
 {
-        using namespace mtx::crypto;
+    using namespace mtx::crypto;
 
-        auto txn = lmdb::txn::begin(env_);
-        auto db  = getOlmSessionsDb(txn, curve25519);
+    auto txn = lmdb::txn::begin(env_);
+    auto db  = getOlmSessionsDb(txn, curve25519);
 
-        std::string_view pickled;
-        bool found = db.get(txn, session_id, pickled);
+    std::string_view pickled;
+    bool found = db.get(txn, session_id, pickled);
 
-        txn.commit();
+    txn.commit();
 
-        if (found) {
-                auto data = json::parse(pickled).get();
-                return unpickle(data.pickled_session, pickle_secret_);
-        }
+    if (found) {
+        auto data = json::parse(pickled).get();
+        return unpickle(data.pickled_session, pickle_secret_);
+    }
 
-        return std::nullopt;
+    return std::nullopt;
 }
 
 std::optional
 Cache::getLatestOlmSession(const std::string &curve25519)
 {
-        using namespace mtx::crypto;
+    using namespace mtx::crypto;
 
-        auto txn = lmdb::txn::begin(env_);
-        auto db  = getOlmSessionsDb(txn, curve25519);
+    auto txn = lmdb::txn::begin(env_);
+    auto db  = getOlmSessionsDb(txn, curve25519);
 
-        std::string_view session_id, pickled_session;
+    std::string_view session_id, pickled_session;
 
-        std::optional currentNewest;
+    std::optional currentNewest;
 
-        auto cursor = lmdb::cursor::open(txn, db);
-        while (cursor.get(session_id, pickled_session, MDB_NEXT)) {
-                auto data = json::parse(pickled_session).get();
-                if (!currentNewest || currentNewest->last_message_ts < data.last_message_ts)
-                        currentNewest = data;
-        }
-        cursor.close();
+    auto cursor = lmdb::cursor::open(txn, db);
+    while (cursor.get(session_id, pickled_session, MDB_NEXT)) {
+        auto data = json::parse(pickled_session).get();
+        if (!currentNewest || currentNewest->last_message_ts < data.last_message_ts)
+            currentNewest = data;
+    }
+    cursor.close();
 
-        txn.commit();
+    txn.commit();
 
-        return currentNewest ? std::optional(unpickle(currentNewest->pickled_session,
-                                                                     pickle_secret_))
-                             : std::nullopt;
+    return currentNewest ? std::optional(unpickle(currentNewest->pickled_session,
+                                                                 pickle_secret_))
+                         : std::nullopt;
 }
 
 std::vector
 Cache::getOlmSessions(const std::string &curve25519)
 {
-        using namespace mtx::crypto;
+    using namespace mtx::crypto;
 
-        auto txn = lmdb::txn::begin(env_);
-        auto db  = getOlmSessionsDb(txn, curve25519);
+    auto txn = lmdb::txn::begin(env_);
+    auto db  = getOlmSessionsDb(txn, curve25519);
 
-        std::string_view session_id, unused;
-        std::vector res;
+    std::string_view session_id, unused;
+    std::vector res;
 
-        auto cursor = lmdb::cursor::open(txn, db);
-        while (cursor.get(session_id, unused, MDB_NEXT))
-                res.emplace_back(session_id);
-        cursor.close();
+    auto cursor = lmdb::cursor::open(txn, db);
+    while (cursor.get(session_id, unused, MDB_NEXT))
+        res.emplace_back(session_id);
+    cursor.close();
 
-        txn.commit();
+    txn.commit();
 
-        return res;
+    return res;
 }
 
 void
 Cache::saveOlmAccount(const std::string &data)
 {
-        auto txn = lmdb::txn::begin(env_);
-        syncStateDb_.put(txn, OLM_ACCOUNT_KEY, data);
-        txn.commit();
+    auto txn = lmdb::txn::begin(env_);
+    syncStateDb_.put(txn, OLM_ACCOUNT_KEY, data);
+    txn.commit();
 }
 
 std::string
 Cache::restoreOlmAccount()
 {
-        auto txn = ro_txn(env_);
+    auto txn = ro_txn(env_);
 
-        std::string_view pickled;
-        syncStateDb_.get(txn, OLM_ACCOUNT_KEY, pickled);
+    std::string_view pickled;
+    syncStateDb_.get(txn, OLM_ACCOUNT_KEY, pickled);
 
-        return std::string(pickled.data(), pickled.size());
+    return std::string(pickled.data(), pickled.size());
 }
 
 void
 Cache::saveBackupVersion(const OnlineBackupVersion &data)
 {
-        auto txn = lmdb::txn::begin(env_);
-        syncStateDb_.put(txn, CURRENT_ONLINE_BACKUP_VERSION, nlohmann::json(data).dump());
-        txn.commit();
+    auto txn = lmdb::txn::begin(env_);
+    syncStateDb_.put(txn, CURRENT_ONLINE_BACKUP_VERSION, nlohmann::json(data).dump());
+    txn.commit();
 }
 
 void
 Cache::deleteBackupVersion()
 {
-        auto txn = lmdb::txn::begin(env_);
-        syncStateDb_.del(txn, CURRENT_ONLINE_BACKUP_VERSION);
-        txn.commit();
+    auto txn = lmdb::txn::begin(env_);
+    syncStateDb_.del(txn, CURRENT_ONLINE_BACKUP_VERSION);
+    txn.commit();
 }
 
 std::optional
 Cache::backupVersion()
 {
-        try {
-                auto txn = ro_txn(env_);
-                std::string_view v;
-                syncStateDb_.get(txn, CURRENT_ONLINE_BACKUP_VERSION, v);
+    try {
+        auto txn = ro_txn(env_);
+        std::string_view v;
+        syncStateDb_.get(txn, CURRENT_ONLINE_BACKUP_VERSION, v);
 
-                return nlohmann::json::parse(v).get();
-        } catch (...) {
-                return std::nullopt;
-        }
+        return nlohmann::json::parse(v).get();
+    } catch (...) {
+        return std::nullopt;
+    }
 }
 
 static void
 fatalSecretError()
 {
-        QMessageBox::critical(
-          ChatPage::instance(),
-          QCoreApplication::translate("SecretStorage", "Failed to connect to secret storage"),
-          QCoreApplication::translate(
-            "SecretStorage",
-            "Nheko could not connect to the secure storage to save encryption secrets to. "
-            "This can have multiple reasons. Check if your D-Bus service is running and "
-            "you have configured a service like KWallet, Gnome Secrets or the equivalent "
-            "for your platform. If you are having trouble, feel free to open an issue "
-            "here: https://github.com/Nheko-Reborn/nheko/issues"));
+    QMessageBox::critical(
+      ChatPage::instance(),
+      QCoreApplication::translate("SecretStorage", "Failed to connect to secret storage"),
+      QCoreApplication::translate(
+        "SecretStorage",
+        "Nheko could not connect to the secure storage to save encryption secrets to. "
+        "This can have multiple reasons. Check if your D-Bus service is running and "
+        "you have configured a service like KWallet, Gnome Secrets or the equivalent "
+        "for your platform. If you are having trouble, feel free to open an issue "
+        "here: https://github.com/Nheko-Reborn/nheko/issues"));
 
-        QCoreApplication::exit(1);
-        exit(1);
+    QCoreApplication::exit(1);
+    exit(1);
 }
 
 void
 Cache::storeSecret(const std::string name, const std::string secret, bool internal)
 {
-        auto settings = UserSettings::instance();
-        auto job      = new QKeychain::WritePasswordJob(QCoreApplication::applicationName());
-        job->setAutoDelete(true);
-        job->setInsecureFallback(true);
-        job->setSettings(UserSettings::instance()->qsettings());
+    auto settings = UserSettings::instance();
+    auto job      = new QKeychain::WritePasswordJob(QCoreApplication::applicationName());
+    job->setAutoDelete(true);
+    job->setInsecureFallback(true);
+    job->setSettings(UserSettings::instance()->qsettings());
 
-        job->setKey(
-          (internal ? "nheko." : "matrix.") +
-          QString(QCryptographicHash::hash(settings->profile().toUtf8(), QCryptographicHash::Sha256)
-                    .toBase64()) +
-          "." + QString::fromStdString(name));
+    job->setKey(
+      (internal ? "nheko." : "matrix.") +
+      QString(QCryptographicHash::hash(settings->profile().toUtf8(), QCryptographicHash::Sha256)
+                .toBase64()) +
+      "." + QString::fromStdString(name));
 
-        job->setTextData(QString::fromStdString(secret));
+    job->setTextData(QString::fromStdString(secret));
 
-        QObject::connect(
-          job,
-          &QKeychain::WritePasswordJob::finished,
-          this,
-          [name, this](QKeychain::Job *job) {
-                  if (job->error()) {
-                          nhlog::db()->warn("Storing secret '{}' failed: {}",
-                                            name,
-                                            job->errorString().toStdString());
-                          fatalSecretError();
-                  } else {
-                          // if we emit the signal directly, qtkeychain breaks and won't execute new
-                          // jobs. You can't start a job from the finish signal of a job.
-                          QTimer::singleShot(100, [this, name] { emit secretChanged(name); });
-                          nhlog::db()->info("Storing secret '{}' successful", name);
-                  }
-          },
-          Qt::ConnectionType::DirectConnection);
-        job->start();
+    QObject::connect(
+      job,
+      &QKeychain::WritePasswordJob::finished,
+      this,
+      [name, this](QKeychain::Job *job) {
+          if (job->error()) {
+              nhlog::db()->warn(
+                "Storing secret '{}' failed: {}", name, job->errorString().toStdString());
+              fatalSecretError();
+          } else {
+              // if we emit the signal directly, qtkeychain breaks and won't execute new
+              // jobs. You can't start a job from the finish signal of a job.
+              QTimer::singleShot(100, [this, name] { emit secretChanged(name); });
+              nhlog::db()->info("Storing secret '{}' successful", name);
+          }
+      },
+      Qt::ConnectionType::DirectConnection);
+    job->start();
 }
 
 void
 Cache::deleteSecret(const std::string name, bool internal)
 {
-        auto settings = UserSettings::instance();
-        QKeychain::DeletePasswordJob job(QCoreApplication::applicationName());
-        job.setAutoDelete(false);
-        job.setInsecureFallback(true);
-        job.setSettings(UserSettings::instance()->qsettings());
+    auto settings = UserSettings::instance();
+    QKeychain::DeletePasswordJob job(QCoreApplication::applicationName());
+    job.setAutoDelete(false);
+    job.setInsecureFallback(true);
+    job.setSettings(UserSettings::instance()->qsettings());
 
-        job.setKey(
-          (internal ? "nheko." : "matrix.") +
-          QString(QCryptographicHash::hash(settings->profile().toUtf8(), QCryptographicHash::Sha256)
-                    .toBase64()) +
-          "." + QString::fromStdString(name));
+    job.setKey(
+      (internal ? "nheko." : "matrix.") +
+      QString(QCryptographicHash::hash(settings->profile().toUtf8(), QCryptographicHash::Sha256)
+                .toBase64()) +
+      "." + QString::fromStdString(name));
 
-        // FIXME(Nico): Nested event loops are dangerous. Some other slots may resume in the mean
-        // time!
-        QEventLoop loop;
-        job.connect(&job, &QKeychain::Job::finished, &loop, &QEventLoop::quit);
-        job.start();
-        loop.exec();
+    // FIXME(Nico): Nested event loops are dangerous. Some other slots may resume in the mean
+    // time!
+    QEventLoop loop;
+    job.connect(&job, &QKeychain::Job::finished, &loop, &QEventLoop::quit);
+    job.start();
+    loop.exec();
 
-        emit secretChanged(name);
+    emit secretChanged(name);
 }
 
 std::optional
 Cache::secret(const std::string name, bool internal)
 {
-        auto settings = UserSettings::instance();
-        QKeychain::ReadPasswordJob job(QCoreApplication::applicationName());
-        job.setAutoDelete(false);
-        job.setInsecureFallback(true);
-        job.setSettings(UserSettings::instance()->qsettings());
+    auto settings = UserSettings::instance();
+    QKeychain::ReadPasswordJob job(QCoreApplication::applicationName());
+    job.setAutoDelete(false);
+    job.setInsecureFallback(true);
+    job.setSettings(UserSettings::instance()->qsettings());
 
-        job.setKey(
-          (internal ? "nheko." : "matrix.") +
-          QString(QCryptographicHash::hash(settings->profile().toUtf8(), QCryptographicHash::Sha256)
-                    .toBase64()) +
-          "." + QString::fromStdString(name));
+    job.setKey(
+      (internal ? "nheko." : "matrix.") +
+      QString(QCryptographicHash::hash(settings->profile().toUtf8(), QCryptographicHash::Sha256)
+                .toBase64()) +
+      "." + QString::fromStdString(name));
 
-        // FIXME(Nico): Nested event loops are dangerous. Some other slots may resume in the mean
-        // time!
-        QEventLoop loop;
-        job.connect(&job, &QKeychain::Job::finished, &loop, &QEventLoop::quit);
-        job.start();
-        loop.exec();
+    // FIXME(Nico): Nested event loops are dangerous. Some other slots may resume in the mean
+    // time!
+    QEventLoop loop;
+    job.connect(&job, &QKeychain::Job::finished, &loop, &QEventLoop::quit);
+    job.start();
+    loop.exec();
 
-        const QString secret = job.textData();
-        if (job.error()) {
-                if (job.error() == QKeychain::Error::EntryNotFound)
-                        return std::nullopt;
-                nhlog::db()->error("Restoring secret '{}' failed ({}): {}",
-                                   name,
-                                   job.error(),
-                                   job.errorString().toStdString());
+    const QString secret = job.textData();
+    if (job.error()) {
+        if (job.error() == QKeychain::Error::EntryNotFound)
+            return std::nullopt;
+        nhlog::db()->error("Restoring secret '{}' failed ({}): {}",
+                           name,
+                           job.error(),
+                           job.errorString().toStdString());
 
-                fatalSecretError();
-                return std::nullopt;
-        }
-        if (secret.isEmpty()) {
-                nhlog::db()->debug("Restored empty secret '{}'.", name);
-                return std::nullopt;
-        }
+        fatalSecretError();
+        return std::nullopt;
+    }
+    if (secret.isEmpty()) {
+        nhlog::db()->debug("Restored empty secret '{}'.", name);
+        return std::nullopt;
+    }
 
-        return secret.toStdString();
+    return secret.toStdString();
 }
 
 std::string
 Cache::pickleSecret()
 {
-        if (pickle_secret_.empty()) {
-                auto s = secret("pickle_secret", true);
-                if (!s) {
-                        this->pickle_secret_ = mtx::client::utils::random_token(64, true);
-                        storeSecret("pickle_secret", pickle_secret_, true);
-                } else {
-                        this->pickle_secret_ = *s;
-                }
+    if (pickle_secret_.empty()) {
+        auto s = secret("pickle_secret", true);
+        if (!s) {
+            this->pickle_secret_ = mtx::client::utils::random_token(64, true);
+            storeSecret("pickle_secret", pickle_secret_, true);
+        } else {
+            this->pickle_secret_ = *s;
         }
+    }
 
-        return pickle_secret_;
+    return pickle_secret_;
 }
 
 void
 Cache::removeInvite(lmdb::txn &txn, const std::string &room_id)
 {
-        invitesDb_.del(txn, room_id);
-        getInviteStatesDb(txn, room_id).drop(txn, true);
-        getInviteMembersDb(txn, room_id).drop(txn, true);
+    invitesDb_.del(txn, room_id);
+    getInviteStatesDb(txn, room_id).drop(txn, true);
+    getInviteMembersDb(txn, room_id).drop(txn, true);
 }
 
 void
 Cache::removeInvite(const std::string &room_id)
 {
-        auto txn = lmdb::txn::begin(env_);
-        removeInvite(txn, room_id);
-        txn.commit();
+    auto txn = lmdb::txn::begin(env_);
+    removeInvite(txn, room_id);
+    txn.commit();
 }
 
 void
 Cache::removeRoom(lmdb::txn &txn, const std::string &roomid)
 {
-        roomsDb_.del(txn, roomid);
-        getStatesDb(txn, roomid).drop(txn, true);
-        getAccountDataDb(txn, roomid).drop(txn, true);
-        getMembersDb(txn, roomid).drop(txn, true);
+    roomsDb_.del(txn, roomid);
+    getStatesDb(txn, roomid).drop(txn, true);
+    getAccountDataDb(txn, roomid).drop(txn, true);
+    getMembersDb(txn, roomid).drop(txn, true);
 }
 
 void
 Cache::removeRoom(const std::string &roomid)
 {
-        auto txn = lmdb::txn::begin(env_, nullptr, 0);
-        roomsDb_.del(txn, roomid);
-        txn.commit();
+    auto txn = lmdb::txn::begin(env_, nullptr, 0);
+    roomsDb_.del(txn, roomid);
+    txn.commit();
 }
 
 void
 Cache::setNextBatchToken(lmdb::txn &txn, const std::string &token)
 {
-        syncStateDb_.put(txn, NEXT_BATCH_KEY, token);
+    syncStateDb_.put(txn, NEXT_BATCH_KEY, token);
 }
 
 bool
 Cache::isInitialized()
 {
-        if (!env_.handle())
-                return false;
+    if (!env_.handle())
+        return false;
 
-        auto txn = ro_txn(env_);
-        std::string_view token;
+    auto txn = ro_txn(env_);
+    std::string_view token;
 
-        bool res = syncStateDb_.get(txn, NEXT_BATCH_KEY, token);
+    bool res = syncStateDb_.get(txn, NEXT_BATCH_KEY, token);
 
-        return res;
+    return res;
 }
 
 std::string
 Cache::nextBatchToken()
 {
-        if (!env_.handle())
-                throw lmdb::error("Env already closed", MDB_INVALID);
+    if (!env_.handle())
+        throw lmdb::error("Env already closed", MDB_INVALID);
 
-        auto txn = ro_txn(env_);
-        std::string_view token;
+    auto txn = ro_txn(env_);
+    std::string_view token;
 
-        bool result = syncStateDb_.get(txn, NEXT_BATCH_KEY, token);
+    bool result = syncStateDb_.get(txn, NEXT_BATCH_KEY, token);
 
-        if (result)
-                return std::string(token.data(), token.size());
-        else
-                return "";
+    if (result)
+        return std::string(token.data(), token.size());
+    else
+        return "";
 }
 
 void
 Cache::deleteData()
 {
-        this->databaseReady_ = false;
-        // TODO: We need to remove the env_ while not accepting new requests.
-        lmdb::dbi_close(env_, syncStateDb_);
-        lmdb::dbi_close(env_, roomsDb_);
-        lmdb::dbi_close(env_, invitesDb_);
-        lmdb::dbi_close(env_, readReceiptsDb_);
-        lmdb::dbi_close(env_, notificationsDb_);
+    this->databaseReady_ = false;
+    // TODO: We need to remove the env_ while not accepting new requests.
+    lmdb::dbi_close(env_, syncStateDb_);
+    lmdb::dbi_close(env_, roomsDb_);
+    lmdb::dbi_close(env_, invitesDb_);
+    lmdb::dbi_close(env_, readReceiptsDb_);
+    lmdb::dbi_close(env_, notificationsDb_);
 
-        lmdb::dbi_close(env_, devicesDb_);
-        lmdb::dbi_close(env_, deviceKeysDb_);
+    lmdb::dbi_close(env_, devicesDb_);
+    lmdb::dbi_close(env_, deviceKeysDb_);
 
-        lmdb::dbi_close(env_, inboundMegolmSessionDb_);
-        lmdb::dbi_close(env_, outboundMegolmSessionDb_);
-        lmdb::dbi_close(env_, megolmSessionDataDb_);
+    lmdb::dbi_close(env_, inboundMegolmSessionDb_);
+    lmdb::dbi_close(env_, outboundMegolmSessionDb_);
+    lmdb::dbi_close(env_, megolmSessionDataDb_);
 
-        env_.close();
+    env_.close();
 
-        verification_storage.status.clear();
+    verification_storage.status.clear();
 
-        if (!cacheDirectory_.isEmpty()) {
-                QDir(cacheDirectory_).removeRecursively();
-                nhlog::db()->info("deleted cache files from disk");
-        }
+    if (!cacheDirectory_.isEmpty()) {
+        QDir(cacheDirectory_).removeRecursively();
+        nhlog::db()->info("deleted cache files from disk");
+    }
 
-        deleteSecret(mtx::secret_storage::secrets::megolm_backup_v1);
-        deleteSecret(mtx::secret_storage::secrets::cross_signing_master);
-        deleteSecret(mtx::secret_storage::secrets::cross_signing_user_signing);
-        deleteSecret(mtx::secret_storage::secrets::cross_signing_self_signing);
-        deleteSecret("pickle_secret", true);
+    deleteSecret(mtx::secret_storage::secrets::megolm_backup_v1);
+    deleteSecret(mtx::secret_storage::secrets::cross_signing_master);
+    deleteSecret(mtx::secret_storage::secrets::cross_signing_user_signing);
+    deleteSecret(mtx::secret_storage::secrets::cross_signing_self_signing);
+    deleteSecret("pickle_secret", true);
 }
 
 //! migrates db to the current format
 bool
 Cache::runMigrations()
 {
-        std::string stored_version;
-        {
-                auto txn = ro_txn(env_);
-
-                std::string_view current_version;
-                bool res = syncStateDb_.get(txn, CACHE_FORMAT_VERSION_KEY, current_version);
-
-                if (!res)
-                        return false;
-
-                stored_version = std::string(current_version);
-        }
-
-        std::vector>> migrations{
-          {"2020.05.01",
-           [this]() {
-                   try {
-                           auto txn = lmdb::txn::begin(env_, nullptr);
-                           auto pending_receipts =
-                             lmdb::dbi::open(txn, "pending_receipts", MDB_CREATE);
-                           lmdb::dbi_drop(txn, pending_receipts, true);
-                           txn.commit();
-                   } catch (const lmdb::error &) {
-                           nhlog::db()->critical(
-                             "Failed to delete pending_receipts database in migration!");
-                           return false;
-                   }
-
-                   nhlog::db()->info("Successfully deleted pending receipts database.");
-                   return true;
-           }},
-          {"2020.07.05",
-           [this]() {
-                   try {
-                           auto txn      = lmdb::txn::begin(env_, nullptr);
-                           auto room_ids = getRoomIds(txn);
-
-                           for (const auto &room_id : room_ids) {
-                                   try {
-                                           auto messagesDb = lmdb::dbi::open(
-                                             txn, std::string(room_id + "/messages").c_str());
-
-                                           // keep some old messages and batch token
-                                           {
-                                                   auto roomsCursor =
-                                                     lmdb::cursor::open(txn, messagesDb);
-                                                   std::string_view ts, stored_message;
-                                                   bool start = true;
-                                                   mtx::responses::Timeline oldMessages;
-                                                   while (roomsCursor.get(ts,
-                                                                          stored_message,
-                                                                          start ? MDB_FIRST
-                                                                                : MDB_NEXT)) {
-                                                           start = false;
-
-                                                           auto j = json::parse(std::string_view(
-                                                             stored_message.data(),
-                                                             stored_message.size()));
-
-                                                           if (oldMessages.prev_batch.empty())
-                                                                   oldMessages.prev_batch =
-                                                                     j["token"].get();
-                                                           else if (j["token"] !=
-                                                                    oldMessages.prev_batch)
-                                                                   break;
-
-                                                           mtx::events::collections::TimelineEvent
-                                                             te;
-                                                           mtx::events::collections::from_json(
-                                                             j["event"], te);
-                                                           oldMessages.events.push_back(te.data);
-                                                   }
-                                                   // messages were stored in reverse order, so we
-                                                   // need to reverse them
-                                                   std::reverse(oldMessages.events.begin(),
-                                                                oldMessages.events.end());
-                                                   // save messages using the new method
-                                                   auto eventsDb = getEventsDb(txn, room_id);
-                                                   saveTimelineMessages(
-                                                     txn, eventsDb, room_id, oldMessages);
-                                           }
-
-                                           // delete old messages db
-                                           lmdb::dbi_drop(txn, messagesDb, true);
-                                   } catch (std::exception &e) {
-                                           nhlog::db()->error(
-                                             "While migrating messages from {}, ignoring error {}",
-                                             room_id,
-                                             e.what());
-                                   }
-                           }
-                           txn.commit();
-                   } catch (const lmdb::error &) {
-                           nhlog::db()->critical(
-                             "Failed to delete messages database in migration!");
-                           return false;
-                   }
-
-                   nhlog::db()->info("Successfully deleted pending receipts database.");
-                   return true;
-           }},
-          {"2020.10.20",
-           [this]() {
-                   try {
-                           using namespace mtx::crypto;
-
-                           auto txn = lmdb::txn::begin(env_);
-
-                           auto mainDb = lmdb::dbi::open(txn, nullptr);
-
-                           std::string_view dbName, ignored;
-                           auto olmDbCursor = lmdb::cursor::open(txn, mainDb);
-                           while (olmDbCursor.get(dbName, ignored, MDB_NEXT)) {
-                                   // skip every db but olm session dbs
-                                   nhlog::db()->debug("Db {}", dbName);
-                                   if (dbName.find("olm_sessions/") != 0)
-                                           continue;
-
-                                   nhlog::db()->debug("Migrating {}", dbName);
-
-                                   auto olmDb = lmdb::dbi::open(txn, std::string(dbName).c_str());
-
-                                   std::string_view session_id, session_value;
-
-                                   std::vector> sessions;
-
-                                   auto cursor = lmdb::cursor::open(txn, olmDb);
-                                   while (cursor.get(session_id, session_value, MDB_NEXT)) {
-                                           nhlog::db()->debug("session_id {}, session_value {}",
-                                                              session_id,
-                                                              session_value);
-                                           StoredOlmSession session;
-                                           bool invalid = false;
-                                           for (auto c : session_value)
-                                                   if (!isprint(c)) {
-                                                           invalid = true;
-                                                           break;
-                                                   }
-                                           if (invalid)
-                                                   continue;
-
-                                           nhlog::db()->debug("Not skipped");
-
-                                           session.pickled_session = session_value;
-                                           sessions.emplace_back(session_id, session);
-                                   }
-                                   cursor.close();
-
-                                   olmDb.drop(txn, true);
-
-                                   auto newDbName = std::string(dbName);
-                                   newDbName.erase(0, sizeof("olm_sessions") - 1);
-                                   newDbName = "olm_sessions.v2" + newDbName;
-
-                                   auto newDb = lmdb::dbi::open(txn, newDbName.c_str(), MDB_CREATE);
-
-                                   for (const auto &[key, value] : sessions) {
-                                           // nhlog::db()->debug("{}\n{}", key, json(value).dump());
-                                           newDb.put(txn, key, json(value).dump());
-                                   }
-                           }
-                           olmDbCursor.close();
-
-                           txn.commit();
-                   } catch (const lmdb::error &) {
-                           nhlog::db()->critical("Failed to migrate olm sessions,");
-                           return false;
-                   }
-
-                   nhlog::db()->info("Successfully migrated olm sessions.");
-                   return true;
-           }},
-          {"2021.08.22",
-           [this]() {
-                   try {
-                           auto txn      = lmdb::txn::begin(env_, nullptr);
-                           auto try_drop = [&txn](const std::string &dbName) {
-                                   try {
-                                           auto db = lmdb::dbi::open(txn, dbName.c_str());
-                                           db.drop(txn, true);
-                                   } catch (std::exception &e) {
-                                           nhlog::db()->warn(
-                                             "Failed to drop '{}': {}", dbName, e.what());
-                                   }
-                           };
-
-                           auto room_ids = getRoomIds(txn);
-
-                           for (const auto &room : room_ids) {
-                                   try_drop(room + "/state");
-                                   try_drop(room + "/state_by_key");
-                                   try_drop(room + "/account_data");
-                                   try_drop(room + "/members");
-                                   try_drop(room + "/mentions");
-                                   try_drop(room + "/events");
-                                   try_drop(room + "/event_order");
-                                   try_drop(room + "/event2order");
-                                   try_drop(room + "/msg2order");
-                                   try_drop(room + "/order2msg");
-                                   try_drop(room + "/pending");
-                                   try_drop(room + "/related");
-                           }
-
-                           // clear db, don't delete
-                           roomsDb_.drop(txn, false);
-                           setNextBatchToken(txn, "");
-
-                           txn.commit();
-                   } catch (const lmdb::error &) {
-                           nhlog::db()->critical("Failed to clear cache!");
-                           return false;
-                   }
-
-                   nhlog::db()->info(
-                     "Successfully cleared the cache. Will do a clean sync after startup.");
-                   return true;
-           }},
-          {"2021.08.31",
-           [this]() {
-                   storeSecret("pickle_secret", "secret", true);
-                   return true;
-           }},
-        };
-
-        nhlog::db()->info("Running migrations, this may take a while!");
-        for (const auto &[target_version, migration] : migrations) {
-                if (target_version > stored_version)
-                        if (!migration()) {
-                                nhlog::db()->critical("migration failure!");
-                                return false;
-                        }
-        }
-        nhlog::db()->info("Migrations finished.");
-
-        setCurrentFormat();
-        return true;
-}
-
-cache::CacheVersion
-Cache::formatVersion()
-{
+    std::string stored_version;
+    {
         auto txn = ro_txn(env_);
 
         std::string_view current_version;
         bool res = syncStateDb_.get(txn, CACHE_FORMAT_VERSION_KEY, current_version);
 
         if (!res)
-                return cache::CacheVersion::Older;
+            return false;
 
-        std::string stored_version(current_version.data(), current_version.size());
+        stored_version = std::string(current_version);
+    }
 
-        if (stored_version < CURRENT_CACHE_FORMAT_VERSION)
-                return cache::CacheVersion::Older;
-        else if (stored_version > CURRENT_CACHE_FORMAT_VERSION)
-                return cache::CacheVersion::Older;
-        else
-                return cache::CacheVersion::Current;
+    std::vector>> migrations{
+      {"2020.05.01",
+       [this]() {
+           try {
+               auto txn              = lmdb::txn::begin(env_, nullptr);
+               auto pending_receipts = lmdb::dbi::open(txn, "pending_receipts", MDB_CREATE);
+               lmdb::dbi_drop(txn, pending_receipts, true);
+               txn.commit();
+           } catch (const lmdb::error &) {
+               nhlog::db()->critical("Failed to delete pending_receipts database in migration!");
+               return false;
+           }
+
+           nhlog::db()->info("Successfully deleted pending receipts database.");
+           return true;
+       }},
+      {"2020.07.05",
+       [this]() {
+           try {
+               auto txn      = lmdb::txn::begin(env_, nullptr);
+               auto room_ids = getRoomIds(txn);
+
+               for (const auto &room_id : room_ids) {
+                   try {
+                       auto messagesDb =
+                         lmdb::dbi::open(txn, std::string(room_id + "/messages").c_str());
+
+                       // keep some old messages and batch token
+                       {
+                           auto roomsCursor = lmdb::cursor::open(txn, messagesDb);
+                           std::string_view ts, stored_message;
+                           bool start = true;
+                           mtx::responses::Timeline oldMessages;
+                           while (
+                             roomsCursor.get(ts, stored_message, start ? MDB_FIRST : MDB_NEXT)) {
+                               start = false;
+
+                               auto j = json::parse(
+                                 std::string_view(stored_message.data(), stored_message.size()));
+
+                               if (oldMessages.prev_batch.empty())
+                                   oldMessages.prev_batch = j["token"].get();
+                               else if (j["token"] != oldMessages.prev_batch)
+                                   break;
+
+                               mtx::events::collections::TimelineEvent te;
+                               mtx::events::collections::from_json(j["event"], te);
+                               oldMessages.events.push_back(te.data);
+                           }
+                           // messages were stored in reverse order, so we
+                           // need to reverse them
+                           std::reverse(oldMessages.events.begin(), oldMessages.events.end());
+                           // save messages using the new method
+                           auto eventsDb = getEventsDb(txn, room_id);
+                           saveTimelineMessages(txn, eventsDb, room_id, oldMessages);
+                       }
+
+                       // delete old messages db
+                       lmdb::dbi_drop(txn, messagesDb, true);
+                   } catch (std::exception &e) {
+                       nhlog::db()->error(
+                         "While migrating messages from {}, ignoring error {}", room_id, e.what());
+                   }
+               }
+               txn.commit();
+           } catch (const lmdb::error &) {
+               nhlog::db()->critical("Failed to delete messages database in migration!");
+               return false;
+           }
+
+           nhlog::db()->info("Successfully deleted pending receipts database.");
+           return true;
+       }},
+      {"2020.10.20",
+       [this]() {
+           try {
+               using namespace mtx::crypto;
+
+               auto txn = lmdb::txn::begin(env_);
+
+               auto mainDb = lmdb::dbi::open(txn, nullptr);
+
+               std::string_view dbName, ignored;
+               auto olmDbCursor = lmdb::cursor::open(txn, mainDb);
+               while (olmDbCursor.get(dbName, ignored, MDB_NEXT)) {
+                   // skip every db but olm session dbs
+                   nhlog::db()->debug("Db {}", dbName);
+                   if (dbName.find("olm_sessions/") != 0)
+                       continue;
+
+                   nhlog::db()->debug("Migrating {}", dbName);
+
+                   auto olmDb = lmdb::dbi::open(txn, std::string(dbName).c_str());
+
+                   std::string_view session_id, session_value;
+
+                   std::vector> sessions;
+
+                   auto cursor = lmdb::cursor::open(txn, olmDb);
+                   while (cursor.get(session_id, session_value, MDB_NEXT)) {
+                       nhlog::db()->debug(
+                         "session_id {}, session_value {}", session_id, session_value);
+                       StoredOlmSession session;
+                       bool invalid = false;
+                       for (auto c : session_value)
+                           if (!isprint(c)) {
+                               invalid = true;
+                               break;
+                           }
+                       if (invalid)
+                           continue;
+
+                       nhlog::db()->debug("Not skipped");
+
+                       session.pickled_session = session_value;
+                       sessions.emplace_back(session_id, session);
+                   }
+                   cursor.close();
+
+                   olmDb.drop(txn, true);
+
+                   auto newDbName = std::string(dbName);
+                   newDbName.erase(0, sizeof("olm_sessions") - 1);
+                   newDbName = "olm_sessions.v2" + newDbName;
+
+                   auto newDb = lmdb::dbi::open(txn, newDbName.c_str(), MDB_CREATE);
+
+                   for (const auto &[key, value] : sessions) {
+                       // nhlog::db()->debug("{}\n{}", key, json(value).dump());
+                       newDb.put(txn, key, json(value).dump());
+                   }
+               }
+               olmDbCursor.close();
+
+               txn.commit();
+           } catch (const lmdb::error &) {
+               nhlog::db()->critical("Failed to migrate olm sessions,");
+               return false;
+           }
+
+           nhlog::db()->info("Successfully migrated olm sessions.");
+           return true;
+       }},
+      {"2021.08.22",
+       [this]() {
+           try {
+               auto txn      = lmdb::txn::begin(env_, nullptr);
+               auto try_drop = [&txn](const std::string &dbName) {
+                   try {
+                       auto db = lmdb::dbi::open(txn, dbName.c_str());
+                       db.drop(txn, true);
+                   } catch (std::exception &e) {
+                       nhlog::db()->warn("Failed to drop '{}': {}", dbName, e.what());
+                   }
+               };
+
+               auto room_ids = getRoomIds(txn);
+
+               for (const auto &room : room_ids) {
+                   try_drop(room + "/state");
+                   try_drop(room + "/state_by_key");
+                   try_drop(room + "/account_data");
+                   try_drop(room + "/members");
+                   try_drop(room + "/mentions");
+                   try_drop(room + "/events");
+                   try_drop(room + "/event_order");
+                   try_drop(room + "/event2order");
+                   try_drop(room + "/msg2order");
+                   try_drop(room + "/order2msg");
+                   try_drop(room + "/pending");
+                   try_drop(room + "/related");
+               }
+
+               // clear db, don't delete
+               roomsDb_.drop(txn, false);
+               setNextBatchToken(txn, "");
+
+               txn.commit();
+           } catch (const lmdb::error &) {
+               nhlog::db()->critical("Failed to clear cache!");
+               return false;
+           }
+
+           nhlog::db()->info("Successfully cleared the cache. Will do a clean sync after startup.");
+           return true;
+       }},
+      {"2021.08.31",
+       [this]() {
+           storeSecret("pickle_secret", "secret", true);
+           return true;
+       }},
+    };
+
+    nhlog::db()->info("Running migrations, this may take a while!");
+    for (const auto &[target_version, migration] : migrations) {
+        if (target_version > stored_version)
+            if (!migration()) {
+                nhlog::db()->critical("migration failure!");
+                return false;
+            }
+    }
+    nhlog::db()->info("Migrations finished.");
+
+    setCurrentFormat();
+    return true;
+}
+
+cache::CacheVersion
+Cache::formatVersion()
+{
+    auto txn = ro_txn(env_);
+
+    std::string_view current_version;
+    bool res = syncStateDb_.get(txn, CACHE_FORMAT_VERSION_KEY, current_version);
+
+    if (!res)
+        return cache::CacheVersion::Older;
+
+    std::string stored_version(current_version.data(), current_version.size());
+
+    if (stored_version < CURRENT_CACHE_FORMAT_VERSION)
+        return cache::CacheVersion::Older;
+    else if (stored_version > CURRENT_CACHE_FORMAT_VERSION)
+        return cache::CacheVersion::Older;
+    else
+        return cache::CacheVersion::Current;
 }
 
 void
 Cache::setCurrentFormat()
 {
-        auto txn = lmdb::txn::begin(env_);
+    auto txn = lmdb::txn::begin(env_);
 
-        syncStateDb_.put(txn, CACHE_FORMAT_VERSION_KEY, CURRENT_CACHE_FORMAT_VERSION);
+    syncStateDb_.put(txn, CACHE_FORMAT_VERSION_KEY, CURRENT_CACHE_FORMAT_VERSION);
 
-        txn.commit();
+    txn.commit();
 }
 
 CachedReceipts
 Cache::readReceipts(const QString &event_id, const QString &room_id)
 {
-        CachedReceipts receipts;
+    CachedReceipts receipts;
 
-        ReadReceiptKey receipt_key{event_id.toStdString(), room_id.toStdString()};
-        nlohmann::json json_key = receipt_key;
+    ReadReceiptKey receipt_key{event_id.toStdString(), room_id.toStdString()};
+    nlohmann::json json_key = receipt_key;
 
-        try {
-                auto txn = ro_txn(env_);
-                auto key = json_key.dump();
+    try {
+        auto txn = ro_txn(env_);
+        auto key = json_key.dump();
 
-                std::string_view value;
+        std::string_view value;
 
-                bool res = readReceiptsDb_.get(txn, key, value);
+        bool res = readReceiptsDb_.get(txn, key, value);
 
-                if (res) {
-                        auto json_response =
-                          json::parse(std::string_view(value.data(), value.size()));
-                        auto values = json_response.get>();
+        if (res) {
+            auto json_response = json::parse(std::string_view(value.data(), value.size()));
+            auto values        = json_response.get>();
 
-                        for (const auto &v : values)
-                                // timestamp, user_id
-                                receipts.emplace(v.second, v.first);
-                }
-
-        } catch (const lmdb::error &e) {
-                nhlog::db()->critical("readReceipts: {}", e.what());
+            for (const auto &v : values)
+                // timestamp, user_id
+                receipts.emplace(v.second, v.first);
         }
 
-        return receipts;
+    } catch (const lmdb::error &e) {
+        nhlog::db()->critical("readReceipts: {}", e.what());
+    }
+
+    return receipts;
 }
 
 void
 Cache::updateReadReceipt(lmdb::txn &txn, const std::string &room_id, const Receipts &receipts)
 {
-        auto user_id = this->localUserId_.toStdString();
-        for (const auto &receipt : receipts) {
-                const auto event_id = receipt.first;
-                auto event_receipts = receipt.second;
+    auto user_id = this->localUserId_.toStdString();
+    for (const auto &receipt : receipts) {
+        const auto event_id = receipt.first;
+        auto event_receipts = receipt.second;
 
-                ReadReceiptKey receipt_key{event_id, room_id};
-                nlohmann::json json_key = receipt_key;
+        ReadReceiptKey receipt_key{event_id, room_id};
+        nlohmann::json json_key = receipt_key;
 
-                try {
-                        const auto key = json_key.dump();
+        try {
+            const auto key = json_key.dump();
 
-                        std::string_view prev_value;
+            std::string_view prev_value;
 
-                        bool exists = readReceiptsDb_.get(txn, key, prev_value);
+            bool exists = readReceiptsDb_.get(txn, key, prev_value);
 
-                        std::map saved_receipts;
+            std::map saved_receipts;
 
-                        // If an entry for the event id already exists, we would
-                        // merge the existing receipts with the new ones.
-                        if (exists) {
-                                auto json_value = json::parse(
-                                  std::string_view(prev_value.data(), prev_value.size()));
+            // If an entry for the event id already exists, we would
+            // merge the existing receipts with the new ones.
+            if (exists) {
+                auto json_value =
+                  json::parse(std::string_view(prev_value.data(), prev_value.size()));
 
-                                // Retrieve the saved receipts.
-                                saved_receipts = json_value.get>();
-                        }
+                // Retrieve the saved receipts.
+                saved_receipts = json_value.get>();
+            }
 
-                        // Append the new ones.
-                        for (const auto &[read_by, timestamp] : event_receipts) {
-                                if (read_by == user_id) {
-                                        emit removeNotification(QString::fromStdString(room_id),
-                                                                QString::fromStdString(event_id));
-                                }
-                                saved_receipts.emplace(read_by, timestamp);
-                        }
-
-                        // Save back the merged (or only the new) receipts.
-                        nlohmann::json json_updated_value = saved_receipts;
-                        std::string merged_receipts       = json_updated_value.dump();
-
-                        readReceiptsDb_.put(txn, key, merged_receipts);
-
-                } catch (const lmdb::error &e) {
-                        nhlog::db()->critical("updateReadReceipts: {}", e.what());
+            // Append the new ones.
+            for (const auto &[read_by, timestamp] : event_receipts) {
+                if (read_by == user_id) {
+                    emit removeNotification(QString::fromStdString(room_id),
+                                            QString::fromStdString(event_id));
                 }
+                saved_receipts.emplace(read_by, timestamp);
+            }
+
+            // Save back the merged (or only the new) receipts.
+            nlohmann::json json_updated_value = saved_receipts;
+            std::string merged_receipts       = json_updated_value.dump();
+
+            readReceiptsDb_.put(txn, key, merged_receipts);
+
+        } catch (const lmdb::error &e) {
+            nhlog::db()->critical("updateReadReceipts: {}", e.what());
         }
+    }
 }
 
 void
 Cache::calculateRoomReadStatus()
 {
-        const auto joined_rooms = joinedRooms();
+    const auto joined_rooms = joinedRooms();
 
-        std::map readStatus;
+    std::map readStatus;
 
-        for (const auto &room : joined_rooms)
-                readStatus.emplace(QString::fromStdString(room), calculateRoomReadStatus(room));
+    for (const auto &room : joined_rooms)
+        readStatus.emplace(QString::fromStdString(room), calculateRoomReadStatus(room));
 
-        emit roomReadStatus(readStatus);
+    emit roomReadStatus(readStatus);
 }
 
 bool
 Cache::calculateRoomReadStatus(const std::string &room_id)
 {
-        std::string last_event_id_, fullyReadEventId_;
-        {
-                auto txn = ro_txn(env_);
+    std::string last_event_id_, fullyReadEventId_;
+    {
+        auto txn = ro_txn(env_);
 
-                // Get last event id on the room.
-                const auto last_event_id = getLastEventId(txn, room_id);
-                const auto localUser     = utils::localUser().toStdString();
+        // Get last event id on the room.
+        const auto last_event_id = getLastEventId(txn, room_id);
+        const auto localUser     = utils::localUser().toStdString();
 
-                std::string fullyReadEventId;
-                if (auto ev = getAccountData(txn, mtx::events::EventType::FullyRead, room_id)) {
-                        if (auto fr = std::get_if<
-                              mtx::events::AccountDataEvent>(
-                              &ev.value())) {
-                                fullyReadEventId = fr->content.event_id;
-                        }
-                }
-
-                if (last_event_id.empty() || fullyReadEventId.empty())
-                        return true;
-
-                if (last_event_id == fullyReadEventId)
-                        return false;
-
-                last_event_id_    = std::string(last_event_id);
-                fullyReadEventId_ = std::string(fullyReadEventId);
+        std::string fullyReadEventId;
+        if (auto ev = getAccountData(txn, mtx::events::EventType::FullyRead, room_id)) {
+            if (auto fr =
+                  std::get_if>(
+                    &ev.value())) {
+                fullyReadEventId = fr->content.event_id;
+            }
         }
 
-        // Retrieve all read receipts for that event.
-        return getEventIndex(room_id, last_event_id_) > getEventIndex(room_id, fullyReadEventId_);
+        if (last_event_id.empty() || fullyReadEventId.empty())
+            return true;
+
+        if (last_event_id == fullyReadEventId)
+            return false;
+
+        last_event_id_    = std::string(last_event_id);
+        fullyReadEventId_ = std::string(fullyReadEventId);
+    }
+
+    // Retrieve all read receipts for that event.
+    return getEventIndex(room_id, last_event_id_) > getEventIndex(room_id, fullyReadEventId_);
 }
 
 void
 Cache::saveState(const mtx::responses::Sync &res)
 {
-        using namespace mtx::events;
-        auto local_user_id = this->localUserId_.toStdString();
+    using namespace mtx::events;
+    auto local_user_id = this->localUserId_.toStdString();
 
-        auto currentBatchToken = nextBatchToken();
+    auto currentBatchToken = nextBatchToken();
 
-        auto txn = lmdb::txn::begin(env_);
+    auto txn = lmdb::txn::begin(env_);
 
-        setNextBatchToken(txn, res.next_batch);
+    setNextBatchToken(txn, res.next_batch);
 
-        if (!res.account_data.events.empty()) {
-                auto accountDataDb = getAccountDataDb(txn, "");
-                for (const auto &ev : res.account_data.events)
-                        std::visit(
-                          [&txn, &accountDataDb](const auto &event) {
-                                  auto j = json(event);
-                                  accountDataDb.put(txn, j["type"].get(), j.dump());
-                          },
-                          ev);
+    if (!res.account_data.events.empty()) {
+        auto accountDataDb = getAccountDataDb(txn, "");
+        for (const auto &ev : res.account_data.events)
+            std::visit(
+              [&txn, &accountDataDb](const auto &event) {
+                  auto j = json(event);
+                  accountDataDb.put(txn, j["type"].get(), j.dump());
+              },
+              ev);
+    }
+
+    auto userKeyCacheDb = getUserKeysDb(txn);
+
+    std::set spaces_with_updates;
+    std::set rooms_with_space_updates;
+
+    // Save joined rooms
+    for (const auto &room : res.rooms.join) {
+        auto statesdb    = getStatesDb(txn, room.first);
+        auto stateskeydb = getStatesKeyDb(txn, room.first);
+        auto membersdb   = getMembersDb(txn, room.first);
+        auto eventsDb    = getEventsDb(txn, room.first);
+
+        saveStateEvents(
+          txn, statesdb, stateskeydb, membersdb, eventsDb, room.first, room.second.state.events);
+        saveStateEvents(
+          txn, statesdb, stateskeydb, membersdb, eventsDb, room.first, room.second.timeline.events);
+
+        saveTimelineMessages(txn, eventsDb, room.first, room.second.timeline);
+
+        RoomInfo updatedInfo;
+        updatedInfo.name       = getRoomName(txn, statesdb, membersdb).toStdString();
+        updatedInfo.topic      = getRoomTopic(txn, statesdb).toStdString();
+        updatedInfo.avatar_url = getRoomAvatarUrl(txn, statesdb, membersdb).toStdString();
+        updatedInfo.version    = getRoomVersion(txn, statesdb).toStdString();
+        updatedInfo.is_space   = getRoomIsSpace(txn, statesdb);
+
+        if (updatedInfo.is_space) {
+            bool space_updates = false;
+            for (const auto &e : room.second.state.events)
+                if (std::holds_alternative>(e) ||
+                    std::holds_alternative>(e))
+                    space_updates = true;
+            for (const auto &e : room.second.timeline.events)
+                if (std::holds_alternative>(e) ||
+                    std::holds_alternative>(e))
+                    space_updates = true;
+
+            if (space_updates)
+                spaces_with_updates.insert(room.first);
         }
 
-        auto userKeyCacheDb = getUserKeysDb(txn);
-
-        std::set spaces_with_updates;
-        std::set rooms_with_space_updates;
-
-        // Save joined rooms
-        for (const auto &room : res.rooms.join) {
-                auto statesdb    = getStatesDb(txn, room.first);
-                auto stateskeydb = getStatesKeyDb(txn, room.first);
-                auto membersdb   = getMembersDb(txn, room.first);
-                auto eventsDb    = getEventsDb(txn, room.first);
-
-                saveStateEvents(txn,
-                                statesdb,
-                                stateskeydb,
-                                membersdb,
-                                eventsDb,
-                                room.first,
-                                room.second.state.events);
-                saveStateEvents(txn,
-                                statesdb,
-                                stateskeydb,
-                                membersdb,
-                                eventsDb,
-                                room.first,
-                                room.second.timeline.events);
-
-                saveTimelineMessages(txn, eventsDb, room.first, room.second.timeline);
-
-                RoomInfo updatedInfo;
-                updatedInfo.name       = getRoomName(txn, statesdb, membersdb).toStdString();
-                updatedInfo.topic      = getRoomTopic(txn, statesdb).toStdString();
-                updatedInfo.avatar_url = getRoomAvatarUrl(txn, statesdb, membersdb).toStdString();
-                updatedInfo.version    = getRoomVersion(txn, statesdb).toStdString();
-                updatedInfo.is_space   = getRoomIsSpace(txn, statesdb);
-
-                if (updatedInfo.is_space) {
-                        bool space_updates = false;
-                        for (const auto &e : room.second.state.events)
-                                if (std::holds_alternative>(e) ||
-                                    std::holds_alternative>(e))
-                                        space_updates = true;
-                        for (const auto &e : room.second.timeline.events)
-                                if (std::holds_alternative>(e) ||
-                                    std::holds_alternative>(e))
-                                        space_updates = true;
-
-                        if (space_updates)
-                                spaces_with_updates.insert(room.first);
+        {
+            bool room_has_space_update = false;
+            for (const auto &e : room.second.state.events) {
+                if (auto se = std::get_if>(&e)) {
+                    spaces_with_updates.insert(se->state_key);
+                    room_has_space_update = true;
                 }
-
-                {
-                        bool room_has_space_update = false;
-                        for (const auto &e : room.second.state.events) {
-                                if (auto se = std::get_if>(&e)) {
-                                        spaces_with_updates.insert(se->state_key);
-                                        room_has_space_update = true;
-                                }
-                        }
-                        for (const auto &e : room.second.timeline.events) {
-                                if (auto se = std::get_if>(&e)) {
-                                        spaces_with_updates.insert(se->state_key);
-                                        room_has_space_update = true;
-                                }
-                        }
-
-                        if (room_has_space_update)
-                                rooms_with_space_updates.insert(room.first);
+            }
+            for (const auto &e : room.second.timeline.events) {
+                if (auto se = std::get_if>(&e)) {
+                    spaces_with_updates.insert(se->state_key);
+                    room_has_space_update = true;
                 }
+            }
 
-                bool has_new_tags = false;
-                // Process the account_data associated with this room
-                if (!room.second.account_data.events.empty()) {
-                        auto accountDataDb = getAccountDataDb(txn, room.first);
-
-                        for (const auto &evt : room.second.account_data.events) {
-                                std::visit(
-                                  [&txn, &accountDataDb](const auto &event) {
-                                          auto j = json(event);
-                                          accountDataDb.put(
-                                            txn, j["type"].get(), j.dump());
-                                  },
-                                  evt);
-
-                                // for tag events
-                                if (std::holds_alternative>(
-                                      evt)) {
-                                        auto tags_evt =
-                                          std::get>(evt);
-                                        has_new_tags = true;
-                                        for (const auto &tag : tags_evt.content.tags) {
-                                                updatedInfo.tags.push_back(tag.first);
-                                        }
-                                }
-                                if (auto fr = std::get_if>(&evt)) {
-                                        nhlog::db()->debug("Fully read: {}", fr->content.event_id);
-                                }
-                        }
-                }
-                if (!has_new_tags) {
-                        // retrieve the old tags, they haven't changed
-                        std::string_view data;
-                        if (roomsDb_.get(txn, room.first, data)) {
-                                try {
-                                        RoomInfo tmp =
-                                          json::parse(std::string_view(data.data(), data.size()));
-                                        updatedInfo.tags = tmp.tags;
-                                } catch (const json::exception &e) {
-                                        nhlog::db()->warn(
-                                          "failed to parse room info: room_id ({}), {}: {}",
-                                          room.first,
-                                          std::string(data.data(), data.size()),
-                                          e.what());
-                                }
-                        }
-                }
-
-                roomsDb_.put(txn, room.first, json(updatedInfo).dump());
-
-                for (const auto &e : room.second.ephemeral.events) {
-                        if (auto receiptsEv = std::get_if<
-                              mtx::events::EphemeralEvent>(&e)) {
-                                Receipts receipts;
-
-                                for (const auto &[event_id, userReceipts] :
-                                     receiptsEv->content.receipts) {
-                                        for (const auto &[user_id, receipt] : userReceipts.users) {
-                                                receipts[event_id][user_id] = receipt.ts;
-                                        }
-                                }
-                                updateReadReceipt(txn, room.first, receipts);
-                        }
-                }
-
-                // Clean up non-valid invites.
-                removeInvite(txn, room.first);
+            if (room_has_space_update)
+                rooms_with_space_updates.insert(room.first);
         }
 
-        saveInvites(txn, res.rooms.invite);
+        bool has_new_tags = false;
+        // Process the account_data associated with this room
+        if (!room.second.account_data.events.empty()) {
+            auto accountDataDb = getAccountDataDb(txn, room.first);
 
-        savePresence(txn, res.presence);
+            for (const auto &evt : room.second.account_data.events) {
+                std::visit(
+                  [&txn, &accountDataDb](const auto &event) {
+                      auto j = json(event);
+                      accountDataDb.put(txn, j["type"].get(), j.dump());
+                  },
+                  evt);
 
-        markUserKeysOutOfDate(txn, userKeyCacheDb, res.device_lists.changed, currentBatchToken);
-
-        removeLeftRooms(txn, res.rooms.leave);
-
-        updateSpaces(txn, spaces_with_updates, std::move(rooms_with_space_updates));
-
-        txn.commit();
-
-        std::map readStatus;
-
-        for (const auto &room : res.rooms.join) {
-                for (const auto &e : room.second.ephemeral.events) {
-                        if (auto receiptsEv = std::get_if<
-                              mtx::events::EphemeralEvent>(&e)) {
-                                std::vector receipts;
-
-                                for (const auto &[event_id, userReceipts] :
-                                     receiptsEv->content.receipts) {
-                                        for (const auto &[user_id, receipt] : userReceipts.users) {
-                                                (void)receipt;
-
-                                                if (user_id != local_user_id) {
-                                                        receipts.push_back(
-                                                          QString::fromStdString(event_id));
-                                                        break;
-                                                }
-                                        }
-                                }
-                                if (!receipts.empty())
-                                        emit newReadReceipts(QString::fromStdString(room.first),
-                                                             receipts);
-                        }
+                // for tag events
+                if (std::holds_alternative>(evt)) {
+                    auto tags_evt = std::get>(evt);
+                    has_new_tags  = true;
+                    for (const auto &tag : tags_evt.content.tags) {
+                        updatedInfo.tags.push_back(tag.first);
+                    }
                 }
-                readStatus.emplace(QString::fromStdString(room.first),
-                                   calculateRoomReadStatus(room.first));
+                if (auto fr = std::get_if<
+                      mtx::events::AccountDataEvent>(&evt)) {
+                    nhlog::db()->debug("Fully read: {}", fr->content.event_id);
+                }
+            }
+        }
+        if (!has_new_tags) {
+            // retrieve the old tags, they haven't changed
+            std::string_view data;
+            if (roomsDb_.get(txn, room.first, data)) {
+                try {
+                    RoomInfo tmp     = json::parse(std::string_view(data.data(), data.size()));
+                    updatedInfo.tags = tmp.tags;
+                } catch (const json::exception &e) {
+                    nhlog::db()->warn("failed to parse room info: room_id ({}), {}: {}",
+                                      room.first,
+                                      std::string(data.data(), data.size()),
+                                      e.what());
+                }
+            }
         }
 
-        emit roomReadStatus(readStatus);
+        roomsDb_.put(txn, room.first, json(updatedInfo).dump());
+
+        for (const auto &e : room.second.ephemeral.events) {
+            if (auto receiptsEv =
+                  std::get_if>(&e)) {
+                Receipts receipts;
+
+                for (const auto &[event_id, userReceipts] : receiptsEv->content.receipts) {
+                    for (const auto &[user_id, receipt] : userReceipts.users) {
+                        receipts[event_id][user_id] = receipt.ts;
+                    }
+                }
+                updateReadReceipt(txn, room.first, receipts);
+            }
+        }
+
+        // Clean up non-valid invites.
+        removeInvite(txn, room.first);
+    }
+
+    saveInvites(txn, res.rooms.invite);
+
+    savePresence(txn, res.presence);
+
+    markUserKeysOutOfDate(txn, userKeyCacheDb, res.device_lists.changed, currentBatchToken);
+
+    removeLeftRooms(txn, res.rooms.leave);
+
+    updateSpaces(txn, spaces_with_updates, std::move(rooms_with_space_updates));
+
+    txn.commit();
+
+    std::map readStatus;
+
+    for (const auto &room : res.rooms.join) {
+        for (const auto &e : room.second.ephemeral.events) {
+            if (auto receiptsEv =
+                  std::get_if>(&e)) {
+                std::vector receipts;
+
+                for (const auto &[event_id, userReceipts] : receiptsEv->content.receipts) {
+                    for (const auto &[user_id, receipt] : userReceipts.users) {
+                        (void)receipt;
+
+                        if (user_id != local_user_id) {
+                            receipts.push_back(QString::fromStdString(event_id));
+                            break;
+                        }
+                    }
+                }
+                if (!receipts.empty())
+                    emit newReadReceipts(QString::fromStdString(room.first), receipts);
+            }
+        }
+        readStatus.emplace(QString::fromStdString(room.first), calculateRoomReadStatus(room.first));
+    }
+
+    emit roomReadStatus(readStatus);
 }
 
 void
 Cache::saveInvites(lmdb::txn &txn, const std::map &rooms)
 {
-        for (const auto &room : rooms) {
-                auto statesdb  = getInviteStatesDb(txn, room.first);
-                auto membersdb = getInviteMembersDb(txn, room.first);
+    for (const auto &room : rooms) {
+        auto statesdb  = getInviteStatesDb(txn, room.first);
+        auto membersdb = getInviteMembersDb(txn, room.first);
 
-                saveInvite(txn, statesdb, membersdb, room.second);
+        saveInvite(txn, statesdb, membersdb, room.second);
 
-                RoomInfo updatedInfo;
-                updatedInfo.name  = getInviteRoomName(txn, statesdb, membersdb).toStdString();
-                updatedInfo.topic = getInviteRoomTopic(txn, statesdb).toStdString();
-                updatedInfo.avatar_url =
-                  getInviteRoomAvatarUrl(txn, statesdb, membersdb).toStdString();
-                updatedInfo.is_space  = getInviteRoomIsSpace(txn, statesdb);
-                updatedInfo.is_invite = true;
+        RoomInfo updatedInfo;
+        updatedInfo.name       = getInviteRoomName(txn, statesdb, membersdb).toStdString();
+        updatedInfo.topic      = getInviteRoomTopic(txn, statesdb).toStdString();
+        updatedInfo.avatar_url = getInviteRoomAvatarUrl(txn, statesdb, membersdb).toStdString();
+        updatedInfo.is_space   = getInviteRoomIsSpace(txn, statesdb);
+        updatedInfo.is_invite  = true;
 
-                invitesDb_.put(txn, room.first, json(updatedInfo).dump());
-        }
+        invitesDb_.put(txn, room.first, json(updatedInfo).dump());
+    }
 }
 
 void
@@ -1638,32 +1586,29 @@ Cache::saveInvite(lmdb::txn &txn,
                   lmdb::dbi &membersdb,
                   const mtx::responses::InvitedRoom &room)
 {
-        using namespace mtx::events;
-        using namespace mtx::events::state;
+    using namespace mtx::events;
+    using namespace mtx::events::state;
 
-        for (const auto &e : room.invite_state) {
-                if (auto msg = std::get_if>(&e)) {
-                        auto display_name = msg->content.display_name.empty()
-                                              ? msg->state_key
-                                              : msg->content.display_name;
+    for (const auto &e : room.invite_state) {
+        if (auto msg = std::get_if>(&e)) {
+            auto display_name =
+              msg->content.display_name.empty() ? msg->state_key : msg->content.display_name;
 
-                        MemberInfo tmp{display_name, msg->content.avatar_url};
+            MemberInfo tmp{display_name, msg->content.avatar_url};
 
-                        membersdb.put(txn, msg->state_key, json(tmp).dump());
-                } else {
-                        std::visit(
-                          [&txn, &statesdb](auto msg) {
-                                  auto j = json(msg);
-                                  bool res =
-                                    statesdb.put(txn, j["type"].get(), j.dump());
+            membersdb.put(txn, msg->state_key, json(tmp).dump());
+        } else {
+            std::visit(
+              [&txn, &statesdb](auto msg) {
+                  auto j   = json(msg);
+                  bool res = statesdb.put(txn, j["type"].get(), j.dump());
 
-                                  if (!res)
-                                          nhlog::db()->warn("couldn't save data: {}",
-                                                            json(msg).dump());
-                          },
-                          e);
-                }
+                  if (!res)
+                      nhlog::db()->warn("couldn't save data: {}", json(msg).dump());
+              },
+              e);
         }
+    }
 }
 
 void
@@ -1671,281 +1616,279 @@ Cache::savePresence(
   lmdb::txn &txn,
   const std::vector> &presenceUpdates)
 {
-        for (const auto &update : presenceUpdates) {
-                auto presenceDb = getPresenceDb(txn);
+    for (const auto &update : presenceUpdates) {
+        auto presenceDb = getPresenceDb(txn);
 
-                presenceDb.put(txn, update.sender, json(update.content).dump());
-        }
+        presenceDb.put(txn, update.sender, json(update.content).dump());
+    }
 }
 
 std::vector
 Cache::roomsWithStateUpdates(const mtx::responses::Sync &res)
 {
-        std::vector rooms;
-        for (const auto &room : res.rooms.join) {
-                bool hasUpdates = false;
-                for (const auto &s : room.second.state.events) {
-                        if (containsStateUpdates(s)) {
-                                hasUpdates = true;
-                                break;
-                        }
-                }
-
-                for (const auto &s : room.second.timeline.events) {
-                        if (containsStateUpdates(s)) {
-                                hasUpdates = true;
-                                break;
-                        }
-                }
-
-                if (hasUpdates)
-                        rooms.emplace_back(room.first);
+    std::vector rooms;
+    for (const auto &room : res.rooms.join) {
+        bool hasUpdates = false;
+        for (const auto &s : room.second.state.events) {
+            if (containsStateUpdates(s)) {
+                hasUpdates = true;
+                break;
+            }
         }
 
-        for (const auto &room : res.rooms.invite) {
-                for (const auto &s : room.second.invite_state) {
-                        if (containsStateUpdates(s)) {
-                                rooms.emplace_back(room.first);
-                                break;
-                        }
-                }
+        for (const auto &s : room.second.timeline.events) {
+            if (containsStateUpdates(s)) {
+                hasUpdates = true;
+                break;
+            }
         }
 
-        return rooms;
+        if (hasUpdates)
+            rooms.emplace_back(room.first);
+    }
+
+    for (const auto &room : res.rooms.invite) {
+        for (const auto &s : room.second.invite_state) {
+            if (containsStateUpdates(s)) {
+                rooms.emplace_back(room.first);
+                break;
+            }
+        }
+    }
+
+    return rooms;
 }
 
 RoomInfo
 Cache::singleRoomInfo(const std::string &room_id)
 {
-        auto txn = ro_txn(env_);
+    auto txn = ro_txn(env_);
 
-        try {
-                auto statesdb = getStatesDb(txn, room_id);
+    try {
+        auto statesdb = getStatesDb(txn, room_id);
 
-                std::string_view data;
+        std::string_view data;
 
-                // Check if the room is joined.
-                if (roomsDb_.get(txn, room_id, data)) {
-                        try {
-                                RoomInfo tmp     = json::parse(data);
-                                tmp.member_count = getMembersDb(txn, room_id).size(txn);
-                                tmp.join_rule    = getRoomJoinRule(txn, statesdb);
-                                tmp.guest_access = getRoomGuestAccess(txn, statesdb);
+        // Check if the room is joined.
+        if (roomsDb_.get(txn, room_id, data)) {
+            try {
+                RoomInfo tmp     = json::parse(data);
+                tmp.member_count = getMembersDb(txn, room_id).size(txn);
+                tmp.join_rule    = getRoomJoinRule(txn, statesdb);
+                tmp.guest_access = getRoomGuestAccess(txn, statesdb);
 
-                                return tmp;
-                        } catch (const json::exception &e) {
-                                nhlog::db()->warn("failed to parse room info: room_id ({}), {}: {}",
-                                                  room_id,
-                                                  std::string(data.data(), data.size()),
-                                                  e.what());
-                        }
-                }
-        } catch (const lmdb::error &e) {
-                nhlog::db()->warn(
-                  "failed to read room info from db: room_id ({}), {}", room_id, e.what());
+                return tmp;
+            } catch (const json::exception &e) {
+                nhlog::db()->warn("failed to parse room info: room_id ({}), {}: {}",
+                                  room_id,
+                                  std::string(data.data(), data.size()),
+                                  e.what());
+            }
         }
+    } catch (const lmdb::error &e) {
+        nhlog::db()->warn("failed to read room info from db: room_id ({}), {}", room_id, e.what());
+    }
 
-        return RoomInfo();
+    return RoomInfo();
 }
 
 std::map
 Cache::getRoomInfo(const std::vector &rooms)
 {
-        std::map room_info;
+    std::map room_info;
 
-        // TODO This should be read only.
-        auto txn = lmdb::txn::begin(env_);
+    // TODO This should be read only.
+    auto txn = lmdb::txn::begin(env_);
 
-        for (const auto &room : rooms) {
-                std::string_view data;
-                auto statesdb = getStatesDb(txn, room);
+    for (const auto &room : rooms) {
+        std::string_view data;
+        auto statesdb = getStatesDb(txn, room);
 
-                // Check if the room is joined.
-                if (roomsDb_.get(txn, room, data)) {
-                        try {
-                                RoomInfo tmp     = json::parse(data);
-                                tmp.member_count = getMembersDb(txn, room).size(txn);
-                                tmp.join_rule    = getRoomJoinRule(txn, statesdb);
-                                tmp.guest_access = getRoomGuestAccess(txn, statesdb);
+        // Check if the room is joined.
+        if (roomsDb_.get(txn, room, data)) {
+            try {
+                RoomInfo tmp     = json::parse(data);
+                tmp.member_count = getMembersDb(txn, room).size(txn);
+                tmp.join_rule    = getRoomJoinRule(txn, statesdb);
+                tmp.guest_access = getRoomGuestAccess(txn, statesdb);
 
-                                room_info.emplace(QString::fromStdString(room), std::move(tmp));
-                        } catch (const json::exception &e) {
-                                nhlog::db()->warn("failed to parse room info: room_id ({}), {}: {}",
-                                                  room,
-                                                  std::string(data.data(), data.size()),
-                                                  e.what());
-                        }
-                } else {
-                        // Check if the room is an invite.
-                        if (invitesDb_.get(txn, room, data)) {
-                                try {
-                                        RoomInfo tmp     = json::parse(std::string_view(data));
-                                        tmp.member_count = getInviteMembersDb(txn, room).size(txn);
+                room_info.emplace(QString::fromStdString(room), std::move(tmp));
+            } catch (const json::exception &e) {
+                nhlog::db()->warn("failed to parse room info: room_id ({}), {}: {}",
+                                  room,
+                                  std::string(data.data(), data.size()),
+                                  e.what());
+            }
+        } else {
+            // Check if the room is an invite.
+            if (invitesDb_.get(txn, room, data)) {
+                try {
+                    RoomInfo tmp     = json::parse(std::string_view(data));
+                    tmp.member_count = getInviteMembersDb(txn, room).size(txn);
 
-                                        room_info.emplace(QString::fromStdString(room),
-                                                          std::move(tmp));
-                                } catch (const json::exception &e) {
-                                        nhlog::db()->warn("failed to parse room info for invite: "
-                                                          "room_id ({}), {}: {}",
-                                                          room,
-                                                          std::string(data.data(), data.size()),
-                                                          e.what());
-                                }
-                        }
+                    room_info.emplace(QString::fromStdString(room), std::move(tmp));
+                } catch (const json::exception &e) {
+                    nhlog::db()->warn("failed to parse room info for invite: "
+                                      "room_id ({}), {}: {}",
+                                      room,
+                                      std::string(data.data(), data.size()),
+                                      e.what());
                 }
+            }
         }
+    }
 
-        txn.commit();
+    txn.commit();
 
-        return room_info;
+    return room_info;
 }
 
 std::vector
 Cache::roomIds()
 {
-        auto txn = ro_txn(env_);
+    auto txn = ro_txn(env_);
 
-        std::vector rooms;
-        std::string_view room_id, unused;
+    std::vector rooms;
+    std::string_view room_id, unused;
 
-        auto roomsCursor = lmdb::cursor::open(txn, roomsDb_);
-        while (roomsCursor.get(room_id, unused, MDB_NEXT))
-                rooms.push_back(QString::fromStdString(std::string(room_id)));
+    auto roomsCursor = lmdb::cursor::open(txn, roomsDb_);
+    while (roomsCursor.get(room_id, unused, MDB_NEXT))
+        rooms.push_back(QString::fromStdString(std::string(room_id)));
 
-        roomsCursor.close();
+    roomsCursor.close();
 
-        return rooms;
+    return rooms;
 }
 
 QMap
 Cache::getTimelineMentions()
 {
-        // TODO: Should be read-only, but getMentionsDb will attempt to create a DB
-        // if it doesn't exist, throwing an error.
-        auto txn = lmdb::txn::begin(env_, nullptr);
+    // TODO: Should be read-only, but getMentionsDb will attempt to create a DB
+    // if it doesn't exist, throwing an error.
+    auto txn = lmdb::txn::begin(env_, nullptr);
 
-        QMap notifs;
+    QMap notifs;
 
-        auto room_ids = getRoomIds(txn);
+    auto room_ids = getRoomIds(txn);
 
-        for (const auto &room_id : room_ids) {
-                auto roomNotifs                         = getTimelineMentionsForRoom(txn, room_id);
-                notifs[QString::fromStdString(room_id)] = roomNotifs;
-        }
+    for (const auto &room_id : room_ids) {
+        auto roomNotifs                         = getTimelineMentionsForRoom(txn, room_id);
+        notifs[QString::fromStdString(room_id)] = roomNotifs;
+    }
 
-        txn.commit();
+    txn.commit();
 
-        return notifs;
+    return notifs;
 }
 
 std::string
 Cache::previousBatchToken(const std::string &room_id)
 {
-        auto txn     = lmdb::txn::begin(env_, nullptr);
-        auto orderDb = getEventOrderDb(txn, room_id);
+    auto txn     = lmdb::txn::begin(env_, nullptr);
+    auto orderDb = getEventOrderDb(txn, room_id);
 
-        auto cursor = lmdb::cursor::open(txn, orderDb);
-        std::string_view indexVal, val;
-        if (!cursor.get(indexVal, val, MDB_FIRST)) {
-                return "";
-        }
+    auto cursor = lmdb::cursor::open(txn, orderDb);
+    std::string_view indexVal, val;
+    if (!cursor.get(indexVal, val, MDB_FIRST)) {
+        return "";
+    }
 
-        auto j = json::parse(val);
+    auto j = json::parse(val);
 
-        return j.value("prev_batch", "");
+    return j.value("prev_batch", "");
 }
 
 Cache::Messages
 Cache::getTimelineMessages(lmdb::txn &txn, const std::string &room_id, uint64_t index, bool forward)
 {
-        // TODO(nico): Limit the messages returned by this maybe?
-        auto orderDb  = getOrderToMessageDb(txn, room_id);
-        auto eventsDb = getEventsDb(txn, room_id);
+    // TODO(nico): Limit the messages returned by this maybe?
+    auto orderDb  = getOrderToMessageDb(txn, room_id);
+    auto eventsDb = getEventsDb(txn, room_id);
 
-        Messages messages{};
+    Messages messages{};
 
-        std::string_view indexVal, event_id;
+    std::string_view indexVal, event_id;
 
-        auto cursor = lmdb::cursor::open(txn, orderDb);
-        if (index == std::numeric_limits::max()) {
-                if (cursor.get(indexVal, event_id, forward ? MDB_FIRST : MDB_LAST)) {
-                        index = lmdb::from_sv(indexVal);
-                } else {
-                        messages.end_of_cache = true;
-                        return messages;
-                }
+    auto cursor = lmdb::cursor::open(txn, orderDb);
+    if (index == std::numeric_limits::max()) {
+        if (cursor.get(indexVal, event_id, forward ? MDB_FIRST : MDB_LAST)) {
+            index = lmdb::from_sv(indexVal);
         } else {
-                if (cursor.get(indexVal, event_id, MDB_SET)) {
-                        index = lmdb::from_sv(indexVal);
-                } else {
-                        messages.end_of_cache = true;
-                        return messages;
-                }
+            messages.end_of_cache = true;
+            return messages;
+        }
+    } else {
+        if (cursor.get(indexVal, event_id, MDB_SET)) {
+            index = lmdb::from_sv(indexVal);
+        } else {
+            messages.end_of_cache = true;
+            return messages;
+        }
+    }
+
+    int counter = 0;
+
+    bool ret;
+    while ((ret = cursor.get(indexVal,
+                             event_id,
+                             counter == 0 ? (forward ? MDB_FIRST : MDB_LAST)
+                                          : (forward ? MDB_NEXT : MDB_PREV))) &&
+           counter++ < BATCH_SIZE) {
+        std::string_view event;
+        bool success = eventsDb.get(txn, event_id, event);
+        if (!success)
+            continue;
+
+        mtx::events::collections::TimelineEvent te;
+        try {
+            mtx::events::collections::from_json(json::parse(event), te);
+        } catch (std::exception &e) {
+            nhlog::db()->error("Failed to parse message from cache {}", e.what());
+            continue;
         }
 
-        int counter = 0;
+        messages.timeline.events.push_back(std::move(te.data));
+    }
+    cursor.close();
 
-        bool ret;
-        while ((ret = cursor.get(indexVal,
-                                 event_id,
-                                 counter == 0 ? (forward ? MDB_FIRST : MDB_LAST)
-                                              : (forward ? MDB_NEXT : MDB_PREV))) &&
-               counter++ < BATCH_SIZE) {
-                std::string_view event;
-                bool success = eventsDb.get(txn, event_id, event);
-                if (!success)
-                        continue;
+    // std::reverse(timeline.events.begin(), timeline.events.end());
+    messages.next_index   = lmdb::from_sv(indexVal);
+    messages.end_of_cache = !ret;
 
-                mtx::events::collections::TimelineEvent te;
-                try {
-                        mtx::events::collections::from_json(json::parse(event), te);
-                } catch (std::exception &e) {
-                        nhlog::db()->error("Failed to parse message from cache {}", e.what());
-                        continue;
-                }
-
-                messages.timeline.events.push_back(std::move(te.data));
-        }
-        cursor.close();
-
-        // std::reverse(timeline.events.begin(), timeline.events.end());
-        messages.next_index   = lmdb::from_sv(indexVal);
-        messages.end_of_cache = !ret;
-
-        return messages;
+    return messages;
 }
 
 std::optional
 Cache::getEvent(const std::string &room_id, const std::string &event_id)
 {
-        auto txn      = ro_txn(env_);
-        auto eventsDb = getEventsDb(txn, room_id);
+    auto txn      = ro_txn(env_);
+    auto eventsDb = getEventsDb(txn, room_id);
 
-        std::string_view event{};
-        bool success = eventsDb.get(txn, event_id, event);
-        if (!success)
-                return {};
+    std::string_view event{};
+    bool success = eventsDb.get(txn, event_id, event);
+    if (!success)
+        return {};
 
-        mtx::events::collections::TimelineEvent te;
-        try {
-                mtx::events::collections::from_json(json::parse(event), te);
-        } catch (std::exception &e) {
-                nhlog::db()->error("Failed to parse message from cache {}", e.what());
-                return std::nullopt;
-        }
+    mtx::events::collections::TimelineEvent te;
+    try {
+        mtx::events::collections::from_json(json::parse(event), te);
+    } catch (std::exception &e) {
+        nhlog::db()->error("Failed to parse message from cache {}", e.what());
+        return std::nullopt;
+    }
 
-        return te;
+    return te;
 }
 void
 Cache::storeEvent(const std::string &room_id,
                   const std::string &event_id,
                   const mtx::events::collections::TimelineEvent &event)
 {
-        auto txn        = lmdb::txn::begin(env_);
-        auto eventsDb   = getEventsDb(txn, room_id);
-        auto event_json = mtx::accessors::serialize_event(event.data);
-        eventsDb.put(txn, event_id, event_json.dump());
-        txn.commit();
+    auto txn        = lmdb::txn::begin(env_);
+    auto eventsDb   = getEventsDb(txn, room_id);
+    auto event_json = mtx::accessors::serialize_event(event.data);
+    eventsDb.put(txn, event_id, event_json.dump());
+    txn.commit();
 }
 
 void
@@ -1953,962 +1896,944 @@ Cache::replaceEvent(const std::string &room_id,
                     const std::string &event_id,
                     const mtx::events::collections::TimelineEvent &event)
 {
-        auto txn         = lmdb::txn::begin(env_);
-        auto eventsDb    = getEventsDb(txn, room_id);
-        auto relationsDb = getRelationsDb(txn, room_id);
-        auto event_json  = mtx::accessors::serialize_event(event.data).dump();
+    auto txn         = lmdb::txn::begin(env_);
+    auto eventsDb    = getEventsDb(txn, room_id);
+    auto relationsDb = getRelationsDb(txn, room_id);
+    auto event_json  = mtx::accessors::serialize_event(event.data).dump();
 
-        {
-                eventsDb.del(txn, event_id);
-                eventsDb.put(txn, event_id, event_json);
-                for (auto relation : mtx::accessors::relations(event.data).relations) {
-                        relationsDb.put(txn, relation.event_id, event_id);
-                }
+    {
+        eventsDb.del(txn, event_id);
+        eventsDb.put(txn, event_id, event_json);
+        for (auto relation : mtx::accessors::relations(event.data).relations) {
+            relationsDb.put(txn, relation.event_id, event_id);
         }
+    }
 
-        txn.commit();
+    txn.commit();
 }
 
 std::vector
 Cache::relatedEvents(const std::string &room_id, const std::string &event_id)
 {
-        auto txn         = ro_txn(env_);
-        auto relationsDb = getRelationsDb(txn, room_id);
+    auto txn         = ro_txn(env_);
+    auto relationsDb = getRelationsDb(txn, room_id);
 
-        std::vector related_ids;
+    std::vector related_ids;
 
-        auto related_cursor         = lmdb::cursor::open(txn, relationsDb);
-        std::string_view related_to = event_id, related_event;
-        bool first                  = true;
+    auto related_cursor         = lmdb::cursor::open(txn, relationsDb);
+    std::string_view related_to = event_id, related_event;
+    bool first                  = true;
 
-        try {
-                if (!related_cursor.get(related_to, related_event, MDB_SET))
-                        return {};
+    try {
+        if (!related_cursor.get(related_to, related_event, MDB_SET))
+            return {};
 
-                while (related_cursor.get(
-                  related_to, related_event, first ? MDB_FIRST_DUP : MDB_NEXT_DUP)) {
-                        first = false;
-                        if (event_id != std::string_view(related_to.data(), related_to.size()))
-                                break;
+        while (
+          related_cursor.get(related_to, related_event, first ? MDB_FIRST_DUP : MDB_NEXT_DUP)) {
+            first = false;
+            if (event_id != std::string_view(related_to.data(), related_to.size()))
+                break;
 
-                        related_ids.emplace_back(related_event.data(), related_event.size());
-                }
-        } catch (const lmdb::error &e) {
-                nhlog::db()->error("related events error: {}", e.what());
+            related_ids.emplace_back(related_event.data(), related_event.size());
         }
+    } catch (const lmdb::error &e) {
+        nhlog::db()->error("related events error: {}", e.what());
+    }
 
-        return related_ids;
+    return related_ids;
 }
 
 size_t
 Cache::memberCount(const std::string &room_id)
 {
-        auto txn = ro_txn(env_);
-        return getMembersDb(txn, room_id).size(txn);
+    auto txn = ro_txn(env_);
+    return getMembersDb(txn, room_id).size(txn);
 }
 
 QMap
 Cache::roomInfo(bool withInvites)
 {
-        QMap result;
+    QMap result;
 
-        auto txn = ro_txn(env_);
+    auto txn = ro_txn(env_);
 
-        std::string_view room_id;
-        std::string_view room_data;
+    std::string_view room_id;
+    std::string_view room_data;
 
-        // Gather info about the joined rooms.
-        auto roomsCursor = lmdb::cursor::open(txn, roomsDb_);
-        while (roomsCursor.get(room_id, room_data, MDB_NEXT)) {
-                RoomInfo tmp     = json::parse(std::move(room_data));
-                tmp.member_count = getMembersDb(txn, std::string(room_id)).size(txn);
-                result.insert(QString::fromStdString(std::string(room_id)), std::move(tmp));
+    // Gather info about the joined rooms.
+    auto roomsCursor = lmdb::cursor::open(txn, roomsDb_);
+    while (roomsCursor.get(room_id, room_data, MDB_NEXT)) {
+        RoomInfo tmp     = json::parse(std::move(room_data));
+        tmp.member_count = getMembersDb(txn, std::string(room_id)).size(txn);
+        result.insert(QString::fromStdString(std::string(room_id)), std::move(tmp));
+    }
+    roomsCursor.close();
+
+    if (withInvites) {
+        // Gather info about the invites.
+        auto invitesCursor = lmdb::cursor::open(txn, invitesDb_);
+        while (invitesCursor.get(room_id, room_data, MDB_NEXT)) {
+            RoomInfo tmp     = json::parse(room_data);
+            tmp.member_count = getInviteMembersDb(txn, std::string(room_id)).size(txn);
+            result.insert(QString::fromStdString(std::string(room_id)), std::move(tmp));
         }
-        roomsCursor.close();
+        invitesCursor.close();
+    }
 
-        if (withInvites) {
-                // Gather info about the invites.
-                auto invitesCursor = lmdb::cursor::open(txn, invitesDb_);
-                while (invitesCursor.get(room_id, room_data, MDB_NEXT)) {
-                        RoomInfo tmp     = json::parse(room_data);
-                        tmp.member_count = getInviteMembersDb(txn, std::string(room_id)).size(txn);
-                        result.insert(QString::fromStdString(std::string(room_id)), std::move(tmp));
-                }
-                invitesCursor.close();
-        }
-
-        return result;
+    return result;
 }
 
 std::string
 Cache::getLastEventId(lmdb::txn &txn, const std::string &room_id)
 {
-        lmdb::dbi orderDb;
-        try {
-                orderDb = getOrderToMessageDb(txn, room_id);
-        } catch (lmdb::runtime_error &e) {
-                nhlog::db()->error("Can't open db for room '{}', probably doesn't exist yet. ({})",
-                                   room_id,
-                                   e.what());
-                return {};
-        }
+    lmdb::dbi orderDb;
+    try {
+        orderDb = getOrderToMessageDb(txn, room_id);
+    } catch (lmdb::runtime_error &e) {
+        nhlog::db()->error(
+          "Can't open db for room '{}', probably doesn't exist yet. ({})", room_id, e.what());
+        return {};
+    }
 
-        std::string_view indexVal, val;
+    std::string_view indexVal, val;
 
-        auto cursor = lmdb::cursor::open(txn, orderDb);
-        if (!cursor.get(indexVal, val, MDB_LAST)) {
-                return {};
-        }
+    auto cursor = lmdb::cursor::open(txn, orderDb);
+    if (!cursor.get(indexVal, val, MDB_LAST)) {
+        return {};
+    }
 
-        return std::string(val.data(), val.size());
+    return std::string(val.data(), val.size());
 }
 
 std::optional
 Cache::getTimelineRange(const std::string &room_id)
 {
-        auto txn = ro_txn(env_);
-        lmdb::dbi orderDb;
-        try {
-                orderDb = getOrderToMessageDb(txn, room_id);
-        } catch (lmdb::runtime_error &e) {
-                nhlog::db()->error("Can't open db for room '{}', probably doesn't exist yet. ({})",
-                                   room_id,
-                                   e.what());
-                return {};
-        }
+    auto txn = ro_txn(env_);
+    lmdb::dbi orderDb;
+    try {
+        orderDb = getOrderToMessageDb(txn, room_id);
+    } catch (lmdb::runtime_error &e) {
+        nhlog::db()->error(
+          "Can't open db for room '{}', probably doesn't exist yet. ({})", room_id, e.what());
+        return {};
+    }
 
-        std::string_view indexVal, val;
+    std::string_view indexVal, val;
 
-        auto cursor = lmdb::cursor::open(txn, orderDb);
-        if (!cursor.get(indexVal, val, MDB_LAST)) {
-                return {};
-        }
+    auto cursor = lmdb::cursor::open(txn, orderDb);
+    if (!cursor.get(indexVal, val, MDB_LAST)) {
+        return {};
+    }
 
-        TimelineRange range{};
-        range.last = lmdb::from_sv(indexVal);
+    TimelineRange range{};
+    range.last = lmdb::from_sv(indexVal);
 
-        if (!cursor.get(indexVal, val, MDB_FIRST)) {
-                return {};
-        }
-        range.first = lmdb::from_sv(indexVal);
+    if (!cursor.get(indexVal, val, MDB_FIRST)) {
+        return {};
+    }
+    range.first = lmdb::from_sv(indexVal);
 
-        return range;
+    return range;
 }
 std::optional
 Cache::getTimelineIndex(const std::string &room_id, std::string_view event_id)
 {
-        if (event_id.empty() || room_id.empty())
-                return {};
+    if (event_id.empty() || room_id.empty())
+        return {};
 
-        auto txn = ro_txn(env_);
+    auto txn = ro_txn(env_);
 
-        lmdb::dbi orderDb;
-        try {
-                orderDb = getMessageToOrderDb(txn, room_id);
-        } catch (lmdb::runtime_error &e) {
-                nhlog::db()->error("Can't open db for room '{}', probably doesn't exist yet. ({})",
-                                   room_id,
-                                   e.what());
-                return {};
-        }
+    lmdb::dbi orderDb;
+    try {
+        orderDb = getMessageToOrderDb(txn, room_id);
+    } catch (lmdb::runtime_error &e) {
+        nhlog::db()->error(
+          "Can't open db for room '{}', probably doesn't exist yet. ({})", room_id, e.what());
+        return {};
+    }
 
-        std::string_view indexVal{event_id.data(), event_id.size()}, val;
+    std::string_view indexVal{event_id.data(), event_id.size()}, val;
 
-        bool success = orderDb.get(txn, indexVal, val);
-        if (!success) {
-                return {};
-        }
+    bool success = orderDb.get(txn, indexVal, val);
+    if (!success) {
+        return {};
+    }
 
-        return lmdb::from_sv(val);
+    return lmdb::from_sv(val);
 }
 
 std::optional
 Cache::getEventIndex(const std::string &room_id, std::string_view event_id)
 {
-        if (room_id.empty() || event_id.empty())
-                return {};
+    if (room_id.empty() || event_id.empty())
+        return {};
 
-        auto txn = ro_txn(env_);
+    auto txn = ro_txn(env_);
 
-        lmdb::dbi orderDb;
-        try {
-                orderDb = getEventToOrderDb(txn, room_id);
-        } catch (lmdb::runtime_error &e) {
-                nhlog::db()->error("Can't open db for room '{}', probably doesn't exist yet. ({})",
-                                   room_id,
-                                   e.what());
-                return {};
-        }
+    lmdb::dbi orderDb;
+    try {
+        orderDb = getEventToOrderDb(txn, room_id);
+    } catch (lmdb::runtime_error &e) {
+        nhlog::db()->error(
+          "Can't open db for room '{}', probably doesn't exist yet. ({})", room_id, e.what());
+        return {};
+    }
 
-        std::string_view val;
+    std::string_view val;
 
-        bool success = orderDb.get(txn, event_id, val);
-        if (!success) {
-                return {};
-        }
+    bool success = orderDb.get(txn, event_id, val);
+    if (!success) {
+        return {};
+    }
 
-        return lmdb::from_sv(val);
+    return lmdb::from_sv(val);
 }
 
 std::optional>
 Cache::lastInvisibleEventAfter(const std::string &room_id, std::string_view event_id)
 {
-        if (room_id.empty() || event_id.empty())
-                return {};
+    if (room_id.empty() || event_id.empty())
+        return {};
 
-        auto txn = ro_txn(env_);
+    auto txn = ro_txn(env_);
 
-        lmdb::dbi orderDb;
-        lmdb::dbi eventOrderDb;
-        lmdb::dbi timelineDb;
-        try {
-                orderDb      = getEventToOrderDb(txn, room_id);
-                eventOrderDb = getEventOrderDb(txn, room_id);
-                timelineDb   = getMessageToOrderDb(txn, room_id);
-        } catch (lmdb::runtime_error &e) {
-                nhlog::db()->error("Can't open db for room '{}', probably doesn't exist yet. ({})",
-                                   room_id,
-                                   e.what());
-                return {};
-        }
+    lmdb::dbi orderDb;
+    lmdb::dbi eventOrderDb;
+    lmdb::dbi timelineDb;
+    try {
+        orderDb      = getEventToOrderDb(txn, room_id);
+        eventOrderDb = getEventOrderDb(txn, room_id);
+        timelineDb   = getMessageToOrderDb(txn, room_id);
+    } catch (lmdb::runtime_error &e) {
+        nhlog::db()->error(
+          "Can't open db for room '{}', probably doesn't exist yet. ({})", room_id, e.what());
+        return {};
+    }
 
-        std::string_view indexVal;
+    std::string_view indexVal;
 
-        bool success = orderDb.get(txn, event_id, indexVal);
-        if (!success) {
-                return {};
-        }
+    bool success = orderDb.get(txn, event_id, indexVal);
+    if (!success) {
+        return {};
+    }
 
-        try {
-                uint64_t prevIdx = lmdb::from_sv(indexVal);
-                std::string prevId{event_id};
-
-                auto cursor = lmdb::cursor::open(txn, eventOrderDb);
-                cursor.get(indexVal, MDB_SET);
-                while (cursor.get(indexVal, event_id, MDB_NEXT)) {
-                        std::string evId = json::parse(event_id)["event_id"].get();
-                        std::string_view temp;
-                        if (timelineDb.get(txn, evId, temp)) {
-                                return std::pair{prevIdx, std::string(prevId)};
-                        } else {
-                                prevIdx = lmdb::from_sv(indexVal);
-                                prevId  = std::move(evId);
-                        }
-                }
+    try {
+        uint64_t prevIdx = lmdb::from_sv(indexVal);
+        std::string prevId{event_id};
 
+        auto cursor = lmdb::cursor::open(txn, eventOrderDb);
+        cursor.get(indexVal, MDB_SET);
+        while (cursor.get(indexVal, event_id, MDB_NEXT)) {
+            std::string evId = json::parse(event_id)["event_id"].get();
+            std::string_view temp;
+            if (timelineDb.get(txn, evId, temp)) {
                 return std::pair{prevIdx, std::string(prevId)};
-        } catch (lmdb::runtime_error &e) {
-                nhlog::db()->error(
-                  "Failed to get last invisible event after {}", event_id, e.what());
-                return {};
+            } else {
+                prevIdx = lmdb::from_sv(indexVal);
+                prevId  = std::move(evId);
+            }
         }
+
+        return std::pair{prevIdx, std::string(prevId)};
+    } catch (lmdb::runtime_error &e) {
+        nhlog::db()->error("Failed to get last invisible event after {}", event_id, e.what());
+        return {};
+    }
 }
 
 std::optional
 Cache::getArrivalIndex(const std::string &room_id, std::string_view event_id)
 {
-        auto txn = ro_txn(env_);
+    auto txn = ro_txn(env_);
 
-        lmdb::dbi orderDb;
-        try {
-                orderDb = getEventToOrderDb(txn, room_id);
-        } catch (lmdb::runtime_error &e) {
-                nhlog::db()->error("Can't open db for room '{}', probably doesn't exist yet. ({})",
-                                   room_id,
-                                   e.what());
-                return {};
-        }
+    lmdb::dbi orderDb;
+    try {
+        orderDb = getEventToOrderDb(txn, room_id);
+    } catch (lmdb::runtime_error &e) {
+        nhlog::db()->error(
+          "Can't open db for room '{}', probably doesn't exist yet. ({})", room_id, e.what());
+        return {};
+    }
 
-        std::string_view val;
+    std::string_view val;
 
-        bool success = orderDb.get(txn, event_id, val);
-        if (!success) {
-                return {};
-        }
+    bool success = orderDb.get(txn, event_id, val);
+    if (!success) {
+        return {};
+    }
 
-        return lmdb::from_sv(val);
+    return lmdb::from_sv(val);
 }
 
 std::optional
 Cache::getTimelineEventId(const std::string &room_id, uint64_t index)
 {
-        auto txn = ro_txn(env_);
-        lmdb::dbi orderDb;
-        try {
-                orderDb = getOrderToMessageDb(txn, room_id);
-        } catch (lmdb::runtime_error &e) {
-                nhlog::db()->error("Can't open db for room '{}', probably doesn't exist yet. ({})",
-                                   room_id,
-                                   e.what());
-                return {};
-        }
+    auto txn = ro_txn(env_);
+    lmdb::dbi orderDb;
+    try {
+        orderDb = getOrderToMessageDb(txn, room_id);
+    } catch (lmdb::runtime_error &e) {
+        nhlog::db()->error(
+          "Can't open db for room '{}', probably doesn't exist yet. ({})", room_id, e.what());
+        return {};
+    }
 
-        std::string_view val;
+    std::string_view val;
 
-        bool success = orderDb.get(txn, lmdb::to_sv(index), val);
-        if (!success) {
-                return {};
-        }
+    bool success = orderDb.get(txn, lmdb::to_sv(index), val);
+    if (!success) {
+        return {};
+    }
 
-        return std::string(val);
+    return std::string(val);
 }
 
 QHash
 Cache::invites()
 {
-        QHash result;
+    QHash result;
 
-        auto txn    = ro_txn(env_);
-        auto cursor = lmdb::cursor::open(txn, invitesDb_);
+    auto txn    = ro_txn(env_);
+    auto cursor = lmdb::cursor::open(txn, invitesDb_);
 
-        std::string_view room_id, room_data;
+    std::string_view room_id, room_data;
 
-        while (cursor.get(room_id, room_data, MDB_NEXT)) {
-                try {
-                        RoomInfo tmp     = json::parse(room_data);
-                        tmp.member_count = getInviteMembersDb(txn, std::string(room_id)).size(txn);
-                        result.insert(QString::fromStdString(std::string(room_id)), std::move(tmp));
-                } catch (const json::exception &e) {
-                        nhlog::db()->warn("failed to parse room info for invite: "
-                                          "room_id ({}), {}: {}",
-                                          room_id,
-                                          std::string(room_data),
-                                          e.what());
-                }
+    while (cursor.get(room_id, room_data, MDB_NEXT)) {
+        try {
+            RoomInfo tmp     = json::parse(room_data);
+            tmp.member_count = getInviteMembersDb(txn, std::string(room_id)).size(txn);
+            result.insert(QString::fromStdString(std::string(room_id)), std::move(tmp));
+        } catch (const json::exception &e) {
+            nhlog::db()->warn("failed to parse room info for invite: "
+                              "room_id ({}), {}: {}",
+                              room_id,
+                              std::string(room_data),
+                              e.what());
         }
+    }
 
-        cursor.close();
+    cursor.close();
 
-        return result;
+    return result;
 }
 
 std::optional
 Cache::invite(std::string_view roomid)
 {
-        std::optional result;
+    std::optional result;
 
-        auto txn = ro_txn(env_);
+    auto txn = ro_txn(env_);
 
-        std::string_view room_data;
+    std::string_view room_data;
 
-        if (invitesDb_.get(txn, roomid, room_data)) {
-                try {
-                        RoomInfo tmp     = json::parse(room_data);
-                        tmp.member_count = getInviteMembersDb(txn, std::string(roomid)).size(txn);
-                        result           = std::move(tmp);
-                } catch (const json::exception &e) {
-                        nhlog::db()->warn("failed to parse room info for invite: "
-                                          "room_id ({}), {}: {}",
-                                          roomid,
-                                          std::string(room_data),
-                                          e.what());
-                }
+    if (invitesDb_.get(txn, roomid, room_data)) {
+        try {
+            RoomInfo tmp     = json::parse(room_data);
+            tmp.member_count = getInviteMembersDb(txn, std::string(roomid)).size(txn);
+            result           = std::move(tmp);
+        } catch (const json::exception &e) {
+            nhlog::db()->warn("failed to parse room info for invite: "
+                              "room_id ({}), {}: {}",
+                              roomid,
+                              std::string(room_data),
+                              e.what());
         }
+    }
 
-        return result;
+    return result;
 }
 
 QString
 Cache::getRoomAvatarUrl(lmdb::txn &txn, lmdb::dbi &statesdb, lmdb::dbi &membersdb)
 {
-        using namespace mtx::events;
-        using namespace mtx::events::state;
+    using namespace mtx::events;
+    using namespace mtx::events::state;
 
-        std::string_view event;
-        bool res = statesdb.get(txn, to_string(mtx::events::EventType::RoomAvatar), event);
+    std::string_view event;
+    bool res = statesdb.get(txn, to_string(mtx::events::EventType::RoomAvatar), event);
 
-        if (res) {
-                try {
-                        StateEvent msg =
-                          json::parse(std::string_view(event.data(), event.size()));
+    if (res) {
+        try {
+            StateEvent msg = json::parse(std::string_view(event.data(), event.size()));
 
-                        if (!msg.content.url.empty())
-                                return QString::fromStdString(msg.content.url);
-                } catch (const json::exception &e) {
-                        nhlog::db()->warn("failed to parse m.room.avatar event: {}", e.what());
-                }
+            if (!msg.content.url.empty())
+                return QString::fromStdString(msg.content.url);
+        } catch (const json::exception &e) {
+            nhlog::db()->warn("failed to parse m.room.avatar event: {}", e.what());
         }
+    }
 
-        // We don't use an avatar for group chats.
-        if (membersdb.size(txn) > 2)
-                return QString();
+    // We don't use an avatar for group chats.
+    if (membersdb.size(txn) > 2)
+        return QString();
 
-        auto cursor = lmdb::cursor::open(txn, membersdb);
-        std::string_view user_id;
-        std::string_view member_data;
-        std::string fallback_url;
+    auto cursor = lmdb::cursor::open(txn, membersdb);
+    std::string_view user_id;
+    std::string_view member_data;
+    std::string fallback_url;
 
-        // Resolve avatar for 1-1 chats.
-        while (cursor.get(user_id, member_data, MDB_NEXT)) {
-                try {
-                        MemberInfo m = json::parse(member_data);
-                        if (user_id == localUserId_.toStdString()) {
-                                fallback_url = m.avatar_url;
-                                continue;
-                        }
+    // Resolve avatar for 1-1 chats.
+    while (cursor.get(user_id, member_data, MDB_NEXT)) {
+        try {
+            MemberInfo m = json::parse(member_data);
+            if (user_id == localUserId_.toStdString()) {
+                fallback_url = m.avatar_url;
+                continue;
+            }
 
-                        cursor.close();
-                        return QString::fromStdString(m.avatar_url);
-                } catch (const json::exception &e) {
-                        nhlog::db()->warn("failed to parse member info: {}", e.what());
-                }
+            cursor.close();
+            return QString::fromStdString(m.avatar_url);
+        } catch (const json::exception &e) {
+            nhlog::db()->warn("failed to parse member info: {}", e.what());
         }
+    }
 
-        cursor.close();
+    cursor.close();
 
-        // Default case when there is only one member.
-        return QString::fromStdString(fallback_url);
+    // Default case when there is only one member.
+    return QString::fromStdString(fallback_url);
 }
 
 QString
 Cache::getRoomName(lmdb::txn &txn, lmdb::dbi &statesdb, lmdb::dbi &membersdb)
 {
-        using namespace mtx::events;
-        using namespace mtx::events::state;
+    using namespace mtx::events;
+    using namespace mtx::events::state;
 
-        std::string_view event;
-        bool res = statesdb.get(txn, to_string(mtx::events::EventType::RoomName), event);
+    std::string_view event;
+    bool res = statesdb.get(txn, to_string(mtx::events::EventType::RoomName), event);
 
-        if (res) {
-                try {
-                        StateEvent msg =
-                          json::parse(std::string_view(event.data(), event.size()));
+    if (res) {
+        try {
+            StateEvent msg = json::parse(std::string_view(event.data(), event.size()));
 
-                        if (!msg.content.name.empty())
-                                return QString::fromStdString(msg.content.name);
-                } catch (const json::exception &e) {
-                        nhlog::db()->warn("failed to parse m.room.name event: {}", e.what());
-                }
+            if (!msg.content.name.empty())
+                return QString::fromStdString(msg.content.name);
+        } catch (const json::exception &e) {
+            nhlog::db()->warn("failed to parse m.room.name event: {}", e.what());
+        }
+    }
+
+    res = statesdb.get(txn, to_string(mtx::events::EventType::RoomCanonicalAlias), event);
+
+    if (res) {
+        try {
+            StateEvent msg =
+              json::parse(std::string_view(event.data(), event.size()));
+
+            if (!msg.content.alias.empty())
+                return QString::fromStdString(msg.content.alias);
+        } catch (const json::exception &e) {
+            nhlog::db()->warn("failed to parse m.room.canonical_alias event: {}", e.what());
+        }
+    }
+
+    auto cursor      = lmdb::cursor::open(txn, membersdb);
+    const auto total = membersdb.size(txn);
+
+    std::size_t ii = 0;
+    std::string_view user_id;
+    std::string_view member_data;
+    std::map members;
+
+    while (cursor.get(user_id, member_data, MDB_NEXT) && ii < 3) {
+        try {
+            members.emplace(user_id, json::parse(member_data));
+        } catch (const json::exception &e) {
+            nhlog::db()->warn("failed to parse member info: {}", e.what());
         }
 
-        res = statesdb.get(txn, to_string(mtx::events::EventType::RoomCanonicalAlias), event);
+        ii++;
+    }
 
-        if (res) {
-                try {
-                        StateEvent msg =
-                          json::parse(std::string_view(event.data(), event.size()));
+    cursor.close();
 
-                        if (!msg.content.alias.empty())
-                                return QString::fromStdString(msg.content.alias);
-                } catch (const json::exception &e) {
-                        nhlog::db()->warn("failed to parse m.room.canonical_alias event: {}",
-                                          e.what());
-                }
+    if (total == 1 && !members.empty())
+        return QString::fromStdString(members.begin()->second.name);
+
+    auto first_member = [&members, this]() {
+        for (const auto &m : members) {
+            if (m.first != localUserId_.toStdString())
+                return QString::fromStdString(m.second.name);
         }
 
-        auto cursor      = lmdb::cursor::open(txn, membersdb);
-        const auto total = membersdb.size(txn);
+        return localUserId_;
+    }();
 
-        std::size_t ii = 0;
-        std::string_view user_id;
-        std::string_view member_data;
-        std::map members;
+    if (total == 2)
+        return first_member;
+    else if (total > 2)
+        return QString("%1 and %2 others").arg(first_member).arg(total - 1);
 
-        while (cursor.get(user_id, member_data, MDB_NEXT) && ii < 3) {
-                try {
-                        members.emplace(user_id, json::parse(member_data));
-                } catch (const json::exception &e) {
-                        nhlog::db()->warn("failed to parse member info: {}", e.what());
-                }
-
-                ii++;
-        }
-
-        cursor.close();
-
-        if (total == 1 && !members.empty())
-                return QString::fromStdString(members.begin()->second.name);
-
-        auto first_member = [&members, this]() {
-                for (const auto &m : members) {
-                        if (m.first != localUserId_.toStdString())
-                                return QString::fromStdString(m.second.name);
-                }
-
-                return localUserId_;
-        }();
-
-        if (total == 2)
-                return first_member;
-        else if (total > 2)
-                return QString("%1 and %2 others").arg(first_member).arg(total - 1);
-
-        return "Empty Room";
+    return "Empty Room";
 }
 
 mtx::events::state::JoinRule
 Cache::getRoomJoinRule(lmdb::txn &txn, lmdb::dbi &statesdb)
 {
-        using namespace mtx::events;
-        using namespace mtx::events::state;
+    using namespace mtx::events;
+    using namespace mtx::events::state;
 
-        std::string_view event;
-        bool res = statesdb.get(txn, to_string(mtx::events::EventType::RoomJoinRules), event);
+    std::string_view event;
+    bool res = statesdb.get(txn, to_string(mtx::events::EventType::RoomJoinRules), event);
 
-        if (res) {
-                try {
-                        StateEvent msg = json::parse(event);
-                        return msg.content.join_rule;
-                } catch (const json::exception &e) {
-                        nhlog::db()->warn("failed to parse m.room.join_rule event: {}", e.what());
-                }
+    if (res) {
+        try {
+            StateEvent msg = json::parse(event);
+            return msg.content.join_rule;
+        } catch (const json::exception &e) {
+            nhlog::db()->warn("failed to parse m.room.join_rule event: {}", e.what());
         }
-        return state::JoinRule::Knock;
+    }
+    return state::JoinRule::Knock;
 }
 
 bool
 Cache::getRoomGuestAccess(lmdb::txn &txn, lmdb::dbi &statesdb)
 {
-        using namespace mtx::events;
-        using namespace mtx::events::state;
+    using namespace mtx::events;
+    using namespace mtx::events::state;
 
-        std::string_view event;
-        bool res = statesdb.get(txn, to_string(mtx::events::EventType::RoomGuestAccess), event);
+    std::string_view event;
+    bool res = statesdb.get(txn, to_string(mtx::events::EventType::RoomGuestAccess), event);
 
-        if (res) {
-                try {
-                        StateEvent msg = json::parse(event);
-                        return msg.content.guest_access == AccessState::CanJoin;
-                } catch (const json::exception &e) {
-                        nhlog::db()->warn("failed to parse m.room.guest_access event: {}",
-                                          e.what());
-                }
+    if (res) {
+        try {
+            StateEvent msg = json::parse(event);
+            return msg.content.guest_access == AccessState::CanJoin;
+        } catch (const json::exception &e) {
+            nhlog::db()->warn("failed to parse m.room.guest_access event: {}", e.what());
         }
-        return false;
+    }
+    return false;
 }
 
 QString
 Cache::getRoomTopic(lmdb::txn &txn, lmdb::dbi &statesdb)
 {
-        using namespace mtx::events;
-        using namespace mtx::events::state;
+    using namespace mtx::events;
+    using namespace mtx::events::state;
 
-        std::string_view event;
-        bool res = statesdb.get(txn, to_string(mtx::events::EventType::RoomTopic), event);
+    std::string_view event;
+    bool res = statesdb.get(txn, to_string(mtx::events::EventType::RoomTopic), event);
 
-        if (res) {
-                try {
-                        StateEvent msg = json::parse(event);
+    if (res) {
+        try {
+            StateEvent msg = json::parse(event);
 
-                        if (!msg.content.topic.empty())
-                                return QString::fromStdString(msg.content.topic);
-                } catch (const json::exception &e) {
-                        nhlog::db()->warn("failed to parse m.room.topic event: {}", e.what());
-                }
+            if (!msg.content.topic.empty())
+                return QString::fromStdString(msg.content.topic);
+        } catch (const json::exception &e) {
+            nhlog::db()->warn("failed to parse m.room.topic event: {}", e.what());
         }
+    }
 
-        return QString();
+    return QString();
 }
 
 QString
 Cache::getRoomVersion(lmdb::txn &txn, lmdb::dbi &statesdb)
 {
-        using namespace mtx::events;
-        using namespace mtx::events::state;
+    using namespace mtx::events;
+    using namespace mtx::events::state;
 
-        std::string_view event;
-        bool res = statesdb.get(txn, to_string(mtx::events::EventType::RoomCreate), event);
+    std::string_view event;
+    bool res = statesdb.get(txn, to_string(mtx::events::EventType::RoomCreate), event);
 
-        if (res) {
-                try {
-                        StateEvent msg = json::parse(event);
+    if (res) {
+        try {
+            StateEvent msg = json::parse(event);
 
-                        if (!msg.content.room_version.empty())
-                                return QString::fromStdString(msg.content.room_version);
-                } catch (const json::exception &e) {
-                        nhlog::db()->warn("failed to parse m.room.create event: {}", e.what());
-                }
+            if (!msg.content.room_version.empty())
+                return QString::fromStdString(msg.content.room_version);
+        } catch (const json::exception &e) {
+            nhlog::db()->warn("failed to parse m.room.create event: {}", e.what());
         }
+    }
 
-        nhlog::db()->warn("m.room.create event is missing room version, assuming version \"1\"");
-        return QString("1");
+    nhlog::db()->warn("m.room.create event is missing room version, assuming version \"1\"");
+    return QString("1");
 }
 
 bool
 Cache::getRoomIsSpace(lmdb::txn &txn, lmdb::dbi &statesdb)
 {
-        using namespace mtx::events;
-        using namespace mtx::events::state;
+    using namespace mtx::events;
+    using namespace mtx::events::state;
 
-        std::string_view event;
-        bool res = statesdb.get(txn, to_string(mtx::events::EventType::RoomCreate), event);
+    std::string_view event;
+    bool res = statesdb.get(txn, to_string(mtx::events::EventType::RoomCreate), event);
 
-        if (res) {
-                try {
-                        StateEvent msg = json::parse(event);
+    if (res) {
+        try {
+            StateEvent msg = json::parse(event);
 
-                        return msg.content.type == mtx::events::state::room_type::space;
-                } catch (const json::exception &e) {
-                        nhlog::db()->warn("failed to parse m.room.create event: {}", e.what());
-                }
+            return msg.content.type == mtx::events::state::room_type::space;
+        } catch (const json::exception &e) {
+            nhlog::db()->warn("failed to parse m.room.create event: {}", e.what());
         }
+    }
 
-        nhlog::db()->warn("m.room.create event is missing room version, assuming version \"1\"");
-        return false;
+    nhlog::db()->warn("m.room.create event is missing room version, assuming version \"1\"");
+    return false;
 }
 
 std::optional
 Cache::getRoomAliases(const std::string &roomid)
 {
-        using namespace mtx::events;
-        using namespace mtx::events::state;
+    using namespace mtx::events;
+    using namespace mtx::events::state;
 
-        auto txn      = ro_txn(env_);
-        auto statesdb = getStatesDb(txn, roomid);
+    auto txn      = ro_txn(env_);
+    auto statesdb = getStatesDb(txn, roomid);
 
-        std::string_view event;
-        bool res = statesdb.get(txn, to_string(mtx::events::EventType::RoomCanonicalAlias), event);
+    std::string_view event;
+    bool res = statesdb.get(txn, to_string(mtx::events::EventType::RoomCanonicalAlias), event);
 
-        if (res) {
-                try {
-                        StateEvent msg = json::parse(event);
+    if (res) {
+        try {
+            StateEvent msg = json::parse(event);
 
-                        return msg.content;
-                } catch (const json::exception &e) {
-                        nhlog::db()->warn("failed to parse m.room.canonical_alias event: {}",
-                                          e.what());
-                }
+            return msg.content;
+        } catch (const json::exception &e) {
+            nhlog::db()->warn("failed to parse m.room.canonical_alias event: {}", e.what());
         }
+    }
 
-        return std::nullopt;
+    return std::nullopt;
 }
 
 QString
 Cache::getInviteRoomName(lmdb::txn &txn, lmdb::dbi &statesdb, lmdb::dbi &membersdb)
 {
-        using namespace mtx::events;
-        using namespace mtx::events::state;
+    using namespace mtx::events;
+    using namespace mtx::events::state;
 
-        std::string_view event;
-        bool res = statesdb.get(txn, to_string(mtx::events::EventType::RoomName), event);
+    std::string_view event;
+    bool res = statesdb.get(txn, to_string(mtx::events::EventType::RoomName), event);
 
-        if (res) {
-                try {
-                        StrippedEvent msg = json::parse(event);
-                        return QString::fromStdString(msg.content.name);
-                } catch (const json::exception &e) {
-                        nhlog::db()->warn("failed to parse m.room.name event: {}", e.what());
-                }
+    if (res) {
+        try {
+            StrippedEvent msg = json::parse(event);
+            return QString::fromStdString(msg.content.name);
+        } catch (const json::exception &e) {
+            nhlog::db()->warn("failed to parse m.room.name event: {}", e.what());
         }
+    }
 
-        auto cursor = lmdb::cursor::open(txn, membersdb);
-        std::string_view user_id, member_data;
+    auto cursor = lmdb::cursor::open(txn, membersdb);
+    std::string_view user_id, member_data;
 
-        while (cursor.get(user_id, member_data, MDB_NEXT)) {
-                if (user_id == localUserId_.toStdString())
-                        continue;
+    while (cursor.get(user_id, member_data, MDB_NEXT)) {
+        if (user_id == localUserId_.toStdString())
+            continue;
 
-                try {
-                        MemberInfo tmp = json::parse(member_data);
-                        cursor.close();
+        try {
+            MemberInfo tmp = json::parse(member_data);
+            cursor.close();
 
-                        return QString::fromStdString(tmp.name);
-                } catch (const json::exception &e) {
-                        nhlog::db()->warn("failed to parse member info: {}", e.what());
-                }
+            return QString::fromStdString(tmp.name);
+        } catch (const json::exception &e) {
+            nhlog::db()->warn("failed to parse member info: {}", e.what());
         }
+    }
 
-        cursor.close();
+    cursor.close();
 
-        return QString("Empty Room");
+    return QString("Empty Room");
 }
 
 QString
 Cache::getInviteRoomAvatarUrl(lmdb::txn &txn, lmdb::dbi &statesdb, lmdb::dbi &membersdb)
 {
-        using namespace mtx::events;
-        using namespace mtx::events::state;
+    using namespace mtx::events;
+    using namespace mtx::events::state;
 
-        std::string_view event;
-        bool res = statesdb.get(txn, to_string(mtx::events::EventType::RoomAvatar), event);
+    std::string_view event;
+    bool res = statesdb.get(txn, to_string(mtx::events::EventType::RoomAvatar), event);
 
-        if (res) {
-                try {
-                        StrippedEvent msg = json::parse(event);
-                        return QString::fromStdString(msg.content.url);
-                } catch (const json::exception &e) {
-                        nhlog::db()->warn("failed to parse m.room.avatar event: {}", e.what());
-                }
+    if (res) {
+        try {
+            StrippedEvent msg = json::parse(event);
+            return QString::fromStdString(msg.content.url);
+        } catch (const json::exception &e) {
+            nhlog::db()->warn("failed to parse m.room.avatar event: {}", e.what());
         }
+    }
 
-        auto cursor = lmdb::cursor::open(txn, membersdb);
-        std::string_view user_id, member_data;
+    auto cursor = lmdb::cursor::open(txn, membersdb);
+    std::string_view user_id, member_data;
 
-        while (cursor.get(user_id, member_data, MDB_NEXT)) {
-                if (user_id == localUserId_.toStdString())
-                        continue;
+    while (cursor.get(user_id, member_data, MDB_NEXT)) {
+        if (user_id == localUserId_.toStdString())
+            continue;
 
-                try {
-                        MemberInfo tmp = json::parse(member_data);
-                        cursor.close();
+        try {
+            MemberInfo tmp = json::parse(member_data);
+            cursor.close();
 
-                        return QString::fromStdString(tmp.avatar_url);
-                } catch (const json::exception &e) {
-                        nhlog::db()->warn("failed to parse member info: {}", e.what());
-                }
+            return QString::fromStdString(tmp.avatar_url);
+        } catch (const json::exception &e) {
+            nhlog::db()->warn("failed to parse member info: {}", e.what());
         }
+    }
 
-        cursor.close();
+    cursor.close();
 
-        return QString();
+    return QString();
 }
 
 QString
 Cache::getInviteRoomTopic(lmdb::txn &txn, lmdb::dbi &db)
 {
-        using namespace mtx::events;
-        using namespace mtx::events::state;
+    using namespace mtx::events;
+    using namespace mtx::events::state;
 
-        std::string_view event;
-        bool res = db.get(txn, to_string(mtx::events::EventType::RoomTopic), event);
+    std::string_view event;
+    bool res = db.get(txn, to_string(mtx::events::EventType::RoomTopic), event);
 
-        if (res) {
-                try {
-                        StrippedEvent msg = json::parse(event);
-                        return QString::fromStdString(msg.content.topic);
-                } catch (const json::exception &e) {
-                        nhlog::db()->warn("failed to parse m.room.topic event: {}", e.what());
-                }
+    if (res) {
+        try {
+            StrippedEvent msg = json::parse(event);
+            return QString::fromStdString(msg.content.topic);
+        } catch (const json::exception &e) {
+            nhlog::db()->warn("failed to parse m.room.topic event: {}", e.what());
         }
+    }
 
-        return QString();
+    return QString();
 }
 
 bool
 Cache::getInviteRoomIsSpace(lmdb::txn &txn, lmdb::dbi &db)
 {
-        using namespace mtx::events;
-        using namespace mtx::events::state;
+    using namespace mtx::events;
+    using namespace mtx::events::state;
 
-        std::string_view event;
-        bool res = db.get(txn, to_string(mtx::events::EventType::RoomCreate), event);
+    std::string_view event;
+    bool res = db.get(txn, to_string(mtx::events::EventType::RoomCreate), event);
 
-        if (res) {
-                try {
-                        StrippedEvent msg = json::parse(event);
-                        return msg.content.type == mtx::events::state::room_type::space;
-                } catch (const json::exception &e) {
-                        nhlog::db()->warn("failed to parse m.room.topic event: {}", e.what());
-                }
+    if (res) {
+        try {
+            StrippedEvent msg = json::parse(event);
+            return msg.content.type == mtx::events::state::room_type::space;
+        } catch (const json::exception &e) {
+            nhlog::db()->warn("failed to parse m.room.topic event: {}", e.what());
         }
+    }
 
-        return false;
+    return false;
 }
 
 std::vector
 Cache::joinedRooms()
 {
-        auto txn         = ro_txn(env_);
-        auto roomsCursor = lmdb::cursor::open(txn, roomsDb_);
+    auto txn         = ro_txn(env_);
+    auto roomsCursor = lmdb::cursor::open(txn, roomsDb_);
 
-        std::string_view id, data;
-        std::vector room_ids;
+    std::string_view id, data;
+    std::vector room_ids;
 
-        // Gather the room ids for the joined rooms.
-        while (roomsCursor.get(id, data, MDB_NEXT))
-                room_ids.emplace_back(id);
+    // Gather the room ids for the joined rooms.
+    while (roomsCursor.get(id, data, MDB_NEXT))
+        room_ids.emplace_back(id);
 
-        roomsCursor.close();
+    roomsCursor.close();
 
-        return room_ids;
+    return room_ids;
 }
 
 std::optional
 Cache::getMember(const std::string &room_id, const std::string &user_id)
 {
-        if (user_id.empty() || !env_.handle())
-                return std::nullopt;
-
-        try {
-                auto txn = ro_txn(env_);
-
-                auto membersdb = getMembersDb(txn, room_id);
-
-                std::string_view info;
-                if (membersdb.get(txn, user_id, info)) {
-                        MemberInfo m = json::parse(info);
-                        return m;
-                }
-        } catch (std::exception &e) {
-                nhlog::db()->warn(
-                  "Failed to read member ({}) in room ({}): {}", user_id, room_id, e.what());
-        }
+    if (user_id.empty() || !env_.handle())
         return std::nullopt;
+
+    try {
+        auto txn = ro_txn(env_);
+
+        auto membersdb = getMembersDb(txn, room_id);
+
+        std::string_view info;
+        if (membersdb.get(txn, user_id, info)) {
+            MemberInfo m = json::parse(info);
+            return m;
+        }
+    } catch (std::exception &e) {
+        nhlog::db()->warn(
+          "Failed to read member ({}) in room ({}): {}", user_id, room_id, e.what());
+    }
+    return std::nullopt;
 }
 
 std::vector
 Cache::getMembers(const std::string &room_id, std::size_t startIndex, std::size_t len)
 {
-        auto txn    = ro_txn(env_);
-        auto db     = getMembersDb(txn, room_id);
-        auto cursor = lmdb::cursor::open(txn, db);
+    auto txn    = ro_txn(env_);
+    auto db     = getMembersDb(txn, room_id);
+    auto cursor = lmdb::cursor::open(txn, db);
 
-        std::size_t currentIndex = 0;
+    std::size_t currentIndex = 0;
 
-        const auto endIndex = std::min(startIndex + len, db.size(txn));
+    const auto endIndex = std::min(startIndex + len, db.size(txn));
 
-        std::vector members;
+    std::vector members;
 
-        std::string_view user_id, user_data;
-        while (cursor.get(user_id, user_data, MDB_NEXT)) {
-                if (currentIndex < startIndex) {
-                        currentIndex += 1;
-                        continue;
-                }
-
-                if (currentIndex >= endIndex)
-                        break;
-
-                try {
-                        MemberInfo tmp = json::parse(user_data);
-                        members.emplace_back(
-                          RoomMember{QString::fromStdString(std::string(user_id)),
-                                     QString::fromStdString(tmp.name)});
-                } catch (const json::exception &e) {
-                        nhlog::db()->warn("{}", e.what());
-                }
-
-                currentIndex += 1;
+    std::string_view user_id, user_data;
+    while (cursor.get(user_id, user_data, MDB_NEXT)) {
+        if (currentIndex < startIndex) {
+            currentIndex += 1;
+            continue;
         }
 
-        cursor.close();
+        if (currentIndex >= endIndex)
+            break;
 
-        return members;
+        try {
+            MemberInfo tmp = json::parse(user_data);
+            members.emplace_back(RoomMember{QString::fromStdString(std::string(user_id)),
+                                            QString::fromStdString(tmp.name)});
+        } catch (const json::exception &e) {
+            nhlog::db()->warn("{}", e.what());
+        }
+
+        currentIndex += 1;
+    }
+
+    cursor.close();
+
+    return members;
 }
 
 std::vector
 Cache::getMembersFromInvite(const std::string &room_id, std::size_t startIndex, std::size_t len)
 {
-        auto txn    = ro_txn(env_);
-        auto db     = getInviteMembersDb(txn, room_id);
-        auto cursor = lmdb::cursor::open(txn, db);
+    auto txn    = ro_txn(env_);
+    auto db     = getInviteMembersDb(txn, room_id);
+    auto cursor = lmdb::cursor::open(txn, db);
 
-        std::size_t currentIndex = 0;
+    std::size_t currentIndex = 0;
 
-        const auto endIndex = std::min(startIndex + len, db.size(txn));
+    const auto endIndex = std::min(startIndex + len, db.size(txn));
 
-        std::vector members;
+    std::vector members;
 
-        std::string_view user_id, user_data;
-        while (cursor.get(user_id, user_data, MDB_NEXT)) {
-                if (currentIndex < startIndex) {
-                        currentIndex += 1;
-                        continue;
-                }
-
-                if (currentIndex >= endIndex)
-                        break;
-
-                try {
-                        MemberInfo tmp = json::parse(user_data);
-                        members.emplace_back(
-                          RoomMember{QString::fromStdString(std::string(user_id)),
-                                     QString::fromStdString(tmp.name)});
-                } catch (const json::exception &e) {
-                        nhlog::db()->warn("{}", e.what());
-                }
-
-                currentIndex += 1;
+    std::string_view user_id, user_data;
+    while (cursor.get(user_id, user_data, MDB_NEXT)) {
+        if (currentIndex < startIndex) {
+            currentIndex += 1;
+            continue;
         }
 
-        cursor.close();
+        if (currentIndex >= endIndex)
+            break;
 
-        return members;
+        try {
+            MemberInfo tmp = json::parse(user_data);
+            members.emplace_back(RoomMember{QString::fromStdString(std::string(user_id)),
+                                            QString::fromStdString(tmp.name)});
+        } catch (const json::exception &e) {
+            nhlog::db()->warn("{}", e.what());
+        }
+
+        currentIndex += 1;
+    }
+
+    cursor.close();
+
+    return members;
 }
 
 bool
 Cache::isRoomMember(const std::string &user_id, const std::string &room_id)
 {
-        try {
-                auto txn = ro_txn(env_);
-                auto db  = getMembersDb(txn, room_id);
+    try {
+        auto txn = ro_txn(env_);
+        auto db  = getMembersDb(txn, room_id);
 
-                std::string_view value;
-                bool res = db.get(txn, user_id, value);
+        std::string_view value;
+        bool res = db.get(txn, user_id, value);
 
-                return res;
-        } catch (std::exception &e) {
-                nhlog::db()->warn("Failed to read member membership ({}) in room ({}): {}",
-                                  user_id,
-                                  room_id,
-                                  e.what());
-        }
-        return false;
+        return res;
+    } catch (std::exception &e) {
+        nhlog::db()->warn(
+          "Failed to read member membership ({}) in room ({}): {}", user_id, room_id, e.what());
+    }
+    return false;
 }
 
 void
 Cache::savePendingMessage(const std::string &room_id,
                           const mtx::events::collections::TimelineEvent &message)
 {
-        auto txn      = lmdb::txn::begin(env_);
-        auto eventsDb = getEventsDb(txn, room_id);
+    auto txn      = lmdb::txn::begin(env_);
+    auto eventsDb = getEventsDb(txn, room_id);
 
-        mtx::responses::Timeline timeline;
-        timeline.events.push_back(message.data);
-        saveTimelineMessages(txn, eventsDb, room_id, timeline);
+    mtx::responses::Timeline timeline;
+    timeline.events.push_back(message.data);
+    saveTimelineMessages(txn, eventsDb, room_id, timeline);
 
-        auto pending = getPendingMessagesDb(txn, room_id);
+    auto pending = getPendingMessagesDb(txn, room_id);
 
-        int64_t now = QDateTime::currentMSecsSinceEpoch();
-        pending.put(txn, lmdb::to_sv(now), mtx::accessors::event_id(message.data));
+    int64_t now = QDateTime::currentMSecsSinceEpoch();
+    pending.put(txn, lmdb::to_sv(now), mtx::accessors::event_id(message.data));
 
-        txn.commit();
+    txn.commit();
 }
 
 std::optional
 Cache::firstPendingMessage(const std::string &room_id)
 {
-        auto txn     = lmdb::txn::begin(env_);
-        auto pending = getPendingMessagesDb(txn, room_id);
+    auto txn     = lmdb::txn::begin(env_);
+    auto pending = getPendingMessagesDb(txn, room_id);
 
-        {
-                auto pendingCursor = lmdb::cursor::open(txn, pending);
-                std::string_view tsIgnored, pendingTxn;
-                while (pendingCursor.get(tsIgnored, pendingTxn, MDB_NEXT)) {
-                        auto eventsDb = getEventsDb(txn, room_id);
-                        std::string_view event;
-                        if (!eventsDb.get(txn, pendingTxn, event)) {
-                                pending.del(txn, tsIgnored, pendingTxn);
-                                continue;
-                        }
+    {
+        auto pendingCursor = lmdb::cursor::open(txn, pending);
+        std::string_view tsIgnored, pendingTxn;
+        while (pendingCursor.get(tsIgnored, pendingTxn, MDB_NEXT)) {
+            auto eventsDb = getEventsDb(txn, room_id);
+            std::string_view event;
+            if (!eventsDb.get(txn, pendingTxn, event)) {
+                pending.del(txn, tsIgnored, pendingTxn);
+                continue;
+            }
 
-                        try {
-                                mtx::events::collections::TimelineEvent te;
-                                mtx::events::collections::from_json(json::parse(event), te);
+            try {
+                mtx::events::collections::TimelineEvent te;
+                mtx::events::collections::from_json(json::parse(event), te);
 
-                                pendingCursor.close();
-                                txn.commit();
-                                return te;
-                        } catch (std::exception &e) {
-                                nhlog::db()->error("Failed to parse message from cache {}",
-                                                   e.what());
-                                pending.del(txn, tsIgnored, pendingTxn);
-                                continue;
-                        }
-                }
+                pendingCursor.close();
+                txn.commit();
+                return te;
+            } catch (std::exception &e) {
+                nhlog::db()->error("Failed to parse message from cache {}", e.what());
+                pending.del(txn, tsIgnored, pendingTxn);
+                continue;
+            }
         }
+    }
 
-        txn.commit();
+    txn.commit();
 
-        return std::nullopt;
+    return std::nullopt;
 }
 
 void
 Cache::removePendingStatus(const std::string &room_id, const std::string &txn_id)
 {
-        auto txn     = lmdb::txn::begin(env_);
-        auto pending = getPendingMessagesDb(txn, room_id);
+    auto txn     = lmdb::txn::begin(env_);
+    auto pending = getPendingMessagesDb(txn, room_id);
 
-        {
-                auto pendingCursor = lmdb::cursor::open(txn, pending);
-                std::string_view tsIgnored, pendingTxn;
-                while (pendingCursor.get(tsIgnored, pendingTxn, MDB_NEXT)) {
-                        if (std::string_view(pendingTxn.data(), pendingTxn.size()) == txn_id)
-                                lmdb::cursor_del(pendingCursor);
-                }
+    {
+        auto pendingCursor = lmdb::cursor::open(txn, pending);
+        std::string_view tsIgnored, pendingTxn;
+        while (pendingCursor.get(tsIgnored, pendingTxn, MDB_NEXT)) {
+            if (std::string_view(pendingTxn.data(), pendingTxn.size()) == txn_id)
+                lmdb::cursor_del(pendingCursor);
         }
+    }
 
-        txn.commit();
+    txn.commit();
 }
 
 void
@@ -2917,403 +2842,398 @@ Cache::saveTimelineMessages(lmdb::txn &txn,
                             const std::string &room_id,
                             const mtx::responses::Timeline &res)
 {
-        if (res.events.empty())
-                return;
+    if (res.events.empty())
+        return;
 
-        auto relationsDb = getRelationsDb(txn, room_id);
+    auto relationsDb = getRelationsDb(txn, room_id);
 
-        auto orderDb     = getEventOrderDb(txn, room_id);
-        auto evToOrderDb = getEventToOrderDb(txn, room_id);
-        auto msg2orderDb = getMessageToOrderDb(txn, room_id);
-        auto order2msgDb = getOrderToMessageDb(txn, room_id);
-        auto pending     = getPendingMessagesDb(txn, room_id);
+    auto orderDb     = getEventOrderDb(txn, room_id);
+    auto evToOrderDb = getEventToOrderDb(txn, room_id);
+    auto msg2orderDb = getMessageToOrderDb(txn, room_id);
+    auto order2msgDb = getOrderToMessageDb(txn, room_id);
+    auto pending     = getPendingMessagesDb(txn, room_id);
 
-        if (res.limited) {
-                lmdb::dbi_drop(txn, orderDb, false);
-                lmdb::dbi_drop(txn, evToOrderDb, false);
-                lmdb::dbi_drop(txn, msg2orderDb, false);
-                lmdb::dbi_drop(txn, order2msgDb, false);
-                lmdb::dbi_drop(txn, pending, true);
+    if (res.limited) {
+        lmdb::dbi_drop(txn, orderDb, false);
+        lmdb::dbi_drop(txn, evToOrderDb, false);
+        lmdb::dbi_drop(txn, msg2orderDb, false);
+        lmdb::dbi_drop(txn, order2msgDb, false);
+        lmdb::dbi_drop(txn, pending, true);
+    }
+
+    using namespace mtx::events;
+    using namespace mtx::events::state;
+
+    std::string_view indexVal, val;
+    uint64_t index = std::numeric_limits::max() / 2;
+    auto cursor    = lmdb::cursor::open(txn, orderDb);
+    if (cursor.get(indexVal, val, MDB_LAST)) {
+        index = lmdb::from_sv(indexVal);
+    }
+
+    uint64_t msgIndex = std::numeric_limits::max() / 2;
+    auto msgCursor    = lmdb::cursor::open(txn, order2msgDb);
+    if (msgCursor.get(indexVal, val, MDB_LAST)) {
+        msgIndex = lmdb::from_sv(indexVal);
+    }
+
+    bool first = true;
+    for (const auto &e : res.events) {
+        auto event  = mtx::accessors::serialize_event(e);
+        auto txn_id = mtx::accessors::transaction_id(e);
+
+        std::string event_id_val = event.value("event_id", "");
+        if (event_id_val.empty()) {
+            nhlog::db()->error("Event without id!");
+            continue;
         }
 
-        using namespace mtx::events;
-        using namespace mtx::events::state;
+        std::string_view event_id = event_id_val;
 
-        std::string_view indexVal, val;
-        uint64_t index = std::numeric_limits::max() / 2;
-        auto cursor    = lmdb::cursor::open(txn, orderDb);
-        if (cursor.get(indexVal, val, MDB_LAST)) {
-                index = lmdb::from_sv(indexVal);
-        }
+        json orderEntry        = json::object();
+        orderEntry["event_id"] = event_id_val;
+        if (first && !res.prev_batch.empty())
+            orderEntry["prev_batch"] = res.prev_batch;
 
-        uint64_t msgIndex = std::numeric_limits::max() / 2;
-        auto msgCursor    = lmdb::cursor::open(txn, order2msgDb);
-        if (msgCursor.get(indexVal, val, MDB_LAST)) {
-                msgIndex = lmdb::from_sv(indexVal);
-        }
+        std::string_view txn_order;
+        if (!txn_id.empty() && evToOrderDb.get(txn, txn_id, txn_order)) {
+            eventsDb.put(txn, event_id, event.dump());
+            eventsDb.del(txn, txn_id);
 
-        bool first = true;
-        for (const auto &e : res.events) {
-                auto event  = mtx::accessors::serialize_event(e);
-                auto txn_id = mtx::accessors::transaction_id(e);
+            std::string_view msg_txn_order;
+            if (msg2orderDb.get(txn, txn_id, msg_txn_order)) {
+                order2msgDb.put(txn, msg_txn_order, event_id);
+                msg2orderDb.put(txn, event_id, msg_txn_order);
+                msg2orderDb.del(txn, txn_id);
+            }
 
-                std::string event_id_val = event.value("event_id", "");
-                if (event_id_val.empty()) {
-                        nhlog::db()->error("Event without id!");
-                        continue;
+            orderDb.put(txn, txn_order, orderEntry.dump());
+            evToOrderDb.put(txn, event_id, txn_order);
+            evToOrderDb.del(txn, txn_id);
+
+            auto relations = mtx::accessors::relations(e);
+            if (!relations.relations.empty()) {
+                for (const auto &r : relations.relations) {
+                    if (!r.event_id.empty()) {
+                        relationsDb.del(txn, r.event_id, txn_id);
+                        relationsDb.put(txn, r.event_id, event_id);
+                    }
                 }
+            }
 
-                std::string_view event_id = event_id_val;
+            auto pendingCursor = lmdb::cursor::open(txn, pending);
+            std::string_view tsIgnored, pendingTxn;
+            while (pendingCursor.get(tsIgnored, pendingTxn, MDB_NEXT)) {
+                if (std::string_view(pendingTxn.data(), pendingTxn.size()) == txn_id)
+                    lmdb::cursor_del(pendingCursor);
+            }
+        } else if (auto redaction =
+                     std::get_if>(&e)) {
+            if (redaction->redacts.empty())
+                continue;
 
-                json orderEntry        = json::object();
-                orderEntry["event_id"] = event_id_val;
-                if (first && !res.prev_batch.empty())
-                        orderEntry["prev_batch"] = res.prev_batch;
+            std::string_view oldEvent;
+            bool success = eventsDb.get(txn, redaction->redacts, oldEvent);
+            if (!success)
+                continue;
 
-                std::string_view txn_order;
-                if (!txn_id.empty() && evToOrderDb.get(txn, txn_id, txn_order)) {
-                        eventsDb.put(txn, event_id, event.dump());
-                        eventsDb.del(txn, txn_id);
+            mtx::events::collections::TimelineEvent te;
+            try {
+                mtx::events::collections::from_json(
+                  json::parse(std::string_view(oldEvent.data(), oldEvent.size())), te);
+                // overwrite the content and add redation data
+                std::visit(
+                  [redaction](auto &ev) {
+                      ev.unsigned_data.redacted_because = *redaction;
+                      ev.unsigned_data.redacted_by      = redaction->event_id;
+                  },
+                  te.data);
+                event = mtx::accessors::serialize_event(te.data);
+                event["content"].clear();
 
-                        std::string_view msg_txn_order;
-                        if (msg2orderDb.get(txn, txn_id, msg_txn_order)) {
-                                order2msgDb.put(txn, msg_txn_order, event_id);
-                                msg2orderDb.put(txn, event_id, msg_txn_order);
-                                msg2orderDb.del(txn, txn_id);
-                        }
+            } catch (std::exception &e) {
+                nhlog::db()->error("Failed to parse message from cache {}", e.what());
+                continue;
+            }
 
-                        orderDb.put(txn, txn_order, orderEntry.dump());
-                        evToOrderDb.put(txn, event_id, txn_order);
-                        evToOrderDb.del(txn, txn_id);
+            eventsDb.put(txn, redaction->redacts, event.dump());
+            eventsDb.put(txn, redaction->event_id, json(*redaction).dump());
+        } else {
+            first = false;
 
-                        auto relations = mtx::accessors::relations(e);
-                        if (!relations.relations.empty()) {
-                                for (const auto &r : relations.relations) {
-                                        if (!r.event_id.empty()) {
-                                                relationsDb.del(txn, r.event_id, txn_id);
-                                                relationsDb.put(txn, r.event_id, event_id);
-                                        }
-                                }
-                        }
+            // This check protects against duplicates in the timeline. If the event_id
+            // is already in the DB, we skip putting it (again) in ordered DBs, and only
+            // update the event itself and its relations.
+            std::string_view unused_read;
+            if (!evToOrderDb.get(txn, event_id, unused_read)) {
+                ++index;
 
-                        auto pendingCursor = lmdb::cursor::open(txn, pending);
-                        std::string_view tsIgnored, pendingTxn;
-                        while (pendingCursor.get(tsIgnored, pendingTxn, MDB_NEXT)) {
-                                if (std::string_view(pendingTxn.data(), pendingTxn.size()) ==
-                                    txn_id)
-                                        lmdb::cursor_del(pendingCursor);
-                        }
-                } else if (auto redaction =
-                             std::get_if>(
-                               &e)) {
-                        if (redaction->redacts.empty())
-                                continue;
+                nhlog::db()->debug("saving '{}'", orderEntry.dump());
 
-                        std::string_view oldEvent;
-                        bool success = eventsDb.get(txn, redaction->redacts, oldEvent);
-                        if (!success)
-                                continue;
+                cursor.put(lmdb::to_sv(index), orderEntry.dump(), MDB_APPEND);
+                evToOrderDb.put(txn, event_id, lmdb::to_sv(index));
 
-                        mtx::events::collections::TimelineEvent te;
-                        try {
-                                mtx::events::collections::from_json(
-                                  json::parse(std::string_view(oldEvent.data(), oldEvent.size())),
-                                  te);
-                                // overwrite the content and add redation data
-                                std::visit(
-                                  [redaction](auto &ev) {
-                                          ev.unsigned_data.redacted_because = *redaction;
-                                          ev.unsigned_data.redacted_by      = redaction->event_id;
-                                  },
-                                  te.data);
-                                event = mtx::accessors::serialize_event(te.data);
-                                event["content"].clear();
+                // TODO(Nico): Allow blacklisting more event types in UI
+                if (!isHiddenEvent(txn, e, room_id)) {
+                    ++msgIndex;
+                    msgCursor.put(lmdb::to_sv(msgIndex), event_id, MDB_APPEND);
 
-                        } catch (std::exception &e) {
-                                nhlog::db()->error("Failed to parse message from cache {}",
-                                                   e.what());
-                                continue;
-                        }
-
-                        eventsDb.put(txn, redaction->redacts, event.dump());
-                        eventsDb.put(txn, redaction->event_id, json(*redaction).dump());
-                } else {
-                        first = false;
-
-                        // This check protects against duplicates in the timeline. If the event_id
-                        // is already in the DB, we skip putting it (again) in ordered DBs, and only
-                        // update the event itself and its relations.
-                        std::string_view unused_read;
-                        if (!evToOrderDb.get(txn, event_id, unused_read)) {
-                                ++index;
-
-                                nhlog::db()->debug("saving '{}'", orderEntry.dump());
-
-                                cursor.put(lmdb::to_sv(index), orderEntry.dump(), MDB_APPEND);
-                                evToOrderDb.put(txn, event_id, lmdb::to_sv(index));
-
-                                // TODO(Nico): Allow blacklisting more event types in UI
-                                if (!isHiddenEvent(txn, e, room_id)) {
-                                        ++msgIndex;
-                                        msgCursor.put(lmdb::to_sv(msgIndex), event_id, MDB_APPEND);
-
-                                        msg2orderDb.put(txn, event_id, lmdb::to_sv(msgIndex));
-                                }
-                        } else {
-                                nhlog::db()->warn("duplicate event '{}'", orderEntry.dump());
-                        }
-                        eventsDb.put(txn, event_id, event.dump());
-
-                        auto relations = mtx::accessors::relations(e);
-                        if (!relations.relations.empty()) {
-                                for (const auto &r : relations.relations) {
-                                        if (!r.event_id.empty()) {
-                                                relationsDb.put(txn, r.event_id, event_id);
-                                        }
-                                }
-                        }
+                    msg2orderDb.put(txn, event_id, lmdb::to_sv(msgIndex));
                 }
+            } else {
+                nhlog::db()->warn("duplicate event '{}'", orderEntry.dump());
+            }
+            eventsDb.put(txn, event_id, event.dump());
+
+            auto relations = mtx::accessors::relations(e);
+            if (!relations.relations.empty()) {
+                for (const auto &r : relations.relations) {
+                    if (!r.event_id.empty()) {
+                        relationsDb.put(txn, r.event_id, event_id);
+                    }
+                }
+            }
         }
+    }
 }
 
 uint64_t
 Cache::saveOldMessages(const std::string &room_id, const mtx::responses::Messages &res)
 {
-        auto txn         = lmdb::txn::begin(env_);
-        auto eventsDb    = getEventsDb(txn, room_id);
-        auto relationsDb = getRelationsDb(txn, room_id);
+    auto txn         = lmdb::txn::begin(env_);
+    auto eventsDb    = getEventsDb(txn, room_id);
+    auto relationsDb = getRelationsDb(txn, room_id);
 
-        auto orderDb     = getEventOrderDb(txn, room_id);
-        auto evToOrderDb = getEventToOrderDb(txn, room_id);
-        auto msg2orderDb = getMessageToOrderDb(txn, room_id);
-        auto order2msgDb = getOrderToMessageDb(txn, room_id);
+    auto orderDb     = getEventOrderDb(txn, room_id);
+    auto evToOrderDb = getEventToOrderDb(txn, room_id);
+    auto msg2orderDb = getMessageToOrderDb(txn, room_id);
+    auto order2msgDb = getOrderToMessageDb(txn, room_id);
 
-        std::string_view indexVal, val;
-        uint64_t index = std::numeric_limits::max() / 2;
-        {
-                auto cursor = lmdb::cursor::open(txn, orderDb);
-                if (cursor.get(indexVal, val, MDB_FIRST)) {
-                        index = lmdb::from_sv(indexVal);
-                }
+    std::string_view indexVal, val;
+    uint64_t index = std::numeric_limits::max() / 2;
+    {
+        auto cursor = lmdb::cursor::open(txn, orderDb);
+        if (cursor.get(indexVal, val, MDB_FIRST)) {
+            index = lmdb::from_sv(indexVal);
         }
+    }
 
-        uint64_t msgIndex = std::numeric_limits::max() / 2;
-        {
-                auto msgCursor = lmdb::cursor::open(txn, order2msgDb);
-                if (msgCursor.get(indexVal, val, MDB_FIRST)) {
-                        msgIndex = lmdb::from_sv(indexVal);
-                }
+    uint64_t msgIndex = std::numeric_limits::max() / 2;
+    {
+        auto msgCursor = lmdb::cursor::open(txn, order2msgDb);
+        if (msgCursor.get(indexVal, val, MDB_FIRST)) {
+            msgIndex = lmdb::from_sv(indexVal);
         }
+    }
 
-        if (res.chunk.empty()) {
-                if (orderDb.get(txn, lmdb::to_sv(index), val)) {
-                        auto orderEntry          = json::parse(val);
-                        orderEntry["prev_batch"] = res.end;
-                        orderDb.put(txn, lmdb::to_sv(index), orderEntry.dump());
-                        txn.commit();
-                }
-                return index;
+    if (res.chunk.empty()) {
+        if (orderDb.get(txn, lmdb::to_sv(index), val)) {
+            auto orderEntry          = json::parse(val);
+            orderEntry["prev_batch"] = res.end;
+            orderDb.put(txn, lmdb::to_sv(index), orderEntry.dump());
+            txn.commit();
         }
+        return index;
+    }
 
-        std::string event_id_val;
-        for (const auto &e : res.chunk) {
-                if (std::holds_alternative<
-                      mtx::events::RedactionEvent>(e))
-                        continue;
+    std::string event_id_val;
+    for (const auto &e : res.chunk) {
+        if (std::holds_alternative>(e))
+            continue;
 
-                auto event                = mtx::accessors::serialize_event(e);
-                event_id_val              = event["event_id"].get();
-                std::string_view event_id = event_id_val;
+        auto event                = mtx::accessors::serialize_event(e);
+        event_id_val              = event["event_id"].get();
+        std::string_view event_id = event_id_val;
 
-                // This check protects against duplicates in the timeline. If the event_id is
-                // already in the DB, we skip putting it (again) in ordered DBs, and only update the
-                // event itself and its relations.
-                std::string_view unused_read;
-                if (!evToOrderDb.get(txn, event_id, unused_read)) {
-                        --index;
+        // This check protects against duplicates in the timeline. If the event_id is
+        // already in the DB, we skip putting it (again) in ordered DBs, and only update the
+        // event itself and its relations.
+        std::string_view unused_read;
+        if (!evToOrderDb.get(txn, event_id, unused_read)) {
+            --index;
 
-                        json orderEntry        = json::object();
-                        orderEntry["event_id"] = event_id_val;
+            json orderEntry        = json::object();
+            orderEntry["event_id"] = event_id_val;
 
-                        orderDb.put(txn, lmdb::to_sv(index), orderEntry.dump());
-                        evToOrderDb.put(txn, event_id, lmdb::to_sv(index));
+            orderDb.put(txn, lmdb::to_sv(index), orderEntry.dump());
+            evToOrderDb.put(txn, event_id, lmdb::to_sv(index));
 
-                        // TODO(Nico): Allow blacklisting more event types in UI
-                        if (!isHiddenEvent(txn, e, room_id)) {
-                                --msgIndex;
-                                order2msgDb.put(txn, lmdb::to_sv(msgIndex), event_id);
+            // TODO(Nico): Allow blacklisting more event types in UI
+            if (!isHiddenEvent(txn, e, room_id)) {
+                --msgIndex;
+                order2msgDb.put(txn, lmdb::to_sv(msgIndex), event_id);
 
-                                msg2orderDb.put(txn, event_id, lmdb::to_sv(msgIndex));
-                        }
-                }
-                eventsDb.put(txn, event_id, event.dump());
-
-                auto relations = mtx::accessors::relations(e);
-                if (!relations.relations.empty()) {
-                        for (const auto &r : relations.relations) {
-                                if (!r.event_id.empty()) {
-                                        relationsDb.put(txn, r.event_id, event_id);
-                                }
-                        }
-                }
+                msg2orderDb.put(txn, event_id, lmdb::to_sv(msgIndex));
+            }
         }
+        eventsDb.put(txn, event_id, event.dump());
 
-        json orderEntry          = json::object();
-        orderEntry["event_id"]   = event_id_val;
-        orderEntry["prev_batch"] = res.end;
-        orderDb.put(txn, lmdb::to_sv(index), orderEntry.dump());
+        auto relations = mtx::accessors::relations(e);
+        if (!relations.relations.empty()) {
+            for (const auto &r : relations.relations) {
+                if (!r.event_id.empty()) {
+                    relationsDb.put(txn, r.event_id, event_id);
+                }
+            }
+        }
+    }
 
-        txn.commit();
+    json orderEntry          = json::object();
+    orderEntry["event_id"]   = event_id_val;
+    orderEntry["prev_batch"] = res.end;
+    orderDb.put(txn, lmdb::to_sv(index), orderEntry.dump());
 
-        return msgIndex;
+    txn.commit();
+
+    return msgIndex;
 }
 
 void
 Cache::clearTimeline(const std::string &room_id)
 {
-        auto txn         = lmdb::txn::begin(env_);
-        auto eventsDb    = getEventsDb(txn, room_id);
-        auto relationsDb = getRelationsDb(txn, room_id);
+    auto txn         = lmdb::txn::begin(env_);
+    auto eventsDb    = getEventsDb(txn, room_id);
+    auto relationsDb = getRelationsDb(txn, room_id);
 
-        auto orderDb     = getEventOrderDb(txn, room_id);
-        auto evToOrderDb = getEventToOrderDb(txn, room_id);
-        auto msg2orderDb = getMessageToOrderDb(txn, room_id);
-        auto order2msgDb = getOrderToMessageDb(txn, room_id);
+    auto orderDb     = getEventOrderDb(txn, room_id);
+    auto evToOrderDb = getEventToOrderDb(txn, room_id);
+    auto msg2orderDb = getMessageToOrderDb(txn, room_id);
+    auto order2msgDb = getOrderToMessageDb(txn, room_id);
 
-        std::string_view indexVal, val;
-        auto cursor = lmdb::cursor::open(txn, orderDb);
+    std::string_view indexVal, val;
+    auto cursor = lmdb::cursor::open(txn, orderDb);
 
-        bool start                   = true;
-        bool passed_pagination_token = false;
-        while (cursor.get(indexVal, val, start ? MDB_LAST : MDB_PREV)) {
-                start = false;
-                json obj;
+    bool start                   = true;
+    bool passed_pagination_token = false;
+    while (cursor.get(indexVal, val, start ? MDB_LAST : MDB_PREV)) {
+        start = false;
+        json obj;
 
-                try {
-                        obj = json::parse(std::string_view(val.data(), val.size()));
-                } catch (std::exception &) {
-                        // workaround bug in the initial db format, where we sometimes didn't store
-                        // json...
-                        obj = {{"event_id", std::string(val.data(), val.size())}};
-                }
-
-                if (passed_pagination_token) {
-                        if (obj.count("event_id") != 0) {
-                                std::string event_id = obj["event_id"].get();
-
-                                if (!event_id.empty()) {
-                                        evToOrderDb.del(txn, event_id);
-                                        eventsDb.del(txn, event_id);
-                                        relationsDb.del(txn, event_id);
-
-                                        std::string_view order{};
-                                        bool exists = msg2orderDb.get(txn, event_id, order);
-                                        if (exists) {
-                                                order2msgDb.del(txn, order);
-                                                msg2orderDb.del(txn, event_id);
-                                        }
-                                }
-                        }
-                        lmdb::cursor_del(cursor);
-                } else {
-                        if (obj.count("prev_batch") != 0)
-                                passed_pagination_token = true;
-                }
+        try {
+            obj = json::parse(std::string_view(val.data(), val.size()));
+        } catch (std::exception &) {
+            // workaround bug in the initial db format, where we sometimes didn't store
+            // json...
+            obj = {{"event_id", std::string(val.data(), val.size())}};
         }
 
-        auto msgCursor = lmdb::cursor::open(txn, order2msgDb);
-        start          = true;
-        while (msgCursor.get(indexVal, val, start ? MDB_LAST : MDB_PREV)) {
-                start = false;
+        if (passed_pagination_token) {
+            if (obj.count("event_id") != 0) {
+                std::string event_id = obj["event_id"].get();
 
-                std::string_view eventId;
-                bool innerStart = true;
-                bool found      = false;
-                while (cursor.get(indexVal, eventId, innerStart ? MDB_LAST : MDB_PREV)) {
-                        innerStart = false;
+                if (!event_id.empty()) {
+                    evToOrderDb.del(txn, event_id);
+                    eventsDb.del(txn, event_id);
+                    relationsDb.del(txn, event_id);
 
-                        json obj;
-                        try {
-                                obj = json::parse(std::string_view(eventId.data(), eventId.size()));
-                        } catch (std::exception &) {
-                                obj = {{"event_id", std::string(eventId.data(), eventId.size())}};
-                        }
-
-                        if (obj["event_id"] == std::string(val.data(), val.size())) {
-                                found = true;
-                                break;
-                        }
+                    std::string_view order{};
+                    bool exists = msg2orderDb.get(txn, event_id, order);
+                    if (exists) {
+                        order2msgDb.del(txn, order);
+                        msg2orderDb.del(txn, event_id);
+                    }
                 }
+            }
+            lmdb::cursor_del(cursor);
+        } else {
+            if (obj.count("prev_batch") != 0)
+                passed_pagination_token = true;
+        }
+    }
 
-                if (!found)
-                        break;
+    auto msgCursor = lmdb::cursor::open(txn, order2msgDb);
+    start          = true;
+    while (msgCursor.get(indexVal, val, start ? MDB_LAST : MDB_PREV)) {
+        start = false;
+
+        std::string_view eventId;
+        bool innerStart = true;
+        bool found      = false;
+        while (cursor.get(indexVal, eventId, innerStart ? MDB_LAST : MDB_PREV)) {
+            innerStart = false;
+
+            json obj;
+            try {
+                obj = json::parse(std::string_view(eventId.data(), eventId.size()));
+            } catch (std::exception &) {
+                obj = {{"event_id", std::string(eventId.data(), eventId.size())}};
+            }
+
+            if (obj["event_id"] == std::string(val.data(), val.size())) {
+                found = true;
+                break;
+            }
         }
 
-        do {
-                lmdb::cursor_del(msgCursor);
-        } while (msgCursor.get(indexVal, val, MDB_PREV));
+        if (!found)
+            break;
+    }
 
-        cursor.close();
-        msgCursor.close();
-        txn.commit();
+    do {
+        lmdb::cursor_del(msgCursor);
+    } while (msgCursor.get(indexVal, val, MDB_PREV));
+
+    cursor.close();
+    msgCursor.close();
+    txn.commit();
 }
 
 mtx::responses::Notifications
 Cache::getTimelineMentionsForRoom(lmdb::txn &txn, const std::string &room_id)
 {
-        auto db = getMentionsDb(txn, room_id);
+    auto db = getMentionsDb(txn, room_id);
 
-        if (db.size(txn) == 0) {
-                return mtx::responses::Notifications{};
-        }
+    if (db.size(txn) == 0) {
+        return mtx::responses::Notifications{};
+    }
 
-        mtx::responses::Notifications notif;
-        std::string_view event_id, msg;
+    mtx::responses::Notifications notif;
+    std::string_view event_id, msg;
 
-        auto cursor = lmdb::cursor::open(txn, db);
+    auto cursor = lmdb::cursor::open(txn, db);
 
-        while (cursor.get(event_id, msg, MDB_NEXT)) {
-                auto obj = json::parse(msg);
+    while (cursor.get(event_id, msg, MDB_NEXT)) {
+        auto obj = json::parse(msg);
 
-                if (obj.count("event") == 0)
-                        continue;
+        if (obj.count("event") == 0)
+            continue;
 
-                mtx::responses::Notification notification;
-                mtx::responses::from_json(obj, notification);
+        mtx::responses::Notification notification;
+        mtx::responses::from_json(obj, notification);
 
-                notif.notifications.push_back(notification);
-        }
-        cursor.close();
+        notif.notifications.push_back(notification);
+    }
+    cursor.close();
 
-        std::reverse(notif.notifications.begin(), notif.notifications.end());
+    std::reverse(notif.notifications.begin(), notif.notifications.end());
 
-        return notif;
+    return notif;
 }
 
 //! Add all notifications containing a user mention to the db.
 void
 Cache::saveTimelineMentions(const mtx::responses::Notifications &res)
 {
-        QMap> notifsByRoom;
+    QMap> notifsByRoom;
 
-        // Sort into room-specific 'buckets'
-        for (const auto ¬if : res.notifications) {
-                json val = notif;
-                notifsByRoom[notif.room_id].push_back(notif);
-        }
+    // Sort into room-specific 'buckets'
+    for (const auto ¬if : res.notifications) {
+        json val = notif;
+        notifsByRoom[notif.room_id].push_back(notif);
+    }
 
-        auto txn = lmdb::txn::begin(env_);
-        // Insert the entire set of mentions for each room at a time.
-        QMap>::const_iterator it =
-          notifsByRoom.constBegin();
-        auto end = notifsByRoom.constEnd();
-        while (it != end) {
-                nhlog::db()->debug("Storing notifications for " + it.key());
-                saveTimelineMentions(txn, it.key(), std::move(it.value()));
-                ++it;
-        }
+    auto txn = lmdb::txn::begin(env_);
+    // Insert the entire set of mentions for each room at a time.
+    QMap>::const_iterator it =
+      notifsByRoom.constBegin();
+    auto end = notifsByRoom.constEnd();
+    while (it != end) {
+        nhlog::db()->debug("Storing notifications for " + it.key());
+        saveTimelineMentions(txn, it.key(), std::move(it.value()));
+        ++it;
+    }
 
-        txn.commit();
+    txn.commit();
 }
 
 void
@@ -3321,138 +3241,138 @@ Cache::saveTimelineMentions(lmdb::txn &txn,
                             const std::string &room_id,
                             const QList &res)
 {
-        auto db = getMentionsDb(txn, room_id);
+    auto db = getMentionsDb(txn, room_id);
 
-        using namespace mtx::events;
-        using namespace mtx::events::state;
+    using namespace mtx::events;
+    using namespace mtx::events::state;
 
-        for (const auto ¬if : res) {
-                const auto event_id = mtx::accessors::event_id(notif.event);
+    for (const auto ¬if : res) {
+        const auto event_id = mtx::accessors::event_id(notif.event);
 
-                // double check that we have the correct room_id...
-                if (room_id.compare(notif.room_id) != 0) {
-                        return;
-                }
-
-                json obj = notif;
-
-                db.put(txn, event_id, obj.dump());
+        // double check that we have the correct room_id...
+        if (room_id.compare(notif.room_id) != 0) {
+            return;
         }
+
+        json obj = notif;
+
+        db.put(txn, event_id, obj.dump());
+    }
 }
 
 void
 Cache::markSentNotification(const std::string &event_id)
 {
-        auto txn = lmdb::txn::begin(env_);
-        notificationsDb_.put(txn, event_id, "");
-        txn.commit();
+    auto txn = lmdb::txn::begin(env_);
+    notificationsDb_.put(txn, event_id, "");
+    txn.commit();
 }
 
 void
 Cache::removeReadNotification(const std::string &event_id)
 {
-        auto txn = lmdb::txn::begin(env_);
+    auto txn = lmdb::txn::begin(env_);
 
-        notificationsDb_.del(txn, event_id);
+    notificationsDb_.del(txn, event_id);
 
-        txn.commit();
+    txn.commit();
 }
 
 bool
 Cache::isNotificationSent(const std::string &event_id)
 {
-        auto txn = ro_txn(env_);
+    auto txn = ro_txn(env_);
 
-        std::string_view value;
-        bool res = notificationsDb_.get(txn, event_id, value);
+    std::string_view value;
+    bool res = notificationsDb_.get(txn, event_id, value);
 
-        return res;
+    return res;
 }
 
 std::vector
 Cache::getRoomIds(lmdb::txn &txn)
 {
-        auto cursor = lmdb::cursor::open(txn, roomsDb_);
+    auto cursor = lmdb::cursor::open(txn, roomsDb_);
 
-        std::vector rooms;
+    std::vector rooms;
 
-        std::string_view room_id, _unused;
-        while (cursor.get(room_id, _unused, MDB_NEXT))
-                rooms.emplace_back(room_id);
+    std::string_view room_id, _unused;
+    while (cursor.get(room_id, _unused, MDB_NEXT))
+        rooms.emplace_back(room_id);
 
-        cursor.close();
+    cursor.close();
 
-        return rooms;
+    return rooms;
 }
 
 void
 Cache::deleteOldMessages()
 {
-        std::string_view indexVal, val;
+    std::string_view indexVal, val;
 
-        auto txn      = lmdb::txn::begin(env_);
-        auto room_ids = getRoomIds(txn);
+    auto txn      = lmdb::txn::begin(env_);
+    auto room_ids = getRoomIds(txn);
 
-        for (const auto &room_id : room_ids) {
-                auto orderDb     = getEventOrderDb(txn, room_id);
-                auto evToOrderDb = getEventToOrderDb(txn, room_id);
-                auto o2m         = getOrderToMessageDb(txn, room_id);
-                auto m2o         = getMessageToOrderDb(txn, room_id);
-                auto eventsDb    = getEventsDb(txn, room_id);
-                auto relationsDb = getRelationsDb(txn, room_id);
-                auto cursor      = lmdb::cursor::open(txn, orderDb);
+    for (const auto &room_id : room_ids) {
+        auto orderDb     = getEventOrderDb(txn, room_id);
+        auto evToOrderDb = getEventToOrderDb(txn, room_id);
+        auto o2m         = getOrderToMessageDb(txn, room_id);
+        auto m2o         = getMessageToOrderDb(txn, room_id);
+        auto eventsDb    = getEventsDb(txn, room_id);
+        auto relationsDb = getRelationsDb(txn, room_id);
+        auto cursor      = lmdb::cursor::open(txn, orderDb);
 
-                uint64_t first, last;
-                if (cursor.get(indexVal, val, MDB_LAST)) {
-                        last = lmdb::from_sv(indexVal);
-                } else {
-                        continue;
-                }
-                if (cursor.get(indexVal, val, MDB_FIRST)) {
-                        first = lmdb::from_sv(indexVal);
-                } else {
-                        continue;
-                }
-
-                size_t message_count = static_cast(last - first);
-                if (message_count < MAX_RESTORED_MESSAGES)
-                        continue;
-
-                bool start = true;
-                while (cursor.get(indexVal, val, start ? MDB_FIRST : MDB_NEXT) &&
-                       message_count-- > MAX_RESTORED_MESSAGES) {
-                        start    = false;
-                        auto obj = json::parse(std::string_view(val.data(), val.size()));
-
-                        if (obj.count("event_id") != 0) {
-                                std::string event_id = obj["event_id"].get();
-                                evToOrderDb.del(txn, event_id);
-                                eventsDb.del(txn, event_id);
-
-                                relationsDb.del(txn, event_id);
-
-                                std::string_view order{};
-                                bool exists = m2o.get(txn, event_id, order);
-                                if (exists) {
-                                        o2m.del(txn, order);
-                                        m2o.del(txn, event_id);
-                                }
-                        }
-                        cursor.del();
-                }
-                cursor.close();
+        uint64_t first, last;
+        if (cursor.get(indexVal, val, MDB_LAST)) {
+            last = lmdb::from_sv(indexVal);
+        } else {
+            continue;
         }
-        txn.commit();
+        if (cursor.get(indexVal, val, MDB_FIRST)) {
+            first = lmdb::from_sv(indexVal);
+        } else {
+            continue;
+        }
+
+        size_t message_count = static_cast(last - first);
+        if (message_count < MAX_RESTORED_MESSAGES)
+            continue;
+
+        bool start = true;
+        while (cursor.get(indexVal, val, start ? MDB_FIRST : MDB_NEXT) &&
+               message_count-- > MAX_RESTORED_MESSAGES) {
+            start    = false;
+            auto obj = json::parse(std::string_view(val.data(), val.size()));
+
+            if (obj.count("event_id") != 0) {
+                std::string event_id = obj["event_id"].get();
+                evToOrderDb.del(txn, event_id);
+                eventsDb.del(txn, event_id);
+
+                relationsDb.del(txn, event_id);
+
+                std::string_view order{};
+                bool exists = m2o.get(txn, event_id, order);
+                if (exists) {
+                    o2m.del(txn, order);
+                    m2o.del(txn, event_id);
+                }
+            }
+            cursor.del();
+        }
+        cursor.close();
+    }
+    txn.commit();
 }
 
 void
 Cache::deleteOldData() noexcept
 {
-        try {
-                deleteOldMessages();
-        } catch (const lmdb::error &e) {
-                nhlog::db()->error("failed to delete old messages: {}", e.what());
-        }
+    try {
+        deleteOldMessages();
+    } catch (const lmdb::error &e) {
+        nhlog::db()->error("failed to delete old messages: {}", e.what());
+    }
 }
 
 void
@@ -3460,241 +3380,232 @@ Cache::updateSpaces(lmdb::txn &txn,
                     const std::set &spaces_with_updates,
                     std::set rooms_with_updates)
 {
-        if (spaces_with_updates.empty() && rooms_with_updates.empty())
-                return;
+    if (spaces_with_updates.empty() && rooms_with_updates.empty())
+        return;
 
-        for (const auto &space : spaces_with_updates) {
-                // delete old entries
-                {
-                        auto cursor         = lmdb::cursor::open(txn, spacesChildrenDb_);
-                        bool first          = true;
-                        std::string_view sp = space, space_child = "";
+    for (const auto &space : spaces_with_updates) {
+        // delete old entries
+        {
+            auto cursor         = lmdb::cursor::open(txn, spacesChildrenDb_);
+            bool first          = true;
+            std::string_view sp = space, space_child = "";
 
-                        if (cursor.get(sp, space_child, MDB_SET)) {
-                                while (cursor.get(
-                                  sp, space_child, first ? MDB_FIRST_DUP : MDB_NEXT_DUP)) {
-                                        first = false;
-                                        spacesParentsDb_.del(txn, space_child, space);
-                                }
-                        }
-                        cursor.close();
-                        spacesChildrenDb_.del(txn, space);
-                }
-
-                for (const auto &event :
-                     getStateEventsWithType(txn, space)) {
-                        if (event.content.via.has_value() && event.state_key.size() > 3 &&
-                            event.state_key.at(0) == '!') {
-                                spacesChildrenDb_.put(txn, space, event.state_key);
-                                spacesParentsDb_.put(txn, event.state_key, space);
-                        }
+            if (cursor.get(sp, space_child, MDB_SET)) {
+                while (cursor.get(sp, space_child, first ? MDB_FIRST_DUP : MDB_NEXT_DUP)) {
+                    first = false;
+                    spacesParentsDb_.del(txn, space_child, space);
                 }
+            }
+            cursor.close();
+            spacesChildrenDb_.del(txn, space);
         }
 
-        const auto space_event_type = to_string(mtx::events::EventType::RoomPowerLevels);
-
-        for (const auto &room : rooms_with_updates) {
-                for (const auto &event :
-                     getStateEventsWithType(txn, room)) {
-                        if (event.content.via.has_value() && event.state_key.size() > 3 &&
-                            event.state_key.at(0) == '!') {
-                                const std::string &space = event.state_key;
-
-                                auto pls =
-                                  getStateEvent(txn, space);
-
-                                if (!pls)
-                                        continue;
-
-                                if (pls->content.user_level(event.sender) >=
-                                    pls->content.state_level(space_event_type)) {
-                                        spacesChildrenDb_.put(txn, space, room);
-                                        spacesParentsDb_.put(txn, room, space);
-                                }
-                        }
-                }
+        for (const auto &event :
+             getStateEventsWithType(txn, space)) {
+            if (event.content.via.has_value() && event.state_key.size() > 3 &&
+                event.state_key.at(0) == '!') {
+                spacesChildrenDb_.put(txn, space, event.state_key);
+                spacesParentsDb_.put(txn, event.state_key, space);
+            }
         }
+    }
+
+    const auto space_event_type = to_string(mtx::events::EventType::RoomPowerLevels);
+
+    for (const auto &room : rooms_with_updates) {
+        for (const auto &event :
+             getStateEventsWithType(txn, room)) {
+            if (event.content.via.has_value() && event.state_key.size() > 3 &&
+                event.state_key.at(0) == '!') {
+                const std::string &space = event.state_key;
+
+                auto pls = getStateEvent(txn, space);
+
+                if (!pls)
+                    continue;
+
+                if (pls->content.user_level(event.sender) >=
+                    pls->content.state_level(space_event_type)) {
+                    spacesChildrenDb_.put(txn, space, room);
+                    spacesParentsDb_.put(txn, room, space);
+                }
+            }
+        }
+    }
 }
 
 QMap>
 Cache::spaces()
 {
-        auto txn = ro_txn(env_);
+    auto txn = ro_txn(env_);
 
-        QMap> ret;
-        {
-                auto cursor = lmdb::cursor::open(txn, spacesChildrenDb_);
-                bool first  = true;
-                std::string_view space_id, space_child;
-                while (cursor.get(space_id, space_child, first ? MDB_FIRST : MDB_NEXT)) {
-                        first = false;
+    QMap> ret;
+    {
+        auto cursor = lmdb::cursor::open(txn, spacesChildrenDb_);
+        bool first  = true;
+        std::string_view space_id, space_child;
+        while (cursor.get(space_id, space_child, first ? MDB_FIRST : MDB_NEXT)) {
+            first = false;
 
-                        if (!space_child.empty()) {
-                                std::string_view room_data;
-                                if (roomsDb_.get(txn, space_id, room_data)) {
-                                        RoomInfo tmp = json::parse(std::move(room_data));
-                                        ret.insert(
-                                          QString::fromUtf8(space_id.data(), space_id.size()), tmp);
-                                } else {
-                                        ret.insert(
-                                          QString::fromUtf8(space_id.data(), space_id.size()),
-                                          std::nullopt);
-                                }
-                        }
+            if (!space_child.empty()) {
+                std::string_view room_data;
+                if (roomsDb_.get(txn, space_id, room_data)) {
+                    RoomInfo tmp = json::parse(std::move(room_data));
+                    ret.insert(QString::fromUtf8(space_id.data(), space_id.size()), tmp);
+                } else {
+                    ret.insert(QString::fromUtf8(space_id.data(), space_id.size()), std::nullopt);
                 }
-                cursor.close();
+            }
         }
+        cursor.close();
+    }
 
-        return ret;
+    return ret;
 }
 
 std::vector
 Cache::getParentRoomIds(const std::string &room_id)
 {
-        auto txn = ro_txn(env_);
+    auto txn = ro_txn(env_);
 
-        std::vector roomids;
-        {
-                auto cursor         = lmdb::cursor::open(txn, spacesParentsDb_);
-                bool first          = true;
-                std::string_view sp = room_id, space_parent;
-                if (cursor.get(sp, space_parent, MDB_SET)) {
-                        while (cursor.get(sp, space_parent, first ? MDB_FIRST_DUP : MDB_NEXT_DUP)) {
-                                first = false;
+    std::vector roomids;
+    {
+        auto cursor         = lmdb::cursor::open(txn, spacesParentsDb_);
+        bool first          = true;
+        std::string_view sp = room_id, space_parent;
+        if (cursor.get(sp, space_parent, MDB_SET)) {
+            while (cursor.get(sp, space_parent, first ? MDB_FIRST_DUP : MDB_NEXT_DUP)) {
+                first = false;
 
-                                if (!space_parent.empty())
-                                        roomids.emplace_back(space_parent);
-                        }
-                }
-                cursor.close();
+                if (!space_parent.empty())
+                    roomids.emplace_back(space_parent);
+            }
         }
+        cursor.close();
+    }
 
-        return roomids;
+    return roomids;
 }
 
 std::vector
 Cache::getChildRoomIds(const std::string &room_id)
 {
-        auto txn = ro_txn(env_);
+    auto txn = ro_txn(env_);
 
-        std::vector roomids;
-        {
-                auto cursor         = lmdb::cursor::open(txn, spacesChildrenDb_);
-                bool first          = true;
-                std::string_view sp = room_id, space_child;
-                if (cursor.get(sp, space_child, MDB_SET)) {
-                        while (cursor.get(sp, space_child, first ? MDB_FIRST_DUP : MDB_NEXT_DUP)) {
-                                first = false;
+    std::vector roomids;
+    {
+        auto cursor         = lmdb::cursor::open(txn, spacesChildrenDb_);
+        bool first          = true;
+        std::string_view sp = room_id, space_child;
+        if (cursor.get(sp, space_child, MDB_SET)) {
+            while (cursor.get(sp, space_child, first ? MDB_FIRST_DUP : MDB_NEXT_DUP)) {
+                first = false;
 
-                                if (!space_child.empty())
-                                        roomids.emplace_back(space_child);
-                        }
-                }
-                cursor.close();
+                if (!space_child.empty())
+                    roomids.emplace_back(space_child);
+            }
         }
+        cursor.close();
+    }
 
-        return roomids;
+    return roomids;
 }
 
 std::vector
 Cache::getImagePacks(const std::string &room_id, std::optional stickers)
 {
-        auto txn = ro_txn(env_);
-        std::vector infos;
+    auto txn = ro_txn(env_);
+    std::vector infos;
 
-        auto addPack = [&infos, stickers](const mtx::events::msc2545::ImagePack &pack,
-                                          const std::string &source_room,
-                                          const std::string &state_key) {
-                if (!pack.pack || !stickers.has_value() ||
-                    (stickers.value() ? pack.pack->is_sticker() : pack.pack->is_emoji())) {
-                        ImagePackInfo info;
-                        info.source_room = source_room;
-                        info.state_key   = state_key;
-                        info.pack.pack   = pack.pack;
+    auto addPack = [&infos, stickers](const mtx::events::msc2545::ImagePack &pack,
+                                      const std::string &source_room,
+                                      const std::string &state_key) {
+        if (!pack.pack || !stickers.has_value() ||
+            (stickers.value() ? pack.pack->is_sticker() : pack.pack->is_emoji())) {
+            ImagePackInfo info;
+            info.source_room = source_room;
+            info.state_key   = state_key;
+            info.pack.pack   = pack.pack;
 
-                        for (const auto &img : pack.images) {
-                                if (stickers.has_value() && img.second.overrides_usage() &&
-                                    (stickers ? !img.second.is_sticker() : !img.second.is_emoji()))
-                                        continue;
+            for (const auto &img : pack.images) {
+                if (stickers.has_value() && img.second.overrides_usage() &&
+                    (stickers ? !img.second.is_sticker() : !img.second.is_emoji()))
+                    continue;
 
-                                info.pack.images.insert(img);
-                        }
+                info.pack.images.insert(img);
+            }
 
-                        if (!info.pack.images.empty())
-                                infos.push_back(std::move(info));
+            if (!info.pack.images.empty())
+                infos.push_back(std::move(info));
+        }
+    };
+
+    // packs from account data
+    if (auto accountpack =
+          getAccountData(txn, mtx::events::EventType::ImagePackInAccountData, "")) {
+        auto tmp =
+          std::get_if>(&*accountpack);
+        if (tmp)
+            addPack(tmp->content, "", "");
+    }
+
+    // packs from rooms, that were enabled globally
+    if (auto roomPacks = getAccountData(txn, mtx::events::EventType::ImagePackRooms, "")) {
+        auto tmp = std::get_if>(
+          &*roomPacks);
+        if (tmp) {
+            for (const auto &[room_id2, state_to_d] : tmp->content.rooms) {
+                // don't add stickers from this room twice
+                if (room_id2 == room_id)
+                    continue;
+
+                for (const auto &[state_id, d] : state_to_d) {
+                    (void)d;
+                    if (auto pack =
+                          getStateEvent(txn, room_id2, state_id))
+                        addPack(pack->content, room_id2, state_id);
                 }
-        };
-
-        // packs from account data
-        if (auto accountpack =
-              getAccountData(txn, mtx::events::EventType::ImagePackInAccountData, "")) {
-                auto tmp =
-                  std::get_if>(
-                    &*accountpack);
-                if (tmp)
-                        addPack(tmp->content, "", "");
+            }
         }
+    }
 
-        // packs from rooms, that were enabled globally
-        if (auto roomPacks = getAccountData(txn, mtx::events::EventType::ImagePackRooms, "")) {
-                auto tmp =
-                  std::get_if>(
-                    &*roomPacks);
-                if (tmp) {
-                        for (const auto &[room_id2, state_to_d] : tmp->content.rooms) {
-                                // don't add stickers from this room twice
-                                if (room_id2 == room_id)
-                                        continue;
+    // packs from current room
+    if (auto pack = getStateEvent(txn, room_id)) {
+        addPack(pack->content, room_id, "");
+    }
+    for (const auto &pack : getStateEventsWithType(txn, room_id)) {
+        addPack(pack.content, room_id, pack.state_key);
+    }
 
-                                for (const auto &[state_id, d] : state_to_d) {
-                                        (void)d;
-                                        if (auto pack =
-                                              getStateEvent(
-                                                txn, room_id2, state_id))
-                                                addPack(pack->content, room_id2, state_id);
-                                }
-                        }
-                }
-        }
-
-        // packs from current room
-        if (auto pack = getStateEvent(txn, room_id)) {
-                addPack(pack->content, room_id, "");
-        }
-        for (const auto &pack :
-             getStateEventsWithType(txn, room_id)) {
-                addPack(pack.content, room_id, pack.state_key);
-        }
-
-        return infos;
+    return infos;
 }
 
 std::optional
 Cache::getAccountData(mtx::events::EventType type, const std::string &room_id)
 {
-        auto txn = ro_txn(env_);
-        return getAccountData(txn, type, room_id);
+    auto txn = ro_txn(env_);
+    return getAccountData(txn, type, room_id);
 }
 
 std::optional
 Cache::getAccountData(lmdb::txn &txn, mtx::events::EventType type, const std::string &room_id)
 {
-        try {
-                auto db = getAccountDataDb(txn, room_id);
+    try {
+        auto db = getAccountDataDb(txn, room_id);
 
-                std::string_view data;
-                if (db.get(txn, to_string(type), data)) {
-                        mtx::responses::utils::RoomAccountDataEvents events;
-                        json j = json::array({
-                          json::parse(data),
-                        });
-                        mtx::responses::utils::parse_room_account_data_events(j, events);
-                        if (events.size() == 1)
-                                return events.front();
-                }
-        } catch (...) {
+        std::string_view data;
+        if (db.get(txn, to_string(type), data)) {
+            mtx::responses::utils::RoomAccountDataEvents events;
+            json j = json::array({
+              json::parse(data),
+            });
+            mtx::responses::utils::parse_room_account_data_events(j, events);
+            if (events.size() == 1)
+                return events.front();
         }
-        return std::nullopt;
+    } catch (...) {
+    }
+    return std::nullopt;
 }
 
 bool
@@ -3702,465 +3613,436 @@ Cache::hasEnoughPowerLevel(const std::vector &eventTypes
                            const std::string &room_id,
                            const std::string &user_id)
 {
-        using namespace mtx::events;
-        using namespace mtx::events::state;
+    using namespace mtx::events;
+    using namespace mtx::events::state;
 
-        auto txn = lmdb::txn::begin(env_);
-        auto db  = getStatesDb(txn, room_id);
+    auto txn = lmdb::txn::begin(env_);
+    auto db  = getStatesDb(txn, room_id);
 
-        int64_t min_event_level = std::numeric_limits::max();
-        int64_t user_level      = std::numeric_limits::min();
+    int64_t min_event_level = std::numeric_limits::max();
+    int64_t user_level      = std::numeric_limits::min();
 
-        std::string_view event;
-        bool res = db.get(txn, to_string(EventType::RoomPowerLevels), event);
+    std::string_view event;
+    bool res = db.get(txn, to_string(EventType::RoomPowerLevels), event);
 
-        if (res) {
-                try {
-                        StateEvent msg =
-                          json::parse(std::string_view(event.data(), event.size()));
+    if (res) {
+        try {
+            StateEvent msg = json::parse(std::string_view(event.data(), event.size()));
 
-                        user_level = msg.content.user_level(user_id);
+            user_level = msg.content.user_level(user_id);
 
-                        for (const auto &ty : eventTypes)
-                                min_event_level =
-                                  std::min(min_event_level, msg.content.state_level(to_string(ty)));
-                } catch (const json::exception &e) {
-                        nhlog::db()->warn("failed to parse m.room.power_levels event: {}",
-                                          e.what());
-                }
+            for (const auto &ty : eventTypes)
+                min_event_level = std::min(min_event_level, msg.content.state_level(to_string(ty)));
+        } catch (const json::exception &e) {
+            nhlog::db()->warn("failed to parse m.room.power_levels event: {}", e.what());
         }
+    }
 
-        txn.commit();
+    txn.commit();
 
-        return user_level >= min_event_level;
+    return user_level >= min_event_level;
 }
 
 std::vector
 Cache::roomMembers(const std::string &room_id)
 {
-        auto txn = ro_txn(env_);
+    auto txn = ro_txn(env_);
 
-        std::vector members;
-        std::string_view user_id, unused;
+    std::vector members;
+    std::string_view user_id, unused;
 
-        auto db = getMembersDb(txn, room_id);
+    auto db = getMembersDb(txn, room_id);
 
-        auto cursor = lmdb::cursor::open(txn, db);
-        while (cursor.get(user_id, unused, MDB_NEXT))
-                members.emplace_back(user_id);
-        cursor.close();
+    auto cursor = lmdb::cursor::open(txn, db);
+    while (cursor.get(user_id, unused, MDB_NEXT))
+        members.emplace_back(user_id);
+    cursor.close();
 
-        return members;
+    return members;
 }
 
 crypto::Trust
 Cache::roomVerificationStatus(const std::string &room_id)
 {
-        crypto::Trust trust = crypto::Verified;
+    crypto::Trust trust = crypto::Verified;
 
-        try {
-                auto txn = lmdb::txn::begin(env_);
+    try {
+        auto txn = lmdb::txn::begin(env_);
 
-                auto db     = getMembersDb(txn, room_id);
-                auto keysDb = getUserKeysDb(txn);
-                std::vector keysToRequest;
+        auto db     = getMembersDb(txn, room_id);
+        auto keysDb = getUserKeysDb(txn);
+        std::vector keysToRequest;
 
-                std::string_view user_id, unused;
-                auto cursor = lmdb::cursor::open(txn, db);
-                while (cursor.get(user_id, unused, MDB_NEXT)) {
-                        auto verif = verificationStatus_(std::string(user_id), txn);
-                        if (verif.unverified_device_count) {
-                                trust = crypto::Unverified;
-                                if (verif.verified_devices.empty() && verif.no_keys) {
-                                        // we probably don't have the keys yet, so query them
-                                        keysToRequest.push_back(std::string(user_id));
-                                }
-                        } else if (verif.user_verified == crypto::TOFU && trust == crypto::Verified)
-                                trust = crypto::TOFU;
-                }
-
-                if (!keysToRequest.empty())
-                        markUserKeysOutOfDate(txn, keysDb, keysToRequest, "");
-
-        } catch (std::exception &e) {
-                nhlog::db()->error(
-                  "Failed to calculate verification status for {}: {}", room_id, e.what());
+        std::string_view user_id, unused;
+        auto cursor = lmdb::cursor::open(txn, db);
+        while (cursor.get(user_id, unused, MDB_NEXT)) {
+            auto verif = verificationStatus_(std::string(user_id), txn);
+            if (verif.unverified_device_count) {
                 trust = crypto::Unverified;
+                if (verif.verified_devices.empty() && verif.no_keys) {
+                    // we probably don't have the keys yet, so query them
+                    keysToRequest.push_back(std::string(user_id));
+                }
+            } else if (verif.user_verified == crypto::TOFU && trust == crypto::Verified)
+                trust = crypto::TOFU;
         }
 
-        return trust;
+        if (!keysToRequest.empty())
+            markUserKeysOutOfDate(txn, keysDb, keysToRequest, "");
+
+    } catch (std::exception &e) {
+        nhlog::db()->error("Failed to calculate verification status for {}: {}", room_id, e.what());
+        trust = crypto::Unverified;
+    }
+
+    return trust;
 }
 
 std::map>
 Cache::getMembersWithKeys(const std::string &room_id, bool verified_only)
 {
-        std::string_view keys;
+    std::string_view keys;
 
-        try {
-                auto txn = ro_txn(env_);
-                std::map> members;
+    try {
+        auto txn = ro_txn(env_);
+        std::map> members;
 
-                auto db     = getMembersDb(txn, room_id);
-                auto keysDb = getUserKeysDb(txn);
+        auto db     = getMembersDb(txn, room_id);
+        auto keysDb = getUserKeysDb(txn);
 
-                std::string_view user_id, unused;
-                auto cursor = lmdb::cursor::open(txn, db);
-                while (cursor.get(user_id, unused, MDB_NEXT)) {
-                        auto res = keysDb.get(txn, user_id, keys);
+        std::string_view user_id, unused;
+        auto cursor = lmdb::cursor::open(txn, db);
+        while (cursor.get(user_id, unused, MDB_NEXT)) {
+            auto res = keysDb.get(txn, user_id, keys);
 
-                        if (res) {
-                                auto k = json::parse(keys).get();
-                                if (verified_only) {
-                                        auto verif = verificationStatus_(std::string(user_id), txn);
+            if (res) {
+                auto k = json::parse(keys).get();
+                if (verified_only) {
+                    auto verif = verificationStatus_(std::string(user_id), txn);
 
-                                        if (verif.user_verified == crypto::Trust::Verified ||
-                                            !verif.verified_devices.empty()) {
-                                                auto keyCopy = k;
-                                                keyCopy.device_keys.clear();
+                    if (verif.user_verified == crypto::Trust::Verified ||
+                        !verif.verified_devices.empty()) {
+                        auto keyCopy = k;
+                        keyCopy.device_keys.clear();
 
-                                                std::copy_if(
-                                                  k.device_keys.begin(),
-                                                  k.device_keys.end(),
-                                                  std::inserter(keyCopy.device_keys,
-                                                                keyCopy.device_keys.end()),
-                                                  [&verif](const auto &key) {
-                                                          auto curve25519 = key.second.keys.find(
-                                                            "curve25519:" + key.first);
-                                                          if (curve25519 == key.second.keys.end())
-                                                                  return false;
-                                                          if (auto t =
-                                                                verif.verified_device_keys.find(
-                                                                  curve25519->second);
-                                                              t ==
-                                                                verif.verified_device_keys.end() ||
-                                                              t->second != crypto::Trust::Verified)
-                                                                  return false;
+                        std::copy_if(
+                          k.device_keys.begin(),
+                          k.device_keys.end(),
+                          std::inserter(keyCopy.device_keys, keyCopy.device_keys.end()),
+                          [&verif](const auto &key) {
+                              auto curve25519 = key.second.keys.find("curve25519:" + key.first);
+                              if (curve25519 == key.second.keys.end())
+                                  return false;
+                              if (auto t = verif.verified_device_keys.find(curve25519->second);
+                                  t == verif.verified_device_keys.end() ||
+                                  t->second != crypto::Trust::Verified)
+                                  return false;
 
-                                                          return key.first ==
-                                                                   key.second.device_id &&
-                                                                 std::find(
-                                                                   verif.verified_devices.begin(),
-                                                                   verif.verified_devices.end(),
-                                                                   key.first) !=
-                                                                   verif.verified_devices.end();
-                                                  });
+                              return key.first == key.second.device_id &&
+                                     std::find(verif.verified_devices.begin(),
+                                               verif.verified_devices.end(),
+                                               key.first) != verif.verified_devices.end();
+                          });
 
-                                                if (!keyCopy.device_keys.empty())
-                                                        members[std::string(user_id)] =
-                                                          std::move(keyCopy);
-                                        }
-                                } else {
-                                        members[std::string(user_id)] = std::move(k);
-                                }
-                        } else {
-                                if (!verified_only)
-                                        members[std::string(user_id)] = {};
-                        }
+                        if (!keyCopy.device_keys.empty())
+                            members[std::string(user_id)] = std::move(keyCopy);
+                    }
+                } else {
+                    members[std::string(user_id)] = std::move(k);
                 }
-                cursor.close();
-
-                return members;
-        } catch (std::exception &e) {
-                nhlog::db()->debug("Error retrieving members: {}", e.what());
-                return {};
+            } else {
+                if (!verified_only)
+                    members[std::string(user_id)] = {};
+            }
         }
+        cursor.close();
+
+        return members;
+    } catch (std::exception &e) {
+        nhlog::db()->debug("Error retrieving members: {}", e.what());
+        return {};
+    }
 }
 
 QString
 Cache::displayName(const QString &room_id, const QString &user_id)
 {
-        if (auto info = getMember(room_id.toStdString(), user_id.toStdString());
-            info && !info->name.empty())
-                return QString::fromStdString(info->name);
+    if (auto info = getMember(room_id.toStdString(), user_id.toStdString());
+        info && !info->name.empty())
+        return QString::fromStdString(info->name);
 
-        return user_id;
+    return user_id;
 }
 
 std::string
 Cache::displayName(const std::string &room_id, const std::string &user_id)
 {
-        if (auto info = getMember(room_id, user_id); info && !info->name.empty())
-                return info->name;
+    if (auto info = getMember(room_id, user_id); info && !info->name.empty())
+        return info->name;
 
-        return user_id;
+    return user_id;
 }
 
 QString
 Cache::avatarUrl(const QString &room_id, const QString &user_id)
 {
-        if (auto info = getMember(room_id.toStdString(), user_id.toStdString());
-            info && !info->avatar_url.empty())
-                return QString::fromStdString(info->avatar_url);
+    if (auto info = getMember(room_id.toStdString(), user_id.toStdString());
+        info && !info->avatar_url.empty())
+        return QString::fromStdString(info->avatar_url);
 
-        return "";
+    return "";
 }
 
 mtx::presence::PresenceState
 Cache::presenceState(const std::string &user_id)
 {
-        if (user_id.empty())
-                return {};
+    if (user_id.empty())
+        return {};
 
-        std::string_view presenceVal;
+    std::string_view presenceVal;
 
-        auto txn = lmdb::txn::begin(env_);
-        auto db  = getPresenceDb(txn);
-        auto res = db.get(txn, user_id, presenceVal);
+    auto txn = lmdb::txn::begin(env_);
+    auto db  = getPresenceDb(txn);
+    auto res = db.get(txn, user_id, presenceVal);
 
-        mtx::presence::PresenceState state = mtx::presence::offline;
+    mtx::presence::PresenceState state = mtx::presence::offline;
 
-        if (res) {
-                mtx::events::presence::Presence presence =
-                  json::parse(std::string_view(presenceVal.data(), presenceVal.size()));
-                state = presence.presence;
-        }
+    if (res) {
+        mtx::events::presence::Presence presence =
+          json::parse(std::string_view(presenceVal.data(), presenceVal.size()));
+        state = presence.presence;
+    }
 
-        txn.commit();
+    txn.commit();
 
-        return state;
+    return state;
 }
 
 std::string
 Cache::statusMessage(const std::string &user_id)
 {
-        if (user_id.empty())
-                return {};
+    if (user_id.empty())
+        return {};
 
-        std::string_view presenceVal;
+    std::string_view presenceVal;
 
-        auto txn = lmdb::txn::begin(env_);
-        auto db  = getPresenceDb(txn);
-        auto res = db.get(txn, user_id, presenceVal);
+    auto txn = lmdb::txn::begin(env_);
+    auto db  = getPresenceDb(txn);
+    auto res = db.get(txn, user_id, presenceVal);
 
-        std::string status_msg;
+    std::string status_msg;
 
-        if (res) {
-                mtx::events::presence::Presence presence = json::parse(presenceVal);
-                status_msg                               = presence.status_msg;
-        }
+    if (res) {
+        mtx::events::presence::Presence presence = json::parse(presenceVal);
+        status_msg                               = presence.status_msg;
+    }
 
-        txn.commit();
+    txn.commit();
 
-        return status_msg;
+    return status_msg;
 }
 
 void
 to_json(json &j, const UserKeyCache &info)
 {
-        j["device_keys"]        = info.device_keys;
-        j["seen_device_keys"]   = info.seen_device_keys;
-        j["seen_device_ids"]    = info.seen_device_ids;
-        j["master_keys"]        = info.master_keys;
-        j["master_key_changed"] = info.master_key_changed;
-        j["user_signing_keys"]  = info.user_signing_keys;
-        j["self_signing_keys"]  = info.self_signing_keys;
-        j["updated_at"]         = info.updated_at;
-        j["last_changed"]       = info.last_changed;
+    j["device_keys"]        = info.device_keys;
+    j["seen_device_keys"]   = info.seen_device_keys;
+    j["seen_device_ids"]    = info.seen_device_ids;
+    j["master_keys"]        = info.master_keys;
+    j["master_key_changed"] = info.master_key_changed;
+    j["user_signing_keys"]  = info.user_signing_keys;
+    j["self_signing_keys"]  = info.self_signing_keys;
+    j["updated_at"]         = info.updated_at;
+    j["last_changed"]       = info.last_changed;
 }
 
 void
 from_json(const json &j, UserKeyCache &info)
 {
-        info.device_keys = j.value("device_keys", std::map{});
-        info.seen_device_keys   = j.value("seen_device_keys", std::set{});
-        info.seen_device_ids    = j.value("seen_device_ids", std::set{});
-        info.master_keys        = j.value("master_keys", mtx::crypto::CrossSigningKeys{});
-        info.master_key_changed = j.value("master_key_changed", false);
-        info.user_signing_keys  = j.value("user_signing_keys", mtx::crypto::CrossSigningKeys{});
-        info.self_signing_keys  = j.value("self_signing_keys", mtx::crypto::CrossSigningKeys{});
-        info.updated_at         = j.value("updated_at", "");
-        info.last_changed       = j.value("last_changed", "");
+    info.device_keys = j.value("device_keys", std::map{});
+    info.seen_device_keys   = j.value("seen_device_keys", std::set{});
+    info.seen_device_ids    = j.value("seen_device_ids", std::set{});
+    info.master_keys        = j.value("master_keys", mtx::crypto::CrossSigningKeys{});
+    info.master_key_changed = j.value("master_key_changed", false);
+    info.user_signing_keys  = j.value("user_signing_keys", mtx::crypto::CrossSigningKeys{});
+    info.self_signing_keys  = j.value("self_signing_keys", mtx::crypto::CrossSigningKeys{});
+    info.updated_at         = j.value("updated_at", "");
+    info.last_changed       = j.value("last_changed", "");
 }
 
 std::optional
 Cache::userKeys(const std::string &user_id)
 {
-        auto txn = ro_txn(env_);
-        return userKeys_(user_id, txn);
+    auto txn = ro_txn(env_);
+    return userKeys_(user_id, txn);
 }
 
 std::optional
 Cache::userKeys_(const std::string &user_id, lmdb::txn &txn)
 {
-        std::string_view keys;
+    std::string_view keys;
 
-        try {
-                auto db  = getUserKeysDb(txn);
-                auto res = db.get(txn, user_id, keys);
+    try {
+        auto db  = getUserKeysDb(txn);
+        auto res = db.get(txn, user_id, keys);
 
-                if (res) {
-                        return json::parse(keys).get();
-                } else {
-                        return {};
-                }
-        } catch (std::exception &e) {
-                nhlog::db()->error("Failed to retrieve user keys for {}: {}", user_id, e.what());
-                return {};
+        if (res) {
+            return json::parse(keys).get();
+        } else {
+            return {};
         }
+    } catch (std::exception &e) {
+        nhlog::db()->error("Failed to retrieve user keys for {}: {}", user_id, e.what());
+        return {};
+    }
 }
 
 void
 Cache::updateUserKeys(const std::string &sync_token, const mtx::responses::QueryKeys &keyQuery)
 {
-        auto txn = lmdb::txn::begin(env_);
-        auto db  = getUserKeysDb(txn);
+    auto txn = lmdb::txn::begin(env_);
+    auto db  = getUserKeysDb(txn);
 
-        std::map updates;
+    std::map updates;
 
-        for (const auto &[user, keys] : keyQuery.device_keys)
-                updates[user].device_keys = keys;
-        for (const auto &[user, keys] : keyQuery.master_keys)
-                updates[user].master_keys = keys;
-        for (const auto &[user, keys] : keyQuery.user_signing_keys)
-                updates[user].user_signing_keys = keys;
-        for (const auto &[user, keys] : keyQuery.self_signing_keys)
-                updates[user].self_signing_keys = keys;
+    for (const auto &[user, keys] : keyQuery.device_keys)
+        updates[user].device_keys = keys;
+    for (const auto &[user, keys] : keyQuery.master_keys)
+        updates[user].master_keys = keys;
+    for (const auto &[user, keys] : keyQuery.user_signing_keys)
+        updates[user].user_signing_keys = keys;
+    for (const auto &[user, keys] : keyQuery.self_signing_keys)
+        updates[user].self_signing_keys = keys;
 
-        for (auto &[user, update] : updates) {
-                nhlog::db()->debug("Updated user keys: {}", user);
+    for (auto &[user, update] : updates) {
+        nhlog::db()->debug("Updated user keys: {}", user);
 
-                auto updateToWrite = update;
+        auto updateToWrite = update;
 
-                std::string_view oldKeys;
-                auto res = db.get(txn, user, oldKeys);
+        std::string_view oldKeys;
+        auto res = db.get(txn, user, oldKeys);
 
-                if (res) {
-                        updateToWrite     = json::parse(oldKeys).get();
-                        auto last_changed = updateToWrite.last_changed;
-                        // skip if we are tracking this and expect it to be up to date with the last
-                        // sync token
-                        if (!last_changed.empty() && last_changed != sync_token) {
-                                nhlog::db()->debug("Not storing update for user {}, because "
-                                                   "last_changed {}, but we fetched update for {}",
-                                                   user,
-                                                   last_changed,
-                                                   sync_token);
-                                continue;
+        if (res) {
+            updateToWrite     = json::parse(oldKeys).get();
+            auto last_changed = updateToWrite.last_changed;
+            // skip if we are tracking this and expect it to be up to date with the last
+            // sync token
+            if (!last_changed.empty() && last_changed != sync_token) {
+                nhlog::db()->debug("Not storing update for user {}, because "
+                                   "last_changed {}, but we fetched update for {}",
+                                   user,
+                                   last_changed,
+                                   sync_token);
+                continue;
+            }
+
+            if (!updateToWrite.master_keys.keys.empty() &&
+                update.master_keys.keys != updateToWrite.master_keys.keys) {
+                nhlog::db()->debug("Master key of {} changed:\nold: {}\nnew: {}",
+                                   user,
+                                   updateToWrite.master_keys.keys.size(),
+                                   update.master_keys.keys.size());
+                updateToWrite.master_key_changed = true;
+            }
+
+            updateToWrite.master_keys       = update.master_keys;
+            updateToWrite.self_signing_keys = update.self_signing_keys;
+            updateToWrite.user_signing_keys = update.user_signing_keys;
+
+            auto oldDeviceKeys = std::move(updateToWrite.device_keys);
+            updateToWrite.device_keys.clear();
+
+            // Don't insert keys, which we have seen once already
+            for (const auto &[device_id, device_keys] : update.device_keys) {
+                if (oldDeviceKeys.count(device_id) &&
+                    oldDeviceKeys.at(device_id).keys == device_keys.keys) {
+                    // this is safe, since the keys are the same
+                    updateToWrite.device_keys[device_id] = device_keys;
+                } else {
+                    bool keyReused = false;
+                    for (const auto &[key_id, key] : device_keys.keys) {
+                        (void)key_id;
+                        if (updateToWrite.seen_device_keys.count(key)) {
+                            nhlog::crypto()->warn(
+                              "Key '{}' reused by ({}: {})", key, user, device_id);
+                            keyReused = true;
+                            break;
+                        }
+                        if (updateToWrite.seen_device_ids.count(device_id)) {
+                            nhlog::crypto()->warn("device_id '{}' reused by ({})", device_id, user);
+                            keyReused = true;
+                            break;
+                        }
+                    }
+
+                    if (!keyReused && !oldDeviceKeys.count(device_id)) {
+                        // ensure the key has a valid signature from itself
+                        std::string device_signing_key = "ed25519:" + device_keys.device_id;
+                        if (device_id != device_keys.device_id) {
+                            nhlog::crypto()->warn("device {}:{} has a different device id "
+                                                  "in the body: {}",
+                                                  user,
+                                                  device_id,
+                                                  device_keys.device_id);
+                            continue;
+                        }
+                        if (!device_keys.signatures.count(user) ||
+                            !device_keys.signatures.at(user).count(device_signing_key)) {
+                            nhlog::crypto()->warn("device {}:{} has no signature", user, device_id);
+                            continue;
                         }
 
-                        if (!updateToWrite.master_keys.keys.empty() &&
-                            update.master_keys.keys != updateToWrite.master_keys.keys) {
-                                nhlog::db()->debug("Master key of {} changed:\nold: {}\nnew: {}",
-                                                   user,
-                                                   updateToWrite.master_keys.keys.size(),
-                                                   update.master_keys.keys.size());
-                                updateToWrite.master_key_changed = true;
+                        if (!mtx::crypto::ed25519_verify_signature(
+                              device_keys.keys.at(device_signing_key),
+                              json(device_keys),
+                              device_keys.signatures.at(user).at(device_signing_key))) {
+                            nhlog::crypto()->warn(
+                              "device {}:{} has an invalid signature", user, device_id);
+                            continue;
                         }
 
-                        updateToWrite.master_keys       = update.master_keys;
-                        updateToWrite.self_signing_keys = update.self_signing_keys;
-                        updateToWrite.user_signing_keys = update.user_signing_keys;
-
-                        auto oldDeviceKeys = std::move(updateToWrite.device_keys);
-                        updateToWrite.device_keys.clear();
-
-                        // Don't insert keys, which we have seen once already
-                        for (const auto &[device_id, device_keys] : update.device_keys) {
-                                if (oldDeviceKeys.count(device_id) &&
-                                    oldDeviceKeys.at(device_id).keys == device_keys.keys) {
-                                        // this is safe, since the keys are the same
-                                        updateToWrite.device_keys[device_id] = device_keys;
-                                } else {
-                                        bool keyReused = false;
-                                        for (const auto &[key_id, key] : device_keys.keys) {
-                                                (void)key_id;
-                                                if (updateToWrite.seen_device_keys.count(key)) {
-                                                        nhlog::crypto()->warn(
-                                                          "Key '{}' reused by ({}: {})",
-                                                          key,
-                                                          user,
-                                                          device_id);
-                                                        keyReused = true;
-                                                        break;
-                                                }
-                                                if (updateToWrite.seen_device_ids.count(
-                                                      device_id)) {
-                                                        nhlog::crypto()->warn(
-                                                          "device_id '{}' reused by ({})",
-                                                          device_id,
-                                                          user);
-                                                        keyReused = true;
-                                                        break;
-                                                }
-                                        }
-
-                                        if (!keyReused && !oldDeviceKeys.count(device_id)) {
-                                                // ensure the key has a valid signature from itself
-                                                std::string device_signing_key =
-                                                  "ed25519:" + device_keys.device_id;
-                                                if (device_id != device_keys.device_id) {
-                                                        nhlog::crypto()->warn(
-                                                          "device {}:{} has a different device id "
-                                                          "in the body: {}",
-                                                          user,
-                                                          device_id,
-                                                          device_keys.device_id);
-                                                        continue;
-                                                }
-                                                if (!device_keys.signatures.count(user) ||
-                                                    !device_keys.signatures.at(user).count(
-                                                      device_signing_key)) {
-                                                        nhlog::crypto()->warn(
-                                                          "device {}:{} has no signature",
-                                                          user,
-                                                          device_id);
-                                                        continue;
-                                                }
-
-                                                if (!mtx::crypto::ed25519_verify_signature(
-                                                      device_keys.keys.at(device_signing_key),
-                                                      json(device_keys),
-                                                      device_keys.signatures.at(user).at(
-                                                        device_signing_key))) {
-                                                        nhlog::crypto()->warn(
-                                                          "device {}:{} has an invalid signature",
-                                                          user,
-                                                          device_id);
-                                                        continue;
-                                                }
-
-                                                updateToWrite.device_keys[device_id] = device_keys;
-                                        }
-                                }
-
-                                for (const auto &[key_id, key] : device_keys.keys) {
-                                        (void)key_id;
-                                        updateToWrite.seen_device_keys.insert(key);
-                                }
-                                updateToWrite.seen_device_ids.insert(device_id);
-                        }
+                        updateToWrite.device_keys[device_id] = device_keys;
+                    }
                 }
-                updateToWrite.updated_at = sync_token;
-                db.put(txn, user, json(updateToWrite).dump());
-        }
 
-        txn.commit();
-
-        std::map tmp;
-        const auto local_user = utils::localUser().toStdString();
-
-        {
-                std::unique_lock lock(verification_storage.verification_storage_mtx);
-                for (auto &[user_id, update] : updates) {
-                        (void)update;
-                        if (user_id == local_user) {
-                                std::swap(tmp, verification_storage.status);
-                        } else {
-                                verification_storage.status.erase(user_id);
-                        }
+                for (const auto &[key_id, key] : device_keys.keys) {
+                    (void)key_id;
+                    updateToWrite.seen_device_keys.insert(key);
                 }
+                updateToWrite.seen_device_ids.insert(device_id);
+            }
         }
+        updateToWrite.updated_at = sync_token;
+        db.put(txn, user, json(updateToWrite).dump());
+    }
 
+    txn.commit();
+
+    std::map tmp;
+    const auto local_user = utils::localUser().toStdString();
+
+    {
+        std::unique_lock lock(verification_storage.verification_storage_mtx);
         for (auto &[user_id, update] : updates) {
-                (void)update;
-                if (user_id == local_user) {
-                        for (const auto &[user, status] : tmp) {
-                                (void)status;
-                                emit verificationStatusChanged(user);
-                        }
-                }
-                emit verificationStatusChanged(user_id);
+            (void)update;
+            if (user_id == local_user) {
+                std::swap(tmp, verification_storage.status);
+            } else {
+                verification_storage.status.erase(user_id);
+            }
         }
+    }
+
+    for (auto &[user_id, update] : updates) {
+        (void)update;
+        if (user_id == local_user) {
+            for (const auto &[user, status] : tmp) {
+                (void)status;
+                emit verificationStatusChanged(user);
+            }
+        }
+        emit verificationStatusChanged(user_id);
+    }
 }
 
 void
@@ -4169,780 +4051,772 @@ Cache::markUserKeysOutOfDate(lmdb::txn &txn,
                              const std::vector &user_ids,
                              const std::string &sync_token)
 {
-        mtx::requests::QueryKeys query;
-        query.token = sync_token;
+    mtx::requests::QueryKeys query;
+    query.token = sync_token;
 
-        for (const auto &user : user_ids) {
-                nhlog::db()->debug("Marking user keys out of date: {}", user);
+    for (const auto &user : user_ids) {
+        nhlog::db()->debug("Marking user keys out of date: {}", user);
 
-                std::string_view oldKeys;
+        std::string_view oldKeys;
 
-                UserKeyCache cacheEntry;
-                auto res = db.get(txn, user, oldKeys);
-                if (res) {
-                        cacheEntry = json::parse(std::string_view(oldKeys.data(), oldKeys.size()))
-                                       .get();
-                }
-                cacheEntry.last_changed = sync_token;
-
-                db.put(txn, user, json(cacheEntry).dump());
-
-                query.device_keys[user] = {};
+        UserKeyCache cacheEntry;
+        auto res = db.get(txn, user, oldKeys);
+        if (res) {
+            cacheEntry =
+              json::parse(std::string_view(oldKeys.data(), oldKeys.size())).get();
         }
+        cacheEntry.last_changed = sync_token;
 
-        if (!query.device_keys.empty())
-                http::client()->query_keys(query,
-                                           [this, sync_token](const mtx::responses::QueryKeys &keys,
-                                                              mtx::http::RequestErr err) {
-                                                   if (err) {
-                                                           nhlog::net()->warn(
-                                                             "failed to query device keys: {} {}",
-                                                             err->matrix_error.error,
-                                                             static_cast(err->status_code));
-                                                           return;
-                                                   }
+        db.put(txn, user, json(cacheEntry).dump());
 
-                                                   emit userKeysUpdate(sync_token, keys);
-                                           });
+        query.device_keys[user] = {};
+    }
+
+    if (!query.device_keys.empty())
+        http::client()->query_keys(
+          query,
+          [this, sync_token](const mtx::responses::QueryKeys &keys, mtx::http::RequestErr err) {
+              if (err) {
+                  nhlog::net()->warn("failed to query device keys: {} {}",
+                                     err->matrix_error.error,
+                                     static_cast(err->status_code));
+                  return;
+              }
+
+              emit userKeysUpdate(sync_token, keys);
+          });
 }
 
 void
 Cache::query_keys(const std::string &user_id,
                   std::function cb)
 {
-        mtx::requests::QueryKeys req;
-        std::string last_changed;
-        {
-                auto txn    = ro_txn(env_);
-                auto cache_ = userKeys_(user_id, txn);
+    mtx::requests::QueryKeys req;
+    std::string last_changed;
+    {
+        auto txn    = ro_txn(env_);
+        auto cache_ = userKeys_(user_id, txn);
 
-                if (cache_.has_value()) {
-                        if (cache_->updated_at == cache_->last_changed) {
-                                cb(cache_.value(), {});
-                                return;
-                        } else
-                                nhlog::db()->info("Keys outdated for {}: {} vs {}",
-                                                  user_id,
-                                                  cache_->updated_at,
-                                                  cache_->last_changed);
-                } else
-                        nhlog::db()->info("No keys found for {}", user_id);
+        if (cache_.has_value()) {
+            if (cache_->updated_at == cache_->last_changed) {
+                cb(cache_.value(), {});
+                return;
+            } else
+                nhlog::db()->info("Keys outdated for {}: {} vs {}",
+                                  user_id,
+                                  cache_->updated_at,
+                                  cache_->last_changed);
+        } else
+            nhlog::db()->info("No keys found for {}", user_id);
 
-                req.device_keys[user_id] = {};
+        req.device_keys[user_id] = {};
 
-                if (cache_)
-                        last_changed = cache_->last_changed;
-                req.token = last_changed;
-        }
+        if (cache_)
+            last_changed = cache_->last_changed;
+        req.token = last_changed;
+    }
 
-        // use context object so that we can disconnect again
-        QObject *context{new QObject(this)};
-        QObject::connect(
-          this,
-          &Cache::verificationStatusChanged,
-          context,
-          [cb, user_id, context_ = context, this](std::string updated_user) mutable {
-                  if (user_id == updated_user) {
-                          context_->deleteLater();
-                          auto txn  = ro_txn(env_);
-                          auto keys = this->userKeys_(user_id, txn);
-                          cb(keys.value_or(UserKeyCache{}), {});
-                  }
-          },
-          Qt::QueuedConnection);
+    // use context object so that we can disconnect again
+    QObject *context{new QObject(this)};
+    QObject::connect(
+      this,
+      &Cache::verificationStatusChanged,
+      context,
+      [cb, user_id, context_ = context, this](std::string updated_user) mutable {
+          if (user_id == updated_user) {
+              context_->deleteLater();
+              auto txn  = ro_txn(env_);
+              auto keys = this->userKeys_(user_id, txn);
+              cb(keys.value_or(UserKeyCache{}), {});
+          }
+      },
+      Qt::QueuedConnection);
 
-        http::client()->query_keys(
-          req,
-          [cb, user_id, last_changed, this](const mtx::responses::QueryKeys &res,
-                                            mtx::http::RequestErr err) {
-                  if (err) {
-                          nhlog::net()->warn("failed to query device keys: {},{}",
-                                             mtx::errors::to_string(err->matrix_error.errcode),
-                                             static_cast(err->status_code));
-                          cb({}, err);
-                          return;
-                  }
+    http::client()->query_keys(
+      req,
+      [cb, user_id, last_changed, this](const mtx::responses::QueryKeys &res,
+                                        mtx::http::RequestErr err) {
+          if (err) {
+              nhlog::net()->warn("failed to query device keys: {},{}",
+                                 mtx::errors::to_string(err->matrix_error.errcode),
+                                 static_cast(err->status_code));
+              cb({}, err);
+              return;
+          }
 
-                  emit userKeysUpdate(last_changed, res);
-          });
+          emit userKeysUpdate(last_changed, res);
+      });
 }
 
 void
 to_json(json &j, const VerificationCache &info)
 {
-        j["device_verified"] = info.device_verified;
-        j["device_blocked"]  = info.device_blocked;
+    j["device_verified"] = info.device_verified;
+    j["device_blocked"]  = info.device_blocked;
 }
 
 void
 from_json(const json &j, VerificationCache &info)
 {
-        info.device_verified = j.at("device_verified").get>();
-        info.device_blocked  = j.at("device_blocked").get>();
+    info.device_verified = j.at("device_verified").get>();
+    info.device_blocked  = j.at("device_blocked").get>();
 }
 
 void
 to_json(json &j, const OnlineBackupVersion &info)
 {
-        j["v"] = info.version;
-        j["a"] = info.algorithm;
+    j["v"] = info.version;
+    j["a"] = info.algorithm;
 }
 
 void
 from_json(const json &j, OnlineBackupVersion &info)
 {
-        info.version   = j.at("v").get();
-        info.algorithm = j.at("a").get();
+    info.version   = j.at("v").get();
+    info.algorithm = j.at("a").get();
 }
 
 std::optional
 Cache::verificationCache(const std::string &user_id, lmdb::txn &txn)
 {
-        std::string_view verifiedVal;
+    std::string_view verifiedVal;
 
-        auto db = getVerificationDb(txn);
+    auto db = getVerificationDb(txn);
 
-        try {
-                VerificationCache verified_state;
-                auto res = db.get(txn, user_id, verifiedVal);
-                if (res) {
-                        verified_state = json::parse(verifiedVal);
-                        return verified_state;
-                } else {
-                        return {};
-                }
-        } catch (std::exception &) {
-                return {};
+    try {
+        VerificationCache verified_state;
+        auto res = db.get(txn, user_id, verifiedVal);
+        if (res) {
+            verified_state = json::parse(verifiedVal);
+            return verified_state;
+        } else {
+            return {};
         }
+    } catch (std::exception &) {
+        return {};
+    }
 }
 
 void
 Cache::markDeviceVerified(const std::string &user_id, const std::string &key)
 {
-        {
-                std::string_view val;
-
-                auto txn = lmdb::txn::begin(env_);
-                auto db  = getVerificationDb(txn);
-
-                try {
-                        VerificationCache verified_state;
-                        auto res = db.get(txn, user_id, val);
-                        if (res) {
-                                verified_state = json::parse(val);
-                        }
-
-                        for (const auto &device : verified_state.device_verified)
-                                if (device == key)
-                                        return;
-
-                        verified_state.device_verified.insert(key);
-                        db.put(txn, user_id, json(verified_state).dump());
-                        txn.commit();
-                } catch (std::exception &) {
-                }
-        }
-
-        const auto local_user = utils::localUser().toStdString();
-        std::map tmp;
-        {
-                std::unique_lock lock(verification_storage.verification_storage_mtx);
-                if (user_id == local_user) {
-                        std::swap(tmp, verification_storage.status);
-                } else {
-                        verification_storage.status.erase(user_id);
-                }
-        }
-        if (user_id == local_user) {
-                for (const auto &[user, status] : tmp) {
-                        (void)status;
-                        emit verificationStatusChanged(user);
-                }
-        } else {
-                emit verificationStatusChanged(user_id);
-        }
-}
-
-void
-Cache::markDeviceUnverified(const std::string &user_id, const std::string &key)
-{
+    {
         std::string_view val;
 
         auto txn = lmdb::txn::begin(env_);
         auto db  = getVerificationDb(txn);
 
         try {
-                VerificationCache verified_state;
-                auto res = db.get(txn, user_id, val);
-                if (res) {
-                        verified_state = json::parse(val);
-                }
+            VerificationCache verified_state;
+            auto res = db.get(txn, user_id, val);
+            if (res) {
+                verified_state = json::parse(val);
+            }
 
-                verified_state.device_verified.erase(key);
+            for (const auto &device : verified_state.device_verified)
+                if (device == key)
+                    return;
 
-                db.put(txn, user_id, json(verified_state).dump());
-                txn.commit();
+            verified_state.device_verified.insert(key);
+            db.put(txn, user_id, json(verified_state).dump());
+            txn.commit();
         } catch (std::exception &) {
         }
+    }
 
-        const auto local_user = utils::localUser().toStdString();
-        std::map tmp;
-        {
-                std::unique_lock lock(verification_storage.verification_storage_mtx);
-                if (user_id == local_user) {
-                        std::swap(tmp, verification_storage.status);
-                } else {
-                        verification_storage.status.erase(user_id);
-                }
-        }
+    const auto local_user = utils::localUser().toStdString();
+    std::map tmp;
+    {
+        std::unique_lock lock(verification_storage.verification_storage_mtx);
         if (user_id == local_user) {
-                for (const auto &[user, status] : tmp) {
-                        (void)status;
-                        emit verificationStatusChanged(user);
-                }
+            std::swap(tmp, verification_storage.status);
         } else {
-                emit verificationStatusChanged(user_id);
+            verification_storage.status.erase(user_id);
         }
+    }
+    if (user_id == local_user) {
+        for (const auto &[user, status] : tmp) {
+            (void)status;
+            emit verificationStatusChanged(user);
+        }
+    } else {
+        emit verificationStatusChanged(user_id);
+    }
+}
+
+void
+Cache::markDeviceUnverified(const std::string &user_id, const std::string &key)
+{
+    std::string_view val;
+
+    auto txn = lmdb::txn::begin(env_);
+    auto db  = getVerificationDb(txn);
+
+    try {
+        VerificationCache verified_state;
+        auto res = db.get(txn, user_id, val);
+        if (res) {
+            verified_state = json::parse(val);
+        }
+
+        verified_state.device_verified.erase(key);
+
+        db.put(txn, user_id, json(verified_state).dump());
+        txn.commit();
+    } catch (std::exception &) {
+    }
+
+    const auto local_user = utils::localUser().toStdString();
+    std::map tmp;
+    {
+        std::unique_lock lock(verification_storage.verification_storage_mtx);
+        if (user_id == local_user) {
+            std::swap(tmp, verification_storage.status);
+        } else {
+            verification_storage.status.erase(user_id);
+        }
+    }
+    if (user_id == local_user) {
+        for (const auto &[user, status] : tmp) {
+            (void)status;
+            emit verificationStatusChanged(user);
+        }
+    } else {
+        emit verificationStatusChanged(user_id);
+    }
 }
 
 VerificationStatus
 Cache::verificationStatus(const std::string &user_id)
 {
-        auto txn = ro_txn(env_);
-        return verificationStatus_(user_id, txn);
+    auto txn = ro_txn(env_);
+    return verificationStatus_(user_id, txn);
 }
 
 VerificationStatus
 Cache::verificationStatus_(const std::string &user_id, lmdb::txn &txn)
 {
-        std::unique_lock lock(verification_storage.verification_storage_mtx);
-        if (verification_storage.status.count(user_id))
-                return verification_storage.status.at(user_id);
+    std::unique_lock lock(verification_storage.verification_storage_mtx);
+    if (verification_storage.status.count(user_id))
+        return verification_storage.status.at(user_id);
 
-        VerificationStatus status;
+    VerificationStatus status;
 
-        // assume there is at least one unverified device until we have checked we have the device
-        // list for that user.
-        status.unverified_device_count = 1;
-        status.no_keys                 = true;
+    // assume there is at least one unverified device until we have checked we have the device
+    // list for that user.
+    status.unverified_device_count = 1;
+    status.no_keys                 = true;
 
-        if (auto verifCache = verificationCache(user_id, txn)) {
-                status.verified_devices = verifCache->device_verified;
+    if (auto verifCache = verificationCache(user_id, txn)) {
+        status.verified_devices = verifCache->device_verified;
+    }
+
+    const auto local_user = utils::localUser().toStdString();
+
+    crypto::Trust trustlevel = crypto::Trust::Unverified;
+    if (user_id == local_user) {
+        status.verified_devices.insert(http::client()->device_id());
+        trustlevel = crypto::Trust::Verified;
+    }
+
+    auto verifyAtLeastOneSig = [](const auto &toVerif,
+                                  const std::map &keys,
+                                  const std::string &keyOwner) {
+        if (!toVerif.signatures.count(keyOwner))
+            return false;
+
+        for (const auto &[key_id, signature] : toVerif.signatures.at(keyOwner)) {
+            if (!keys.count(key_id))
+                continue;
+
+            if (mtx::crypto::ed25519_verify_signature(keys.at(key_id), json(toVerif), signature))
+                return true;
+        }
+        return false;
+    };
+
+    auto updateUnverifiedDevices = [&status](auto &theirDeviceKeys) {
+        int currentVerifiedDevices = 0;
+        for (auto device_id : status.verified_devices) {
+            if (theirDeviceKeys.count(device_id))
+                currentVerifiedDevices++;
+        }
+        status.unverified_device_count =
+          static_cast(theirDeviceKeys.size()) - currentVerifiedDevices;
+    };
+
+    try {
+        // for local user verify this device_key -> our master_key -> our self_signing_key
+        // -> our device_keys
+        //
+        // for other user verify this device_key -> our master_key -> our user_signing_key
+        // -> their master_key -> their self_signing_key -> their device_keys
+        //
+        // This means verifying the other user adds 2 extra steps,verifying our user_signing
+        // key and their master key
+        auto ourKeys   = userKeys_(local_user, txn);
+        auto theirKeys = userKeys_(user_id, txn);
+        if (theirKeys)
+            status.no_keys = false;
+
+        if (!ourKeys || !theirKeys) {
+            verification_storage.status[user_id] = status;
+            return status;
         }
 
-        const auto local_user = utils::localUser().toStdString();
+        // Update verified devices count to count without cross-signing
+        updateUnverifiedDevices(theirKeys->device_keys);
 
-        crypto::Trust trustlevel = crypto::Trust::Unverified;
-        if (user_id == local_user) {
-                status.verified_devices.insert(http::client()->device_id());
+        {
+            auto &mk           = ourKeys->master_keys;
+            std::string dev_id = "ed25519:" + http::client()->device_id();
+            if (!mk.signatures.count(local_user) || !mk.signatures.at(local_user).count(dev_id) ||
+                !mtx::crypto::ed25519_verify_signature(olm::client()->identity_keys().ed25519,
+                                                       json(mk),
+                                                       mk.signatures.at(local_user).at(dev_id))) {
+                nhlog::crypto()->debug("We have not verified our own master key");
+                verification_storage.status[user_id] = status;
+                return status;
+            }
+        }
+
+        auto master_keys = ourKeys->master_keys.keys;
+
+        if (user_id != local_user) {
+            bool theirMasterKeyVerified =
+              verifyAtLeastOneSig(ourKeys->user_signing_keys, master_keys, local_user) &&
+              verifyAtLeastOneSig(
+                theirKeys->master_keys, ourKeys->user_signing_keys.keys, local_user);
+
+            if (theirMasterKeyVerified)
                 trustlevel = crypto::Trust::Verified;
-        }
-
-        auto verifyAtLeastOneSig = [](const auto &toVerif,
-                                      const std::map &keys,
-                                      const std::string &keyOwner) {
-                if (!toVerif.signatures.count(keyOwner))
-                        return false;
-
-                for (const auto &[key_id, signature] : toVerif.signatures.at(keyOwner)) {
-                        if (!keys.count(key_id))
-                                continue;
-
-                        if (mtx::crypto::ed25519_verify_signature(
-                              keys.at(key_id), json(toVerif), signature))
-                                return true;
-                }
-                return false;
-        };
-
-        auto updateUnverifiedDevices = [&status](auto &theirDeviceKeys) {
-                int currentVerifiedDevices = 0;
-                for (auto device_id : status.verified_devices) {
-                        if (theirDeviceKeys.count(device_id))
-                                currentVerifiedDevices++;
-                }
-                status.unverified_device_count =
-                  static_cast(theirDeviceKeys.size()) - currentVerifiedDevices;
-        };
-
-        try {
-                // for local user verify this device_key -> our master_key -> our self_signing_key
-                // -> our device_keys
-                //
-                // for other user verify this device_key -> our master_key -> our user_signing_key
-                // -> their master_key -> their self_signing_key -> their device_keys
-                //
-                // This means verifying the other user adds 2 extra steps,verifying our user_signing
-                // key and their master key
-                auto ourKeys   = userKeys_(local_user, txn);
-                auto theirKeys = userKeys_(user_id, txn);
-                if (theirKeys)
-                        status.no_keys = false;
-
-                if (!ourKeys || !theirKeys) {
-                        verification_storage.status[user_id] = status;
-                        return status;
-                }
-
-                // Update verified devices count to count without cross-signing
-                updateUnverifiedDevices(theirKeys->device_keys);
-
-                {
-                        auto &mk           = ourKeys->master_keys;
-                        std::string dev_id = "ed25519:" + http::client()->device_id();
-                        if (!mk.signatures.count(local_user) ||
-                            !mk.signatures.at(local_user).count(dev_id) ||
-                            !mtx::crypto::ed25519_verify_signature(
-                              olm::client()->identity_keys().ed25519,
-                              json(mk),
-                              mk.signatures.at(local_user).at(dev_id))) {
-                                nhlog::crypto()->debug("We have not verified our own master key");
-                                verification_storage.status[user_id] = status;
-                                return status;
-                        }
-                }
-
-                auto master_keys = ourKeys->master_keys.keys;
-
-                if (user_id != local_user) {
-                        bool theirMasterKeyVerified =
-                          verifyAtLeastOneSig(
-                            ourKeys->user_signing_keys, master_keys, local_user) &&
-                          verifyAtLeastOneSig(
-                            theirKeys->master_keys, ourKeys->user_signing_keys.keys, local_user);
-
-                        if (theirMasterKeyVerified)
-                                trustlevel = crypto::Trust::Verified;
-                        else if (!theirKeys->master_key_changed)
-                                trustlevel = crypto::Trust::TOFU;
-                        else {
-                                verification_storage.status[user_id] = status;
-                                return status;
-                        }
-
-                        master_keys = theirKeys->master_keys.keys;
-                }
-
-                status.user_verified = trustlevel;
-
-                verification_storage.status[user_id] = status;
-                if (!verifyAtLeastOneSig(theirKeys->self_signing_keys, master_keys, user_id))
-                        return status;
-
-                for (const auto &[device, device_key] : theirKeys->device_keys) {
-                        (void)device;
-                        try {
-                                auto identkey =
-                                  device_key.keys.at("curve25519:" + device_key.device_id);
-                                if (verifyAtLeastOneSig(
-                                      device_key, theirKeys->self_signing_keys.keys, user_id)) {
-                                        status.verified_devices.insert(device_key.device_id);
-                                        status.verified_device_keys[identkey] = trustlevel;
-                                }
-                        } catch (...) {
-                        }
-                }
-
-                updateUnverifiedDevices(theirKeys->device_keys);
+            else if (!theirKeys->master_key_changed)
+                trustlevel = crypto::Trust::TOFU;
+            else {
                 verification_storage.status[user_id] = status;
                 return status;
-        } catch (std::exception &e) {
-                nhlog::db()->error(
-                  "Failed to calculate verification status of {}: {}", user_id, e.what());
-                return status;
+            }
+
+            master_keys = theirKeys->master_keys.keys;
         }
+
+        status.user_verified = trustlevel;
+
+        verification_storage.status[user_id] = status;
+        if (!verifyAtLeastOneSig(theirKeys->self_signing_keys, master_keys, user_id))
+            return status;
+
+        for (const auto &[device, device_key] : theirKeys->device_keys) {
+            (void)device;
+            try {
+                auto identkey = device_key.keys.at("curve25519:" + device_key.device_id);
+                if (verifyAtLeastOneSig(device_key, theirKeys->self_signing_keys.keys, user_id)) {
+                    status.verified_devices.insert(device_key.device_id);
+                    status.verified_device_keys[identkey] = trustlevel;
+                }
+            } catch (...) {
+            }
+        }
+
+        updateUnverifiedDevices(theirKeys->device_keys);
+        verification_storage.status[user_id] = status;
+        return status;
+    } catch (std::exception &e) {
+        nhlog::db()->error("Failed to calculate verification status of {}: {}", user_id, e.what());
+        return status;
+    }
 }
 
 void
 to_json(json &j, const RoomInfo &info)
 {
-        j["name"]         = info.name;
-        j["topic"]        = info.topic;
-        j["avatar_url"]   = info.avatar_url;
-        j["version"]      = info.version;
-        j["is_invite"]    = info.is_invite;
-        j["is_space"]     = info.is_space;
-        j["join_rule"]    = info.join_rule;
-        j["guest_access"] = info.guest_access;
+    j["name"]         = info.name;
+    j["topic"]        = info.topic;
+    j["avatar_url"]   = info.avatar_url;
+    j["version"]      = info.version;
+    j["is_invite"]    = info.is_invite;
+    j["is_space"]     = info.is_space;
+    j["join_rule"]    = info.join_rule;
+    j["guest_access"] = info.guest_access;
 
-        if (info.member_count != 0)
-                j["member_count"] = info.member_count;
+    if (info.member_count != 0)
+        j["member_count"] = info.member_count;
 
-        if (info.tags.size() != 0)
-                j["tags"] = info.tags;
+    if (info.tags.size() != 0)
+        j["tags"] = info.tags;
 }
 
 void
 from_json(const json &j, RoomInfo &info)
 {
-        info.name       = j.at("name");
-        info.topic      = j.at("topic");
-        info.avatar_url = j.at("avatar_url");
-        info.version    = j.value(
-          "version", QCoreApplication::translate("RoomInfo", "no version stored").toStdString());
-        info.is_invite    = j.at("is_invite");
-        info.is_space     = j.value("is_space", false);
-        info.join_rule    = j.at("join_rule");
-        info.guest_access = j.at("guest_access");
+    info.name       = j.at("name");
+    info.topic      = j.at("topic");
+    info.avatar_url = j.at("avatar_url");
+    info.version    = j.value(
+      "version", QCoreApplication::translate("RoomInfo", "no version stored").toStdString());
+    info.is_invite    = j.at("is_invite");
+    info.is_space     = j.value("is_space", false);
+    info.join_rule    = j.at("join_rule");
+    info.guest_access = j.at("guest_access");
 
-        if (j.count("member_count"))
-                info.member_count = j.at("member_count");
+    if (j.count("member_count"))
+        info.member_count = j.at("member_count");
 
-        if (j.count("tags"))
-                info.tags = j.at("tags").get>();
+    if (j.count("tags"))
+        info.tags = j.at("tags").get>();
 }
 
 void
 to_json(json &j, const ReadReceiptKey &key)
 {
-        j = json{{"event_id", key.event_id}, {"room_id", key.room_id}};
+    j = json{{"event_id", key.event_id}, {"room_id", key.room_id}};
 }
 
 void
 from_json(const json &j, ReadReceiptKey &key)
 {
-        key.event_id = j.at("event_id").get();
-        key.room_id  = j.at("room_id").get();
+    key.event_id = j.at("event_id").get();
+    key.room_id  = j.at("room_id").get();
 }
 
 void
 to_json(json &j, const MemberInfo &info)
 {
-        j["name"]       = info.name;
-        j["avatar_url"] = info.avatar_url;
+    j["name"]       = info.name;
+    j["avatar_url"] = info.avatar_url;
 }
 
 void
 from_json(const json &j, MemberInfo &info)
 {
-        info.name       = j.at("name");
-        info.avatar_url = j.at("avatar_url");
+    info.name       = j.at("name");
+    info.avatar_url = j.at("avatar_url");
 }
 
 void
 to_json(nlohmann::json &obj, const DeviceKeysToMsgIndex &msg)
 {
-        obj["deviceids"] = msg.deviceids;
+    obj["deviceids"] = msg.deviceids;
 }
 
 void
 from_json(const nlohmann::json &obj, DeviceKeysToMsgIndex &msg)
 {
-        msg.deviceids = obj.at("deviceids").get();
+    msg.deviceids = obj.at("deviceids").get();
 }
 
 void
 to_json(nlohmann::json &obj, const SharedWithUsers &msg)
 {
-        obj["keys"] = msg.keys;
+    obj["keys"] = msg.keys;
 }
 
 void
 from_json(const nlohmann::json &obj, SharedWithUsers &msg)
 {
-        msg.keys = obj.at("keys").get>();
+    msg.keys = obj.at("keys").get>();
 }
 
 void
 to_json(nlohmann::json &obj, const GroupSessionData &msg)
 {
-        obj["message_index"] = msg.message_index;
-        obj["ts"]            = msg.timestamp;
-        obj["trust"]         = msg.trusted;
+    obj["message_index"] = msg.message_index;
+    obj["ts"]            = msg.timestamp;
+    obj["trust"]         = msg.trusted;
 
-        obj["sender_claimed_ed25519_key"]      = msg.sender_claimed_ed25519_key;
-        obj["forwarding_curve25519_key_chain"] = msg.forwarding_curve25519_key_chain;
+    obj["sender_claimed_ed25519_key"]      = msg.sender_claimed_ed25519_key;
+    obj["forwarding_curve25519_key_chain"] = msg.forwarding_curve25519_key_chain;
 
-        obj["currently"] = msg.currently;
+    obj["currently"] = msg.currently;
 
-        obj["indices"] = msg.indices;
+    obj["indices"] = msg.indices;
 }
 
 void
 from_json(const nlohmann::json &obj, GroupSessionData &msg)
 {
-        msg.message_index = obj.at("message_index");
-        msg.timestamp     = obj.value("ts", 0ULL);
-        msg.trusted       = obj.value("trust", true);
+    msg.message_index = obj.at("message_index");
+    msg.timestamp     = obj.value("ts", 0ULL);
+    msg.trusted       = obj.value("trust", true);
 
-        msg.sender_claimed_ed25519_key = obj.value("sender_claimed_ed25519_key", "");
-        msg.forwarding_curve25519_key_chain =
-          obj.value("forwarding_curve25519_key_chain", std::vector{});
+    msg.sender_claimed_ed25519_key = obj.value("sender_claimed_ed25519_key", "");
+    msg.forwarding_curve25519_key_chain =
+      obj.value("forwarding_curve25519_key_chain", std::vector{});
 
-        msg.currently = obj.value("currently", SharedWithUsers{});
+    msg.currently = obj.value("currently", SharedWithUsers{});
 
-        msg.indices = obj.value("indices", std::map());
+    msg.indices = obj.value("indices", std::map());
 }
 
 void
 to_json(nlohmann::json &obj, const DevicePublicKeys &msg)
 {
-        obj["ed25519"]    = msg.ed25519;
-        obj["curve25519"] = msg.curve25519;
+    obj["ed25519"]    = msg.ed25519;
+    obj["curve25519"] = msg.curve25519;
 }
 
 void
 from_json(const nlohmann::json &obj, DevicePublicKeys &msg)
 {
-        msg.ed25519    = obj.at("ed25519");
-        msg.curve25519 = obj.at("curve25519");
+    msg.ed25519    = obj.at("ed25519");
+    msg.curve25519 = obj.at("curve25519");
 }
 
 void
 to_json(nlohmann::json &obj, const MegolmSessionIndex &msg)
 {
-        obj["room_id"]    = msg.room_id;
-        obj["session_id"] = msg.session_id;
-        obj["sender_key"] = msg.sender_key;
+    obj["room_id"]    = msg.room_id;
+    obj["session_id"] = msg.session_id;
+    obj["sender_key"] = msg.sender_key;
 }
 
 void
 from_json(const nlohmann::json &obj, MegolmSessionIndex &msg)
 {
-        msg.room_id    = obj.at("room_id");
-        msg.session_id = obj.at("session_id");
-        msg.sender_key = obj.at("sender_key");
+    msg.room_id    = obj.at("room_id");
+    msg.session_id = obj.at("session_id");
+    msg.sender_key = obj.at("sender_key");
 }
 
 void
 to_json(nlohmann::json &obj, const StoredOlmSession &msg)
 {
-        obj["ts"] = msg.last_message_ts;
-        obj["s"]  = msg.pickled_session;
+    obj["ts"] = msg.last_message_ts;
+    obj["s"]  = msg.pickled_session;
 }
 void
 from_json(const nlohmann::json &obj, StoredOlmSession &msg)
 {
-        msg.last_message_ts = obj.at("ts").get();
-        msg.pickled_session = obj.at("s").get();
+    msg.last_message_ts = obj.at("ts").get();
+    msg.pickled_session = obj.at("s").get();
 }
 
 namespace cache {
 void
 init(const QString &user_id)
 {
-        qRegisterMetaType();
-        qRegisterMetaType();
-        qRegisterMetaType();
-        qRegisterMetaType>();
-        qRegisterMetaType>();
-        qRegisterMetaType>();
-        qRegisterMetaType();
+    qRegisterMetaType();
+    qRegisterMetaType();
+    qRegisterMetaType();
+    qRegisterMetaType>();
+    qRegisterMetaType>();
+    qRegisterMetaType>();
+    qRegisterMetaType();
 
-        instance_ = std::make_unique(user_id);
+    instance_ = std::make_unique(user_id);
 }
 
 Cache *
 client()
 {
-        return instance_.get();
+    return instance_.get();
 }
 
 std::string
 displayName(const std::string &room_id, const std::string &user_id)
 {
-        return instance_->displayName(room_id, user_id);
+    return instance_->displayName(room_id, user_id);
 }
 
 QString
 displayName(const QString &room_id, const QString &user_id)
 {
-        return instance_->displayName(room_id, user_id);
+    return instance_->displayName(room_id, user_id);
 }
 QString
 avatarUrl(const QString &room_id, const QString &user_id)
 {
-        return instance_->avatarUrl(room_id, user_id);
+    return instance_->avatarUrl(room_id, user_id);
 }
 
 mtx::presence::PresenceState
 presenceState(const std::string &user_id)
 {
-        if (!instance_)
-                return {};
-        return instance_->presenceState(user_id);
+    if (!instance_)
+        return {};
+    return instance_->presenceState(user_id);
 }
 std::string
 statusMessage(const std::string &user_id)
 {
-        return instance_->statusMessage(user_id);
+    return instance_->statusMessage(user_id);
 }
 
 // user cache stores user keys
 std::optional
 userKeys(const std::string &user_id)
 {
-        return instance_->userKeys(user_id);
+    return instance_->userKeys(user_id);
 }
 void
 updateUserKeys(const std::string &sync_token, const mtx::responses::QueryKeys &keyQuery)
 {
-        instance_->updateUserKeys(sync_token, keyQuery);
+    instance_->updateUserKeys(sync_token, keyQuery);
 }
 
 // device & user verification cache
 std::optional
 verificationStatus(const std::string &user_id)
 {
-        return instance_->verificationStatus(user_id);
+    return instance_->verificationStatus(user_id);
 }
 
 void
 markDeviceVerified(const std::string &user_id, const std::string &device)
 {
-        instance_->markDeviceVerified(user_id, device);
+    instance_->markDeviceVerified(user_id, device);
 }
 
 void
 markDeviceUnverified(const std::string &user_id, const std::string &device)
 {
-        instance_->markDeviceUnverified(user_id, device);
+    instance_->markDeviceUnverified(user_id, device);
 }
 
 std::vector
 joinedRooms()
 {
-        return instance_->joinedRooms();
+    return instance_->joinedRooms();
 }
 
 QMap
 roomInfo(bool withInvites)
 {
-        return instance_->roomInfo(withInvites);
+    return instance_->roomInfo(withInvites);
 }
 QHash
 invites()
 {
-        return instance_->invites();
+    return instance_->invites();
 }
 
 QString
 getRoomName(lmdb::txn &txn, lmdb::dbi &statesdb, lmdb::dbi &membersdb)
 {
-        return instance_->getRoomName(txn, statesdb, membersdb);
+    return instance_->getRoomName(txn, statesdb, membersdb);
 }
 mtx::events::state::JoinRule
 getRoomJoinRule(lmdb::txn &txn, lmdb::dbi &statesdb)
 {
-        return instance_->getRoomJoinRule(txn, statesdb);
+    return instance_->getRoomJoinRule(txn, statesdb);
 }
 bool
 getRoomGuestAccess(lmdb::txn &txn, lmdb::dbi &statesdb)
 {
-        return instance_->getRoomGuestAccess(txn, statesdb);
+    return instance_->getRoomGuestAccess(txn, statesdb);
 }
 QString
 getRoomTopic(lmdb::txn &txn, lmdb::dbi &statesdb)
 {
-        return instance_->getRoomTopic(txn, statesdb);
+    return instance_->getRoomTopic(txn, statesdb);
 }
 QString
 getRoomAvatarUrl(lmdb::txn &txn, lmdb::dbi &statesdb, lmdb::dbi &membersdb)
 {
-        return instance_->getRoomAvatarUrl(txn, statesdb, membersdb);
+    return instance_->getRoomAvatarUrl(txn, statesdb, membersdb);
 }
 
 std::vector
 getMembers(const std::string &room_id, std::size_t startIndex, std::size_t len)
 {
-        return instance_->getMembers(room_id, startIndex, len);
+    return instance_->getMembers(room_id, startIndex, len);
 }
 
 std::vector
 getMembersFromInvite(const std::string &room_id, std::size_t startIndex, std::size_t len)
 {
-        return instance_->getMembersFromInvite(room_id, startIndex, len);
+    return instance_->getMembersFromInvite(room_id, startIndex, len);
 }
 
 void
 saveState(const mtx::responses::Sync &res)
 {
-        instance_->saveState(res);
+    instance_->saveState(res);
 }
 bool
 isInitialized()
 {
-        return instance_->isInitialized();
+    return instance_->isInitialized();
 }
 
 std::string
 nextBatchToken()
 {
-        return instance_->nextBatchToken();
+    return instance_->nextBatchToken();
 }
 
 void
 deleteData()
 {
-        instance_->deleteData();
+    instance_->deleteData();
 }
 
 void
 removeInvite(lmdb::txn &txn, const std::string &room_id)
 {
-        instance_->removeInvite(txn, room_id);
+    instance_->removeInvite(txn, room_id);
 }
 void
 removeInvite(const std::string &room_id)
 {
-        instance_->removeInvite(room_id);
+    instance_->removeInvite(room_id);
 }
 void
 removeRoom(lmdb::txn &txn, const std::string &roomid)
 {
-        instance_->removeRoom(txn, roomid);
+    instance_->removeRoom(txn, roomid);
 }
 void
 removeRoom(const std::string &roomid)
 {
-        instance_->removeRoom(roomid);
+    instance_->removeRoom(roomid);
 }
 void
 removeRoom(const QString &roomid)
 {
-        instance_->removeRoom(roomid.toStdString());
+    instance_->removeRoom(roomid.toStdString());
 }
 void
 setup()
 {
-        instance_->setup();
+    instance_->setup();
 }
 
 bool
 runMigrations()
 {
-        return instance_->runMigrations();
+    return instance_->runMigrations();
 }
 
 cache::CacheVersion
 formatVersion()
 {
-        return instance_->formatVersion();
+    return instance_->formatVersion();
 }
 
 void
 setCurrentFormat()
 {
-        instance_->setCurrentFormat();
+    instance_->setCurrentFormat();
 }
 
 std::vector
 roomIds()
 {
-        return instance_->roomIds();
+    return instance_->roomIds();
 }
 
 QMap
 getTimelineMentions()
 {
-        return instance_->getTimelineMentions();
+    return instance_->getTimelineMentions();
 }
 
 //! Retrieve all the user ids from a room.
 std::vector
 roomMembers(const std::string &room_id)
 {
-        return instance_->roomMembers(room_id);
+    return instance_->roomMembers(room_id);
 }
 
 //! Check if the given user has power leve greater than than
@@ -4952,48 +4826,48 @@ hasEnoughPowerLevel(const std::vector &eventTypes,
                     const std::string &room_id,
                     const std::string &user_id)
 {
-        return instance_->hasEnoughPowerLevel(eventTypes, room_id, user_id);
+    return instance_->hasEnoughPowerLevel(eventTypes, room_id, user_id);
 }
 
 void
 updateReadReceipt(lmdb::txn &txn, const std::string &room_id, const Receipts &receipts)
 {
-        instance_->updateReadReceipt(txn, room_id, receipts);
+    instance_->updateReadReceipt(txn, room_id, receipts);
 }
 
 UserReceipts
 readReceipts(const QString &event_id, const QString &room_id)
 {
-        return instance_->readReceipts(event_id, room_id);
+    return instance_->readReceipts(event_id, room_id);
 }
 
 std::optional
 getEventIndex(const std::string &room_id, std::string_view event_id)
 {
-        return instance_->getEventIndex(room_id, event_id);
+    return instance_->getEventIndex(room_id, event_id);
 }
 
 std::optional>
 lastInvisibleEventAfter(const std::string &room_id, std::string_view event_id)
 {
-        return instance_->lastInvisibleEventAfter(room_id, event_id);
+    return instance_->lastInvisibleEventAfter(room_id, event_id);
 }
 
 RoomInfo
 singleRoomInfo(const std::string &room_id)
 {
-        return instance_->singleRoomInfo(room_id);
+    return instance_->singleRoomInfo(room_id);
 }
 std::vector
 roomsWithStateUpdates(const mtx::responses::Sync &res)
 {
-        return instance_->roomsWithStateUpdates(res);
+    return instance_->roomsWithStateUpdates(res);
 }
 
 std::map
 getRoomInfo(const std::vector &rooms)
 {
-        return instance_->getRoomInfo(rooms);
+    return instance_->getRoomInfo(rooms);
 }
 
 //! Calculates which the read status of a room.
@@ -5001,74 +4875,74 @@ getRoomInfo(const std::vector &rooms)
 bool
 calculateRoomReadStatus(const std::string &room_id)
 {
-        return instance_->calculateRoomReadStatus(room_id);
+    return instance_->calculateRoomReadStatus(room_id);
 }
 void
 calculateRoomReadStatus()
 {
-        instance_->calculateRoomReadStatus();
+    instance_->calculateRoomReadStatus();
 }
 
 void
 markSentNotification(const std::string &event_id)
 {
-        instance_->markSentNotification(event_id);
+    instance_->markSentNotification(event_id);
 }
 //! Removes an event from the sent notifications.
 void
 removeReadNotification(const std::string &event_id)
 {
-        instance_->removeReadNotification(event_id);
+    instance_->removeReadNotification(event_id);
 }
 //! Check if we have sent a desktop notification for the given event id.
 bool
 isNotificationSent(const std::string &event_id)
 {
-        return instance_->isNotificationSent(event_id);
+    return instance_->isNotificationSent(event_id);
 }
 
 //! Add all notifications containing a user mention to the db.
 void
 saveTimelineMentions(const mtx::responses::Notifications &res)
 {
-        instance_->saveTimelineMentions(res);
+    instance_->saveTimelineMentions(res);
 }
 
 //! Remove old unused data.
 void
 deleteOldMessages()
 {
-        instance_->deleteOldMessages();
+    instance_->deleteOldMessages();
 }
 void
 deleteOldData() noexcept
 {
-        instance_->deleteOldData();
+    instance_->deleteOldData();
 }
 //! Retrieve all saved room ids.
 std::vector
 getRoomIds(lmdb::txn &txn)
 {
-        return instance_->getRoomIds(txn);
+    return instance_->getRoomIds(txn);
 }
 
 //! Mark a room that uses e2e encryption.
 void
 setEncryptedRoom(lmdb::txn &txn, const std::string &room_id)
 {
-        instance_->setEncryptedRoom(txn, room_id);
+    instance_->setEncryptedRoom(txn, room_id);
 }
 bool
 isRoomEncrypted(const std::string &room_id)
 {
-        return instance_->isRoomEncrypted(room_id);
+    return instance_->isRoomEncrypted(room_id);
 }
 
 //! Check if a user is a member of the room.
 bool
 isRoomMember(const std::string &user_id, const std::string &room_id)
 {
-        return instance_->isRoomMember(user_id, room_id);
+    return instance_->isRoomMember(user_id, room_id);
 }
 
 //
@@ -5079,40 +4953,40 @@ saveOutboundMegolmSession(const std::string &room_id,
                           const GroupSessionData &data,
                           mtx::crypto::OutboundGroupSessionPtr &session)
 {
-        instance_->saveOutboundMegolmSession(room_id, data, session);
+    instance_->saveOutboundMegolmSession(room_id, data, session);
 }
 OutboundGroupSessionDataRef
 getOutboundMegolmSession(const std::string &room_id)
 {
-        return instance_->getOutboundMegolmSession(room_id);
+    return instance_->getOutboundMegolmSession(room_id);
 }
 bool
 outboundMegolmSessionExists(const std::string &room_id) noexcept
 {
-        return instance_->outboundMegolmSessionExists(room_id);
+    return instance_->outboundMegolmSessionExists(room_id);
 }
 void
 updateOutboundMegolmSession(const std::string &room_id,
                             const GroupSessionData &data,
                             mtx::crypto::OutboundGroupSessionPtr &session)
 {
-        instance_->updateOutboundMegolmSession(room_id, data, session);
+    instance_->updateOutboundMegolmSession(room_id, data, session);
 }
 void
 dropOutboundMegolmSession(const std::string &room_id)
 {
-        instance_->dropOutboundMegolmSession(room_id);
+    instance_->dropOutboundMegolmSession(room_id);
 }
 
 void
 importSessionKeys(const mtx::crypto::ExportedSessionKeys &keys)
 {
-        instance_->importSessionKeys(keys);
+    instance_->importSessionKeys(keys);
 }
 mtx::crypto::ExportedSessionKeys
 exportSessionKeys()
 {
-        return instance_->exportSessionKeys();
+    return instance_->exportSessionKeys();
 }
 
 //
@@ -5123,22 +4997,22 @@ saveInboundMegolmSession(const MegolmSessionIndex &index,
                          mtx::crypto::InboundGroupSessionPtr session,
                          const GroupSessionData &data)
 {
-        instance_->saveInboundMegolmSession(index, std::move(session), data);
+    instance_->saveInboundMegolmSession(index, std::move(session), data);
 }
 mtx::crypto::InboundGroupSessionPtr
 getInboundMegolmSession(const MegolmSessionIndex &index)
 {
-        return instance_->getInboundMegolmSession(index);
+    return instance_->getInboundMegolmSession(index);
 }
 bool
 inboundMegolmSessionExists(const MegolmSessionIndex &index)
 {
-        return instance_->inboundMegolmSessionExists(index);
+    return instance_->inboundMegolmSessionExists(index);
 }
 std::optional
 getMegolmSessionData(const MegolmSessionIndex &index)
 {
-        return instance_->getMegolmSessionData(index);
+    return instance_->getMegolmSessionData(index);
 }
 
 //
@@ -5149,43 +5023,43 @@ saveOlmSession(const std::string &curve25519,
                mtx::crypto::OlmSessionPtr session,
                uint64_t timestamp)
 {
-        instance_->saveOlmSession(curve25519, std::move(session), timestamp);
+    instance_->saveOlmSession(curve25519, std::move(session), timestamp);
 }
 std::vector
 getOlmSessions(const std::string &curve25519)
 {
-        return instance_->getOlmSessions(curve25519);
+    return instance_->getOlmSessions(curve25519);
 }
 std::optional
 getOlmSession(const std::string &curve25519, const std::string &session_id)
 {
-        return instance_->getOlmSession(curve25519, session_id);
+    return instance_->getOlmSession(curve25519, session_id);
 }
 std::optional
 getLatestOlmSession(const std::string &curve25519)
 {
-        return instance_->getLatestOlmSession(curve25519);
+    return instance_->getLatestOlmSession(curve25519);
 }
 
 void
 saveOlmAccount(const std::string &pickled)
 {
-        instance_->saveOlmAccount(pickled);
+    instance_->saveOlmAccount(pickled);
 }
 std::string
 restoreOlmAccount()
 {
-        return instance_->restoreOlmAccount();
+    return instance_->restoreOlmAccount();
 }
 
 void
 storeSecret(const std::string &name, const std::string &secret)
 {
-        instance_->storeSecret(name, secret);
+    instance_->storeSecret(name, secret);
 }
 std::optional
 secret(const std::string &name)
 {
-        return instance_->secret(name);
+    return instance_->secret(name);
 }
 } // namespace cache
diff --git a/src/CacheCryptoStructs.h b/src/CacheCryptoStructs.h
index 80dd1046..b7461848 100644
--- a/src/CacheCryptoStructs.h
+++ b/src/CacheCryptoStructs.h
@@ -19,48 +19,48 @@ Q_NAMESPACE
 //! How much a participant is trusted.
 enum Trust
 {
-        Unverified, //! Device unverified or master key changed.
-        TOFU,       //! Device is signed by the sender, but the user is not verified, but they never
-                    //! changed the master key.
-        Verified,   //! User was verified and has crosssigned this device or device is verified.
+    Unverified, //! Device unverified or master key changed.
+    TOFU,       //! Device is signed by the sender, but the user is not verified, but they never
+                //! changed the master key.
+    Verified,   //! User was verified and has crosssigned this device or device is verified.
 };
 Q_ENUM_NS(Trust)
 }
 
 struct DeviceKeysToMsgIndex
 {
-        // map from device key to message_index
-        // Using the device id is safe because we check for reuse on device list updates
-        // Using the device id makes our logic much easier to read.
-        std::map deviceids;
+    // map from device key to message_index
+    // Using the device id is safe because we check for reuse on device list updates
+    // Using the device id makes our logic much easier to read.
+    std::map deviceids;
 };
 
 struct SharedWithUsers
 {
-        // userid to keys
-        std::map keys;
+    // userid to keys
+    std::map keys;
 };
 
 // Extra information associated with an outbound megolm session.
 struct GroupSessionData
 {
-        uint64_t message_index = 0;
-        uint64_t timestamp     = 0;
+    uint64_t message_index = 0;
+    uint64_t timestamp     = 0;
 
-        // If we got the session via key sharing or forwarding, we can usually trust it.
-        // If it came from asymmetric key backup, it is not trusted.
-        // TODO(Nico): What about forwards? They might come from key backup?
-        bool trusted = true;
+    // If we got the session via key sharing or forwarding, we can usually trust it.
+    // If it came from asymmetric key backup, it is not trusted.
+    // TODO(Nico): What about forwards? They might come from key backup?
+    bool trusted = true;
 
-        std::string sender_claimed_ed25519_key;
-        std::vector forwarding_curve25519_key_chain;
+    std::string sender_claimed_ed25519_key;
+    std::vector forwarding_curve25519_key_chain;
 
-        //! map from index to event_id to check for replay attacks
-        std::map indices;
+    //! map from index to event_id to check for replay attacks
+    std::map indices;
 
-        // who has access to this session.
-        // Rotate, when a user leaves the room and share, when a user gets added.
-        SharedWithUsers currently;
+    // who has access to this session.
+    // Rotate, when a user leaves the room and share, when a user gets added.
+    SharedWithUsers currently;
 };
 
 void
@@ -70,14 +70,14 @@ from_json(const nlohmann::json &obj, GroupSessionData &msg);
 
 struct OutboundGroupSessionDataRef
 {
-        mtx::crypto::OutboundGroupSessionPtr session;
-        GroupSessionData data;
+    mtx::crypto::OutboundGroupSessionPtr session;
+    GroupSessionData data;
 };
 
 struct DevicePublicKeys
 {
-        std::string ed25519;
-        std::string curve25519;
+    std::string ed25519;
+    std::string curve25519;
 };
 
 void
@@ -88,19 +88,19 @@ from_json(const nlohmann::json &obj, DevicePublicKeys &msg);
 //! Represents a unique megolm session identifier.
 struct MegolmSessionIndex
 {
-        MegolmSessionIndex() = default;
-        MegolmSessionIndex(std::string room_id_, const mtx::events::msg::Encrypted &e)
-          : room_id(std::move(room_id_))
-          , session_id(e.session_id)
-          , sender_key(e.sender_key)
-        {}
+    MegolmSessionIndex() = default;
+    MegolmSessionIndex(std::string room_id_, const mtx::events::msg::Encrypted &e)
+      : room_id(std::move(room_id_))
+      , session_id(e.session_id)
+      , sender_key(e.sender_key)
+    {}
 
-        //! The room in which this session exists.
-        std::string room_id;
-        //! The session_id of the megolm session.
-        std::string session_id;
-        //! The curve25519 public key of the sender.
-        std::string sender_key;
+    //! The room in which this session exists.
+    std::string room_id;
+    //! The session_id of the megolm session.
+    std::string session_id;
+    //! The curve25519 public key of the sender.
+    std::string sender_key;
 };
 
 void
@@ -110,8 +110,8 @@ from_json(const nlohmann::json &obj, MegolmSessionIndex &msg);
 
 struct StoredOlmSession
 {
-        std::uint64_t last_message_ts = 0;
-        std::string pickled_session;
+    std::uint64_t last_message_ts = 0;
+    std::string pickled_session;
 };
 void
 to_json(nlohmann::json &obj, const StoredOlmSession &msg);
@@ -121,43 +121,43 @@ from_json(const nlohmann::json &obj, StoredOlmSession &msg);
 //! Verification status of a single user
 struct VerificationStatus
 {
-        //! True, if the users master key is verified
-        crypto::Trust user_verified = crypto::Trust::Unverified;
-        //! List of all devices marked as verified
-        std::set verified_devices;
-        //! Map from sender key/curve25519 to trust status
-        std::map verified_device_keys;
-        //! Count of unverified devices
-        int unverified_device_count = 0;
-        // if the keys are not in cache
-        bool no_keys = false;
+    //! True, if the users master key is verified
+    crypto::Trust user_verified = crypto::Trust::Unverified;
+    //! List of all devices marked as verified
+    std::set verified_devices;
+    //! Map from sender key/curve25519 to trust status
+    std::map verified_device_keys;
+    //! Count of unverified devices
+    int unverified_device_count = 0;
+    // if the keys are not in cache
+    bool no_keys = false;
 };
 
 //! In memory cache of verification status
 struct VerificationStorage
 {
-        //! mapping of user to verification status
-        std::map status;
-        std::mutex verification_storage_mtx;
+    //! mapping of user to verification status
+    std::map status;
+    std::mutex verification_storage_mtx;
 };
 
 // this will store the keys of the user with whom a encrypted room is shared with
 struct UserKeyCache
 {
-        //! Device id to device keys
-        std::map device_keys;
-        //! cross signing keys
-        mtx::crypto::CrossSigningKeys master_keys, user_signing_keys, self_signing_keys;
-        //! Sync token when nheko last fetched the keys
-        std::string updated_at;
-        //! Sync token when the keys last changed. updated != last_changed means they are outdated.
-        std::string last_changed;
-        //! if the master key has ever changed
-        bool master_key_changed = false;
-        //! Device keys that were already used at least once
-        std::set seen_device_keys;
-        //! Device ids that were already used at least once
-        std::set seen_device_ids;
+    //! Device id to device keys
+    std::map device_keys;
+    //! cross signing keys
+    mtx::crypto::CrossSigningKeys master_keys, user_signing_keys, self_signing_keys;
+    //! Sync token when nheko last fetched the keys
+    std::string updated_at;
+    //! Sync token when the keys last changed. updated != last_changed means they are outdated.
+    std::string last_changed;
+    //! if the master key has ever changed
+    bool master_key_changed = false;
+    //! Device keys that were already used at least once
+    std::set seen_device_keys;
+    //! Device ids that were already used at least once
+    std::set seen_device_ids;
 };
 
 void
@@ -169,10 +169,10 @@ from_json(const nlohmann::json &j, UserKeyCache &info);
 // UserKeyCache stores only keys of users with which encrypted room is shared
 struct VerificationCache
 {
-        //! list of verified device_ids with device-verification
-        std::set device_verified;
-        //! list of devices the user blocks
-        std::set device_blocked;
+    //! list of verified device_ids with device-verification
+    std::set device_verified;
+    //! list of devices the user blocks
+    std::set device_blocked;
 };
 
 void
@@ -182,10 +182,10 @@ from_json(const nlohmann::json &j, VerificationCache &info);
 
 struct OnlineBackupVersion
 {
-        //! the version of the online backup currently enabled
-        std::string version;
-        //! the algorithm used by the backup
-        std::string algorithm;
+    //! the version of the online backup currently enabled
+    std::string version;
+    //! the algorithm used by the backup
+    std::string algorithm;
 };
 
 void
diff --git a/src/CacheStructs.h b/src/CacheStructs.h
index 5f4d392a..e28f5b2d 100644
--- a/src/CacheStructs.h
+++ b/src/CacheStructs.h
@@ -16,23 +16,23 @@
 namespace cache {
 enum class CacheVersion : int
 {
-        Older   = -1,
-        Current = 0,
-        Newer   = 1,
+    Older   = -1,
+    Current = 0,
+    Newer   = 1,
 };
 }
 
 struct RoomMember
 {
-        QString user_id;
-        QString display_name;
+    QString user_id;
+    QString display_name;
 };
 
 //! Used to uniquely identify a list of read receipts.
 struct ReadReceiptKey
 {
-        std::string event_id;
-        std::string room_id;
+    std::string event_id;
+    std::string room_id;
 };
 
 void
@@ -43,49 +43,49 @@ from_json(const nlohmann::json &j, ReadReceiptKey &key);
 
 struct DescInfo
 {
-        QString event_id;
-        QString userid;
-        QString body;
-        QString descriptiveTime;
-        uint64_t timestamp;
-        QDateTime datetime;
+    QString event_id;
+    QString userid;
+    QString body;
+    QString descriptiveTime;
+    uint64_t timestamp;
+    QDateTime datetime;
 };
 
 inline bool
 operator==(const DescInfo &a, const DescInfo &b)
 {
-        return std::tie(a.timestamp, a.event_id, a.userid, a.body, a.descriptiveTime) ==
-               std::tie(b.timestamp, b.event_id, b.userid, b.body, b.descriptiveTime);
+    return std::tie(a.timestamp, a.event_id, a.userid, a.body, a.descriptiveTime) ==
+           std::tie(b.timestamp, b.event_id, b.userid, b.body, b.descriptiveTime);
 }
 inline bool
 operator!=(const DescInfo &a, const DescInfo &b)
 {
-        return std::tie(a.timestamp, a.event_id, a.userid, a.body, a.descriptiveTime) !=
-               std::tie(b.timestamp, b.event_id, b.userid, b.body, b.descriptiveTime);
+    return std::tie(a.timestamp, a.event_id, a.userid, a.body, a.descriptiveTime) !=
+           std::tie(b.timestamp, b.event_id, b.userid, b.body, b.descriptiveTime);
 }
 
 //! UI info associated with a room.
 struct RoomInfo
 {
-        //! The calculated name of the room.
-        std::string name;
-        //! The topic of the room.
-        std::string topic;
-        //! The calculated avatar url of the room.
-        std::string avatar_url;
-        //! The calculated version of this room set at creation time.
-        std::string version;
-        //! Whether or not the room is an invite.
-        bool is_invite = false;
-        //! Wheter or not the room is a space
-        bool is_space = false;
-        //! Total number of members in the room.
-        size_t member_count = 0;
-        //! Who can access to the room.
-        mtx::events::state::JoinRule join_rule = mtx::events::state::JoinRule::Public;
-        bool guest_access                      = false;
-        //! The list of tags associated with this room
-        std::vector tags;
+    //! The calculated name of the room.
+    std::string name;
+    //! The topic of the room.
+    std::string topic;
+    //! The calculated avatar url of the room.
+    std::string avatar_url;
+    //! The calculated version of this room set at creation time.
+    std::string version;
+    //! Whether or not the room is an invite.
+    bool is_invite = false;
+    //! Wheter or not the room is a space
+    bool is_space = false;
+    //! Total number of members in the room.
+    size_t member_count = 0;
+    //! Who can access to the room.
+    mtx::events::state::JoinRule join_rule = mtx::events::state::JoinRule::Public;
+    bool guest_access                      = false;
+    //! The list of tags associated with this room
+    std::vector tags;
 };
 
 void
@@ -96,8 +96,8 @@ from_json(const nlohmann::json &j, RoomInfo &info);
 //! Basic information per member.
 struct MemberInfo
 {
-        std::string name;
-        std::string avatar_url;
+    std::string name;
+    std::string avatar_url;
 };
 
 void
@@ -107,13 +107,13 @@ from_json(const nlohmann::json &j, MemberInfo &info);
 
 struct RoomSearchResult
 {
-        std::string room_id;
-        RoomInfo info;
+    std::string room_id;
+    RoomInfo info;
 };
 
 struct ImagePackInfo
 {
-        mtx::events::msc2545::ImagePack pack;
-        std::string source_room;
-        std::string state_key;
+    mtx::events::msc2545::ImagePack pack;
+    std::string source_room;
+    std::string state_key;
 };
diff --git a/src/Cache_p.h b/src/Cache_p.h
index ff2f31e5..a15010e6 100644
--- a/src/Cache_p.h
+++ b/src/Cache_p.h
@@ -32,694 +32,658 @@
 
 class Cache : public QObject
 {
-        Q_OBJECT
+    Q_OBJECT
 
 public:
-        Cache(const QString &userId, QObject *parent = nullptr);
+    Cache(const QString &userId, QObject *parent = nullptr);
 
-        std::string displayName(const std::string &room_id, const std::string &user_id);
-        QString displayName(const QString &room_id, const QString &user_id);
-        QString avatarUrl(const QString &room_id, const QString &user_id);
+    std::string displayName(const std::string &room_id, const std::string &user_id);
+    QString displayName(const QString &room_id, const QString &user_id);
+    QString avatarUrl(const QString &room_id, const QString &user_id);
 
-        // presence
-        mtx::presence::PresenceState presenceState(const std::string &user_id);
-        std::string statusMessage(const std::string &user_id);
+    // presence
+    mtx::presence::PresenceState presenceState(const std::string &user_id);
+    std::string statusMessage(const std::string &user_id);
 
-        // user cache stores user keys
-        std::map> getMembersWithKeys(
-          const std::string &room_id,
-          bool verified_only);
-        void updateUserKeys(const std::string &sync_token,
-                            const mtx::responses::QueryKeys &keyQuery);
-        void markUserKeysOutOfDate(lmdb::txn &txn,
-                                   lmdb::dbi &db,
-                                   const std::vector &user_ids,
-                                   const std::string &sync_token);
-        void query_keys(const std::string &user_id,
-                        std::function cb);
+    // user cache stores user keys
+    std::map> getMembersWithKeys(
+      const std::string &room_id,
+      bool verified_only);
+    void updateUserKeys(const std::string &sync_token, const mtx::responses::QueryKeys &keyQuery);
+    void markUserKeysOutOfDate(lmdb::txn &txn,
+                               lmdb::dbi &db,
+                               const std::vector &user_ids,
+                               const std::string &sync_token);
+    void query_keys(const std::string &user_id,
+                    std::function cb);
 
-        // device & user verification cache
-        std::optional userKeys(const std::string &user_id);
-        VerificationStatus verificationStatus(const std::string &user_id);
-        void markDeviceVerified(const std::string &user_id, const std::string &device);
-        void markDeviceUnverified(const std::string &user_id, const std::string &device);
-        crypto::Trust roomVerificationStatus(const std::string &room_id);
+    // device & user verification cache
+    std::optional userKeys(const std::string &user_id);
+    VerificationStatus verificationStatus(const std::string &user_id);
+    void markDeviceVerified(const std::string &user_id, const std::string &device);
+    void markDeviceUnverified(const std::string &user_id, const std::string &device);
+    crypto::Trust roomVerificationStatus(const std::string &room_id);
 
-        std::vector joinedRooms();
+    std::vector joinedRooms();
 
-        QMap roomInfo(bool withInvites = true);
-        std::optional getRoomAliases(const std::string &roomid);
-        QHash invites();
-        std::optional invite(std::string_view roomid);
-        QMap> spaces();
+    QMap roomInfo(bool withInvites = true);
+    std::optional getRoomAliases(const std::string &roomid);
+    QHash invites();
+    std::optional invite(std::string_view roomid);
+    QMap> spaces();
 
-        //! Calculate & return the name of the room.
-        QString getRoomName(lmdb::txn &txn, lmdb::dbi &statesdb, lmdb::dbi &membersdb);
-        //! Get room join rules
-        mtx::events::state::JoinRule getRoomJoinRule(lmdb::txn &txn, lmdb::dbi &statesdb);
-        bool getRoomGuestAccess(lmdb::txn &txn, lmdb::dbi &statesdb);
-        //! Retrieve the topic of the room if any.
-        QString getRoomTopic(lmdb::txn &txn, lmdb::dbi &statesdb);
-        //! Retrieve the room avatar's url if any.
-        QString getRoomAvatarUrl(lmdb::txn &txn, lmdb::dbi &statesdb, lmdb::dbi &membersdb);
-        //! Retrieve the version of the room if any.
-        QString getRoomVersion(lmdb::txn &txn, lmdb::dbi &statesdb);
-        //! Retrieve if the room is a space
-        bool getRoomIsSpace(lmdb::txn &txn, lmdb::dbi &statesdb);
+    //! Calculate & return the name of the room.
+    QString getRoomName(lmdb::txn &txn, lmdb::dbi &statesdb, lmdb::dbi &membersdb);
+    //! Get room join rules
+    mtx::events::state::JoinRule getRoomJoinRule(lmdb::txn &txn, lmdb::dbi &statesdb);
+    bool getRoomGuestAccess(lmdb::txn &txn, lmdb::dbi &statesdb);
+    //! Retrieve the topic of the room if any.
+    QString getRoomTopic(lmdb::txn &txn, lmdb::dbi &statesdb);
+    //! Retrieve the room avatar's url if any.
+    QString getRoomAvatarUrl(lmdb::txn &txn, lmdb::dbi &statesdb, lmdb::dbi &membersdb);
+    //! Retrieve the version of the room if any.
+    QString getRoomVersion(lmdb::txn &txn, lmdb::dbi &statesdb);
+    //! Retrieve if the room is a space
+    bool getRoomIsSpace(lmdb::txn &txn, lmdb::dbi &statesdb);
 
-        //! Get a specific state event
-        template
-        std::optional> getStateEvent(const std::string &room_id,
-                                                                std::string_view state_key = "")
-        {
-                auto txn = lmdb::txn::begin(env_, nullptr, MDB_RDONLY);
-                return getStateEvent(txn, room_id, state_key);
-        }
+    //! Get a specific state event
+    template
+    std::optional> getStateEvent(const std::string &room_id,
+                                                            std::string_view state_key = "")
+    {
+        auto txn = lmdb::txn::begin(env_, nullptr, MDB_RDONLY);
+        return getStateEvent(txn, room_id, state_key);
+    }
 
-        //! retrieve a specific event from account data
-        //! pass empty room_id for global account data
-        std::optional getAccountData(
-          mtx::events::EventType type,
-          const std::string &room_id = "");
+    //! retrieve a specific event from account data
+    //! pass empty room_id for global account data
+    std::optional getAccountData(
+      mtx::events::EventType type,
+      const std::string &room_id = "");
 
-        //! Retrieve member info from a room.
-        std::vector getMembers(const std::string &room_id,
-                                           std::size_t startIndex = 0,
-                                           std::size_t len        = 30);
+    //! Retrieve member info from a room.
+    std::vector getMembers(const std::string &room_id,
+                                       std::size_t startIndex = 0,
+                                       std::size_t len        = 30);
 
-        std::vector getMembersFromInvite(const std::string &room_id,
-                                                     std::size_t startIndex = 0,
-                                                     std::size_t len        = 30);
-        size_t memberCount(const std::string &room_id);
+    std::vector getMembersFromInvite(const std::string &room_id,
+                                                 std::size_t startIndex = 0,
+                                                 std::size_t len        = 30);
+    size_t memberCount(const std::string &room_id);
 
-        void saveState(const mtx::responses::Sync &res);
-        bool isInitialized();
-        bool isDatabaseReady() { return databaseReady_ && isInitialized(); }
+    void saveState(const mtx::responses::Sync &res);
+    bool isInitialized();
+    bool isDatabaseReady() { return databaseReady_ && isInitialized(); }
 
-        std::string nextBatchToken();
+    std::string nextBatchToken();
 
-        void deleteData();
+    void deleteData();
 
-        void removeInvite(lmdb::txn &txn, const std::string &room_id);
-        void removeInvite(const std::string &room_id);
-        void removeRoom(lmdb::txn &txn, const std::string &roomid);
-        void removeRoom(const std::string &roomid);
-        void setup();
+    void removeInvite(lmdb::txn &txn, const std::string &room_id);
+    void removeInvite(const std::string &room_id);
+    void removeRoom(lmdb::txn &txn, const std::string &roomid);
+    void removeRoom(const std::string &roomid);
+    void setup();
 
-        cache::CacheVersion formatVersion();
-        void setCurrentFormat();
-        bool runMigrations();
+    cache::CacheVersion formatVersion();
+    void setCurrentFormat();
+    bool runMigrations();
 
-        std::vector roomIds();
-        QMap getTimelineMentions();
+    std::vector roomIds();
+    QMap getTimelineMentions();
 
-        //! Retrieve all the user ids from a room.
-        std::vector roomMembers(const std::string &room_id);
+    //! Retrieve all the user ids from a room.
+    std::vector roomMembers(const std::string &room_id);
 
-        //! Check if the given user has power leve greater than than
-        //! lowest power level of the given events.
-        bool hasEnoughPowerLevel(const std::vector &eventTypes,
+    //! Check if the given user has power leve greater than than
+    //! lowest power level of the given events.
+    bool hasEnoughPowerLevel(const std::vector &eventTypes,
+                             const std::string &room_id,
+                             const std::string &user_id);
+
+    //! Adds a user to the read list for the given event.
+    //!
+    //! There should be only one user id present in a receipt list per room.
+    //! The user id should be removed from any other lists.
+    using Receipts = std::map>;
+    void updateReadReceipt(lmdb::txn &txn, const std::string &room_id, const Receipts &receipts);
+
+    //! Retrieve all the read receipts for the given event id and room.
+    //!
+    //! Returns a map of user ids and the time of the read receipt in milliseconds.
+    using UserReceipts = std::multimap>;
+    UserReceipts readReceipts(const QString &event_id, const QString &room_id);
+
+    RoomInfo singleRoomInfo(const std::string &room_id);
+    std::vector roomsWithStateUpdates(const mtx::responses::Sync &res);
+    std::map getRoomInfo(const std::vector &rooms);
+
+    //! Calculates which the read status of a room.
+    //! Whether all the events in the timeline have been read.
+    bool calculateRoomReadStatus(const std::string &room_id);
+    void calculateRoomReadStatus();
+
+    void markSentNotification(const std::string &event_id);
+    //! Removes an event from the sent notifications.
+    void removeReadNotification(const std::string &event_id);
+    //! Check if we have sent a desktop notification for the given event id.
+    bool isNotificationSent(const std::string &event_id);
+
+    //! Add all notifications containing a user mention to the db.
+    void saveTimelineMentions(const mtx::responses::Notifications &res);
+
+    //! retrieve events in timeline and related functions
+    struct Messages
+    {
+        mtx::responses::Timeline timeline;
+        uint64_t next_index;
+        bool end_of_cache = false;
+    };
+    Messages getTimelineMessages(lmdb::txn &txn,
                                  const std::string &room_id,
-                                 const std::string &user_id);
+                                 uint64_t index = std::numeric_limits::max(),
+                                 bool forward   = false);
 
-        //! Adds a user to the read list for the given event.
-        //!
-        //! There should be only one user id present in a receipt list per room.
-        //! The user id should be removed from any other lists.
-        using Receipts = std::map>;
-        void updateReadReceipt(lmdb::txn &txn,
-                               const std::string &room_id,
-                               const Receipts &receipts);
+    std::optional getEvent(const std::string &room_id,
+                                                                    const std::string &event_id);
+    void storeEvent(const std::string &room_id,
+                    const std::string &event_id,
+                    const mtx::events::collections::TimelineEvent &event);
+    void replaceEvent(const std::string &room_id,
+                      const std::string &event_id,
+                      const mtx::events::collections::TimelineEvent &event);
+    std::vector relatedEvents(const std::string &room_id, const std::string &event_id);
 
-        //! Retrieve all the read receipts for the given event id and room.
-        //!
-        //! Returns a map of user ids and the time of the read receipt in milliseconds.
-        using UserReceipts = std::multimap>;
-        UserReceipts readReceipts(const QString &event_id, const QString &room_id);
+    struct TimelineRange
+    {
+        uint64_t first, last;
+    };
+    std::optional getTimelineRange(const std::string &room_id);
+    std::optional getTimelineIndex(const std::string &room_id, std::string_view event_id);
+    std::optional getEventIndex(const std::string &room_id, std::string_view event_id);
+    std::optional> lastInvisibleEventAfter(
+      const std::string &room_id,
+      std::string_view event_id);
+    std::optional getTimelineEventId(const std::string &room_id, uint64_t index);
+    std::optional getArrivalIndex(const std::string &room_id, std::string_view event_id);
 
-        RoomInfo singleRoomInfo(const std::string &room_id);
-        std::vector roomsWithStateUpdates(const mtx::responses::Sync &res);
-        std::map getRoomInfo(const std::vector &rooms);
+    std::string previousBatchToken(const std::string &room_id);
+    uint64_t saveOldMessages(const std::string &room_id, const mtx::responses::Messages &res);
+    void savePendingMessage(const std::string &room_id,
+                            const mtx::events::collections::TimelineEvent &message);
+    std::optional firstPendingMessage(
+      const std::string &room_id);
+    void removePendingStatus(const std::string &room_id, const std::string &txn_id);
 
-        //! Calculates which the read status of a room.
-        //! Whether all the events in the timeline have been read.
-        bool calculateRoomReadStatus(const std::string &room_id);
-        void calculateRoomReadStatus();
+    //! clear timeline keeping only the latest batch
+    void clearTimeline(const std::string &room_id);
 
-        void markSentNotification(const std::string &event_id);
-        //! Removes an event from the sent notifications.
-        void removeReadNotification(const std::string &event_id);
-        //! Check if we have sent a desktop notification for the given event id.
-        bool isNotificationSent(const std::string &event_id);
+    //! Remove old unused data.
+    void deleteOldMessages();
+    void deleteOldData() noexcept;
+    //! Retrieve all saved room ids.
+    std::vector getRoomIds(lmdb::txn &txn);
+    std::vector getParentRoomIds(const std::string &room_id);
+    std::vector getChildRoomIds(const std::string &room_id);
 
-        //! Add all notifications containing a user mention to the db.
-        void saveTimelineMentions(const mtx::responses::Notifications &res);
+    std::vector getImagePacks(const std::string &room_id,
+                                             std::optional stickers);
 
-        //! retrieve events in timeline and related functions
-        struct Messages
-        {
-                mtx::responses::Timeline timeline;
-                uint64_t next_index;
-                bool end_of_cache = false;
+    //! Mark a room that uses e2e encryption.
+    void setEncryptedRoom(lmdb::txn &txn, const std::string &room_id);
+    bool isRoomEncrypted(const std::string &room_id);
+    std::optional roomEncryptionSettings(
+      const std::string &room_id);
+
+    //! Check if a user is a member of the room.
+    bool isRoomMember(const std::string &user_id, const std::string &room_id);
+
+    //
+    // Outbound Megolm Sessions
+    //
+    void saveOutboundMegolmSession(const std::string &room_id,
+                                   const GroupSessionData &data,
+                                   mtx::crypto::OutboundGroupSessionPtr &session);
+    OutboundGroupSessionDataRef getOutboundMegolmSession(const std::string &room_id);
+    bool outboundMegolmSessionExists(const std::string &room_id) noexcept;
+    void updateOutboundMegolmSession(const std::string &room_id,
+                                     const GroupSessionData &data,
+                                     mtx::crypto::OutboundGroupSessionPtr &session);
+    void dropOutboundMegolmSession(const std::string &room_id);
+
+    void importSessionKeys(const mtx::crypto::ExportedSessionKeys &keys);
+    mtx::crypto::ExportedSessionKeys exportSessionKeys();
+
+    //
+    // Inbound Megolm Sessions
+    //
+    void saveInboundMegolmSession(const MegolmSessionIndex &index,
+                                  mtx::crypto::InboundGroupSessionPtr session,
+                                  const GroupSessionData &data);
+    mtx::crypto::InboundGroupSessionPtr getInboundMegolmSession(const MegolmSessionIndex &index);
+    bool inboundMegolmSessionExists(const MegolmSessionIndex &index);
+    std::optional getMegolmSessionData(const MegolmSessionIndex &index);
+
+    //
+    // Olm Sessions
+    //
+    void saveOlmSession(const std::string &curve25519,
+                        mtx::crypto::OlmSessionPtr session,
+                        uint64_t timestamp);
+    std::vector getOlmSessions(const std::string &curve25519);
+    std::optional getOlmSession(const std::string &curve25519,
+                                                            const std::string &session_id);
+    std::optional getLatestOlmSession(const std::string &curve25519);
+
+    void saveOlmAccount(const std::string &pickled);
+    std::string restoreOlmAccount();
+
+    void saveBackupVersion(const OnlineBackupVersion &data);
+    void deleteBackupVersion();
+    std::optional backupVersion();
+
+    void storeSecret(const std::string name, const std::string secret, bool internal = false);
+    void deleteSecret(const std::string name, bool internal = false);
+    std::optional secret(const std::string name, bool internal = false);
+
+    std::string pickleSecret();
+
+    template
+    constexpr static bool isStateEvent_ =
+      std::is_same_v>,
+                     mtx::events::StateEvent().content)>>;
+
+    static int compare_state_key(const MDB_val *a, const MDB_val *b)
+    {
+        auto get_skey = [](const MDB_val *v) {
+            return nlohmann::json::parse(
+                     std::string_view(static_cast(v->mv_data), v->mv_size))
+              .value("key", "");
         };
-        Messages getTimelineMessages(lmdb::txn &txn,
-                                     const std::string &room_id,
-                                     uint64_t index = std::numeric_limits::max(),
-                                     bool forward   = false);
 
-        std::optional getEvent(
-          const std::string &room_id,
-          const std::string &event_id);
-        void storeEvent(const std::string &room_id,
-                        const std::string &event_id,
-                        const mtx::events::collections::TimelineEvent &event);
-        void replaceEvent(const std::string &room_id,
-                          const std::string &event_id,
-                          const mtx::events::collections::TimelineEvent &event);
-        std::vector relatedEvents(const std::string &room_id,
-                                               const std::string &event_id);
-
-        struct TimelineRange
-        {
-                uint64_t first, last;
-        };
-        std::optional getTimelineRange(const std::string &room_id);
-        std::optional getTimelineIndex(const std::string &room_id,
-                                                 std::string_view event_id);
-        std::optional getEventIndex(const std::string &room_id,
-                                              std::string_view event_id);
-        std::optional> lastInvisibleEventAfter(
-          const std::string &room_id,
-          std::string_view event_id);
-        std::optional getTimelineEventId(const std::string &room_id, uint64_t index);
-        std::optional getArrivalIndex(const std::string &room_id,
-                                                std::string_view event_id);
-
-        std::string previousBatchToken(const std::string &room_id);
-        uint64_t saveOldMessages(const std::string &room_id, const mtx::responses::Messages &res);
-        void savePendingMessage(const std::string &room_id,
-                                const mtx::events::collections::TimelineEvent &message);
-        std::optional firstPendingMessage(
-          const std::string &room_id);
-        void removePendingStatus(const std::string &room_id, const std::string &txn_id);
-
-        //! clear timeline keeping only the latest batch
-        void clearTimeline(const std::string &room_id);
-
-        //! Remove old unused data.
-        void deleteOldMessages();
-        void deleteOldData() noexcept;
-        //! Retrieve all saved room ids.
-        std::vector getRoomIds(lmdb::txn &txn);
-        std::vector getParentRoomIds(const std::string &room_id);
-        std::vector getChildRoomIds(const std::string &room_id);
-
-        std::vector getImagePacks(const std::string &room_id,
-                                                 std::optional stickers);
-
-        //! Mark a room that uses e2e encryption.
-        void setEncryptedRoom(lmdb::txn &txn, const std::string &room_id);
-        bool isRoomEncrypted(const std::string &room_id);
-        std::optional roomEncryptionSettings(
-          const std::string &room_id);
-
-        //! Check if a user is a member of the room.
-        bool isRoomMember(const std::string &user_id, const std::string &room_id);
-
-        //
-        // Outbound Megolm Sessions
-        //
-        void saveOutboundMegolmSession(const std::string &room_id,
-                                       const GroupSessionData &data,
-                                       mtx::crypto::OutboundGroupSessionPtr &session);
-        OutboundGroupSessionDataRef getOutboundMegolmSession(const std::string &room_id);
-        bool outboundMegolmSessionExists(const std::string &room_id) noexcept;
-        void updateOutboundMegolmSession(const std::string &room_id,
-                                         const GroupSessionData &data,
-                                         mtx::crypto::OutboundGroupSessionPtr &session);
-        void dropOutboundMegolmSession(const std::string &room_id);
-
-        void importSessionKeys(const mtx::crypto::ExportedSessionKeys &keys);
-        mtx::crypto::ExportedSessionKeys exportSessionKeys();
-
-        //
-        // Inbound Megolm Sessions
-        //
-        void saveInboundMegolmSession(const MegolmSessionIndex &index,
-                                      mtx::crypto::InboundGroupSessionPtr session,
-                                      const GroupSessionData &data);
-        mtx::crypto::InboundGroupSessionPtr getInboundMegolmSession(
-          const MegolmSessionIndex &index);
-        bool inboundMegolmSessionExists(const MegolmSessionIndex &index);
-        std::optional getMegolmSessionData(const MegolmSessionIndex &index);
-
-        //
-        // Olm Sessions
-        //
-        void saveOlmSession(const std::string &curve25519,
-                            mtx::crypto::OlmSessionPtr session,
-                            uint64_t timestamp);
-        std::vector getOlmSessions(const std::string &curve25519);
-        std::optional getOlmSession(const std::string &curve25519,
-                                                                const std::string &session_id);
-        std::optional getLatestOlmSession(
-          const std::string &curve25519);
-
-        void saveOlmAccount(const std::string &pickled);
-        std::string restoreOlmAccount();
-
-        void saveBackupVersion(const OnlineBackupVersion &data);
-        void deleteBackupVersion();
-        std::optional backupVersion();
-
-        void storeSecret(const std::string name, const std::string secret, bool internal = false);
-        void deleteSecret(const std::string name, bool internal = false);
-        std::optional secret(const std::string name, bool internal = false);
-
-        std::string pickleSecret();
-
-        template
-        constexpr static bool isStateEvent_ =
-          std::is_same_v>,
-                         mtx::events::StateEvent().content)>>;
-
-        static int compare_state_key(const MDB_val *a, const MDB_val *b)
-        {
-                auto get_skey = [](const MDB_val *v) {
-                        return nlohmann::json::parse(
-                                 std::string_view(static_cast(v->mv_data),
-                                                  v->mv_size))
-                          .value("key", "");
-                };
-
-                return get_skey(a).compare(get_skey(b));
-        }
+        return get_skey(a).compare(get_skey(b));
+    }
 signals:
-        void newReadReceipts(const QString &room_id, const std::vector &event_ids);
-        void roomReadStatus(const std::map &status);
-        void removeNotification(const QString &room_id, const QString &event_id);
-        void userKeysUpdate(const std::string &sync_token,
-                            const mtx::responses::QueryKeys &keyQuery);
-        void verificationStatusChanged(const std::string &userid);
-        void secretChanged(const std::string name);
+    void newReadReceipts(const QString &room_id, const std::vector &event_ids);
+    void roomReadStatus(const std::map &status);
+    void removeNotification(const QString &room_id, const QString &event_id);
+    void userKeysUpdate(const std::string &sync_token, const mtx::responses::QueryKeys &keyQuery);
+    void verificationStatusChanged(const std::string &userid);
+    void secretChanged(const std::string name);
 
 private:
-        //! Save an invited room.
-        void saveInvite(lmdb::txn &txn,
+    //! Save an invited room.
+    void saveInvite(lmdb::txn &txn,
+                    lmdb::dbi &statesdb,
+                    lmdb::dbi &membersdb,
+                    const mtx::responses::InvitedRoom &room);
+
+    //! Add a notification containing a user mention to the db.
+    void saveTimelineMentions(lmdb::txn &txn,
+                              const std::string &room_id,
+                              const QList &res);
+
+    //! Get timeline items that a user was mentions in for a given room
+    mtx::responses::Notifications getTimelineMentionsForRoom(lmdb::txn &txn,
+                                                             const std::string &room_id);
+
+    QString getInviteRoomName(lmdb::txn &txn, lmdb::dbi &statesdb, lmdb::dbi &membersdb);
+    QString getInviteRoomTopic(lmdb::txn &txn, lmdb::dbi &statesdb);
+    QString getInviteRoomAvatarUrl(lmdb::txn &txn, lmdb::dbi &statesdb, lmdb::dbi &membersdb);
+    bool getInviteRoomIsSpace(lmdb::txn &txn, lmdb::dbi &db);
+
+    std::optional getMember(const std::string &room_id, const std::string &user_id);
+
+    std::string getLastEventId(lmdb::txn &txn, const std::string &room_id);
+    void saveTimelineMessages(lmdb::txn &txn,
+                              lmdb::dbi &eventsDb,
+                              const std::string &room_id,
+                              const mtx::responses::Timeline &res);
+
+    //! retrieve a specific event from account data
+    //! pass empty room_id for global account data
+    std::optional
+    getAccountData(lmdb::txn &txn, mtx::events::EventType type, const std::string &room_id);
+    bool isHiddenEvent(lmdb::txn &txn,
+                       mtx::events::collections::TimelineEvents e,
+                       const std::string &room_id);
+
+    //! Remove a room from the cache.
+    // void removeLeftRoom(lmdb::txn &txn, const std::string &room_id);
+    template
+    void saveStateEvents(lmdb::txn &txn,
+                         lmdb::dbi &statesdb,
+                         lmdb::dbi &stateskeydb,
+                         lmdb::dbi &membersdb,
+                         lmdb::dbi &eventsDb,
+                         const std::string &room_id,
+                         const std::vector &events)
+    {
+        for (const auto &e : events)
+            saveStateEvent(txn, statesdb, stateskeydb, membersdb, eventsDb, room_id, e);
+    }
+
+    template
+    void saveStateEvent(lmdb::txn &txn,
                         lmdb::dbi &statesdb,
+                        lmdb::dbi &stateskeydb,
                         lmdb::dbi &membersdb,
-                        const mtx::responses::InvitedRoom &room);
+                        lmdb::dbi &eventsDb,
+                        const std::string &room_id,
+                        const T &event)
+    {
+        using namespace mtx::events;
+        using namespace mtx::events::state;
 
-        //! Add a notification containing a user mention to the db.
-        void saveTimelineMentions(lmdb::txn &txn,
-                                  const std::string &room_id,
-                                  const QList &res);
+        if (auto e = std::get_if>(&event); e != nullptr) {
+            switch (e->content.membership) {
+            //
+            // We only keep users with invite or join membership.
+            //
+            case Membership::Invite:
+            case Membership::Join: {
+                auto display_name =
+                  e->content.display_name.empty() ? e->state_key : e->content.display_name;
 
-        //! Get timeline items that a user was mentions in for a given room
-        mtx::responses::Notifications getTimelineMentionsForRoom(lmdb::txn &txn,
-                                                                 const std::string &room_id);
+                // Lightweight representation of a member.
+                MemberInfo tmp{display_name, e->content.avatar_url};
 
-        QString getInviteRoomName(lmdb::txn &txn, lmdb::dbi &statesdb, lmdb::dbi &membersdb);
-        QString getInviteRoomTopic(lmdb::txn &txn, lmdb::dbi &statesdb);
-        QString getInviteRoomAvatarUrl(lmdb::txn &txn, lmdb::dbi &statesdb, lmdb::dbi &membersdb);
-        bool getInviteRoomIsSpace(lmdb::txn &txn, lmdb::dbi &db);
+                membersdb.put(txn, e->state_key, json(tmp).dump());
+                break;
+            }
+            default: {
+                membersdb.del(txn, e->state_key, "");
+                break;
+            }
+            }
 
-        std::optional getMember(const std::string &room_id, const std::string &user_id);
-
-        std::string getLastEventId(lmdb::txn &txn, const std::string &room_id);
-        void saveTimelineMessages(lmdb::txn &txn,
-                                  lmdb::dbi &eventsDb,
-                                  const std::string &room_id,
-                                  const mtx::responses::Timeline &res);
-
-        //! retrieve a specific event from account data
-        //! pass empty room_id for global account data
-        std::optional
-        getAccountData(lmdb::txn &txn, mtx::events::EventType type, const std::string &room_id);
-        bool isHiddenEvent(lmdb::txn &txn,
-                           mtx::events::collections::TimelineEvents e,
-                           const std::string &room_id);
-
-        //! Remove a room from the cache.
-        // void removeLeftRoom(lmdb::txn &txn, const std::string &room_id);
-        template
-        void saveStateEvents(lmdb::txn &txn,
-                             lmdb::dbi &statesdb,
-                             lmdb::dbi &stateskeydb,
-                             lmdb::dbi &membersdb,
-                             lmdb::dbi &eventsDb,
-                             const std::string &room_id,
-                             const std::vector &events)
-        {
-                for (const auto &e : events)
-                        saveStateEvent(txn, statesdb, stateskeydb, membersdb, eventsDb, room_id, e);
+            return;
+        } else if (std::holds_alternative>(event)) {
+            setEncryptedRoom(txn, room_id);
+            return;
         }
 
-        template
-        void saveStateEvent(lmdb::txn &txn,
-                            lmdb::dbi &statesdb,
-                            lmdb::dbi &stateskeydb,
-                            lmdb::dbi &membersdb,
-                            lmdb::dbi &eventsDb,
-                            const std::string &room_id,
-                            const T &event)
+        std::visit(
+          [&txn, &statesdb, &stateskeydb, &eventsDb, &membersdb](const auto &e) {
+              if constexpr (isStateEvent_) {
+                  eventsDb.put(txn, e.event_id, json(e).dump());
+
+                  if (e.type != EventType::Unsupported) {
+                      if (std::is_same_v>,
+                                         StateEvent>) {
+                          if (e.type == EventType::RoomMember)
+                              membersdb.del(txn, e.state_key, "");
+                          else if (e.state_key.empty())
+                              statesdb.del(txn, to_string(e.type));
+                          else
+                              stateskeydb.del(txn,
+                                              to_string(e.type),
+                                              json::object({
+                                                             {"key", e.state_key},
+                                                             {"id", e.event_id},
+                                                           })
+                                                .dump());
+                      } else if (e.state_key.empty())
+                          statesdb.put(txn, to_string(e.type), json(e).dump());
+                      else
+                          stateskeydb.put(txn,
+                                          to_string(e.type),
+                                          json::object({
+                                                         {"key", e.state_key},
+                                                         {"id", e.event_id},
+                                                       })
+                                            .dump());
+                  }
+              }
+          },
+          event);
+    }
+
+    template
+    std::optional> getStateEvent(lmdb::txn &txn,
+                                                            const std::string &room_id,
+                                                            std::string_view state_key = "")
+    {
+        constexpr auto type = mtx::events::state_content_to_type;
+        static_assert(type != mtx::events::EventType::Unsupported,
+                      "Not a supported type in state events.");
+
+        if (room_id.empty())
+            return std::nullopt;
+        const auto typeStr = to_string(type);
+
+        std::string_view value;
+        if (state_key.empty()) {
+            auto db = getStatesDb(txn, room_id);
+            if (!db.get(txn, typeStr, value)) {
+                return std::nullopt;
+            }
+        } else {
+            auto db                   = getStatesKeyDb(txn, room_id);
+            std::string d             = json::object({{"key", state_key}}).dump();
+            std::string_view data     = d;
+            std::string_view typeStrV = typeStr;
+
+            auto cursor = lmdb::cursor::open(txn, db);
+            if (!cursor.get(typeStrV, data, MDB_GET_BOTH))
+                return std::nullopt;
+
+            try {
+                auto eventsDb = getEventsDb(txn, room_id);
+                if (!eventsDb.get(txn, json::parse(data)["id"].get(), value))
+                    return std::nullopt;
+            } catch (std::exception &e) {
+                return std::nullopt;
+            }
+        }
+
+        try {
+            return json::parse(value).get>();
+        } catch (std::exception &e) {
+            return std::nullopt;
+        }
+    }
+
+    template
+    std::vector> getStateEventsWithType(lmdb::txn &txn,
+                                                                   const std::string &room_id)
+
+    {
+        constexpr auto type = mtx::events::state_content_to_type;
+        static_assert(type != mtx::events::EventType::Unsupported,
+                      "Not a supported type in state events.");
+
+        if (room_id.empty())
+            return {};
+
+        std::vector> events;
+
         {
-                using namespace mtx::events;
-                using namespace mtx::events::state;
+            auto db                   = getStatesKeyDb(txn, room_id);
+            auto eventsDb             = getEventsDb(txn, room_id);
+            const auto typeStr        = to_string(type);
+            std::string_view typeStrV = typeStr;
+            std::string_view data;
+            std::string_view value;
 
-                if (auto e = std::get_if>(&event); e != nullptr) {
-                        switch (e->content.membership) {
-                        //
-                        // We only keep users with invite or join membership.
-                        //
-                        case Membership::Invite:
-                        case Membership::Join: {
-                                auto display_name = e->content.display_name.empty()
-                                                      ? e->state_key
-                                                      : e->content.display_name;
+            auto cursor = lmdb::cursor::open(txn, db);
+            bool first  = true;
+            if (cursor.get(typeStrV, data, MDB_SET)) {
+                while (cursor.get(typeStrV, data, first ? MDB_FIRST_DUP : MDB_NEXT_DUP)) {
+                    first = false;
 
-                                // Lightweight representation of a member.
-                                MemberInfo tmp{display_name, e->content.avatar_url};
-
-                                membersdb.put(txn, e->state_key, json(tmp).dump());
-                                break;
-                        }
-                        default: {
-                                membersdb.del(txn, e->state_key, "");
-                                break;
-                        }
-                        }
-
-                        return;
-                } else if (std::holds_alternative>(event)) {
-                        setEncryptedRoom(txn, room_id);
-                        return;
+                    if (eventsDb.get(txn, json::parse(data)["id"].get(), value))
+                        events.push_back(json::parse(value).get>());
                 }
-
-                std::visit(
-                  [&txn, &statesdb, &stateskeydb, &eventsDb, &membersdb](const auto &e) {
-                          if constexpr (isStateEvent_) {
-                                  eventsDb.put(txn, e.event_id, json(e).dump());
-
-                                  if (e.type != EventType::Unsupported) {
-                                          if (std::is_same_v<
-                                                std::remove_cv_t<
-                                                  std::remove_reference_t>,
-                                                StateEvent>) {
-                                                  if (e.type == EventType::RoomMember)
-                                                          membersdb.del(txn, e.state_key, "");
-                                                  else if (e.state_key.empty())
-                                                          statesdb.del(txn, to_string(e.type));
-                                                  else
-                                                          stateskeydb.del(
-                                                            txn,
-                                                            to_string(e.type),
-                                                            json::object({
-                                                                           {"key", e.state_key},
-                                                                           {"id", e.event_id},
-                                                                         })
-                                                              .dump());
-                                          } else if (e.state_key.empty())
-                                                  statesdb.put(
-                                                    txn, to_string(e.type), json(e).dump());
-                                          else
-                                                  stateskeydb.put(
-                                                    txn,
-                                                    to_string(e.type),
-                                                    json::object({
-                                                                   {"key", e.state_key},
-                                                                   {"id", e.event_id},
-                                                                 })
-                                                      .dump());
-                                  }
-                          }
-                  },
-                  event);
+            }
         }
 
-        template
-        std::optional> getStateEvent(lmdb::txn &txn,
-                                                                const std::string &room_id,
-                                                                std::string_view state_key = "")
-        {
-                constexpr auto type = mtx::events::state_content_to_type;
-                static_assert(type != mtx::events::EventType::Unsupported,
-                              "Not a supported type in state events.");
+        return events;
+    }
+    void saveInvites(lmdb::txn &txn,
+                     const std::map &rooms);
 
-                if (room_id.empty())
-                        return std::nullopt;
-                const auto typeStr = to_string(type);
+    void savePresence(
+      lmdb::txn &txn,
+      const std::vector> &presenceUpdates);
 
-                std::string_view value;
-                if (state_key.empty()) {
-                        auto db = getStatesDb(txn, room_id);
-                        if (!db.get(txn, typeStr, value)) {
-                                return std::nullopt;
-                        }
-                } else {
-                        auto db                   = getStatesKeyDb(txn, room_id);
-                        std::string d             = json::object({{"key", state_key}}).dump();
-                        std::string_view data     = d;
-                        std::string_view typeStrV = typeStr;
+    //! Sends signals for the rooms that are removed.
+    void removeLeftRooms(lmdb::txn &txn,
+                         const std::map &rooms)
+    {
+        for (const auto &room : rooms) {
+            removeRoom(txn, room.first);
 
-                        auto cursor = lmdb::cursor::open(txn, db);
-                        if (!cursor.get(typeStrV, data, MDB_GET_BOTH))
-                                return std::nullopt;
-
-                        try {
-                                auto eventsDb = getEventsDb(txn, room_id);
-                                if (!eventsDb.get(
-                                      txn, json::parse(data)["id"].get(), value))
-                                        return std::nullopt;
-                        } catch (std::exception &e) {
-                                return std::nullopt;
-                        }
-                }
-
-                try {
-                        return json::parse(value).get>();
-                } catch (std::exception &e) {
-                        return std::nullopt;
-                }
+            // Clean up leftover invites.
+            removeInvite(txn, room.first);
         }
+    }
 
-        template
-        std::vector> getStateEventsWithType(lmdb::txn &txn,
-                                                                       const std::string &room_id)
+    void updateSpaces(lmdb::txn &txn,
+                      const std::set &spaces_with_updates,
+                      std::set rooms_with_updates);
 
-        {
-                constexpr auto type = mtx::events::state_content_to_type;
-                static_assert(type != mtx::events::EventType::Unsupported,
-                              "Not a supported type in state events.");
+    lmdb::dbi getPendingReceiptsDb(lmdb::txn &txn)
+    {
+        return lmdb::dbi::open(txn, "pending_receipts", MDB_CREATE);
+    }
 
-                if (room_id.empty())
-                        return {};
+    lmdb::dbi getEventsDb(lmdb::txn &txn, const std::string &room_id)
+    {
+        return lmdb::dbi::open(txn, std::string(room_id + "/events").c_str(), MDB_CREATE);
+    }
 
-                std::vector> events;
+    lmdb::dbi getEventOrderDb(lmdb::txn &txn, const std::string &room_id)
+    {
+        return lmdb::dbi::open(
+          txn, std::string(room_id + "/event_order").c_str(), MDB_CREATE | MDB_INTEGERKEY);
+    }
 
-                {
-                        auto db                   = getStatesKeyDb(txn, room_id);
-                        auto eventsDb             = getEventsDb(txn, room_id);
-                        const auto typeStr        = to_string(type);
-                        std::string_view typeStrV = typeStr;
-                        std::string_view data;
-                        std::string_view value;
+    // inverse of EventOrderDb
+    lmdb::dbi getEventToOrderDb(lmdb::txn &txn, const std::string &room_id)
+    {
+        return lmdb::dbi::open(txn, std::string(room_id + "/event2order").c_str(), MDB_CREATE);
+    }
 
-                        auto cursor = lmdb::cursor::open(txn, db);
-                        bool first  = true;
-                        if (cursor.get(typeStrV, data, MDB_SET)) {
-                                while (cursor.get(
-                                  typeStrV, data, first ? MDB_FIRST_DUP : MDB_NEXT_DUP)) {
-                                        first = false;
+    lmdb::dbi getMessageToOrderDb(lmdb::txn &txn, const std::string &room_id)
+    {
+        return lmdb::dbi::open(txn, std::string(room_id + "/msg2order").c_str(), MDB_CREATE);
+    }
 
-                                        if (eventsDb.get(txn,
-                                                         json::parse(data)["id"].get(),
-                                                         value))
-                                                events.push_back(
-                                                  json::parse(value)
-                                                    .get>());
-                                }
-                        }
-                }
+    lmdb::dbi getOrderToMessageDb(lmdb::txn &txn, const std::string &room_id)
+    {
+        return lmdb::dbi::open(
+          txn, std::string(room_id + "/order2msg").c_str(), MDB_CREATE | MDB_INTEGERKEY);
+    }
 
-                return events;
-        }
-        void saveInvites(lmdb::txn &txn,
-                         const std::map &rooms);
+    lmdb::dbi getPendingMessagesDb(lmdb::txn &txn, const std::string &room_id)
+    {
+        return lmdb::dbi::open(
+          txn, std::string(room_id + "/pending").c_str(), MDB_CREATE | MDB_INTEGERKEY);
+    }
 
-        void savePresence(
-          lmdb::txn &txn,
-          const std::vector> &presenceUpdates);
+    lmdb::dbi getRelationsDb(lmdb::txn &txn, const std::string &room_id)
+    {
+        return lmdb::dbi::open(
+          txn, std::string(room_id + "/related").c_str(), MDB_CREATE | MDB_DUPSORT);
+    }
 
-        //! Sends signals for the rooms that are removed.
-        void removeLeftRooms(lmdb::txn &txn,
-                             const std::map &rooms)
-        {
-                for (const auto &room : rooms) {
-                        removeRoom(txn, room.first);
+    lmdb::dbi getInviteStatesDb(lmdb::txn &txn, const std::string &room_id)
+    {
+        return lmdb::dbi::open(txn, std::string(room_id + "/invite_state").c_str(), MDB_CREATE);
+    }
 
-                        // Clean up leftover invites.
-                        removeInvite(txn, room.first);
-                }
-        }
+    lmdb::dbi getInviteMembersDb(lmdb::txn &txn, const std::string &room_id)
+    {
+        return lmdb::dbi::open(txn, std::string(room_id + "/invite_members").c_str(), MDB_CREATE);
+    }
 
-        void updateSpaces(lmdb::txn &txn,
-                          const std::set &spaces_with_updates,
-                          std::set rooms_with_updates);
+    lmdb::dbi getStatesDb(lmdb::txn &txn, const std::string &room_id)
+    {
+        return lmdb::dbi::open(txn, std::string(room_id + "/state").c_str(), MDB_CREATE);
+    }
 
-        lmdb::dbi getPendingReceiptsDb(lmdb::txn &txn)
-        {
-                return lmdb::dbi::open(txn, "pending_receipts", MDB_CREATE);
-        }
+    lmdb::dbi getStatesKeyDb(lmdb::txn &txn, const std::string &room_id)
+    {
+        auto db = lmdb::dbi::open(
+          txn, std::string(room_id + "/state_by_key").c_str(), MDB_CREATE | MDB_DUPSORT);
+        lmdb::dbi_set_dupsort(txn, db, compare_state_key);
+        return db;
+    }
 
-        lmdb::dbi getEventsDb(lmdb::txn &txn, const std::string &room_id)
-        {
-                return lmdb::dbi::open(txn, std::string(room_id + "/events").c_str(), MDB_CREATE);
-        }
+    lmdb::dbi getAccountDataDb(lmdb::txn &txn, const std::string &room_id)
+    {
+        return lmdb::dbi::open(txn, std::string(room_id + "/account_data").c_str(), MDB_CREATE);
+    }
 
-        lmdb::dbi getEventOrderDb(lmdb::txn &txn, const std::string &room_id)
-        {
-                return lmdb::dbi::open(
-                  txn, std::string(room_id + "/event_order").c_str(), MDB_CREATE | MDB_INTEGERKEY);
-        }
+    lmdb::dbi getMembersDb(lmdb::txn &txn, const std::string &room_id)
+    {
+        return lmdb::dbi::open(txn, std::string(room_id + "/members").c_str(), MDB_CREATE);
+    }
 
-        // inverse of EventOrderDb
-        lmdb::dbi getEventToOrderDb(lmdb::txn &txn, const std::string &room_id)
-        {
-                return lmdb::dbi::open(
-                  txn, std::string(room_id + "/event2order").c_str(), MDB_CREATE);
-        }
+    lmdb::dbi getMentionsDb(lmdb::txn &txn, const std::string &room_id)
+    {
+        return lmdb::dbi::open(txn, std::string(room_id + "/mentions").c_str(), MDB_CREATE);
+    }
 
-        lmdb::dbi getMessageToOrderDb(lmdb::txn &txn, const std::string &room_id)
-        {
-                return lmdb::dbi::open(
-                  txn, std::string(room_id + "/msg2order").c_str(), MDB_CREATE);
-        }
+    lmdb::dbi getPresenceDb(lmdb::txn &txn) { return lmdb::dbi::open(txn, "presence", MDB_CREATE); }
 
-        lmdb::dbi getOrderToMessageDb(lmdb::txn &txn, const std::string &room_id)
-        {
-                return lmdb::dbi::open(
-                  txn, std::string(room_id + "/order2msg").c_str(), MDB_CREATE | MDB_INTEGERKEY);
-        }
+    lmdb::dbi getUserKeysDb(lmdb::txn &txn) { return lmdb::dbi::open(txn, "user_key", MDB_CREATE); }
 
-        lmdb::dbi getPendingMessagesDb(lmdb::txn &txn, const std::string &room_id)
-        {
-                return lmdb::dbi::open(
-                  txn, std::string(room_id + "/pending").c_str(), MDB_CREATE | MDB_INTEGERKEY);
-        }
+    lmdb::dbi getVerificationDb(lmdb::txn &txn)
+    {
+        return lmdb::dbi::open(txn, "verified", MDB_CREATE);
+    }
 
-        lmdb::dbi getRelationsDb(lmdb::txn &txn, const std::string &room_id)
-        {
-                return lmdb::dbi::open(
-                  txn, std::string(room_id + "/related").c_str(), MDB_CREATE | MDB_DUPSORT);
-        }
+    //! Retrieves or creates the database that stores the open OLM sessions between our device
+    //! and the given curve25519 key which represents another device.
+    //!
+    //! Each entry is a map from the session_id to the pickled representation of the session.
+    lmdb::dbi getOlmSessionsDb(lmdb::txn &txn, const std::string &curve25519_key)
+    {
+        return lmdb::dbi::open(
+          txn, std::string("olm_sessions.v2/" + curve25519_key).c_str(), MDB_CREATE);
+    }
 
-        lmdb::dbi getInviteStatesDb(lmdb::txn &txn, const std::string &room_id)
-        {
-                return lmdb::dbi::open(
-                  txn, std::string(room_id + "/invite_state").c_str(), MDB_CREATE);
-        }
+    QString getDisplayName(const mtx::events::StateEvent &event)
+    {
+        if (!event.content.display_name.empty())
+            return QString::fromStdString(event.content.display_name);
 
-        lmdb::dbi getInviteMembersDb(lmdb::txn &txn, const std::string &room_id)
-        {
-                return lmdb::dbi::open(
-                  txn, std::string(room_id + "/invite_members").c_str(), MDB_CREATE);
-        }
+        return QString::fromStdString(event.state_key);
+    }
 
-        lmdb::dbi getStatesDb(lmdb::txn &txn, const std::string &room_id)
-        {
-                return lmdb::dbi::open(txn, std::string(room_id + "/state").c_str(), MDB_CREATE);
-        }
+    std::optional verificationCache(const std::string &user_id, lmdb::txn &txn);
+    VerificationStatus verificationStatus_(const std::string &user_id, lmdb::txn &txn);
+    std::optional userKeys_(const std::string &user_id, lmdb::txn &txn);
 
-        lmdb::dbi getStatesKeyDb(lmdb::txn &txn, const std::string &room_id)
-        {
-                auto db = lmdb::dbi::open(
-                  txn, std::string(room_id + "/state_by_key").c_str(), MDB_CREATE | MDB_DUPSORT);
-                lmdb::dbi_set_dupsort(txn, db, compare_state_key);
-                return db;
-        }
+    void setNextBatchToken(lmdb::txn &txn, const std::string &token);
 
-        lmdb::dbi getAccountDataDb(lmdb::txn &txn, const std::string &room_id)
-        {
-                return lmdb::dbi::open(
-                  txn, std::string(room_id + "/account_data").c_str(), MDB_CREATE);
-        }
+    lmdb::env env_;
+    lmdb::dbi syncStateDb_;
+    lmdb::dbi roomsDb_;
+    lmdb::dbi spacesChildrenDb_, spacesParentsDb_;
+    lmdb::dbi invitesDb_;
+    lmdb::dbi readReceiptsDb_;
+    lmdb::dbi notificationsDb_;
 
-        lmdb::dbi getMembersDb(lmdb::txn &txn, const std::string &room_id)
-        {
-                return lmdb::dbi::open(txn, std::string(room_id + "/members").c_str(), MDB_CREATE);
-        }
+    lmdb::dbi devicesDb_;
+    lmdb::dbi deviceKeysDb_;
 
-        lmdb::dbi getMentionsDb(lmdb::txn &txn, const std::string &room_id)
-        {
-                return lmdb::dbi::open(txn, std::string(room_id + "/mentions").c_str(), MDB_CREATE);
-        }
+    lmdb::dbi inboundMegolmSessionDb_;
+    lmdb::dbi outboundMegolmSessionDb_;
+    lmdb::dbi megolmSessionDataDb_;
 
-        lmdb::dbi getPresenceDb(lmdb::txn &txn)
-        {
-                return lmdb::dbi::open(txn, "presence", MDB_CREATE);
-        }
+    lmdb::dbi encryptedRooms_;
 
-        lmdb::dbi getUserKeysDb(lmdb::txn &txn)
-        {
-                return lmdb::dbi::open(txn, "user_key", MDB_CREATE);
-        }
+    QString localUserId_;
+    QString cacheDirectory_;
 
-        lmdb::dbi getVerificationDb(lmdb::txn &txn)
-        {
-                return lmdb::dbi::open(txn, "verified", MDB_CREATE);
-        }
+    std::string pickle_secret_;
 
-        //! Retrieves or creates the database that stores the open OLM sessions between our device
-        //! and the given curve25519 key which represents another device.
-        //!
-        //! Each entry is a map from the session_id to the pickled representation of the session.
-        lmdb::dbi getOlmSessionsDb(lmdb::txn &txn, const std::string &curve25519_key)
-        {
-                return lmdb::dbi::open(
-                  txn, std::string("olm_sessions.v2/" + curve25519_key).c_str(), MDB_CREATE);
-        }
+    VerificationStorage verification_storage;
 
-        QString getDisplayName(const mtx::events::StateEvent &event)
-        {
-                if (!event.content.display_name.empty())
-                        return QString::fromStdString(event.content.display_name);
-
-                return QString::fromStdString(event.state_key);
-        }
-
-        std::optional verificationCache(const std::string &user_id,
-                                                           lmdb::txn &txn);
-        VerificationStatus verificationStatus_(const std::string &user_id, lmdb::txn &txn);
-        std::optional userKeys_(const std::string &user_id, lmdb::txn &txn);
-
-        void setNextBatchToken(lmdb::txn &txn, const std::string &token);
-
-        lmdb::env env_;
-        lmdb::dbi syncStateDb_;
-        lmdb::dbi roomsDb_;
-        lmdb::dbi spacesChildrenDb_, spacesParentsDb_;
-        lmdb::dbi invitesDb_;
-        lmdb::dbi readReceiptsDb_;
-        lmdb::dbi notificationsDb_;
-
-        lmdb::dbi devicesDb_;
-        lmdb::dbi deviceKeysDb_;
-
-        lmdb::dbi inboundMegolmSessionDb_;
-        lmdb::dbi outboundMegolmSessionDb_;
-        lmdb::dbi megolmSessionDataDb_;
-
-        lmdb::dbi encryptedRooms_;
-
-        QString localUserId_;
-        QString cacheDirectory_;
-
-        std::string pickle_secret_;
-
-        VerificationStorage verification_storage;
-
-        bool databaseReady_ = false;
+    bool databaseReady_ = false;
 };
 
 namespace cache {
diff --git a/src/CallDevices.cpp b/src/CallDevices.cpp
index 825d2f72..be185470 100644
--- a/src/CallDevices.cpp
+++ b/src/CallDevices.cpp
@@ -27,20 +27,20 @@ namespace {
 
 struct AudioSource
 {
-        std::string name;
-        GstDevice *device;
+    std::string name;
+    GstDevice *device;
 };
 
 struct VideoSource
 {
-        struct Caps
-        {
-                std::string resolution;
-                std::vector frameRates;
-        };
-        std::string name;
-        GstDevice *device;
-        std::vector caps;
+    struct Caps
+    {
+        std::string resolution;
+        std::vector frameRates;
+    };
+    std::string name;
+    GstDevice *device;
+    std::vector caps;
 };
 
 std::vector audioSources_;
@@ -50,315 +50,304 @@ using FrameRate = std::pair;
 std::optional
 getFrameRate(const GValue *value)
 {
-        if (GST_VALUE_HOLDS_FRACTION(value)) {
-                gint num = gst_value_get_fraction_numerator(value);
-                gint den = gst_value_get_fraction_denominator(value);
-                return FrameRate{num, den};
-        }
-        return std::nullopt;
+    if (GST_VALUE_HOLDS_FRACTION(value)) {
+        gint num = gst_value_get_fraction_numerator(value);
+        gint den = gst_value_get_fraction_denominator(value);
+        return FrameRate{num, den};
+    }
+    return std::nullopt;
 }
 
 void
 addFrameRate(std::vector &rates, const FrameRate &rate)
 {
-        constexpr double minimumFrameRate = 15.0;
-        if (static_cast(rate.first) / rate.second >= minimumFrameRate)
-                rates.push_back(std::to_string(rate.first) + "/" + std::to_string(rate.second));
+    constexpr double minimumFrameRate = 15.0;
+    if (static_cast(rate.first) / rate.second >= minimumFrameRate)
+        rates.push_back(std::to_string(rate.first) + "/" + std::to_string(rate.second));
 }
 
 void
 setDefaultDevice(bool isVideo)
 {
-        auto settings = ChatPage::instance()->userSettings();
-        if (isVideo && settings->camera().isEmpty()) {
-                const VideoSource &camera = videoSources_.front();
-                settings->setCamera(QString::fromStdString(camera.name));
-                settings->setCameraResolution(
-                  QString::fromStdString(camera.caps.front().resolution));
-                settings->setCameraFrameRate(
-                  QString::fromStdString(camera.caps.front().frameRates.front()));
-        } else if (!isVideo && settings->microphone().isEmpty()) {
-                settings->setMicrophone(QString::fromStdString(audioSources_.front().name));
-        }
+    auto settings = ChatPage::instance()->userSettings();
+    if (isVideo && settings->camera().isEmpty()) {
+        const VideoSource &camera = videoSources_.front();
+        settings->setCamera(QString::fromStdString(camera.name));
+        settings->setCameraResolution(QString::fromStdString(camera.caps.front().resolution));
+        settings->setCameraFrameRate(
+          QString::fromStdString(camera.caps.front().frameRates.front()));
+    } else if (!isVideo && settings->microphone().isEmpty()) {
+        settings->setMicrophone(QString::fromStdString(audioSources_.front().name));
+    }
 }
 
 void
 addDevice(GstDevice *device)
 {
-        if (!device)
-                return;
+    if (!device)
+        return;
 
-        gchar *name  = gst_device_get_display_name(device);
-        gchar *type  = gst_device_get_device_class(device);
-        bool isVideo = !std::strncmp(type, "Video", 5);
-        g_free(type);
-        nhlog::ui()->debug("WebRTC: {} device added: {}", isVideo ? "video" : "audio", name);
-        if (!isVideo) {
-                audioSources_.push_back({name, device});
-                g_free(name);
-                setDefaultDevice(false);
-                return;
-        }
-
-        GstCaps *gstcaps = gst_device_get_caps(device);
-        if (!gstcaps) {
-                nhlog::ui()->debug("WebRTC: unable to get caps for {}", name);
-                g_free(name);
-                return;
-        }
-
-        VideoSource source{name, device, {}};
+    gchar *name  = gst_device_get_display_name(device);
+    gchar *type  = gst_device_get_device_class(device);
+    bool isVideo = !std::strncmp(type, "Video", 5);
+    g_free(type);
+    nhlog::ui()->debug("WebRTC: {} device added: {}", isVideo ? "video" : "audio", name);
+    if (!isVideo) {
+        audioSources_.push_back({name, device});
         g_free(name);
-        guint nCaps = gst_caps_get_size(gstcaps);
-        for (guint i = 0; i < nCaps; ++i) {
-                GstStructure *structure  = gst_caps_get_structure(gstcaps, i);
-                const gchar *struct_name = gst_structure_get_name(structure);
-                if (!std::strcmp(struct_name, "video/x-raw")) {
-                        gint widthpx, heightpx;
-                        if (gst_structure_get(structure,
-                                              "width",
-                                              G_TYPE_INT,
-                                              &widthpx,
-                                              "height",
-                                              G_TYPE_INT,
-                                              &heightpx,
-                                              nullptr)) {
-                                VideoSource::Caps caps;
-                                caps.resolution =
-                                  std::to_string(widthpx) + "x" + std::to_string(heightpx);
-                                const GValue *value =
-                                  gst_structure_get_value(structure, "framerate");
-                                if (auto fr = getFrameRate(value); fr)
-                                        addFrameRate(caps.frameRates, *fr);
-                                else if (GST_VALUE_HOLDS_FRACTION_RANGE(value)) {
-                                        addFrameRate(
-                                          caps.frameRates,
-                                          *getFrameRate(gst_value_get_fraction_range_min(value)));
-                                        addFrameRate(
-                                          caps.frameRates,
-                                          *getFrameRate(gst_value_get_fraction_range_max(value)));
-                                } else if (GST_VALUE_HOLDS_LIST(value)) {
-                                        guint nRates = gst_value_list_get_size(value);
-                                        for (guint j = 0; j < nRates; ++j) {
-                                                const GValue *rate =
-                                                  gst_value_list_get_value(value, j);
-                                                if (auto frate = getFrameRate(rate); frate)
-                                                        addFrameRate(caps.frameRates, *frate);
-                                        }
-                                }
-                                if (!caps.frameRates.empty())
-                                        source.caps.push_back(std::move(caps));
-                        }
+        setDefaultDevice(false);
+        return;
+    }
+
+    GstCaps *gstcaps = gst_device_get_caps(device);
+    if (!gstcaps) {
+        nhlog::ui()->debug("WebRTC: unable to get caps for {}", name);
+        g_free(name);
+        return;
+    }
+
+    VideoSource source{name, device, {}};
+    g_free(name);
+    guint nCaps = gst_caps_get_size(gstcaps);
+    for (guint i = 0; i < nCaps; ++i) {
+        GstStructure *structure  = gst_caps_get_structure(gstcaps, i);
+        const gchar *struct_name = gst_structure_get_name(structure);
+        if (!std::strcmp(struct_name, "video/x-raw")) {
+            gint widthpx, heightpx;
+            if (gst_structure_get(structure,
+                                  "width",
+                                  G_TYPE_INT,
+                                  &widthpx,
+                                  "height",
+                                  G_TYPE_INT,
+                                  &heightpx,
+                                  nullptr)) {
+                VideoSource::Caps caps;
+                caps.resolution     = std::to_string(widthpx) + "x" + std::to_string(heightpx);
+                const GValue *value = gst_structure_get_value(structure, "framerate");
+                if (auto fr = getFrameRate(value); fr)
+                    addFrameRate(caps.frameRates, *fr);
+                else if (GST_VALUE_HOLDS_FRACTION_RANGE(value)) {
+                    addFrameRate(caps.frameRates,
+                                 *getFrameRate(gst_value_get_fraction_range_min(value)));
+                    addFrameRate(caps.frameRates,
+                                 *getFrameRate(gst_value_get_fraction_range_max(value)));
+                } else if (GST_VALUE_HOLDS_LIST(value)) {
+                    guint nRates = gst_value_list_get_size(value);
+                    for (guint j = 0; j < nRates; ++j) {
+                        const GValue *rate = gst_value_list_get_value(value, j);
+                        if (auto frate = getFrameRate(rate); frate)
+                            addFrameRate(caps.frameRates, *frate);
+                    }
                 }
+                if (!caps.frameRates.empty())
+                    source.caps.push_back(std::move(caps));
+            }
         }
-        gst_caps_unref(gstcaps);
-        videoSources_.push_back(std::move(source));
-        setDefaultDevice(true);
+    }
+    gst_caps_unref(gstcaps);
+    videoSources_.push_back(std::move(source));
+    setDefaultDevice(true);
 }
 
 template
 bool
 removeDevice(T &sources, GstDevice *device, bool changed)
 {
-        if (auto it = std::find_if(sources.begin(),
-                                   sources.end(),
-                                   [device](const auto &s) { return s.device == device; });
-            it != sources.end()) {
-                nhlog::ui()->debug(std::string("WebRTC: device ") +
-                                     (changed ? "changed: " : "removed: ") + "{}",
-                                   it->name);
-                gst_object_unref(device);
-                sources.erase(it);
-                return true;
-        }
-        return false;
+    if (auto it = std::find_if(
+          sources.begin(), sources.end(), [device](const auto &s) { return s.device == device; });
+        it != sources.end()) {
+        nhlog::ui()->debug(
+          std::string("WebRTC: device ") + (changed ? "changed: " : "removed: ") + "{}", it->name);
+        gst_object_unref(device);
+        sources.erase(it);
+        return true;
+    }
+    return false;
 }
 
 void
 removeDevice(GstDevice *device, bool changed)
 {
-        if (device) {
-                if (removeDevice(audioSources_, device, changed) ||
-                    removeDevice(videoSources_, device, changed))
-                        return;
-        }
+    if (device) {
+        if (removeDevice(audioSources_, device, changed) ||
+            removeDevice(videoSources_, device, changed))
+            return;
+    }
 }
 
 gboolean
 newBusMessage(GstBus *bus G_GNUC_UNUSED, GstMessage *msg, gpointer user_data G_GNUC_UNUSED)
 {
-        switch (GST_MESSAGE_TYPE(msg)) {
-        case GST_MESSAGE_DEVICE_ADDED: {
-                GstDevice *device;
-                gst_message_parse_device_added(msg, &device);
-                addDevice(device);
-                emit CallDevices::instance().devicesChanged();
-                break;
-        }
-        case GST_MESSAGE_DEVICE_REMOVED: {
-                GstDevice *device;
-                gst_message_parse_device_removed(msg, &device);
-                removeDevice(device, false);
-                emit CallDevices::instance().devicesChanged();
-                break;
-        }
-        case GST_MESSAGE_DEVICE_CHANGED: {
-                GstDevice *device;
-                GstDevice *oldDevice;
-                gst_message_parse_device_changed(msg, &device, &oldDevice);
-                removeDevice(oldDevice, true);
-                addDevice(device);
-                break;
-        }
-        default:
-                break;
-        }
-        return TRUE;
+    switch (GST_MESSAGE_TYPE(msg)) {
+    case GST_MESSAGE_DEVICE_ADDED: {
+        GstDevice *device;
+        gst_message_parse_device_added(msg, &device);
+        addDevice(device);
+        emit CallDevices::instance().devicesChanged();
+        break;
+    }
+    case GST_MESSAGE_DEVICE_REMOVED: {
+        GstDevice *device;
+        gst_message_parse_device_removed(msg, &device);
+        removeDevice(device, false);
+        emit CallDevices::instance().devicesChanged();
+        break;
+    }
+    case GST_MESSAGE_DEVICE_CHANGED: {
+        GstDevice *device;
+        GstDevice *oldDevice;
+        gst_message_parse_device_changed(msg, &device, &oldDevice);
+        removeDevice(oldDevice, true);
+        addDevice(device);
+        break;
+    }
+    default:
+        break;
+    }
+    return TRUE;
 }
 
 template
 std::vector
 deviceNames(T &sources, const std::string &defaultDevice)
 {
-        std::vector ret;
-        ret.reserve(sources.size());
-        for (const auto &s : sources)
-                ret.push_back(s.name);
+    std::vector ret;
+    ret.reserve(sources.size());
+    for (const auto &s : sources)
+        ret.push_back(s.name);
 
-        // move default device to top of the list
-        if (auto it = std::find(ret.begin(), ret.end(), defaultDevice); it != ret.end())
-                std::swap(ret.front(), *it);
+    // move default device to top of the list
+    if (auto it = std::find(ret.begin(), ret.end(), defaultDevice); it != ret.end())
+        std::swap(ret.front(), *it);
 
-        return ret;
+    return ret;
 }
 
 std::optional
 getVideoSource(const std::string &cameraName)
 {
-        if (auto it = std::find_if(videoSources_.cbegin(),
-                                   videoSources_.cend(),
-                                   [&cameraName](const auto &s) { return s.name == cameraName; });
-            it != videoSources_.cend()) {
-                return *it;
-        }
-        return std::nullopt;
+    if (auto it = std::find_if(videoSources_.cbegin(),
+                               videoSources_.cend(),
+                               [&cameraName](const auto &s) { return s.name == cameraName; });
+        it != videoSources_.cend()) {
+        return *it;
+    }
+    return std::nullopt;
 }
 
 std::pair
 tokenise(std::string_view str, char delim)
 {
-        std::pair ret;
-        ret.first  = std::atoi(str.data());
-        auto pos   = str.find_first_of(delim);
-        ret.second = std::atoi(str.data() + pos + 1);
-        return ret;
+    std::pair ret;
+    ret.first  = std::atoi(str.data());
+    auto pos   = str.find_first_of(delim);
+    ret.second = std::atoi(str.data() + pos + 1);
+    return ret;
 }
 }
 
 void
 CallDevices::init()
 {
-        static GstDeviceMonitor *monitor = nullptr;
-        if (!monitor) {
-                monitor       = gst_device_monitor_new();
-                GstCaps *caps = gst_caps_new_empty_simple("audio/x-raw");
-                gst_device_monitor_add_filter(monitor, "Audio/Source", caps);
-                gst_device_monitor_add_filter(monitor, "Audio/Duplex", caps);
-                gst_caps_unref(caps);
-                caps = gst_caps_new_empty_simple("video/x-raw");
-                gst_device_monitor_add_filter(monitor, "Video/Source", caps);
-                gst_device_monitor_add_filter(monitor, "Video/Duplex", caps);
-                gst_caps_unref(caps);
+    static GstDeviceMonitor *monitor = nullptr;
+    if (!monitor) {
+        monitor       = gst_device_monitor_new();
+        GstCaps *caps = gst_caps_new_empty_simple("audio/x-raw");
+        gst_device_monitor_add_filter(monitor, "Audio/Source", caps);
+        gst_device_monitor_add_filter(monitor, "Audio/Duplex", caps);
+        gst_caps_unref(caps);
+        caps = gst_caps_new_empty_simple("video/x-raw");
+        gst_device_monitor_add_filter(monitor, "Video/Source", caps);
+        gst_device_monitor_add_filter(monitor, "Video/Duplex", caps);
+        gst_caps_unref(caps);
 
-                GstBus *bus = gst_device_monitor_get_bus(monitor);
-                gst_bus_add_watch(bus, newBusMessage, nullptr);
-                gst_object_unref(bus);
-                if (!gst_device_monitor_start(monitor)) {
-                        nhlog::ui()->error("WebRTC: failed to start device monitor");
-                        return;
-                }
+        GstBus *bus = gst_device_monitor_get_bus(monitor);
+        gst_bus_add_watch(bus, newBusMessage, nullptr);
+        gst_object_unref(bus);
+        if (!gst_device_monitor_start(monitor)) {
+            nhlog::ui()->error("WebRTC: failed to start device monitor");
+            return;
         }
+    }
 }
 
 bool
 CallDevices::haveMic() const
 {
-        return !audioSources_.empty();
+    return !audioSources_.empty();
 }
 
 bool
 CallDevices::haveCamera() const
 {
-        return !videoSources_.empty();
+    return !videoSources_.empty();
 }
 
 std::vector
 CallDevices::names(bool isVideo, const std::string &defaultDevice) const
 {
-        return isVideo ? deviceNames(videoSources_, defaultDevice)
-                       : deviceNames(audioSources_, defaultDevice);
+    return isVideo ? deviceNames(videoSources_, defaultDevice)
+                   : deviceNames(audioSources_, defaultDevice);
 }
 
 std::vector
 CallDevices::resolutions(const std::string &cameraName) const
 {
-        std::vector ret;
-        if (auto s = getVideoSource(cameraName); s) {
-                ret.reserve(s->caps.size());
-                for (const auto &c : s->caps)
-                        ret.push_back(c.resolution);
-        }
-        return ret;
+    std::vector ret;
+    if (auto s = getVideoSource(cameraName); s) {
+        ret.reserve(s->caps.size());
+        for (const auto &c : s->caps)
+            ret.push_back(c.resolution);
+    }
+    return ret;
 }
 
 std::vector
 CallDevices::frameRates(const std::string &cameraName, const std::string &resolution) const
 {
-        if (auto s = getVideoSource(cameraName); s) {
-                if (auto it =
-                      std::find_if(s->caps.cbegin(),
+    if (auto s = getVideoSource(cameraName); s) {
+        if (auto it = std::find_if(s->caps.cbegin(),
                                    s->caps.cend(),
                                    [&](const auto &c) { return c.resolution == resolution; });
-                    it != s->caps.cend())
-                        return it->frameRates;
-        }
-        return {};
+            it != s->caps.cend())
+            return it->frameRates;
+    }
+    return {};
 }
 
 GstDevice *
 CallDevices::audioDevice() const
 {
-        std::string name = ChatPage::instance()->userSettings()->microphone().toStdString();
-        if (auto it = std::find_if(audioSources_.cbegin(),
-                                   audioSources_.cend(),
-                                   [&name](const auto &s) { return s.name == name; });
-            it != audioSources_.cend()) {
-                nhlog::ui()->debug("WebRTC: microphone: {}", name);
-                return it->device;
-        } else {
-                nhlog::ui()->error("WebRTC: unknown microphone: {}", name);
-                return nullptr;
-        }
+    std::string name = ChatPage::instance()->userSettings()->microphone().toStdString();
+    if (auto it = std::find_if(audioSources_.cbegin(),
+                               audioSources_.cend(),
+                               [&name](const auto &s) { return s.name == name; });
+        it != audioSources_.cend()) {
+        nhlog::ui()->debug("WebRTC: microphone: {}", name);
+        return it->device;
+    } else {
+        nhlog::ui()->error("WebRTC: unknown microphone: {}", name);
+        return nullptr;
+    }
 }
 
 GstDevice *
 CallDevices::videoDevice(std::pair &resolution, std::pair &frameRate) const
 {
-        auto settings    = ChatPage::instance()->userSettings();
-        std::string name = settings->camera().toStdString();
-        if (auto s = getVideoSource(name); s) {
-                nhlog::ui()->debug("WebRTC: camera: {}", name);
-                resolution = tokenise(settings->cameraResolution().toStdString(), 'x');
-                frameRate  = tokenise(settings->cameraFrameRate().toStdString(), '/');
-                nhlog::ui()->debug(
-                  "WebRTC: camera resolution: {}x{}", resolution.first, resolution.second);
-                nhlog::ui()->debug(
-                  "WebRTC: camera frame rate: {}/{}", frameRate.first, frameRate.second);
-                return s->device;
-        } else {
-                nhlog::ui()->error("WebRTC: unknown camera: {}", name);
-                return nullptr;
-        }
+    auto settings    = ChatPage::instance()->userSettings();
+    std::string name = settings->camera().toStdString();
+    if (auto s = getVideoSource(name); s) {
+        nhlog::ui()->debug("WebRTC: camera: {}", name);
+        resolution = tokenise(settings->cameraResolution().toStdString(), 'x');
+        frameRate  = tokenise(settings->cameraFrameRate().toStdString(), '/');
+        nhlog::ui()->debug("WebRTC: camera resolution: {}x{}", resolution.first, resolution.second);
+        nhlog::ui()->debug("WebRTC: camera frame rate: {}/{}", frameRate.first, frameRate.second);
+        return s->device;
+    } else {
+        nhlog::ui()->error("WebRTC: unknown camera: {}", name);
+        return nullptr;
+    }
 }
 
 #else
@@ -366,31 +355,31 @@ CallDevices::videoDevice(std::pair &resolution, std::pair &f
 bool
 CallDevices::haveMic() const
 {
-        return false;
+    return false;
 }
 
 bool
 CallDevices::haveCamera() const
 {
-        return false;
+    return false;
 }
 
 std::vector
 CallDevices::names(bool, const std::string &) const
 {
-        return {};
+    return {};
 }
 
 std::vector
 CallDevices::resolutions(const std::string &) const
 {
-        return {};
+    return {};
 }
 
 std::vector
 CallDevices::frameRates(const std::string &, const std::string &) const
 {
-        return {};
+    return {};
 }
 
 #endif
diff --git a/src/CallDevices.h b/src/CallDevices.h
index 69325f97..d30ce644 100644
--- a/src/CallDevices.h
+++ b/src/CallDevices.h
@@ -14,35 +14,34 @@ typedef struct _GstDevice GstDevice;
 
 class CallDevices : public QObject
 {
-        Q_OBJECT
+    Q_OBJECT
 
 public:
-        static CallDevices &instance()
-        {
-                static CallDevices instance;
-                return instance;
-        }
+    static CallDevices &instance()
+    {
+        static CallDevices instance;
+        return instance;
+    }
 
-        bool haveMic() const;
-        bool haveCamera() const;
-        std::vector names(bool isVideo, const std::string &defaultDevice) const;
-        std::vector resolutions(const std::string &cameraName) const;
-        std::vector frameRates(const std::string &cameraName,
-                                            const std::string &resolution) const;
+    bool haveMic() const;
+    bool haveCamera() const;
+    std::vector names(bool isVideo, const std::string &defaultDevice) const;
+    std::vector resolutions(const std::string &cameraName) const;
+    std::vector frameRates(const std::string &cameraName,
+                                        const std::string &resolution) const;
 
 signals:
-        void devicesChanged();
+    void devicesChanged();
 
 private:
-        CallDevices();
+    CallDevices();
 
-        friend class WebRTCSession;
-        void init();
-        GstDevice *audioDevice() const;
-        GstDevice *videoDevice(std::pair &resolution,
-                               std::pair &frameRate) const;
+    friend class WebRTCSession;
+    void init();
+    GstDevice *audioDevice() const;
+    GstDevice *videoDevice(std::pair &resolution, std::pair &frameRate) const;
 
 public:
-        CallDevices(CallDevices const &) = delete;
-        void operator=(CallDevices const &) = delete;
+    CallDevices(CallDevices const &) = delete;
+    void operator=(CallDevices const &) = delete;
 };
diff --git a/src/CallManager.cpp b/src/CallManager.cpp
index 601c9d6b..0f701b0d 100644
--- a/src/CallManager.cpp
+++ b/src/CallManager.cpp
@@ -54,206 +54,197 @@ CallManager::CallManager(QObject *parent)
   , session_(WebRTCSession::instance())
   , turnServerTimer_(this)
 {
-        qRegisterMetaType>();
-        qRegisterMetaType();
-        qRegisterMetaType();
+    qRegisterMetaType>();
+    qRegisterMetaType();
+    qRegisterMetaType();
 
-        connect(
-          &session_,
-          &WebRTCSession::offerCreated,
-          this,
-          [this](const std::string &sdp, const std::vector &candidates) {
-                  nhlog::ui()->debug("WebRTC: call id: {} - sending offer", callid_);
-                  emit newMessage(roomid_, CallInvite{callid_, sdp, "0", timeoutms_});
-                  emit newMessage(roomid_, CallCandidates{callid_, candidates, "0"});
-                  std::string callid(callid_);
-                  QTimer::singleShot(timeoutms_, this, [this, callid]() {
-                          if (session_.state() == webrtc::State::OFFERSENT && callid == callid_) {
-                                  hangUp(CallHangUp::Reason::InviteTimeOut);
-                                  emit ChatPage::instance()->showNotification(
-                                    "The remote side failed to pick up.");
-                          }
-                  });
+    connect(
+      &session_,
+      &WebRTCSession::offerCreated,
+      this,
+      [this](const std::string &sdp, const std::vector &candidates) {
+          nhlog::ui()->debug("WebRTC: call id: {} - sending offer", callid_);
+          emit newMessage(roomid_, CallInvite{callid_, sdp, "0", timeoutms_});
+          emit newMessage(roomid_, CallCandidates{callid_, candidates, "0"});
+          std::string callid(callid_);
+          QTimer::singleShot(timeoutms_, this, [this, callid]() {
+              if (session_.state() == webrtc::State::OFFERSENT && callid == callid_) {
+                  hangUp(CallHangUp::Reason::InviteTimeOut);
+                  emit ChatPage::instance()->showNotification("The remote side failed to pick up.");
+              }
           });
+      });
 
-        connect(
-          &session_,
-          &WebRTCSession::answerCreated,
-          this,
-          [this](const std::string &sdp, const std::vector &candidates) {
-                  nhlog::ui()->debug("WebRTC: call id: {} - sending answer", callid_);
-                  emit newMessage(roomid_, CallAnswer{callid_, sdp, "0"});
-                  emit newMessage(roomid_, CallCandidates{callid_, candidates, "0"});
-          });
+    connect(
+      &session_,
+      &WebRTCSession::answerCreated,
+      this,
+      [this](const std::string &sdp, const std::vector &candidates) {
+          nhlog::ui()->debug("WebRTC: call id: {} - sending answer", callid_);
+          emit newMessage(roomid_, CallAnswer{callid_, sdp, "0"});
+          emit newMessage(roomid_, CallCandidates{callid_, candidates, "0"});
+      });
 
-        connect(&session_,
-                &WebRTCSession::newICECandidate,
-                this,
-                [this](const CallCandidates::Candidate &candidate) {
-                        nhlog::ui()->debug("WebRTC: call id: {} - sending ice candidate", callid_);
-                        emit newMessage(roomid_, CallCandidates{callid_, {candidate}, "0"});
-                });
+    connect(&session_,
+            &WebRTCSession::newICECandidate,
+            this,
+            [this](const CallCandidates::Candidate &candidate) {
+                nhlog::ui()->debug("WebRTC: call id: {} - sending ice candidate", callid_);
+                emit newMessage(roomid_, CallCandidates{callid_, {candidate}, "0"});
+            });
 
-        connect(&turnServerTimer_, &QTimer::timeout, this, &CallManager::retrieveTurnServer);
+    connect(&turnServerTimer_, &QTimer::timeout, this, &CallManager::retrieveTurnServer);
 
-        connect(this,
-                &CallManager::turnServerRetrieved,
-                this,
-                [this](const mtx::responses::TurnServer &res) {
-                        nhlog::net()->info("TURN server(s) retrieved from homeserver:");
-                        nhlog::net()->info("username: {}", res.username);
-                        nhlog::net()->info("ttl: {} seconds", res.ttl);
-                        for (const auto &u : res.uris)
-                                nhlog::net()->info("uri: {}", u);
+    connect(
+      this, &CallManager::turnServerRetrieved, this, [this](const mtx::responses::TurnServer &res) {
+          nhlog::net()->info("TURN server(s) retrieved from homeserver:");
+          nhlog::net()->info("username: {}", res.username);
+          nhlog::net()->info("ttl: {} seconds", res.ttl);
+          for (const auto &u : res.uris)
+              nhlog::net()->info("uri: {}", u);
 
-                        // Request new credentials close to expiry
-                        // See https://tools.ietf.org/html/draft-uberti-behave-turn-rest-00
-                        turnURIs_    = getTurnURIs(res);
-                        uint32_t ttl = std::max(res.ttl, UINT32_C(3600));
-                        if (res.ttl < 3600)
-                                nhlog::net()->warn("Setting ttl to 1 hour");
-                        turnServerTimer_.setInterval(ttl * 1000 * 0.9);
-                });
+          // Request new credentials close to expiry
+          // See https://tools.ietf.org/html/draft-uberti-behave-turn-rest-00
+          turnURIs_    = getTurnURIs(res);
+          uint32_t ttl = std::max(res.ttl, UINT32_C(3600));
+          if (res.ttl < 3600)
+              nhlog::net()->warn("Setting ttl to 1 hour");
+          turnServerTimer_.setInterval(ttl * 1000 * 0.9);
+      });
 
-        connect(&session_, &WebRTCSession::stateChanged, this, [this](webrtc::State state) {
-                switch (state) {
-                case webrtc::State::DISCONNECTED:
-                        playRingtone(QUrl("qrc:/media/media/callend.ogg"), false);
-                        clear();
-                        break;
-                case webrtc::State::ICEFAILED: {
-                        QString error("Call connection failed.");
-                        if (turnURIs_.empty())
-                                error += " Your homeserver has no configured TURN server.";
-                        emit ChatPage::instance()->showNotification(error);
-                        hangUp(CallHangUp::Reason::ICEFailed);
-                        break;
-                }
+    connect(&session_, &WebRTCSession::stateChanged, this, [this](webrtc::State state) {
+        switch (state) {
+        case webrtc::State::DISCONNECTED:
+            playRingtone(QUrl("qrc:/media/media/callend.ogg"), false);
+            clear();
+            break;
+        case webrtc::State::ICEFAILED: {
+            QString error("Call connection failed.");
+            if (turnURIs_.empty())
+                error += " Your homeserver has no configured TURN server.";
+            emit ChatPage::instance()->showNotification(error);
+            hangUp(CallHangUp::Reason::ICEFailed);
+            break;
+        }
+        default:
+            break;
+        }
+        emit newCallState();
+    });
+
+    connect(
+      &CallDevices::instance(), &CallDevices::devicesChanged, this, &CallManager::devicesChanged);
+
+    connect(
+      &player_, &QMediaPlayer::mediaStatusChanged, this, [this](QMediaPlayer::MediaStatus status) {
+          if (status == QMediaPlayer::LoadedMedia)
+              player_.play();
+      });
+
+    connect(&player_,
+            QOverload::of(&QMediaPlayer::error),
+            [this](QMediaPlayer::Error error) {
+                stopRingtone();
+                switch (error) {
+                case QMediaPlayer::FormatError:
+                case QMediaPlayer::ResourceError:
+                    nhlog::ui()->error("WebRTC: valid ringtone file not found");
+                    break;
+                case QMediaPlayer::AccessDeniedError:
+                    nhlog::ui()->error("WebRTC: access to ringtone file denied");
+                    break;
                 default:
-                        break;
+                    nhlog::ui()->error("WebRTC: unable to play ringtone");
+                    break;
                 }
-                emit newCallState();
-        });
-
-        connect(&CallDevices::instance(),
-                &CallDevices::devicesChanged,
-                this,
-                &CallManager::devicesChanged);
-
-        connect(&player_,
-                &QMediaPlayer::mediaStatusChanged,
-                this,
-                [this](QMediaPlayer::MediaStatus status) {
-                        if (status == QMediaPlayer::LoadedMedia)
-                                player_.play();
-                });
-
-        connect(&player_,
-                QOverload::of(&QMediaPlayer::error),
-                [this](QMediaPlayer::Error error) {
-                        stopRingtone();
-                        switch (error) {
-                        case QMediaPlayer::FormatError:
-                        case QMediaPlayer::ResourceError:
-                                nhlog::ui()->error("WebRTC: valid ringtone file not found");
-                                break;
-                        case QMediaPlayer::AccessDeniedError:
-                                nhlog::ui()->error("WebRTC: access to ringtone file denied");
-                                break;
-                        default:
-                                nhlog::ui()->error("WebRTC: unable to play ringtone");
-                                break;
-                        }
-                });
+            });
 }
 
 void
 CallManager::sendInvite(const QString &roomid, CallType callType, unsigned int windowIndex)
 {
-        if (isOnCall())
-                return;
-        if (callType == CallType::SCREEN) {
-                if (!screenShareSupported())
-                        return;
-                if (windows_.empty() || windowIndex >= windows_.size()) {
-                        nhlog::ui()->error("WebRTC: window index out of range");
-                        return;
-                }
+    if (isOnCall())
+        return;
+    if (callType == CallType::SCREEN) {
+        if (!screenShareSupported())
+            return;
+        if (windows_.empty() || windowIndex >= windows_.size()) {
+            nhlog::ui()->error("WebRTC: window index out of range");
+            return;
         }
+    }
 
-        auto roomInfo = cache::singleRoomInfo(roomid.toStdString());
-        if (roomInfo.member_count != 2) {
-                emit ChatPage::instance()->showNotification("Calls are limited to 1:1 rooms.");
-                return;
-        }
+    auto roomInfo = cache::singleRoomInfo(roomid.toStdString());
+    if (roomInfo.member_count != 2) {
+        emit ChatPage::instance()->showNotification("Calls are limited to 1:1 rooms.");
+        return;
+    }
 
-        std::string errorMessage;
-        if (!session_.havePlugins(false, &errorMessage) ||
-            ((callType == CallType::VIDEO || callType == CallType::SCREEN) &&
-             !session_.havePlugins(true, &errorMessage))) {
-                emit ChatPage::instance()->showNotification(QString::fromStdString(errorMessage));
-                return;
-        }
+    std::string errorMessage;
+    if (!session_.havePlugins(false, &errorMessage) ||
+        ((callType == CallType::VIDEO || callType == CallType::SCREEN) &&
+         !session_.havePlugins(true, &errorMessage))) {
+        emit ChatPage::instance()->showNotification(QString::fromStdString(errorMessage));
+        return;
+    }
 
-        callType_ = callType;
-        roomid_   = roomid;
-        session_.setTurnServers(turnURIs_);
-        generateCallID();
-        std::string strCallType = callType_ == CallType::VOICE
-                                    ? "voice"
-                                    : (callType_ == CallType::VIDEO ? "video" : "screen");
-        nhlog::ui()->debug("WebRTC: call id: {} - creating {} invite", callid_, strCallType);
-        std::vector members(cache::getMembers(roomid.toStdString()));
-        const RoomMember &callee =
-          members.front().user_id == utils::localUser() ? members.back() : members.front();
-        callParty_ = callee.user_id;
-        callPartyDisplayName_ =
-          callee.display_name.isEmpty() ? callee.user_id : callee.display_name;
-        callPartyAvatarUrl_ = QString::fromStdString(roomInfo.avatar_url);
-        emit newInviteState();
-        playRingtone(QUrl("qrc:/media/media/ringback.ogg"), true);
-        if (!session_.createOffer(
-              callType, callType == CallType::SCREEN ? windows_[windowIndex].second : 0)) {
-                emit ChatPage::instance()->showNotification("Problem setting up call.");
-                endCall();
-        }
+    callType_ = callType;
+    roomid_   = roomid;
+    session_.setTurnServers(turnURIs_);
+    generateCallID();
+    std::string strCallType =
+      callType_ == CallType::VOICE ? "voice" : (callType_ == CallType::VIDEO ? "video" : "screen");
+    nhlog::ui()->debug("WebRTC: call id: {} - creating {} invite", callid_, strCallType);
+    std::vector members(cache::getMembers(roomid.toStdString()));
+    const RoomMember &callee =
+      members.front().user_id == utils::localUser() ? members.back() : members.front();
+    callParty_            = callee.user_id;
+    callPartyDisplayName_ = callee.display_name.isEmpty() ? callee.user_id : callee.display_name;
+    callPartyAvatarUrl_   = QString::fromStdString(roomInfo.avatar_url);
+    emit newInviteState();
+    playRingtone(QUrl("qrc:/media/media/ringback.ogg"), true);
+    if (!session_.createOffer(callType,
+                              callType == CallType::SCREEN ? windows_[windowIndex].second : 0)) {
+        emit ChatPage::instance()->showNotification("Problem setting up call.");
+        endCall();
+    }
 }
 
 namespace {
 std::string
 callHangUpReasonString(CallHangUp::Reason reason)
 {
-        switch (reason) {
-        case CallHangUp::Reason::ICEFailed:
-                return "ICE failed";
-        case CallHangUp::Reason::InviteTimeOut:
-                return "Invite time out";
-        default:
-                return "User";
-        }
+    switch (reason) {
+    case CallHangUp::Reason::ICEFailed:
+        return "ICE failed";
+    case CallHangUp::Reason::InviteTimeOut:
+        return "Invite time out";
+    default:
+        return "User";
+    }
 }
 }
 
 void
 CallManager::hangUp(CallHangUp::Reason reason)
 {
-        if (!callid_.empty()) {
-                nhlog::ui()->debug(
-                  "WebRTC: call id: {} - hanging up ({})", callid_, callHangUpReasonString(reason));
-                emit newMessage(roomid_, CallHangUp{callid_, "0", reason});
-                endCall();
-        }
+    if (!callid_.empty()) {
+        nhlog::ui()->debug(
+          "WebRTC: call id: {} - hanging up ({})", callid_, callHangUpReasonString(reason));
+        emit newMessage(roomid_, CallHangUp{callid_, "0", reason});
+        endCall();
+    }
 }
 
 void
 CallManager::syncEvent(const mtx::events::collections::TimelineEvents &event)
 {
 #ifdef GSTREAMER_AVAILABLE
-        if (handleEvent(event) || handleEvent(event) ||
-            handleEvent(event) || handleEvent(event))
-                return;
+    if (handleEvent(event) || handleEvent(event) ||
+        handleEvent(event) || handleEvent(event))
+        return;
 #else
-        (void)event;
+    (void)event;
 #endif
 }
 
@@ -261,325 +252,321 @@ template
 bool
 CallManager::handleEvent(const mtx::events::collections::TimelineEvents &event)
 {
-        if (std::holds_alternative>(event)) {
-                handleEvent(std::get>(event));
-                return true;
-        }
-        return false;
+    if (std::holds_alternative>(event)) {
+        handleEvent(std::get>(event));
+        return true;
+    }
+    return false;
 }
 
 void
 CallManager::handleEvent(const RoomEvent &callInviteEvent)
 {
-        const char video[]     = "m=video";
-        const std::string &sdp = callInviteEvent.content.sdp;
-        bool isVideo           = std::search(sdp.cbegin(),
-                                   sdp.cend(),
-                                   std::cbegin(video),
-                                   std::cend(video) - 1,
-                                   [](unsigned char c1, unsigned char c2) {
-                                           return std::tolower(c1) == std::tolower(c2);
-                                   }) != sdp.cend();
+    const char video[]     = "m=video";
+    const std::string &sdp = callInviteEvent.content.sdp;
+    bool isVideo           = std::search(sdp.cbegin(),
+                               sdp.cend(),
+                               std::cbegin(video),
+                               std::cend(video) - 1,
+                               [](unsigned char c1, unsigned char c2) {
+                                   return std::tolower(c1) == std::tolower(c2);
+                               }) != sdp.cend();
 
-        nhlog::ui()->debug("WebRTC: call id: {} - incoming {} CallInvite from {}",
-                           callInviteEvent.content.call_id,
-                           (isVideo ? "video" : "voice"),
-                           callInviteEvent.sender);
+    nhlog::ui()->debug("WebRTC: call id: {} - incoming {} CallInvite from {}",
+                       callInviteEvent.content.call_id,
+                       (isVideo ? "video" : "voice"),
+                       callInviteEvent.sender);
 
-        if (callInviteEvent.content.call_id.empty())
-                return;
+    if (callInviteEvent.content.call_id.empty())
+        return;
 
-        auto roomInfo = cache::singleRoomInfo(callInviteEvent.room_id);
-        if (isOnCall() || roomInfo.member_count != 2) {
-                emit newMessage(QString::fromStdString(callInviteEvent.room_id),
-                                CallHangUp{callInviteEvent.content.call_id,
-                                           "0",
-                                           CallHangUp::Reason::InviteTimeOut});
-                return;
-        }
+    auto roomInfo = cache::singleRoomInfo(callInviteEvent.room_id);
+    if (isOnCall() || roomInfo.member_count != 2) {
+        emit newMessage(
+          QString::fromStdString(callInviteEvent.room_id),
+          CallHangUp{callInviteEvent.content.call_id, "0", CallHangUp::Reason::InviteTimeOut});
+        return;
+    }
 
-        const QString &ringtone = ChatPage::instance()->userSettings()->ringtone();
-        if (ringtone != "Mute")
-                playRingtone(ringtone == "Default" ? QUrl("qrc:/media/media/ring.ogg")
-                                                   : QUrl::fromLocalFile(ringtone),
-                             true);
-        roomid_ = QString::fromStdString(callInviteEvent.room_id);
-        callid_ = callInviteEvent.content.call_id;
-        remoteICECandidates_.clear();
+    const QString &ringtone = ChatPage::instance()->userSettings()->ringtone();
+    if (ringtone != "Mute")
+        playRingtone(ringtone == "Default" ? QUrl("qrc:/media/media/ring.ogg")
+                                           : QUrl::fromLocalFile(ringtone),
+                     true);
+    roomid_ = QString::fromStdString(callInviteEvent.room_id);
+    callid_ = callInviteEvent.content.call_id;
+    remoteICECandidates_.clear();
 
-        std::vector members(cache::getMembers(callInviteEvent.room_id));
-        const RoomMember &caller =
-          members.front().user_id == utils::localUser() ? members.back() : members.front();
-        callParty_ = caller.user_id;
-        callPartyDisplayName_ =
-          caller.display_name.isEmpty() ? caller.user_id : caller.display_name;
-        callPartyAvatarUrl_ = QString::fromStdString(roomInfo.avatar_url);
+    std::vector members(cache::getMembers(callInviteEvent.room_id));
+    const RoomMember &caller =
+      members.front().user_id == utils::localUser() ? members.back() : members.front();
+    callParty_            = caller.user_id;
+    callPartyDisplayName_ = caller.display_name.isEmpty() ? caller.user_id : caller.display_name;
+    callPartyAvatarUrl_   = QString::fromStdString(roomInfo.avatar_url);
 
-        haveCallInvite_ = true;
-        callType_       = isVideo ? CallType::VIDEO : CallType::VOICE;
-        inviteSDP_      = callInviteEvent.content.sdp;
-        emit newInviteState();
+    haveCallInvite_ = true;
+    callType_       = isVideo ? CallType::VIDEO : CallType::VOICE;
+    inviteSDP_      = callInviteEvent.content.sdp;
+    emit newInviteState();
 }
 
 void
 CallManager::acceptInvite()
 {
-        if (!haveCallInvite_)
-                return;
+    if (!haveCallInvite_)
+        return;
 
-        stopRingtone();
-        std::string errorMessage;
-        if (!session_.havePlugins(false, &errorMessage) ||
-            (callType_ == CallType::VIDEO && !session_.havePlugins(true, &errorMessage))) {
-                emit ChatPage::instance()->showNotification(QString::fromStdString(errorMessage));
-                hangUp();
-                return;
-        }
+    stopRingtone();
+    std::string errorMessage;
+    if (!session_.havePlugins(false, &errorMessage) ||
+        (callType_ == CallType::VIDEO && !session_.havePlugins(true, &errorMessage))) {
+        emit ChatPage::instance()->showNotification(QString::fromStdString(errorMessage));
+        hangUp();
+        return;
+    }
 
-        session_.setTurnServers(turnURIs_);
-        if (!session_.acceptOffer(inviteSDP_)) {
-                emit ChatPage::instance()->showNotification("Problem setting up call.");
-                hangUp();
-                return;
-        }
-        session_.acceptICECandidates(remoteICECandidates_);
-        remoteICECandidates_.clear();
-        haveCallInvite_ = false;
-        emit newInviteState();
+    session_.setTurnServers(turnURIs_);
+    if (!session_.acceptOffer(inviteSDP_)) {
+        emit ChatPage::instance()->showNotification("Problem setting up call.");
+        hangUp();
+        return;
+    }
+    session_.acceptICECandidates(remoteICECandidates_);
+    remoteICECandidates_.clear();
+    haveCallInvite_ = false;
+    emit newInviteState();
 }
 
 void
 CallManager::handleEvent(const RoomEvent &callCandidatesEvent)
 {
-        if (callCandidatesEvent.sender == utils::localUser().toStdString())
-                return;
+    if (callCandidatesEvent.sender == utils::localUser().toStdString())
+        return;
 
-        nhlog::ui()->debug("WebRTC: call id: {} - incoming CallCandidates from {}",
-                           callCandidatesEvent.content.call_id,
-                           callCandidatesEvent.sender);
+    nhlog::ui()->debug("WebRTC: call id: {} - incoming CallCandidates from {}",
+                       callCandidatesEvent.content.call_id,
+                       callCandidatesEvent.sender);
 
-        if (callid_ == callCandidatesEvent.content.call_id) {
-                if (isOnCall())
-                        session_.acceptICECandidates(callCandidatesEvent.content.candidates);
-                else {
-                        // CallInvite has been received and we're awaiting localUser to accept or
-                        // reject the call
-                        for (const auto &c : callCandidatesEvent.content.candidates)
-                                remoteICECandidates_.push_back(c);
-                }
+    if (callid_ == callCandidatesEvent.content.call_id) {
+        if (isOnCall())
+            session_.acceptICECandidates(callCandidatesEvent.content.candidates);
+        else {
+            // CallInvite has been received and we're awaiting localUser to accept or
+            // reject the call
+            for (const auto &c : callCandidatesEvent.content.candidates)
+                remoteICECandidates_.push_back(c);
         }
+    }
 }
 
 void
 CallManager::handleEvent(const RoomEvent &callAnswerEvent)
 {
-        nhlog::ui()->debug("WebRTC: call id: {} - incoming CallAnswer from {}",
-                           callAnswerEvent.content.call_id,
-                           callAnswerEvent.sender);
+    nhlog::ui()->debug("WebRTC: call id: {} - incoming CallAnswer from {}",
+                       callAnswerEvent.content.call_id,
+                       callAnswerEvent.sender);
 
-        if (callAnswerEvent.sender == utils::localUser().toStdString() &&
-            callid_ == callAnswerEvent.content.call_id) {
-                if (!isOnCall()) {
-                        emit ChatPage::instance()->showNotification(
-                          "Call answered on another device.");
-                        stopRingtone();
-                        haveCallInvite_ = false;
-                        emit newInviteState();
-                }
-                return;
+    if (callAnswerEvent.sender == utils::localUser().toStdString() &&
+        callid_ == callAnswerEvent.content.call_id) {
+        if (!isOnCall()) {
+            emit ChatPage::instance()->showNotification("Call answered on another device.");
+            stopRingtone();
+            haveCallInvite_ = false;
+            emit newInviteState();
         }
+        return;
+    }
 
-        if (isOnCall() && callid_ == callAnswerEvent.content.call_id) {
-                stopRingtone();
-                if (!session_.acceptAnswer(callAnswerEvent.content.sdp)) {
-                        emit ChatPage::instance()->showNotification("Problem setting up call.");
-                        hangUp();
-                }
+    if (isOnCall() && callid_ == callAnswerEvent.content.call_id) {
+        stopRingtone();
+        if (!session_.acceptAnswer(callAnswerEvent.content.sdp)) {
+            emit ChatPage::instance()->showNotification("Problem setting up call.");
+            hangUp();
         }
+    }
 }
 
 void
 CallManager::handleEvent(const RoomEvent &callHangUpEvent)
 {
-        nhlog::ui()->debug("WebRTC: call id: {} - incoming CallHangUp ({}) from {}",
-                           callHangUpEvent.content.call_id,
-                           callHangUpReasonString(callHangUpEvent.content.reason),
-                           callHangUpEvent.sender);
+    nhlog::ui()->debug("WebRTC: call id: {} - incoming CallHangUp ({}) from {}",
+                       callHangUpEvent.content.call_id,
+                       callHangUpReasonString(callHangUpEvent.content.reason),
+                       callHangUpEvent.sender);
 
-        if (callid_ == callHangUpEvent.content.call_id)
-                endCall();
+    if (callid_ == callHangUpEvent.content.call_id)
+        endCall();
 }
 
 void
 CallManager::toggleMicMute()
 {
-        session_.toggleMicMute();
-        emit micMuteChanged();
+    session_.toggleMicMute();
+    emit micMuteChanged();
 }
 
 bool
 CallManager::callsSupported()
 {
 #ifdef GSTREAMER_AVAILABLE
-        return true;
+    return true;
 #else
-        return false;
+    return false;
 #endif
 }
 
 bool
 CallManager::screenShareSupported()
 {
-        return std::getenv("DISPLAY") && !std::getenv("WAYLAND_DISPLAY");
+    return std::getenv("DISPLAY") && !std::getenv("WAYLAND_DISPLAY");
 }
 
 QStringList
 CallManager::devices(bool isVideo) const
 {
-        QStringList ret;
-        const QString &defaultDevice = isVideo ? ChatPage::instance()->userSettings()->camera()
-                                               : ChatPage::instance()->userSettings()->microphone();
-        std::vector devices =
-          CallDevices::instance().names(isVideo, defaultDevice.toStdString());
-        ret.reserve(devices.size());
-        std::transform(devices.cbegin(),
-                       devices.cend(),
-                       std::back_inserter(ret),
-                       [](const auto &d) { return QString::fromStdString(d); });
+    QStringList ret;
+    const QString &defaultDevice = isVideo ? ChatPage::instance()->userSettings()->camera()
+                                           : ChatPage::instance()->userSettings()->microphone();
+    std::vector devices =
+      CallDevices::instance().names(isVideo, defaultDevice.toStdString());
+    ret.reserve(devices.size());
+    std::transform(devices.cbegin(), devices.cend(), std::back_inserter(ret), [](const auto &d) {
+        return QString::fromStdString(d);
+    });
 
-        return ret;
+    return ret;
 }
 
 void
 CallManager::generateCallID()
 {
-        using namespace std::chrono;
-        uint64_t ms = duration_cast(system_clock::now().time_since_epoch()).count();
-        callid_     = "c" + std::to_string(ms);
+    using namespace std::chrono;
+    uint64_t ms = duration_cast(system_clock::now().time_since_epoch()).count();
+    callid_     = "c" + std::to_string(ms);
 }
 
 void
 CallManager::clear()
 {
-        roomid_.clear();
-        callParty_.clear();
-        callPartyDisplayName_.clear();
-        callPartyAvatarUrl_.clear();
-        callid_.clear();
-        callType_       = CallType::VOICE;
-        haveCallInvite_ = false;
-        emit newInviteState();
-        inviteSDP_.clear();
-        remoteICECandidates_.clear();
+    roomid_.clear();
+    callParty_.clear();
+    callPartyDisplayName_.clear();
+    callPartyAvatarUrl_.clear();
+    callid_.clear();
+    callType_       = CallType::VOICE;
+    haveCallInvite_ = false;
+    emit newInviteState();
+    inviteSDP_.clear();
+    remoteICECandidates_.clear();
 }
 
 void
 CallManager::endCall()
 {
-        stopRingtone();
-        session_.end();
-        clear();
+    stopRingtone();
+    session_.end();
+    clear();
 }
 
 void
 CallManager::refreshTurnServer()
 {
-        turnURIs_.clear();
-        turnServerTimer_.start(2000);
+    turnURIs_.clear();
+    turnServerTimer_.start(2000);
 }
 
 void
 CallManager::retrieveTurnServer()
 {
-        http::client()->get_turn_server(
-          [this](const mtx::responses::TurnServer &res, mtx::http::RequestErr err) {
-                  if (err) {
-                          turnServerTimer_.setInterval(5000);
-                          return;
-                  }
-                  emit turnServerRetrieved(res);
-          });
+    http::client()->get_turn_server(
+      [this](const mtx::responses::TurnServer &res, mtx::http::RequestErr err) {
+          if (err) {
+              turnServerTimer_.setInterval(5000);
+              return;
+          }
+          emit turnServerRetrieved(res);
+      });
 }
 
 void
 CallManager::playRingtone(const QUrl &ringtone, bool repeat)
 {
-        static QMediaPlaylist playlist;
-        playlist.clear();
-        playlist.setPlaybackMode(repeat ? QMediaPlaylist::CurrentItemInLoop
-                                        : QMediaPlaylist::CurrentItemOnce);
-        playlist.addMedia(ringtone);
-        player_.setVolume(100);
-        player_.setPlaylist(&playlist);
+    static QMediaPlaylist playlist;
+    playlist.clear();
+    playlist.setPlaybackMode(repeat ? QMediaPlaylist::CurrentItemInLoop
+                                    : QMediaPlaylist::CurrentItemOnce);
+    playlist.addMedia(ringtone);
+    player_.setVolume(100);
+    player_.setPlaylist(&playlist);
 }
 
 void
 CallManager::stopRingtone()
 {
-        player_.setPlaylist(nullptr);
+    player_.setPlaylist(nullptr);
 }
 
 QStringList
 CallManager::windowList()
 {
-        windows_.clear();
-        windows_.push_back({tr("Entire screen"), 0});
+    windows_.clear();
+    windows_.push_back({tr("Entire screen"), 0});
 
 #ifdef XCB_AVAILABLE
-        std::unique_ptr> connection(
-          xcb_connect(nullptr, nullptr), [](xcb_connection_t *c) { xcb_disconnect(c); });
-        if (xcb_connection_has_error(connection.get())) {
-                nhlog::ui()->error("Failed to connect to X server");
-                return {};
+    std::unique_ptr> connection(
+      xcb_connect(nullptr, nullptr), [](xcb_connection_t *c) { xcb_disconnect(c); });
+    if (xcb_connection_has_error(connection.get())) {
+        nhlog::ui()->error("Failed to connect to X server");
+        return {};
+    }
+
+    xcb_ewmh_connection_t ewmh;
+    if (!xcb_ewmh_init_atoms_replies(
+          &ewmh, xcb_ewmh_init_atoms(connection.get(), &ewmh), nullptr)) {
+        nhlog::ui()->error("Failed to connect to EWMH server");
+        return {};
+    }
+    std::unique_ptr>
+      ewmhconnection(&ewmh, [](xcb_ewmh_connection_t *c) { xcb_ewmh_connection_wipe(c); });
+
+    for (int i = 0; i < ewmh.nb_screens; i++) {
+        xcb_ewmh_get_windows_reply_t clients;
+        if (!xcb_ewmh_get_client_list_reply(
+              &ewmh, xcb_ewmh_get_client_list(&ewmh, i), &clients, nullptr)) {
+            nhlog::ui()->error("Failed to request window list");
+            return {};
         }
 
-        xcb_ewmh_connection_t ewmh;
-        if (!xcb_ewmh_init_atoms_replies(
-              &ewmh, xcb_ewmh_init_atoms(connection.get(), &ewmh), nullptr)) {
-                nhlog::ui()->error("Failed to connect to EWMH server");
-                return {};
-        }
-        std::unique_ptr>
-          ewmhconnection(&ewmh, [](xcb_ewmh_connection_t *c) { xcb_ewmh_connection_wipe(c); });
-
-        for (int i = 0; i < ewmh.nb_screens; i++) {
-                xcb_ewmh_get_windows_reply_t clients;
-                if (!xcb_ewmh_get_client_list_reply(
-                      &ewmh, xcb_ewmh_get_client_list(&ewmh, i), &clients, nullptr)) {
-                        nhlog::ui()->error("Failed to request window list");
-                        return {};
-                }
-
-                for (uint32_t w = 0; w < clients.windows_len; w++) {
-                        xcb_window_t window = clients.windows[w];
-
-                        std::string name;
-                        xcb_ewmh_get_utf8_strings_reply_t data;
-                        auto getName = [](xcb_ewmh_get_utf8_strings_reply_t *r) {
-                                std::string name(r->strings, r->strings_len);
-                                xcb_ewmh_get_utf8_strings_reply_wipe(r);
-                                return name;
-                        };
-
-                        xcb_get_property_cookie_t cookie = xcb_ewmh_get_wm_name(&ewmh, window);
-                        if (xcb_ewmh_get_wm_name_reply(&ewmh, cookie, &data, nullptr))
-                                name = getName(&data);
-
-                        cookie = xcb_ewmh_get_wm_visible_name(&ewmh, window);
-                        if (xcb_ewmh_get_wm_visible_name_reply(&ewmh, cookie, &data, nullptr))
-                                name = getName(&data);
-
-                        windows_.push_back({QString::fromStdString(name), window});
-                }
-                xcb_ewmh_get_windows_reply_wipe(&clients);
+        for (uint32_t w = 0; w < clients.windows_len; w++) {
+            xcb_window_t window = clients.windows[w];
+
+            std::string name;
+            xcb_ewmh_get_utf8_strings_reply_t data;
+            auto getName = [](xcb_ewmh_get_utf8_strings_reply_t *r) {
+                std::string name(r->strings, r->strings_len);
+                xcb_ewmh_get_utf8_strings_reply_wipe(r);
+                return name;
+            };
+
+            xcb_get_property_cookie_t cookie = xcb_ewmh_get_wm_name(&ewmh, window);
+            if (xcb_ewmh_get_wm_name_reply(&ewmh, cookie, &data, nullptr))
+                name = getName(&data);
+
+            cookie = xcb_ewmh_get_wm_visible_name(&ewmh, window);
+            if (xcb_ewmh_get_wm_visible_name_reply(&ewmh, cookie, &data, nullptr))
+                name = getName(&data);
+
+            windows_.push_back({QString::fromStdString(name), window});
         }
+        xcb_ewmh_get_windows_reply_wipe(&clients);
+    }
 #endif
-        QStringList ret;
-        ret.reserve(windows_.size());
-        for (const auto &w : windows_)
-                ret.append(w.first);
+    QStringList ret;
+    ret.reserve(windows_.size());
+    for (const auto &w : windows_)
+        ret.append(w.first);
 
-        return ret;
+    return ret;
 }
 
 #ifdef GSTREAMER_AVAILABLE
@@ -591,22 +578,22 @@ unsigned int busWatchId_ = 0;
 gboolean
 newBusMessage(GstBus *bus G_GNUC_UNUSED, GstMessage *msg, gpointer G_GNUC_UNUSED)
 {
-        switch (GST_MESSAGE_TYPE(msg)) {
-        case GST_MESSAGE_EOS:
-                if (pipe_) {
-                        gst_element_set_state(GST_ELEMENT(pipe_), GST_STATE_NULL);
-                        gst_object_unref(pipe_);
-                        pipe_ = nullptr;
-                }
-                if (busWatchId_) {
-                        g_source_remove(busWatchId_);
-                        busWatchId_ = 0;
-                }
-                break;
-        default:
-                break;
+    switch (GST_MESSAGE_TYPE(msg)) {
+    case GST_MESSAGE_EOS:
+        if (pipe_) {
+            gst_element_set_state(GST_ELEMENT(pipe_), GST_STATE_NULL);
+            gst_object_unref(pipe_);
+            pipe_ = nullptr;
         }
-        return TRUE;
+        if (busWatchId_) {
+            g_source_remove(busWatchId_);
+            busWatchId_ = 0;
+        }
+        break;
+    default:
+        break;
+    }
+    return TRUE;
 }
 }
 #endif
@@ -615,50 +602,50 @@ void
 CallManager::previewWindow(unsigned int index) const
 {
 #ifdef GSTREAMER_AVAILABLE
-        if (windows_.empty() || index >= windows_.size() || !gst_is_initialized())
-                return;
+    if (windows_.empty() || index >= windows_.size() || !gst_is_initialized())
+        return;
 
-        GstElement *ximagesrc = gst_element_factory_make("ximagesrc", nullptr);
-        if (!ximagesrc) {
-                nhlog::ui()->error("Failed to create ximagesrc");
-                return;
-        }
-        GstElement *videoconvert = gst_element_factory_make("videoconvert", nullptr);
-        GstElement *videoscale   = gst_element_factory_make("videoscale", nullptr);
-        GstElement *capsfilter   = gst_element_factory_make("capsfilter", nullptr);
-        GstElement *ximagesink   = gst_element_factory_make("ximagesink", nullptr);
+    GstElement *ximagesrc = gst_element_factory_make("ximagesrc", nullptr);
+    if (!ximagesrc) {
+        nhlog::ui()->error("Failed to create ximagesrc");
+        return;
+    }
+    GstElement *videoconvert = gst_element_factory_make("videoconvert", nullptr);
+    GstElement *videoscale   = gst_element_factory_make("videoscale", nullptr);
+    GstElement *capsfilter   = gst_element_factory_make("capsfilter", nullptr);
+    GstElement *ximagesink   = gst_element_factory_make("ximagesink", nullptr);
 
-        g_object_set(ximagesrc, "use-damage", FALSE, nullptr);
-        g_object_set(ximagesrc, "show-pointer", FALSE, nullptr);
-        g_object_set(ximagesrc, "xid", windows_[index].second, nullptr);
+    g_object_set(ximagesrc, "use-damage", FALSE, nullptr);
+    g_object_set(ximagesrc, "show-pointer", FALSE, nullptr);
+    g_object_set(ximagesrc, "xid", windows_[index].second, nullptr);
 
-        GstCaps *caps = gst_caps_new_simple(
-          "video/x-raw", "width", G_TYPE_INT, 480, "height", G_TYPE_INT, 360, nullptr);
-        g_object_set(capsfilter, "caps", caps, nullptr);
-        gst_caps_unref(caps);
+    GstCaps *caps = gst_caps_new_simple(
+      "video/x-raw", "width", G_TYPE_INT, 480, "height", G_TYPE_INT, 360, nullptr);
+    g_object_set(capsfilter, "caps", caps, nullptr);
+    gst_caps_unref(caps);
 
-        pipe_ = gst_pipeline_new(nullptr);
-        gst_bin_add_many(
-          GST_BIN(pipe_), ximagesrc, videoconvert, videoscale, capsfilter, ximagesink, nullptr);
-        if (!gst_element_link_many(
-              ximagesrc, videoconvert, videoscale, capsfilter, ximagesink, nullptr)) {
-                nhlog::ui()->error("Failed to link preview window elements");
-                gst_object_unref(pipe_);
-                pipe_ = nullptr;
-                return;
-        }
-        if (gst_element_set_state(pipe_, GST_STATE_PLAYING) == GST_STATE_CHANGE_FAILURE) {
-                nhlog::ui()->error("Unable to start preview pipeline");
-                gst_object_unref(pipe_);
-                pipe_ = nullptr;
-                return;
-        }
+    pipe_ = gst_pipeline_new(nullptr);
+    gst_bin_add_many(
+      GST_BIN(pipe_), ximagesrc, videoconvert, videoscale, capsfilter, ximagesink, nullptr);
+    if (!gst_element_link_many(
+          ximagesrc, videoconvert, videoscale, capsfilter, ximagesink, nullptr)) {
+        nhlog::ui()->error("Failed to link preview window elements");
+        gst_object_unref(pipe_);
+        pipe_ = nullptr;
+        return;
+    }
+    if (gst_element_set_state(pipe_, GST_STATE_PLAYING) == GST_STATE_CHANGE_FAILURE) {
+        nhlog::ui()->error("Unable to start preview pipeline");
+        gst_object_unref(pipe_);
+        pipe_ = nullptr;
+        return;
+    }
 
-        GstBus *bus = gst_pipeline_get_bus(GST_PIPELINE(pipe_));
-        busWatchId_ = gst_bus_add_watch(bus, newBusMessage, nullptr);
-        gst_object_unref(bus);
+    GstBus *bus = gst_pipeline_get_bus(GST_PIPELINE(pipe_));
+    busWatchId_ = gst_bus_add_watch(bus, newBusMessage, nullptr);
+    gst_object_unref(bus);
 #else
-        (void)index;
+    (void)index;
 #endif
 }
 
@@ -666,29 +653,28 @@ namespace {
 std::vector
 getTurnURIs(const mtx::responses::TurnServer &turnServer)
 {
-        // gstreamer expects: turn(s)://username:password@host:port?transport=udp(tcp)
-        // where username and password are percent-encoded
-        std::vector ret;
-        for (const auto &uri : turnServer.uris) {
-                if (auto c = uri.find(':'); c == std::string::npos) {
-                        nhlog::ui()->error("Invalid TURN server uri: {}", uri);
-                        continue;
-                } else {
-                        std::string scheme = std::string(uri, 0, c);
-                        if (scheme != "turn" && scheme != "turns") {
-                                nhlog::ui()->error("Invalid TURN server uri: {}", uri);
-                                continue;
-                        }
+    // gstreamer expects: turn(s)://username:password@host:port?transport=udp(tcp)
+    // where username and password are percent-encoded
+    std::vector ret;
+    for (const auto &uri : turnServer.uris) {
+        if (auto c = uri.find(':'); c == std::string::npos) {
+            nhlog::ui()->error("Invalid TURN server uri: {}", uri);
+            continue;
+        } else {
+            std::string scheme = std::string(uri, 0, c);
+            if (scheme != "turn" && scheme != "turns") {
+                nhlog::ui()->error("Invalid TURN server uri: {}", uri);
+                continue;
+            }
 
-                        QString encodedUri =
-                          QString::fromStdString(scheme) + "://" +
-                          QUrl::toPercentEncoding(QString::fromStdString(turnServer.username)) +
-                          ":" +
-                          QUrl::toPercentEncoding(QString::fromStdString(turnServer.password)) +
-                          "@" + QString::fromStdString(std::string(uri, ++c));
-                        ret.push_back(encodedUri.toStdString());
-                }
+            QString encodedUri =
+              QString::fromStdString(scheme) + "://" +
+              QUrl::toPercentEncoding(QString::fromStdString(turnServer.username)) + ":" +
+              QUrl::toPercentEncoding(QString::fromStdString(turnServer.password)) + "@" +
+              QString::fromStdString(std::string(uri, ++c));
+            ret.push_back(encodedUri.toStdString());
         }
-        return ret;
+    }
+    return ret;
 }
 }
diff --git a/src/CallManager.h b/src/CallManager.h
index 407b8366..22f31814 100644
--- a/src/CallManager.h
+++ b/src/CallManager.h
@@ -26,93 +26,92 @@ class QUrl;
 
 class CallManager : public QObject
 {
-        Q_OBJECT
-        Q_PROPERTY(bool haveCallInvite READ haveCallInvite NOTIFY newInviteState)
-        Q_PROPERTY(bool isOnCall READ isOnCall NOTIFY newCallState)
-        Q_PROPERTY(webrtc::CallType callType READ callType NOTIFY newInviteState)
-        Q_PROPERTY(webrtc::State callState READ callState NOTIFY newCallState)
-        Q_PROPERTY(QString callParty READ callParty NOTIFY newInviteState)
-        Q_PROPERTY(QString callPartyDisplayName READ callPartyDisplayName NOTIFY newInviteState)
-        Q_PROPERTY(QString callPartyAvatarUrl READ callPartyAvatarUrl NOTIFY newInviteState)
-        Q_PROPERTY(bool isMicMuted READ isMicMuted NOTIFY micMuteChanged)
-        Q_PROPERTY(bool haveLocalPiP READ haveLocalPiP NOTIFY newCallState)
-        Q_PROPERTY(QStringList mics READ mics NOTIFY devicesChanged)
-        Q_PROPERTY(QStringList cameras READ cameras NOTIFY devicesChanged)
-        Q_PROPERTY(bool callsSupported READ callsSupported CONSTANT)
-        Q_PROPERTY(bool screenShareSupported READ screenShareSupported CONSTANT)
+    Q_OBJECT
+    Q_PROPERTY(bool haveCallInvite READ haveCallInvite NOTIFY newInviteState)
+    Q_PROPERTY(bool isOnCall READ isOnCall NOTIFY newCallState)
+    Q_PROPERTY(webrtc::CallType callType READ callType NOTIFY newInviteState)
+    Q_PROPERTY(webrtc::State callState READ callState NOTIFY newCallState)
+    Q_PROPERTY(QString callParty READ callParty NOTIFY newInviteState)
+    Q_PROPERTY(QString callPartyDisplayName READ callPartyDisplayName NOTIFY newInviteState)
+    Q_PROPERTY(QString callPartyAvatarUrl READ callPartyAvatarUrl NOTIFY newInviteState)
+    Q_PROPERTY(bool isMicMuted READ isMicMuted NOTIFY micMuteChanged)
+    Q_PROPERTY(bool haveLocalPiP READ haveLocalPiP NOTIFY newCallState)
+    Q_PROPERTY(QStringList mics READ mics NOTIFY devicesChanged)
+    Q_PROPERTY(QStringList cameras READ cameras NOTIFY devicesChanged)
+    Q_PROPERTY(bool callsSupported READ callsSupported CONSTANT)
+    Q_PROPERTY(bool screenShareSupported READ screenShareSupported CONSTANT)
 
 public:
-        CallManager(QObject *);
+    CallManager(QObject *);
 
-        bool haveCallInvite() const { return haveCallInvite_; }
-        bool isOnCall() const { return session_.state() != webrtc::State::DISCONNECTED; }
-        webrtc::CallType callType() const { return callType_; }
-        webrtc::State callState() const { return session_.state(); }
-        QString callParty() const { return callParty_; }
-        QString callPartyDisplayName() const { return callPartyDisplayName_; }
-        QString callPartyAvatarUrl() const { return callPartyAvatarUrl_; }
-        bool isMicMuted() const { return session_.isMicMuted(); }
-        bool haveLocalPiP() const { return session_.haveLocalPiP(); }
-        QStringList mics() const { return devices(false); }
-        QStringList cameras() const { return devices(true); }
-        void refreshTurnServer();
+    bool haveCallInvite() const { return haveCallInvite_; }
+    bool isOnCall() const { return session_.state() != webrtc::State::DISCONNECTED; }
+    webrtc::CallType callType() const { return callType_; }
+    webrtc::State callState() const { return session_.state(); }
+    QString callParty() const { return callParty_; }
+    QString callPartyDisplayName() const { return callPartyDisplayName_; }
+    QString callPartyAvatarUrl() const { return callPartyAvatarUrl_; }
+    bool isMicMuted() const { return session_.isMicMuted(); }
+    bool haveLocalPiP() const { return session_.haveLocalPiP(); }
+    QStringList mics() const { return devices(false); }
+    QStringList cameras() const { return devices(true); }
+    void refreshTurnServer();
 
-        static bool callsSupported();
-        static bool screenShareSupported();
+    static bool callsSupported();
+    static bool screenShareSupported();
 
 public slots:
-        void sendInvite(const QString &roomid, webrtc::CallType, unsigned int windowIndex = 0);
-        void syncEvent(const mtx::events::collections::TimelineEvents &event);
-        void toggleMicMute();
-        void toggleLocalPiP() { session_.toggleLocalPiP(); }
-        void acceptInvite();
-        void hangUp(
-          mtx::events::msg::CallHangUp::Reason = mtx::events::msg::CallHangUp::Reason::User);
-        QStringList windowList();
-        void previewWindow(unsigned int windowIndex) const;
+    void sendInvite(const QString &roomid, webrtc::CallType, unsigned int windowIndex = 0);
+    void syncEvent(const mtx::events::collections::TimelineEvents &event);
+    void toggleMicMute();
+    void toggleLocalPiP() { session_.toggleLocalPiP(); }
+    void acceptInvite();
+    void hangUp(mtx::events::msg::CallHangUp::Reason = mtx::events::msg::CallHangUp::Reason::User);
+    QStringList windowList();
+    void previewWindow(unsigned int windowIndex) const;
 
 signals:
-        void newMessage(const QString &roomid, const mtx::events::msg::CallInvite &);
-        void newMessage(const QString &roomid, const mtx::events::msg::CallCandidates &);
-        void newMessage(const QString &roomid, const mtx::events::msg::CallAnswer &);
-        void newMessage(const QString &roomid, const mtx::events::msg::CallHangUp &);
-        void newInviteState();
-        void newCallState();
-        void micMuteChanged();
-        void devicesChanged();
-        void turnServerRetrieved(const mtx::responses::TurnServer &);
+    void newMessage(const QString &roomid, const mtx::events::msg::CallInvite &);
+    void newMessage(const QString &roomid, const mtx::events::msg::CallCandidates &);
+    void newMessage(const QString &roomid, const mtx::events::msg::CallAnswer &);
+    void newMessage(const QString &roomid, const mtx::events::msg::CallHangUp &);
+    void newInviteState();
+    void newCallState();
+    void micMuteChanged();
+    void devicesChanged();
+    void turnServerRetrieved(const mtx::responses::TurnServer &);
 
 private slots:
-        void retrieveTurnServer();
+    void retrieveTurnServer();
 
 private:
-        WebRTCSession &session_;
-        QString roomid_;
-        QString callParty_;
-        QString callPartyDisplayName_;
-        QString callPartyAvatarUrl_;
-        std::string callid_;
-        const uint32_t timeoutms_  = 120000;
-        webrtc::CallType callType_ = webrtc::CallType::VOICE;
-        bool haveCallInvite_       = false;
-        std::string inviteSDP_;
-        std::vector remoteICECandidates_;
-        std::vector turnURIs_;
-        QTimer turnServerTimer_;
-        QMediaPlayer player_;
-        std::vector> windows_;
+    WebRTCSession &session_;
+    QString roomid_;
+    QString callParty_;
+    QString callPartyDisplayName_;
+    QString callPartyAvatarUrl_;
+    std::string callid_;
+    const uint32_t timeoutms_  = 120000;
+    webrtc::CallType callType_ = webrtc::CallType::VOICE;
+    bool haveCallInvite_       = false;
+    std::string inviteSDP_;
+    std::vector remoteICECandidates_;
+    std::vector turnURIs_;
+    QTimer turnServerTimer_;
+    QMediaPlayer player_;
+    std::vector> windows_;
 
-        template
-        bool handleEvent(const mtx::events::collections::TimelineEvents &event);
-        void handleEvent(const mtx::events::RoomEvent &);
-        void handleEvent(const mtx::events::RoomEvent &);
-        void handleEvent(const mtx::events::RoomEvent &);
-        void handleEvent(const mtx::events::RoomEvent &);
-        void answerInvite(const mtx::events::msg::CallInvite &, bool isVideo);
-        void generateCallID();
-        QStringList devices(bool isVideo) const;
-        void clear();
-        void endCall();
-        void playRingtone(const QUrl &ringtone, bool repeat);
-        void stopRingtone();
+    template
+    bool handleEvent(const mtx::events::collections::TimelineEvents &event);
+    void handleEvent(const mtx::events::RoomEvent &);
+    void handleEvent(const mtx::events::RoomEvent &);
+    void handleEvent(const mtx::events::RoomEvent &);
+    void handleEvent(const mtx::events::RoomEvent &);
+    void answerInvite(const mtx::events::msg::CallInvite &, bool isVideo);
+    void generateCallID();
+    QStringList devices(bool isVideo) const;
+    void clear();
+    void endCall();
+    void playRingtone(const QUrl &ringtone, bool repeat);
+    void stopRingtone();
 };
diff --git a/src/ChatPage.cpp b/src/ChatPage.cpp
index 3887f8b8..673f39ee 100644
--- a/src/ChatPage.cpp
+++ b/src/ChatPage.cpp
@@ -50,660 +50,641 @@ ChatPage::ChatPage(QSharedPointer userSettings, QWidget *parent)
   , notificationsManager(this)
   , callManager_(new CallManager(this))
 {
-        setObjectName("chatPage");
+    setObjectName("chatPage");
 
-        instance_ = this;
+    instance_ = this;
 
-        qRegisterMetaType>();
-        qRegisterMetaType>();
-        qRegisterMetaType();
-        qRegisterMetaType();
-        qRegisterMetaType();
+    qRegisterMetaType>();
+    qRegisterMetaType>();
+    qRegisterMetaType();
+    qRegisterMetaType();
+    qRegisterMetaType();
 
-        topLayout_ = new QHBoxLayout(this);
-        topLayout_->setSpacing(0);
-        topLayout_->setMargin(0);
+    topLayout_ = new QHBoxLayout(this);
+    topLayout_->setSpacing(0);
+    topLayout_->setMargin(0);
 
-        view_manager_ = new TimelineViewManager(callManager_, this);
+    view_manager_ = new TimelineViewManager(callManager_, this);
 
-        topLayout_->addWidget(view_manager_->getWidget());
+    topLayout_->addWidget(view_manager_->getWidget());
 
-        connect(this,
-                &ChatPage::downloadedSecrets,
-                this,
-                &ChatPage::decryptDownloadedSecrets,
-                Qt::QueuedConnection);
+    connect(this,
+            &ChatPage::downloadedSecrets,
+            this,
+            &ChatPage::decryptDownloadedSecrets,
+            Qt::QueuedConnection);
 
-        connect(this, &ChatPage::connectionLost, this, [this]() {
-                nhlog::net()->info("connectivity lost");
-                isConnected_ = false;
-                http::client()->shutdown();
-        });
-        connect(this, &ChatPage::connectionRestored, this, [this]() {
-                nhlog::net()->info("trying to re-connect");
-                isConnected_ = true;
+    connect(this, &ChatPage::connectionLost, this, [this]() {
+        nhlog::net()->info("connectivity lost");
+        isConnected_ = false;
+        http::client()->shutdown();
+    });
+    connect(this, &ChatPage::connectionRestored, this, [this]() {
+        nhlog::net()->info("trying to re-connect");
+        isConnected_ = true;
 
-                // Drop all pending connections.
-                http::client()->shutdown();
-                trySync();
-        });
+        // Drop all pending connections.
+        http::client()->shutdown();
+        trySync();
+    });
 
-        connectivityTimer_.setInterval(CHECK_CONNECTIVITY_INTERVAL);
-        connect(&connectivityTimer_, &QTimer::timeout, this, [=]() {
-                if (http::client()->access_token().empty()) {
-                        connectivityTimer_.stop();
-                        return;
-                }
+    connectivityTimer_.setInterval(CHECK_CONNECTIVITY_INTERVAL);
+    connect(&connectivityTimer_, &QTimer::timeout, this, [=]() {
+        if (http::client()->access_token().empty()) {
+            connectivityTimer_.stop();
+            return;
+        }
 
-                http::client()->versions(
-                  [this](const mtx::responses::Versions &, mtx::http::RequestErr err) {
-                          if (err) {
-                                  emit connectionLost();
-                                  return;
-                          }
+        http::client()->versions(
+          [this](const mtx::responses::Versions &, mtx::http::RequestErr err) {
+              if (err) {
+                  emit connectionLost();
+                  return;
+              }
 
-                          if (!isConnected_)
-                                  emit connectionRestored();
-                  });
-        });
-
-        connect(this, &ChatPage::loggedOut, this, &ChatPage::logout);
-
-        connect(
-          view_manager_,
-          &TimelineViewManager::inviteUsers,
-          this,
-          [this](QString roomId, QStringList users) {
-                  for (int ii = 0; ii < users.size(); ++ii) {
-                          QTimer::singleShot(ii * 500, this, [this, roomId, ii, users]() {
-                                  const auto user = users.at(ii);
-
-                                  http::client()->invite_user(
-                                    roomId.toStdString(),
-                                    user.toStdString(),
-                                    [this, user](const mtx::responses::RoomInvite &,
-                                                 mtx::http::RequestErr err) {
-                                            if (err) {
-                                                    emit showNotification(
-                                                      tr("Failed to invite user: %1").arg(user));
-                                                    return;
-                                            }
-
-                                            emit showNotification(tr("Invited user: %1").arg(user));
-                                    });
-                          });
-                  }
+              if (!isConnected_)
+                  emit connectionRestored();
           });
+    });
 
-        connect(this, &ChatPage::leftRoom, this, &ChatPage::removeRoom);
-        connect(this, &ChatPage::changeToRoom, this, &ChatPage::changeRoom, Qt::QueuedConnection);
-        connect(this, &ChatPage::notificationsRetrieved, this, &ChatPage::sendNotifications);
-        connect(this,
-                &ChatPage::highlightedNotifsRetrieved,
-                this,
-                [](const mtx::responses::Notifications ¬if) {
-                        try {
-                                cache::saveTimelineMentions(notif);
-                        } catch (const lmdb::error &e) {
-                                nhlog::db()->error("failed to save mentions: {}", e.what());
+    connect(this, &ChatPage::loggedOut, this, &ChatPage::logout);
+
+    connect(
+      view_manager_,
+      &TimelineViewManager::inviteUsers,
+      this,
+      [this](QString roomId, QStringList users) {
+          for (int ii = 0; ii < users.size(); ++ii) {
+              QTimer::singleShot(ii * 500, this, [this, roomId, ii, users]() {
+                  const auto user = users.at(ii);
+
+                  http::client()->invite_user(
+                    roomId.toStdString(),
+                    user.toStdString(),
+                    [this, user](const mtx::responses::RoomInvite &, mtx::http::RequestErr err) {
+                        if (err) {
+                            emit showNotification(tr("Failed to invite user: %1").arg(user));
+                            return;
                         }
-                });
 
-        connect(¬ificationsManager,
-                &NotificationsManager::notificationClicked,
-                this,
-                [this](const QString &roomid, const QString &eventid) {
-                        Q_UNUSED(eventid)
-                        view_manager_->rooms()->setCurrentRoom(roomid);
-                        activateWindow();
-                });
-        connect(¬ificationsManager,
-                &NotificationsManager::sendNotificationReply,
-                this,
-                [this](const QString &roomid, const QString &eventid, const QString &body) {
-                        view_manager_->rooms()->setCurrentRoom(roomid);
-                        view_manager_->queueReply(roomid, eventid, body);
-                        activateWindow();
-                });
+                        emit showNotification(tr("Invited user: %1").arg(user));
+                    });
+              });
+          }
+      });
 
-        connect(QCoreApplication::instance(), &QCoreApplication::aboutToQuit, this, [this]() {
-                // ensure the qml context is shutdown before we destroy all other singletons
-                // Otherwise Qml will try to access the room list or settings, after they have been
-                // destroyed
-                topLayout_->removeWidget(view_manager_->getWidget());
-                delete view_manager_->getWidget();
-        });
-
-        connect(
-          this,
-          &ChatPage::initializeViews,
-          view_manager_,
-          [this](const mtx::responses::Rooms &rooms) { view_manager_->sync(rooms); },
-          Qt::QueuedConnection);
-        connect(this,
-                &ChatPage::initializeEmptyViews,
-                view_manager_,
-                &TimelineViewManager::initializeRoomlist);
-        connect(
-          this, &ChatPage::chatFocusChanged, view_manager_, &TimelineViewManager::chatFocusChanged);
-        connect(this, &ChatPage::syncUI, this, [this](const mtx::responses::Rooms &rooms) {
-                view_manager_->sync(rooms);
-
-                static unsigned int prevNotificationCount = 0;
-                unsigned int notificationCount            = 0;
-                for (const auto &room : rooms.join) {
-                        notificationCount += room.second.unread_notifications.notification_count;
+    connect(this, &ChatPage::leftRoom, this, &ChatPage::removeRoom);
+    connect(this, &ChatPage::changeToRoom, this, &ChatPage::changeRoom, Qt::QueuedConnection);
+    connect(this, &ChatPage::notificationsRetrieved, this, &ChatPage::sendNotifications);
+    connect(this,
+            &ChatPage::highlightedNotifsRetrieved,
+            this,
+            [](const mtx::responses::Notifications ¬if) {
+                try {
+                    cache::saveTimelineMentions(notif);
+                } catch (const lmdb::error &e) {
+                    nhlog::db()->error("failed to save mentions: {}", e.what());
                 }
+            });
 
-                // HACK: If we had less notifications last time we checked, send an alert if the
-                // user wanted one. Technically, this may cause an alert to be missed if new ones
-                // come in while you are reading old ones. Since the window is almost certainly open
-                // in this edge case, that's probably a non-issue.
-                // TODO: Replace this once we have proper pushrules support. This is a horrible hack
-                if (prevNotificationCount < notificationCount) {
-                        if (userSettings_->hasAlertOnNotification())
-                                QApplication::alert(this);
-                }
-                prevNotificationCount = notificationCount;
+    connect(¬ificationsManager,
+            &NotificationsManager::notificationClicked,
+            this,
+            [this](const QString &roomid, const QString &eventid) {
+                Q_UNUSED(eventid)
+                view_manager_->rooms()->setCurrentRoom(roomid);
+                activateWindow();
+            });
+    connect(¬ificationsManager,
+            &NotificationsManager::sendNotificationReply,
+            this,
+            [this](const QString &roomid, const QString &eventid, const QString &body) {
+                view_manager_->rooms()->setCurrentRoom(roomid);
+                view_manager_->queueReply(roomid, eventid, body);
+                activateWindow();
+            });
 
-                // No need to check amounts for this section, as this function internally checks for
-                // duplicates.
-                if (notificationCount && userSettings_->hasNotifications())
-                        http::client()->notifications(
-                          5,
-                          "",
-                          "",
-                          [this](const mtx::responses::Notifications &res,
-                                 mtx::http::RequestErr err) {
-                                  if (err) {
-                                          nhlog::net()->warn(
-                                            "failed to retrieve notifications: {} ({})",
-                                            err->matrix_error.error,
-                                            static_cast(err->status_code));
-                                          return;
-                                  }
+    connect(QCoreApplication::instance(), &QCoreApplication::aboutToQuit, this, [this]() {
+        // ensure the qml context is shutdown before we destroy all other singletons
+        // Otherwise Qml will try to access the room list or settings, after they have been
+        // destroyed
+        topLayout_->removeWidget(view_manager_->getWidget());
+        delete view_manager_->getWidget();
+    });
 
-                                  emit notificationsRetrieved(std::move(res));
-                          });
-        });
+    connect(
+      this,
+      &ChatPage::initializeViews,
+      view_manager_,
+      [this](const mtx::responses::Rooms &rooms) { view_manager_->sync(rooms); },
+      Qt::QueuedConnection);
+    connect(this,
+            &ChatPage::initializeEmptyViews,
+            view_manager_,
+            &TimelineViewManager::initializeRoomlist);
+    connect(
+      this, &ChatPage::chatFocusChanged, view_manager_, &TimelineViewManager::chatFocusChanged);
+    connect(this, &ChatPage::syncUI, this, [this](const mtx::responses::Rooms &rooms) {
+        view_manager_->sync(rooms);
 
-        connect(
-          this, &ChatPage::tryInitialSyncCb, this, &ChatPage::tryInitialSync, Qt::QueuedConnection);
-        connect(this, &ChatPage::trySyncCb, this, &ChatPage::trySync, Qt::QueuedConnection);
-        connect(
-          this,
-          &ChatPage::tryDelayedSyncCb,
-          this,
-          [this]() { QTimer::singleShot(RETRY_TIMEOUT, this, &ChatPage::trySync); },
-          Qt::QueuedConnection);
+        static unsigned int prevNotificationCount = 0;
+        unsigned int notificationCount            = 0;
+        for (const auto &room : rooms.join) {
+            notificationCount += room.second.unread_notifications.notification_count;
+        }
 
-        connect(this,
-                &ChatPage::newSyncResponse,
-                this,
-                &ChatPage::handleSyncResponse,
-                Qt::QueuedConnection);
+        // HACK: If we had less notifications last time we checked, send an alert if the
+        // user wanted one. Technically, this may cause an alert to be missed if new ones
+        // come in while you are reading old ones. Since the window is almost certainly open
+        // in this edge case, that's probably a non-issue.
+        // TODO: Replace this once we have proper pushrules support. This is a horrible hack
+        if (prevNotificationCount < notificationCount) {
+            if (userSettings_->hasAlertOnNotification())
+                QApplication::alert(this);
+        }
+        prevNotificationCount = notificationCount;
 
-        connect(this, &ChatPage::dropToLoginPageCb, this, &ChatPage::dropToLoginPage);
+        // No need to check amounts for this section, as this function internally checks for
+        // duplicates.
+        if (notificationCount && userSettings_->hasNotifications())
+            http::client()->notifications(
+              5,
+              "",
+              "",
+              [this](const mtx::responses::Notifications &res, mtx::http::RequestErr err) {
+                  if (err) {
+                      nhlog::net()->warn("failed to retrieve notifications: {} ({})",
+                                         err->matrix_error.error,
+                                         static_cast(err->status_code));
+                      return;
+                  }
 
-        connectCallMessage();
-        connectCallMessage();
-        connectCallMessage();
-        connectCallMessage();
+                  emit notificationsRetrieved(std::move(res));
+              });
+    });
+
+    connect(
+      this, &ChatPage::tryInitialSyncCb, this, &ChatPage::tryInitialSync, Qt::QueuedConnection);
+    connect(this, &ChatPage::trySyncCb, this, &ChatPage::trySync, Qt::QueuedConnection);
+    connect(
+      this,
+      &ChatPage::tryDelayedSyncCb,
+      this,
+      [this]() { QTimer::singleShot(RETRY_TIMEOUT, this, &ChatPage::trySync); },
+      Qt::QueuedConnection);
+
+    connect(
+      this, &ChatPage::newSyncResponse, this, &ChatPage::handleSyncResponse, Qt::QueuedConnection);
+
+    connect(this, &ChatPage::dropToLoginPageCb, this, &ChatPage::dropToLoginPage);
+
+    connectCallMessage();
+    connectCallMessage();
+    connectCallMessage();
+    connectCallMessage();
 }
 
 void
 ChatPage::logout()
 {
-        resetUI();
-        deleteConfigs();
+    resetUI();
+    deleteConfigs();
 
-        emit closing();
-        connectivityTimer_.stop();
+    emit closing();
+    connectivityTimer_.stop();
 }
 
 void
 ChatPage::dropToLoginPage(const QString &msg)
 {
-        nhlog::ui()->info("dropping to the login page: {}", msg.toStdString());
+    nhlog::ui()->info("dropping to the login page: {}", msg.toStdString());
 
-        http::client()->shutdown();
-        connectivityTimer_.stop();
+    http::client()->shutdown();
+    connectivityTimer_.stop();
 
-        resetUI();
-        deleteConfigs();
+    resetUI();
+    deleteConfigs();
 
-        emit showLoginPage(msg);
+    emit showLoginPage(msg);
 }
 
 void
 ChatPage::resetUI()
 {
-        view_manager_->clearAll();
+    view_manager_->clearAll();
 
-        emit unreadMessages(0);
+    emit unreadMessages(0);
 }
 
 void
 ChatPage::deleteConfigs()
 {
-        auto settings = UserSettings::instance()->qsettings();
+    auto settings = UserSettings::instance()->qsettings();
 
-        if (UserSettings::instance()->profile() != "") {
-                settings->beginGroup("profile");
-                settings->beginGroup(UserSettings::instance()->profile());
-        }
-        settings->beginGroup("auth");
-        settings->remove("");
-        settings->endGroup(); // auth
+    if (UserSettings::instance()->profile() != "") {
+        settings->beginGroup("profile");
+        settings->beginGroup(UserSettings::instance()->profile());
+    }
+    settings->beginGroup("auth");
+    settings->remove("");
+    settings->endGroup(); // auth
 
-        http::client()->shutdown();
-        cache::deleteData();
+    http::client()->shutdown();
+    cache::deleteData();
 }
 
 void
 ChatPage::bootstrap(QString userid, QString homeserver, QString token)
 {
-        using namespace mtx::identifiers;
+    using namespace mtx::identifiers;
 
-        try {
-                http::client()->set_user(parse(userid.toStdString()));
-        } catch (const std::invalid_argument &) {
-                nhlog::ui()->critical("bootstrapped with invalid user_id: {}",
-                                      userid.toStdString());
-        }
+    try {
+        http::client()->set_user(parse(userid.toStdString()));
+    } catch (const std::invalid_argument &) {
+        nhlog::ui()->critical("bootstrapped with invalid user_id: {}", userid.toStdString());
+    }
 
-        http::client()->set_server(homeserver.toStdString());
-        http::client()->set_access_token(token.toStdString());
-        http::client()->verify_certificates(
-          !UserSettings::instance()->disableCertificateValidation());
+    http::client()->set_server(homeserver.toStdString());
+    http::client()->set_access_token(token.toStdString());
+    http::client()->verify_certificates(!UserSettings::instance()->disableCertificateValidation());
 
-        // The Olm client needs the user_id & device_id that will be included
-        // in the generated payloads & keys.
-        olm::client()->set_user_id(http::client()->user_id().to_string());
-        olm::client()->set_device_id(http::client()->device_id());
+    // The Olm client needs the user_id & device_id that will be included
+    // in the generated payloads & keys.
+    olm::client()->set_user_id(http::client()->user_id().to_string());
+    olm::client()->set_device_id(http::client()->device_id());
 
-        try {
-                cache::init(userid);
+    try {
+        cache::init(userid);
 
-                connect(cache::client(),
-                        &Cache::newReadReceipts,
-                        view_manager_,
-                        &TimelineViewManager::updateReadReceipts);
+        connect(cache::client(),
+                &Cache::newReadReceipts,
+                view_manager_,
+                &TimelineViewManager::updateReadReceipts);
 
-                connect(cache::client(),
-                        &Cache::removeNotification,
-                        ¬ificationsManager,
-                        &NotificationsManager::removeNotification);
+        connect(cache::client(),
+                &Cache::removeNotification,
+                ¬ificationsManager,
+                &NotificationsManager::removeNotification);
 
-                const bool isInitialized = cache::isInitialized();
-                const auto cacheVersion  = cache::formatVersion();
+        const bool isInitialized = cache::isInitialized();
+        const auto cacheVersion  = cache::formatVersion();
 
-                callManager_->refreshTurnServer();
+        callManager_->refreshTurnServer();
 
-                if (!isInitialized) {
-                        cache::setCurrentFormat();
-                } else {
-                        if (cacheVersion == cache::CacheVersion::Current) {
-                                loadStateFromCache();
-                                return;
-                        } else if (cacheVersion == cache::CacheVersion::Older) {
-                                if (!cache::runMigrations()) {
-                                        QMessageBox::critical(
-                                          this,
+        if (!isInitialized) {
+            cache::setCurrentFormat();
+        } else {
+            if (cacheVersion == cache::CacheVersion::Current) {
+                loadStateFromCache();
+                return;
+            } else if (cacheVersion == cache::CacheVersion::Older) {
+                if (!cache::runMigrations()) {
+                    QMessageBox::critical(this,
                                           tr("Cache migration failed!"),
                                           tr("Migrating the cache to the current version failed. "
                                              "This can have different reasons. Please open an "
                                              "issue and try to use an older version in the mean "
                                              "time. Alternatively you can try deleting the cache "
                                              "manually."));
-                                        QCoreApplication::quit();
-                                }
-                                loadStateFromCache();
-                                return;
-                        } else if (cacheVersion == cache::CacheVersion::Newer) {
-                                QMessageBox::critical(
-                                  this,
-                                  tr("Incompatible cache version"),
-                                  tr("The cache on your disk is newer than this version of Nheko "
-                                     "supports. Please update or clear your cache."));
-                                QCoreApplication::quit();
-                                return;
-                        }
+                    QCoreApplication::quit();
                 }
-
-        } catch (const lmdb::error &e) {
-                nhlog::db()->critical("failure during boot: {}", e.what());
-                cache::deleteData();
-                nhlog::net()->info("falling back to initial sync");
+                loadStateFromCache();
+                return;
+            } else if (cacheVersion == cache::CacheVersion::Newer) {
+                QMessageBox::critical(
+                  this,
+                  tr("Incompatible cache version"),
+                  tr("The cache on your disk is newer than this version of Nheko "
+                     "supports. Please update or clear your cache."));
+                QCoreApplication::quit();
+                return;
+            }
         }
 
-        try {
-                // It's the first time syncing with this device
-                // There isn't a saved olm account to restore.
-                nhlog::crypto()->info("creating new olm account");
-                olm::client()->create_new_account();
-                cache::saveOlmAccount(olm::client()->save(cache::client()->pickleSecret()));
-        } catch (const lmdb::error &e) {
-                nhlog::crypto()->critical("failed to save olm account {}", e.what());
-                emit dropToLoginPageCb(QString::fromStdString(e.what()));
-                return;
-        } catch (const mtx::crypto::olm_exception &e) {
-                nhlog::crypto()->critical("failed to create new olm account {}", e.what());
-                emit dropToLoginPageCb(QString::fromStdString(e.what()));
-                return;
-        }
+    } catch (const lmdb::error &e) {
+        nhlog::db()->critical("failure during boot: {}", e.what());
+        cache::deleteData();
+        nhlog::net()->info("falling back to initial sync");
+    }
 
-        getProfileInfo();
-        getBackupVersion();
-        tryInitialSync();
+    try {
+        // It's the first time syncing with this device
+        // There isn't a saved olm account to restore.
+        nhlog::crypto()->info("creating new olm account");
+        olm::client()->create_new_account();
+        cache::saveOlmAccount(olm::client()->save(cache::client()->pickleSecret()));
+    } catch (const lmdb::error &e) {
+        nhlog::crypto()->critical("failed to save olm account {}", e.what());
+        emit dropToLoginPageCb(QString::fromStdString(e.what()));
+        return;
+    } catch (const mtx::crypto::olm_exception &e) {
+        nhlog::crypto()->critical("failed to create new olm account {}", e.what());
+        emit dropToLoginPageCb(QString::fromStdString(e.what()));
+        return;
+    }
+
+    getProfileInfo();
+    getBackupVersion();
+    tryInitialSync();
 }
 
 void
 ChatPage::loadStateFromCache()
 {
-        nhlog::db()->info("restoring state from cache");
+    nhlog::db()->info("restoring state from cache");
 
-        try {
-                olm::client()->load(cache::restoreOlmAccount(), cache::client()->pickleSecret());
+    try {
+        olm::client()->load(cache::restoreOlmAccount(), cache::client()->pickleSecret());
 
-                emit initializeEmptyViews();
-                emit initializeMentions(cache::getTimelineMentions());
+        emit initializeEmptyViews();
+        emit initializeMentions(cache::getTimelineMentions());
 
-                cache::calculateRoomReadStatus();
+        cache::calculateRoomReadStatus();
 
-        } catch (const mtx::crypto::olm_exception &e) {
-                nhlog::crypto()->critical("failed to restore olm account: {}", e.what());
-                emit dropToLoginPageCb(tr("Failed to restore OLM account. Please login again."));
-                return;
-        } catch (const lmdb::error &e) {
-                nhlog::db()->critical("failed to restore cache: {}", e.what());
-                emit dropToLoginPageCb(tr("Failed to restore save data. Please login again."));
-                return;
-        } catch (const json::exception &e) {
-                nhlog::db()->critical("failed to parse cache data: {}", e.what());
-                emit dropToLoginPageCb(tr("Failed to restore save data. Please login again."));
-                return;
-        } catch (const std::exception &e) {
-                nhlog::db()->critical("failed to load cache data: {}", e.what());
-                emit dropToLoginPageCb(tr("Failed to restore save data. Please login again."));
-                return;
-        }
+    } catch (const mtx::crypto::olm_exception &e) {
+        nhlog::crypto()->critical("failed to restore olm account: {}", e.what());
+        emit dropToLoginPageCb(tr("Failed to restore OLM account. Please login again."));
+        return;
+    } catch (const lmdb::error &e) {
+        nhlog::db()->critical("failed to restore cache: {}", e.what());
+        emit dropToLoginPageCb(tr("Failed to restore save data. Please login again."));
+        return;
+    } catch (const json::exception &e) {
+        nhlog::db()->critical("failed to parse cache data: {}", e.what());
+        emit dropToLoginPageCb(tr("Failed to restore save data. Please login again."));
+        return;
+    } catch (const std::exception &e) {
+        nhlog::db()->critical("failed to load cache data: {}", e.what());
+        emit dropToLoginPageCb(tr("Failed to restore save data. Please login again."));
+        return;
+    }
 
-        nhlog::crypto()->info("ed25519   : {}", olm::client()->identity_keys().ed25519);
-        nhlog::crypto()->info("curve25519: {}", olm::client()->identity_keys().curve25519);
+    nhlog::crypto()->info("ed25519   : {}", olm::client()->identity_keys().ed25519);
+    nhlog::crypto()->info("curve25519: {}", olm::client()->identity_keys().curve25519);
 
-        getProfileInfo();
-        getBackupVersion();
-        verifyOneTimeKeyCountAfterStartup();
+    getProfileInfo();
+    getBackupVersion();
+    verifyOneTimeKeyCountAfterStartup();
 
-        emit contentLoaded();
+    emit contentLoaded();
 
-        // Start receiving events.
-        emit trySyncCb();
+    // Start receiving events.
+    emit trySyncCb();
 }
 
 void
 ChatPage::removeRoom(const QString &room_id)
 {
-        try {
-                cache::removeRoom(room_id);
-                cache::removeInvite(room_id.toStdString());
-        } catch (const lmdb::error &e) {
-                nhlog::db()->critical("failure while removing room: {}", e.what());
-                // TODO: Notify the user.
-        }
+    try {
+        cache::removeRoom(room_id);
+        cache::removeInvite(room_id.toStdString());
+    } catch (const lmdb::error &e) {
+        nhlog::db()->critical("failure while removing room: {}", e.what());
+        // TODO: Notify the user.
+    }
 }
 
 void
 ChatPage::sendNotifications(const mtx::responses::Notifications &res)
 {
-        for (const auto &item : res.notifications) {
-                const auto event_id = mtx::accessors::event_id(item.event);
+    for (const auto &item : res.notifications) {
+        const auto event_id = mtx::accessors::event_id(item.event);
 
-                try {
-                        if (item.read) {
-                                cache::removeReadNotification(event_id);
-                                continue;
-                        }
+        try {
+            if (item.read) {
+                cache::removeReadNotification(event_id);
+                continue;
+            }
 
-                        if (!cache::isNotificationSent(event_id)) {
-                                const auto room_id = QString::fromStdString(item.room_id);
+            if (!cache::isNotificationSent(event_id)) {
+                const auto room_id = QString::fromStdString(item.room_id);
 
-                                // We should only sent one notification per event.
-                                cache::markSentNotification(event_id);
+                // We should only sent one notification per event.
+                cache::markSentNotification(event_id);
 
-                                // Don't send a notification when the current room is opened.
-                                if (isRoomActive(room_id))
-                                        continue;
+                // Don't send a notification when the current room is opened.
+                if (isRoomActive(room_id))
+                    continue;
 
-                                if (userSettings_->hasDesktopNotifications()) {
-                                        auto info = cache::singleRoomInfo(item.room_id);
+                if (userSettings_->hasDesktopNotifications()) {
+                    auto info = cache::singleRoomInfo(item.room_id);
 
-                                        AvatarProvider::resolve(
-                                          QString::fromStdString(info.avatar_url),
-                                          96,
-                                          this,
-                                          [this, item](QPixmap image) {
-                                                  notificationsManager.postNotification(
-                                                    item, image.toImage());
-                                          });
-                                }
-                        }
-                } catch (const lmdb::error &e) {
-                        nhlog::db()->warn("error while sending notification: {}", e.what());
+                    AvatarProvider::resolve(QString::fromStdString(info.avatar_url),
+                                            96,
+                                            this,
+                                            [this, item](QPixmap image) {
+                                                notificationsManager.postNotification(
+                                                  item, image.toImage());
+                                            });
                 }
+            }
+        } catch (const lmdb::error &e) {
+            nhlog::db()->warn("error while sending notification: {}", e.what());
         }
+    }
 }
 
 void
 ChatPage::tryInitialSync()
 {
-        nhlog::crypto()->info("ed25519   : {}", olm::client()->identity_keys().ed25519);
-        nhlog::crypto()->info("curve25519: {}", olm::client()->identity_keys().curve25519);
+    nhlog::crypto()->info("ed25519   : {}", olm::client()->identity_keys().ed25519);
+    nhlog::crypto()->info("curve25519: {}", olm::client()->identity_keys().curve25519);
 
-        // Upload one time keys for the device.
-        nhlog::crypto()->info("generating one time keys");
-        olm::client()->generate_one_time_keys(MAX_ONETIME_KEYS);
+    // Upload one time keys for the device.
+    nhlog::crypto()->info("generating one time keys");
+    olm::client()->generate_one_time_keys(MAX_ONETIME_KEYS);
 
-        http::client()->upload_keys(
-          olm::client()->create_upload_keys_request(),
-          [this](const mtx::responses::UploadKeys &res, mtx::http::RequestErr err) {
-                  if (err) {
-                          const int status_code = static_cast(err->status_code);
+    http::client()->upload_keys(
+      olm::client()->create_upload_keys_request(),
+      [this](const mtx::responses::UploadKeys &res, mtx::http::RequestErr err) {
+          if (err) {
+              const int status_code = static_cast(err->status_code);
 
-                          if (status_code == 404) {
-                                  nhlog::net()->warn(
-                                    "skipping key uploading. server doesn't provide /keys/upload");
-                                  return startInitialSync();
-                          }
+              if (status_code == 404) {
+                  nhlog::net()->warn("skipping key uploading. server doesn't provide /keys/upload");
+                  return startInitialSync();
+              }
 
-                          nhlog::crypto()->critical("failed to upload one time keys: {} {}",
-                                                    err->matrix_error.error,
-                                                    status_code);
+              nhlog::crypto()->critical(
+                "failed to upload one time keys: {} {}", err->matrix_error.error, status_code);
 
-                          QString errorMsg(tr("Failed to setup encryption keys. Server response: "
-                                              "%1 %2. Please try again later.")
-                                             .arg(QString::fromStdString(err->matrix_error.error))
-                                             .arg(status_code));
+              QString errorMsg(tr("Failed to setup encryption keys. Server response: "
+                                  "%1 %2. Please try again later.")
+                                 .arg(QString::fromStdString(err->matrix_error.error))
+                                 .arg(status_code));
 
-                          emit dropToLoginPageCb(errorMsg);
-                          return;
-                  }
+              emit dropToLoginPageCb(errorMsg);
+              return;
+          }
 
-                  olm::mark_keys_as_published();
+          olm::mark_keys_as_published();
 
-                  for (const auto &entry : res.one_time_key_counts)
-                          nhlog::net()->info(
-                            "uploaded {} {} one-time keys", entry.second, entry.first);
+          for (const auto &entry : res.one_time_key_counts)
+              nhlog::net()->info("uploaded {} {} one-time keys", entry.second, entry.first);
 
-                  startInitialSync();
-          });
+          startInitialSync();
+      });
 }
 
 void
 ChatPage::startInitialSync()
 {
-        nhlog::net()->info("trying initial sync");
+    nhlog::net()->info("trying initial sync");
 
-        mtx::http::SyncOpts opts;
-        opts.timeout      = 0;
-        opts.set_presence = currentPresence();
+    mtx::http::SyncOpts opts;
+    opts.timeout      = 0;
+    opts.set_presence = currentPresence();
 
-        http::client()->sync(
-          opts, [this](const mtx::responses::Sync &res, mtx::http::RequestErr err) {
-                  // TODO: Initial Sync should include mentions as well...
+    http::client()->sync(opts, [this](const mtx::responses::Sync &res, mtx::http::RequestErr err) {
+        // TODO: Initial Sync should include mentions as well...
 
-                  if (err) {
-                          const auto error      = QString::fromStdString(err->matrix_error.error);
-                          const auto msg        = tr("Please try to login again: %1").arg(error);
-                          const auto err_code   = mtx::errors::to_string(err->matrix_error.errcode);
-                          const int status_code = static_cast(err->status_code);
+        if (err) {
+            const auto error      = QString::fromStdString(err->matrix_error.error);
+            const auto msg        = tr("Please try to login again: %1").arg(error);
+            const auto err_code   = mtx::errors::to_string(err->matrix_error.errcode);
+            const int status_code = static_cast(err->status_code);
 
-                          nhlog::net()->error("initial sync error: {} {} {} {}",
-                                              err->parse_error,
-                                              status_code,
-                                              err->error_code,
-                                              err_code);
+            nhlog::net()->error("initial sync error: {} {} {} {}",
+                                err->parse_error,
+                                status_code,
+                                err->error_code,
+                                err_code);
 
-                          // non http related errors
-                          if (status_code <= 0 || status_code >= 600) {
-                                  startInitialSync();
-                                  return;
-                          }
+            // non http related errors
+            if (status_code <= 0 || status_code >= 600) {
+                startInitialSync();
+                return;
+            }
 
-                          switch (status_code) {
-                          case 502:
-                          case 504:
-                          case 524: {
-                                  startInitialSync();
-                                  return;
-                          }
-                          default: {
-                                  emit dropToLoginPageCb(msg);
-                                  return;
-                          }
-                          }
-                  }
+            switch (status_code) {
+            case 502:
+            case 504:
+            case 524: {
+                startInitialSync();
+                return;
+            }
+            default: {
+                emit dropToLoginPageCb(msg);
+                return;
+            }
+            }
+        }
 
-                  nhlog::net()->info("initial sync completed");
+        nhlog::net()->info("initial sync completed");
 
-                  try {
-                          cache::client()->saveState(res);
+        try {
+            cache::client()->saveState(res);
 
-                          olm::handle_to_device_messages(res.to_device.events);
+            olm::handle_to_device_messages(res.to_device.events);
 
-                          emit initializeViews(std::move(res.rooms));
-                          emit initializeMentions(cache::getTimelineMentions());
+            emit initializeViews(std::move(res.rooms));
+            emit initializeMentions(cache::getTimelineMentions());
 
-                          cache::calculateRoomReadStatus();
-                  } catch (const lmdb::error &e) {
-                          nhlog::db()->error("failed to save state after initial sync: {}",
-                                             e.what());
-                          startInitialSync();
-                          return;
-                  }
+            cache::calculateRoomReadStatus();
+        } catch (const lmdb::error &e) {
+            nhlog::db()->error("failed to save state after initial sync: {}", e.what());
+            startInitialSync();
+            return;
+        }
 
-                  emit trySyncCb();
-                  emit contentLoaded();
-          });
+        emit trySyncCb();
+        emit contentLoaded();
+    });
 }
 
 void
 ChatPage::handleSyncResponse(const mtx::responses::Sync &res, const std::string &prev_batch_token)
 {
-        try {
-                if (prev_batch_token != cache::nextBatchToken()) {
-                        nhlog::net()->warn("Duplicate sync, dropping");
-                        return;
-                }
-        } catch (const lmdb::error &e) {
-                nhlog::db()->warn("Logged out in the mean time, dropping sync");
+    try {
+        if (prev_batch_token != cache::nextBatchToken()) {
+            nhlog::net()->warn("Duplicate sync, dropping");
+            return;
         }
+    } catch (const lmdb::error &e) {
+        nhlog::db()->warn("Logged out in the mean time, dropping sync");
+    }
 
-        nhlog::net()->debug("sync completed: {}", res.next_batch);
+    nhlog::net()->debug("sync completed: {}", res.next_batch);
 
-        // Ensure that we have enough one-time keys available.
-        ensureOneTimeKeyCount(res.device_one_time_keys_count);
+    // Ensure that we have enough one-time keys available.
+    ensureOneTimeKeyCount(res.device_one_time_keys_count);
 
-        // TODO: fine grained error handling
-        try {
-                cache::client()->saveState(res);
-                olm::handle_to_device_messages(res.to_device.events);
+    // TODO: fine grained error handling
+    try {
+        cache::client()->saveState(res);
+        olm::handle_to_device_messages(res.to_device.events);
 
-                auto updates = cache::getRoomInfo(cache::client()->roomsWithStateUpdates(res));
+        auto updates = cache::getRoomInfo(cache::client()->roomsWithStateUpdates(res));
 
-                emit syncUI(res.rooms);
+        emit syncUI(res.rooms);
 
-                // if we process a lot of syncs (1 every 200ms), this means we clean the
-                // db every 100s
-                static int syncCounter = 0;
-                if (syncCounter++ >= 500) {
-                        cache::deleteOldData();
-                        syncCounter = 0;
-                }
-        } catch (const lmdb::map_full_error &e) {
-                nhlog::db()->error("lmdb is full: {}", e.what());
-                cache::deleteOldData();
-        } catch (const lmdb::error &e) {
-                nhlog::db()->error("saving sync response: {}", e.what());
+        // if we process a lot of syncs (1 every 200ms), this means we clean the
+        // db every 100s
+        static int syncCounter = 0;
+        if (syncCounter++ >= 500) {
+            cache::deleteOldData();
+            syncCounter = 0;
         }
+    } catch (const lmdb::map_full_error &e) {
+        nhlog::db()->error("lmdb is full: {}", e.what());
+        cache::deleteOldData();
+    } catch (const lmdb::error &e) {
+        nhlog::db()->error("saving sync response: {}", e.what());
+    }
 
-        emit trySyncCb();
+    emit trySyncCb();
 }
 
 void
 ChatPage::trySync()
 {
-        mtx::http::SyncOpts opts;
-        opts.set_presence = currentPresence();
+    mtx::http::SyncOpts opts;
+    opts.set_presence = currentPresence();
 
-        if (!connectivityTimer_.isActive())
-                connectivityTimer_.start();
+    if (!connectivityTimer_.isActive())
+        connectivityTimer_.start();
 
-        try {
-                opts.since = cache::nextBatchToken();
-        } catch (const lmdb::error &e) {
-                nhlog::db()->error("failed to retrieve next batch token: {}", e.what());
-                return;
-        }
+    try {
+        opts.since = cache::nextBatchToken();
+    } catch (const lmdb::error &e) {
+        nhlog::db()->error("failed to retrieve next batch token: {}", e.what());
+        return;
+    }
 
-        http::client()->sync(
-          opts,
-          [this, since = opts.since](const mtx::responses::Sync &res, mtx::http::RequestErr err) {
-                  if (err) {
-                          const auto error      = QString::fromStdString(err->matrix_error.error);
-                          const auto msg        = tr("Please try to login again: %1").arg(error);
-                          const auto err_code   = mtx::errors::to_string(err->matrix_error.errcode);
-                          const int status_code = static_cast(err->status_code);
+    http::client()->sync(
+      opts, [this, since = opts.since](const mtx::responses::Sync &res, mtx::http::RequestErr err) {
+          if (err) {
+              const auto error      = QString::fromStdString(err->matrix_error.error);
+              const auto msg        = tr("Please try to login again: %1").arg(error);
+              const auto err_code   = mtx::errors::to_string(err->matrix_error.errcode);
+              const int status_code = static_cast(err->status_code);
 
-                          if ((http::is_logged_in() &&
-                               (err->matrix_error.errcode ==
-                                  mtx::errors::ErrorCode::M_UNKNOWN_TOKEN ||
-                                err->matrix_error.errcode ==
-                                  mtx::errors::ErrorCode::M_MISSING_TOKEN)) ||
-                              !http::is_logged_in()) {
-                                  emit dropToLoginPageCb(msg);
-                                  return;
-                          }
+              if ((http::is_logged_in() &&
+                   (err->matrix_error.errcode == mtx::errors::ErrorCode::M_UNKNOWN_TOKEN ||
+                    err->matrix_error.errcode == mtx::errors::ErrorCode::M_MISSING_TOKEN)) ||
+                  !http::is_logged_in()) {
+                  emit dropToLoginPageCb(msg);
+                  return;
+              }
 
-                          nhlog::net()->error("sync error: {} {} {} {}",
-                                              err->parse_error,
-                                              status_code,
-                                              err->error_code,
-                                              err_code);
-                          emit tryDelayedSyncCb();
-                          return;
-                  }
+              nhlog::net()->error("sync error: {} {} {} {}",
+                                  err->parse_error,
+                                  status_code,
+                                  err->error_code,
+                                  err_code);
+              emit tryDelayedSyncCb();
+              return;
+          }
 
-                  emit newSyncResponse(res, since);
-          });
+          emit newSyncResponse(res, since);
+      });
 }
 
 void
 ChatPage::joinRoom(const QString &room)
 {
-        const auto room_id = room.toStdString();
-        joinRoomVia(room_id, {}, false);
+    const auto room_id = room.toStdString();
+    joinRoomVia(room_id, {}, false);
 }
 
 void
@@ -711,692 +692,662 @@ ChatPage::joinRoomVia(const std::string &room_id,
                       const std::vector &via,
                       bool promptForConfirmation)
 {
-        if (promptForConfirmation &&
-            QMessageBox::Yes !=
-              QMessageBox::question(
-                this,
-                tr("Confirm join"),
-                tr("Do you really want to join %1?").arg(QString::fromStdString(room_id))))
-                return;
+    if (promptForConfirmation &&
+        QMessageBox::Yes !=
+          QMessageBox::question(
+            this,
+            tr("Confirm join"),
+            tr("Do you really want to join %1?").arg(QString::fromStdString(room_id))))
+        return;
 
-        http::client()->join_room(
-          room_id, via, [this, room_id](const mtx::responses::RoomId &, mtx::http::RequestErr err) {
-                  if (err) {
-                          emit showNotification(
-                            tr("Failed to join room: %1")
-                              .arg(QString::fromStdString(err->matrix_error.error)));
-                          return;
-                  }
+    http::client()->join_room(
+      room_id, via, [this, room_id](const mtx::responses::RoomId &, mtx::http::RequestErr err) {
+          if (err) {
+              emit showNotification(
+                tr("Failed to join room: %1").arg(QString::fromStdString(err->matrix_error.error)));
+              return;
+          }
 
-                  emit tr("You joined the room");
+          emit tr("You joined the room");
 
-                  // We remove any invites with the same room_id.
-                  try {
-                          cache::removeInvite(room_id);
-                  } catch (const lmdb::error &e) {
-                          emit showNotification(tr("Failed to remove invite: %1").arg(e.what()));
-                  }
+          // We remove any invites with the same room_id.
+          try {
+              cache::removeInvite(room_id);
+          } catch (const lmdb::error &e) {
+              emit showNotification(tr("Failed to remove invite: %1").arg(e.what()));
+          }
 
-                  view_manager_->rooms()->setCurrentRoom(QString::fromStdString(room_id));
-          });
+          view_manager_->rooms()->setCurrentRoom(QString::fromStdString(room_id));
+      });
 }
 
 void
 ChatPage::createRoom(const mtx::requests::CreateRoom &req)
 {
-        http::client()->create_room(
-          req, [this](const mtx::responses::CreateRoom &res, mtx::http::RequestErr err) {
-                  if (err) {
-                          const auto err_code   = mtx::errors::to_string(err->matrix_error.errcode);
-                          const auto error      = err->matrix_error.error;
-                          const int status_code = static_cast(err->status_code);
+    http::client()->create_room(
+      req, [this](const mtx::responses::CreateRoom &res, mtx::http::RequestErr err) {
+          if (err) {
+              const auto err_code   = mtx::errors::to_string(err->matrix_error.errcode);
+              const auto error      = err->matrix_error.error;
+              const int status_code = static_cast(err->status_code);
 
-                          nhlog::net()->warn(
-                            "failed to create room: {} {} ({})", error, err_code, status_code);
+              nhlog::net()->warn("failed to create room: {} {} ({})", error, err_code, status_code);
 
-                          emit showNotification(
-                            tr("Room creation failed: %1").arg(QString::fromStdString(error)));
-                          return;
-                  }
+              emit showNotification(
+                tr("Room creation failed: %1").arg(QString::fromStdString(error)));
+              return;
+          }
 
-                  QString newRoomId = QString::fromStdString(res.room_id.to_string());
-                  emit showNotification(tr("Room %1 created.").arg(newRoomId));
-                  emit newRoom(newRoomId);
-                  emit changeToRoom(newRoomId);
-          });
+          QString newRoomId = QString::fromStdString(res.room_id.to_string());
+          emit showNotification(tr("Room %1 created.").arg(newRoomId));
+          emit newRoom(newRoomId);
+          emit changeToRoom(newRoomId);
+      });
 }
 
 void
 ChatPage::leaveRoom(const QString &room_id)
 {
-        http::client()->leave_room(
-          room_id.toStdString(),
-          [this, room_id](const mtx::responses::Empty &, mtx::http::RequestErr err) {
-                  if (err) {
-                          emit showNotification(
-                            tr("Failed to leave room: %1")
-                              .arg(QString::fromStdString(err->matrix_error.error)));
-                          return;
-                  }
+    http::client()->leave_room(
+      room_id.toStdString(),
+      [this, room_id](const mtx::responses::Empty &, mtx::http::RequestErr err) {
+          if (err) {
+              emit showNotification(tr("Failed to leave room: %1")
+                                      .arg(QString::fromStdString(err->matrix_error.error)));
+              return;
+          }
 
-                  emit leftRoom(room_id);
-          });
+          emit leftRoom(room_id);
+      });
 }
 
 void
 ChatPage::changeRoom(const QString &room_id)
 {
-        view_manager_->rooms()->setCurrentRoom(room_id);
+    view_manager_->rooms()->setCurrentRoom(room_id);
 }
 
 void
 ChatPage::inviteUser(QString userid, QString reason)
 {
-        auto room = currentRoom();
+    auto room = currentRoom();
 
-        if (QMessageBox::question(this,
-                                  tr("Confirm invite"),
-                                  tr("Do you really want to invite %1 (%2)?")
-                                    .arg(cache::displayName(room, userid))
-                                    .arg(userid)) != QMessageBox::Yes)
-                return;
+    if (QMessageBox::question(this,
+                              tr("Confirm invite"),
+                              tr("Do you really want to invite %1 (%2)?")
+                                .arg(cache::displayName(room, userid))
+                                .arg(userid)) != QMessageBox::Yes)
+        return;
 
-        http::client()->invite_user(
-          room.toStdString(),
-          userid.toStdString(),
-          [this, userid, room](const mtx::responses::Empty &, mtx::http::RequestErr err) {
-                  if (err) {
-                          emit showNotification(
-                            tr("Failed to invite %1 to %2: %3")
-                              .arg(userid)
-                              .arg(room)
-                              .arg(QString::fromStdString(err->matrix_error.error)));
-                  } else
-                          emit showNotification(tr("Invited user: %1").arg(userid));
-          },
-          reason.trimmed().toStdString());
+    http::client()->invite_user(
+      room.toStdString(),
+      userid.toStdString(),
+      [this, userid, room](const mtx::responses::Empty &, mtx::http::RequestErr err) {
+          if (err) {
+              emit showNotification(tr("Failed to invite %1 to %2: %3")
+                                      .arg(userid)
+                                      .arg(room)
+                                      .arg(QString::fromStdString(err->matrix_error.error)));
+          } else
+              emit showNotification(tr("Invited user: %1").arg(userid));
+      },
+      reason.trimmed().toStdString());
 }
 void
 ChatPage::kickUser(QString userid, QString reason)
 {
-        auto room = currentRoom();
+    auto room = currentRoom();
 
-        if (QMessageBox::question(this,
-                                  tr("Confirm kick"),
-                                  tr("Do you really want to kick %1 (%2)?")
-                                    .arg(cache::displayName(room, userid))
-                                    .arg(userid)) != QMessageBox::Yes)
-                return;
+    if (QMessageBox::question(this,
+                              tr("Confirm kick"),
+                              tr("Do you really want to kick %1 (%2)?")
+                                .arg(cache::displayName(room, userid))
+                                .arg(userid)) != QMessageBox::Yes)
+        return;
 
-        http::client()->kick_user(
-          room.toStdString(),
-          userid.toStdString(),
-          [this, userid, room](const mtx::responses::Empty &, mtx::http::RequestErr err) {
-                  if (err) {
-                          emit showNotification(
-                            tr("Failed to kick %1 from %2: %3")
-                              .arg(userid)
-                              .arg(room)
-                              .arg(QString::fromStdString(err->matrix_error.error)));
-                  } else
-                          emit showNotification(tr("Kicked user: %1").arg(userid));
-          },
-          reason.trimmed().toStdString());
+    http::client()->kick_user(
+      room.toStdString(),
+      userid.toStdString(),
+      [this, userid, room](const mtx::responses::Empty &, mtx::http::RequestErr err) {
+          if (err) {
+              emit showNotification(tr("Failed to kick %1 from %2: %3")
+                                      .arg(userid)
+                                      .arg(room)
+                                      .arg(QString::fromStdString(err->matrix_error.error)));
+          } else
+              emit showNotification(tr("Kicked user: %1").arg(userid));
+      },
+      reason.trimmed().toStdString());
 }
 void
 ChatPage::banUser(QString userid, QString reason)
 {
-        auto room = currentRoom();
+    auto room = currentRoom();
 
-        if (QMessageBox::question(this,
-                                  tr("Confirm ban"),
-                                  tr("Do you really want to ban %1 (%2)?")
-                                    .arg(cache::displayName(room, userid))
-                                    .arg(userid)) != QMessageBox::Yes)
-                return;
+    if (QMessageBox::question(this,
+                              tr("Confirm ban"),
+                              tr("Do you really want to ban %1 (%2)?")
+                                .arg(cache::displayName(room, userid))
+                                .arg(userid)) != QMessageBox::Yes)
+        return;
 
-        http::client()->ban_user(
-          room.toStdString(),
-          userid.toStdString(),
-          [this, userid, room](const mtx::responses::Empty &, mtx::http::RequestErr err) {
-                  if (err) {
-                          emit showNotification(
-                            tr("Failed to ban %1 in %2: %3")
-                              .arg(userid)
-                              .arg(room)
-                              .arg(QString::fromStdString(err->matrix_error.error)));
-                  } else
-                          emit showNotification(tr("Banned user: %1").arg(userid));
-          },
-          reason.trimmed().toStdString());
+    http::client()->ban_user(
+      room.toStdString(),
+      userid.toStdString(),
+      [this, userid, room](const mtx::responses::Empty &, mtx::http::RequestErr err) {
+          if (err) {
+              emit showNotification(tr("Failed to ban %1 in %2: %3")
+                                      .arg(userid)
+                                      .arg(room)
+                                      .arg(QString::fromStdString(err->matrix_error.error)));
+          } else
+              emit showNotification(tr("Banned user: %1").arg(userid));
+      },
+      reason.trimmed().toStdString());
 }
 void
 ChatPage::unbanUser(QString userid, QString reason)
 {
-        auto room = currentRoom();
+    auto room = currentRoom();
 
-        if (QMessageBox::question(this,
-                                  tr("Confirm unban"),
-                                  tr("Do you really want to unban %1 (%2)?")
-                                    .arg(cache::displayName(room, userid))
-                                    .arg(userid)) != QMessageBox::Yes)
-                return;
+    if (QMessageBox::question(this,
+                              tr("Confirm unban"),
+                              tr("Do you really want to unban %1 (%2)?")
+                                .arg(cache::displayName(room, userid))
+                                .arg(userid)) != QMessageBox::Yes)
+        return;
 
-        http::client()->unban_user(
-          room.toStdString(),
-          userid.toStdString(),
-          [this, userid, room](const mtx::responses::Empty &, mtx::http::RequestErr err) {
-                  if (err) {
-                          emit showNotification(
-                            tr("Failed to unban %1 in %2: %3")
-                              .arg(userid)
-                              .arg(room)
-                              .arg(QString::fromStdString(err->matrix_error.error)));
-                  } else
-                          emit showNotification(tr("Unbanned user: %1").arg(userid));
-          },
-          reason.trimmed().toStdString());
+    http::client()->unban_user(
+      room.toStdString(),
+      userid.toStdString(),
+      [this, userid, room](const mtx::responses::Empty &, mtx::http::RequestErr err) {
+          if (err) {
+              emit showNotification(tr("Failed to unban %1 in %2: %3")
+                                      .arg(userid)
+                                      .arg(room)
+                                      .arg(QString::fromStdString(err->matrix_error.error)));
+          } else
+              emit showNotification(tr("Unbanned user: %1").arg(userid));
+      },
+      reason.trimmed().toStdString());
 }
 
 void
 ChatPage::receivedSessionKey(const std::string &room_id, const std::string &session_id)
 {
-        view_manager_->receivedSessionKey(room_id, session_id);
+    view_manager_->receivedSessionKey(room_id, session_id);
 }
 
 QString
 ChatPage::status() const
 {
-        return QString::fromStdString(cache::statusMessage(utils::localUser().toStdString()));
+    return QString::fromStdString(cache::statusMessage(utils::localUser().toStdString()));
 }
 
 void
 ChatPage::setStatus(const QString &status)
 {
-        http::client()->put_presence_status(
-          currentPresence(), status.toStdString(), [](mtx::http::RequestErr err) {
-                  if (err) {
-                          nhlog::net()->warn("failed to set presence status_msg: {}",
-                                             err->matrix_error.error);
-                  }
-          });
+    http::client()->put_presence_status(
+      currentPresence(), status.toStdString(), [](mtx::http::RequestErr err) {
+          if (err) {
+              nhlog::net()->warn("failed to set presence status_msg: {}", err->matrix_error.error);
+          }
+      });
 }
 
 mtx::presence::PresenceState
 ChatPage::currentPresence() const
 {
-        switch (userSettings_->presence()) {
-        case UserSettings::Presence::Online:
-                return mtx::presence::online;
-        case UserSettings::Presence::Unavailable:
-                return mtx::presence::unavailable;
-        case UserSettings::Presence::Offline:
-                return mtx::presence::offline;
-        default:
-                return mtx::presence::online;
-        }
+    switch (userSettings_->presence()) {
+    case UserSettings::Presence::Online:
+        return mtx::presence::online;
+    case UserSettings::Presence::Unavailable:
+        return mtx::presence::unavailable;
+    case UserSettings::Presence::Offline:
+        return mtx::presence::offline;
+    default:
+        return mtx::presence::online;
+    }
 }
 
 void
 ChatPage::verifyOneTimeKeyCountAfterStartup()
 {
-        http::client()->upload_keys(
-          olm::client()->create_upload_keys_request(),
-          [this](const mtx::responses::UploadKeys &res, mtx::http::RequestErr err) {
-                  if (err) {
-                          nhlog::crypto()->warn("failed to update one-time keys: {} {} {}",
-                                                err->matrix_error.error,
-                                                static_cast(err->status_code),
-                                                static_cast(err->error_code));
+    http::client()->upload_keys(
+      olm::client()->create_upload_keys_request(),
+      [this](const mtx::responses::UploadKeys &res, mtx::http::RequestErr err) {
+          if (err) {
+              nhlog::crypto()->warn("failed to update one-time keys: {} {} {}",
+                                    err->matrix_error.error,
+                                    static_cast(err->status_code),
+                                    static_cast(err->error_code));
 
-                          if (err->status_code < 400 || err->status_code >= 500)
-                                  return;
-                  }
+              if (err->status_code < 400 || err->status_code >= 500)
+                  return;
+          }
 
-                  std::map key_counts;
-                  auto count = 0;
-                  if (auto c = res.one_time_key_counts.find(mtx::crypto::SIGNED_CURVE25519);
-                      c == res.one_time_key_counts.end()) {
-                          key_counts[mtx::crypto::SIGNED_CURVE25519] = 0;
-                  } else {
-                          key_counts[mtx::crypto::SIGNED_CURVE25519] = c->second;
-                          count                                      = c->second;
-                  }
+          std::map key_counts;
+          auto count = 0;
+          if (auto c = res.one_time_key_counts.find(mtx::crypto::SIGNED_CURVE25519);
+              c == res.one_time_key_counts.end()) {
+              key_counts[mtx::crypto::SIGNED_CURVE25519] = 0;
+          } else {
+              key_counts[mtx::crypto::SIGNED_CURVE25519] = c->second;
+              count                                      = c->second;
+          }
 
-                  nhlog::crypto()->info(
-                    "Fetched server key count {} {}", count, mtx::crypto::SIGNED_CURVE25519);
+          nhlog::crypto()->info(
+            "Fetched server key count {} {}", count, mtx::crypto::SIGNED_CURVE25519);
 
-                  ensureOneTimeKeyCount(key_counts);
-          });
+          ensureOneTimeKeyCount(key_counts);
+      });
 }
 
 void
 ChatPage::ensureOneTimeKeyCount(const std::map &counts)
 {
-        if (auto count = counts.find(mtx::crypto::SIGNED_CURVE25519); count != counts.end()) {
-                nhlog::crypto()->debug(
-                  "Updated server key count {} {}", count->second, mtx::crypto::SIGNED_CURVE25519);
+    if (auto count = counts.find(mtx::crypto::SIGNED_CURVE25519); count != counts.end()) {
+        nhlog::crypto()->debug(
+          "Updated server key count {} {}", count->second, mtx::crypto::SIGNED_CURVE25519);
 
-                if (count->second < MAX_ONETIME_KEYS) {
-                        const int nkeys = MAX_ONETIME_KEYS - count->second;
+        if (count->second < MAX_ONETIME_KEYS) {
+            const int nkeys = MAX_ONETIME_KEYS - count->second;
 
-                        nhlog::crypto()->info(
-                          "uploading {} {} keys", nkeys, mtx::crypto::SIGNED_CURVE25519);
-                        olm::client()->generate_one_time_keys(nkeys);
+            nhlog::crypto()->info("uploading {} {} keys", nkeys, mtx::crypto::SIGNED_CURVE25519);
+            olm::client()->generate_one_time_keys(nkeys);
 
-                        http::client()->upload_keys(
-                          olm::client()->create_upload_keys_request(),
-                          [](const mtx::responses::UploadKeys &, mtx::http::RequestErr err) {
-                                  if (err) {
-                                          nhlog::crypto()->warn(
-                                            "failed to update one-time keys: {} {} {}",
+            http::client()->upload_keys(
+              olm::client()->create_upload_keys_request(),
+              [](const mtx::responses::UploadKeys &, mtx::http::RequestErr err) {
+                  if (err) {
+                      nhlog::crypto()->warn("failed to update one-time keys: {} {} {}",
                                             err->matrix_error.error,
                                             static_cast(err->status_code),
                                             static_cast(err->error_code));
 
-                                          if (err->status_code < 400 || err->status_code >= 500)
-                                                  return;
-                                  }
+                      if (err->status_code < 400 || err->status_code >= 500)
+                          return;
+                  }
 
-                                  // mark as published anyway, otherwise we may end up in a loop.
-                                  olm::mark_keys_as_published();
-                          });
-                } else if (count->second > 2 * MAX_ONETIME_KEYS) {
-                        nhlog::crypto()->warn("too many one-time keys, deleting 1");
-                        mtx::requests::ClaimKeys req;
-                        req.one_time_keys[http::client()->user_id().to_string()]
-                                         [http::client()->device_id()] =
-                          std::string(mtx::crypto::SIGNED_CURVE25519);
-                        http::client()->claim_keys(
-                          req, [](const mtx::responses::ClaimKeys &, mtx::http::RequestErr err) {
-                                  if (err)
-                                          nhlog::crypto()->warn(
-                                            "failed to clear 1 one-time key: {} {} {}",
+                  // mark as published anyway, otherwise we may end up in a loop.
+                  olm::mark_keys_as_published();
+              });
+        } else if (count->second > 2 * MAX_ONETIME_KEYS) {
+            nhlog::crypto()->warn("too many one-time keys, deleting 1");
+            mtx::requests::ClaimKeys req;
+            req.one_time_keys[http::client()->user_id().to_string()][http::client()->device_id()] =
+              std::string(mtx::crypto::SIGNED_CURVE25519);
+            http::client()->claim_keys(
+              req, [](const mtx::responses::ClaimKeys &, mtx::http::RequestErr err) {
+                  if (err)
+                      nhlog::crypto()->warn("failed to clear 1 one-time key: {} {} {}",
                                             err->matrix_error.error,
                                             static_cast(err->status_code),
                                             static_cast(err->error_code));
-                                  else
-                                          nhlog::crypto()->info("cleared 1 one-time key");
-                          });
-                }
+                  else
+                      nhlog::crypto()->info("cleared 1 one-time key");
+              });
         }
+    }
 }
 
 void
 ChatPage::getProfileInfo()
 {
-        const auto userid = utils::localUser().toStdString();
+    const auto userid = utils::localUser().toStdString();
 
-        http::client()->get_profile(
-          userid, [this](const mtx::responses::Profile &res, mtx::http::RequestErr err) {
-                  if (err) {
-                          nhlog::net()->warn("failed to retrieve own profile info");
-                          return;
-                  }
+    http::client()->get_profile(
+      userid, [this](const mtx::responses::Profile &res, mtx::http::RequestErr err) {
+          if (err) {
+              nhlog::net()->warn("failed to retrieve own profile info");
+              return;
+          }
 
-                  emit setUserDisplayName(QString::fromStdString(res.display_name));
+          emit setUserDisplayName(QString::fromStdString(res.display_name));
 
-                  emit setUserAvatar(QString::fromStdString(res.avatar_url));
-          });
+          emit setUserAvatar(QString::fromStdString(res.avatar_url));
+      });
 }
 
 void
 ChatPage::getBackupVersion()
 {
-        if (!UserSettings::instance()->useOnlineKeyBackup()) {
-                nhlog::crypto()->info("Online key backup disabled.");
-                return;
-        }
+    if (!UserSettings::instance()->useOnlineKeyBackup()) {
+        nhlog::crypto()->info("Online key backup disabled.");
+        return;
+    }
 
-        http::client()->backup_version(
-          [this](const mtx::responses::backup::BackupVersion &res, mtx::http::RequestErr err) {
-                  if (err) {
-                          nhlog::net()->warn("Failed to retrieve backup version");
-                          if (err->status_code == 404)
-                                  cache::client()->deleteBackupVersion();
-                          return;
+    http::client()->backup_version(
+      [this](const mtx::responses::backup::BackupVersion &res, mtx::http::RequestErr err) {
+          if (err) {
+              nhlog::net()->warn("Failed to retrieve backup version");
+              if (err->status_code == 404)
+                  cache::client()->deleteBackupVersion();
+              return;
+          }
+
+          // switch to UI thread for secrets stuff
+          QTimer::singleShot(0, this, [res] {
+              auto auth_data = nlohmann::json::parse(res.auth_data);
+
+              if (res.algorithm == "m.megolm_backup.v1.curve25519-aes-sha2") {
+                  auto key = cache::secret(mtx::secret_storage::secrets::megolm_backup_v1);
+                  if (!key) {
+                      nhlog::crypto()->info("No key for online key backup.");
+                      cache::client()->deleteBackupVersion();
+                      return;
                   }
 
-                  // switch to UI thread for secrets stuff
-                  QTimer::singleShot(0, this, [res] {
-                          auto auth_data = nlohmann::json::parse(res.auth_data);
+                  using namespace mtx::crypto;
+                  auto pubkey = CURVE25519_public_key_from_private(to_binary_buf(base642bin(*key)));
 
-                          if (res.algorithm == "m.megolm_backup.v1.curve25519-aes-sha2") {
-                                  auto key =
-                                    cache::secret(mtx::secret_storage::secrets::megolm_backup_v1);
-                                  if (!key) {
-                                          nhlog::crypto()->info("No key for online key backup.");
-                                          cache::client()->deleteBackupVersion();
-                                          return;
-                                  }
-
-                                  using namespace mtx::crypto;
-                                  auto pubkey = CURVE25519_public_key_from_private(
-                                    to_binary_buf(base642bin(*key)));
-
-                                  if (auth_data["public_key"].get() != pubkey) {
-                                          nhlog::crypto()->info(
-                                            "Our backup key {} does not match the one "
+                  if (auth_data["public_key"].get() != pubkey) {
+                      nhlog::crypto()->info("Our backup key {} does not match the one "
                                             "used in the online backup {}",
                                             pubkey,
                                             auth_data["public_key"]);
-                                          cache::client()->deleteBackupVersion();
-                                          return;
-                                  }
+                      cache::client()->deleteBackupVersion();
+                      return;
+                  }
 
-                                  nhlog::crypto()->info("Using online key backup.");
-                                  OnlineBackupVersion data{};
-                                  data.algorithm = res.algorithm;
-                                  data.version   = res.version;
-                                  cache::client()->saveBackupVersion(data);
-                          } else {
-                                  nhlog::crypto()->info("Unsupported key backup algorithm: {}",
-                                                        res.algorithm);
-                                  cache::client()->deleteBackupVersion();
-                          }
-                  });
+                  nhlog::crypto()->info("Using online key backup.");
+                  OnlineBackupVersion data{};
+                  data.algorithm = res.algorithm;
+                  data.version   = res.version;
+                  cache::client()->saveBackupVersion(data);
+              } else {
+                  nhlog::crypto()->info("Unsupported key backup algorithm: {}", res.algorithm);
+                  cache::client()->deleteBackupVersion();
+              }
           });
+      });
 }
 
 void
 ChatPage::initiateLogout()
 {
-        http::client()->logout([this](const mtx::responses::Logout &, mtx::http::RequestErr err) {
-                if (err) {
-                        // TODO: handle special errors
-                        emit contentLoaded();
-                        nhlog::net()->warn("failed to logout: {} - {}",
-                                           mtx::errors::to_string(err->matrix_error.errcode),
-                                           err->matrix_error.error);
-                        return;
-                }
+    http::client()->logout([this](const mtx::responses::Logout &, mtx::http::RequestErr err) {
+        if (err) {
+            // TODO: handle special errors
+            emit contentLoaded();
+            nhlog::net()->warn("failed to logout: {} - {}",
+                               mtx::errors::to_string(err->matrix_error.errcode),
+                               err->matrix_error.error);
+            return;
+        }
 
-                emit loggedOut();
-        });
+        emit loggedOut();
+    });
 
-        emit showOverlayProgressBar();
+    emit showOverlayProgressBar();
 }
 
 template
 void
 ChatPage::connectCallMessage()
 {
-        connect(callManager_,
-                qOverload(&CallManager::newMessage),
-                view_manager_,
-                qOverload(&TimelineViewManager::queueCallMessage));
+    connect(callManager_,
+            qOverload(&CallManager::newMessage),
+            view_manager_,
+            qOverload(&TimelineViewManager::queueCallMessage));
 }
 
 void
 ChatPage::decryptDownloadedSecrets(mtx::secret_storage::AesHmacSha2KeyDescription keyDesc,
                                    const SecretsToDecrypt &secrets)
 {
-        QString text = QInputDialog::getText(
+    QString text = QInputDialog::getText(
+      ChatPage::instance(),
+      QCoreApplication::translate("CrossSigningSecrets", "Decrypt secrets"),
+      keyDesc.name.empty()
+        ? QCoreApplication::translate(
+            "CrossSigningSecrets", "Enter your recovery key or passphrase to decrypt your secrets:")
+        : QCoreApplication::translate(
+            "CrossSigningSecrets",
+            "Enter your recovery key or passphrase called %1 to decrypt your secrets:")
+            .arg(QString::fromStdString(keyDesc.name)),
+      QLineEdit::Password);
+
+    if (text.isEmpty())
+        return;
+
+    auto decryptionKey = mtx::crypto::key_from_recoverykey(text.toStdString(), keyDesc);
+
+    if (!decryptionKey && keyDesc.passphrase) {
+        try {
+            decryptionKey = mtx::crypto::key_from_passphrase(text.toStdString(), keyDesc);
+        } catch (std::exception &e) {
+            nhlog::crypto()->error("Failed to derive secret key from passphrase: {}", e.what());
+        }
+    }
+
+    if (!decryptionKey) {
+        QMessageBox::information(
           ChatPage::instance(),
-          QCoreApplication::translate("CrossSigningSecrets", "Decrypt secrets"),
-          keyDesc.name.empty()
-            ? QCoreApplication::translate(
-                "CrossSigningSecrets",
-                "Enter your recovery key or passphrase to decrypt your secrets:")
-            : QCoreApplication::translate(
-                "CrossSigningSecrets",
-                "Enter your recovery key or passphrase called %1 to decrypt your secrets:")
-                .arg(QString::fromStdString(keyDesc.name)),
-          QLineEdit::Password);
+          QCoreApplication::translate("CrossSigningSecrets", "Decryption failed"),
+          QCoreApplication::translate("CrossSigningSecrets",
+                                      "Failed to decrypt secrets with the "
+                                      "provided recovery key or passphrase"));
+        return;
+    }
 
-        if (text.isEmpty())
-                return;
-
-        auto decryptionKey = mtx::crypto::key_from_recoverykey(text.toStdString(), keyDesc);
-
-        if (!decryptionKey && keyDesc.passphrase) {
-                try {
-                        decryptionKey =
-                          mtx::crypto::key_from_passphrase(text.toStdString(), keyDesc);
-                } catch (std::exception &e) {
-                        nhlog::crypto()->error("Failed to derive secret key from passphrase: {}",
-                                               e.what());
-                }
-        }
-
-        if (!decryptionKey) {
-                QMessageBox::information(
-                  ChatPage::instance(),
-                  QCoreApplication::translate("CrossSigningSecrets", "Decryption failed"),
-                  QCoreApplication::translate("CrossSigningSecrets",
-                                              "Failed to decrypt secrets with the "
-                                              "provided recovery key or passphrase"));
-                return;
-        }
-
-        for (const auto &[secretName, encryptedSecret] : secrets) {
-                auto decrypted = mtx::crypto::decrypt(encryptedSecret, *decryptionKey, secretName);
-                if (!decrypted.empty())
-                        cache::storeSecret(secretName, decrypted);
-        }
+    for (const auto &[secretName, encryptedSecret] : secrets) {
+        auto decrypted = mtx::crypto::decrypt(encryptedSecret, *decryptionKey, secretName);
+        if (!decrypted.empty())
+            cache::storeSecret(secretName, decrypted);
+    }
 }
 
 void
 ChatPage::startChat(QString userid)
 {
-        auto joined_rooms = cache::joinedRooms();
-        auto room_infos   = cache::getRoomInfo(joined_rooms);
+    auto joined_rooms = cache::joinedRooms();
+    auto room_infos   = cache::getRoomInfo(joined_rooms);
 
-        for (std::string room_id : joined_rooms) {
-                if (room_infos[QString::fromStdString(room_id)].member_count == 2) {
-                        auto room_members = cache::roomMembers(room_id);
-                        if (std::find(room_members.begin(),
-                                      room_members.end(),
-                                      (userid).toStdString()) != room_members.end()) {
-                                view_manager_->rooms()->setCurrentRoom(
-                                  QString::fromStdString(room_id));
-                                return;
-                        }
-                }
-        }
-
-        if (QMessageBox::Yes !=
-            QMessageBox::question(
-              this,
-              tr("Confirm invite"),
-              tr("Do you really want to start a private chat with %1?").arg(userid)))
+    for (std::string room_id : joined_rooms) {
+        if (room_infos[QString::fromStdString(room_id)].member_count == 2) {
+            auto room_members = cache::roomMembers(room_id);
+            if (std::find(room_members.begin(), room_members.end(), (userid).toStdString()) !=
+                room_members.end()) {
+                view_manager_->rooms()->setCurrentRoom(QString::fromStdString(room_id));
                 return;
-
-        mtx::requests::CreateRoom req;
-        req.preset     = mtx::requests::Preset::PrivateChat;
-        req.visibility = mtx::common::RoomVisibility::Private;
-        if (utils::localUser() != userid) {
-                req.invite    = {userid.toStdString()};
-                req.is_direct = true;
+            }
         }
-        emit ChatPage::instance()->createRoom(req);
+    }
+
+    if (QMessageBox::Yes !=
+        QMessageBox::question(
+          this,
+          tr("Confirm invite"),
+          tr("Do you really want to start a private chat with %1?").arg(userid)))
+        return;
+
+    mtx::requests::CreateRoom req;
+    req.preset     = mtx::requests::Preset::PrivateChat;
+    req.visibility = mtx::common::RoomVisibility::Private;
+    if (utils::localUser() != userid) {
+        req.invite    = {userid.toStdString()};
+        req.is_direct = true;
+    }
+    emit ChatPage::instance()->createRoom(req);
 }
 
 static QString
 mxidFromSegments(QStringRef sigil, QStringRef mxid)
 {
-        if (mxid.isEmpty())
-                return "";
+    if (mxid.isEmpty())
+        return "";
 
-        auto mxid_ = QUrl::fromPercentEncoding(mxid.toUtf8());
+    auto mxid_ = QUrl::fromPercentEncoding(mxid.toUtf8());
 
-        if (sigil == "u") {
-                return "@" + mxid_;
-        } else if (sigil == "roomid") {
-                return "!" + mxid_;
-        } else if (sigil == "r") {
-                return "#" + mxid_;
-                //} else if (sigil == "group") {
-                //        return "+" + mxid_;
-        } else {
-                return "";
-        }
+    if (sigil == "u") {
+        return "@" + mxid_;
+    } else if (sigil == "roomid") {
+        return "!" + mxid_;
+    } else if (sigil == "r") {
+        return "#" + mxid_;
+        //} else if (sigil == "group") {
+        //        return "+" + mxid_;
+    } else {
+        return "";
+    }
 }
 
 bool
 ChatPage::handleMatrixUri(const QByteArray &uri)
 {
-        nhlog::ui()->info("Received uri! {}", uri.toStdString());
-        QUrl uri_{QString::fromUtf8(uri)};
+    nhlog::ui()->info("Received uri! {}", uri.toStdString());
+    QUrl uri_{QString::fromUtf8(uri)};
 
-        // Convert matrix.to URIs to proper format
-        if (uri_.scheme() == "https" && uri_.host() == "matrix.to") {
-                QString p = uri_.fragment(QUrl::FullyEncoded);
-                if (p.startsWith("/"))
-                        p.remove(0, 1);
+    // Convert matrix.to URIs to proper format
+    if (uri_.scheme() == "https" && uri_.host() == "matrix.to") {
+        QString p = uri_.fragment(QUrl::FullyEncoded);
+        if (p.startsWith("/"))
+            p.remove(0, 1);
 
-                auto temp = p.split("?");
-                QString query;
-                if (temp.size() >= 2)
-                        query = QUrl::fromPercentEncoding(temp.takeAt(1).toUtf8());
+        auto temp = p.split("?");
+        QString query;
+        if (temp.size() >= 2)
+            query = QUrl::fromPercentEncoding(temp.takeAt(1).toUtf8());
 
-                temp            = temp.first().split("/");
-                auto identifier = QUrl::fromPercentEncoding(temp.takeFirst().toUtf8());
-                QString eventId = QUrl::fromPercentEncoding(temp.join('/').toUtf8());
-                if (!identifier.isEmpty()) {
-                        if (identifier.startsWith("@")) {
-                                QByteArray newUri =
-                                  "matrix:u/" + QUrl::toPercentEncoding(identifier.remove(0, 1));
-                                if (!query.isEmpty())
-                                        newUri.append("?" + query.toUtf8());
-                                return handleMatrixUri(QUrl::fromEncoded(newUri));
-                        } else if (identifier.startsWith("#")) {
-                                QByteArray newUri =
-                                  "matrix:r/" + QUrl::toPercentEncoding(identifier.remove(0, 1));
-                                if (!eventId.isEmpty())
-                                        newUri.append(
-                                          "/e/" + QUrl::toPercentEncoding(eventId.remove(0, 1)));
-                                if (!query.isEmpty())
-                                        newUri.append("?" + query.toUtf8());
-                                return handleMatrixUri(QUrl::fromEncoded(newUri));
-                        } else if (identifier.startsWith("!")) {
-                                QByteArray newUri = "matrix:roomid/" + QUrl::toPercentEncoding(
-                                                                         identifier.remove(0, 1));
-                                if (!eventId.isEmpty())
-                                        newUri.append(
-                                          "/e/" + QUrl::toPercentEncoding(eventId.remove(0, 1)));
-                                if (!query.isEmpty())
-                                        newUri.append("?" + query.toUtf8());
-                                return handleMatrixUri(QUrl::fromEncoded(newUri));
-                        }
-                }
+        temp            = temp.first().split("/");
+        auto identifier = QUrl::fromPercentEncoding(temp.takeFirst().toUtf8());
+        QString eventId = QUrl::fromPercentEncoding(temp.join('/').toUtf8());
+        if (!identifier.isEmpty()) {
+            if (identifier.startsWith("@")) {
+                QByteArray newUri = "matrix:u/" + QUrl::toPercentEncoding(identifier.remove(0, 1));
+                if (!query.isEmpty())
+                    newUri.append("?" + query.toUtf8());
+                return handleMatrixUri(QUrl::fromEncoded(newUri));
+            } else if (identifier.startsWith("#")) {
+                QByteArray newUri = "matrix:r/" + QUrl::toPercentEncoding(identifier.remove(0, 1));
+                if (!eventId.isEmpty())
+                    newUri.append("/e/" + QUrl::toPercentEncoding(eventId.remove(0, 1)));
+                if (!query.isEmpty())
+                    newUri.append("?" + query.toUtf8());
+                return handleMatrixUri(QUrl::fromEncoded(newUri));
+            } else if (identifier.startsWith("!")) {
+                QByteArray newUri =
+                  "matrix:roomid/" + QUrl::toPercentEncoding(identifier.remove(0, 1));
+                if (!eventId.isEmpty())
+                    newUri.append("/e/" + QUrl::toPercentEncoding(eventId.remove(0, 1)));
+                if (!query.isEmpty())
+                    newUri.append("?" + query.toUtf8());
+                return handleMatrixUri(QUrl::fromEncoded(newUri));
+            }
         }
+    }
 
-        // non-matrix URIs are not handled by us, return false
-        if (uri_.scheme() != "matrix")
-                return false;
+    // non-matrix URIs are not handled by us, return false
+    if (uri_.scheme() != "matrix")
+        return false;
 
-        auto tempPath = uri_.path(QUrl::ComponentFormattingOption::FullyEncoded);
-        if (tempPath.startsWith('/'))
-                tempPath.remove(0, 1);
-        auto segments = tempPath.splitRef('/');
+    auto tempPath = uri_.path(QUrl::ComponentFormattingOption::FullyEncoded);
+    if (tempPath.startsWith('/'))
+        tempPath.remove(0, 1);
+    auto segments = tempPath.splitRef('/');
 
-        if (segments.size() != 2 && segments.size() != 4)
-                return false;
+    if (segments.size() != 2 && segments.size() != 4)
+        return false;
 
-        auto sigil1 = segments[0];
-        auto mxid1  = mxidFromSegments(sigil1, segments[1]);
-        if (mxid1.isEmpty())
-                return false;
+    auto sigil1 = segments[0];
+    auto mxid1  = mxidFromSegments(sigil1, segments[1]);
+    if (mxid1.isEmpty())
+        return false;
 
-        QString mxid2;
-        if (segments.size() == 4 && segments[2] == "e") {
-                if (segments[3].isEmpty())
-                        return false;
-                else
-                        mxid2 = "$" + QUrl::fromPercentEncoding(segments[3].toUtf8());
+    QString mxid2;
+    if (segments.size() == 4 && segments[2] == "e") {
+        if (segments[3].isEmpty())
+            return false;
+        else
+            mxid2 = "$" + QUrl::fromPercentEncoding(segments[3].toUtf8());
+    }
+
+    std::vector vias;
+    QString action;
+
+    for (QString item : uri_.query(QUrl::ComponentFormattingOption::FullyEncoded).split('&')) {
+        nhlog::ui()->info("item: {}", item.toStdString());
+
+        if (item.startsWith("action=")) {
+            action = item.remove("action=");
+        } else if (item.startsWith("via=")) {
+            vias.push_back(QUrl::fromPercentEncoding(item.remove("via=").toUtf8()).toStdString());
         }
+    }
 
-        std::vector vias;
-        QString action;
-
-        for (QString item : uri_.query(QUrl::ComponentFormattingOption::FullyEncoded).split('&')) {
-                nhlog::ui()->info("item: {}", item.toStdString());
-
-                if (item.startsWith("action=")) {
-                        action = item.remove("action=");
-                } else if (item.startsWith("via=")) {
-                        vias.push_back(
-                          QUrl::fromPercentEncoding(item.remove("via=").toUtf8()).toStdString());
-                }
-        }
-
-        if (sigil1 == "u") {
-                if (action.isEmpty()) {
-                        auto t = view_manager_->rooms()->currentRoom();
-                        if (t &&
-                            cache::isRoomMember(mxid1.toStdString(), t->roomId().toStdString())) {
-                                t->openUserProfile(mxid1);
-                                return true;
-                        }
-                        emit view_manager_->openGlobalUserProfile(mxid1);
-                } else if (action == "chat") {
-                        this->startChat(mxid1);
-                }
+    if (sigil1 == "u") {
+        if (action.isEmpty()) {
+            auto t = view_manager_->rooms()->currentRoom();
+            if (t && cache::isRoomMember(mxid1.toStdString(), t->roomId().toStdString())) {
+                t->openUserProfile(mxid1);
                 return true;
-        } else if (sigil1 == "roomid") {
-                auto joined_rooms = cache::joinedRooms();
-                auto targetRoomId = mxid1.toStdString();
+            }
+            emit view_manager_->openGlobalUserProfile(mxid1);
+        } else if (action == "chat") {
+            this->startChat(mxid1);
+        }
+        return true;
+    } else if (sigil1 == "roomid") {
+        auto joined_rooms = cache::joinedRooms();
+        auto targetRoomId = mxid1.toStdString();
 
-                for (auto roomid : joined_rooms) {
-                        if (roomid == targetRoomId) {
-                                view_manager_->rooms()->setCurrentRoom(mxid1);
-                                if (!mxid2.isEmpty())
-                                        view_manager_->showEvent(mxid1, mxid2);
-                                return true;
-                        }
-                }
+        for (auto roomid : joined_rooms) {
+            if (roomid == targetRoomId) {
+                view_manager_->rooms()->setCurrentRoom(mxid1);
+                if (!mxid2.isEmpty())
+                    view_manager_->showEvent(mxid1, mxid2);
+                return true;
+            }
+        }
 
-                if (action == "join" || action.isEmpty()) {
-                        joinRoomVia(targetRoomId, vias);
-                        return true;
-                }
-                return false;
-        } else if (sigil1 == "r") {
-                auto joined_rooms    = cache::joinedRooms();
-                auto targetRoomAlias = mxid1.toStdString();
-
-                for (auto roomid : joined_rooms) {
-                        auto aliases = cache::client()->getRoomAliases(roomid);
-                        if (aliases) {
-                                if (aliases->alias == targetRoomAlias) {
-                                        view_manager_->rooms()->setCurrentRoom(
-                                          QString::fromStdString(roomid));
-                                        if (!mxid2.isEmpty())
-                                                view_manager_->showEvent(
-                                                  QString::fromStdString(roomid), mxid2);
-                                        return true;
-                                }
-                        }
-                }
-
-                if (action == "join" || action.isEmpty()) {
-                        joinRoomVia(mxid1.toStdString(), vias);
-                        return true;
-                }
-                return false;
+        if (action == "join" || action.isEmpty()) {
+            joinRoomVia(targetRoomId, vias);
+            return true;
         }
         return false;
+    } else if (sigil1 == "r") {
+        auto joined_rooms    = cache::joinedRooms();
+        auto targetRoomAlias = mxid1.toStdString();
+
+        for (auto roomid : joined_rooms) {
+            auto aliases = cache::client()->getRoomAliases(roomid);
+            if (aliases) {
+                if (aliases->alias == targetRoomAlias) {
+                    view_manager_->rooms()->setCurrentRoom(QString::fromStdString(roomid));
+                    if (!mxid2.isEmpty())
+                        view_manager_->showEvent(QString::fromStdString(roomid), mxid2);
+                    return true;
+                }
+            }
+        }
+
+        if (action == "join" || action.isEmpty()) {
+            joinRoomVia(mxid1.toStdString(), vias);
+            return true;
+        }
+        return false;
+    }
+    return false;
 }
 
 bool
 ChatPage::handleMatrixUri(const QUrl &uri)
 {
-        return handleMatrixUri(
-          uri.toString(QUrl::ComponentFormattingOption::FullyEncoded).toUtf8());
+    return handleMatrixUri(uri.toString(QUrl::ComponentFormattingOption::FullyEncoded).toUtf8());
 }
 
 bool
 ChatPage::isRoomActive(const QString &room_id)
 {
-        return isActiveWindow() && currentRoom() == room_id;
+    return isActiveWindow() && currentRoom() == room_id;
 }
 
 QString
 ChatPage::currentRoom() const
 {
-        if (view_manager_->rooms()->currentRoom())
-                return view_manager_->rooms()->currentRoom()->roomId();
-        else
-                return "";
+    if (view_manager_->rooms()->currentRoom())
+        return view_manager_->rooms()->currentRoom()->roomId();
+    else
+        return "";
 }
diff --git a/src/ChatPage.h b/src/ChatPage.h
index 66e4c6ab..8f3dc53e 100644
--- a/src/ChatPage.h
+++ b/src/ChatPage.h
@@ -52,186 +52,181 @@ using SecretsToDecrypt = std::map userSettings, QWidget *parent = nullptr);
+    ChatPage(QSharedPointer userSettings, QWidget *parent = nullptr);
 
-        // Initialize all the components of the UI.
-        void bootstrap(QString userid, QString homeserver, QString token);
+    // Initialize all the components of the UI.
+    void bootstrap(QString userid, QString homeserver, QString token);
 
-        static ChatPage *instance() { return instance_; }
+    static ChatPage *instance() { return instance_; }
 
-        QSharedPointer userSettings() { return userSettings_; }
-        CallManager *callManager() { return callManager_; }
-        TimelineViewManager *timelineManager() { return view_manager_; }
-        void deleteConfigs();
+    QSharedPointer userSettings() { return userSettings_; }
+    CallManager *callManager() { return callManager_; }
+    TimelineViewManager *timelineManager() { return view_manager_; }
+    void deleteConfigs();
 
-        void initiateLogout();
+    void initiateLogout();
 
-        QString status() const;
-        void setStatus(const QString &status);
+    QString status() const;
+    void setStatus(const QString &status);
 
-        mtx::presence::PresenceState currentPresence() const;
+    mtx::presence::PresenceState currentPresence() const;
 
-        // TODO(Nico): Get rid of this!
-        QString currentRoom() const;
+    // TODO(Nico): Get rid of this!
+    QString currentRoom() const;
 
 public slots:
-        bool handleMatrixUri(const QByteArray &uri);
-        bool handleMatrixUri(const QUrl &uri);
+    bool handleMatrixUri(const QByteArray &uri);
+    bool handleMatrixUri(const QUrl &uri);
 
-        void startChat(QString userid);
-        void leaveRoom(const QString &room_id);
-        void createRoom(const mtx::requests::CreateRoom &req);
-        void joinRoom(const QString &room);
-        void joinRoomVia(const std::string &room_id,
-                         const std::vector &via,
-                         bool promptForConfirmation = true);
+    void startChat(QString userid);
+    void leaveRoom(const QString &room_id);
+    void createRoom(const mtx::requests::CreateRoom &req);
+    void joinRoom(const QString &room);
+    void joinRoomVia(const std::string &room_id,
+                     const std::vector &via,
+                     bool promptForConfirmation = true);
 
-        void inviteUser(QString userid, QString reason);
-        void kickUser(QString userid, QString reason);
-        void banUser(QString userid, QString reason);
-        void unbanUser(QString userid, QString reason);
+    void inviteUser(QString userid, QString reason);
+    void kickUser(QString userid, QString reason);
+    void banUser(QString userid, QString reason);
+    void unbanUser(QString userid, QString reason);
 
-        void receivedSessionKey(const std::string &room_id, const std::string &session_id);
-        void decryptDownloadedSecrets(mtx::secret_storage::AesHmacSha2KeyDescription keyDesc,
-                                      const SecretsToDecrypt &secrets);
+    void receivedSessionKey(const std::string &room_id, const std::string &session_id);
+    void decryptDownloadedSecrets(mtx::secret_storage::AesHmacSha2KeyDescription keyDesc,
+                                  const SecretsToDecrypt &secrets);
 signals:
-        void connectionLost();
-        void connectionRestored();
+    void connectionLost();
+    void connectionRestored();
 
-        void notificationsRetrieved(const mtx::responses::Notifications &);
-        void highlightedNotifsRetrieved(const mtx::responses::Notifications &,
-                                        const QPoint widgetPos);
+    void notificationsRetrieved(const mtx::responses::Notifications &);
+    void highlightedNotifsRetrieved(const mtx::responses::Notifications &, const QPoint widgetPos);
 
-        void contentLoaded();
-        void closing();
-        void changeWindowTitle(const int);
-        void unreadMessages(int count);
-        void showNotification(const QString &msg);
-        void showLoginPage(const QString &msg);
-        void showUserSettingsPage();
-        void showOverlayProgressBar();
+    void contentLoaded();
+    void closing();
+    void changeWindowTitle(const int);
+    void unreadMessages(int count);
+    void showNotification(const QString &msg);
+    void showLoginPage(const QString &msg);
+    void showUserSettingsPage();
+    void showOverlayProgressBar();
 
-        void ownProfileOk();
-        void setUserDisplayName(const QString &name);
-        void setUserAvatar(const QString &avatar);
-        void loggedOut();
+    void ownProfileOk();
+    void setUserDisplayName(const QString &name);
+    void setUserAvatar(const QString &avatar);
+    void loggedOut();
 
-        void trySyncCb();
-        void tryDelayedSyncCb();
-        void tryInitialSyncCb();
-        void newSyncResponse(const mtx::responses::Sync &res, const std::string &prev_batch_token);
-        void leftRoom(const QString &room_id);
-        void newRoom(const QString &room_id);
-        void changeToRoom(const QString &room_id);
+    void trySyncCb();
+    void tryDelayedSyncCb();
+    void tryInitialSyncCb();
+    void newSyncResponse(const mtx::responses::Sync &res, const std::string &prev_batch_token);
+    void leftRoom(const QString &room_id);
+    void newRoom(const QString &room_id);
+    void changeToRoom(const QString &room_id);
 
-        void initializeViews(const mtx::responses::Rooms &rooms);
-        void initializeEmptyViews();
-        void initializeMentions(const QMap ¬ifs);
-        void syncUI(const mtx::responses::Rooms &rooms);
-        void dropToLoginPageCb(const QString &msg);
+    void initializeViews(const mtx::responses::Rooms &rooms);
+    void initializeEmptyViews();
+    void initializeMentions(const QMap ¬ifs);
+    void syncUI(const mtx::responses::Rooms &rooms);
+    void dropToLoginPageCb(const QString &msg);
 
-        void notifyMessage(const QString &roomid,
-                           const QString &eventid,
-                           const QString &roomname,
-                           const QString &sender,
-                           const QString &message,
-                           const QImage &icon);
+    void notifyMessage(const QString &roomid,
+                       const QString &eventid,
+                       const QString &roomname,
+                       const QString &sender,
+                       const QString &message,
+                       const QImage &icon);
 
-        void retrievedPresence(const QString &statusMsg, mtx::presence::PresenceState state);
-        void themeChanged();
-        void decryptSidebarChanged();
-        void chatFocusChanged(const bool focused);
+    void retrievedPresence(const QString &statusMsg, mtx::presence::PresenceState state);
+    void themeChanged();
+    void decryptSidebarChanged();
+    void chatFocusChanged(const bool focused);
 
-        //! Signals for device verificaiton
-        void receivedDeviceVerificationAccept(
-          const mtx::events::msg::KeyVerificationAccept &message);
-        void receivedDeviceVerificationRequest(
-          const mtx::events::msg::KeyVerificationRequest &message,
-          std::string sender);
-        void receivedRoomDeviceVerificationRequest(
-          const mtx::events::RoomEvent &message,
-          TimelineModel *model);
-        void receivedDeviceVerificationCancel(
-          const mtx::events::msg::KeyVerificationCancel &message);
-        void receivedDeviceVerificationKey(const mtx::events::msg::KeyVerificationKey &message);
-        void receivedDeviceVerificationMac(const mtx::events::msg::KeyVerificationMac &message);
-        void receivedDeviceVerificationStart(const mtx::events::msg::KeyVerificationStart &message,
-                                             std::string sender);
-        void receivedDeviceVerificationReady(const mtx::events::msg::KeyVerificationReady &message);
-        void receivedDeviceVerificationDone(const mtx::events::msg::KeyVerificationDone &message);
+    //! Signals for device verificaiton
+    void receivedDeviceVerificationAccept(const mtx::events::msg::KeyVerificationAccept &message);
+    void receivedDeviceVerificationRequest(const mtx::events::msg::KeyVerificationRequest &message,
+                                           std::string sender);
+    void receivedRoomDeviceVerificationRequest(
+      const mtx::events::RoomEvent &message,
+      TimelineModel *model);
+    void receivedDeviceVerificationCancel(const mtx::events::msg::KeyVerificationCancel &message);
+    void receivedDeviceVerificationKey(const mtx::events::msg::KeyVerificationKey &message);
+    void receivedDeviceVerificationMac(const mtx::events::msg::KeyVerificationMac &message);
+    void receivedDeviceVerificationStart(const mtx::events::msg::KeyVerificationStart &message,
+                                         std::string sender);
+    void receivedDeviceVerificationReady(const mtx::events::msg::KeyVerificationReady &message);
+    void receivedDeviceVerificationDone(const mtx::events::msg::KeyVerificationDone &message);
 
-        void downloadedSecrets(mtx::secret_storage::AesHmacSha2KeyDescription keyDesc,
-                               const SecretsToDecrypt &secrets);
+    void downloadedSecrets(mtx::secret_storage::AesHmacSha2KeyDescription keyDesc,
+                           const SecretsToDecrypt &secrets);
 
 private slots:
-        void logout();
-        void removeRoom(const QString &room_id);
-        void changeRoom(const QString &room_id);
-        void dropToLoginPage(const QString &msg);
+    void logout();
+    void removeRoom(const QString &room_id);
+    void changeRoom(const QString &room_id);
+    void dropToLoginPage(const QString &msg);
 
-        void handleSyncResponse(const mtx::responses::Sync &res,
-                                const std::string &prev_batch_token);
+    void handleSyncResponse(const mtx::responses::Sync &res, const std::string &prev_batch_token);
 
 private:
-        static ChatPage *instance_;
+    static ChatPage *instance_;
 
-        void startInitialSync();
-        void tryInitialSync();
-        void trySync();
-        void verifyOneTimeKeyCountAfterStartup();
-        void ensureOneTimeKeyCount(const std::map &counts);
-        void getProfileInfo();
-        void getBackupVersion();
+    void startInitialSync();
+    void tryInitialSync();
+    void trySync();
+    void verifyOneTimeKeyCountAfterStartup();
+    void ensureOneTimeKeyCount(const std::map &counts);
+    void getProfileInfo();
+    void getBackupVersion();
 
-        //! Check if the given room is currently open.
-        bool isRoomActive(const QString &room_id);
+    //! Check if the given room is currently open.
+    bool isRoomActive(const QString &room_id);
 
-        using UserID      = QString;
-        using Membership  = mtx::events::StateEvent;
-        using Memberships = std::map;
+    using UserID      = QString;
+    using Membership  = mtx::events::StateEvent;
+    using Memberships = std::map;
 
-        void loadStateFromCache();
-        void resetUI();
+    void loadStateFromCache();
+    void resetUI();
 
-        template
-        Memberships getMemberships(const std::vector &events) const;
+    template
+    Memberships getMemberships(const std::vector &events) const;
 
-        //! Send desktop notification for the received messages.
-        void sendNotifications(const mtx::responses::Notifications &);
+    //! Send desktop notification for the received messages.
+    void sendNotifications(const mtx::responses::Notifications &);
 
-        template
-        void connectCallMessage();
+    template
+    void connectCallMessage();
 
-        QHBoxLayout *topLayout_;
+    QHBoxLayout *topLayout_;
 
-        TimelineViewManager *view_manager_;
+    TimelineViewManager *view_manager_;
 
-        QTimer connectivityTimer_;
-        std::atomic_bool isConnected_;
+    QTimer connectivityTimer_;
+    std::atomic_bool isConnected_;
 
-        // Global user settings.
-        QSharedPointer userSettings_;
+    // Global user settings.
+    QSharedPointer userSettings_;
 
-        NotificationsManager notificationsManager;
-        CallManager *callManager_;
+    NotificationsManager notificationsManager;
+    CallManager *callManager_;
 };
 
 template
 std::map>
 ChatPage::getMemberships(const std::vector &collection) const
 {
-        std::map> memberships;
+    std::map> memberships;
 
-        using Member = mtx::events::StateEvent;
+    using Member = mtx::events::StateEvent;
 
-        for (const auto &event : collection) {
-                if (auto member = std::get_if(event)) {
-                        memberships.emplace(member->state_key, *member);
-                }
+    for (const auto &event : collection) {
+        if (auto member = std::get_if(event)) {
+            memberships.emplace(member->state_key, *member);
         }
+    }
 
-        return memberships;
+    return memberships;
 }
diff --git a/src/Clipboard.cpp b/src/Clipboard.cpp
index d4d5bab7..93d913be 100644
--- a/src/Clipboard.cpp
+++ b/src/Clipboard.cpp
@@ -10,18 +10,17 @@
 Clipboard::Clipboard(QObject *parent)
   : QObject(parent)
 {
-        connect(
-          QGuiApplication::clipboard(), &QClipboard::dataChanged, this, &Clipboard::textChanged);
+    connect(QGuiApplication::clipboard(), &QClipboard::dataChanged, this, &Clipboard::textChanged);
 }
 
 void
 Clipboard::setText(QString text)
 {
-        QGuiApplication::clipboard()->setText(text);
+    QGuiApplication::clipboard()->setText(text);
 }
 
 QString
 Clipboard::text() const
 {
-        return QGuiApplication::clipboard()->text();
+    return QGuiApplication::clipboard()->text();
 }
diff --git a/src/Clipboard.h b/src/Clipboard.h
index fa74da22..1a6584ca 100644
--- a/src/Clipboard.h
+++ b/src/Clipboard.h
@@ -9,14 +9,14 @@
 
 class Clipboard : public QObject
 {
-        Q_OBJECT
-        Q_PROPERTY(QString text READ text WRITE setText NOTIFY textChanged)
+    Q_OBJECT
+    Q_PROPERTY(QString text READ text WRITE setText NOTIFY textChanged)
 
 public:
-        Clipboard(QObject *parent = nullptr);
+    Clipboard(QObject *parent = nullptr);
 
-        QString text() const;
-        void setText(QString text_);
+    QString text() const;
+    void setText(QString text_);
 signals:
-        void textChanged();
+    void textChanged();
 };
diff --git a/src/ColorImageProvider.cpp b/src/ColorImageProvider.cpp
index 41fd5d8f..9c371c8c 100644
--- a/src/ColorImageProvider.cpp
+++ b/src/ColorImageProvider.cpp
@@ -9,23 +9,23 @@
 QPixmap
 ColorImageProvider::requestPixmap(const QString &id, QSize *size, const QSize &)
 {
-        auto args = id.split('?');
+    auto args = id.split('?');
 
-        QPixmap source(args[0]);
+    QPixmap source(args[0]);
 
-        if (size)
-                *size = QSize(source.width(), source.height());
+    if (size)
+        *size = QSize(source.width(), source.height());
 
-        if (args.size() < 2)
-                return source;
+    if (args.size() < 2)
+        return source;
 
-        QColor color(args[1]);
+    QColor color(args[1]);
 
-        QPixmap colorized = source;
-        QPainter painter(&colorized);
-        painter.setCompositionMode(QPainter::CompositionMode_SourceIn);
-        painter.fillRect(colorized.rect(), color);
-        painter.end();
+    QPixmap colorized = source;
+    QPainter painter(&colorized);
+    painter.setCompositionMode(QPainter::CompositionMode_SourceIn);
+    painter.fillRect(colorized.rect(), color);
+    painter.end();
 
-        return colorized;
+    return colorized;
 }
diff --git a/src/ColorImageProvider.h b/src/ColorImageProvider.h
index 9ae8c85e..f2997e0a 100644
--- a/src/ColorImageProvider.h
+++ b/src/ColorImageProvider.h
@@ -7,9 +7,9 @@
 class ColorImageProvider : public QQuickImageProvider
 {
 public:
-        ColorImageProvider()
-          : QQuickImageProvider(QQuickImageProvider::Pixmap)
-        {}
+    ColorImageProvider()
+      : QQuickImageProvider(QQuickImageProvider::Pixmap)
+    {}
 
-        QPixmap requestPixmap(const QString &id, QSize *size, const QSize &requestedSize) override;
+    QPixmap requestPixmap(const QString &id, QSize *size, const QSize &requestedSize) override;
 };
diff --git a/src/CombinedImagePackModel.cpp b/src/CombinedImagePackModel.cpp
index 341a34ec..9a52f810 100644
--- a/src/CombinedImagePackModel.cpp
+++ b/src/CombinedImagePackModel.cpp
@@ -13,65 +13,65 @@ CombinedImagePackModel::CombinedImagePackModel(const std::string &roomId,
   : QAbstractListModel(parent)
   , room_id(roomId)
 {
-        auto packs = cache::client()->getImagePacks(room_id, stickers);
+    auto packs = cache::client()->getImagePacks(room_id, stickers);
 
-        for (const auto &pack : packs) {
-                QString packname =
-                  pack.pack.pack ? QString::fromStdString(pack.pack.pack->display_name) : "";
+    for (const auto &pack : packs) {
+        QString packname =
+          pack.pack.pack ? QString::fromStdString(pack.pack.pack->display_name) : "";
 
-                for (const auto &img : pack.pack.images) {
-                        ImageDesc i{};
-                        i.shortcode = QString::fromStdString(img.first);
-                        i.packname  = packname;
-                        i.image     = img.second;
-                        images.push_back(std::move(i));
-                }
+        for (const auto &img : pack.pack.images) {
+            ImageDesc i{};
+            i.shortcode = QString::fromStdString(img.first);
+            i.packname  = packname;
+            i.image     = img.second;
+            images.push_back(std::move(i));
         }
+    }
 }
 
 int
 CombinedImagePackModel::rowCount(const QModelIndex &) const
 {
-        return (int)images.size();
+    return (int)images.size();
 }
 
 QHash
 CombinedImagePackModel::roleNames() const
 {
-        return {
-          {CompletionModel::CompletionRole, "completionRole"},
-          {CompletionModel::SearchRole, "searchRole"},
-          {CompletionModel::SearchRole2, "searchRole2"},
-          {Roles::Url, "url"},
-          {Roles::ShortCode, "shortcode"},
-          {Roles::Body, "body"},
-          {Roles::PackName, "packname"},
-          {Roles::OriginalRow, "originalRow"},
-        };
+    return {
+      {CompletionModel::CompletionRole, "completionRole"},
+      {CompletionModel::SearchRole, "searchRole"},
+      {CompletionModel::SearchRole2, "searchRole2"},
+      {Roles::Url, "url"},
+      {Roles::ShortCode, "shortcode"},
+      {Roles::Body, "body"},
+      {Roles::PackName, "packname"},
+      {Roles::OriginalRow, "originalRow"},
+    };
 }
 
 QVariant
 CombinedImagePackModel::data(const QModelIndex &index, int role) const
 {
-        if (hasIndex(index.row(), index.column(), index.parent())) {
-                switch (role) {
-                case CompletionModel::CompletionRole:
-                        return QString::fromStdString(images[index.row()].image.url);
-                case Roles::Url:
-                        return QString::fromStdString(images[index.row()].image.url);
-                case CompletionModel::SearchRole:
-                case Roles::ShortCode:
-                        return images[index.row()].shortcode;
-                case CompletionModel::SearchRole2:
-                case Roles::Body:
-                        return QString::fromStdString(images[index.row()].image.body);
-                case Roles::PackName:
-                        return images[index.row()].packname;
-                case Roles::OriginalRow:
-                        return index.row();
-                default:
-                        return {};
-                }
+    if (hasIndex(index.row(), index.column(), index.parent())) {
+        switch (role) {
+        case CompletionModel::CompletionRole:
+            return QString::fromStdString(images[index.row()].image.url);
+        case Roles::Url:
+            return QString::fromStdString(images[index.row()].image.url);
+        case CompletionModel::SearchRole:
+        case Roles::ShortCode:
+            return images[index.row()].shortcode;
+        case CompletionModel::SearchRole2:
+        case Roles::Body:
+            return QString::fromStdString(images[index.row()].image.body);
+        case Roles::PackName:
+            return images[index.row()].packname;
+        case Roles::OriginalRow:
+            return index.row();
+        default:
+            return {};
         }
-        return {};
+    }
+    return {};
 }
diff --git a/src/CombinedImagePackModel.h b/src/CombinedImagePackModel.h
index f0f69799..ec49b325 100644
--- a/src/CombinedImagePackModel.h
+++ b/src/CombinedImagePackModel.h
@@ -10,39 +10,39 @@
 
 class CombinedImagePackModel : public QAbstractListModel
 {
-        Q_OBJECT
+    Q_OBJECT
 public:
-        enum Roles
-        {
-                Url = Qt::UserRole,
-                ShortCode,
-                Body,
-                PackName,
-                OriginalRow,
-        };
+    enum Roles
+    {
+        Url = Qt::UserRole,
+        ShortCode,
+        Body,
+        PackName,
+        OriginalRow,
+    };
 
-        CombinedImagePackModel(const std::string &roomId, bool stickers, QObject *parent = nullptr);
-        QHash roleNames() const override;
-        int rowCount(const QModelIndex &parent = QModelIndex()) const override;
-        QVariant data(const QModelIndex &index, int role) const override;
+    CombinedImagePackModel(const std::string &roomId, bool stickers, QObject *parent = nullptr);
+    QHash roleNames() const override;
+    int rowCount(const QModelIndex &parent = QModelIndex()) const override;
+    QVariant data(const QModelIndex &index, int role) const override;
 
-        mtx::events::msc2545::PackImage imageAt(int row)
-        {
-                if (row < 0 || static_cast(row) >= images.size())
-                        return {};
-                return images.at(static_cast(row)).image;
-        }
+    mtx::events::msc2545::PackImage imageAt(int row)
+    {
+        if (row < 0 || static_cast(row) >= images.size())
+            return {};
+        return images.at(static_cast(row)).image;
+    }
 
 private:
-        std::string room_id;
+    std::string room_id;
 
-        struct ImageDesc
-        {
-                QString shortcode;
-                QString packname;
+    struct ImageDesc
+    {
+        QString shortcode;
+        QString packname;
 
-                mtx::events::msc2545::PackImage image;
-        };
+        mtx::events::msc2545::PackImage image;
+    };
 
-        std::vector images;
+    std::vector images;
 };
diff --git a/src/CompletionModelRoles.h b/src/CompletionModelRoles.h
index 8505e761..9a735d60 100644
--- a/src/CompletionModelRoles.h
+++ b/src/CompletionModelRoles.h
@@ -12,8 +12,8 @@ namespace CompletionModel {
 // Start at Qt::UserRole * 2 to prevent clashes
 enum Roles
 {
-        CompletionRole = Qt::UserRole * 2, // The string to replace the active completion
-        SearchRole,                        // String completer uses for search
-        SearchRole2,                       // Secondary string completer uses for search
+    CompletionRole = Qt::UserRole * 2, // The string to replace the active completion
+    SearchRole,                        // String completer uses for search
+    SearchRole2,                       // Secondary string completer uses for search
 };
 }
diff --git a/src/CompletionProxyModel.cpp b/src/CompletionProxyModel.cpp
index e68944c7..454f54b7 100644
--- a/src/CompletionProxyModel.cpp
+++ b/src/CompletionProxyModel.cpp
@@ -18,154 +18,154 @@ CompletionProxyModel::CompletionProxyModel(QAbstractItemModel *model,
   , maxMistakes_(max_mistakes)
   , max_completions_(max_completions)
 {
-        setSourceModel(model);
-        QChar splitPoints(' ');
+    setSourceModel(model);
+    QChar splitPoints(' ');
 
-        // insert all the full texts
-        for (int i = 0; i < sourceModel()->rowCount(); i++) {
-                if (static_cast(i) < max_completions_)
-                        mapping.push_back(i);
+    // insert all the full texts
+    for (int i = 0; i < sourceModel()->rowCount(); i++) {
+        if (static_cast(i) < max_completions_)
+            mapping.push_back(i);
 
-                auto string1 = sourceModel()
-                                 ->data(sourceModel()->index(i, 0), CompletionModel::SearchRole)
-                                 .toString()
-                                 .toLower();
-                if (!string1.isEmpty())
-                        trie_.insert(string1.toUcs4(), i);
+        auto string1 = sourceModel()
+                         ->data(sourceModel()->index(i, 0), CompletionModel::SearchRole)
+                         .toString()
+                         .toLower();
+        if (!string1.isEmpty())
+            trie_.insert(string1.toUcs4(), i);
 
-                auto string2 = sourceModel()
-                                 ->data(sourceModel()->index(i, 0), CompletionModel::SearchRole2)
-                                 .toString()
-                                 .toLower();
-                if (!string2.isEmpty())
-                        trie_.insert(string2.toUcs4(), i);
+        auto string2 = sourceModel()
+                         ->data(sourceModel()->index(i, 0), CompletionModel::SearchRole2)
+                         .toString()
+                         .toLower();
+        if (!string2.isEmpty())
+            trie_.insert(string2.toUcs4(), i);
+    }
+
+    // insert the partial matches
+    for (int i = 0; i < sourceModel()->rowCount(); i++) {
+        auto string1 = sourceModel()
+                         ->data(sourceModel()->index(i, 0), CompletionModel::SearchRole)
+                         .toString()
+                         .toLower();
+
+        for (const auto &e : string1.splitRef(splitPoints)) {
+            if (!e.isEmpty()) // NOTE(Nico): Use Qt::SkipEmptyParts in Qt 5.14
+                trie_.insert(e.toUcs4(), i);
         }
 
-        // insert the partial matches
-        for (int i = 0; i < sourceModel()->rowCount(); i++) {
-                auto string1 = sourceModel()
-                                 ->data(sourceModel()->index(i, 0), CompletionModel::SearchRole)
-                                 .toString()
-                                 .toLower();
+        auto string2 = sourceModel()
+                         ->data(sourceModel()->index(i, 0), CompletionModel::SearchRole2)
+                         .toString()
+                         .toLower();
 
-                for (const auto &e : string1.splitRef(splitPoints)) {
-                        if (!e.isEmpty()) // NOTE(Nico): Use Qt::SkipEmptyParts in Qt 5.14
-                                trie_.insert(e.toUcs4(), i);
-                }
-
-                auto string2 = sourceModel()
-                                 ->data(sourceModel()->index(i, 0), CompletionModel::SearchRole2)
-                                 .toString()
-                                 .toLower();
-
-                if (!string2.isEmpty()) {
-                        for (const auto &e : string2.splitRef(splitPoints)) {
-                                if (!e.isEmpty()) // NOTE(Nico): Use Qt::SkipEmptyParts in Qt 5.14
-                                        trie_.insert(e.toUcs4(), i);
-                        }
-                }
+        if (!string2.isEmpty()) {
+            for (const auto &e : string2.splitRef(splitPoints)) {
+                if (!e.isEmpty()) // NOTE(Nico): Use Qt::SkipEmptyParts in Qt 5.14
+                    trie_.insert(e.toUcs4(), i);
+            }
         }
+    }
 
-        connect(
-          this,
-          &CompletionProxyModel::newSearchString,
-          this,
-          [this](QString s) {
-                  s.remove(":");
-                  s.remove("@");
-                  searchString_ = s.toLower();
-                  invalidate();
-          },
-          Qt::QueuedConnection);
+    connect(
+      this,
+      &CompletionProxyModel::newSearchString,
+      this,
+      [this](QString s) {
+          s.remove(":");
+          s.remove("@");
+          searchString_ = s.toLower();
+          invalidate();
+      },
+      Qt::QueuedConnection);
 }
 
 void
 CompletionProxyModel::invalidate()
 {
-        auto key = searchString_.toUcs4();
-        beginResetModel();
-        if (!key.empty()) // return default model data, if no search string
-                mapping = trie_.search(key, max_completions_, maxMistakes_);
-        endResetModel();
+    auto key = searchString_.toUcs4();
+    beginResetModel();
+    if (!key.empty()) // return default model data, if no search string
+        mapping = trie_.search(key, max_completions_, maxMistakes_);
+    endResetModel();
 }
 
 QHash
 CompletionProxyModel::roleNames() const
 {
-        return this->sourceModel()->roleNames();
+    return this->sourceModel()->roleNames();
 }
 
 int
 CompletionProxyModel::rowCount(const QModelIndex &) const
 {
-        if (searchString_.isEmpty())
-                return std::min(static_cast(std::min(max_completions_,
-                                                                  std::numeric_limits::max())),
-                                sourceModel()->rowCount());
-        else
-                return (int)mapping.size();
+    if (searchString_.isEmpty())
+        return std::min(
+          static_cast(std::min(max_completions_, std::numeric_limits::max())),
+          sourceModel()->rowCount());
+    else
+        return (int)mapping.size();
 }
 
 QModelIndex
 CompletionProxyModel::mapFromSource(const QModelIndex &sourceIndex) const
 {
-        // return default model data, if no search string
-        if (searchString_.isEmpty()) {
-                return index(sourceIndex.row(), 0);
-        }
+    // return default model data, if no search string
+    if (searchString_.isEmpty()) {
+        return index(sourceIndex.row(), 0);
+    }
 
-        for (int i = 0; i < (int)mapping.size(); i++) {
-                if (mapping[i] == sourceIndex.row()) {
-                        return index(i, 0);
-                }
+    for (int i = 0; i < (int)mapping.size(); i++) {
+        if (mapping[i] == sourceIndex.row()) {
+            return index(i, 0);
         }
-        return QModelIndex();
+    }
+    return QModelIndex();
 }
 
 QModelIndex
 CompletionProxyModel::mapToSource(const QModelIndex &proxyIndex) const
 {
-        auto row = proxyIndex.row();
+    auto row = proxyIndex.row();
 
-        // return default model data, if no search string
-        if (searchString_.isEmpty()) {
-                return index(row, 0);
-        }
+    // return default model data, if no search string
+    if (searchString_.isEmpty()) {
+        return index(row, 0);
+    }
 
-        if (row < 0 || row >= (int)mapping.size())
-                return QModelIndex();
+    if (row < 0 || row >= (int)mapping.size())
+        return QModelIndex();
 
-        return sourceModel()->index(mapping[row], 0);
+    return sourceModel()->index(mapping[row], 0);
 }
 
 QModelIndex
 CompletionProxyModel::index(int row, int column, const QModelIndex &) const
 {
-        return createIndex(row, column);
+    return createIndex(row, column);
 }
 
 QModelIndex
 CompletionProxyModel::parent(const QModelIndex &) const
 {
-        return QModelIndex{};
+    return QModelIndex{};
 }
 int
 CompletionProxyModel::columnCount(const QModelIndex &) const
 {
-        return sourceModel()->columnCount();
+    return sourceModel()->columnCount();
 }
 
 QVariant
 CompletionProxyModel::completionAt(int i) const
 {
-        if (i >= 0 && i < rowCount())
-                return data(index(i, 0), CompletionModel::CompletionRole);
-        else
-                return {};
+    if (i >= 0 && i < rowCount())
+        return data(index(i, 0), CompletionModel::CompletionRole);
+    else
+        return {};
 }
 
 void
 CompletionProxyModel::setSearchString(QString s)
 {
-        emit newSearchString(s);
+    emit newSearchString(s);
 }
diff --git a/src/CompletionProxyModel.h b/src/CompletionProxyModel.h
index d85d9343..c6331a2d 100644
--- a/src/CompletionProxyModel.h
+++ b/src/CompletionProxyModel.h
@@ -11,179 +11,176 @@
 template
 struct trie
 {
-        std::vector values;
-        std::map next;
+    std::vector values;
+    std::map next;
 
-        void insert(const QVector &keys, const Value &v)
-        {
-                auto t = this;
-                for (const auto k : keys) {
-                        t = &t->next[k];
-                }
-
-                t->values.push_back(v);
+    void insert(const QVector &keys, const Value &v)
+    {
+        auto t = this;
+        for (const auto k : keys) {
+            t = &t->next[k];
         }
 
-        std::vector valuesAndSubvalues(size_t limit = -1) const
-        {
-                std::vector ret;
-                if (limit < 200)
-                        ret.reserve(limit);
+        t->values.push_back(v);
+    }
 
-                for (const auto &v : values) {
-                        if (ret.size() >= limit)
-                                return ret;
-                        else
-                                ret.push_back(v);
-                }
-
-                for (const auto &[k, t] : next) {
-                        (void)k;
-                        if (ret.size() >= limit)
-                                return ret;
-                        else {
-                                auto temp = t.valuesAndSubvalues(limit - ret.size());
-                                for (auto &&v : temp) {
-                                        if (ret.size() >= limit)
-                                                return ret;
-
-                                        if (std::find(ret.begin(), ret.end(), v) == ret.end()) {
-                                                ret.push_back(std::move(v));
-                                        }
-                                }
-                        }
-                }
+    std::vector valuesAndSubvalues(size_t limit = -1) const
+    {
+        std::vector ret;
+        if (limit < 200)
+            ret.reserve(limit);
 
+        for (const auto &v : values) {
+            if (ret.size() >= limit)
                 return ret;
+            else
+                ret.push_back(v);
         }
 
-        std::vector search(const QVector &keys, //< TODO(Nico): replace this with a span
-                                  size_t result_count_limit,
-                                  size_t max_edit_distance_ = 2) const
-        {
-                std::vector ret;
-                if (!result_count_limit)
+        for (const auto &[k, t] : next) {
+            (void)k;
+            if (ret.size() >= limit)
+                return ret;
+            else {
+                auto temp = t.valuesAndSubvalues(limit - ret.size());
+                for (auto &&v : temp) {
+                    if (ret.size() >= limit)
                         return ret;
 
-                if (keys.isEmpty())
-                        return valuesAndSubvalues(result_count_limit);
+                    if (std::find(ret.begin(), ret.end(), v) == ret.end()) {
+                        ret.push_back(std::move(v));
+                    }
+                }
+            }
+        }
 
-                auto append = [&ret, result_count_limit](std::vector &&in) {
-                        for (auto &&v : in) {
-                                if (ret.size() >= result_count_limit)
-                                        return;
+        return ret;
+    }
 
-                                if (std::find(ret.begin(), ret.end(), v) == ret.end()) {
-                                        ret.push_back(std::move(v));
-                                }
+    std::vector search(const QVector &keys, //< TODO(Nico): replace this with a span
+                              size_t result_count_limit,
+                              size_t max_edit_distance_ = 2) const
+    {
+        std::vector ret;
+        if (!result_count_limit)
+            return ret;
+
+        if (keys.isEmpty())
+            return valuesAndSubvalues(result_count_limit);
+
+        auto append = [&ret, result_count_limit](std::vector &&in) {
+            for (auto &&v : in) {
+                if (ret.size() >= result_count_limit)
+                    return;
+
+                if (std::find(ret.begin(), ret.end(), v) == ret.end()) {
+                    ret.push_back(std::move(v));
+                }
+            }
+        };
+
+        auto limit = [&ret, result_count_limit] {
+            return std::min(result_count_limit, (result_count_limit - ret.size()) * 2);
+        };
+
+        // Try first exact matches, then with maximum errors
+        for (size_t max_edit_distance = 0;
+             max_edit_distance <= max_edit_distance_ && ret.size() < result_count_limit;
+             max_edit_distance += 1) {
+            if (max_edit_distance && ret.size() < result_count_limit) {
+                max_edit_distance -= 1;
+
+                // swap chars case
+                if (keys.size() >= 2) {
+                    auto t = this;
+                    for (int i = 1; i >= 0; i--) {
+                        if (auto e = t->next.find(keys[i]); e != t->next.end()) {
+                            t = &e->second;
+                        } else {
+                            t = nullptr;
+                            break;
                         }
-                };
+                    }
 
-                auto limit = [&ret, result_count_limit] {
-                        return std::min(result_count_limit, (result_count_limit - ret.size()) * 2);
-                };
-
-                // Try first exact matches, then with maximum errors
-                for (size_t max_edit_distance = 0;
-                     max_edit_distance <= max_edit_distance_ && ret.size() < result_count_limit;
-                     max_edit_distance += 1) {
-                        if (max_edit_distance && ret.size() < result_count_limit) {
-                                max_edit_distance -= 1;
-
-                                // swap chars case
-                                if (keys.size() >= 2) {
-                                        auto t = this;
-                                        for (int i = 1; i >= 0; i--) {
-                                                if (auto e = t->next.find(keys[i]);
-                                                    e != t->next.end()) {
-                                                        t = &e->second;
-                                                } else {
-                                                        t = nullptr;
-                                                        break;
-                                                }
-                                        }
-
-                                        if (t) {
-                                                append(t->search(
-                                                  keys.mid(2), limit(), max_edit_distance));
-                                        }
-                                }
-
-                                // insert case
-                                for (const auto &[k, t] : this->next) {
-                                        if (k == keys[0])
-                                                continue;
-                                        if (ret.size() >= limit())
-                                                break;
-
-                                        // insert
-                                        append(t.search(keys, limit(), max_edit_distance));
-                                }
-
-                                // delete character case
-                                append(this->search(keys.mid(1), limit(), max_edit_distance));
-
-                                // substitute case
-                                for (const auto &[k, t] : this->next) {
-                                        if (k == keys[0])
-                                                continue;
-                                        if (ret.size() >= limit())
-                                                break;
-
-                                        // substitute
-                                        append(t.search(keys.mid(1), limit(), max_edit_distance));
-                                }
-
-                                max_edit_distance += 1;
-                        }
-
-                        if (auto e = this->next.find(keys[0]); e != this->next.end()) {
-                                append(e->second.search(keys.mid(1), limit(), max_edit_distance));
-                        }
+                    if (t) {
+                        append(t->search(keys.mid(2), limit(), max_edit_distance));
+                    }
                 }
 
-                return ret;
+                // insert case
+                for (const auto &[k, t] : this->next) {
+                    if (k == keys[0])
+                        continue;
+                    if (ret.size() >= limit())
+                        break;
+
+                    // insert
+                    append(t.search(keys, limit(), max_edit_distance));
+                }
+
+                // delete character case
+                append(this->search(keys.mid(1), limit(), max_edit_distance));
+
+                // substitute case
+                for (const auto &[k, t] : this->next) {
+                    if (k == keys[0])
+                        continue;
+                    if (ret.size() >= limit())
+                        break;
+
+                    // substitute
+                    append(t.search(keys.mid(1), limit(), max_edit_distance));
+                }
+
+                max_edit_distance += 1;
+            }
+
+            if (auto e = this->next.find(keys[0]); e != this->next.end()) {
+                append(e->second.search(keys.mid(1), limit(), max_edit_distance));
+            }
         }
+
+        return ret;
+    }
 };
 
 class CompletionProxyModel : public QAbstractProxyModel
 {
-        Q_OBJECT
-        Q_PROPERTY(
-          QString searchString READ searchString WRITE setSearchString NOTIFY newSearchString)
+    Q_OBJECT
+    Q_PROPERTY(QString searchString READ searchString WRITE setSearchString NOTIFY newSearchString)
 public:
-        CompletionProxyModel(QAbstractItemModel *model,
-                             int max_mistakes       = 2,
-                             size_t max_completions = 7,
-                             QObject *parent        = nullptr);
+    CompletionProxyModel(QAbstractItemModel *model,
+                         int max_mistakes       = 2,
+                         size_t max_completions = 7,
+                         QObject *parent        = nullptr);
 
-        void invalidate();
+    void invalidate();
 
-        QHash roleNames() const override;
-        int rowCount(const QModelIndex &parent = QModelIndex()) const override;
-        int columnCount(const QModelIndex &) const override;
+    QHash roleNames() const override;
+    int rowCount(const QModelIndex &parent = QModelIndex()) const override;
+    int columnCount(const QModelIndex &) const override;
 
-        QModelIndex mapFromSource(const QModelIndex &sourceIndex) const override;
-        QModelIndex mapToSource(const QModelIndex &proxyIndex) const override;
+    QModelIndex mapFromSource(const QModelIndex &sourceIndex) const override;
+    QModelIndex mapToSource(const QModelIndex &proxyIndex) const override;
 
-        QModelIndex index(int row,
-                          int column,
-                          const QModelIndex &parent = QModelIndex()) const override;
-        QModelIndex parent(const QModelIndex &) const override;
+    QModelIndex index(int row,
+                      int column,
+                      const QModelIndex &parent = QModelIndex()) const override;
+    QModelIndex parent(const QModelIndex &) const override;
 
 public slots:
-        QVariant completionAt(int i) const;
+    QVariant completionAt(int i) const;
 
-        void setSearchString(QString s);
-        QString searchString() const { return searchString_; }
+    void setSearchString(QString s);
+    QString searchString() const { return searchString_; }
 
 signals:
-        void newSearchString(QString);
+    void newSearchString(QString);
 
 private:
-        QString searchString_;
-        trie trie_;
-        std::vector mapping;
-        int maxMistakes_;
-        size_t max_completions_;
+    QString searchString_;
+    trie trie_;
+    std::vector mapping;
+    int maxMistakes_;
+    size_t max_completions_;
 };
diff --git a/src/DeviceVerificationFlow.cpp b/src/DeviceVerificationFlow.cpp
index 1760ea9a..d4e27022 100644
--- a/src/DeviceVerificationFlow.cpp
+++ b/src/DeviceVerificationFlow.cpp
@@ -38,685 +38,653 @@ DeviceVerificationFlow::DeviceVerificationFlow(QObject *,
   , deviceId(deviceId_)
   , model_(model)
 {
-        timeout = new QTimer(this);
-        timeout->setSingleShot(true);
-        this->sas           = olm::client()->sas_init();
-        this->isMacVerified = false;
+    timeout = new QTimer(this);
+    timeout->setSingleShot(true);
+    this->sas           = olm::client()->sas_init();
+    this->isMacVerified = false;
 
-        auto user_id   = userID.toStdString();
-        this->toClient = mtx::identifiers::parse(user_id);
-        cache::client()->query_keys(
-          user_id, [user_id, this](const UserKeyCache &res, mtx::http::RequestErr err) {
-                  if (err) {
-                          nhlog::net()->warn("failed to query device keys: {},{}",
-                                             mtx::errors::to_string(err->matrix_error.errcode),
-                                             static_cast(err->status_code));
-                          return;
-                  }
+    auto user_id   = userID.toStdString();
+    this->toClient = mtx::identifiers::parse(user_id);
+    cache::client()->query_keys(
+      user_id, [user_id, this](const UserKeyCache &res, mtx::http::RequestErr err) {
+          if (err) {
+              nhlog::net()->warn("failed to query device keys: {},{}",
+                                 mtx::errors::to_string(err->matrix_error.errcode),
+                                 static_cast(err->status_code));
+              return;
+          }
 
-                  if (!this->deviceId.isEmpty() &&
-                      (res.device_keys.find(deviceId.toStdString()) == res.device_keys.end())) {
-                          nhlog::net()->warn("no devices retrieved {}", user_id);
-                          return;
-                  }
+          if (!this->deviceId.isEmpty() &&
+              (res.device_keys.find(deviceId.toStdString()) == res.device_keys.end())) {
+              nhlog::net()->warn("no devices retrieved {}", user_id);
+              return;
+          }
 
-                  this->their_keys = res;
+          this->their_keys = res;
+      });
+
+    cache::client()->query_keys(
+      http::client()->user_id().to_string(),
+      [this](const UserKeyCache &res, mtx::http::RequestErr err) {
+          if (err) {
+              nhlog::net()->warn("failed to query device keys: {},{}",
+                                 mtx::errors::to_string(err->matrix_error.errcode),
+                                 static_cast(err->status_code));
+              return;
+          }
+
+          if (res.master_keys.keys.empty())
+              return;
+
+          if (auto status = cache::verificationStatus(http::client()->user_id().to_string());
+              status && status->user_verified == crypto::Trust::Verified)
+              this->our_trusted_master_key = res.master_keys.keys.begin()->second;
+      });
+
+    if (model) {
+        connect(
+          this->model_, &TimelineModel::updateFlowEventId, this, [this](std::string event_id_) {
+              this->relation.rel_type = mtx::common::RelationType::Reference;
+              this->relation.event_id = event_id_;
+              this->transaction_id    = event_id_;
           });
+    }
 
-        cache::client()->query_keys(
-          http::client()->user_id().to_string(),
-          [this](const UserKeyCache &res, mtx::http::RequestErr err) {
-                  if (err) {
-                          nhlog::net()->warn("failed to query device keys: {},{}",
-                                             mtx::errors::to_string(err->matrix_error.errcode),
-                                             static_cast(err->status_code));
-                          return;
+    connect(timeout, &QTimer::timeout, this, [this]() {
+        nhlog::crypto()->info("verification: timeout");
+        if (state_ != Success && state_ != Failed)
+            this->cancelVerification(DeviceVerificationFlow::Error::Timeout);
+    });
+
+    connect(ChatPage::instance(),
+            &ChatPage::receivedDeviceVerificationStart,
+            this,
+            &DeviceVerificationFlow::handleStartMessage);
+    connect(ChatPage::instance(),
+            &ChatPage::receivedDeviceVerificationAccept,
+            this,
+            [this](const mtx::events::msg::KeyVerificationAccept &msg) {
+                nhlog::crypto()->info("verification: received accept");
+                if (msg.transaction_id.has_value()) {
+                    if (msg.transaction_id.value() != this->transaction_id)
+                        return;
+                } else if (msg.relations.references()) {
+                    if (msg.relations.references() != this->relation.event_id)
+                        return;
+                }
+                if ((msg.key_agreement_protocol == "curve25519-hkdf-sha256") &&
+                    (msg.hash == "sha256") &&
+                    (msg.message_authentication_code == "hkdf-hmac-sha256")) {
+                    this->commitment = msg.commitment;
+                    if (std::find(msg.short_authentication_string.begin(),
+                                  msg.short_authentication_string.end(),
+                                  mtx::events::msg::SASMethods::Emoji) !=
+                        msg.short_authentication_string.end()) {
+                        this->method = mtx::events::msg::SASMethods::Emoji;
+                    } else {
+                        this->method = mtx::events::msg::SASMethods::Decimal;
+                    }
+                    this->mac_method = msg.message_authentication_code;
+                    this->sendVerificationKey();
+                } else {
+                    this->cancelVerification(DeviceVerificationFlow::Error::UnknownMethod);
+                }
+            });
+
+    connect(ChatPage::instance(),
+            &ChatPage::receivedDeviceVerificationCancel,
+            this,
+            [this](const mtx::events::msg::KeyVerificationCancel &msg) {
+                nhlog::crypto()->info("verification: received cancel");
+                if (msg.transaction_id.has_value()) {
+                    if (msg.transaction_id.value() != this->transaction_id)
+                        return;
+                } else if (msg.relations.references()) {
+                    if (msg.relations.references() != this->relation.event_id)
+                        return;
+                }
+                error_ = User;
+                emit errorChanged();
+                setState(Failed);
+            });
+
+    connect(
+      ChatPage::instance(),
+      &ChatPage::receivedDeviceVerificationKey,
+      this,
+      [this](const mtx::events::msg::KeyVerificationKey &msg) {
+          nhlog::crypto()->info("verification: received key");
+          if (msg.transaction_id.has_value()) {
+              if (msg.transaction_id.value() != this->transaction_id)
+                  return;
+          } else if (msg.relations.references()) {
+              if (msg.relations.references() != this->relation.event_id)
+                  return;
+          }
+
+          if (sender) {
+              if (state_ != WaitingForOtherToAccept) {
+                  this->cancelVerification(OutOfOrder);
+                  return;
+              }
+          } else {
+              if (state_ != WaitingForKeys) {
+                  this->cancelVerification(OutOfOrder);
+                  return;
+              }
+          }
+
+          this->sas->set_their_key(msg.key);
+          std::string info;
+          if (this->sender == true) {
+              info = "MATRIX_KEY_VERIFICATION_SAS|" + http::client()->user_id().to_string() + "|" +
+                     http::client()->device_id() + "|" + this->sas->public_key() + "|" +
+                     this->toClient.to_string() + "|" + this->deviceId.toStdString() + "|" +
+                     msg.key + "|" + this->transaction_id;
+          } else {
+              info = "MATRIX_KEY_VERIFICATION_SAS|" + this->toClient.to_string() + "|" +
+                     this->deviceId.toStdString() + "|" + msg.key + "|" +
+                     http::client()->user_id().to_string() + "|" + http::client()->device_id() +
+                     "|" + this->sas->public_key() + "|" + this->transaction_id;
+          }
+
+          nhlog::ui()->info("Info is: '{}'", info);
+
+          if (this->sender == false) {
+              this->sendVerificationKey();
+          } else {
+              if (this->commitment != mtx::crypto::bin2base64_unpadded(mtx::crypto::sha256(
+                                        msg.key + this->canonical_json.dump()))) {
+                  this->cancelVerification(DeviceVerificationFlow::Error::MismatchedCommitment);
+                  return;
+              }
+          }
+
+          if (this->method == mtx::events::msg::SASMethods::Emoji) {
+              this->sasList = this->sas->generate_bytes_emoji(info);
+              setState(CompareEmoji);
+          } else if (this->method == mtx::events::msg::SASMethods::Decimal) {
+              this->sasList = this->sas->generate_bytes_decimal(info);
+              setState(CompareNumber);
+          }
+      });
+
+    connect(
+      ChatPage::instance(),
+      &ChatPage::receivedDeviceVerificationMac,
+      this,
+      [this](const mtx::events::msg::KeyVerificationMac &msg) {
+          nhlog::crypto()->info("verification: received mac");
+          if (msg.transaction_id.has_value()) {
+              if (msg.transaction_id.value() != this->transaction_id)
+                  return;
+          } else if (msg.relations.references()) {
+              if (msg.relations.references() != this->relation.event_id)
+                  return;
+          }
+
+          std::map key_list;
+          std::string key_string;
+          for (const auto &mac : msg.mac) {
+              for (const auto &[deviceid, key] : their_keys.device_keys) {
+                  (void)deviceid;
+                  if (key.keys.count(mac.first))
+                      key_list[mac.first] = key.keys.at(mac.first);
+              }
+
+              if (their_keys.master_keys.keys.count(mac.first))
+                  key_list[mac.first] = their_keys.master_keys.keys[mac.first];
+              if (their_keys.user_signing_keys.keys.count(mac.first))
+                  key_list[mac.first] = their_keys.user_signing_keys.keys[mac.first];
+              if (their_keys.self_signing_keys.keys.count(mac.first))
+                  key_list[mac.first] = their_keys.self_signing_keys.keys[mac.first];
+          }
+          auto macs = key_verification_mac(sas.get(),
+                                           toClient,
+                                           this->deviceId.toStdString(),
+                                           http::client()->user_id(),
+                                           http::client()->device_id(),
+                                           this->transaction_id,
+                                           key_list);
+
+          for (const auto &[key, mac] : macs.mac) {
+              if (mac != msg.mac.at(key)) {
+                  this->cancelVerification(DeviceVerificationFlow::Error::KeyMismatch);
+                  return;
+              }
+          }
+
+          if (msg.keys == macs.keys) {
+              mtx::requests::KeySignaturesUpload req;
+              if (utils::localUser().toStdString() == this->toClient.to_string()) {
+                  // self verification, sign master key with device key, if we
+                  // verified it
+                  for (const auto &mac : msg.mac) {
+                      if (their_keys.master_keys.keys.count(mac.first)) {
+                          json j = their_keys.master_keys;
+                          j.erase("signatures");
+                          j.erase("unsigned");
+                          mtx::crypto::CrossSigningKeys master_key = j;
+                          master_key.signatures[utils::localUser().toStdString()]
+                                               ["ed25519:" + http::client()->device_id()] =
+                            olm::client()->sign_message(j.dump());
+                          req.signatures[utils::localUser().toStdString()]
+                                        [master_key.keys.at(mac.first)] = master_key;
+                      } else if (mac.first == "ed25519:" + this->deviceId.toStdString()) {
+                          // Sign their device key with self signing key
+
+                          auto device_id = this->deviceId.toStdString();
+
+                          if (their_keys.device_keys.count(device_id)) {
+                              json j = their_keys.device_keys.at(device_id);
+                              j.erase("signatures");
+                              j.erase("unsigned");
+
+                              auto secret = cache::secret(
+                                mtx::secret_storage::secrets::cross_signing_self_signing);
+                              if (!secret)
+                                  continue;
+                              auto ssk = mtx::crypto::PkSigning::from_seed(*secret);
+
+                              mtx::crypto::DeviceKeys dev = j;
+                              dev.signatures[utils::localUser().toStdString()]
+                                            ["ed25519:" + ssk.public_key()] = ssk.sign(j.dump());
+
+                              req.signatures[utils::localUser().toStdString()][device_id] = dev;
+                          }
+                      }
                   }
+              } else {
+                  // Sign their master key with user signing key
+                  for (const auto &mac : msg.mac) {
+                      if (their_keys.master_keys.keys.count(mac.first)) {
+                          json j = their_keys.master_keys;
+                          j.erase("signatures");
+                          j.erase("unsigned");
 
-                  if (res.master_keys.keys.empty())
-                          return;
+                          auto secret =
+                            cache::secret(mtx::secret_storage::secrets::cross_signing_user_signing);
+                          if (!secret)
+                              continue;
+                          auto usk = mtx::crypto::PkSigning::from_seed(*secret);
 
-                  if (auto status =
-                        cache::verificationStatus(http::client()->user_id().to_string());
-                      status && status->user_verified == crypto::Trust::Verified)
-                          this->our_trusted_master_key = res.master_keys.keys.begin()->second;
-          });
+                          mtx::crypto::CrossSigningKeys master_key = j;
+                          master_key.signatures[utils::localUser().toStdString()]
+                                               ["ed25519:" + usk.public_key()] = usk.sign(j.dump());
 
-        if (model) {
-                connect(this->model_,
-                        &TimelineModel::updateFlowEventId,
-                        this,
-                        [this](std::string event_id_) {
-                                this->relation.rel_type = mtx::common::RelationType::Reference;
-                                this->relation.event_id = event_id_;
-                                this->transaction_id    = event_id_;
-                        });
-        }
+                          req.signatures[toClient.to_string()][master_key.keys.at(mac.first)] =
+                            master_key;
+                      }
+                  }
+              }
 
-        connect(timeout, &QTimer::timeout, this, [this]() {
-                nhlog::crypto()->info("verification: timeout");
-                if (state_ != Success && state_ != Failed)
-                        this->cancelVerification(DeviceVerificationFlow::Error::Timeout);
-        });
-
-        connect(ChatPage::instance(),
-                &ChatPage::receivedDeviceVerificationStart,
-                this,
-                &DeviceVerificationFlow::handleStartMessage);
-        connect(ChatPage::instance(),
-                &ChatPage::receivedDeviceVerificationAccept,
-                this,
-                [this](const mtx::events::msg::KeyVerificationAccept &msg) {
-                        nhlog::crypto()->info("verification: received accept");
-                        if (msg.transaction_id.has_value()) {
-                                if (msg.transaction_id.value() != this->transaction_id)
-                                        return;
-                        } else if (msg.relations.references()) {
-                                if (msg.relations.references() != this->relation.event_id)
-                                        return;
+              if (!req.signatures.empty()) {
+                  http::client()->keys_signatures_upload(
+                    req,
+                    [](const mtx::responses::KeySignaturesUpload &res, mtx::http::RequestErr err) {
+                        if (err) {
+                            nhlog::net()->error("failed to upload signatures: {},{}",
+                                                mtx::errors::to_string(err->matrix_error.errcode),
+                                                static_cast(err->status_code));
                         }
-                        if ((msg.key_agreement_protocol == "curve25519-hkdf-sha256") &&
-                            (msg.hash == "sha256") &&
-                            (msg.message_authentication_code == "hkdf-hmac-sha256")) {
-                                this->commitment = msg.commitment;
-                                if (std::find(msg.short_authentication_string.begin(),
-                                              msg.short_authentication_string.end(),
-                                              mtx::events::msg::SASMethods::Emoji) !=
-                                    msg.short_authentication_string.end()) {
-                                        this->method = mtx::events::msg::SASMethods::Emoji;
-                                } else {
-                                        this->method = mtx::events::msg::SASMethods::Decimal;
-                                }
-                                this->mac_method = msg.message_authentication_code;
-                                this->sendVerificationKey();
-                        } else {
-                                this->cancelVerification(
-                                  DeviceVerificationFlow::Error::UnknownMethod);
-                        }
-                });
 
-        connect(ChatPage::instance(),
-                &ChatPage::receivedDeviceVerificationCancel,
-                this,
-                [this](const mtx::events::msg::KeyVerificationCancel &msg) {
-                        nhlog::crypto()->info("verification: received cancel");
-                        if (msg.transaction_id.has_value()) {
-                                if (msg.transaction_id.value() != this->transaction_id)
-                                        return;
-                        } else if (msg.relations.references()) {
-                                if (msg.relations.references() != this->relation.event_id)
-                                        return;
-                        }
+                        for (const auto &[user_id, tmp] : res.errors)
+                            for (const auto &[key_id, e] : tmp)
+                                nhlog::net()->error("signature error for user {} and key "
+                                                    "id {}: {}, {}",
+                                                    user_id,
+                                                    key_id,
+                                                    mtx::errors::to_string(e.errcode),
+                                                    e.error);
+                    });
+              }
+
+              this->isMacVerified = true;
+              this->acceptDevice();
+          } else {
+              this->cancelVerification(DeviceVerificationFlow::Error::KeyMismatch);
+          }
+      });
+
+    connect(ChatPage::instance(),
+            &ChatPage::receivedDeviceVerificationReady,
+            this,
+            [this](const mtx::events::msg::KeyVerificationReady &msg) {
+                nhlog::crypto()->info("verification: received ready");
+                if (!sender) {
+                    if (msg.from_device != http::client()->device_id()) {
                         error_ = User;
                         emit errorChanged();
                         setState(Failed);
-                });
+                    }
 
-        connect(ChatPage::instance(),
-                &ChatPage::receivedDeviceVerificationKey,
-                this,
-                [this](const mtx::events::msg::KeyVerificationKey &msg) {
-                        nhlog::crypto()->info("verification: received key");
-                        if (msg.transaction_id.has_value()) {
-                                if (msg.transaction_id.value() != this->transaction_id)
-                                        return;
-                        } else if (msg.relations.references()) {
-                                if (msg.relations.references() != this->relation.event_id)
-                                        return;
-                        }
+                    return;
+                }
 
-                        if (sender) {
-                                if (state_ != WaitingForOtherToAccept) {
-                                        this->cancelVerification(OutOfOrder);
-                                        return;
-                                }
-                        } else {
-                                if (state_ != WaitingForKeys) {
-                                        this->cancelVerification(OutOfOrder);
-                                        return;
-                                }
-                        }
+                if (msg.transaction_id.has_value()) {
+                    if (msg.transaction_id.value() != this->transaction_id)
+                        return;
+                } else if (msg.relations.references()) {
+                    if (msg.relations.references() != this->relation.event_id)
+                        return;
+                    else {
+                        this->deviceId = QString::fromStdString(msg.from_device);
+                    }
+                }
+                this->startVerificationRequest();
+            });
 
-                        this->sas->set_their_key(msg.key);
-                        std::string info;
-                        if (this->sender == true) {
-                                info = "MATRIX_KEY_VERIFICATION_SAS|" +
-                                       http::client()->user_id().to_string() + "|" +
-                                       http::client()->device_id() + "|" + this->sas->public_key() +
-                                       "|" + this->toClient.to_string() + "|" +
-                                       this->deviceId.toStdString() + "|" + msg.key + "|" +
-                                       this->transaction_id;
-                        } else {
-                                info = "MATRIX_KEY_VERIFICATION_SAS|" + this->toClient.to_string() +
-                                       "|" + this->deviceId.toStdString() + "|" + msg.key + "|" +
-                                       http::client()->user_id().to_string() + "|" +
-                                       http::client()->device_id() + "|" + this->sas->public_key() +
-                                       "|" + this->transaction_id;
-                        }
+    connect(ChatPage::instance(),
+            &ChatPage::receivedDeviceVerificationDone,
+            this,
+            [this](const mtx::events::msg::KeyVerificationDone &msg) {
+                nhlog::crypto()->info("verification: receoved done");
+                if (msg.transaction_id.has_value()) {
+                    if (msg.transaction_id.value() != this->transaction_id)
+                        return;
+                } else if (msg.relations.references()) {
+                    if (msg.relations.references() != this->relation.event_id)
+                        return;
+                }
+                nhlog::ui()->info("Flow done on other side");
+            });
 
-                        nhlog::ui()->info("Info is: '{}'", info);
-
-                        if (this->sender == false) {
-                                this->sendVerificationKey();
-                        } else {
-                                if (this->commitment !=
-                                    mtx::crypto::bin2base64_unpadded(
-                                      mtx::crypto::sha256(msg.key + this->canonical_json.dump()))) {
-                                        this->cancelVerification(
-                                          DeviceVerificationFlow::Error::MismatchedCommitment);
-                                        return;
-                                }
-                        }
-
-                        if (this->method == mtx::events::msg::SASMethods::Emoji) {
-                                this->sasList = this->sas->generate_bytes_emoji(info);
-                                setState(CompareEmoji);
-                        } else if (this->method == mtx::events::msg::SASMethods::Decimal) {
-                                this->sasList = this->sas->generate_bytes_decimal(info);
-                                setState(CompareNumber);
-                        }
-                });
-
-        connect(
-          ChatPage::instance(),
-          &ChatPage::receivedDeviceVerificationMac,
-          this,
-          [this](const mtx::events::msg::KeyVerificationMac &msg) {
-                  nhlog::crypto()->info("verification: received mac");
-                  if (msg.transaction_id.has_value()) {
-                          if (msg.transaction_id.value() != this->transaction_id)
-                                  return;
-                  } else if (msg.relations.references()) {
-                          if (msg.relations.references() != this->relation.event_id)
-                                  return;
-                  }
-
-                  std::map key_list;
-                  std::string key_string;
-                  for (const auto &mac : msg.mac) {
-                          for (const auto &[deviceid, key] : their_keys.device_keys) {
-                                  (void)deviceid;
-                                  if (key.keys.count(mac.first))
-                                          key_list[mac.first] = key.keys.at(mac.first);
-                          }
-
-                          if (their_keys.master_keys.keys.count(mac.first))
-                                  key_list[mac.first] = their_keys.master_keys.keys[mac.first];
-                          if (their_keys.user_signing_keys.keys.count(mac.first))
-                                  key_list[mac.first] =
-                                    their_keys.user_signing_keys.keys[mac.first];
-                          if (their_keys.self_signing_keys.keys.count(mac.first))
-                                  key_list[mac.first] =
-                                    their_keys.self_signing_keys.keys[mac.first];
-                  }
-                  auto macs = key_verification_mac(sas.get(),
-                                                   toClient,
-                                                   this->deviceId.toStdString(),
-                                                   http::client()->user_id(),
-                                                   http::client()->device_id(),
-                                                   this->transaction_id,
-                                                   key_list);
-
-                  for (const auto &[key, mac] : macs.mac) {
-                          if (mac != msg.mac.at(key)) {
-                                  this->cancelVerification(
-                                    DeviceVerificationFlow::Error::KeyMismatch);
-                                  return;
-                          }
-                  }
-
-                  if (msg.keys == macs.keys) {
-                          mtx::requests::KeySignaturesUpload req;
-                          if (utils::localUser().toStdString() == this->toClient.to_string()) {
-                                  // self verification, sign master key with device key, if we
-                                  // verified it
-                                  for (const auto &mac : msg.mac) {
-                                          if (their_keys.master_keys.keys.count(mac.first)) {
-                                                  json j = their_keys.master_keys;
-                                                  j.erase("signatures");
-                                                  j.erase("unsigned");
-                                                  mtx::crypto::CrossSigningKeys master_key = j;
-                                                  master_key
-                                                    .signatures[utils::localUser().toStdString()]
-                                                               ["ed25519:" +
-                                                                http::client()->device_id()] =
-                                                    olm::client()->sign_message(j.dump());
-                                                  req.signatures[utils::localUser().toStdString()]
-                                                                [master_key.keys.at(mac.first)] =
-                                                    master_key;
-                                          } else if (mac.first ==
-                                                     "ed25519:" + this->deviceId.toStdString()) {
-                                                  // Sign their device key with self signing key
-
-                                                  auto device_id = this->deviceId.toStdString();
-
-                                                  if (their_keys.device_keys.count(device_id)) {
-                                                          json j =
-                                                            their_keys.device_keys.at(device_id);
-                                                          j.erase("signatures");
-                                                          j.erase("unsigned");
-
-                                                          auto secret = cache::secret(
-                                                            mtx::secret_storage::secrets::
-                                                              cross_signing_self_signing);
-                                                          if (!secret)
-                                                                  continue;
-                                                          auto ssk =
-                                                            mtx::crypto::PkSigning::from_seed(
-                                                              *secret);
-
-                                                          mtx::crypto::DeviceKeys dev = j;
-                                                          dev.signatures
-                                                            [utils::localUser().toStdString()]
-                                                            ["ed25519:" + ssk.public_key()] =
-                                                            ssk.sign(j.dump());
-
-                                                          req.signatures[utils::localUser()
-                                                                           .toStdString()]
-                                                                        [device_id] = dev;
-                                                  }
-                                          }
-                                  }
-                          } else {
-                                  // Sign their master key with user signing key
-                                  for (const auto &mac : msg.mac) {
-                                          if (their_keys.master_keys.keys.count(mac.first)) {
-                                                  json j = their_keys.master_keys;
-                                                  j.erase("signatures");
-                                                  j.erase("unsigned");
-
-                                                  auto secret =
-                                                    cache::secret(mtx::secret_storage::secrets::
-                                                                    cross_signing_user_signing);
-                                                  if (!secret)
-                                                          continue;
-                                                  auto usk =
-                                                    mtx::crypto::PkSigning::from_seed(*secret);
-
-                                                  mtx::crypto::CrossSigningKeys master_key = j;
-                                                  master_key
-                                                    .signatures[utils::localUser().toStdString()]
-                                                               ["ed25519:" + usk.public_key()] =
-                                                    usk.sign(j.dump());
-
-                                                  req.signatures[toClient.to_string()]
-                                                                [master_key.keys.at(mac.first)] =
-                                                    master_key;
-                                          }
-                                  }
-                          }
-
-                          if (!req.signatures.empty()) {
-                                  http::client()->keys_signatures_upload(
-                                    req,
-                                    [](const mtx::responses::KeySignaturesUpload &res,
-                                       mtx::http::RequestErr err) {
-                                            if (err) {
-                                                    nhlog::net()->error(
-                                                      "failed to upload signatures: {},{}",
-                                                      mtx::errors::to_string(
-                                                        err->matrix_error.errcode),
-                                                      static_cast(err->status_code));
-                                            }
-
-                                            for (const auto &[user_id, tmp] : res.errors)
-                                                    for (const auto &[key_id, e] : tmp)
-                                                            nhlog::net()->error(
-                                                              "signature error for user {} and key "
-                                                              "id {}: {}, {}",
-                                                              user_id,
-                                                              key_id,
-                                                              mtx::errors::to_string(e.errcode),
-                                                              e.error);
-                                    });
-                          }
-
-                          this->isMacVerified = true;
-                          this->acceptDevice();
-                  } else {
-                          this->cancelVerification(DeviceVerificationFlow::Error::KeyMismatch);
-                  }
-          });
-
-        connect(ChatPage::instance(),
-                &ChatPage::receivedDeviceVerificationReady,
-                this,
-                [this](const mtx::events::msg::KeyVerificationReady &msg) {
-                        nhlog::crypto()->info("verification: received ready");
-                        if (!sender) {
-                                if (msg.from_device != http::client()->device_id()) {
-                                        error_ = User;
-                                        emit errorChanged();
-                                        setState(Failed);
-                                }
-
-                                return;
-                        }
-
-                        if (msg.transaction_id.has_value()) {
-                                if (msg.transaction_id.value() != this->transaction_id)
-                                        return;
-                        } else if (msg.relations.references()) {
-                                if (msg.relations.references() != this->relation.event_id)
-                                        return;
-                                else {
-                                        this->deviceId = QString::fromStdString(msg.from_device);
-                                }
-                        }
-                        this->startVerificationRequest();
-                });
-
-        connect(ChatPage::instance(),
-                &ChatPage::receivedDeviceVerificationDone,
-                this,
-                [this](const mtx::events::msg::KeyVerificationDone &msg) {
-                        nhlog::crypto()->info("verification: receoved done");
-                        if (msg.transaction_id.has_value()) {
-                                if (msg.transaction_id.value() != this->transaction_id)
-                                        return;
-                        } else if (msg.relations.references()) {
-                                if (msg.relations.references() != this->relation.event_id)
-                                        return;
-                        }
-                        nhlog::ui()->info("Flow done on other side");
-                });
-
-        timeout->start(TIMEOUT);
+    timeout->start(TIMEOUT);
 }
 
 QString
 DeviceVerificationFlow::state()
 {
-        switch (state_) {
-        case PromptStartVerification:
-                return "PromptStartVerification";
-        case CompareEmoji:
-                return "CompareEmoji";
-        case CompareNumber:
-                return "CompareNumber";
-        case WaitingForKeys:
-                return "WaitingForKeys";
-        case WaitingForOtherToAccept:
-                return "WaitingForOtherToAccept";
-        case WaitingForMac:
-                return "WaitingForMac";
-        case Success:
-                return "Success";
-        case Failed:
-                return "Failed";
-        default:
-                return "";
-        }
+    switch (state_) {
+    case PromptStartVerification:
+        return "PromptStartVerification";
+    case CompareEmoji:
+        return "CompareEmoji";
+    case CompareNumber:
+        return "CompareNumber";
+    case WaitingForKeys:
+        return "WaitingForKeys";
+    case WaitingForOtherToAccept:
+        return "WaitingForOtherToAccept";
+    case WaitingForMac:
+        return "WaitingForMac";
+    case Success:
+        return "Success";
+    case Failed:
+        return "Failed";
+    default:
+        return "";
+    }
 }
 
 void
 DeviceVerificationFlow::next()
 {
-        if (sender) {
-                switch (state_) {
-                case PromptStartVerification:
-                        sendVerificationRequest();
-                        break;
-                case CompareEmoji:
-                case CompareNumber:
-                        sendVerificationMac();
-                        break;
-                case WaitingForKeys:
-                case WaitingForOtherToAccept:
-                case WaitingForMac:
-                case Success:
-                case Failed:
-                        nhlog::db()->error("verification: Invalid state transition!");
-                        break;
-                }
-        } else {
-                switch (state_) {
-                case PromptStartVerification:
-                        if (canonical_json.is_null())
-                                sendVerificationReady();
-                        else // legacy path without request and ready
-                                acceptVerificationRequest();
-                        break;
-                case CompareEmoji:
-                        [[fallthrough]];
-                case CompareNumber:
-                        sendVerificationMac();
-                        break;
-                case WaitingForKeys:
-                case WaitingForOtherToAccept:
-                case WaitingForMac:
-                case Success:
-                case Failed:
-                        nhlog::db()->error("verification: Invalid state transition!");
-                        break;
-                }
+    if (sender) {
+        switch (state_) {
+        case PromptStartVerification:
+            sendVerificationRequest();
+            break;
+        case CompareEmoji:
+        case CompareNumber:
+            sendVerificationMac();
+            break;
+        case WaitingForKeys:
+        case WaitingForOtherToAccept:
+        case WaitingForMac:
+        case Success:
+        case Failed:
+            nhlog::db()->error("verification: Invalid state transition!");
+            break;
         }
+    } else {
+        switch (state_) {
+        case PromptStartVerification:
+            if (canonical_json.is_null())
+                sendVerificationReady();
+            else // legacy path without request and ready
+                acceptVerificationRequest();
+            break;
+        case CompareEmoji:
+            [[fallthrough]];
+        case CompareNumber:
+            sendVerificationMac();
+            break;
+        case WaitingForKeys:
+        case WaitingForOtherToAccept:
+        case WaitingForMac:
+        case Success:
+        case Failed:
+            nhlog::db()->error("verification: Invalid state transition!");
+            break;
+        }
+    }
 }
 
 QString
 DeviceVerificationFlow::getUserId()
 {
-        return QString::fromStdString(this->toClient.to_string());
+    return QString::fromStdString(this->toClient.to_string());
 }
 
 QString
 DeviceVerificationFlow::getDeviceId()
 {
-        return this->deviceId;
+    return this->deviceId;
 }
 
 bool
 DeviceVerificationFlow::getSender()
 {
-        return this->sender;
+    return this->sender;
 }
 
 std::vector
 DeviceVerificationFlow::getSasList()
 {
-        return this->sasList;
+    return this->sasList;
 }
 
 bool
 DeviceVerificationFlow::isSelfVerification() const
 {
-        return this->toClient.to_string() == http::client()->user_id().to_string();
+    return this->toClient.to_string() == http::client()->user_id().to_string();
 }
 
 void
 DeviceVerificationFlow::setEventId(std::string event_id_)
 {
-        this->relation.rel_type = mtx::common::RelationType::Reference;
-        this->relation.event_id = event_id_;
-        this->transaction_id    = event_id_;
+    this->relation.rel_type = mtx::common::RelationType::Reference;
+    this->relation.event_id = event_id_;
+    this->transaction_id    = event_id_;
 }
 
 void
 DeviceVerificationFlow::handleStartMessage(const mtx::events::msg::KeyVerificationStart &msg,
                                            std::string)
 {
-        if (msg.transaction_id.has_value()) {
-                if (msg.transaction_id.value() != this->transaction_id)
-                        return;
-        } else if (msg.relations.references()) {
-                if (msg.relations.references() != this->relation.event_id)
-                        return;
-        }
-        if ((std::find(msg.key_agreement_protocols.begin(),
-                       msg.key_agreement_protocols.end(),
-                       "curve25519-hkdf-sha256") != msg.key_agreement_protocols.end()) &&
-            (std::find(msg.hashes.begin(), msg.hashes.end(), "sha256") != msg.hashes.end()) &&
-            (std::find(msg.message_authentication_codes.begin(),
-                       msg.message_authentication_codes.end(),
-                       "hkdf-hmac-sha256") != msg.message_authentication_codes.end())) {
-                if (std::find(msg.short_authentication_string.begin(),
-                              msg.short_authentication_string.end(),
-                              mtx::events::msg::SASMethods::Emoji) !=
-                    msg.short_authentication_string.end()) {
-                        this->method = mtx::events::msg::SASMethods::Emoji;
-                } else if (std::find(msg.short_authentication_string.begin(),
-                                     msg.short_authentication_string.end(),
-                                     mtx::events::msg::SASMethods::Decimal) !=
-                           msg.short_authentication_string.end()) {
-                        this->method = mtx::events::msg::SASMethods::Decimal;
-                } else {
-                        this->cancelVerification(DeviceVerificationFlow::Error::UnknownMethod);
-                        return;
-                }
-                if (!sender)
-                        this->canonical_json = nlohmann::json(msg);
-                else {
-                        if (utils::localUser().toStdString() < this->toClient.to_string()) {
-                                this->canonical_json = nlohmann::json(msg);
-                        }
-                }
-
-                if (state_ != PromptStartVerification)
-                        this->acceptVerificationRequest();
+    if (msg.transaction_id.has_value()) {
+        if (msg.transaction_id.value() != this->transaction_id)
+            return;
+    } else if (msg.relations.references()) {
+        if (msg.relations.references() != this->relation.event_id)
+            return;
+    }
+    if ((std::find(msg.key_agreement_protocols.begin(),
+                   msg.key_agreement_protocols.end(),
+                   "curve25519-hkdf-sha256") != msg.key_agreement_protocols.end()) &&
+        (std::find(msg.hashes.begin(), msg.hashes.end(), "sha256") != msg.hashes.end()) &&
+        (std::find(msg.message_authentication_codes.begin(),
+                   msg.message_authentication_codes.end(),
+                   "hkdf-hmac-sha256") != msg.message_authentication_codes.end())) {
+        if (std::find(msg.short_authentication_string.begin(),
+                      msg.short_authentication_string.end(),
+                      mtx::events::msg::SASMethods::Emoji) !=
+            msg.short_authentication_string.end()) {
+            this->method = mtx::events::msg::SASMethods::Emoji;
+        } else if (std::find(msg.short_authentication_string.begin(),
+                             msg.short_authentication_string.end(),
+                             mtx::events::msg::SASMethods::Decimal) !=
+                   msg.short_authentication_string.end()) {
+            this->method = mtx::events::msg::SASMethods::Decimal;
         } else {
-                this->cancelVerification(DeviceVerificationFlow::Error::UnknownMethod);
+            this->cancelVerification(DeviceVerificationFlow::Error::UnknownMethod);
+            return;
         }
+        if (!sender)
+            this->canonical_json = nlohmann::json(msg);
+        else {
+            if (utils::localUser().toStdString() < this->toClient.to_string()) {
+                this->canonical_json = nlohmann::json(msg);
+            }
+        }
+
+        if (state_ != PromptStartVerification)
+            this->acceptVerificationRequest();
+    } else {
+        this->cancelVerification(DeviceVerificationFlow::Error::UnknownMethod);
+    }
 }
 
 //! accepts a verification
 void
 DeviceVerificationFlow::acceptVerificationRequest()
 {
-        mtx::events::msg::KeyVerificationAccept req;
+    mtx::events::msg::KeyVerificationAccept req;
 
-        req.method                      = mtx::events::msg::VerificationMethods::SASv1;
-        req.key_agreement_protocol      = "curve25519-hkdf-sha256";
-        req.hash                        = "sha256";
-        req.message_authentication_code = "hkdf-hmac-sha256";
-        if (this->method == mtx::events::msg::SASMethods::Emoji)
-                req.short_authentication_string = {mtx::events::msg::SASMethods::Emoji};
-        else if (this->method == mtx::events::msg::SASMethods::Decimal)
-                req.short_authentication_string = {mtx::events::msg::SASMethods::Decimal};
-        req.commitment = mtx::crypto::bin2base64_unpadded(
-          mtx::crypto::sha256(this->sas->public_key() + this->canonical_json.dump()));
+    req.method                      = mtx::events::msg::VerificationMethods::SASv1;
+    req.key_agreement_protocol      = "curve25519-hkdf-sha256";
+    req.hash                        = "sha256";
+    req.message_authentication_code = "hkdf-hmac-sha256";
+    if (this->method == mtx::events::msg::SASMethods::Emoji)
+        req.short_authentication_string = {mtx::events::msg::SASMethods::Emoji};
+    else if (this->method == mtx::events::msg::SASMethods::Decimal)
+        req.short_authentication_string = {mtx::events::msg::SASMethods::Decimal};
+    req.commitment = mtx::crypto::bin2base64_unpadded(
+      mtx::crypto::sha256(this->sas->public_key() + this->canonical_json.dump()));
 
-        send(req);
-        setState(WaitingForKeys);
+    send(req);
+    setState(WaitingForKeys);
 }
 //! responds verification request
 void
 DeviceVerificationFlow::sendVerificationReady()
 {
-        mtx::events::msg::KeyVerificationReady req;
+    mtx::events::msg::KeyVerificationReady req;
 
-        req.from_device = http::client()->device_id();
-        req.methods     = {mtx::events::msg::VerificationMethods::SASv1};
+    req.from_device = http::client()->device_id();
+    req.methods     = {mtx::events::msg::VerificationMethods::SASv1};
 
-        send(req);
-        setState(WaitingForKeys);
+    send(req);
+    setState(WaitingForKeys);
 }
 //! accepts a verification
 void
 DeviceVerificationFlow::sendVerificationDone()
 {
-        mtx::events::msg::KeyVerificationDone req;
+    mtx::events::msg::KeyVerificationDone req;
 
-        send(req);
+    send(req);
 }
 //! starts the verification flow
 void
 DeviceVerificationFlow::startVerificationRequest()
 {
-        mtx::events::msg::KeyVerificationStart req;
+    mtx::events::msg::KeyVerificationStart req;
 
-        req.from_device                  = http::client()->device_id();
-        req.method                       = mtx::events::msg::VerificationMethods::SASv1;
-        req.key_agreement_protocols      = {"curve25519-hkdf-sha256"};
-        req.hashes                       = {"sha256"};
-        req.message_authentication_codes = {"hkdf-hmac-sha256"};
-        req.short_authentication_string  = {mtx::events::msg::SASMethods::Decimal,
-                                           mtx::events::msg::SASMethods::Emoji};
+    req.from_device                  = http::client()->device_id();
+    req.method                       = mtx::events::msg::VerificationMethods::SASv1;
+    req.key_agreement_protocols      = {"curve25519-hkdf-sha256"};
+    req.hashes                       = {"sha256"};
+    req.message_authentication_codes = {"hkdf-hmac-sha256"};
+    req.short_authentication_string  = {mtx::events::msg::SASMethods::Decimal,
+                                       mtx::events::msg::SASMethods::Emoji};
 
-        if (this->type == DeviceVerificationFlow::Type::ToDevice) {
-                mtx::requests::ToDeviceMessages body;
-                req.transaction_id   = this->transaction_id;
-                this->canonical_json = nlohmann::json(req);
-        } else if (this->type == DeviceVerificationFlow::Type::RoomMsg && model_) {
-                req.relations.relations.push_back(this->relation);
-                // Set synthesized to surpress the nheko relation extensions
-                req.relations.synthesized = true;
-                this->canonical_json      = nlohmann::json(req);
-        }
-        send(req);
-        setState(WaitingForOtherToAccept);
+    if (this->type == DeviceVerificationFlow::Type::ToDevice) {
+        mtx::requests::ToDeviceMessages body;
+        req.transaction_id   = this->transaction_id;
+        this->canonical_json = nlohmann::json(req);
+    } else if (this->type == DeviceVerificationFlow::Type::RoomMsg && model_) {
+        req.relations.relations.push_back(this->relation);
+        // Set synthesized to surpress the nheko relation extensions
+        req.relations.synthesized = true;
+        this->canonical_json      = nlohmann::json(req);
+    }
+    send(req);
+    setState(WaitingForOtherToAccept);
 }
 //! sends a verification request
 void
 DeviceVerificationFlow::sendVerificationRequest()
 {
-        mtx::events::msg::KeyVerificationRequest req;
+    mtx::events::msg::KeyVerificationRequest req;
 
-        req.from_device = http::client()->device_id();
-        req.methods     = {mtx::events::msg::VerificationMethods::SASv1};
+    req.from_device = http::client()->device_id();
+    req.methods     = {mtx::events::msg::VerificationMethods::SASv1};
 
-        if (this->type == DeviceVerificationFlow::Type::ToDevice) {
-                QDateTime currentTime = QDateTime::currentDateTimeUtc();
+    if (this->type == DeviceVerificationFlow::Type::ToDevice) {
+        QDateTime currentTime = QDateTime::currentDateTimeUtc();
 
-                req.timestamp = (uint64_t)currentTime.toMSecsSinceEpoch();
+        req.timestamp = (uint64_t)currentTime.toMSecsSinceEpoch();
 
-        } else if (this->type == DeviceVerificationFlow::Type::RoomMsg && model_) {
-                req.to      = this->toClient.to_string();
-                req.msgtype = "m.key.verification.request";
-                req.body = "User is requesting to verify keys with you. However, your client does "
-                           "not support this method, so you will need to use the legacy method of "
-                           "key verification.";
-        }
+    } else if (this->type == DeviceVerificationFlow::Type::RoomMsg && model_) {
+        req.to      = this->toClient.to_string();
+        req.msgtype = "m.key.verification.request";
+        req.body    = "User is requesting to verify keys with you. However, your client does "
+                   "not support this method, so you will need to use the legacy method of "
+                   "key verification.";
+    }
 
-        send(req);
-        setState(WaitingForOtherToAccept);
+    send(req);
+    setState(WaitingForOtherToAccept);
 }
 //! cancels a verification flow
 void
 DeviceVerificationFlow::cancelVerification(DeviceVerificationFlow::Error error_code)
 {
-        if (state_ == State::Success || state_ == State::Failed)
-                return;
+    if (state_ == State::Success || state_ == State::Failed)
+        return;
 
-        mtx::events::msg::KeyVerificationCancel req;
+    mtx::events::msg::KeyVerificationCancel req;
 
-        if (error_code == DeviceVerificationFlow::Error::UnknownMethod) {
-                req.code   = "m.unknown_method";
-                req.reason = "unknown method received";
-        } else if (error_code == DeviceVerificationFlow::Error::MismatchedCommitment) {
-                req.code   = "m.mismatched_commitment";
-                req.reason = "commitment didn't match";
-        } else if (error_code == DeviceVerificationFlow::Error::MismatchedSAS) {
-                req.code   = "m.mismatched_sas";
-                req.reason = "sas didn't match";
-        } else if (error_code == DeviceVerificationFlow::Error::KeyMismatch) {
-                req.code   = "m.key_match";
-                req.reason = "keys did not match";
-        } else if (error_code == DeviceVerificationFlow::Error::Timeout) {
-                req.code   = "m.timeout";
-                req.reason = "timed out";
-        } else if (error_code == DeviceVerificationFlow::Error::User) {
-                req.code   = "m.user";
-                req.reason = "user cancelled the verification";
-        } else if (error_code == DeviceVerificationFlow::Error::OutOfOrder) {
-                req.code   = "m.unexpected_message";
-                req.reason = "received messages out of order";
-        }
+    if (error_code == DeviceVerificationFlow::Error::UnknownMethod) {
+        req.code   = "m.unknown_method";
+        req.reason = "unknown method received";
+    } else if (error_code == DeviceVerificationFlow::Error::MismatchedCommitment) {
+        req.code   = "m.mismatched_commitment";
+        req.reason = "commitment didn't match";
+    } else if (error_code == DeviceVerificationFlow::Error::MismatchedSAS) {
+        req.code   = "m.mismatched_sas";
+        req.reason = "sas didn't match";
+    } else if (error_code == DeviceVerificationFlow::Error::KeyMismatch) {
+        req.code   = "m.key_match";
+        req.reason = "keys did not match";
+    } else if (error_code == DeviceVerificationFlow::Error::Timeout) {
+        req.code   = "m.timeout";
+        req.reason = "timed out";
+    } else if (error_code == DeviceVerificationFlow::Error::User) {
+        req.code   = "m.user";
+        req.reason = "user cancelled the verification";
+    } else if (error_code == DeviceVerificationFlow::Error::OutOfOrder) {
+        req.code   = "m.unexpected_message";
+        req.reason = "received messages out of order";
+    }
 
-        this->error_ = error_code;
-        emit errorChanged();
-        this->setState(Failed);
+    this->error_ = error_code;
+    emit errorChanged();
+    this->setState(Failed);
 
-        send(req);
+    send(req);
 }
 //! sends the verification key
 void
 DeviceVerificationFlow::sendVerificationKey()
 {
-        mtx::events::msg::KeyVerificationKey req;
+    mtx::events::msg::KeyVerificationKey req;
 
-        req.key = this->sas->public_key();
+    req.key = this->sas->public_key();
 
-        send(req);
+    send(req);
 }
 
 mtx::events::msg::KeyVerificationMac
@@ -728,79 +696,78 @@ key_verification_mac(mtx::crypto::SAS *sas,
                      const std::string &transactionId,
                      std::map keys)
 {
-        mtx::events::msg::KeyVerificationMac req;
+    mtx::events::msg::KeyVerificationMac req;
 
-        std::string info = "MATRIX_KEY_VERIFICATION_MAC" + sender.to_string() + senderDevice +
-                           receiver.to_string() + receiverDevice + transactionId;
+    std::string info = "MATRIX_KEY_VERIFICATION_MAC" + sender.to_string() + senderDevice +
+                       receiver.to_string() + receiverDevice + transactionId;
 
-        std::string key_list;
-        bool first = true;
-        for (const auto &[key_id, key] : keys) {
-                req.mac[key_id] = sas->calculate_mac(key, info + key_id);
+    std::string key_list;
+    bool first = true;
+    for (const auto &[key_id, key] : keys) {
+        req.mac[key_id] = sas->calculate_mac(key, info + key_id);
 
-                if (!first)
-                        key_list += ",";
-                key_list += key_id;
-                first = false;
-        }
+        if (!first)
+            key_list += ",";
+        key_list += key_id;
+        first = false;
+    }
 
-        req.keys = sas->calculate_mac(key_list, info + "KEY_IDS");
+    req.keys = sas->calculate_mac(key_list, info + "KEY_IDS");
 
-        return req;
+    return req;
 }
 
 //! sends the mac of the keys
 void
 DeviceVerificationFlow::sendVerificationMac()
 {
-        std::map key_list;
-        key_list["ed25519:" + http::client()->device_id()] = olm::client()->identity_keys().ed25519;
+    std::map key_list;
+    key_list["ed25519:" + http::client()->device_id()] = olm::client()->identity_keys().ed25519;
 
-        // send our master key, if we trust it
-        if (!this->our_trusted_master_key.empty())
-                key_list["ed25519:" + our_trusted_master_key] = our_trusted_master_key;
+    // send our master key, if we trust it
+    if (!this->our_trusted_master_key.empty())
+        key_list["ed25519:" + our_trusted_master_key] = our_trusted_master_key;
 
-        mtx::events::msg::KeyVerificationMac req =
-          key_verification_mac(sas.get(),
-                               http::client()->user_id(),
-                               http::client()->device_id(),
-                               this->toClient,
-                               this->deviceId.toStdString(),
-                               this->transaction_id,
-                               key_list);
+    mtx::events::msg::KeyVerificationMac req = key_verification_mac(sas.get(),
+                                                                    http::client()->user_id(),
+                                                                    http::client()->device_id(),
+                                                                    this->toClient,
+                                                                    this->deviceId.toStdString(),
+                                                                    this->transaction_id,
+                                                                    key_list);
 
-        send(req);
+    send(req);
 
-        setState(WaitingForMac);
-        acceptDevice();
+    setState(WaitingForMac);
+    acceptDevice();
 }
 //! Completes the verification flow
 void
 DeviceVerificationFlow::acceptDevice()
 {
-        if (!isMacVerified) {
-                setState(WaitingForMac);
-        } else if (state_ == WaitingForMac) {
-                cache::markDeviceVerified(this->toClient.to_string(), this->deviceId.toStdString());
-                this->sendVerificationDone();
-                setState(Success);
+    if (!isMacVerified) {
+        setState(WaitingForMac);
+    } else if (state_ == WaitingForMac) {
+        cache::markDeviceVerified(this->toClient.to_string(), this->deviceId.toStdString());
+        this->sendVerificationDone();
+        setState(Success);
 
-                // Request secrets. We should probably check somehow, if a device knowns about the
-                // secrets.
-                if (utils::localUser().toStdString() == this->toClient.to_string() &&
-                    (!cache::secret(mtx::secret_storage::secrets::cross_signing_self_signing) ||
-                     !cache::secret(mtx::secret_storage::secrets::cross_signing_user_signing))) {
-                        olm::request_cross_signing_keys();
-                }
+        // Request secrets. We should probably check somehow, if a device knowns about the
+        // secrets.
+        if (utils::localUser().toStdString() == this->toClient.to_string() &&
+            (!cache::secret(mtx::secret_storage::secrets::cross_signing_self_signing) ||
+             !cache::secret(mtx::secret_storage::secrets::cross_signing_user_signing))) {
+            olm::request_cross_signing_keys();
         }
+    }
 }
 
 void
 DeviceVerificationFlow::unverify()
 {
-        cache::markDeviceUnverified(this->toClient.to_string(), this->deviceId.toStdString());
+    cache::markDeviceUnverified(this->toClient.to_string(), this->deviceId.toStdString());
 
-        emit refreshProfile();
+    emit refreshProfile();
 }
 
 QSharedPointer
@@ -810,22 +777,22 @@ DeviceVerificationFlow::NewInRoomVerification(QObject *parent_,
                                               QString other_user_,
                                               QString event_id_)
 {
-        QSharedPointer flow(
-          new DeviceVerificationFlow(parent_,
-                                     Type::RoomMsg,
-                                     timelineModel_,
-                                     other_user_,
-                                     QString::fromStdString(msg.from_device)));
+    QSharedPointer flow(
+      new DeviceVerificationFlow(parent_,
+                                 Type::RoomMsg,
+                                 timelineModel_,
+                                 other_user_,
+                                 QString::fromStdString(msg.from_device)));
 
-        flow->setEventId(event_id_.toStdString());
+    flow->setEventId(event_id_.toStdString());
 
-        if (std::find(msg.methods.begin(),
-                      msg.methods.end(),
-                      mtx::events::msg::VerificationMethods::SASv1) == msg.methods.end()) {
-                flow->cancelVerification(UnknownMethod);
-        }
+    if (std::find(msg.methods.begin(),
+                  msg.methods.end(),
+                  mtx::events::msg::VerificationMethods::SASv1) == msg.methods.end()) {
+        flow->cancelVerification(UnknownMethod);
+    }
 
-        return flow;
+    return flow;
 }
 QSharedPointer
 DeviceVerificationFlow::NewToDeviceVerification(QObject *parent_,
@@ -833,17 +800,17 @@ DeviceVerificationFlow::NewToDeviceVerification(QObject *parent_,
                                                 QString other_user_,
                                                 QString txn_id_)
 {
-        QSharedPointer flow(new DeviceVerificationFlow(
-          parent_, Type::ToDevice, nullptr, other_user_, QString::fromStdString(msg.from_device)));
-        flow->transaction_id = txn_id_.toStdString();
+    QSharedPointer flow(new DeviceVerificationFlow(
+      parent_, Type::ToDevice, nullptr, other_user_, QString::fromStdString(msg.from_device)));
+    flow->transaction_id = txn_id_.toStdString();
 
-        if (std::find(msg.methods.begin(),
-                      msg.methods.end(),
-                      mtx::events::msg::VerificationMethods::SASv1) == msg.methods.end()) {
-                flow->cancelVerification(UnknownMethod);
-        }
+    if (std::find(msg.methods.begin(),
+                  msg.methods.end(),
+                  mtx::events::msg::VerificationMethods::SASv1) == msg.methods.end()) {
+        flow->cancelVerification(UnknownMethod);
+    }
 
-        return flow;
+    return flow;
 }
 QSharedPointer
 DeviceVerificationFlow::NewToDeviceVerification(QObject *parent_,
@@ -851,32 +818,32 @@ DeviceVerificationFlow::NewToDeviceVerification(QObject *parent_,
                                                 QString other_user_,
                                                 QString txn_id_)
 {
-        QSharedPointer flow(new DeviceVerificationFlow(
-          parent_, Type::ToDevice, nullptr, other_user_, QString::fromStdString(msg.from_device)));
-        flow->transaction_id = txn_id_.toStdString();
+    QSharedPointer flow(new DeviceVerificationFlow(
+      parent_, Type::ToDevice, nullptr, other_user_, QString::fromStdString(msg.from_device)));
+    flow->transaction_id = txn_id_.toStdString();
 
-        flow->handleStartMessage(msg, "");
+    flow->handleStartMessage(msg, "");
 
-        return flow;
+    return flow;
 }
 QSharedPointer
 DeviceVerificationFlow::InitiateUserVerification(QObject *parent_,
                                                  TimelineModel *timelineModel_,
                                                  QString userid)
 {
-        QSharedPointer flow(
-          new DeviceVerificationFlow(parent_, Type::RoomMsg, timelineModel_, userid, ""));
-        flow->sender = true;
-        return flow;
+    QSharedPointer flow(
+      new DeviceVerificationFlow(parent_, Type::RoomMsg, timelineModel_, userid, ""));
+    flow->sender = true;
+    return flow;
 }
 QSharedPointer
 DeviceVerificationFlow::InitiateDeviceVerification(QObject *parent_, QString userid, QString device)
 {
-        QSharedPointer flow(
-          new DeviceVerificationFlow(parent_, Type::ToDevice, nullptr, userid, device));
+    QSharedPointer flow(
+      new DeviceVerificationFlow(parent_, Type::ToDevice, nullptr, userid, device));
 
-        flow->sender         = true;
-        flow->transaction_id = http::client()->generate_txn_id();
+    flow->sender         = true;
+    flow->transaction_id = http::client()->generate_txn_id();
 
-        return flow;
+    return flow;
 }
diff --git a/src/DeviceVerificationFlow.h b/src/DeviceVerificationFlow.h
index 4685a450..f71fa337 100644
--- a/src/DeviceVerificationFlow.h
+++ b/src/DeviceVerificationFlow.h
@@ -60,192 +60,189 @@ using sas_ptr = std::unique_ptr;
 // clang-format on
 class DeviceVerificationFlow : public QObject
 {
-        Q_OBJECT
-        Q_PROPERTY(QString state READ state NOTIFY stateChanged)
-        Q_PROPERTY(Error error READ error NOTIFY errorChanged)
-        Q_PROPERTY(QString userId READ getUserId CONSTANT)
-        Q_PROPERTY(QString deviceId READ getDeviceId CONSTANT)
-        Q_PROPERTY(bool sender READ getSender CONSTANT)
-        Q_PROPERTY(std::vector sasList READ getSasList CONSTANT)
-        Q_PROPERTY(bool isDeviceVerification READ isDeviceVerification CONSTANT)
-        Q_PROPERTY(bool isSelfVerification READ isSelfVerification CONSTANT)
+    Q_OBJECT
+    Q_PROPERTY(QString state READ state NOTIFY stateChanged)
+    Q_PROPERTY(Error error READ error NOTIFY errorChanged)
+    Q_PROPERTY(QString userId READ getUserId CONSTANT)
+    Q_PROPERTY(QString deviceId READ getDeviceId CONSTANT)
+    Q_PROPERTY(bool sender READ getSender CONSTANT)
+    Q_PROPERTY(std::vector sasList READ getSasList CONSTANT)
+    Q_PROPERTY(bool isDeviceVerification READ isDeviceVerification CONSTANT)
+    Q_PROPERTY(bool isSelfVerification READ isSelfVerification CONSTANT)
 
 public:
-        enum State
-        {
-                PromptStartVerification,
-                WaitingForOtherToAccept,
-                WaitingForKeys,
-                CompareEmoji,
-                CompareNumber,
-                WaitingForMac,
-                Success,
-                Failed,
-        };
-        Q_ENUM(State)
+    enum State
+    {
+        PromptStartVerification,
+        WaitingForOtherToAccept,
+        WaitingForKeys,
+        CompareEmoji,
+        CompareNumber,
+        WaitingForMac,
+        Success,
+        Failed,
+    };
+    Q_ENUM(State)
 
-        enum Type
-        {
-                ToDevice,
-                RoomMsg
-        };
+    enum Type
+    {
+        ToDevice,
+        RoomMsg
+    };
 
-        enum Error
-        {
-                UnknownMethod,
-                MismatchedCommitment,
-                MismatchedSAS,
-                KeyMismatch,
-                Timeout,
-                User,
-                OutOfOrder,
-        };
-        Q_ENUM(Error)
+    enum Error
+    {
+        UnknownMethod,
+        MismatchedCommitment,
+        MismatchedSAS,
+        KeyMismatch,
+        Timeout,
+        User,
+        OutOfOrder,
+    };
+    Q_ENUM(Error)
 
-        static QSharedPointer NewInRoomVerification(
-          QObject *parent_,
-          TimelineModel *timelineModel_,
-          const mtx::events::msg::KeyVerificationRequest &msg,
-          QString other_user_,
-          QString event_id_);
-        static QSharedPointer NewToDeviceVerification(
-          QObject *parent_,
-          const mtx::events::msg::KeyVerificationRequest &msg,
-          QString other_user_,
-          QString txn_id_);
-        static QSharedPointer NewToDeviceVerification(
-          QObject *parent_,
-          const mtx::events::msg::KeyVerificationStart &msg,
-          QString other_user_,
-          QString txn_id_);
-        static QSharedPointer
-        InitiateUserVerification(QObject *parent_, TimelineModel *timelineModel_, QString userid);
-        static QSharedPointer InitiateDeviceVerification(QObject *parent,
-                                                                                 QString userid,
-                                                                                 QString device);
+    static QSharedPointer NewInRoomVerification(
+      QObject *parent_,
+      TimelineModel *timelineModel_,
+      const mtx::events::msg::KeyVerificationRequest &msg,
+      QString other_user_,
+      QString event_id_);
+    static QSharedPointer NewToDeviceVerification(
+      QObject *parent_,
+      const mtx::events::msg::KeyVerificationRequest &msg,
+      QString other_user_,
+      QString txn_id_);
+    static QSharedPointer NewToDeviceVerification(
+      QObject *parent_,
+      const mtx::events::msg::KeyVerificationStart &msg,
+      QString other_user_,
+      QString txn_id_);
+    static QSharedPointer
+    InitiateUserVerification(QObject *parent_, TimelineModel *timelineModel_, QString userid);
+    static QSharedPointer InitiateDeviceVerification(QObject *parent,
+                                                                             QString userid,
+                                                                             QString device);
 
-        // getters
-        QString state();
-        Error error() { return error_; }
-        QString getUserId();
-        QString getDeviceId();
-        bool getSender();
-        std::vector getSasList();
-        QString transactionId() { return QString::fromStdString(this->transaction_id); }
-        // setters
-        void setDeviceId(QString deviceID);
-        void setEventId(std::string event_id);
-        bool isDeviceVerification() const
-        {
-                return this->type == DeviceVerificationFlow::Type::ToDevice;
-        }
-        bool isSelfVerification() const;
+    // getters
+    QString state();
+    Error error() { return error_; }
+    QString getUserId();
+    QString getDeviceId();
+    bool getSender();
+    std::vector getSasList();
+    QString transactionId() { return QString::fromStdString(this->transaction_id); }
+    // setters
+    void setDeviceId(QString deviceID);
+    void setEventId(std::string event_id);
+    bool isDeviceVerification() const
+    {
+        return this->type == DeviceVerificationFlow::Type::ToDevice;
+    }
+    bool isSelfVerification() const;
 
-        void callback_fn(const UserKeyCache &res, mtx::http::RequestErr err, std::string user_id);
+    void callback_fn(const UserKeyCache &res, mtx::http::RequestErr err, std::string user_id);
 
 public slots:
-        //! unverifies a device
-        void unverify();
-        //! Continues the flow
-        void next();
-        //! Cancel the flow
-        void cancel() { cancelVerification(User); }
+    //! unverifies a device
+    void unverify();
+    //! Continues the flow
+    void next();
+    //! Cancel the flow
+    void cancel() { cancelVerification(User); }
 
 signals:
-        void refreshProfile();
-        void stateChanged();
-        void errorChanged();
+    void refreshProfile();
+    void stateChanged();
+    void errorChanged();
 
 private:
-        DeviceVerificationFlow(QObject *,
-                               DeviceVerificationFlow::Type flow_type,
-                               TimelineModel *model,
-                               QString userID,
-                               QString deviceId_);
-        void setState(State state)
-        {
-                if (state != state_) {
-                        state_ = state;
-                        emit stateChanged();
-                }
+    DeviceVerificationFlow(QObject *,
+                           DeviceVerificationFlow::Type flow_type,
+                           TimelineModel *model,
+                           QString userID,
+                           QString deviceId_);
+    void setState(State state)
+    {
+        if (state != state_) {
+            state_ = state;
+            emit stateChanged();
+        }
+    }
+
+    void handleStartMessage(const mtx::events::msg::KeyVerificationStart &msg, std::string);
+    //! sends a verification request
+    void sendVerificationRequest();
+    //! accepts a verification request
+    void sendVerificationReady();
+    //! completes the verification flow();
+    void sendVerificationDone();
+    //! accepts a verification
+    void acceptVerificationRequest();
+    //! starts the verification flow
+    void startVerificationRequest();
+    //! cancels a verification flow
+    void cancelVerification(DeviceVerificationFlow::Error error_code);
+    //! sends the verification key
+    void sendVerificationKey();
+    //! sends the mac of the keys
+    void sendVerificationMac();
+    //! Completes the verification flow
+    void acceptDevice();
+
+    std::string transaction_id;
+
+    bool sender;
+    Type type;
+    mtx::identifiers::User toClient;
+    QString deviceId;
+
+    // public part of our master key, when trusted or empty
+    std::string our_trusted_master_key;
+
+    mtx::events::msg::SASMethods method = mtx::events::msg::SASMethods::Emoji;
+    QTimer *timeout                     = nullptr;
+    sas_ptr sas;
+    std::string mac_method;
+    std::string commitment;
+    nlohmann::json canonical_json;
+
+    std::vector sasList;
+    UserKeyCache their_keys;
+    TimelineModel *model_;
+    mtx::common::Relation relation;
+
+    State state_ = PromptStartVerification;
+    Error error_ = UnknownMethod;
+
+    bool isMacVerified = false;
+
+    template
+    void send(T msg)
+    {
+        if (this->type == DeviceVerificationFlow::Type::ToDevice) {
+            mtx::requests::ToDeviceMessages body;
+            msg.transaction_id                           = this->transaction_id;
+            body[this->toClient][deviceId.toStdString()] = msg;
+
+            http::client()->send_to_device(
+              this->transaction_id, body, [](mtx::http::RequestErr err) {
+                  if (err)
+                      nhlog::net()->warn("failed to send verification to_device message: {} {}",
+                                         err->matrix_error.error,
+                                         static_cast(err->status_code));
+              });
+        } else if (this->type == DeviceVerificationFlow::Type::RoomMsg && model_) {
+            if constexpr (!std::is_same_v) {
+                msg.relations.relations.push_back(this->relation);
+                // Set synthesized to surpress the nheko relation extensions
+                msg.relations.synthesized = true;
+            }
+            (model_)->sendMessageEvent(msg, mtx::events::to_device_content_to_type);
         }
 
-        void handleStartMessage(const mtx::events::msg::KeyVerificationStart &msg, std::string);
-        //! sends a verification request
-        void sendVerificationRequest();
-        //! accepts a verification request
-        void sendVerificationReady();
-        //! completes the verification flow();
-        void sendVerificationDone();
-        //! accepts a verification
-        void acceptVerificationRequest();
-        //! starts the verification flow
-        void startVerificationRequest();
-        //! cancels a verification flow
-        void cancelVerification(DeviceVerificationFlow::Error error_code);
-        //! sends the verification key
-        void sendVerificationKey();
-        //! sends the mac of the keys
-        void sendVerificationMac();
-        //! Completes the verification flow
-        void acceptDevice();
-
-        std::string transaction_id;
-
-        bool sender;
-        Type type;
-        mtx::identifiers::User toClient;
-        QString deviceId;
-
-        // public part of our master key, when trusted or empty
-        std::string our_trusted_master_key;
-
-        mtx::events::msg::SASMethods method = mtx::events::msg::SASMethods::Emoji;
-        QTimer *timeout                     = nullptr;
-        sas_ptr sas;
-        std::string mac_method;
-        std::string commitment;
-        nlohmann::json canonical_json;
-
-        std::vector sasList;
-        UserKeyCache their_keys;
-        TimelineModel *model_;
-        mtx::common::Relation relation;
-
-        State state_ = PromptStartVerification;
-        Error error_ = UnknownMethod;
-
-        bool isMacVerified = false;
-
-        template
-        void send(T msg)
-        {
-                if (this->type == DeviceVerificationFlow::Type::ToDevice) {
-                        mtx::requests::ToDeviceMessages body;
-                        msg.transaction_id                           = this->transaction_id;
-                        body[this->toClient][deviceId.toStdString()] = msg;
-
-                        http::client()->send_to_device(
-                          this->transaction_id, body, [](mtx::http::RequestErr err) {
-                                  if (err)
-                                          nhlog::net()->warn(
-                                            "failed to send verification to_device message: {} {}",
-                                            err->matrix_error.error,
-                                            static_cast(err->status_code));
-                          });
-                } else if (this->type == DeviceVerificationFlow::Type::RoomMsg && model_) {
-                        if constexpr (!std::is_same_v) {
-                                msg.relations.relations.push_back(this->relation);
-                                // Set synthesized to surpress the nheko relation extensions
-                                msg.relations.synthesized = true;
-                        }
-                        (model_)->sendMessageEvent(msg, mtx::events::to_device_content_to_type);
-                }
-
-                nhlog::net()->debug(
-                  "Sent verification step: {} in state: {}",
-                  mtx::events::to_string(mtx::events::to_device_content_to_type),
-                  state().toStdString());
-        }
+        nhlog::net()->debug("Sent verification step: {} in state: {}",
+                            mtx::events::to_string(mtx::events::to_device_content_to_type),
+                            state().toStdString());
+    }
 };
diff --git a/src/EventAccessors.cpp b/src/EventAccessors.cpp
index 362bf4e9..d794a384 100644
--- a/src/EventAccessors.cpp
+++ b/src/EventAccessors.cpp
@@ -16,463 +16,460 @@ using is_detected = typename nheko::detail::detector
-        bool operator()(const mtx::events::StateEvent &)
-        {
-                return true;
-        }
-        template
-        bool operator()(const mtx::events::Event &)
-        {
-                return false;
-        }
+    template
+    bool operator()(const mtx::events::StateEvent &)
+    {
+        return true;
+    }
+    template
+    bool operator()(const mtx::events::Event &)
+    {
+        return false;
+    }
 };
 
 struct EventMsgType
 {
-        template
-        using msgtype_t = decltype(E::msgtype);
-        template
-        mtx::events::MessageType operator()(const mtx::events::Event &e)
-        {
-                if constexpr (is_detected::value) {
-                        if constexpr (std::is_same_v,
-                                                     std::remove_cv_t>)
-                                return mtx::events::getMessageType(e.content.msgtype.value());
-                        else if constexpr (std::is_same_v<
-                                             std::string,
-                                             std::remove_cv_t>)
-                                return mtx::events::getMessageType(e.content.msgtype);
-                }
-                return mtx::events::MessageType::Unknown;
+    template
+    using msgtype_t = decltype(E::msgtype);
+    template
+    mtx::events::MessageType operator()(const mtx::events::Event &e)
+    {
+        if constexpr (is_detected::value) {
+            if constexpr (std::is_same_v,
+                                         std::remove_cv_t>)
+                return mtx::events::getMessageType(e.content.msgtype.value());
+            else if constexpr (std::is_same_v>)
+                return mtx::events::getMessageType(e.content.msgtype);
         }
+        return mtx::events::MessageType::Unknown;
+    }
 };
 
 struct EventRoomName
 {
-        template
-        std::string operator()(const T &e)
-        {
-                if constexpr (std::is_same_v, T>)
-                        return e.content.name;
-                return "";
-        }
+    template
+    std::string operator()(const T &e)
+    {
+        if constexpr (std::is_same_v, T>)
+            return e.content.name;
+        return "";
+    }
 };
 
 struct EventRoomTopic
 {
-        template
-        std::string operator()(const T &e)
-        {
-                if constexpr (std::is_same_v, T>)
-                        return e.content.topic;
-                return "";
-        }
+    template
+    std::string operator()(const T &e)
+    {
+        if constexpr (std::is_same_v, T>)
+            return e.content.topic;
+        return "";
+    }
 };
 
 struct CallType
 {
-        template
-        std::string operator()(const T &e)
-        {
-                if constexpr (std::is_same_v,
-                                             T>) {
-                        const char video[]     = "m=video";
-                        const std::string &sdp = e.content.sdp;
-                        return std::search(sdp.cbegin(),
-                                           sdp.cend(),
-                                           std::cbegin(video),
-                                           std::cend(video) - 1,
-                                           [](unsigned char c1, unsigned char c2) {
-                                                   return std::tolower(c1) == std::tolower(c2);
-                                           }) != sdp.cend()
-                                 ? "video"
-                                 : "voice";
-                }
-                return std::string();
+    template
+    std::string operator()(const T &e)
+    {
+        if constexpr (std::is_same_v, T>) {
+            const char video[]     = "m=video";
+            const std::string &sdp = e.content.sdp;
+            return std::search(sdp.cbegin(),
+                               sdp.cend(),
+                               std::cbegin(video),
+                               std::cend(video) - 1,
+                               [](unsigned char c1, unsigned char c2) {
+                                   return std::tolower(c1) == std::tolower(c2);
+                               }) != sdp.cend()
+                     ? "video"
+                     : "voice";
         }
+        return std::string();
+    }
 };
 
 struct EventBody
 {
-        template
-        using body_t = decltype(C::body);
-        template
-        std::string operator()(const mtx::events::Event &e)
-        {
-                if constexpr (is_detected::value) {
-                        if constexpr (std::is_same_v,
-                                                     std::remove_cv_t>)
-                                return e.content.body ? e.content.body.value() : "";
-                        else if constexpr (std::is_same_v<
-                                             std::string,
-                                             std::remove_cv_t>)
-                                return e.content.body;
-                }
-                return "";
+    template
+    using body_t = decltype(C::body);
+    template
+    std::string operator()(const mtx::events::Event &e)
+    {
+        if constexpr (is_detected::value) {
+            if constexpr (std::is_same_v,
+                                         std::remove_cv_t>)
+                return e.content.body ? e.content.body.value() : "";
+            else if constexpr (std::is_same_v>)
+                return e.content.body;
         }
+        return "";
+    }
 };
 
 struct EventFormattedBody
 {
-        template
-        using formatted_body_t = decltype(C::formatted_body);
-        template
-        std::string operator()(const mtx::events::RoomEvent &e)
-        {
-                if constexpr (is_detected::value) {
-                        if (e.content.format == "org.matrix.custom.html")
-                                return e.content.formatted_body;
-                }
-                return "";
+    template
+    using formatted_body_t = decltype(C::formatted_body);
+    template
+    std::string operator()(const mtx::events::RoomEvent &e)
+    {
+        if constexpr (is_detected::value) {
+            if (e.content.format == "org.matrix.custom.html")
+                return e.content.formatted_body;
         }
+        return "";
+    }
 };
 
 struct EventFile
 {
-        template
-        using file_t = decltype(Content::file);
-        template
-        std::optional operator()(const mtx::events::Event &e)
-        {
-                if constexpr (is_detected::value)
-                        return e.content.file;
-                return std::nullopt;
-        }
+    template
+    using file_t = decltype(Content::file);
+    template
+    std::optional operator()(const mtx::events::Event &e)
+    {
+        if constexpr (is_detected::value)
+            return e.content.file;
+        return std::nullopt;
+    }
 };
 
 struct EventUrl
 {
-        template
-        using url_t = decltype(Content::url);
-        template
-        std::string operator()(const mtx::events::Event &e)
-        {
-                if constexpr (is_detected::value) {
-                        if (auto file = EventFile{}(e))
-                                return file->url;
-                        return e.content.url;
-                }
-                return "";
+    template
+    using url_t = decltype(Content::url);
+    template
+    std::string operator()(const mtx::events::Event &e)
+    {
+        if constexpr (is_detected::value) {
+            if (auto file = EventFile{}(e))
+                return file->url;
+            return e.content.url;
         }
+        return "";
+    }
 };
 
 struct EventThumbnailUrl
 {
-        template
-        using thumbnail_url_t = decltype(Content::info.thumbnail_url);
-        template
-        std::string operator()(const mtx::events::Event &e)
-        {
-                if constexpr (is_detected::value) {
-                        return e.content.info.thumbnail_url;
-                }
-                return "";
+    template
+    using thumbnail_url_t = decltype(Content::info.thumbnail_url);
+    template
+    std::string operator()(const mtx::events::Event &e)
+    {
+        if constexpr (is_detected::value) {
+            return e.content.info.thumbnail_url;
         }
+        return "";
+    }
 };
 
 struct EventBlurhash
 {
-        template
-        using blurhash_t = decltype(Content::info.blurhash);
-        template
-        std::string operator()(const mtx::events::Event &e)
-        {
-                if constexpr (is_detected::value) {
-                        return e.content.info.blurhash;
-                }
-                return "";
+    template
+    using blurhash_t = decltype(Content::info.blurhash);
+    template
+    std::string operator()(const mtx::events::Event &e)
+    {
+        if constexpr (is_detected::value) {
+            return e.content.info.blurhash;
         }
+        return "";
+    }
 };
 
 struct EventFilename
 {
-        template
-        std::string operator()(const mtx::events::Event &)
-        {
-                return "";
-        }
-        std::string operator()(const mtx::events::RoomEvent &e)
-        {
-                // body may be the original filename
-                return e.content.body;
-        }
-        std::string operator()(const mtx::events::RoomEvent &e)
-        {
-                // body may be the original filename
-                return e.content.body;
-        }
-        std::string operator()(const mtx::events::RoomEvent &e)
-        {
-                // body may be the original filename
-                return e.content.body;
-        }
-        std::string operator()(const mtx::events::RoomEvent &e)
-        {
-                // body may be the original filename
-                if (!e.content.filename.empty())
-                        return e.content.filename;
-                return e.content.body;
-        }
+    template
+    std::string operator()(const mtx::events::Event &)
+    {
+        return "";
+    }
+    std::string operator()(const mtx::events::RoomEvent &e)
+    {
+        // body may be the original filename
+        return e.content.body;
+    }
+    std::string operator()(const mtx::events::RoomEvent &e)
+    {
+        // body may be the original filename
+        return e.content.body;
+    }
+    std::string operator()(const mtx::events::RoomEvent &e)
+    {
+        // body may be the original filename
+        return e.content.body;
+    }
+    std::string operator()(const mtx::events::RoomEvent &e)
+    {
+        // body may be the original filename
+        if (!e.content.filename.empty())
+            return e.content.filename;
+        return e.content.body;
+    }
 };
 
 struct EventMimeType
 {
-        template
-        using mimetype_t = decltype(Content::info.mimetype);
-        template
-        std::string operator()(const mtx::events::Event &e)
-        {
-                if constexpr (is_detected::value) {
-                        return e.content.info.mimetype;
-                }
-                return "";
+    template
+    using mimetype_t = decltype(Content::info.mimetype);
+    template
+    std::string operator()(const mtx::events::Event &e)
+    {
+        if constexpr (is_detected::value) {
+            return e.content.info.mimetype;
         }
+        return "";
+    }
 };
 
 struct EventFilesize
 {
-        template
-        using filesize_t = decltype(Content::info.size);
-        template
-        int64_t operator()(const mtx::events::RoomEvent &e)
-        {
-                if constexpr (is_detected::value) {
-                        return e.content.info.size;
-                }
-                return 0;
+    template
+    using filesize_t = decltype(Content::info.size);
+    template
+    int64_t operator()(const mtx::events::RoomEvent &e)
+    {
+        if constexpr (is_detected::value) {
+            return e.content.info.size;
         }
+        return 0;
+    }
 };
 
 struct EventRelations
 {
-        template
-        using related_ev_id_t = decltype(Content::relations);
-        template
-        mtx::common::Relations operator()(const mtx::events::Event &e)
-        {
-                if constexpr (is_detected::value) {
-                        return e.content.relations;
-                }
-                return {};
+    template
+    using related_ev_id_t = decltype(Content::relations);
+    template
+    mtx::common::Relations operator()(const mtx::events::Event &e)
+    {
+        if constexpr (is_detected::value) {
+            return e.content.relations;
         }
+        return {};
+    }
 };
 
 struct SetEventRelations
 {
-        mtx::common::Relations new_relations;
-        template
-        using related_ev_id_t = decltype(Content::relations);
-        template
-        void operator()(mtx::events::Event &e)
-        {
-                if constexpr (is_detected::value) {
-                        e.content.relations = std::move(new_relations);
-                }
+    mtx::common::Relations new_relations;
+    template
+    using related_ev_id_t = decltype(Content::relations);
+    template
+    void operator()(mtx::events::Event &e)
+    {
+        if constexpr (is_detected::value) {
+            e.content.relations = std::move(new_relations);
         }
+    }
 };
 
 struct EventTransactionId
 {
-        template
-        std::string operator()(const mtx::events::RoomEvent &e)
-        {
-                return e.unsigned_data.transaction_id;
-        }
-        template
-        std::string operator()(const mtx::events::Event &e)
-        {
-                return e.unsigned_data.transaction_id;
-        }
+    template
+    std::string operator()(const mtx::events::RoomEvent &e)
+    {
+        return e.unsigned_data.transaction_id;
+    }
+    template
+    std::string operator()(const mtx::events::Event &e)
+    {
+        return e.unsigned_data.transaction_id;
+    }
 };
 
 struct EventMediaHeight
 {
-        template
-        using h_t = decltype(Content::info.h);
-        template
-        uint64_t operator()(const mtx::events::Event &e)
-        {
-                if constexpr (is_detected::value) {
-                        return e.content.info.h;
-                }
-                return -1;
+    template
+    using h_t = decltype(Content::info.h);
+    template
+    uint64_t operator()(const mtx::events::Event &e)
+    {
+        if constexpr (is_detected::value) {
+            return e.content.info.h;
         }
+        return -1;
+    }
 };
 
 struct EventMediaWidth
 {
-        template
-        using w_t = decltype(Content::info.w);
-        template
-        uint64_t operator()(const mtx::events::Event &e)
-        {
-                if constexpr (is_detected::value) {
-                        return e.content.info.w;
-                }
-                return -1;
+    template
+    using w_t = decltype(Content::info.w);
+    template
+    uint64_t operator()(const mtx::events::Event &e)
+    {
+        if constexpr (is_detected::value) {
+            return e.content.info.w;
         }
+        return -1;
+    }
 };
 
 template
 double
 eventPropHeight(const mtx::events::RoomEvent &e)
 {
-        auto w = eventWidth(e);
-        if (w == 0)
-                w = 1;
+    auto w = eventWidth(e);
+    if (w == 0)
+        w = 1;
 
-        double prop = eventHeight(e) / (double)w;
+    double prop = eventHeight(e) / (double)w;
 
-        return prop > 0 ? prop : 1.;
+    return prop > 0 ? prop : 1.;
 }
 }
 
 std::string
 mtx::accessors::event_id(const mtx::events::collections::TimelineEvents &event)
 {
-        return std::visit([](const auto e) { return e.event_id; }, event);
+    return std::visit([](const auto e) { return e.event_id; }, event);
 }
 std::string
 mtx::accessors::room_id(const mtx::events::collections::TimelineEvents &event)
 {
-        return std::visit([](const auto e) { return e.room_id; }, event);
+    return std::visit([](const auto e) { return e.room_id; }, event);
 }
 
 std::string
 mtx::accessors::sender(const mtx::events::collections::TimelineEvents &event)
 {
-        return std::visit([](const auto e) { return e.sender; }, event);
+    return std::visit([](const auto e) { return e.sender; }, event);
 }
 
 QDateTime
 mtx::accessors::origin_server_ts(const mtx::events::collections::TimelineEvents &event)
 {
-        return QDateTime::fromMSecsSinceEpoch(
-          std::visit([](const auto e) { return e.origin_server_ts; }, event));
+    return QDateTime::fromMSecsSinceEpoch(
+      std::visit([](const auto e) { return e.origin_server_ts; }, event));
 }
 
 std::string
 mtx::accessors::filename(const mtx::events::collections::TimelineEvents &event)
 {
-        return std::visit(EventFilename{}, event);
+    return std::visit(EventFilename{}, event);
 }
 
 mtx::events::MessageType
 mtx::accessors::msg_type(const mtx::events::collections::TimelineEvents &event)
 {
-        return std::visit(EventMsgType{}, event);
+    return std::visit(EventMsgType{}, event);
 }
 std::string
 mtx::accessors::room_name(const mtx::events::collections::TimelineEvents &event)
 {
-        return std::visit(EventRoomName{}, event);
+    return std::visit(EventRoomName{}, event);
 }
 std::string
 mtx::accessors::room_topic(const mtx::events::collections::TimelineEvents &event)
 {
-        return std::visit(EventRoomTopic{}, event);
+    return std::visit(EventRoomTopic{}, event);
 }
 
 std::string
 mtx::accessors::call_type(const mtx::events::collections::TimelineEvents &event)
 {
-        return std::visit(CallType{}, event);
+    return std::visit(CallType{}, event);
 }
 
 std::string
 mtx::accessors::body(const mtx::events::collections::TimelineEvents &event)
 {
-        return std::visit(EventBody{}, event);
+    return std::visit(EventBody{}, event);
 }
 
 std::string
 mtx::accessors::formatted_body(const mtx::events::collections::TimelineEvents &event)
 {
-        return std::visit(EventFormattedBody{}, event);
+    return std::visit(EventFormattedBody{}, event);
 }
 
 QString
 mtx::accessors::formattedBodyWithFallback(const mtx::events::collections::TimelineEvents &event)
 {
-        auto formatted = formatted_body(event);
-        if (!formatted.empty())
-                return QString::fromStdString(formatted);
-        else
-                return QString::fromStdString(body(event)).toHtmlEscaped().replace("\n", "
"); + auto formatted = formatted_body(event); + if (!formatted.empty()) + return QString::fromStdString(formatted); + else + return QString::fromStdString(body(event)).toHtmlEscaped().replace("\n", "
"); } std::optional mtx::accessors::file(const mtx::events::collections::TimelineEvents &event) { - return std::visit(EventFile{}, event); + return std::visit(EventFile{}, event); } std::string mtx::accessors::url(const mtx::events::collections::TimelineEvents &event) { - return std::visit(EventUrl{}, event); + return std::visit(EventUrl{}, event); } std::string mtx::accessors::thumbnail_url(const mtx::events::collections::TimelineEvents &event) { - return std::visit(EventThumbnailUrl{}, event); + return std::visit(EventThumbnailUrl{}, event); } std::string mtx::accessors::blurhash(const mtx::events::collections::TimelineEvents &event) { - return std::visit(EventBlurhash{}, event); + return std::visit(EventBlurhash{}, event); } std::string mtx::accessors::mimetype(const mtx::events::collections::TimelineEvents &event) { - return std::visit(EventMimeType{}, event); + return std::visit(EventMimeType{}, event); } mtx::common::Relations mtx::accessors::relations(const mtx::events::collections::TimelineEvents &event) { - return std::visit(EventRelations{}, event); + return std::visit(EventRelations{}, event); } void mtx::accessors::set_relations(mtx::events::collections::TimelineEvents &event, mtx::common::Relations relations) { - std::visit(SetEventRelations{std::move(relations)}, event); + std::visit(SetEventRelations{std::move(relations)}, event); } std::string mtx::accessors::transaction_id(const mtx::events::collections::TimelineEvents &event) { - return std::visit(EventTransactionId{}, event); + return std::visit(EventTransactionId{}, event); } int64_t mtx::accessors::filesize(const mtx::events::collections::TimelineEvents &event) { - return std::visit(EventFilesize{}, event); + return std::visit(EventFilesize{}, event); } uint64_t mtx::accessors::media_height(const mtx::events::collections::TimelineEvents &event) { - return std::visit(EventMediaHeight{}, event); + return std::visit(EventMediaHeight{}, event); } uint64_t mtx::accessors::media_width(const mtx::events::collections::TimelineEvents &event) { - return std::visit(EventMediaWidth{}, event); + return std::visit(EventMediaWidth{}, event); } nlohmann::json mtx::accessors::serialize_event(const mtx::events::collections::TimelineEvents &event) { - return std::visit([](const auto &e) { return nlohmann::json(e); }, event); + return std::visit([](const auto &e) { return nlohmann::json(e); }, event); } bool mtx::accessors::is_state_event(const mtx::events::collections::TimelineEvents &event) { - return std::visit(IsStateEvent{}, event); + return std::visit(IsStateEvent{}, event); } diff --git a/src/EventAccessors.h b/src/EventAccessors.h index a58c7de0..c6b8e854 100644 --- a/src/EventAccessors.h +++ b/src/EventAccessors.h @@ -14,24 +14,24 @@ namespace nheko { struct nonesuch { - ~nonesuch() = delete; - nonesuch(nonesuch const &) = delete; - void operator=(nonesuch const &) = delete; + ~nonesuch() = delete; + nonesuch(nonesuch const &) = delete; + void operator=(nonesuch const &) = delete; }; namespace detail { template class Op, class... Args> struct detector { - using value_t = std::false_type; - using type = Default; + using value_t = std::false_type; + using type = Default; }; template class Op, class... Args> struct detector>, Op, Args...> { - using value_t = std::true_type; - using type = Op; + using value_t = std::true_type; + using type = Op; }; } // namespace detail diff --git a/src/ImagePackListModel.cpp b/src/ImagePackListModel.cpp index 6392de22..39e46f01 100644 --- a/src/ImagePackListModel.cpp +++ b/src/ImagePackListModel.cpp @@ -13,82 +13,81 @@ ImagePackListModel::ImagePackListModel(const std::string &roomId, QObject *paren : QAbstractListModel(parent) , room_id(roomId) { - auto packs_ = cache::client()->getImagePacks(room_id, std::nullopt); + auto packs_ = cache::client()->getImagePacks(room_id, std::nullopt); - for (const auto &pack : packs_) { - packs.push_back( - QSharedPointer(new SingleImagePackModel(pack))); - } + for (const auto &pack : packs_) { + packs.push_back(QSharedPointer(new SingleImagePackModel(pack))); + } } int ImagePackListModel::rowCount(const QModelIndex &) const { - return (int)packs.size(); + return (int)packs.size(); } QHash ImagePackListModel::roleNames() const { - return { - {Roles::DisplayName, "displayName"}, - {Roles::AvatarUrl, "avatarUrl"}, - {Roles::FromAccountData, "fromAccountData"}, - {Roles::FromCurrentRoom, "fromCurrentRoom"}, - {Roles::StateKey, "statekey"}, - {Roles::RoomId, "roomid"}, - }; + return { + {Roles::DisplayName, "displayName"}, + {Roles::AvatarUrl, "avatarUrl"}, + {Roles::FromAccountData, "fromAccountData"}, + {Roles::FromCurrentRoom, "fromCurrentRoom"}, + {Roles::StateKey, "statekey"}, + {Roles::RoomId, "roomid"}, + }; } QVariant ImagePackListModel::data(const QModelIndex &index, int role) const { - if (hasIndex(index.row(), index.column(), index.parent())) { - const auto &pack = packs.at(index.row()); - switch (role) { - case Roles::DisplayName: - return pack->packname(); - case Roles::AvatarUrl: - return pack->avatarUrl(); - case Roles::FromAccountData: - return pack->roomid().isEmpty(); - case Roles::FromCurrentRoom: - return pack->roomid().toStdString() == this->room_id; - case Roles::StateKey: - return pack->statekey(); - case Roles::RoomId: - return pack->roomid(); - default: - return {}; - } + if (hasIndex(index.row(), index.column(), index.parent())) { + const auto &pack = packs.at(index.row()); + switch (role) { + case Roles::DisplayName: + return pack->packname(); + case Roles::AvatarUrl: + return pack->avatarUrl(); + case Roles::FromAccountData: + return pack->roomid().isEmpty(); + case Roles::FromCurrentRoom: + return pack->roomid().toStdString() == this->room_id; + case Roles::StateKey: + return pack->statekey(); + case Roles::RoomId: + return pack->roomid(); + default: + return {}; } - return {}; + } + return {}; } SingleImagePackModel * ImagePackListModel::packAt(int row) { - if (row < 0 || static_cast(row) >= packs.size()) - return {}; - auto e = packs.at(row).get(); - QQmlEngine::setObjectOwnership(e, QQmlEngine::CppOwnership); - return e; + if (row < 0 || static_cast(row) >= packs.size()) + return {}; + auto e = packs.at(row).get(); + QQmlEngine::setObjectOwnership(e, QQmlEngine::CppOwnership); + return e; } SingleImagePackModel * ImagePackListModel::newPack(bool inRoom) { - ImagePackInfo info{}; - if (inRoom) - info.source_room = room_id; - return new SingleImagePackModel(info); + ImagePackInfo info{}; + if (inRoom) + info.source_room = room_id; + return new SingleImagePackModel(info); } bool ImagePackListModel::containsAccountPack() const { - for (const auto &p : packs) - if (p->roomid().isEmpty()) - return true; - return false; + for (const auto &p : packs) + if (p->roomid().isEmpty()) + return true; + return false; } diff --git a/src/ImagePackListModel.h b/src/ImagePackListModel.h index 2aa5abb2..0b39729a 100644 --- a/src/ImagePackListModel.h +++ b/src/ImagePackListModel.h @@ -11,31 +11,31 @@ class SingleImagePackModel; class ImagePackListModel : public QAbstractListModel { - Q_OBJECT - Q_PROPERTY(bool containsAccountPack READ containsAccountPack CONSTANT) + Q_OBJECT + Q_PROPERTY(bool containsAccountPack READ containsAccountPack CONSTANT) public: - enum Roles - { - DisplayName = Qt::UserRole, - AvatarUrl, - FromAccountData, - FromCurrentRoom, - StateKey, - RoomId, - }; + enum Roles + { + DisplayName = Qt::UserRole, + AvatarUrl, + FromAccountData, + FromCurrentRoom, + StateKey, + RoomId, + }; - ImagePackListModel(const std::string &roomId, QObject *parent = nullptr); - QHash roleNames() const override; - int rowCount(const QModelIndex &parent = QModelIndex()) const override; - QVariant data(const QModelIndex &index, int role) const override; + ImagePackListModel(const std::string &roomId, QObject *parent = nullptr); + QHash roleNames() const override; + int rowCount(const QModelIndex &parent = QModelIndex()) const override; + QVariant data(const QModelIndex &index, int role) const override; - Q_INVOKABLE SingleImagePackModel *packAt(int row); - Q_INVOKABLE SingleImagePackModel *newPack(bool inRoom); + Q_INVOKABLE SingleImagePackModel *packAt(int row); + Q_INVOKABLE SingleImagePackModel *newPack(bool inRoom); - bool containsAccountPack() const; + bool containsAccountPack() const; private: - std::string room_id; + std::string room_id; - std::vector> packs; + std::vector> packs; }; diff --git a/src/InviteesModel.cpp b/src/InviteesModel.cpp index 27b2116f..e045581a 100644 --- a/src/InviteesModel.cpp +++ b/src/InviteesModel.cpp @@ -16,69 +16,68 @@ InviteesModel::InviteesModel(QObject *parent) void InviteesModel::addUser(QString mxid) { - beginInsertRows(QModelIndex(), invitees_.count(), invitees_.count()); + beginInsertRows(QModelIndex(), invitees_.count(), invitees_.count()); - auto invitee = new Invitee{mxid, this}; - auto indexOfInvitee = invitees_.count(); - connect(invitee, &Invitee::userInfoLoaded, this, [this, indexOfInvitee]() { - emit dataChanged(index(indexOfInvitee), index(indexOfInvitee)); - }); + auto invitee = new Invitee{mxid, this}; + auto indexOfInvitee = invitees_.count(); + connect(invitee, &Invitee::userInfoLoaded, this, [this, indexOfInvitee]() { + emit dataChanged(index(indexOfInvitee), index(indexOfInvitee)); + }); - invitees_.push_back(invitee); + invitees_.push_back(invitee); - endInsertRows(); - emit countChanged(); + endInsertRows(); + emit countChanged(); } QHash InviteesModel::roleNames() const { - return {{Mxid, "mxid"}, {DisplayName, "displayName"}, {AvatarUrl, "avatarUrl"}}; + return {{Mxid, "mxid"}, {DisplayName, "displayName"}, {AvatarUrl, "avatarUrl"}}; } QVariant InviteesModel::data(const QModelIndex &index, int role) const { - if (!index.isValid() || index.row() >= (int)invitees_.size() || index.row() < 0) - return {}; + if (!index.isValid() || index.row() >= (int)invitees_.size() || index.row() < 0) + return {}; - switch (role) { - case Mxid: - return invitees_[index.row()]->mxid_; - case DisplayName: - return invitees_[index.row()]->displayName_; - case AvatarUrl: - return invitees_[index.row()]->avatarUrl_; - default: - return {}; - } + switch (role) { + case Mxid: + return invitees_[index.row()]->mxid_; + case DisplayName: + return invitees_[index.row()]->displayName_; + case AvatarUrl: + return invitees_[index.row()]->avatarUrl_; + default: + return {}; + } } QStringList InviteesModel::mxids() { - QStringList mxidList; - for (int i = 0; i < invitees_.length(); ++i) - mxidList.push_back(invitees_[i]->mxid_); - return mxidList; + QStringList mxidList; + for (int i = 0; i < invitees_.length(); ++i) + mxidList.push_back(invitees_[i]->mxid_); + return mxidList; } Invitee::Invitee(const QString &mxid, QObject *parent) : QObject{parent} , mxid_{mxid} { - http::client()->get_profile( - mxid_.toStdString(), - [this](const mtx::responses::Profile &res, mtx::http::RequestErr err) { - if (err) { - nhlog::net()->warn("failed to retrieve profile info"); - emit userInfoLoaded(); - return; - } + http::client()->get_profile( + mxid_.toStdString(), [this](const mtx::responses::Profile &res, mtx::http::RequestErr err) { + if (err) { + nhlog::net()->warn("failed to retrieve profile info"); + emit userInfoLoaded(); + return; + } - displayName_ = QString::fromStdString(res.display_name); - avatarUrl_ = QString::fromStdString(res.avatar_url); + displayName_ = QString::fromStdString(res.display_name); + avatarUrl_ = QString::fromStdString(res.avatar_url); - emit userInfoLoaded(); - }); + emit userInfoLoaded(); + }); } diff --git a/src/InviteesModel.h b/src/InviteesModel.h index a4e19ebb..fd64116b 100644 --- a/src/InviteesModel.h +++ b/src/InviteesModel.h @@ -10,54 +10,54 @@ class Invitee : public QObject { - Q_OBJECT + Q_OBJECT public: - Invitee(const QString &mxid, QObject *parent = nullptr); + Invitee(const QString &mxid, QObject *parent = nullptr); signals: - void userInfoLoaded(); + void userInfoLoaded(); private: - const QString mxid_; - QString displayName_; - QString avatarUrl_; + const QString mxid_; + QString displayName_; + QString avatarUrl_; - friend class InviteesModel; + friend class InviteesModel; }; class InviteesModel : public QAbstractListModel { - Q_OBJECT + Q_OBJECT - Q_PROPERTY(int count READ rowCount NOTIFY countChanged) + Q_PROPERTY(int count READ rowCount NOTIFY countChanged) public: - enum Roles - { - Mxid, - DisplayName, - AvatarUrl, - }; + enum Roles + { + Mxid, + DisplayName, + AvatarUrl, + }; - InviteesModel(QObject *parent = nullptr); + InviteesModel(QObject *parent = nullptr); - Q_INVOKABLE void addUser(QString mxid); + Q_INVOKABLE void addUser(QString mxid); - QHash roleNames() const override; - int rowCount(const QModelIndex & = QModelIndex()) const override - { - return (int)invitees_.size(); - } - QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; - QStringList mxids(); + QHash roleNames() const override; + int rowCount(const QModelIndex & = QModelIndex()) const override + { + return (int)invitees_.size(); + } + QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; + QStringList mxids(); signals: - void accept(); - void countChanged(); + void accept(); + void countChanged(); private: - QVector invitees_; + QVector invitees_; }; #endif // INVITEESMODEL_H diff --git a/src/JdenticonProvider.cpp b/src/JdenticonProvider.cpp index 23b601fc..e2828286 100644 --- a/src/JdenticonProvider.cpp +++ b/src/JdenticonProvider.cpp @@ -22,20 +22,20 @@ static QPixmap clipRadius(QPixmap img, double radius) { - QPixmap out(img.size()); - out.fill(Qt::transparent); + QPixmap out(img.size()); + out.fill(Qt::transparent); - QPainter painter(&out); - painter.setRenderHint(QPainter::Antialiasing, true); - painter.setRenderHint(QPainter::SmoothPixmapTransform, true); + QPainter painter(&out); + painter.setRenderHint(QPainter::Antialiasing, true); + painter.setRenderHint(QPainter::SmoothPixmapTransform, true); - QPainterPath ppath; - ppath.addRoundedRect(img.rect(), radius, radius, Qt::SizeMode::RelativeSize); + QPainterPath ppath; + ppath.addRoundedRect(img.rect(), radius, radius, Qt::SizeMode::RelativeSize); - painter.setClipPath(ppath); - painter.drawPixmap(img.rect(), img); + painter.setClipPath(ppath); + painter.drawPixmap(img.rect(), img); - return out; + return out; } JdenticonResponse::JdenticonResponse(const QString &key, @@ -49,64 +49,64 @@ JdenticonResponse::JdenticonResponse(const QString &key, , m_pixmap{m_requestedSize} , jdenticonInterface_{Jdenticon::getJdenticonInterface()} { - setAutoDelete(false); + setAutoDelete(false); } void JdenticonResponse::run() { - m_pixmap.fill(Qt::transparent); + m_pixmap.fill(Qt::transparent); - QPainter painter; - painter.begin(&m_pixmap); - painter.setRenderHint(QPainter::Antialiasing, true); - painter.setRenderHint(QPainter::SmoothPixmapTransform, true); + QPainter painter; + painter.begin(&m_pixmap); + painter.setRenderHint(QPainter::Antialiasing, true); + painter.setRenderHint(QPainter::SmoothPixmapTransform, true); - try { - QSvgRenderer renderer{ - jdenticonInterface_->generate(m_key, m_requestedSize.width()).toUtf8()}; - renderer.render(&painter); - } catch (std::exception &e) { - nhlog::ui()->error( - "caught {} in jdenticonprovider, key '{}'", e.what(), m_key.toStdString()); - } + try { + QSvgRenderer renderer{ + jdenticonInterface_->generate(m_key, m_requestedSize.width()).toUtf8()}; + renderer.render(&painter); + } catch (std::exception &e) { + nhlog::ui()->error( + "caught {} in jdenticonprovider, key '{}'", e.what(), m_key.toStdString()); + } - painter.end(); + painter.end(); - m_pixmap = clipRadius(m_pixmap, m_radius); + m_pixmap = clipRadius(m_pixmap, m_radius); - emit finished(); + emit finished(); } namespace Jdenticon { JdenticonInterface * getJdenticonInterface() { - static JdenticonInterface *interface = nullptr; - static bool interfaceExists{true}; + static JdenticonInterface *interface = nullptr; + static bool interfaceExists{true}; - if (interface == nullptr && interfaceExists) { - QDir pluginsDir(qApp->applicationDirPath()); + if (interface == nullptr && interfaceExists) { + QDir pluginsDir(qApp->applicationDirPath()); - bool plugins = pluginsDir.cd("plugins"); - if (plugins) { - for (const QString &fileName : pluginsDir.entryList(QDir::Files)) { - QPluginLoader pluginLoader(pluginsDir.absoluteFilePath(fileName)); - QObject *plugin = pluginLoader.instance(); - if (plugin) { - interface = qobject_cast(plugin); - if (interface) { - nhlog::ui()->info("Loaded jdenticon plugin."); - break; - } - } - } - } else { - nhlog::ui()->info("jdenticon plugin not found."); - interfaceExists = false; + bool plugins = pluginsDir.cd("plugins"); + if (plugins) { + for (const QString &fileName : pluginsDir.entryList(QDir::Files)) { + QPluginLoader pluginLoader(pluginsDir.absoluteFilePath(fileName)); + QObject *plugin = pluginLoader.instance(); + if (plugin) { + interface = qobject_cast(plugin); + if (interface) { + nhlog::ui()->info("Loaded jdenticon plugin."); + break; + } } + } + } else { + nhlog::ui()->info("jdenticon plugin not found."); + interfaceExists = false; } + } - return interface; + return interface; } } diff --git a/src/JdenticonProvider.h b/src/JdenticonProvider.h index bcda29c8..f4ef6d10 100644 --- a/src/JdenticonProvider.h +++ b/src/JdenticonProvider.h @@ -23,59 +23,58 @@ class JdenticonResponse , public QRunnable { public: - JdenticonResponse(const QString &key, bool crop, double radius, const QSize &requestedSize); + JdenticonResponse(const QString &key, bool crop, double radius, const QSize &requestedSize); - QQuickTextureFactory *textureFactory() const override - { - return QQuickTextureFactory::textureFactoryForImage(m_pixmap.toImage()); - } + QQuickTextureFactory *textureFactory() const override + { + return QQuickTextureFactory::textureFactoryForImage(m_pixmap.toImage()); + } - void run() override; + void run() override; - QString m_key; - bool m_crop; - double m_radius; - QSize m_requestedSize; - QPixmap m_pixmap; - JdenticonInterface *jdenticonInterface_ = nullptr; + QString m_key; + bool m_crop; + double m_radius; + QSize m_requestedSize; + QPixmap m_pixmap; + JdenticonInterface *jdenticonInterface_ = nullptr; }; class JdenticonProvider : public QObject , public QQuickAsyncImageProvider { - Q_OBJECT + Q_OBJECT public: - static bool isAvailable() { return Jdenticon::getJdenticonInterface() != nullptr; } + static bool isAvailable() { return Jdenticon::getJdenticonInterface() != nullptr; } public slots: - QQuickImageResponse *requestImageResponse(const QString &id, - const QSize &requestedSize) override - { - auto id_ = id; - bool crop = true; - double radius = 0; + QQuickImageResponse *requestImageResponse(const QString &id, + const QSize &requestedSize) override + { + auto id_ = id; + bool crop = true; + double radius = 0; - auto queryStart = id.lastIndexOf('?'); - if (queryStart != -1) { - id_ = id.left(queryStart); - auto query = id.midRef(queryStart + 1); - auto queryBits = query.split('&'); + auto queryStart = id.lastIndexOf('?'); + if (queryStart != -1) { + id_ = id.left(queryStart); + auto query = id.midRef(queryStart + 1); + auto queryBits = query.split('&'); - for (auto b : queryBits) { - if (b.startsWith("radius=")) { - radius = b.mid(7).toDouble(); - } - } + for (auto b : queryBits) { + if (b.startsWith("radius=")) { + radius = b.mid(7).toDouble(); } - - JdenticonResponse *response = - new JdenticonResponse(id_, crop, radius, requestedSize); - pool.start(response); - return response; + } } + JdenticonResponse *response = new JdenticonResponse(id_, crop, radius, requestedSize); + pool.start(response); + return response; + } + private: - QThreadPool pool; + QThreadPool pool; }; diff --git a/src/Logging.cpp b/src/Logging.cpp index 67bcaf7a..a18a1cee 100644 --- a/src/Logging.cpp +++ b/src/Logging.cpp @@ -25,35 +25,35 @@ constexpr auto MAX_LOG_FILES = 3; void qmlMessageHandler(QtMsgType type, const QMessageLogContext &context, const QString &msg) { - std::string localMsg = msg.toStdString(); - const char *file = context.file ? context.file : ""; - const char *function = context.function ? context.function : ""; + std::string localMsg = msg.toStdString(); + const char *file = context.file ? context.file : ""; + const char *function = context.function ? context.function : ""; - if ( - // The default style has the point size set. If you use pixel size anywhere, you get - // that warning, which is useless, since sometimes you need the pixel size to match the - // text to the size of the outer element for example. This is done in the avatar and - // without that you get one warning for every Avatar displayed, which is stupid! - msg.endsWith(QStringLiteral("Both point size and pixel size set. Using pixel size."))) - return; + if ( + // The default style has the point size set. If you use pixel size anywhere, you get + // that warning, which is useless, since sometimes you need the pixel size to match the + // text to the size of the outer element for example. This is done in the avatar and + // without that you get one warning for every Avatar displayed, which is stupid! + msg.endsWith(QStringLiteral("Both point size and pixel size set. Using pixel size."))) + return; - switch (type) { - case QtDebugMsg: - nhlog::qml()->debug("{} ({}:{}, {})", localMsg, file, context.line, function); - break; - case QtInfoMsg: - nhlog::qml()->info("{} ({}:{}, {})", localMsg, file, context.line, function); - break; - case QtWarningMsg: - nhlog::qml()->warn("{} ({}:{}, {})", localMsg, file, context.line, function); - break; - case QtCriticalMsg: - nhlog::qml()->critical("{} ({}:{}, {})", localMsg, file, context.line, function); - break; - case QtFatalMsg: - nhlog::qml()->critical("{} ({}:{}, {})", localMsg, file, context.line, function); - break; - } + switch (type) { + case QtDebugMsg: + nhlog::qml()->debug("{} ({}:{}, {})", localMsg, file, context.line, function); + break; + case QtInfoMsg: + nhlog::qml()->info("{} ({}:{}, {})", localMsg, file, context.line, function); + break; + case QtWarningMsg: + nhlog::qml()->warn("{} ({}:{}, {})", localMsg, file, context.line, function); + break; + case QtCriticalMsg: + nhlog::qml()->critical("{} ({}:{}, {})", localMsg, file, context.line, function); + break; + case QtFatalMsg: + nhlog::qml()->critical("{} ({}:{}, {})", localMsg, file, context.line, function); + break; + } } } @@ -63,60 +63,59 @@ bool enable_debug_log_from_commandline = false; void init(const std::string &file_path) { - auto file_sink = std::make_shared( - file_path, MAX_FILE_SIZE, MAX_LOG_FILES); + auto file_sink = std::make_shared( + file_path, MAX_FILE_SIZE, MAX_LOG_FILES); - auto console_sink = std::make_shared(); + auto console_sink = std::make_shared(); - std::vector sinks; - sinks.push_back(file_sink); - sinks.push_back(console_sink); + std::vector sinks; + sinks.push_back(file_sink); + sinks.push_back(console_sink); - net_logger = std::make_shared("net", std::begin(sinks), std::end(sinks)); - ui_logger = std::make_shared("ui", std::begin(sinks), std::end(sinks)); - db_logger = std::make_shared("db", std::begin(sinks), std::end(sinks)); - crypto_logger = - std::make_shared("crypto", std::begin(sinks), std::end(sinks)); - qml_logger = std::make_shared("qml", std::begin(sinks), std::end(sinks)); + net_logger = std::make_shared("net", std::begin(sinks), std::end(sinks)); + ui_logger = std::make_shared("ui", std::begin(sinks), std::end(sinks)); + db_logger = std::make_shared("db", std::begin(sinks), std::end(sinks)); + crypto_logger = std::make_shared("crypto", std::begin(sinks), std::end(sinks)); + qml_logger = std::make_shared("qml", std::begin(sinks), std::end(sinks)); - if (nheko::enable_debug_log || enable_debug_log_from_commandline) { - db_logger->set_level(spdlog::level::trace); - ui_logger->set_level(spdlog::level::trace); - crypto_logger->set_level(spdlog::level::trace); - net_logger->set_level(spdlog::level::trace); - qml_logger->set_level(spdlog::level::trace); - } + if (nheko::enable_debug_log || enable_debug_log_from_commandline) { + db_logger->set_level(spdlog::level::trace); + ui_logger->set_level(spdlog::level::trace); + crypto_logger->set_level(spdlog::level::trace); + net_logger->set_level(spdlog::level::trace); + qml_logger->set_level(spdlog::level::trace); + } - qInstallMessageHandler(qmlMessageHandler); + qInstallMessageHandler(qmlMessageHandler); } std::shared_ptr ui() { - return ui_logger; + return ui_logger; } std::shared_ptr net() { - return net_logger; + return net_logger; } std::shared_ptr db() { - return db_logger; + return db_logger; } std::shared_ptr crypto() { - return crypto_logger; + return crypto_logger; } std::shared_ptr qml() { - return qml_logger; + return qml_logger; } } diff --git a/src/LoginPage.cpp b/src/LoginPage.cpp index f53d81ba..64e9c865 100644 --- a/src/LoginPage.cpp +++ b/src/LoginPage.cpp @@ -34,477 +34,468 @@ LoginPage::LoginPage(QWidget *parent) : QWidget(parent) , inferredServerAddress_() { - qRegisterMetaType("LoginPage::LoginMethod"); + qRegisterMetaType("LoginPage::LoginMethod"); - top_layout_ = new QVBoxLayout(); + top_layout_ = new QVBoxLayout(); - top_bar_layout_ = new QHBoxLayout(); - top_bar_layout_->setSpacing(0); - top_bar_layout_->setMargin(0); + top_bar_layout_ = new QHBoxLayout(); + top_bar_layout_->setSpacing(0); + top_bar_layout_->setMargin(0); - back_button_ = new FlatButton(this); - back_button_->setMinimumSize(QSize(30, 30)); + back_button_ = new FlatButton(this); + back_button_->setMinimumSize(QSize(30, 30)); - top_bar_layout_->addWidget(back_button_, 0, Qt::AlignLeft | Qt::AlignVCenter); - top_bar_layout_->addStretch(1); + top_bar_layout_->addWidget(back_button_, 0, Qt::AlignLeft | Qt::AlignVCenter); + top_bar_layout_->addStretch(1); - QIcon icon; - icon.addFile(":/icons/icons/ui/angle-pointing-to-left.png"); + QIcon icon; + icon.addFile(":/icons/icons/ui/angle-pointing-to-left.png"); - back_button_->setIcon(icon); - back_button_->setIconSize(QSize(32, 32)); + back_button_->setIcon(icon); + back_button_->setIconSize(QSize(32, 32)); - QIcon logo; - logo.addFile(":/logos/login.png"); + QIcon logo; + logo.addFile(":/logos/login.png"); - logo_ = new QLabel(this); - logo_->setPixmap(logo.pixmap(128)); + logo_ = new QLabel(this); + logo_->setPixmap(logo.pixmap(128)); - logo_layout_ = new QHBoxLayout(); - logo_layout_->setContentsMargins(0, 0, 0, 20); - logo_layout_->addWidget(logo_, 0, Qt::AlignHCenter); + logo_layout_ = new QHBoxLayout(); + logo_layout_->setContentsMargins(0, 0, 0, 20); + logo_layout_->addWidget(logo_, 0, Qt::AlignHCenter); - form_wrapper_ = new QHBoxLayout(); - form_widget_ = new QWidget(); - form_widget_->setMinimumSize(QSize(350, 200)); + form_wrapper_ = new QHBoxLayout(); + form_widget_ = new QWidget(); + form_widget_->setMinimumSize(QSize(350, 200)); - form_layout_ = new QVBoxLayout(); - form_layout_->setSpacing(20); - form_layout_->setContentsMargins(0, 0, 0, 30); - form_widget_->setLayout(form_layout_); + form_layout_ = new QVBoxLayout(); + form_layout_->setSpacing(20); + form_layout_->setContentsMargins(0, 0, 0, 30); + form_widget_->setLayout(form_layout_); - form_wrapper_->addStretch(1); - form_wrapper_->addWidget(form_widget_); - form_wrapper_->addStretch(1); + form_wrapper_->addStretch(1); + form_wrapper_->addWidget(form_widget_); + form_wrapper_->addStretch(1); - matrixid_input_ = new TextField(this); - matrixid_input_->setLabel(tr("Matrix ID")); - matrixid_input_->setRegexp(QRegularExpression("@.+?:.{3,}")); - matrixid_input_->setPlaceholderText(tr("e.g @joe:matrix.org")); - matrixid_input_->setToolTip( - tr("Your login name. A mxid should start with @ followed by the user id. After the user " - "id you need to include your server name after a :.\nYou can also put your homeserver " - "address there, if your server doesn't support .well-known lookup.\nExample: " - "@user:server.my\nIf Nheko fails to discover your homeserver, it will show you a " - "field to enter the server manually.")); + matrixid_input_ = new TextField(this); + matrixid_input_->setLabel(tr("Matrix ID")); + matrixid_input_->setRegexp(QRegularExpression("@.+?:.{3,}")); + matrixid_input_->setPlaceholderText(tr("e.g @joe:matrix.org")); + matrixid_input_->setToolTip( + tr("Your login name. A mxid should start with @ followed by the user id. After the user " + "id you need to include your server name after a :.\nYou can also put your homeserver " + "address there, if your server doesn't support .well-known lookup.\nExample: " + "@user:server.my\nIf Nheko fails to discover your homeserver, it will show you a " + "field to enter the server manually.")); - spinner_ = new LoadingIndicator(this); - spinner_->setFixedHeight(40); - spinner_->setFixedWidth(40); - spinner_->hide(); + spinner_ = new LoadingIndicator(this); + spinner_->setFixedHeight(40); + spinner_->setFixedWidth(40); + spinner_->hide(); - errorIcon_ = new QLabel(this); - errorIcon_->setPixmap(QPixmap(":/icons/icons/error.png")); - errorIcon_->hide(); + errorIcon_ = new QLabel(this); + errorIcon_->setPixmap(QPixmap(":/icons/icons/error.png")); + errorIcon_->hide(); - matrixidLayout_ = new QHBoxLayout(); - matrixidLayout_->addWidget(matrixid_input_, 0, Qt::AlignVCenter); + matrixidLayout_ = new QHBoxLayout(); + matrixidLayout_->addWidget(matrixid_input_, 0, Qt::AlignVCenter); - QFont font; + QFont font; - error_matrixid_label_ = new QLabel(this); - error_matrixid_label_->setFont(font); - error_matrixid_label_->setWordWrap(true); + error_matrixid_label_ = new QLabel(this); + error_matrixid_label_->setFont(font); + error_matrixid_label_->setWordWrap(true); - password_input_ = new TextField(this); - password_input_->setLabel(tr("Password")); - password_input_->setEchoMode(QLineEdit::Password); - password_input_->setToolTip(tr("Your password.")); + password_input_ = new TextField(this); + password_input_->setLabel(tr("Password")); + password_input_->setEchoMode(QLineEdit::Password); + password_input_->setToolTip(tr("Your password.")); - deviceName_ = new TextField(this); - deviceName_->setLabel(tr("Device name")); - deviceName_->setToolTip( - tr("A name for this device, which will be shown to others, when verifying your devices. " - "If none is provided a default is used.")); + deviceName_ = new TextField(this); + deviceName_->setLabel(tr("Device name")); + deviceName_->setToolTip( + tr("A name for this device, which will be shown to others, when verifying your devices. " + "If none is provided a default is used.")); - serverInput_ = new TextField(this); - serverInput_->setLabel(tr("Homeserver address")); - serverInput_->setPlaceholderText(tr("server.my:8787")); - serverInput_->setToolTip(tr("The address that can be used to contact you homeservers " - "client API.\nExample: https://server.my:8787")); - serverInput_->hide(); + serverInput_ = new TextField(this); + serverInput_->setLabel(tr("Homeserver address")); + serverInput_->setPlaceholderText(tr("server.my:8787")); + serverInput_->setToolTip(tr("The address that can be used to contact you homeservers " + "client API.\nExample: https://server.my:8787")); + serverInput_->hide(); - serverLayout_ = new QHBoxLayout(); - serverLayout_->addWidget(serverInput_, 0, Qt::AlignVCenter); + serverLayout_ = new QHBoxLayout(); + serverLayout_->addWidget(serverInput_, 0, Qt::AlignVCenter); - form_layout_->addLayout(matrixidLayout_); - form_layout_->addWidget(error_matrixid_label_, 0, Qt::AlignHCenter); - form_layout_->addWidget(password_input_); - form_layout_->addWidget(deviceName_, Qt::AlignHCenter); - form_layout_->addLayout(serverLayout_); + form_layout_->addLayout(matrixidLayout_); + form_layout_->addWidget(error_matrixid_label_, 0, Qt::AlignHCenter); + form_layout_->addWidget(password_input_); + form_layout_->addWidget(deviceName_, Qt::AlignHCenter); + form_layout_->addLayout(serverLayout_); - error_matrixid_label_->hide(); + error_matrixid_label_->hide(); - button_layout_ = new QHBoxLayout(); - button_layout_->setSpacing(20); - button_layout_->setContentsMargins(0, 0, 0, 30); + button_layout_ = new QHBoxLayout(); + button_layout_->setSpacing(20); + button_layout_->setContentsMargins(0, 0, 0, 30); - login_button_ = new RaisedButton(tr("LOGIN"), this); - login_button_->setMinimumSize(150, 65); - login_button_->setFontSize(20); - login_button_->setCornerRadius(3); + login_button_ = new RaisedButton(tr("LOGIN"), this); + login_button_->setMinimumSize(150, 65); + login_button_->setFontSize(20); + login_button_->setCornerRadius(3); - sso_login_button_ = new RaisedButton(tr("SSO LOGIN"), this); - sso_login_button_->setMinimumSize(150, 65); - sso_login_button_->setFontSize(20); - sso_login_button_->setCornerRadius(3); - sso_login_button_->setVisible(false); + sso_login_button_ = new RaisedButton(tr("SSO LOGIN"), this); + sso_login_button_->setMinimumSize(150, 65); + sso_login_button_->setFontSize(20); + sso_login_button_->setCornerRadius(3); + sso_login_button_->setVisible(false); - button_layout_->addStretch(1); - button_layout_->addWidget(login_button_); - button_layout_->addWidget(sso_login_button_); - button_layout_->addStretch(1); + button_layout_->addStretch(1); + button_layout_->addWidget(login_button_); + button_layout_->addWidget(sso_login_button_); + button_layout_->addStretch(1); - error_label_ = new QLabel(this); - error_label_->setFont(font); - error_label_->setWordWrap(true); + error_label_ = new QLabel(this); + error_label_->setFont(font); + error_label_->setWordWrap(true); - top_layout_->addLayout(top_bar_layout_); - top_layout_->addStretch(1); - top_layout_->addLayout(logo_layout_); - top_layout_->addLayout(form_wrapper_); - top_layout_->addStretch(1); - top_layout_->addLayout(button_layout_); - top_layout_->addWidget(error_label_, 0, Qt::AlignHCenter); - top_layout_->addStretch(1); + top_layout_->addLayout(top_bar_layout_); + top_layout_->addStretch(1); + top_layout_->addLayout(logo_layout_); + top_layout_->addLayout(form_wrapper_); + top_layout_->addStretch(1); + top_layout_->addLayout(button_layout_); + top_layout_->addWidget(error_label_, 0, Qt::AlignHCenter); + top_layout_->addStretch(1); - setLayout(top_layout_); + setLayout(top_layout_); - connect(this, &LoginPage::versionOkCb, this, &LoginPage::versionOk, Qt::QueuedConnection); - connect( - this, &LoginPage::versionErrorCb, this, &LoginPage::versionError, Qt::QueuedConnection); + connect(this, &LoginPage::versionOkCb, this, &LoginPage::versionOk, Qt::QueuedConnection); + connect(this, &LoginPage::versionErrorCb, this, &LoginPage::versionError, Qt::QueuedConnection); - connect(back_button_, SIGNAL(clicked()), this, SLOT(onBackButtonClicked())); - connect(login_button_, &RaisedButton::clicked, this, [this]() { - onLoginButtonClicked(passwordSupported ? LoginMethod::Password : LoginMethod::SSO); - }); - connect(sso_login_button_, &RaisedButton::clicked, this, [this]() { - onLoginButtonClicked(LoginMethod::SSO); - }); - connect(this, - &LoginPage::showErrorMessage, - this, - static_cast(&LoginPage::showError), - Qt::QueuedConnection); - connect(matrixid_input_, SIGNAL(returnPressed()), login_button_, SLOT(click())); - connect(password_input_, SIGNAL(returnPressed()), login_button_, SLOT(click())); - connect(deviceName_, SIGNAL(returnPressed()), login_button_, SLOT(click())); - connect(serverInput_, SIGNAL(returnPressed()), login_button_, SLOT(click())); - connect(matrixid_input_, SIGNAL(editingFinished()), this, SLOT(onMatrixIdEntered())); - connect(serverInput_, SIGNAL(editingFinished()), this, SLOT(onServerAddressEntered())); + connect(back_button_, SIGNAL(clicked()), this, SLOT(onBackButtonClicked())); + connect(login_button_, &RaisedButton::clicked, this, [this]() { + onLoginButtonClicked(passwordSupported ? LoginMethod::Password : LoginMethod::SSO); + }); + connect(sso_login_button_, &RaisedButton::clicked, this, [this]() { + onLoginButtonClicked(LoginMethod::SSO); + }); + connect(this, + &LoginPage::showErrorMessage, + this, + static_cast(&LoginPage::showError), + Qt::QueuedConnection); + connect(matrixid_input_, SIGNAL(returnPressed()), login_button_, SLOT(click())); + connect(password_input_, SIGNAL(returnPressed()), login_button_, SLOT(click())); + connect(deviceName_, SIGNAL(returnPressed()), login_button_, SLOT(click())); + connect(serverInput_, SIGNAL(returnPressed()), login_button_, SLOT(click())); + connect(matrixid_input_, SIGNAL(editingFinished()), this, SLOT(onMatrixIdEntered())); + connect(serverInput_, SIGNAL(editingFinished()), this, SLOT(onServerAddressEntered())); } void LoginPage::showError(const QString &msg) { - auto rect = QFontMetrics(font()).boundingRect(msg); - int width = rect.width(); - int height = rect.height(); - error_label_->setFixedHeight((int)qCeil(width / 200.0) * height); - error_label_->setText(msg); + auto rect = QFontMetrics(font()).boundingRect(msg); + int width = rect.width(); + int height = rect.height(); + error_label_->setFixedHeight((int)qCeil(width / 200.0) * height); + error_label_->setText(msg); } void LoginPage::showError(QLabel *label, const QString &msg) { - auto rect = QFontMetrics(font()).boundingRect(msg); - int width = rect.width(); - int height = rect.height(); - label->setFixedHeight((int)qCeil(width / 200.0) * height); - label->setText(msg); + auto rect = QFontMetrics(font()).boundingRect(msg); + int width = rect.width(); + int height = rect.height(); + label->setFixedHeight((int)qCeil(width / 200.0) * height); + label->setText(msg); } void LoginPage::onMatrixIdEntered() { - error_label_->setText(""); + error_label_->setText(""); - User user; + User user; - if (!matrixid_input_->isValid()) { - error_matrixid_label_->show(); - showError(error_matrixid_label_, - tr("You have entered an invalid Matrix ID e.g @joe:matrix.org")); - return; + if (!matrixid_input_->isValid()) { + error_matrixid_label_->show(); + showError(error_matrixid_label_, + tr("You have entered an invalid Matrix ID e.g @joe:matrix.org")); + return; + } else { + error_matrixid_label_->setText(""); + error_matrixid_label_->hide(); + } + + try { + user = parse(matrixid_input_->text().toStdString()); + } catch (const std::exception &) { + showError(error_matrixid_label_, + tr("You have entered an invalid Matrix ID e.g @joe:matrix.org")); + return; + } + + QString homeServer = QString::fromStdString(user.hostname()); + if (homeServer != inferredServerAddress_) { + serverInput_->hide(); + serverLayout_->removeWidget(errorIcon_); + errorIcon_->hide(); + if (serverInput_->isVisible()) { + matrixidLayout_->removeWidget(spinner_); + serverLayout_->addWidget(spinner_, 0, Qt::AlignVCenter | Qt::AlignRight); + spinner_->start(); } else { - error_matrixid_label_->setText(""); - error_matrixid_label_->hide(); + serverLayout_->removeWidget(spinner_); + matrixidLayout_->addWidget(spinner_, 0, Qt::AlignVCenter | Qt::AlignRight); + spinner_->start(); } - try { - user = parse(matrixid_input_->text().toStdString()); - } catch (const std::exception &) { - showError(error_matrixid_label_, - tr("You have entered an invalid Matrix ID e.g @joe:matrix.org")); - return; - } + inferredServerAddress_ = homeServer; + serverInput_->setText(homeServer); - QString homeServer = QString::fromStdString(user.hostname()); - if (homeServer != inferredServerAddress_) { - serverInput_->hide(); - serverLayout_->removeWidget(errorIcon_); - errorIcon_->hide(); - if (serverInput_->isVisible()) { - matrixidLayout_->removeWidget(spinner_); - serverLayout_->addWidget(spinner_, 0, Qt::AlignVCenter | Qt::AlignRight); - spinner_->start(); - } else { - serverLayout_->removeWidget(spinner_); - matrixidLayout_->addWidget(spinner_, 0, Qt::AlignVCenter | Qt::AlignRight); - spinner_->start(); - } + http::client()->set_server(user.hostname()); + http::client()->verify_certificates( + !UserSettings::instance()->disableCertificateValidation()); - inferredServerAddress_ = homeServer; - serverInput_->setText(homeServer); + http::client()->well_known( + [this](const mtx::responses::WellKnown &res, mtx::http::RequestErr err) { + if (err) { + if (err->status_code == 404) { + nhlog::net()->info("Autodiscovery: No .well-known."); + checkHomeserverVersion(); + return; + } - http::client()->set_server(user.hostname()); - http::client()->verify_certificates( - !UserSettings::instance()->disableCertificateValidation()); + if (!err->parse_error.empty()) { + emit versionErrorCb(tr("Autodiscovery failed. Received malformed response.")); + nhlog::net()->error("Autodiscovery failed. Received malformed response."); + return; + } - http::client()->well_known([this](const mtx::responses::WellKnown &res, - mtx::http::RequestErr err) { - if (err) { - if (err->status_code == 404) { - nhlog::net()->info("Autodiscovery: No .well-known."); - checkHomeserverVersion(); - return; - } + emit versionErrorCb(tr("Autodiscovery failed. Unknown error when " + "requesting .well-known.")); + nhlog::net()->error("Autodiscovery failed. Unknown error when " + "requesting .well-known. {} {}", + err->status_code, + err->error_code); + return; + } - if (!err->parse_error.empty()) { - emit versionErrorCb( - tr("Autodiscovery failed. Received malformed response.")); - nhlog::net()->error( - "Autodiscovery failed. Received malformed response."); - return; - } - - emit versionErrorCb(tr("Autodiscovery failed. Unknown error when " - "requesting .well-known.")); - nhlog::net()->error("Autodiscovery failed. Unknown error when " - "requesting .well-known. {} {}", - err->status_code, - err->error_code); - return; - } - - nhlog::net()->info("Autodiscovery: Discovered '" + res.homeserver.base_url + - "'"); - http::client()->set_server(res.homeserver.base_url); - checkHomeserverVersion(); - }); - } + nhlog::net()->info("Autodiscovery: Discovered '" + res.homeserver.base_url + "'"); + http::client()->set_server(res.homeserver.base_url); + checkHomeserverVersion(); + }); + } } void LoginPage::checkHomeserverVersion() { - http::client()->versions( - [this](const mtx::responses::Versions &, mtx::http::RequestErr err) { - if (err) { - if (err->status_code == 404) { - emit versionErrorCb(tr("The required endpoints were not found. " - "Possibly not a Matrix server.")); - return; - } + http::client()->versions([this](const mtx::responses::Versions &, mtx::http::RequestErr err) { + if (err) { + if (err->status_code == 404) { + emit versionErrorCb(tr("The required endpoints were not found. " + "Possibly not a Matrix server.")); + return; + } - if (!err->parse_error.empty()) { - emit versionErrorCb(tr("Received malformed response. Make sure " - "the homeserver domain is valid.")); - return; - } + if (!err->parse_error.empty()) { + emit versionErrorCb(tr("Received malformed response. Make sure " + "the homeserver domain is valid.")); + return; + } - emit versionErrorCb(tr( - "An unknown error occured. Make sure the homeserver domain is valid.")); - return; + emit versionErrorCb( + tr("An unknown error occured. Make sure the homeserver domain is valid.")); + return; + } + + http::client()->get_login( + [this](mtx::responses::LoginFlows flows, mtx::http::RequestErr err) { + if (err || flows.flows.empty()) + emit versionOkCb(true, false); + + bool ssoSupported_ = false; + bool passwordSupported_ = false; + for (const auto &flow : flows.flows) { + if (flow.type == mtx::user_interactive::auth_types::sso) { + ssoSupported_ = true; + } else if (flow.type == mtx::user_interactive::auth_types::password) { + passwordSupported_ = true; } - - http::client()->get_login( - [this](mtx::responses::LoginFlows flows, mtx::http::RequestErr err) { - if (err || flows.flows.empty()) - emit versionOkCb(true, false); - - bool ssoSupported_ = false; - bool passwordSupported_ = false; - for (const auto &flow : flows.flows) { - if (flow.type == mtx::user_interactive::auth_types::sso) { - ssoSupported_ = true; - } else if (flow.type == - mtx::user_interactive::auth_types::password) { - passwordSupported_ = true; - } - } - emit versionOkCb(passwordSupported_, ssoSupported_); - }); + } + emit versionOkCb(passwordSupported_, ssoSupported_); }); + }); } void LoginPage::onServerAddressEntered() { - error_label_->setText(""); - http::client()->verify_certificates( - !UserSettings::instance()->disableCertificateValidation()); - http::client()->set_server(serverInput_->text().toStdString()); - checkHomeserverVersion(); + error_label_->setText(""); + http::client()->verify_certificates(!UserSettings::instance()->disableCertificateValidation()); + http::client()->set_server(serverInput_->text().toStdString()); + checkHomeserverVersion(); - serverLayout_->removeWidget(errorIcon_); - errorIcon_->hide(); - serverLayout_->addWidget(spinner_, 0, Qt::AlignVCenter | Qt::AlignRight); - spinner_->start(); + serverLayout_->removeWidget(errorIcon_); + errorIcon_->hide(); + serverLayout_->addWidget(spinner_, 0, Qt::AlignVCenter | Qt::AlignRight); + spinner_->start(); } void LoginPage::versionError(const QString &error) { - showError(error_label_, error); - serverInput_->show(); + showError(error_label_, error); + serverInput_->show(); - spinner_->stop(); - serverLayout_->removeWidget(spinner_); - serverLayout_->addWidget(errorIcon_, 0, Qt::AlignVCenter | Qt::AlignRight); - errorIcon_->show(); - matrixidLayout_->removeWidget(spinner_); + spinner_->stop(); + serverLayout_->removeWidget(spinner_); + serverLayout_->addWidget(errorIcon_, 0, Qt::AlignVCenter | Qt::AlignRight); + errorIcon_->show(); + matrixidLayout_->removeWidget(spinner_); } void LoginPage::versionOk(bool passwordSupported_, bool ssoSupported_) { - passwordSupported = passwordSupported_; - ssoSupported = ssoSupported_; + passwordSupported = passwordSupported_; + ssoSupported = ssoSupported_; - serverLayout_->removeWidget(spinner_); - matrixidLayout_->removeWidget(spinner_); - spinner_->stop(); + serverLayout_->removeWidget(spinner_); + matrixidLayout_->removeWidget(spinner_); + spinner_->stop(); - sso_login_button_->setVisible(ssoSupported); - login_button_->setVisible(passwordSupported); + sso_login_button_->setVisible(ssoSupported); + login_button_->setVisible(passwordSupported); - if (serverInput_->isVisible()) - serverInput_->hide(); + if (serverInput_->isVisible()) + serverInput_->hide(); } void LoginPage::onLoginButtonClicked(LoginMethod loginMethod) { - error_label_->setText(""); - User user; + error_label_->setText(""); + User user; - if (!matrixid_input_->isValid()) { - error_matrixid_label_->show(); - showError(error_matrixid_label_, - tr("You have entered an invalid Matrix ID e.g @joe:matrix.org")); - return; - } else { - error_matrixid_label_->setText(""); - error_matrixid_label_->hide(); - } + if (!matrixid_input_->isValid()) { + error_matrixid_label_->show(); + showError(error_matrixid_label_, + tr("You have entered an invalid Matrix ID e.g @joe:matrix.org")); + return; + } else { + error_matrixid_label_->setText(""); + error_matrixid_label_->hide(); + } - try { - user = parse(matrixid_input_->text().toStdString()); - } catch (const std::exception &) { - showError(error_matrixid_label_, - tr("You have entered an invalid Matrix ID e.g @joe:matrix.org")); - return; - } + try { + user = parse(matrixid_input_->text().toStdString()); + } catch (const std::exception &) { + showError(error_matrixid_label_, + tr("You have entered an invalid Matrix ID e.g @joe:matrix.org")); + return; + } - if (loginMethod == LoginMethod::Password) { - if (password_input_->text().isEmpty()) - return showError(error_label_, tr("Empty password")); + if (loginMethod == LoginMethod::Password) { + if (password_input_->text().isEmpty()) + return showError(error_label_, tr("Empty password")); - http::client()->login( - user.localpart(), - password_input_->text().toStdString(), - deviceName_->text().trimmed().isEmpty() ? initialDeviceName() - : deviceName_->text().toStdString(), - [this](const mtx::responses::Login &res, mtx::http::RequestErr err) { - if (err) { - auto error = err->matrix_error.error; - if (error.empty()) - error = err->parse_error; + http::client()->login( + user.localpart(), + password_input_->text().toStdString(), + deviceName_->text().trimmed().isEmpty() ? initialDeviceName() + : deviceName_->text().toStdString(), + [this](const mtx::responses::Login &res, mtx::http::RequestErr err) { + if (err) { + auto error = err->matrix_error.error; + if (error.empty()) + error = err->parse_error; - showErrorMessage(error_label_, QString::fromStdString(error)); - emit errorOccurred(); - return; - } + showErrorMessage(error_label_, QString::fromStdString(error)); + emit errorOccurred(); + return; + } - if (res.well_known) { - http::client()->set_server(res.well_known->homeserver.base_url); - nhlog::net()->info("Login requested to user server: " + - res.well_known->homeserver.base_url); - } + if (res.well_known) { + http::client()->set_server(res.well_known->homeserver.base_url); + nhlog::net()->info("Login requested to user server: " + + res.well_known->homeserver.base_url); + } - emit loginOk(res); - }); - } else { - auto sso = new SSOHandler(); - connect(sso, &SSOHandler::ssoSuccess, this, [this, sso](std::string token) { - mtx::requests::Login req{}; - req.token = token; - req.type = mtx::user_interactive::auth_types::token; - req.device_id = deviceName_->text().trimmed().isEmpty() - ? initialDeviceName() - : deviceName_->text().toStdString(); - http::client()->login( - req, [this](const mtx::responses::Login &res, mtx::http::RequestErr err) { - if (err) { - showErrorMessage( - error_label_, - QString::fromStdString(err->matrix_error.error)); - emit errorOccurred(); - return; - } + emit loginOk(res); + }); + } else { + auto sso = new SSOHandler(); + connect(sso, &SSOHandler::ssoSuccess, this, [this, sso](std::string token) { + mtx::requests::Login req{}; + req.token = token; + req.type = mtx::user_interactive::auth_types::token; + req.device_id = deviceName_->text().trimmed().isEmpty() + ? initialDeviceName() + : deviceName_->text().toStdString(); + http::client()->login( + req, [this](const mtx::responses::Login &res, mtx::http::RequestErr err) { + if (err) { + showErrorMessage(error_label_, + QString::fromStdString(err->matrix_error.error)); + emit errorOccurred(); + return; + } - if (res.well_known) { - http::client()->set_server( - res.well_known->homeserver.base_url); - nhlog::net()->info("Login requested to user server: " + - res.well_known->homeserver.base_url); - } + if (res.well_known) { + http::client()->set_server(res.well_known->homeserver.base_url); + nhlog::net()->info("Login requested to user server: " + + res.well_known->homeserver.base_url); + } - emit loginOk(res); - }); - sso->deleteLater(); - }); - connect(sso, &SSOHandler::ssoFailed, this, [this, sso]() { - showErrorMessage(error_label_, tr("SSO login failed")); - emit errorOccurred(); - sso->deleteLater(); - }); + emit loginOk(res); + }); + sso->deleteLater(); + }); + connect(sso, &SSOHandler::ssoFailed, this, [this, sso]() { + showErrorMessage(error_label_, tr("SSO login failed")); + emit errorOccurred(); + sso->deleteLater(); + }); - QDesktopServices::openUrl( - QString::fromStdString(http::client()->login_sso_redirect(sso->url()))); - } + QDesktopServices::openUrl( + QString::fromStdString(http::client()->login_sso_redirect(sso->url()))); + } - emit loggingIn(); + emit loggingIn(); } void LoginPage::reset() { - matrixid_input_->clear(); - password_input_->clear(); - password_input_->show(); - serverInput_->clear(); + matrixid_input_->clear(); + password_input_->clear(); + password_input_->show(); + serverInput_->clear(); - spinner_->stop(); - errorIcon_->hide(); - serverLayout_->removeWidget(spinner_); - serverLayout_->removeWidget(errorIcon_); - matrixidLayout_->removeWidget(spinner_); + spinner_->stop(); + errorIcon_->hide(); + serverLayout_->removeWidget(spinner_); + serverLayout_->removeWidget(errorIcon_); + matrixidLayout_->removeWidget(spinner_); - inferredServerAddress_.clear(); + inferredServerAddress_.clear(); } void LoginPage::onBackButtonClicked() { - emit backButtonClicked(); + emit backButtonClicked(); } void LoginPage::paintEvent(QPaintEvent *) { - QStyleOption opt; - opt.init(this); - QPainter p(this); - style()->drawPrimitive(QStyle::PE_Widget, &opt, &p, this); + QStyleOption opt; + opt.init(this); + QPainter p(this); + style()->drawPrimitive(QStyle::PE_Widget, &opt, &p, this); } diff --git a/src/LoginPage.h b/src/LoginPage.h index 2e1eb9b9..01dd27e1 100644 --- a/src/LoginPage.h +++ b/src/LoginPage.h @@ -24,101 +24,101 @@ struct Login; class LoginPage : public QWidget { - Q_OBJECT + Q_OBJECT public: - enum class LoginMethod - { - Password, - SSO, - }; + enum class LoginMethod + { + Password, + SSO, + }; - LoginPage(QWidget *parent = nullptr); + LoginPage(QWidget *parent = nullptr); - void reset(); + void reset(); signals: - void backButtonClicked(); - void loggingIn(); - void errorOccurred(); + void backButtonClicked(); + void loggingIn(); + void errorOccurred(); - //! Used to trigger the corresponding slot outside of the main thread. - void versionErrorCb(const QString &err); - void versionOkCb(bool passwordSupported, bool ssoSupported); + //! Used to trigger the corresponding slot outside of the main thread. + void versionErrorCb(const QString &err); + void versionOkCb(bool passwordSupported, bool ssoSupported); - void loginOk(const mtx::responses::Login &res); - void showErrorMessage(QLabel *label, const QString &msg); + void loginOk(const mtx::responses::Login &res); + void showErrorMessage(QLabel *label, const QString &msg); protected: - void paintEvent(QPaintEvent *event) override; + void paintEvent(QPaintEvent *event) override; public slots: - // Displays errors produced during the login. - void showError(const QString &msg); - void showError(QLabel *label, const QString &msg); + // Displays errors produced during the login. + void showError(const QString &msg); + void showError(QLabel *label, const QString &msg); private slots: - // Callback for the back button. - void onBackButtonClicked(); + // Callback for the back button. + void onBackButtonClicked(); - // Callback for the login button. - void onLoginButtonClicked(LoginMethod loginMethod); + // Callback for the login button. + void onLoginButtonClicked(LoginMethod loginMethod); - // Callback for probing the server found in the mxid - void onMatrixIdEntered(); + // Callback for probing the server found in the mxid + void onMatrixIdEntered(); - // Callback for probing the manually entered server - void onServerAddressEntered(); + // Callback for probing the manually entered server + void onServerAddressEntered(); - // Callback for errors produced during server probing - void versionError(const QString &error_message); - // Callback for successful server probing - void versionOk(bool passwordSupported, bool ssoSupported); + // Callback for errors produced during server probing + void versionError(const QString &error_message); + // Callback for successful server probing + void versionOk(bool passwordSupported, bool ssoSupported); private: - void checkHomeserverVersion(); - std::string initialDeviceName() - { + void checkHomeserverVersion(); + std::string initialDeviceName() + { #if defined(Q_OS_MAC) - return "Nheko on macOS"; + return "Nheko on macOS"; #elif defined(Q_OS_LINUX) - return "Nheko on Linux"; + return "Nheko on Linux"; #elif defined(Q_OS_WIN) - return "Nheko on Windows"; + return "Nheko on Windows"; #elif defined(Q_OS_FREEBSD) - return "Nheko on FreeBSD"; + return "Nheko on FreeBSD"; #else - return "Nheko"; + return "Nheko"; #endif - } + } - QVBoxLayout *top_layout_; + QVBoxLayout *top_layout_; - QHBoxLayout *top_bar_layout_; - QHBoxLayout *logo_layout_; - QHBoxLayout *button_layout_; + QHBoxLayout *top_bar_layout_; + QHBoxLayout *logo_layout_; + QHBoxLayout *button_layout_; - QLabel *logo_; - QLabel *error_label_; - QLabel *error_matrixid_label_; + QLabel *logo_; + QLabel *error_label_; + QLabel *error_matrixid_label_; - QHBoxLayout *serverLayout_; - QHBoxLayout *matrixidLayout_; - LoadingIndicator *spinner_; - QLabel *errorIcon_; - QString inferredServerAddress_; + QHBoxLayout *serverLayout_; + QHBoxLayout *matrixidLayout_; + LoadingIndicator *spinner_; + QLabel *errorIcon_; + QString inferredServerAddress_; - FlatButton *back_button_; - RaisedButton *login_button_, *sso_login_button_; + FlatButton *back_button_; + RaisedButton *login_button_, *sso_login_button_; - QWidget *form_widget_; - QHBoxLayout *form_wrapper_; - QVBoxLayout *form_layout_; + QWidget *form_widget_; + QHBoxLayout *form_wrapper_; + QVBoxLayout *form_layout_; - TextField *matrixid_input_; - TextField *password_input_; - TextField *deviceName_; - TextField *serverInput_; - bool passwordSupported = true; - bool ssoSupported = false; + TextField *matrixid_input_; + TextField *password_input_; + TextField *deviceName_; + TextField *serverInput_; + bool passwordSupported = true; + bool ssoSupported = false; }; diff --git a/src/MainWindow.cpp b/src/MainWindow.cpp index b423304f..bc53b906 100644 --- a/src/MainWindow.cpp +++ b/src/MainWindow.cpp @@ -43,415 +43,407 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) , userSettings_{UserSettings::instance()} { - instance_ = this; + instance_ = this; - setWindowTitle(0); - setObjectName("MainWindow"); + setWindowTitle(0); + setObjectName("MainWindow"); - modal_ = new OverlayModal(this); + modal_ = new OverlayModal(this); - restoreWindowSize(); + restoreWindowSize(); - QFont font; - font.setStyleStrategy(QFont::PreferAntialias); - setFont(font); + QFont font; + font.setStyleStrategy(QFont::PreferAntialias); + setFont(font); - trayIcon_ = new TrayIcon(":/logos/nheko.svg", this); + trayIcon_ = new TrayIcon(":/logos/nheko.svg", this); - welcome_page_ = new WelcomePage(this); - login_page_ = new LoginPage(this); - register_page_ = new RegisterPage(this); - chat_page_ = new ChatPage(userSettings_, this); - userSettingsPage_ = new UserSettingsPage(userSettings_, this); + welcome_page_ = new WelcomePage(this); + login_page_ = new LoginPage(this); + register_page_ = new RegisterPage(this); + chat_page_ = new ChatPage(userSettings_, this); + userSettingsPage_ = new UserSettingsPage(userSettings_, this); - // Initialize sliding widget manager. - pageStack_ = new QStackedWidget(this); - pageStack_->addWidget(welcome_page_); - pageStack_->addWidget(login_page_); - pageStack_->addWidget(register_page_); - pageStack_->addWidget(chat_page_); - pageStack_->addWidget(userSettingsPage_); + // Initialize sliding widget manager. + pageStack_ = new QStackedWidget(this); + pageStack_->addWidget(welcome_page_); + pageStack_->addWidget(login_page_); + pageStack_->addWidget(register_page_); + pageStack_->addWidget(chat_page_); + pageStack_->addWidget(userSettingsPage_); - setCentralWidget(pageStack_); + setCentralWidget(pageStack_); - connect(welcome_page_, SIGNAL(userLogin()), this, SLOT(showLoginPage())); - connect(welcome_page_, SIGNAL(userRegister()), this, SLOT(showRegisterPage())); + connect(welcome_page_, SIGNAL(userLogin()), this, SLOT(showLoginPage())); + connect(welcome_page_, SIGNAL(userRegister()), this, SLOT(showRegisterPage())); - connect(login_page_, SIGNAL(backButtonClicked()), this, SLOT(showWelcomePage())); - connect(login_page_, &LoginPage::loggingIn, this, &MainWindow::showOverlayProgressBar); - connect( - register_page_, &RegisterPage::registering, this, &MainWindow::showOverlayProgressBar); - connect( - login_page_, &LoginPage::errorOccurred, this, [this]() { removeOverlayProgressBar(); }); - connect(register_page_, &RegisterPage::errorOccurred, this, [this]() { - removeOverlayProgressBar(); - }); - connect(register_page_, SIGNAL(backButtonClicked()), this, SLOT(showWelcomePage())); + connect(login_page_, SIGNAL(backButtonClicked()), this, SLOT(showWelcomePage())); + connect(login_page_, &LoginPage::loggingIn, this, &MainWindow::showOverlayProgressBar); + connect(register_page_, &RegisterPage::registering, this, &MainWindow::showOverlayProgressBar); + connect(login_page_, &LoginPage::errorOccurred, this, [this]() { removeOverlayProgressBar(); }); + connect( + register_page_, &RegisterPage::errorOccurred, this, [this]() { removeOverlayProgressBar(); }); + connect(register_page_, SIGNAL(backButtonClicked()), this, SLOT(showWelcomePage())); - connect(chat_page_, &ChatPage::closing, this, &MainWindow::showWelcomePage); - connect( - chat_page_, &ChatPage::showOverlayProgressBar, this, &MainWindow::showOverlayProgressBar); - connect(chat_page_, &ChatPage::unreadMessages, this, &MainWindow::setWindowTitle); - connect(chat_page_, SIGNAL(unreadMessages(int)), trayIcon_, SLOT(setUnreadCount(int))); - connect(chat_page_, &ChatPage::showLoginPage, this, [this](const QString &msg) { - login_page_->showError(msg); - showLoginPage(); - }); + connect(chat_page_, &ChatPage::closing, this, &MainWindow::showWelcomePage); + connect( + chat_page_, &ChatPage::showOverlayProgressBar, this, &MainWindow::showOverlayProgressBar); + connect(chat_page_, &ChatPage::unreadMessages, this, &MainWindow::setWindowTitle); + connect(chat_page_, SIGNAL(unreadMessages(int)), trayIcon_, SLOT(setUnreadCount(int))); + connect(chat_page_, &ChatPage::showLoginPage, this, [this](const QString &msg) { + login_page_->showError(msg); + showLoginPage(); + }); - connect(userSettingsPage_, &UserSettingsPage::moveBack, this, [this]() { - pageStack_->setCurrentWidget(chat_page_); - }); + connect(userSettingsPage_, &UserSettingsPage::moveBack, this, [this]() { + pageStack_->setCurrentWidget(chat_page_); + }); - connect( - userSettingsPage_, SIGNAL(trayOptionChanged(bool)), trayIcon_, SLOT(setVisible(bool))); - connect( - userSettingsPage_, &UserSettingsPage::themeChanged, chat_page_, &ChatPage::themeChanged); - connect(trayIcon_, - SIGNAL(activated(QSystemTrayIcon::ActivationReason)), - this, - SLOT(iconActivated(QSystemTrayIcon::ActivationReason))); + connect(userSettingsPage_, SIGNAL(trayOptionChanged(bool)), trayIcon_, SLOT(setVisible(bool))); + connect( + userSettingsPage_, &UserSettingsPage::themeChanged, chat_page_, &ChatPage::themeChanged); + connect(trayIcon_, + SIGNAL(activated(QSystemTrayIcon::ActivationReason)), + this, + SLOT(iconActivated(QSystemTrayIcon::ActivationReason))); - connect(chat_page_, SIGNAL(contentLoaded()), this, SLOT(removeOverlayProgressBar())); + connect(chat_page_, SIGNAL(contentLoaded()), this, SLOT(removeOverlayProgressBar())); - connect(this, &MainWindow::focusChanged, chat_page_, &ChatPage::chatFocusChanged); + connect(this, &MainWindow::focusChanged, chat_page_, &ChatPage::chatFocusChanged); - connect( - chat_page_, &ChatPage::showUserSettingsPage, this, &MainWindow::showUserSettingsPage); + connect(chat_page_, &ChatPage::showUserSettingsPage, this, &MainWindow::showUserSettingsPage); - connect(login_page_, &LoginPage::loginOk, this, [this](const mtx::responses::Login &res) { - http::client()->set_user(res.user_id); - showChatPage(); - }); + connect(login_page_, &LoginPage::loginOk, this, [this](const mtx::responses::Login &res) { + http::client()->set_user(res.user_id); + showChatPage(); + }); - connect(register_page_, &RegisterPage::registerOk, this, &MainWindow::showChatPage); + connect(register_page_, &RegisterPage::registerOk, this, &MainWindow::showChatPage); - QShortcut *quitShortcut = new QShortcut(QKeySequence::Quit, this); - connect(quitShortcut, &QShortcut::activated, this, QApplication::quit); + QShortcut *quitShortcut = new QShortcut(QKeySequence::Quit, this); + connect(quitShortcut, &QShortcut::activated, this, QApplication::quit); - trayIcon_->setVisible(userSettings_->tray()); + trayIcon_->setVisible(userSettings_->tray()); - // load cache on event loop - QTimer::singleShot(0, this, [this] { - if (hasActiveUser()) { - QString token = userSettings_->accessToken(); - QString home_server = userSettings_->homeserver(); - QString user_id = userSettings_->userId(); - QString device_id = userSettings_->deviceId(); + // load cache on event loop + QTimer::singleShot(0, this, [this] { + if (hasActiveUser()) { + QString token = userSettings_->accessToken(); + QString home_server = userSettings_->homeserver(); + QString user_id = userSettings_->userId(); + QString device_id = userSettings_->deviceId(); - http::client()->set_access_token(token.toStdString()); - http::client()->set_server(home_server.toStdString()); - http::client()->set_device_id(device_id.toStdString()); + http::client()->set_access_token(token.toStdString()); + http::client()->set_server(home_server.toStdString()); + http::client()->set_device_id(device_id.toStdString()); - try { - using namespace mtx::identifiers; - http::client()->set_user(parse(user_id.toStdString())); - } catch (const std::invalid_argument &) { - nhlog::ui()->critical("bootstrapped with invalid user_id: {}", - user_id.toStdString()); - } + try { + using namespace mtx::identifiers; + http::client()->set_user(parse(user_id.toStdString())); + } catch (const std::invalid_argument &) { + nhlog::ui()->critical("bootstrapped with invalid user_id: {}", + user_id.toStdString()); + } - showChatPage(); - } - }); + showChatPage(); + } + }); } void MainWindow::setWindowTitle(int notificationCount) { - QString name = "nheko"; + QString name = "nheko"; - if (!userSettings_.data()->profile().isEmpty()) - name += " | " + userSettings_.data()->profile(); - if (notificationCount > 0) { - name.append(QString{" (%1)"}.arg(notificationCount)); - } - QMainWindow::setWindowTitle(name); + if (!userSettings_.data()->profile().isEmpty()) + name += " | " + userSettings_.data()->profile(); + if (notificationCount > 0) { + name.append(QString{" (%1)"}.arg(notificationCount)); + } + QMainWindow::setWindowTitle(name); } bool MainWindow::event(QEvent *event) { - auto type = event->type(); - if (type == QEvent::WindowActivate) { - emit focusChanged(true); - } else if (type == QEvent::WindowDeactivate) { - emit focusChanged(false); - } + auto type = event->type(); + if (type == QEvent::WindowActivate) { + emit focusChanged(true); + } else if (type == QEvent::WindowDeactivate) { + emit focusChanged(false); + } - return QMainWindow::event(event); + return QMainWindow::event(event); } void MainWindow::restoreWindowSize() { - int savedWidth = userSettings_->qsettings()->value("window/width").toInt(); - int savedheight = userSettings_->qsettings()->value("window/height").toInt(); + int savedWidth = userSettings_->qsettings()->value("window/width").toInt(); + int savedheight = userSettings_->qsettings()->value("window/height").toInt(); - nhlog::ui()->info("Restoring window size {}x{}", savedWidth, savedheight); + nhlog::ui()->info("Restoring window size {}x{}", savedWidth, savedheight); - if (savedWidth == 0 || savedheight == 0) - resize(conf::window::width, conf::window::height); - else - resize(savedWidth, savedheight); + if (savedWidth == 0 || savedheight == 0) + resize(conf::window::width, conf::window::height); + else + resize(savedWidth, savedheight); } void MainWindow::saveCurrentWindowSize() { - auto settings = userSettings_->qsettings(); - QSize current = size(); + auto settings = userSettings_->qsettings(); + QSize current = size(); - settings->setValue("window/width", current.width()); - settings->setValue("window/height", current.height()); + settings->setValue("window/width", current.width()); + settings->setValue("window/height", current.height()); } void MainWindow::removeOverlayProgressBar() { - QTimer *timer = new QTimer(this); - timer->setSingleShot(true); + QTimer *timer = new QTimer(this); + timer->setSingleShot(true); - connect(timer, &QTimer::timeout, [this, timer]() { - timer->deleteLater(); + connect(timer, &QTimer::timeout, [this, timer]() { + timer->deleteLater(); - if (modal_) - modal_->hide(); + if (modal_) + modal_->hide(); - if (spinner_) - spinner_->stop(); - }); + if (spinner_) + spinner_->stop(); + }); - // FIXME: Snackbar doesn't work if it's initialized in the constructor. - QTimer::singleShot(0, this, [this]() { - snackBar_ = new SnackBar(this); - connect(chat_page_, &ChatPage::showNotification, snackBar_, &SnackBar::showMessage); - }); + // FIXME: Snackbar doesn't work if it's initialized in the constructor. + QTimer::singleShot(0, this, [this]() { + snackBar_ = new SnackBar(this); + connect(chat_page_, &ChatPage::showNotification, snackBar_, &SnackBar::showMessage); + }); - timer->start(50); + timer->start(50); } void MainWindow::showChatPage() { - auto userid = QString::fromStdString(http::client()->user_id().to_string()); - auto device_id = QString::fromStdString(http::client()->device_id()); - auto homeserver = QString::fromStdString(http::client()->server() + ":" + - std::to_string(http::client()->port())); - auto token = QString::fromStdString(http::client()->access_token()); + auto userid = QString::fromStdString(http::client()->user_id().to_string()); + auto device_id = QString::fromStdString(http::client()->device_id()); + auto homeserver = QString::fromStdString(http::client()->server() + ":" + + std::to_string(http::client()->port())); + auto token = QString::fromStdString(http::client()->access_token()); - userSettings_.data()->setUserId(userid); - userSettings_.data()->setAccessToken(token); - userSettings_.data()->setDeviceId(device_id); - userSettings_.data()->setHomeserver(homeserver); + userSettings_.data()->setUserId(userid); + userSettings_.data()->setAccessToken(token); + userSettings_.data()->setDeviceId(device_id); + userSettings_.data()->setHomeserver(homeserver); - showOverlayProgressBar(); + showOverlayProgressBar(); - pageStack_->setCurrentWidget(chat_page_); + pageStack_->setCurrentWidget(chat_page_); - pageStack_->removeWidget(welcome_page_); - pageStack_->removeWidget(login_page_); - pageStack_->removeWidget(register_page_); + pageStack_->removeWidget(welcome_page_); + pageStack_->removeWidget(login_page_); + pageStack_->removeWidget(register_page_); - login_page_->reset(); - chat_page_->bootstrap(userid, homeserver, token); - connect(cache::client(), - &Cache::secretChanged, - userSettingsPage_, - &UserSettingsPage::updateSecretStatus); - emit reload(); + login_page_->reset(); + chat_page_->bootstrap(userid, homeserver, token); + connect(cache::client(), + &Cache::secretChanged, + userSettingsPage_, + &UserSettingsPage::updateSecretStatus); + emit reload(); } void MainWindow::closeEvent(QCloseEvent *event) { - if (WebRTCSession::instance().state() != webrtc::State::DISCONNECTED) { - if (QMessageBox::question(this, "nheko", "A call is in progress. Quit?") != - QMessageBox::Yes) { - event->ignore(); - return; - } + if (WebRTCSession::instance().state() != webrtc::State::DISCONNECTED) { + if (QMessageBox::question(this, "nheko", "A call is in progress. Quit?") != + QMessageBox::Yes) { + event->ignore(); + return; } + } - if (!qApp->isSavingSession() && isVisible() && pageSupportsTray() && - userSettings_->tray()) { - event->ignore(); - hide(); - } + if (!qApp->isSavingSession() && isVisible() && pageSupportsTray() && userSettings_->tray()) { + event->ignore(); + hide(); + } } void MainWindow::iconActivated(QSystemTrayIcon::ActivationReason reason) { - switch (reason) { - case QSystemTrayIcon::Trigger: - if (!isVisible()) { - show(); - } else { - hide(); - } - break; - default: - break; + switch (reason) { + case QSystemTrayIcon::Trigger: + if (!isVisible()) { + show(); + } else { + hide(); } + break; + default: + break; + } } bool MainWindow::hasActiveUser() { - auto settings = userSettings_->qsettings(); - QString prefix; - if (userSettings_->profile() != "") - prefix = "profile/" + userSettings_->profile() + "/"; + auto settings = userSettings_->qsettings(); + QString prefix; + if (userSettings_->profile() != "") + prefix = "profile/" + userSettings_->profile() + "/"; - return settings->contains(prefix + "auth/access_token") && - settings->contains(prefix + "auth/home_server") && - settings->contains(prefix + "auth/user_id"); + return settings->contains(prefix + "auth/access_token") && + settings->contains(prefix + "auth/home_server") && + settings->contains(prefix + "auth/user_id"); } void MainWindow::openLeaveRoomDialog(const QString &room_id) { - auto dialog = new dialogs::LeaveRoom(this); - connect(dialog, &dialogs::LeaveRoom::leaving, this, [this, room_id]() { - chat_page_->leaveRoom(room_id); - }); + auto dialog = new dialogs::LeaveRoom(this); + connect(dialog, &dialogs::LeaveRoom::leaving, this, [this, room_id]() { + chat_page_->leaveRoom(room_id); + }); - showDialog(dialog); + showDialog(dialog); } void MainWindow::showOverlayProgressBar() { - spinner_ = new LoadingIndicator(this); - spinner_->setFixedHeight(100); - spinner_->setFixedWidth(100); - spinner_->setObjectName("ChatPageLoadSpinner"); - spinner_->start(); + spinner_ = new LoadingIndicator(this); + spinner_->setFixedHeight(100); + spinner_->setFixedWidth(100); + spinner_->setObjectName("ChatPageLoadSpinner"); + spinner_->start(); - showSolidOverlayModal(spinner_); + showSolidOverlayModal(spinner_); } void MainWindow::openJoinRoomDialog(std::function callback) { - auto dialog = new dialogs::JoinRoom(this); - connect(dialog, &dialogs::JoinRoom::joinRoom, this, [callback](const QString &room) { - if (!room.isEmpty()) - callback(room); - }); + auto dialog = new dialogs::JoinRoom(this); + connect(dialog, &dialogs::JoinRoom::joinRoom, this, [callback](const QString &room) { + if (!room.isEmpty()) + callback(room); + }); - showDialog(dialog); + showDialog(dialog); } void MainWindow::openCreateRoomDialog( std::function callback) { - auto dialog = new dialogs::CreateRoom(this); - connect(dialog, - &dialogs::CreateRoom::createRoom, - this, - [callback](const mtx::requests::CreateRoom &request) { callback(request); }); + auto dialog = new dialogs::CreateRoom(this); + connect(dialog, + &dialogs::CreateRoom::createRoom, + this, + [callback](const mtx::requests::CreateRoom &request) { callback(request); }); - showDialog(dialog); + showDialog(dialog); } void MainWindow::showTransparentOverlayModal(QWidget *content, QFlags flags) { - modal_->setWidget(content); - modal_->setColor(QColor(30, 30, 30, 150)); - modal_->setDismissible(true); - modal_->setContentAlignment(flags); - modal_->raise(); - modal_->show(); + modal_->setWidget(content); + modal_->setColor(QColor(30, 30, 30, 150)); + modal_->setDismissible(true); + modal_->setContentAlignment(flags); + modal_->raise(); + modal_->show(); } void MainWindow::showSolidOverlayModal(QWidget *content, QFlags flags) { - modal_->setWidget(content); - modal_->setColor(QColor(30, 30, 30)); - modal_->setDismissible(false); - modal_->setContentAlignment(flags); - modal_->raise(); - modal_->show(); + modal_->setWidget(content); + modal_->setColor(QColor(30, 30, 30)); + modal_->setDismissible(false); + modal_->setContentAlignment(flags); + modal_->raise(); + modal_->show(); } void MainWindow::openLogoutDialog() { - auto dialog = new dialogs::Logout(this); - connect(dialog, &dialogs::Logout::loggingOut, this, [this]() { - if (WebRTCSession::instance().state() != webrtc::State::DISCONNECTED) { - if (QMessageBox::question( - this, "nheko", "A call is in progress. Log out?") != - QMessageBox::Yes) { - return; - } - WebRTCSession::instance().end(); - } - chat_page_->initiateLogout(); - }); + auto dialog = new dialogs::Logout(this); + connect(dialog, &dialogs::Logout::loggingOut, this, [this]() { + if (WebRTCSession::instance().state() != webrtc::State::DISCONNECTED) { + if (QMessageBox::question(this, "nheko", "A call is in progress. Log out?") != + QMessageBox::Yes) { + return; + } + WebRTCSession::instance().end(); + } + chat_page_->initiateLogout(); + }); - showDialog(dialog); + showDialog(dialog); } bool MainWindow::hasActiveDialogs() const { - return !modal_ && modal_->isVisible(); + return !modal_ && modal_->isVisible(); } bool MainWindow::pageSupportsTray() const { - return !welcome_page_->isVisible() && !login_page_->isVisible() && - !register_page_->isVisible(); + return !welcome_page_->isVisible() && !login_page_->isVisible() && !register_page_->isVisible(); } void MainWindow::hideOverlay() { - if (modal_) - modal_->hide(); + if (modal_) + modal_->hide(); } inline void MainWindow::showDialog(QWidget *dialog) { - utils::centerWidget(dialog, this); - dialog->raise(); - dialog->show(); + utils::centerWidget(dialog, this); + dialog->raise(); + dialog->show(); } void MainWindow::showWelcomePage() { - removeOverlayProgressBar(); - pageStack_->addWidget(welcome_page_); - pageStack_->setCurrentWidget(welcome_page_); + removeOverlayProgressBar(); + pageStack_->addWidget(welcome_page_); + pageStack_->setCurrentWidget(welcome_page_); } void MainWindow::showLoginPage() { - if (modal_) - modal_->hide(); + if (modal_) + modal_->hide(); - pageStack_->addWidget(login_page_); - pageStack_->setCurrentWidget(login_page_); + pageStack_->addWidget(login_page_); + pageStack_->setCurrentWidget(login_page_); } void MainWindow::showRegisterPage() { - pageStack_->addWidget(register_page_); - pageStack_->setCurrentWidget(register_page_); + pageStack_->addWidget(register_page_); + pageStack_->setCurrentWidget(register_page_); } void MainWindow::showUserSettingsPage() { - pageStack_->setCurrentWidget(userSettingsPage_); + pageStack_->setCurrentWidget(userSettingsPage_); } diff --git a/src/MainWindow.h b/src/MainWindow.h index d9ffb9b1..eff8fbe7 100644 --- a/src/MainWindow.h +++ b/src/MainWindow.h @@ -46,93 +46,92 @@ class ReCaptcha; class MainWindow : public QMainWindow { - Q_OBJECT + Q_OBJECT - Q_PROPERTY(int x READ x CONSTANT) - Q_PROPERTY(int y READ y CONSTANT) - Q_PROPERTY(int width READ width CONSTANT) - Q_PROPERTY(int height READ height CONSTANT) + Q_PROPERTY(int x READ x CONSTANT) + Q_PROPERTY(int y READ y CONSTANT) + Q_PROPERTY(int width READ width CONSTANT) + Q_PROPERTY(int height READ height CONSTANT) public: - explicit MainWindow(QWidget *parent = nullptr); + explicit MainWindow(QWidget *parent = nullptr); - static MainWindow *instance() { return instance_; } - void saveCurrentWindowSize(); + static MainWindow *instance() { return instance_; } + void saveCurrentWindowSize(); - void openLeaveRoomDialog(const QString &room_id); - void openInviteUsersDialog(std::function callback); - void openCreateRoomDialog( - std::function callback); - void openJoinRoomDialog(std::function callback); - void openLogoutDialog(); + void openLeaveRoomDialog(const QString &room_id); + void openInviteUsersDialog(std::function callback); + void openCreateRoomDialog( + std::function callback); + void openJoinRoomDialog(std::function callback); + void openLogoutDialog(); - void hideOverlay(); - void showSolidOverlayModal(QWidget *content, - QFlags flags = Qt::AlignCenter); - void showTransparentOverlayModal(QWidget *content, - QFlags flags = Qt::AlignTop | - Qt::AlignHCenter); + void hideOverlay(); + void showSolidOverlayModal(QWidget *content, QFlags flags = Qt::AlignCenter); + void showTransparentOverlayModal(QWidget *content, + QFlags flags = Qt::AlignTop | + Qt::AlignHCenter); protected: - void closeEvent(QCloseEvent *event) override; - bool event(QEvent *event) override; + void closeEvent(QCloseEvent *event) override; + bool event(QEvent *event) override; private slots: - //! Handle interaction with the tray icon. - void iconActivated(QSystemTrayIcon::ActivationReason reason); + //! Handle interaction with the tray icon. + void iconActivated(QSystemTrayIcon::ActivationReason reason); - //! Show the welcome page in the main window. - void showWelcomePage(); + //! Show the welcome page in the main window. + void showWelcomePage(); - //! Show the login page in the main window. - void showLoginPage(); + //! Show the login page in the main window. + void showLoginPage(); - //! Show the register page in the main window. - void showRegisterPage(); + //! Show the register page in the main window. + void showRegisterPage(); - //! Show user settings page. - void showUserSettingsPage(); + //! Show user settings page. + void showUserSettingsPage(); - //! Show the chat page and start communicating with the given access token. - void showChatPage(); + //! Show the chat page and start communicating with the given access token. + void showChatPage(); - void showOverlayProgressBar(); - void removeOverlayProgressBar(); + void showOverlayProgressBar(); + void removeOverlayProgressBar(); - virtual void setWindowTitle(int notificationCount); + virtual void setWindowTitle(int notificationCount); signals: - void focusChanged(const bool focused); - void reload(); + void focusChanged(const bool focused); + void reload(); private: - void showDialog(QWidget *dialog); - bool hasActiveUser(); - void restoreWindowSize(); - //! Check if there is an open dialog. - bool hasActiveDialogs() const; - //! Check if the current page supports the "minimize to tray" functionality. - bool pageSupportsTray() const; + void showDialog(QWidget *dialog); + bool hasActiveUser(); + void restoreWindowSize(); + //! Check if there is an open dialog. + bool hasActiveDialogs() const; + //! Check if the current page supports the "minimize to tray" functionality. + bool pageSupportsTray() const; - static MainWindow *instance_; + static MainWindow *instance_; - //! The initial welcome screen. - WelcomePage *welcome_page_; - //! The login screen. - LoginPage *login_page_; - //! The register page. - RegisterPage *register_page_; - //! A stacked widget that handles the transitions between widgets. - QStackedWidget *pageStack_; - //! The main chat area. - ChatPage *chat_page_; - UserSettingsPage *userSettingsPage_; - QSharedPointer userSettings_; - //! Tray icon that shows the unread message count. - TrayIcon *trayIcon_; - //! Notifications display. - SnackBar *snackBar_ = nullptr; - //! Overlay modal used to project other widgets. - OverlayModal *modal_ = nullptr; - LoadingIndicator *spinner_ = nullptr; + //! The initial welcome screen. + WelcomePage *welcome_page_; + //! The login screen. + LoginPage *login_page_; + //! The register page. + RegisterPage *register_page_; + //! A stacked widget that handles the transitions between widgets. + QStackedWidget *pageStack_; + //! The main chat area. + ChatPage *chat_page_; + UserSettingsPage *userSettingsPage_; + QSharedPointer userSettings_; + //! Tray icon that shows the unread message count. + TrayIcon *trayIcon_; + //! Notifications display. + SnackBar *snackBar_ = nullptr; + //! Overlay modal used to project other widgets. + OverlayModal *modal_ = nullptr; + LoadingIndicator *spinner_ = nullptr; }; diff --git a/src/MatrixClient.cpp b/src/MatrixClient.cpp index 196a9322..2ceb53a8 100644 --- a/src/MatrixClient.cpp +++ b/src/MatrixClient.cpp @@ -37,31 +37,31 @@ namespace http { mtx::http::Client * client() { - return client_.get(); + return client_.get(); } bool is_logged_in() { - return !client_->access_token().empty(); + return !client_->access_token().empty(); } void init() { - qRegisterMetaType(); - qRegisterMetaType(); - qRegisterMetaType(); - qRegisterMetaType(); - qRegisterMetaType(); - qRegisterMetaType(); - qRegisterMetaType(); - qRegisterMetaType(); - qRegisterMetaType(); - qRegisterMetaType>(); - qRegisterMetaType>(); - qRegisterMetaType>("std::map"); - qRegisterMetaType>(); + qRegisterMetaType(); + qRegisterMetaType(); + qRegisterMetaType(); + qRegisterMetaType(); + qRegisterMetaType(); + qRegisterMetaType(); + qRegisterMetaType(); + qRegisterMetaType(); + qRegisterMetaType(); + qRegisterMetaType>(); + qRegisterMetaType>(); + qRegisterMetaType>("std::map"); + qRegisterMetaType>(); } } // namespace http diff --git a/src/MemberList.cpp b/src/MemberList.cpp index 0c0f0cdd..34730e9a 100644 --- a/src/MemberList.cpp +++ b/src/MemberList.cpp @@ -15,98 +15,96 @@ MemberList::MemberList(const QString &room_id, QObject *parent) : QAbstractListModel{parent} , room_id_{room_id} { - try { - info_ = cache::singleRoomInfo(room_id_.toStdString()); - } catch (const lmdb::error &) { - nhlog::db()->warn("failed to retrieve room info from cache: {}", - room_id_.toStdString()); - } + try { + info_ = cache::singleRoomInfo(room_id_.toStdString()); + } catch (const lmdb::error &) { + nhlog::db()->warn("failed to retrieve room info from cache: {}", room_id_.toStdString()); + } - try { - auto members = cache::getMembers(room_id_.toStdString()); - addUsers(members); - numUsersLoaded_ = members.size(); - } catch (const lmdb::error &e) { - nhlog::db()->critical("Failed to retrieve members from cache: {}", e.what()); - } + try { + auto members = cache::getMembers(room_id_.toStdString()); + addUsers(members); + numUsersLoaded_ = members.size(); + } catch (const lmdb::error &e) { + nhlog::db()->critical("Failed to retrieve members from cache: {}", e.what()); + } } void MemberList::addUsers(const std::vector &members) { - beginInsertRows( - QModelIndex{}, m_memberList.count(), m_memberList.count() + members.size() - 1); + beginInsertRows(QModelIndex{}, m_memberList.count(), m_memberList.count() + members.size() - 1); - for (const auto &member : members) - m_memberList.push_back( - {member, - ChatPage::instance()->timelineManager()->rooms()->currentRoom()->avatarUrl( - member.user_id)}); + for (const auto &member : members) + m_memberList.push_back( + {member, + ChatPage::instance()->timelineManager()->rooms()->currentRoom()->avatarUrl( + member.user_id)}); - endInsertRows(); + endInsertRows(); } QHash MemberList::roleNames() const { - return { - {Mxid, "mxid"}, - {DisplayName, "displayName"}, - {AvatarUrl, "avatarUrl"}, - {Trustlevel, "trustlevel"}, - }; + return { + {Mxid, "mxid"}, + {DisplayName, "displayName"}, + {AvatarUrl, "avatarUrl"}, + {Trustlevel, "trustlevel"}, + }; } QVariant MemberList::data(const QModelIndex &index, int role) const { - if (!index.isValid() || index.row() >= (int)m_memberList.size() || index.row() < 0) - return {}; + if (!index.isValid() || index.row() >= (int)m_memberList.size() || index.row() < 0) + return {}; - switch (role) { - case Mxid: - return m_memberList[index.row()].first.user_id; - case DisplayName: - return m_memberList[index.row()].first.display_name; - case AvatarUrl: - return m_memberList[index.row()].second; - case Trustlevel: { - auto stat = - cache::verificationStatus(m_memberList[index.row()].first.user_id.toStdString()); + switch (role) { + case Mxid: + return m_memberList[index.row()].first.user_id; + case DisplayName: + return m_memberList[index.row()].first.display_name; + case AvatarUrl: + return m_memberList[index.row()].second; + case Trustlevel: { + auto stat = + cache::verificationStatus(m_memberList[index.row()].first.user_id.toStdString()); - if (!stat) - return crypto::Unverified; - if (stat->unverified_device_count) - return crypto::Unverified; - else - return stat->user_verified; - } - default: - return {}; - } + if (!stat) + return crypto::Unverified; + if (stat->unverified_device_count) + return crypto::Unverified; + else + return stat->user_verified; + } + default: + return {}; + } } bool MemberList::canFetchMore(const QModelIndex &) const { - const size_t numMembers = rowCount(); - if (numMembers > 1 && numMembers < info_.member_count) - return true; - else - return false; + const size_t numMembers = rowCount(); + if (numMembers > 1 && numMembers < info_.member_count) + return true; + else + return false; } void MemberList::fetchMore(const QModelIndex &) { - loadingMoreMembers_ = true; - emit loadingMoreMembersChanged(); + loadingMoreMembers_ = true; + emit loadingMoreMembersChanged(); - auto members = cache::getMembers(room_id_.toStdString(), rowCount()); - addUsers(members); - numUsersLoaded_ += members.size(); - emit numUsersLoadedChanged(); + auto members = cache::getMembers(room_id_.toStdString(), rowCount()); + addUsers(members); + numUsersLoaded_ += members.size(); + emit numUsersLoadedChanged(); - loadingMoreMembers_ = false; - emit loadingMoreMembersChanged(); + loadingMoreMembers_ = false; + emit loadingMoreMembersChanged(); } diff --git a/src/MemberList.h b/src/MemberList.h index cffcd83d..b16ac983 100644 --- a/src/MemberList.h +++ b/src/MemberList.h @@ -10,59 +10,59 @@ class MemberList : public QAbstractListModel { - Q_OBJECT + Q_OBJECT - Q_PROPERTY(QString roomName READ roomName NOTIFY roomNameChanged) - Q_PROPERTY(int memberCount READ memberCount NOTIFY memberCountChanged) - Q_PROPERTY(QString avatarUrl READ avatarUrl NOTIFY avatarUrlChanged) - Q_PROPERTY(QString roomId READ roomId NOTIFY roomIdChanged) - Q_PROPERTY(int numUsersLoaded READ numUsersLoaded NOTIFY numUsersLoadedChanged) - Q_PROPERTY(bool loadingMoreMembers READ loadingMoreMembers NOTIFY loadingMoreMembersChanged) + Q_PROPERTY(QString roomName READ roomName NOTIFY roomNameChanged) + Q_PROPERTY(int memberCount READ memberCount NOTIFY memberCountChanged) + Q_PROPERTY(QString avatarUrl READ avatarUrl NOTIFY avatarUrlChanged) + Q_PROPERTY(QString roomId READ roomId NOTIFY roomIdChanged) + Q_PROPERTY(int numUsersLoaded READ numUsersLoaded NOTIFY numUsersLoadedChanged) + Q_PROPERTY(bool loadingMoreMembers READ loadingMoreMembers NOTIFY loadingMoreMembersChanged) public: - enum Roles - { - Mxid, - DisplayName, - AvatarUrl, - Trustlevel, - }; - MemberList(const QString &room_id, QObject *parent = nullptr); + enum Roles + { + Mxid, + DisplayName, + AvatarUrl, + Trustlevel, + }; + MemberList(const QString &room_id, QObject *parent = nullptr); - QHash roleNames() const override; - int rowCount(const QModelIndex &parent = QModelIndex()) const override - { - Q_UNUSED(parent) - return static_cast(m_memberList.size()); - } - QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; + QHash roleNames() const override; + int rowCount(const QModelIndex &parent = QModelIndex()) const override + { + Q_UNUSED(parent) + return static_cast(m_memberList.size()); + } + QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; - QString roomName() const { return QString::fromStdString(info_.name); } - int memberCount() const { return info_.member_count; } - QString avatarUrl() const { return QString::fromStdString(info_.avatar_url); } - QString roomId() const { return room_id_; } - int numUsersLoaded() const { return numUsersLoaded_; } - bool loadingMoreMembers() const { return loadingMoreMembers_; } + QString roomName() const { return QString::fromStdString(info_.name); } + int memberCount() const { return info_.member_count; } + QString avatarUrl() const { return QString::fromStdString(info_.avatar_url); } + QString roomId() const { return room_id_; } + int numUsersLoaded() const { return numUsersLoaded_; } + bool loadingMoreMembers() const { return loadingMoreMembers_; } signals: - void roomNameChanged(); - void memberCountChanged(); - void avatarUrlChanged(); - void roomIdChanged(); - void numUsersLoadedChanged(); - void loadingMoreMembersChanged(); + void roomNameChanged(); + void memberCountChanged(); + void avatarUrlChanged(); + void roomIdChanged(); + void numUsersLoadedChanged(); + void loadingMoreMembersChanged(); public slots: - void addUsers(const std::vector &users); + void addUsers(const std::vector &users); protected: - bool canFetchMore(const QModelIndex &) const override; - void fetchMore(const QModelIndex &) override; + bool canFetchMore(const QModelIndex &) const override; + void fetchMore(const QModelIndex &) override; private: - QVector> m_memberList; - QString room_id_; - RoomInfo info_; - int numUsersLoaded_{0}; - bool loadingMoreMembers_{false}; + QVector> m_memberList; + QString room_id_; + RoomInfo info_; + int numUsersLoaded_{0}; + bool loadingMoreMembers_{false}; }; diff --git a/src/MxcImageProvider.cpp b/src/MxcImageProvider.cpp index 056374a9..5d0ee0be 100644 --- a/src/MxcImageProvider.cpp +++ b/src/MxcImageProvider.cpp @@ -24,70 +24,70 @@ QHash infos; QQuickImageResponse * MxcImageProvider::requestImageResponse(const QString &id, const QSize &requestedSize) { - auto id_ = id; - bool crop = true; - double radius = 0; + auto id_ = id; + bool crop = true; + double radius = 0; - auto queryStart = id.lastIndexOf('?'); - if (queryStart != -1) { - id_ = id.left(queryStart); - auto query = id.midRef(queryStart + 1); - auto queryBits = query.split('&'); + auto queryStart = id.lastIndexOf('?'); + if (queryStart != -1) { + id_ = id.left(queryStart); + auto query = id.midRef(queryStart + 1); + auto queryBits = query.split('&'); - for (auto b : queryBits) { - if (b == "scale") { - crop = false; - } else if (b.startsWith("radius=")) { - radius = b.mid(7).toDouble(); - } - } + for (auto b : queryBits) { + if (b == "scale") { + crop = false; + } else if (b.startsWith("radius=")) { + radius = b.mid(7).toDouble(); + } } + } - MxcImageResponse *response = new MxcImageResponse(id_, crop, radius, requestedSize); - pool.start(response); - return response; + MxcImageResponse *response = new MxcImageResponse(id_, crop, radius, requestedSize); + pool.start(response); + return response; } void MxcImageProvider::addEncryptionInfo(mtx::crypto::EncryptedFile info) { - infos.insert(QString::fromStdString(info.url), info); + infos.insert(QString::fromStdString(info.url), info); } void MxcImageResponse::run() { - MxcImageProvider::download( - m_id, - m_requestedSize, - [this](QString, QSize, QImage image, QString) { - if (image.isNull()) { - m_error = "Failed to download image."; - } else { - m_image = image; - } - emit finished(); - }, - m_crop, - m_radius); + MxcImageProvider::download( + m_id, + m_requestedSize, + [this](QString, QSize, QImage image, QString) { + if (image.isNull()) { + m_error = "Failed to download image."; + } else { + m_image = image; + } + emit finished(); + }, + m_crop, + m_radius); } static QImage clipRadius(QImage img, double radius) { - QImage out(img.size(), QImage::Format_ARGB32_Premultiplied); - out.fill(Qt::transparent); + QImage out(img.size(), QImage::Format_ARGB32_Premultiplied); + out.fill(Qt::transparent); - QPainter painter(&out); - painter.setRenderHint(QPainter::Antialiasing, true); - painter.setRenderHint(QPainter::SmoothPixmapTransform, true); + QPainter painter(&out); + painter.setRenderHint(QPainter::Antialiasing, true); + painter.setRenderHint(QPainter::SmoothPixmapTransform, true); - QPainterPath ppath; - ppath.addRoundedRect(img.rect(), radius, radius, Qt::SizeMode::RelativeSize); + QPainterPath ppath; + ppath.addRoundedRect(img.rect(), radius, radius, Qt::SizeMode::RelativeSize); - painter.setClipPath(ppath); - painter.drawImage(img.rect(), img); + painter.setClipPath(ppath); + painter.drawImage(img.rect(), img); - return out; + return out; } void @@ -97,187 +97,165 @@ MxcImageProvider::download(const QString &id, bool crop, double radius) { - std::optional encryptionInfo; - auto temp = infos.find("mxc://" + id); - if (temp != infos.end()) - encryptionInfo = *temp; + std::optional encryptionInfo; + auto temp = infos.find("mxc://" + id); + if (temp != infos.end()) + encryptionInfo = *temp; - if (requestedSize.isValid() && !encryptionInfo) { - QString fileName = - QString("%1_%2x%3_%4_radius%5") - .arg(QString::fromUtf8(id.toUtf8().toBase64(QByteArray::Base64UrlEncoding | - QByteArray::OmitTrailingEquals))) - .arg(requestedSize.width()) - .arg(requestedSize.height()) - .arg(crop ? "crop" : "scale") - .arg(radius); - QFileInfo fileInfo(QStandardPaths::writableLocation(QStandardPaths::CacheLocation) + - "/media_cache", - fileName); - QDir().mkpath(fileInfo.absolutePath()); + if (requestedSize.isValid() && !encryptionInfo) { + QString fileName = QString("%1_%2x%3_%4_radius%5") + .arg(QString::fromUtf8(id.toUtf8().toBase64( + QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals))) + .arg(requestedSize.width()) + .arg(requestedSize.height()) + .arg(crop ? "crop" : "scale") + .arg(radius); + QFileInfo fileInfo(QStandardPaths::writableLocation(QStandardPaths::CacheLocation) + + "/media_cache", + fileName); + QDir().mkpath(fileInfo.absolutePath()); - if (fileInfo.exists()) { - QImage image = utils::readImageFromFile(fileInfo.absoluteFilePath()); - if (!image.isNull()) { - image = image.scaled( - requestedSize, Qt::KeepAspectRatio, Qt::SmoothTransformation); + if (fileInfo.exists()) { + QImage image = utils::readImageFromFile(fileInfo.absoluteFilePath()); + if (!image.isNull()) { + image = image.scaled(requestedSize, Qt::KeepAspectRatio, Qt::SmoothTransformation); - if (radius != 0) { - image = clipRadius(std::move(image), radius); - } - - if (!image.isNull()) { - then(id, requestedSize, image, fileInfo.absoluteFilePath()); - return; - } - } + if (radius != 0) { + image = clipRadius(std::move(image), radius); } - mtx::http::ThumbOpts opts; - opts.mxc_url = "mxc://" + id.toStdString(); - opts.width = requestedSize.width() > 0 ? requestedSize.width() : -1; - opts.height = requestedSize.height() > 0 ? requestedSize.height() : -1; - opts.method = crop ? "crop" : "scale"; - http::client()->get_thumbnail( - opts, - [fileInfo, requestedSize, radius, then, id](const std::string &res, - mtx::http::RequestErr err) { - if (err || res.empty()) { - then(id, QSize(), {}, ""); - - return; - } - - auto data = QByteArray(res.data(), (int)res.size()); - QImage image = utils::readImage(data); - if (!image.isNull()) { - image = image.scaled( - requestedSize, Qt::KeepAspectRatio, Qt::SmoothTransformation); - - if (radius != 0) { - image = clipRadius(std::move(image), radius); - } - } - image.setText("mxc url", "mxc://" + id); - if (image.save(fileInfo.absoluteFilePath(), "png")) - nhlog::ui()->debug("Wrote: {}", - fileInfo.absoluteFilePath().toStdString()); - else - nhlog::ui()->debug("Failed to write: {}", - fileInfo.absoluteFilePath().toStdString()); - - then(id, requestedSize, image, fileInfo.absoluteFilePath()); - }); - } else { - try { - QString fileName = - QString("%1_radius%2") - .arg(QString::fromUtf8(id.toUtf8().toBase64( - QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals))) - .arg(radius); - - QFileInfo fileInfo( - QStandardPaths::writableLocation(QStandardPaths::CacheLocation) + - "/media_cache", - fileName); - QDir().mkpath(fileInfo.absolutePath()); - - if (fileInfo.exists()) { - if (encryptionInfo) { - QFile f(fileInfo.absoluteFilePath()); - f.open(QIODevice::ReadOnly); - - QByteArray fileData = f.readAll(); - auto tempData = - mtx::crypto::to_string(mtx::crypto::decrypt_file( - fileData.toStdString(), encryptionInfo.value())); - auto data = - QByteArray(tempData.data(), (int)tempData.size()); - QImage image = utils::readImage(data); - image.setText("mxc url", "mxc://" + id); - if (!image.isNull()) { - if (radius != 0) { - image = - clipRadius(std::move(image), radius); - } - - then(id, - requestedSize, - image, - fileInfo.absoluteFilePath()); - return; - } - } else { - QImage image = - utils::readImageFromFile(fileInfo.absoluteFilePath()); - if (!image.isNull()) { - if (radius != 0) { - image = - clipRadius(std::move(image), radius); - } - - then(id, - requestedSize, - image, - fileInfo.absoluteFilePath()); - return; - } - } - } - - http::client()->download( - "mxc://" + id.toStdString(), - [fileInfo, requestedSize, then, id, radius, encryptionInfo]( - const std::string &res, - const std::string &, - const std::string &originalFilename, - mtx::http::RequestErr err) { - if (err) { - then(id, QSize(), {}, ""); - return; - } - - auto tempData = res; - QFile f(fileInfo.absoluteFilePath()); - if (!f.open(QIODevice::Truncate | QIODevice::WriteOnly)) { - then(id, QSize(), {}, ""); - return; - } - f.write(tempData.data(), tempData.size()); - f.close(); - - if (encryptionInfo) { - tempData = - mtx::crypto::to_string(mtx::crypto::decrypt_file( - tempData, encryptionInfo.value())); - auto data = - QByteArray(tempData.data(), (int)tempData.size()); - QImage image = utils::readImage(data); - if (radius != 0) { - image = clipRadius(std::move(image), radius); - } - - image.setText("original filename", - QString::fromStdString(originalFilename)); - image.setText("mxc url", "mxc://" + id); - then( - id, requestedSize, image, fileInfo.absoluteFilePath()); - return; - } - - QImage image = - utils::readImageFromFile(fileInfo.absoluteFilePath()); - if (radius != 0) { - image = clipRadius(std::move(image), radius); - } - - image.setText("original filename", - QString::fromStdString(originalFilename)); - image.setText("mxc url", "mxc://" + id); - then(id, requestedSize, image, fileInfo.absoluteFilePath()); - }); - } catch (std::exception &e) { - nhlog::net()->error("Exception while downloading media: {}", e.what()); + if (!image.isNull()) { + then(id, requestedSize, image, fileInfo.absoluteFilePath()); + return; } + } } + + mtx::http::ThumbOpts opts; + opts.mxc_url = "mxc://" + id.toStdString(); + opts.width = requestedSize.width() > 0 ? requestedSize.width() : -1; + opts.height = requestedSize.height() > 0 ? requestedSize.height() : -1; + opts.method = crop ? "crop" : "scale"; + http::client()->get_thumbnail( + opts, + [fileInfo, requestedSize, radius, then, id](const std::string &res, + mtx::http::RequestErr err) { + if (err || res.empty()) { + then(id, QSize(), {}, ""); + + return; + } + + auto data = QByteArray(res.data(), (int)res.size()); + QImage image = utils::readImage(data); + if (!image.isNull()) { + image = + image.scaled(requestedSize, Qt::KeepAspectRatio, Qt::SmoothTransformation); + + if (radius != 0) { + image = clipRadius(std::move(image), radius); + } + } + image.setText("mxc url", "mxc://" + id); + if (image.save(fileInfo.absoluteFilePath(), "png")) + nhlog::ui()->debug("Wrote: {}", fileInfo.absoluteFilePath().toStdString()); + else + nhlog::ui()->debug("Failed to write: {}", + fileInfo.absoluteFilePath().toStdString()); + + then(id, requestedSize, image, fileInfo.absoluteFilePath()); + }); + } else { + try { + QString fileName = QString("%1_radius%2") + .arg(QString::fromUtf8(id.toUtf8().toBase64( + QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals))) + .arg(radius); + + QFileInfo fileInfo(QStandardPaths::writableLocation(QStandardPaths::CacheLocation) + + "/media_cache", + fileName); + QDir().mkpath(fileInfo.absolutePath()); + + if (fileInfo.exists()) { + if (encryptionInfo) { + QFile f(fileInfo.absoluteFilePath()); + f.open(QIODevice::ReadOnly); + + QByteArray fileData = f.readAll(); + auto tempData = mtx::crypto::to_string( + mtx::crypto::decrypt_file(fileData.toStdString(), encryptionInfo.value())); + auto data = QByteArray(tempData.data(), (int)tempData.size()); + QImage image = utils::readImage(data); + image.setText("mxc url", "mxc://" + id); + if (!image.isNull()) { + if (radius != 0) { + image = clipRadius(std::move(image), radius); + } + + then(id, requestedSize, image, fileInfo.absoluteFilePath()); + return; + } + } else { + QImage image = utils::readImageFromFile(fileInfo.absoluteFilePath()); + if (!image.isNull()) { + if (radius != 0) { + image = clipRadius(std::move(image), radius); + } + + then(id, requestedSize, image, fileInfo.absoluteFilePath()); + return; + } + } + } + + http::client()->download( + "mxc://" + id.toStdString(), + [fileInfo, requestedSize, then, id, radius, encryptionInfo]( + const std::string &res, + const std::string &, + const std::string &originalFilename, + mtx::http::RequestErr err) { + if (err) { + then(id, QSize(), {}, ""); + return; + } + + auto tempData = res; + QFile f(fileInfo.absoluteFilePath()); + if (!f.open(QIODevice::Truncate | QIODevice::WriteOnly)) { + then(id, QSize(), {}, ""); + return; + } + f.write(tempData.data(), tempData.size()); + f.close(); + + if (encryptionInfo) { + tempData = mtx::crypto::to_string( + mtx::crypto::decrypt_file(tempData, encryptionInfo.value())); + auto data = QByteArray(tempData.data(), (int)tempData.size()); + QImage image = utils::readImage(data); + if (radius != 0) { + image = clipRadius(std::move(image), radius); + } + + image.setText("original filename", QString::fromStdString(originalFilename)); + image.setText("mxc url", "mxc://" + id); + then(id, requestedSize, image, fileInfo.absoluteFilePath()); + return; + } + + QImage image = utils::readImageFromFile(fileInfo.absoluteFilePath()); + if (radius != 0) { + image = clipRadius(std::move(image), radius); + } + + image.setText("original filename", QString::fromStdString(originalFilename)); + image.setText("mxc url", "mxc://" + id); + then(id, requestedSize, image, fileInfo.absoluteFilePath()); + }); + } catch (std::exception &e) { + nhlog::net()->error("Exception while downloading media: {}", e.what()); + } + } } diff --git a/src/MxcImageProvider.h b/src/MxcImageProvider.h index 6de83c0e..3cf5bbf4 100644 --- a/src/MxcImageProvider.h +++ b/src/MxcImageProvider.h @@ -19,46 +19,46 @@ class MxcImageResponse , public QRunnable { public: - MxcImageResponse(const QString &id, bool crop, double radius, const QSize &requestedSize) - : m_id(id) - , m_requestedSize(requestedSize) - , m_crop(crop) - , m_radius(radius) - { - setAutoDelete(false); - } + MxcImageResponse(const QString &id, bool crop, double radius, const QSize &requestedSize) + : m_id(id) + , m_requestedSize(requestedSize) + , m_crop(crop) + , m_radius(radius) + { + setAutoDelete(false); + } - QQuickTextureFactory *textureFactory() const override - { - return QQuickTextureFactory::textureFactoryForImage(m_image); - } - QString errorString() const override { return m_error; } + QQuickTextureFactory *textureFactory() const override + { + return QQuickTextureFactory::textureFactoryForImage(m_image); + } + QString errorString() const override { return m_error; } - void run() override; + void run() override; - QString m_id, m_error; - QSize m_requestedSize; - QImage m_image; - bool m_crop; - double m_radius; + QString m_id, m_error; + QSize m_requestedSize; + QImage m_image; + bool m_crop; + double m_radius; }; class MxcImageProvider : public QObject , public QQuickAsyncImageProvider { - Q_OBJECT + Q_OBJECT public slots: - QQuickImageResponse *requestImageResponse(const QString &id, - const QSize &requestedSize) override; + QQuickImageResponse *requestImageResponse(const QString &id, + const QSize &requestedSize) override; - static void addEncryptionInfo(mtx::crypto::EncryptedFile info); - static void download(const QString &id, - const QSize &requestedSize, - std::function then, - bool crop = true, - double radius = 0); + static void addEncryptionInfo(mtx::crypto::EncryptedFile info); + static void download(const QString &id, + const QSize &requestedSize, + std::function then, + bool crop = true, + double radius = 0); private: - QThreadPool pool; + QThreadPool pool; }; diff --git a/src/Olm.cpp b/src/Olm.cpp index 72dc582f..60460b5c 100644 --- a/src/Olm.cpp +++ b/src/Olm.cpp @@ -39,433 +39,385 @@ backup_session_key(const MegolmSessionIndex &idx, void from_json(const nlohmann::json &obj, OlmMessage &msg) { - if (obj.at("type") != "m.room.encrypted") - throw std::invalid_argument("invalid type for olm message"); + if (obj.at("type") != "m.room.encrypted") + throw std::invalid_argument("invalid type for olm message"); - if (obj.at("content").at("algorithm") != OLM_ALGO) - throw std::invalid_argument("invalid algorithm for olm message"); + if (obj.at("content").at("algorithm") != OLM_ALGO) + throw std::invalid_argument("invalid algorithm for olm message"); - msg.sender = obj.at("sender"); - msg.sender_key = obj.at("content").at("sender_key"); - msg.ciphertext = obj.at("content") - .at("ciphertext") - .get>(); + msg.sender = obj.at("sender"); + msg.sender_key = obj.at("content").at("sender_key"); + msg.ciphertext = obj.at("content") + .at("ciphertext") + .get>(); } mtx::crypto::OlmClient * client() { - return client_.get(); + return client_.get(); } static void handle_secret_request(const mtx::events::DeviceEvent *e, const std::string &sender) { - using namespace mtx::events; + using namespace mtx::events; - if (e->content.action != mtx::events::msg::RequestAction::Request) - return; + if (e->content.action != mtx::events::msg::RequestAction::Request) + return; - auto local_user = http::client()->user_id(); + auto local_user = http::client()->user_id(); - if (sender != local_user.to_string()) - return; + if (sender != local_user.to_string()) + return; - auto verificationStatus = cache::verificationStatus(local_user.to_string()); + auto verificationStatus = cache::verificationStatus(local_user.to_string()); - if (!verificationStatus) - return; + if (!verificationStatus) + return; - auto deviceKeys = cache::userKeys(local_user.to_string()); - if (!deviceKeys) - return; + auto deviceKeys = cache::userKeys(local_user.to_string()); + if (!deviceKeys) + return; - if (std::find(verificationStatus->verified_devices.begin(), - verificationStatus->verified_devices.end(), - e->content.requesting_device_id) == - verificationStatus->verified_devices.end()) - return; + if (std::find(verificationStatus->verified_devices.begin(), + verificationStatus->verified_devices.end(), + e->content.requesting_device_id) == verificationStatus->verified_devices.end()) + return; - // this is a verified device - mtx::events::DeviceEvent secretSend; - secretSend.type = EventType::SecretSend; - secretSend.content.request_id = e->content.request_id; + // this is a verified device + mtx::events::DeviceEvent secretSend; + secretSend.type = EventType::SecretSend; + secretSend.content.request_id = e->content.request_id; - auto secret = cache::client()->secret(e->content.name); - if (!secret) - return; - secretSend.content.secret = secret.value(); + auto secret = cache::client()->secret(e->content.name); + if (!secret) + return; + secretSend.content.secret = secret.value(); - send_encrypted_to_device_messages( - {{local_user.to_string(), {{e->content.requesting_device_id}}}}, secretSend); + send_encrypted_to_device_messages( + {{local_user.to_string(), {{e->content.requesting_device_id}}}}, secretSend); - nhlog::net()->info("Sent secret '{}' to ({},{})", - e->content.name, - local_user.to_string(), - e->content.requesting_device_id); + nhlog::net()->info("Sent secret '{}' to ({},{})", + e->content.name, + local_user.to_string(), + e->content.requesting_device_id); } void handle_to_device_messages(const std::vector &msgs) { - if (msgs.empty()) - return; - nhlog::crypto()->info("received {} to_device messages", msgs.size()); - nlohmann::json j_msg; + if (msgs.empty()) + return; + nhlog::crypto()->info("received {} to_device messages", msgs.size()); + nlohmann::json j_msg; - for (const auto &msg : msgs) { - j_msg = std::visit([](auto &e) { return json(e); }, std::move(msg)); - if (j_msg.count("type") == 0) { - nhlog::crypto()->warn("received message with no type field: {}", - j_msg.dump(2)); - continue; - } - - std::string msg_type = j_msg.at("type"); - - if (msg_type == to_string(mtx::events::EventType::RoomEncrypted)) { - try { - olm::OlmMessage olm_msg = j_msg; - cache::client()->query_keys( - olm_msg.sender, - [olm_msg](const UserKeyCache &userKeys, mtx::http::RequestErr e) { - if (e) { - nhlog::crypto()->error( - "Failed to query user keys, dropping olm " - "message"); - return; - } - handle_olm_message(std::move(olm_msg), userKeys); - }); - } catch (const nlohmann::json::exception &e) { - nhlog::crypto()->warn( - "parsing error for olm message: {} {}", e.what(), j_msg.dump(2)); - } catch (const std::invalid_argument &e) { - nhlog::crypto()->warn("validation error for olm message: {} {}", - e.what(), - j_msg.dump(2)); - } - - } else if (msg_type == to_string(mtx::events::EventType::RoomKeyRequest)) { - nhlog::crypto()->warn("handling key request event: {}", j_msg.dump(2)); - try { - mtx::events::DeviceEvent req = j_msg; - if (req.content.action == mtx::events::msg::RequestAction::Request) - handle_key_request_message(req); - else - nhlog::crypto()->warn( - "ignore key request (unhandled action): {}", - req.content.request_id); - } catch (const nlohmann::json::exception &e) { - nhlog::crypto()->warn( - "parsing error for key_request message: {} {}", - e.what(), - j_msg.dump(2)); - } - } else if (msg_type == to_string(mtx::events::EventType::KeyVerificationAccept)) { - auto message = std::get< - mtx::events::DeviceEvent>(msg); - ChatPage::instance()->receivedDeviceVerificationAccept(message.content); - } else if (msg_type == to_string(mtx::events::EventType::KeyVerificationRequest)) { - auto message = std::get< - mtx::events::DeviceEvent>(msg); - ChatPage::instance()->receivedDeviceVerificationRequest(message.content, - message.sender); - } else if (msg_type == to_string(mtx::events::EventType::KeyVerificationCancel)) { - auto message = std::get< - mtx::events::DeviceEvent>(msg); - ChatPage::instance()->receivedDeviceVerificationCancel(message.content); - } else if (msg_type == to_string(mtx::events::EventType::KeyVerificationKey)) { - auto message = - std::get>( - msg); - ChatPage::instance()->receivedDeviceVerificationKey(message.content); - } else if (msg_type == to_string(mtx::events::EventType::KeyVerificationMac)) { - auto message = - std::get>( - msg); - ChatPage::instance()->receivedDeviceVerificationMac(message.content); - } else if (msg_type == to_string(mtx::events::EventType::KeyVerificationStart)) { - auto message = std::get< - mtx::events::DeviceEvent>(msg); - ChatPage::instance()->receivedDeviceVerificationStart(message.content, - message.sender); - } else if (msg_type == to_string(mtx::events::EventType::KeyVerificationReady)) { - auto message = std::get< - mtx::events::DeviceEvent>(msg); - ChatPage::instance()->receivedDeviceVerificationReady(message.content); - } else if (msg_type == to_string(mtx::events::EventType::KeyVerificationDone)) { - auto message = - std::get>( - msg); - ChatPage::instance()->receivedDeviceVerificationDone(message.content); - } else if (auto e = - std::get_if>( - &msg)) { - handle_secret_request(e, e->sender); - } else { - nhlog::crypto()->warn("unhandled event: {}", j_msg.dump(2)); - } + for (const auto &msg : msgs) { + j_msg = std::visit([](auto &e) { return json(e); }, std::move(msg)); + if (j_msg.count("type") == 0) { + nhlog::crypto()->warn("received message with no type field: {}", j_msg.dump(2)); + continue; } + + std::string msg_type = j_msg.at("type"); + + if (msg_type == to_string(mtx::events::EventType::RoomEncrypted)) { + try { + olm::OlmMessage olm_msg = j_msg; + cache::client()->query_keys( + olm_msg.sender, [olm_msg](const UserKeyCache &userKeys, mtx::http::RequestErr e) { + if (e) { + nhlog::crypto()->error("Failed to query user keys, dropping olm " + "message"); + return; + } + handle_olm_message(std::move(olm_msg), userKeys); + }); + } catch (const nlohmann::json::exception &e) { + nhlog::crypto()->warn( + "parsing error for olm message: {} {}", e.what(), j_msg.dump(2)); + } catch (const std::invalid_argument &e) { + nhlog::crypto()->warn( + "validation error for olm message: {} {}", e.what(), j_msg.dump(2)); + } + + } else if (msg_type == to_string(mtx::events::EventType::RoomKeyRequest)) { + nhlog::crypto()->warn("handling key request event: {}", j_msg.dump(2)); + try { + mtx::events::DeviceEvent req = j_msg; + if (req.content.action == mtx::events::msg::RequestAction::Request) + handle_key_request_message(req); + else + nhlog::crypto()->warn("ignore key request (unhandled action): {}", + req.content.request_id); + } catch (const nlohmann::json::exception &e) { + nhlog::crypto()->warn( + "parsing error for key_request message: {} {}", e.what(), j_msg.dump(2)); + } + } else if (msg_type == to_string(mtx::events::EventType::KeyVerificationAccept)) { + auto message = + std::get>(msg); + ChatPage::instance()->receivedDeviceVerificationAccept(message.content); + } else if (msg_type == to_string(mtx::events::EventType::KeyVerificationRequest)) { + auto message = + std::get>(msg); + ChatPage::instance()->receivedDeviceVerificationRequest(message.content, + message.sender); + } else if (msg_type == to_string(mtx::events::EventType::KeyVerificationCancel)) { + auto message = + std::get>(msg); + ChatPage::instance()->receivedDeviceVerificationCancel(message.content); + } else if (msg_type == to_string(mtx::events::EventType::KeyVerificationKey)) { + auto message = + std::get>(msg); + ChatPage::instance()->receivedDeviceVerificationKey(message.content); + } else if (msg_type == to_string(mtx::events::EventType::KeyVerificationMac)) { + auto message = + std::get>(msg); + ChatPage::instance()->receivedDeviceVerificationMac(message.content); + } else if (msg_type == to_string(mtx::events::EventType::KeyVerificationStart)) { + auto message = + std::get>(msg); + ChatPage::instance()->receivedDeviceVerificationStart(message.content, message.sender); + } else if (msg_type == to_string(mtx::events::EventType::KeyVerificationReady)) { + auto message = + std::get>(msg); + ChatPage::instance()->receivedDeviceVerificationReady(message.content); + } else if (msg_type == to_string(mtx::events::EventType::KeyVerificationDone)) { + auto message = + std::get>(msg); + ChatPage::instance()->receivedDeviceVerificationDone(message.content); + } else if (auto e = + std::get_if>(&msg)) { + handle_secret_request(e, e->sender); + } else { + nhlog::crypto()->warn("unhandled event: {}", j_msg.dump(2)); + } + } } void handle_olm_message(const OlmMessage &msg, const UserKeyCache &otherUserDeviceKeys) { - nhlog::crypto()->info("sender : {}", msg.sender); - nhlog::crypto()->info("sender_key: {}", msg.sender_key); + nhlog::crypto()->info("sender : {}", msg.sender); + nhlog::crypto()->info("sender_key: {}", msg.sender_key); - if (msg.sender_key == olm::client()->identity_keys().ed25519) { - nhlog::crypto()->warn("Ignoring olm message from ourselves!"); + if (msg.sender_key == olm::client()->identity_keys().ed25519) { + nhlog::crypto()->warn("Ignoring olm message from ourselves!"); + return; + } + + const auto my_key = olm::client()->identity_keys().curve25519; + + bool failed_decryption = false; + + for (const auto &cipher : msg.ciphertext) { + // We skip messages not meant for the current device. + if (cipher.first != my_key) { + nhlog::crypto()->debug( + "Skipping message for {} since we are {}.", cipher.first, my_key); + continue; + } + + const auto type = cipher.second.type; + nhlog::crypto()->info("type: {}", type == 0 ? "OLM_PRE_KEY" : "OLM_MESSAGE"); + + auto payload = try_olm_decryption(msg.sender_key, cipher.second); + + if (payload.is_null()) { + // Check for PRE_KEY message + if (cipher.second.type == 0) { + payload = handle_pre_key_olm_message(msg.sender, msg.sender_key, cipher.second); + } else { + nhlog::crypto()->error("Undecryptable olm message!"); + failed_decryption = true; + continue; + } + } + + if (!payload.is_null()) { + mtx::events::collections::DeviceEvents device_event; + + // Other properties are included in order to prevent an attacker from + // publishing someone else's curve25519 keys as their own and subsequently + // claiming to have sent messages which they didn't. sender must correspond + // to the user who sent the event, recipient to the local user, and + // recipient_keys to the local ed25519 key. + std::string receiver_ed25519 = payload["recipient_keys"]["ed25519"]; + if (receiver_ed25519.empty() || + receiver_ed25519 != olm::client()->identity_keys().ed25519) { + nhlog::crypto()->warn("Decrypted event doesn't include our ed25519: {}", + payload.dump()); return; - } + } + std::string receiver = payload["recipient"]; + if (receiver.empty() || receiver != http::client()->user_id().to_string()) { + nhlog::crypto()->warn("Decrypted event doesn't include our user_id: {}", + payload.dump()); + return; + } - const auto my_key = olm::client()->identity_keys().curve25519; + // Clients must confirm that the sender_key and the ed25519 field value + // under the keys property match the keys returned by /keys/query for the + // given user, and must also verify the signature of the payload. Without + // this check, a client cannot be sure that the sender device owns the + // private part of the ed25519 key it claims to have in the Olm payload. + // This is crucial when the ed25519 key corresponds to a verified device. + std::string sender_ed25519 = payload["keys"]["ed25519"]; + if (sender_ed25519.empty()) { + nhlog::crypto()->warn("Decrypted event doesn't include sender ed25519: {}", + payload.dump()); + return; + } - bool failed_decryption = false; + bool from_their_device = false; + for (auto [device_id, key] : otherUserDeviceKeys.device_keys) { + auto c_key = key.keys.find("curve25519:" + device_id); + auto e_key = key.keys.find("ed25519:" + device_id); - for (const auto &cipher : msg.ciphertext) { - // We skip messages not meant for the current device. - if (cipher.first != my_key) { - nhlog::crypto()->debug( - "Skipping message for {} since we are {}.", cipher.first, my_key); - continue; - } - - const auto type = cipher.second.type; - nhlog::crypto()->info("type: {}", type == 0 ? "OLM_PRE_KEY" : "OLM_MESSAGE"); - - auto payload = try_olm_decryption(msg.sender_key, cipher.second); - - if (payload.is_null()) { - // Check for PRE_KEY message - if (cipher.second.type == 0) { - payload = handle_pre_key_olm_message( - msg.sender, msg.sender_key, cipher.second); - } else { - nhlog::crypto()->error("Undecryptable olm message!"); - failed_decryption = true; - continue; - } - } - - if (!payload.is_null()) { - mtx::events::collections::DeviceEvents device_event; - - // Other properties are included in order to prevent an attacker from - // publishing someone else's curve25519 keys as their own and subsequently - // claiming to have sent messages which they didn't. sender must correspond - // to the user who sent the event, recipient to the local user, and - // recipient_keys to the local ed25519 key. - std::string receiver_ed25519 = payload["recipient_keys"]["ed25519"]; - if (receiver_ed25519.empty() || - receiver_ed25519 != olm::client()->identity_keys().ed25519) { - nhlog::crypto()->warn( - "Decrypted event doesn't include our ed25519: {}", - payload.dump()); - return; - } - std::string receiver = payload["recipient"]; - if (receiver.empty() || receiver != http::client()->user_id().to_string()) { - nhlog::crypto()->warn( - "Decrypted event doesn't include our user_id: {}", - payload.dump()); - return; - } - - // Clients must confirm that the sender_key and the ed25519 field value - // under the keys property match the keys returned by /keys/query for the - // given user, and must also verify the signature of the payload. Without - // this check, a client cannot be sure that the sender device owns the - // private part of the ed25519 key it claims to have in the Olm payload. - // This is crucial when the ed25519 key corresponds to a verified device. - std::string sender_ed25519 = payload["keys"]["ed25519"]; - if (sender_ed25519.empty()) { - nhlog::crypto()->warn( - "Decrypted event doesn't include sender ed25519: {}", - payload.dump()); - return; - } - - bool from_their_device = false; - for (auto [device_id, key] : otherUserDeviceKeys.device_keys) { - auto c_key = key.keys.find("curve25519:" + device_id); - auto e_key = key.keys.find("ed25519:" + device_id); - - if (c_key == key.keys.end() || e_key == key.keys.end()) { - nhlog::crypto()->warn( - "Skipping device {} as we have no keys for it.", + if (c_key == key.keys.end() || e_key == key.keys.end()) { + nhlog::crypto()->warn("Skipping device {} as we have no keys for it.", device_id); - } else if (c_key->second == msg.sender_key && - e_key->second == sender_ed25519) { - from_their_device = true; - break; - } - } - if (!from_their_device) { - nhlog::crypto()->warn("Decrypted event isn't sent from a device " - "listed by that user! {}", - payload.dump()); - return; - } + } else if (c_key->second == msg.sender_key && e_key->second == sender_ed25519) { + from_their_device = true; + break; + } + } + if (!from_their_device) { + nhlog::crypto()->warn("Decrypted event isn't sent from a device " + "listed by that user! {}", + payload.dump()); + return; + } - { - std::string msg_type = payload["type"]; - json event_array = json::array(); - event_array.push_back(payload); + { + std::string msg_type = payload["type"]; + json event_array = json::array(); + event_array.push_back(payload); - std::vector temp_events; - mtx::responses::utils::parse_device_events(event_array, - temp_events); - if (temp_events.empty()) { - nhlog::crypto()->warn("Decrypted unknown event: {}", - payload.dump()); - return; - } - device_event = temp_events.at(0); - } + std::vector temp_events; + mtx::responses::utils::parse_device_events(event_array, temp_events); + if (temp_events.empty()) { + nhlog::crypto()->warn("Decrypted unknown event: {}", payload.dump()); + return; + } + device_event = temp_events.at(0); + } - using namespace mtx::events; - if (auto e1 = - std::get_if>(&device_event)) { - ChatPage::instance()->receivedDeviceVerificationAccept(e1->content); - } else if (auto e2 = std::get_if>( - &device_event)) { - ChatPage::instance()->receivedDeviceVerificationRequest(e2->content, - e2->sender); - } else if (auto e3 = std::get_if>( - &device_event)) { - ChatPage::instance()->receivedDeviceVerificationCancel(e3->content); - } else if (auto e4 = std::get_if>( - &device_event)) { - ChatPage::instance()->receivedDeviceVerificationKey(e4->content); - } else if (auto e5 = std::get_if>( - &device_event)) { - ChatPage::instance()->receivedDeviceVerificationMac(e5->content); - } else if (auto e6 = std::get_if>( - &device_event)) { - ChatPage::instance()->receivedDeviceVerificationStart(e6->content, - e6->sender); - } else if (auto e7 = std::get_if>( - &device_event)) { - ChatPage::instance()->receivedDeviceVerificationReady(e7->content); - } else if (auto e8 = std::get_if>( - &device_event)) { - ChatPage::instance()->receivedDeviceVerificationDone(e8->content); - } else if (auto roomKey = - std::get_if>(&device_event)) { - create_inbound_megolm_session( - *roomKey, msg.sender_key, sender_ed25519); - } else if (auto forwardedRoomKey = - std::get_if>( - &device_event)) { - forwardedRoomKey->content.forwarding_curve25519_key_chain.push_back( - msg.sender_key); - import_inbound_megolm_session(*forwardedRoomKey); - } else if (auto e = - std::get_if>(&device_event)) { - auto local_user = http::client()->user_id(); + using namespace mtx::events; + if (auto e1 = std::get_if>(&device_event)) { + ChatPage::instance()->receivedDeviceVerificationAccept(e1->content); + } else if (auto e2 = + std::get_if>(&device_event)) { + ChatPage::instance()->receivedDeviceVerificationRequest(e2->content, e2->sender); + } else if (auto e3 = + std::get_if>(&device_event)) { + ChatPage::instance()->receivedDeviceVerificationCancel(e3->content); + } else if (auto e4 = std::get_if>(&device_event)) { + ChatPage::instance()->receivedDeviceVerificationKey(e4->content); + } else if (auto e5 = std::get_if>(&device_event)) { + ChatPage::instance()->receivedDeviceVerificationMac(e5->content); + } else if (auto e6 = + std::get_if>(&device_event)) { + ChatPage::instance()->receivedDeviceVerificationStart(e6->content, e6->sender); + } else if (auto e7 = + std::get_if>(&device_event)) { + ChatPage::instance()->receivedDeviceVerificationReady(e7->content); + } else if (auto e8 = + std::get_if>(&device_event)) { + ChatPage::instance()->receivedDeviceVerificationDone(e8->content); + } else if (auto roomKey = std::get_if>(&device_event)) { + create_inbound_megolm_session(*roomKey, msg.sender_key, sender_ed25519); + } else if (auto forwardedRoomKey = + std::get_if>(&device_event)) { + forwardedRoomKey->content.forwarding_curve25519_key_chain.push_back(msg.sender_key); + import_inbound_megolm_session(*forwardedRoomKey); + } else if (auto e = std::get_if>(&device_event)) { + auto local_user = http::client()->user_id(); - if (msg.sender != local_user.to_string()) - return; + if (msg.sender != local_user.to_string()) + return; - auto secret_name = - request_id_to_secret_name.find(e->content.request_id); + auto secret_name = request_id_to_secret_name.find(e->content.request_id); - if (secret_name != request_id_to_secret_name.end()) { - nhlog::crypto()->info("Received secret: {}", - secret_name->second); + if (secret_name != request_id_to_secret_name.end()) { + nhlog::crypto()->info("Received secret: {}", secret_name->second); - mtx::events::msg::SecretRequest secretRequest{}; - secretRequest.action = - mtx::events::msg::RequestAction::Cancellation; - secretRequest.requesting_device_id = - http::client()->device_id(); - secretRequest.request_id = e->content.request_id; + mtx::events::msg::SecretRequest secretRequest{}; + secretRequest.action = mtx::events::msg::RequestAction::Cancellation; + secretRequest.requesting_device_id = http::client()->device_id(); + secretRequest.request_id = e->content.request_id; - auto verificationStatus = - cache::verificationStatus(local_user.to_string()); - - if (!verificationStatus) - return; - - auto deviceKeys = cache::userKeys(local_user.to_string()); - std::string sender_device_id; - if (deviceKeys) { - for (auto &[dev, key] : deviceKeys->device_keys) { - if (key.keys["curve25519:" + dev] == - msg.sender_key) { - sender_device_id = dev; - break; - } - } - } - - std::map< - mtx::identifiers::User, - std::map> - body; - - for (const auto &dev : - verificationStatus->verified_devices) { - if (dev != secretRequest.requesting_device_id && - dev != sender_device_id) - body[local_user][dev] = secretRequest; - } - - http::client() - ->send_to_device( - http::client()->generate_txn_id(), - body, - [name = - secret_name->second](mtx::http::RequestErr err) { - if (err) { - nhlog::net()->error( - "Failed to send request cancellation " - "for secrect " - "'{}'", - name); - } - }); - - nhlog::crypto()->info("Storing secret {}", - secret_name->second); - cache::client()->storeSecret(secret_name->second, - e->content.secret); - - request_id_to_secret_name.erase(secret_name); - } - - } else if (auto sec_req = - std::get_if>(&device_event)) { - handle_secret_request(sec_req, msg.sender); - } + auto verificationStatus = cache::verificationStatus(local_user.to_string()); + if (!verificationStatus) return; - } else { - failed_decryption = true; - } - } - if (failed_decryption) { - try { - std::map> targets; - for (auto [device_id, key] : otherUserDeviceKeys.device_keys) { - if (key.keys.at("curve25519:" + device_id) == msg.sender_key) - targets[msg.sender].push_back(device_id); + auto deviceKeys = cache::userKeys(local_user.to_string()); + std::string sender_device_id; + if (deviceKeys) { + for (auto &[dev, key] : deviceKeys->device_keys) { + if (key.keys["curve25519:" + dev] == msg.sender_key) { + sender_device_id = dev; + break; + } } + } - send_encrypted_to_device_messages( - targets, mtx::events::DeviceEvent{}, true); - nhlog::crypto()->info("Recovering from broken olm channel with {}:{}", - msg.sender, - msg.sender_key); - } catch (std::exception &e) { - nhlog::crypto()->error("Failed to recover from broken olm sessions: {}", - e.what()); + std::map> + body; + + for (const auto &dev : verificationStatus->verified_devices) { + if (dev != secretRequest.requesting_device_id && dev != sender_device_id) + body[local_user][dev] = secretRequest; + } + + http::client()->send_to_device( + http::client()->generate_txn_id(), + body, + [name = secret_name->second](mtx::http::RequestErr err) { + if (err) { + nhlog::net()->error("Failed to send request cancellation " + "for secrect " + "'{}'", + name); + } + }); + + nhlog::crypto()->info("Storing secret {}", secret_name->second); + cache::client()->storeSecret(secret_name->second, e->content.secret); + + request_id_to_secret_name.erase(secret_name); } + + } else if (auto sec_req = std::get_if>(&device_event)) { + handle_secret_request(sec_req, msg.sender); + } + + return; + } else { + failed_decryption = true; } + } + + if (failed_decryption) { + try { + std::map> targets; + for (auto [device_id, key] : otherUserDeviceKeys.device_keys) { + if (key.keys.at("curve25519:" + device_id) == msg.sender_key) + targets[msg.sender].push_back(device_id); + } + + send_encrypted_to_device_messages( + targets, mtx::events::DeviceEvent{}, true); + nhlog::crypto()->info( + "Recovering from broken olm channel with {}:{}", msg.sender, msg.sender_key); + } catch (std::exception &e) { + nhlog::crypto()->error("Failed to recover from broken olm sessions: {}", e.what()); + } + } } nlohmann::json @@ -473,320 +425,291 @@ handle_pre_key_olm_message(const std::string &sender, const std::string &sender_key, const mtx::events::msg::OlmCipherContent &content) { - nhlog::crypto()->info("opening olm session with {}", sender); + nhlog::crypto()->info("opening olm session with {}", sender); - mtx::crypto::OlmSessionPtr inbound_session = nullptr; - try { - inbound_session = - olm::client()->create_inbound_session_from(sender_key, content.body); + mtx::crypto::OlmSessionPtr inbound_session = nullptr; + try { + inbound_session = olm::client()->create_inbound_session_from(sender_key, content.body); - // We also remove the one time key used to establish that - // session so we'll have to update our copy of the account object. - cache::saveOlmAccount(olm::client()->save(cache::client()->pickleSecret())); - } catch (const mtx::crypto::olm_exception &e) { - nhlog::crypto()->critical( - "failed to create inbound session with {}: {}", sender, e.what()); - return {}; - } + // We also remove the one time key used to establish that + // session so we'll have to update our copy of the account object. + cache::saveOlmAccount(olm::client()->save(cache::client()->pickleSecret())); + } catch (const mtx::crypto::olm_exception &e) { + nhlog::crypto()->critical("failed to create inbound session with {}: {}", sender, e.what()); + return {}; + } - if (!mtx::crypto::matches_inbound_session_from( - inbound_session.get(), sender_key, content.body)) { - nhlog::crypto()->warn("inbound olm session doesn't match sender's key ({})", - sender); - return {}; - } + if (!mtx::crypto::matches_inbound_session_from( + inbound_session.get(), sender_key, content.body)) { + nhlog::crypto()->warn("inbound olm session doesn't match sender's key ({})", sender); + return {}; + } - mtx::crypto::BinaryBuf output; - try { - output = - olm::client()->decrypt_message(inbound_session.get(), content.type, content.body); - } catch (const mtx::crypto::olm_exception &e) { - nhlog::crypto()->critical( - "failed to decrypt olm message {}: {}", content.body, e.what()); - return {}; - } + mtx::crypto::BinaryBuf output; + try { + output = olm::client()->decrypt_message(inbound_session.get(), content.type, content.body); + } catch (const mtx::crypto::olm_exception &e) { + nhlog::crypto()->critical("failed to decrypt olm message {}: {}", content.body, e.what()); + return {}; + } - auto plaintext = json::parse(std::string((char *)output.data(), output.size())); - nhlog::crypto()->debug("decrypted message: \n {}", plaintext.dump(2)); + auto plaintext = json::parse(std::string((char *)output.data(), output.size())); + nhlog::crypto()->debug("decrypted message: \n {}", plaintext.dump(2)); - try { - nhlog::crypto()->debug("New olm session: {}", - mtx::crypto::session_id(inbound_session.get())); - cache::saveOlmSession( - sender_key, std::move(inbound_session), QDateTime::currentMSecsSinceEpoch()); - } catch (const lmdb::error &e) { - nhlog::db()->warn( - "failed to save inbound olm session from {}: {}", sender, e.what()); - } + try { + nhlog::crypto()->debug("New olm session: {}", + mtx::crypto::session_id(inbound_session.get())); + cache::saveOlmSession( + sender_key, std::move(inbound_session), QDateTime::currentMSecsSinceEpoch()); + } catch (const lmdb::error &e) { + nhlog::db()->warn("failed to save inbound olm session from {}: {}", sender, e.what()); + } - return plaintext; + return plaintext; } mtx::events::msg::Encrypted encrypt_group_message(const std::string &room_id, const std::string &device_id, nlohmann::json body) { - using namespace mtx::events; - using namespace mtx::identifiers; + using namespace mtx::events; + using namespace mtx::identifiers; - auto own_user_id = http::client()->user_id().to_string(); + auto own_user_id = http::client()->user_id().to_string(); - auto members = cache::client()->getMembersWithKeys( - room_id, UserSettings::instance()->onlyShareKeysWithVerifiedUsers()); + auto members = cache::client()->getMembersWithKeys( + room_id, UserSettings::instance()->onlyShareKeysWithVerifiedUsers()); - std::map> sendSessionTo; - mtx::crypto::OutboundGroupSessionPtr session = nullptr; - GroupSessionData group_session_data; + std::map> sendSessionTo; + mtx::crypto::OutboundGroupSessionPtr session = nullptr; + GroupSessionData group_session_data; - if (cache::outboundMegolmSessionExists(room_id)) { - auto res = cache::getOutboundMegolmSession(room_id); - auto encryptionSettings = cache::client()->roomEncryptionSettings(room_id); - mtx::events::state::Encryption defaultSettings; + if (cache::outboundMegolmSessionExists(room_id)) { + auto res = cache::getOutboundMegolmSession(room_id); + auto encryptionSettings = cache::client()->roomEncryptionSettings(room_id); + mtx::events::state::Encryption defaultSettings; - // rotate if we crossed the limits for this key - if (res.data.message_index < - encryptionSettings.value_or(defaultSettings).rotation_period_msgs && - (QDateTime::currentMSecsSinceEpoch() - res.data.timestamp) < - encryptionSettings.value_or(defaultSettings).rotation_period_ms) { - auto member_it = members.begin(); - auto session_member_it = res.data.currently.keys.begin(); - auto session_member_it_end = res.data.currently.keys.end(); + // rotate if we crossed the limits for this key + if (res.data.message_index < + encryptionSettings.value_or(defaultSettings).rotation_period_msgs && + (QDateTime::currentMSecsSinceEpoch() - res.data.timestamp) < + encryptionSettings.value_or(defaultSettings).rotation_period_ms) { + auto member_it = members.begin(); + auto session_member_it = res.data.currently.keys.begin(); + auto session_member_it_end = res.data.currently.keys.end(); - while (member_it != members.end() || - session_member_it != session_member_it_end) { - if (member_it == members.end()) { - // a member left, purge session! - nhlog::crypto()->debug( - "Rotating megolm session because of left member"); - break; - } + while (member_it != members.end() || session_member_it != session_member_it_end) { + if (member_it == members.end()) { + // a member left, purge session! + nhlog::crypto()->debug("Rotating megolm session because of left member"); + break; + } - if (session_member_it == session_member_it_end) { - // share with all remaining members - while (member_it != members.end()) { - sendSessionTo[member_it->first] = {}; + if (session_member_it == session_member_it_end) { + // share with all remaining members + while (member_it != members.end()) { + sendSessionTo[member_it->first] = {}; - if (member_it->second) - for (const auto &dev : - member_it->second->device_keys) - if (member_it->first != - own_user_id || - dev.first != device_id) - sendSessionTo[member_it - ->first] - .push_back(dev.first); + if (member_it->second) + for (const auto &dev : member_it->second->device_keys) + if (member_it->first != own_user_id || dev.first != device_id) + sendSessionTo[member_it->first].push_back(dev.first); - ++member_it; - } + ++member_it; + } - session = std::move(res.session); - break; - } + session = std::move(res.session); + break; + } - if (member_it->first > session_member_it->first) { - // a member left, purge session - nhlog::crypto()->debug( - "Rotating megolm session because of left member"); - break; - } else if (member_it->first < session_member_it->first) { - // new member, send them the session at this index - sendSessionTo[member_it->first] = {}; + if (member_it->first > session_member_it->first) { + // a member left, purge session + nhlog::crypto()->debug("Rotating megolm session because of left member"); + break; + } else if (member_it->first < session_member_it->first) { + // new member, send them the session at this index + sendSessionTo[member_it->first] = {}; - if (member_it->second) { - for (const auto &dev : - member_it->second->device_keys) - if (member_it->first != own_user_id || - dev.first != device_id) - sendSessionTo[member_it->first] - .push_back(dev.first); - } + if (member_it->second) { + for (const auto &dev : member_it->second->device_keys) + if (member_it->first != own_user_id || dev.first != device_id) + sendSessionTo[member_it->first].push_back(dev.first); + } - ++member_it; - } else { - // compare devices - bool device_removed = false; - for (const auto &dev : - session_member_it->second.deviceids) { - if (!member_it->second || - !member_it->second->device_keys.count( - dev.first)) { - device_removed = true; - break; - } - } - - if (device_removed) { - // device removed, rotate session! - nhlog::crypto()->debug( - "Rotating megolm session because of removed " - "device of {}", - member_it->first); - break; - } - - // check for new devices to share with - if (member_it->second) - for (const auto &dev : - member_it->second->device_keys) - if (!session_member_it->second.deviceids - .count(dev.first) && - (member_it->first != own_user_id || - dev.first != device_id)) - sendSessionTo[member_it->first] - .push_back(dev.first); - - ++member_it; - ++session_member_it; - if (member_it == members.end() && - session_member_it == session_member_it_end) { - // all devices match or are newly added - session = std::move(res.session); - } - } + ++member_it; + } else { + // compare devices + bool device_removed = false; + for (const auto &dev : session_member_it->second.deviceids) { + if (!member_it->second || + !member_it->second->device_keys.count(dev.first)) { + device_removed = true; + break; } - } + } - group_session_data = std::move(res.data); + if (device_removed) { + // device removed, rotate session! + nhlog::crypto()->debug("Rotating megolm session because of removed " + "device of {}", + member_it->first); + break; + } + + // check for new devices to share with + if (member_it->second) + for (const auto &dev : member_it->second->device_keys) + if (!session_member_it->second.deviceids.count(dev.first) && + (member_it->first != own_user_id || dev.first != device_id)) + sendSessionTo[member_it->first].push_back(dev.first); + + ++member_it; + ++session_member_it; + if (member_it == members.end() && session_member_it == session_member_it_end) { + // all devices match or are newly added + session = std::move(res.session); + } + } + } } - if (!session) { - nhlog::ui()->debug("creating new outbound megolm session"); + group_session_data = std::move(res.data); + } - // Create a new outbound megolm session. - session = olm::client()->init_outbound_group_session(); - const auto session_id = mtx::crypto::session_id(session.get()); - const auto session_key = mtx::crypto::session_key(session.get()); + if (!session) { + nhlog::ui()->debug("creating new outbound megolm session"); - // Saving the new megolm session. - GroupSessionData session_data{}; - session_data.message_index = 0; - session_data.timestamp = QDateTime::currentMSecsSinceEpoch(); - session_data.sender_claimed_ed25519_key = olm::client()->identity_keys().ed25519; + // Create a new outbound megolm session. + session = olm::client()->init_outbound_group_session(); + const auto session_id = mtx::crypto::session_id(session.get()); + const auto session_key = mtx::crypto::session_key(session.get()); - sendSessionTo.clear(); + // Saving the new megolm session. + GroupSessionData session_data{}; + session_data.message_index = 0; + session_data.timestamp = QDateTime::currentMSecsSinceEpoch(); + session_data.sender_claimed_ed25519_key = olm::client()->identity_keys().ed25519; - for (const auto &[user, devices] : members) { - sendSessionTo[user] = {}; - session_data.currently.keys[user] = {}; - if (devices) { - for (const auto &[device_id_, key] : devices->device_keys) { - (void)key; - if (device_id != device_id_ || user != own_user_id) { - sendSessionTo[user].push_back(device_id_); - session_data.currently.keys[user] - .deviceids[device_id_] = 0; - } - } - } + sendSessionTo.clear(); + + for (const auto &[user, devices] : members) { + sendSessionTo[user] = {}; + session_data.currently.keys[user] = {}; + if (devices) { + for (const auto &[device_id_, key] : devices->device_keys) { + (void)key; + if (device_id != device_id_ || user != own_user_id) { + sendSessionTo[user].push_back(device_id_); + session_data.currently.keys[user].deviceids[device_id_] = 0; + } } - - { - MegolmSessionIndex index; - index.room_id = room_id; - index.session_id = session_id; - index.sender_key = olm::client()->identity_keys().curve25519; - auto megolm_session = - olm::client()->init_inbound_group_session(session_key); - backup_session_key(index, session_data, megolm_session); - cache::saveInboundMegolmSession( - index, std::move(megolm_session), session_data); - } - - cache::saveOutboundMegolmSession(room_id, session_data, session); - group_session_data = std::move(session_data); + } } - mtx::events::DeviceEvent megolm_payload{}; - megolm_payload.content.algorithm = MEGOLM_ALGO; - megolm_payload.content.room_id = room_id; - megolm_payload.content.session_id = mtx::crypto::session_id(session.get()); - megolm_payload.content.session_key = mtx::crypto::session_key(session.get()); - megolm_payload.type = mtx::events::EventType::RoomKey; - - if (!sendSessionTo.empty()) - olm::send_encrypted_to_device_messages(sendSessionTo, megolm_payload); - - // relations shouldn't be encrypted... - mtx::common::Relations relations = mtx::common::parse_relations(body["content"]); - - auto payload = olm::client()->encrypt_group_message(session.get(), body.dump()); - - // Prepare the m.room.encrypted event. - msg::Encrypted data; - data.ciphertext = std::string((char *)payload.data(), payload.size()); - data.sender_key = olm::client()->identity_keys().curve25519; - data.session_id = mtx::crypto::session_id(session.get()); - data.device_id = device_id; - data.algorithm = MEGOLM_ALGO; - data.relations = relations; - - group_session_data.message_index = olm_outbound_group_session_message_index(session.get()); - nhlog::crypto()->debug("next message_index {}", group_session_data.message_index); - - // update current set of members for the session with the new members and that message_index - for (const auto &[user, devices] : sendSessionTo) { - if (!group_session_data.currently.keys.count(user)) - group_session_data.currently.keys[user] = {}; - - for (const auto &device_id_ : devices) { - if (!group_session_data.currently.keys[user].deviceids.count(device_id_)) - group_session_data.currently.keys[user].deviceids[device_id_] = - group_session_data.message_index; - } + { + MegolmSessionIndex index; + index.room_id = room_id; + index.session_id = session_id; + index.sender_key = olm::client()->identity_keys().curve25519; + auto megolm_session = olm::client()->init_inbound_group_session(session_key); + backup_session_key(index, session_data, megolm_session); + cache::saveInboundMegolmSession(index, std::move(megolm_session), session_data); } - // We need to re-pickle the session after we send a message to save the new message_index. - cache::updateOutboundMegolmSession(room_id, group_session_data, session); + cache::saveOutboundMegolmSession(room_id, session_data, session); + group_session_data = std::move(session_data); + } - return data; + mtx::events::DeviceEvent megolm_payload{}; + megolm_payload.content.algorithm = MEGOLM_ALGO; + megolm_payload.content.room_id = room_id; + megolm_payload.content.session_id = mtx::crypto::session_id(session.get()); + megolm_payload.content.session_key = mtx::crypto::session_key(session.get()); + megolm_payload.type = mtx::events::EventType::RoomKey; + + if (!sendSessionTo.empty()) + olm::send_encrypted_to_device_messages(sendSessionTo, megolm_payload); + + // relations shouldn't be encrypted... + mtx::common::Relations relations = mtx::common::parse_relations(body["content"]); + + auto payload = olm::client()->encrypt_group_message(session.get(), body.dump()); + + // Prepare the m.room.encrypted event. + msg::Encrypted data; + data.ciphertext = std::string((char *)payload.data(), payload.size()); + data.sender_key = olm::client()->identity_keys().curve25519; + data.session_id = mtx::crypto::session_id(session.get()); + data.device_id = device_id; + data.algorithm = MEGOLM_ALGO; + data.relations = relations; + + group_session_data.message_index = olm_outbound_group_session_message_index(session.get()); + nhlog::crypto()->debug("next message_index {}", group_session_data.message_index); + + // update current set of members for the session with the new members and that message_index + for (const auto &[user, devices] : sendSessionTo) { + if (!group_session_data.currently.keys.count(user)) + group_session_data.currently.keys[user] = {}; + + for (const auto &device_id_ : devices) { + if (!group_session_data.currently.keys[user].deviceids.count(device_id_)) + group_session_data.currently.keys[user].deviceids[device_id_] = + group_session_data.message_index; + } + } + + // We need to re-pickle the session after we send a message to save the new message_index. + cache::updateOutboundMegolmSession(room_id, group_session_data, session); + + return data; } nlohmann::json try_olm_decryption(const std::string &sender_key, const mtx::events::msg::OlmCipherContent &msg) { - auto session_ids = cache::getOlmSessions(sender_key); + auto session_ids = cache::getOlmSessions(sender_key); - nhlog::crypto()->info("attempt to decrypt message with {} known session_ids", - session_ids.size()); + nhlog::crypto()->info("attempt to decrypt message with {} known session_ids", + session_ids.size()); - for (const auto &id : session_ids) { - auto session = cache::getOlmSession(sender_key, id); + for (const auto &id : session_ids) { + auto session = cache::getOlmSession(sender_key, id); - if (!session) { - nhlog::crypto()->warn("Unknown olm session: {}:{}", sender_key, id); - continue; - } - - mtx::crypto::BinaryBuf text; - - try { - text = olm::client()->decrypt_message(session->get(), msg.type, msg.body); - nhlog::crypto()->debug("Updated olm session: {}", - mtx::crypto::session_id(session->get())); - cache::saveOlmSession( - id, std::move(session.value()), QDateTime::currentMSecsSinceEpoch()); - } catch (const mtx::crypto::olm_exception &e) { - nhlog::crypto()->debug("failed to decrypt olm message ({}, {}) with {}: {}", - msg.type, - sender_key, - id, - e.what()); - continue; - } catch (const lmdb::error &e) { - nhlog::crypto()->critical("failed to save session: {}", e.what()); - return {}; - } - - try { - return json::parse(std::string_view((char *)text.data(), text.size())); - } catch (const json::exception &e) { - nhlog::crypto()->critical( - "failed to parse the decrypted session msg: {} {}", - e.what(), - std::string_view((char *)text.data(), text.size())); - } + if (!session) { + nhlog::crypto()->warn("Unknown olm session: {}:{}", sender_key, id); + continue; } - return {}; + mtx::crypto::BinaryBuf text; + + try { + text = olm::client()->decrypt_message(session->get(), msg.type, msg.body); + nhlog::crypto()->debug("Updated olm session: {}", + mtx::crypto::session_id(session->get())); + cache::saveOlmSession( + id, std::move(session.value()), QDateTime::currentMSecsSinceEpoch()); + } catch (const mtx::crypto::olm_exception &e) { + nhlog::crypto()->debug("failed to decrypt olm message ({}, {}) with {}: {}", + msg.type, + sender_key, + id, + e.what()); + continue; + } catch (const lmdb::error &e) { + nhlog::crypto()->critical("failed to save session: {}", e.what()); + return {}; + } + + try { + return json::parse(std::string_view((char *)text.data(), text.size())); + } catch (const json::exception &e) { + nhlog::crypto()->critical("failed to parse the decrypted session msg: {} {}", + e.what(), + std::string_view((char *)text.data(), text.size())); + } + } + + return {}; } void @@ -794,75 +717,73 @@ create_inbound_megolm_session(const mtx::events::DeviceEventinit_inbound_group_session(roomKey.content.session_key); - backup_session_key(index, data, megolm_session); - cache::saveInboundMegolmSession(index, std::move(megolm_session), data); - } catch (const lmdb::error &e) { - nhlog::crypto()->critical("failed to save inbound megolm session: {}", e.what()); - return; - } catch (const mtx::crypto::olm_exception &e) { - nhlog::crypto()->critical("failed to create inbound megolm session: {}", e.what()); - return; - } + auto megolm_session = + olm::client()->init_inbound_group_session(roomKey.content.session_key); + backup_session_key(index, data, megolm_session); + cache::saveInboundMegolmSession(index, std::move(megolm_session), data); + } catch (const lmdb::error &e) { + nhlog::crypto()->critical("failed to save inbound megolm session: {}", e.what()); + return; + } catch (const mtx::crypto::olm_exception &e) { + nhlog::crypto()->critical("failed to create inbound megolm session: {}", e.what()); + return; + } - nhlog::crypto()->info( - "established inbound megolm session ({}, {})", roomKey.content.room_id, roomKey.sender); + nhlog::crypto()->info( + "established inbound megolm session ({}, {})", roomKey.content.room_id, roomKey.sender); - ChatPage::instance()->receivedSessionKey(index.room_id, index.session_id); + ChatPage::instance()->receivedSessionKey(index.room_id, index.session_id); } void import_inbound_megolm_session( const mtx::events::DeviceEvent &roomKey) { - MegolmSessionIndex index; - index.room_id = roomKey.content.room_id; - index.session_id = roomKey.content.session_id; - index.sender_key = roomKey.content.sender_key; + MegolmSessionIndex index; + index.room_id = roomKey.content.room_id; + index.session_id = roomKey.content.session_id; + index.sender_key = roomKey.content.sender_key; - try { - auto megolm_session = - olm::client()->import_inbound_group_session(roomKey.content.session_key); + try { + auto megolm_session = + olm::client()->import_inbound_group_session(roomKey.content.session_key); - GroupSessionData data{}; - data.forwarding_curve25519_key_chain = - roomKey.content.forwarding_curve25519_key_chain; - data.sender_claimed_ed25519_key = roomKey.content.sender_claimed_ed25519_key; - // may have come from online key backup, so we can't trust it... - data.trusted = false; - // if we got it forwarded from the sender, assume it is trusted. They may still have - // used key backup, but it is unlikely. - if (roomKey.content.forwarding_curve25519_key_chain.size() == 1 && - roomKey.content.forwarding_curve25519_key_chain.back() == - roomKey.content.sender_key) { - data.trusted = true; - } - - backup_session_key(index, data, megolm_session); - cache::saveInboundMegolmSession(index, std::move(megolm_session), data); - } catch (const lmdb::error &e) { - nhlog::crypto()->critical("failed to save inbound megolm session: {}", e.what()); - return; - } catch (const mtx::crypto::olm_exception &e) { - nhlog::crypto()->critical("failed to import inbound megolm session: {}", e.what()); - return; + GroupSessionData data{}; + data.forwarding_curve25519_key_chain = roomKey.content.forwarding_curve25519_key_chain; + data.sender_claimed_ed25519_key = roomKey.content.sender_claimed_ed25519_key; + // may have come from online key backup, so we can't trust it... + data.trusted = false; + // if we got it forwarded from the sender, assume it is trusted. They may still have + // used key backup, but it is unlikely. + if (roomKey.content.forwarding_curve25519_key_chain.size() == 1 && + roomKey.content.forwarding_curve25519_key_chain.back() == roomKey.content.sender_key) { + data.trusted = true; } - nhlog::crypto()->info( - "established inbound megolm session ({}, {})", roomKey.content.room_id, roomKey.sender); + backup_session_key(index, data, megolm_session); + cache::saveInboundMegolmSession(index, std::move(megolm_session), data); + } catch (const lmdb::error &e) { + nhlog::crypto()->critical("failed to save inbound megolm session: {}", e.what()); + return; + } catch (const mtx::crypto::olm_exception &e) { + nhlog::crypto()->critical("failed to import inbound megolm session: {}", e.what()); + return; + } - ChatPage::instance()->receivedSessionKey(index.room_id, index.session_id); + nhlog::crypto()->info( + "established inbound megolm session ({}, {})", roomKey.content.room_id, roomKey.sender); + + ChatPage::instance()->receivedSessionKey(index.room_id, index.session_id); } void @@ -870,165 +791,156 @@ backup_session_key(const MegolmSessionIndex &idx, const GroupSessionData &data, mtx::crypto::InboundGroupSessionPtr &session) { - try { - if (!UserSettings::instance()->useOnlineKeyBackup()) { - // Online key backup disabled - return; - } - - auto backupVersion = cache::client()->backupVersion(); - if (!backupVersion) { - // no trusted OKB - return; - } - - using namespace mtx::crypto; - - auto decryptedSecret = - cache::secret(mtx::secret_storage::secrets::megolm_backup_v1); - if (!decryptedSecret) { - // no backup key available - return; - } - auto sessionDecryptionKey = to_binary_buf(base642bin(*decryptedSecret)); - - auto public_key = - mtx::crypto::CURVE25519_public_key_from_private(sessionDecryptionKey); - - mtx::responses::backup::SessionData sessionData; - sessionData.algorithm = mtx::crypto::MEGOLM_ALGO; - sessionData.forwarding_curve25519_key_chain = data.forwarding_curve25519_key_chain; - sessionData.sender_claimed_keys["ed25519"] = data.sender_claimed_ed25519_key; - sessionData.sender_key = idx.sender_key; - sessionData.session_key = mtx::crypto::export_session(session.get(), -1); - - auto encrypt_session = mtx::crypto::encrypt_session(sessionData, public_key); - - mtx::responses::backup::SessionBackup bk; - bk.first_message_index = olm_inbound_group_session_first_known_index(session.get()); - bk.forwarded_count = data.forwarding_curve25519_key_chain.size(); - bk.is_verified = false; - bk.session_data = std::move(encrypt_session); - - http::client()->put_room_keys( - backupVersion->version, - idx.room_id, - idx.session_id, - bk, - [idx](mtx::http::RequestErr err) { - if (err) { - nhlog::net()->warn( - "failed to backup session key ({}:{}): {} ({})", - idx.room_id, - idx.session_id, - err->matrix_error.error, - static_cast(err->status_code)); - } else { - nhlog::crypto()->debug( - "backed up session key ({}:{})", idx.room_id, idx.session_id); - } - }); - } catch (std::exception &e) { - nhlog::net()->warn("failed to backup session key: {}", e.what()); - } -} - -void -mark_keys_as_published() -{ - olm::client()->mark_keys_as_published(); - cache::saveOlmAccount(olm::client()->save(cache::client()->pickleSecret())); -} - -void -lookup_keybackup(const std::string room, const std::string session_id) -{ + try { if (!UserSettings::instance()->useOnlineKeyBackup()) { - // Online key backup disabled - return; + // Online key backup disabled + return; } auto backupVersion = cache::client()->backupVersion(); if (!backupVersion) { - // no trusted OKB - return; + // no trusted OKB + return; } using namespace mtx::crypto; auto decryptedSecret = cache::secret(mtx::secret_storage::secrets::megolm_backup_v1); if (!decryptedSecret) { - // no backup key available - return; + // no backup key available + return; } auto sessionDecryptionKey = to_binary_buf(base642bin(*decryptedSecret)); - http::client()->room_keys( + auto public_key = mtx::crypto::CURVE25519_public_key_from_private(sessionDecryptionKey); + + mtx::responses::backup::SessionData sessionData; + sessionData.algorithm = mtx::crypto::MEGOLM_ALGO; + sessionData.forwarding_curve25519_key_chain = data.forwarding_curve25519_key_chain; + sessionData.sender_claimed_keys["ed25519"] = data.sender_claimed_ed25519_key; + sessionData.sender_key = idx.sender_key; + sessionData.session_key = mtx::crypto::export_session(session.get(), -1); + + auto encrypt_session = mtx::crypto::encrypt_session(sessionData, public_key); + + mtx::responses::backup::SessionBackup bk; + bk.first_message_index = olm_inbound_group_session_first_known_index(session.get()); + bk.forwarded_count = data.forwarding_curve25519_key_chain.size(); + bk.is_verified = false; + bk.session_data = std::move(encrypt_session); + + http::client()->put_room_keys( backupVersion->version, - room, - session_id, - [room, session_id, sessionDecryptionKey](const mtx::responses::backup::SessionBackup &bk, - mtx::http::RequestErr err) { - if (err) { - if (err->status_code != 404) - nhlog::crypto()->error( - "Failed to dowload key {}:{}: {} - {}", - room, - session_id, - mtx::errors::to_string(err->matrix_error.errcode), - err->matrix_error.error); - return; - } - try { - auto session = decrypt_session(bk.session_data, sessionDecryptionKey); - - if (session.algorithm != mtx::crypto::MEGOLM_ALGO) - // don't know this algorithm - return; - - MegolmSessionIndex index; - index.room_id = room; - index.session_id = session_id; - index.sender_key = session.sender_key; - - GroupSessionData data{}; - data.forwarding_curve25519_key_chain = - session.forwarding_curve25519_key_chain; - data.sender_claimed_ed25519_key = session.sender_claimed_keys["ed25519"]; - // online key backup can't be trusted, because anyone can upload to it. - data.trusted = false; - - auto megolm_session = - olm::client()->import_inbound_group_session(session.session_key); - - if (!cache::inboundMegolmSessionExists(index) || - olm_inbound_group_session_first_known_index(megolm_session.get()) < - olm_inbound_group_session_first_known_index( - cache::getInboundMegolmSession(index).get())) { - cache::saveInboundMegolmSession( - index, std::move(megolm_session), data); - - nhlog::crypto()->info("imported inbound megolm session " - "from key backup ({}, {})", - room, - session_id); - - // call on UI thread - QTimer::singleShot(0, ChatPage::instance(), [index] { - ChatPage::instance()->receivedSessionKey( - index.room_id, index.session_id); - }); - } - } catch (const lmdb::error &e) { - nhlog::crypto()->critical("failed to save inbound megolm session: {}", - e.what()); - return; - } catch (const mtx::crypto::olm_exception &e) { - nhlog::crypto()->critical("failed to import inbound megolm session: {}", - e.what()); - return; - } + idx.room_id, + idx.session_id, + bk, + [idx](mtx::http::RequestErr err) { + if (err) { + nhlog::net()->warn("failed to backup session key ({}:{}): {} ({})", + idx.room_id, + idx.session_id, + err->matrix_error.error, + static_cast(err->status_code)); + } else { + nhlog::crypto()->debug( + "backed up session key ({}:{})", idx.room_id, idx.session_id); + } }); + } catch (std::exception &e) { + nhlog::net()->warn("failed to backup session key: {}", e.what()); + } +} + +void +mark_keys_as_published() +{ + olm::client()->mark_keys_as_published(); + cache::saveOlmAccount(olm::client()->save(cache::client()->pickleSecret())); +} + +void +lookup_keybackup(const std::string room, const std::string session_id) +{ + if (!UserSettings::instance()->useOnlineKeyBackup()) { + // Online key backup disabled + return; + } + + auto backupVersion = cache::client()->backupVersion(); + if (!backupVersion) { + // no trusted OKB + return; + } + + using namespace mtx::crypto; + + auto decryptedSecret = cache::secret(mtx::secret_storage::secrets::megolm_backup_v1); + if (!decryptedSecret) { + // no backup key available + return; + } + auto sessionDecryptionKey = to_binary_buf(base642bin(*decryptedSecret)); + + http::client()->room_keys( + backupVersion->version, + room, + session_id, + [room, session_id, sessionDecryptionKey](const mtx::responses::backup::SessionBackup &bk, + mtx::http::RequestErr err) { + if (err) { + if (err->status_code != 404) + nhlog::crypto()->error("Failed to dowload key {}:{}: {} - {}", + room, + session_id, + mtx::errors::to_string(err->matrix_error.errcode), + err->matrix_error.error); + return; + } + try { + auto session = decrypt_session(bk.session_data, sessionDecryptionKey); + + if (session.algorithm != mtx::crypto::MEGOLM_ALGO) + // don't know this algorithm + return; + + MegolmSessionIndex index; + index.room_id = room; + index.session_id = session_id; + index.sender_key = session.sender_key; + + GroupSessionData data{}; + data.forwarding_curve25519_key_chain = session.forwarding_curve25519_key_chain; + data.sender_claimed_ed25519_key = session.sender_claimed_keys["ed25519"]; + // online key backup can't be trusted, because anyone can upload to it. + data.trusted = false; + + auto megolm_session = + olm::client()->import_inbound_group_session(session.session_key); + + if (!cache::inboundMegolmSessionExists(index) || + olm_inbound_group_session_first_known_index(megolm_session.get()) < + olm_inbound_group_session_first_known_index( + cache::getInboundMegolmSession(index).get())) { + cache::saveInboundMegolmSession(index, std::move(megolm_session), data); + + nhlog::crypto()->info("imported inbound megolm session " + "from key backup ({}, {})", + room, + session_id); + + // call on UI thread + QTimer::singleShot(0, ChatPage::instance(), [index] { + ChatPage::instance()->receivedSessionKey(index.room_id, index.session_id); + }); + } + } catch (const lmdb::error &e) { + nhlog::crypto()->critical("failed to save inbound megolm session: {}", e.what()); + return; + } catch (const mtx::crypto::olm_exception &e) { + nhlog::crypto()->critical("failed to import inbound megolm session: {}", e.what()); + return; + } + }); } void @@ -1036,158 +948,152 @@ send_key_request_for(mtx::events::EncryptedEvent e, const std::string &request_id, bool cancel) { - using namespace mtx::events; + using namespace mtx::events; - nhlog::crypto()->debug("sending key request: sender_key {}, session_id {}", - e.content.sender_key, - e.content.session_id); + nhlog::crypto()->debug("sending key request: sender_key {}, session_id {}", + e.content.sender_key, + e.content.session_id); - mtx::events::msg::KeyRequest request; - request.action = cancel ? mtx::events::msg::RequestAction::Cancellation - : mtx::events::msg::RequestAction::Request; + mtx::events::msg::KeyRequest request; + request.action = cancel ? mtx::events::msg::RequestAction::Cancellation + : mtx::events::msg::RequestAction::Request; - request.algorithm = MEGOLM_ALGO; - request.room_id = e.room_id; - request.sender_key = e.content.sender_key; - request.session_id = e.content.session_id; - request.request_id = request_id; - request.requesting_device_id = http::client()->device_id(); + request.algorithm = MEGOLM_ALGO; + request.room_id = e.room_id; + request.sender_key = e.content.sender_key; + request.session_id = e.content.session_id; + request.request_id = request_id; + request.requesting_device_id = http::client()->device_id(); - nhlog::crypto()->debug("m.room_key_request: {}", json(request).dump(2)); + nhlog::crypto()->debug("m.room_key_request: {}", json(request).dump(2)); - std::map> body; - body[mtx::identifiers::parse(e.sender)][e.content.device_id] = - request; - body[http::client()->user_id()]["*"] = request; + std::map> body; + body[mtx::identifiers::parse(e.sender)][e.content.device_id] = request; + body[http::client()->user_id()]["*"] = request; - http::client()->send_to_device( - http::client()->generate_txn_id(), body, [e](mtx::http::RequestErr err) { - if (err) { - nhlog::net()->warn("failed to send " - "send_to_device " - "message: {}", - err->matrix_error.error); - } + http::client()->send_to_device( + http::client()->generate_txn_id(), body, [e](mtx::http::RequestErr err) { + if (err) { + nhlog::net()->warn("failed to send " + "send_to_device " + "message: {}", + err->matrix_error.error); + } - nhlog::net()->info("m.room_key_request sent to {}:{} and your own devices", - e.sender, - e.content.device_id); - }); + nhlog::net()->info( + "m.room_key_request sent to {}:{} and your own devices", e.sender, e.content.device_id); + }); - // http::client()->room_keys + // http::client()->room_keys } void handle_key_request_message(const mtx::events::DeviceEvent &req) { - if (req.content.algorithm != MEGOLM_ALGO) { - nhlog::crypto()->debug("ignoring key request {} with invalid algorithm: {}", - req.content.request_id, - req.content.algorithm); - return; + if (req.content.algorithm != MEGOLM_ALGO) { + nhlog::crypto()->debug("ignoring key request {} with invalid algorithm: {}", + req.content.request_id, + req.content.algorithm); + return; + } + + // Check if we were the sender of the session being requested (unless it is actually us + // requesting the session). + if (req.sender != http::client()->user_id().to_string() && + req.content.sender_key != olm::client()->identity_keys().curve25519) { + nhlog::crypto()->debug( + "ignoring key request {} because we did not create the requested session: " + "\nrequested({}) ours({})", + req.content.request_id, + req.content.sender_key, + olm::client()->identity_keys().curve25519); + return; + } + + // Check that the requested session_id and the one we have saved match. + MegolmSessionIndex index{}; + index.room_id = req.content.room_id; + index.session_id = req.content.session_id; + index.sender_key = req.content.sender_key; + + // Check if we have the keys for the requested session. + auto sessionData = cache::getMegolmSessionData(index); + if (!sessionData) { + nhlog::crypto()->warn("requested session not found in room: {}", req.content.room_id); + return; + } + + const auto session = cache::getInboundMegolmSession(index); + if (!session) { + nhlog::crypto()->warn("No session with id {} in db", req.content.session_id); + return; + } + + if (!cache::isRoomMember(req.sender, req.content.room_id)) { + nhlog::crypto()->warn("user {} that requested the session key is not member of the room {}", + req.sender, + req.content.room_id); + return; + } + + // check if device is verified + auto verificationStatus = cache::verificationStatus(req.sender); + bool verifiedDevice = false; + if (verificationStatus && + // Share keys, if the option to share with trusted users is enabled or with yourself + (ChatPage::instance()->userSettings()->shareKeysWithTrustedUsers() || + req.sender == http::client()->user_id().to_string())) { + for (const auto &dev : verificationStatus->verified_devices) { + if (dev == req.content.requesting_device_id) { + verifiedDevice = true; + nhlog::crypto()->debug("Verified device: {}", dev); + break; + } } + } - // Check if we were the sender of the session being requested (unless it is actually us - // requesting the session). - if (req.sender != http::client()->user_id().to_string() && - req.content.sender_key != olm::client()->identity_keys().curve25519) { - nhlog::crypto()->debug( - "ignoring key request {} because we did not create the requested session: " - "\nrequested({}) ours({})", - req.content.request_id, - req.content.sender_key, - olm::client()->identity_keys().curve25519); - return; + bool shouldSeeKeys = false; + uint64_t minimumIndex = -1; + if (sessionData->currently.keys.count(req.sender)) { + if (sessionData->currently.keys.at(req.sender) + .deviceids.count(req.content.requesting_device_id)) { + shouldSeeKeys = true; + minimumIndex = sessionData->currently.keys.at(req.sender) + .deviceids.at(req.content.requesting_device_id); } + } - // Check that the requested session_id and the one we have saved match. - MegolmSessionIndex index{}; - index.room_id = req.content.room_id; - index.session_id = req.content.session_id; - index.sender_key = req.content.sender_key; + if (!verifiedDevice && !shouldSeeKeys) { + nhlog::crypto()->debug("ignoring key request for room {}", req.content.room_id); + return; + } - // Check if we have the keys for the requested session. - auto sessionData = cache::getMegolmSessionData(index); - if (!sessionData) { - nhlog::crypto()->warn("requested session not found in room: {}", - req.content.room_id); - return; - } + if (verifiedDevice) { + // share the minimum index we have + minimumIndex = -1; + } - const auto session = cache::getInboundMegolmSession(index); - if (!session) { - nhlog::crypto()->warn("No session with id {} in db", req.content.session_id); - return; - } + try { + auto session_key = mtx::crypto::export_session(session.get(), minimumIndex); - if (!cache::isRoomMember(req.sender, req.content.room_id)) { - nhlog::crypto()->warn( - "user {} that requested the session key is not member of the room {}", - req.sender, - req.content.room_id); - return; - } + // + // Prepare the m.room_key event. + // + mtx::events::msg::ForwardedRoomKey forward_key{}; + forward_key.algorithm = MEGOLM_ALGO; + forward_key.room_id = index.room_id; + forward_key.session_id = index.session_id; + forward_key.session_key = session_key; + forward_key.sender_key = index.sender_key; - // check if device is verified - auto verificationStatus = cache::verificationStatus(req.sender); - bool verifiedDevice = false; - if (verificationStatus && - // Share keys, if the option to share with trusted users is enabled or with yourself - (ChatPage::instance()->userSettings()->shareKeysWithTrustedUsers() || - req.sender == http::client()->user_id().to_string())) { - for (const auto &dev : verificationStatus->verified_devices) { - if (dev == req.content.requesting_device_id) { - verifiedDevice = true; - nhlog::crypto()->debug("Verified device: {}", dev); - break; - } - } - } + // TODO(Nico): Figure out if this is correct + forward_key.sender_claimed_ed25519_key = sessionData->sender_claimed_ed25519_key; + forward_key.forwarding_curve25519_key_chain = sessionData->forwarding_curve25519_key_chain; - bool shouldSeeKeys = false; - uint64_t minimumIndex = -1; - if (sessionData->currently.keys.count(req.sender)) { - if (sessionData->currently.keys.at(req.sender) - .deviceids.count(req.content.requesting_device_id)) { - shouldSeeKeys = true; - minimumIndex = sessionData->currently.keys.at(req.sender) - .deviceids.at(req.content.requesting_device_id); - } - } - - if (!verifiedDevice && !shouldSeeKeys) { - nhlog::crypto()->debug("ignoring key request for room {}", req.content.room_id); - return; - } - - if (verifiedDevice) { - // share the minimum index we have - minimumIndex = -1; - } - - try { - auto session_key = mtx::crypto::export_session(session.get(), minimumIndex); - - // - // Prepare the m.room_key event. - // - mtx::events::msg::ForwardedRoomKey forward_key{}; - forward_key.algorithm = MEGOLM_ALGO; - forward_key.room_id = index.room_id; - forward_key.session_id = index.session_id; - forward_key.session_key = session_key; - forward_key.sender_key = index.sender_key; - - // TODO(Nico): Figure out if this is correct - forward_key.sender_claimed_ed25519_key = sessionData->sender_claimed_ed25519_key; - forward_key.forwarding_curve25519_key_chain = - sessionData->forwarding_curve25519_key_chain; - - send_megolm_key_to_device( - req.sender, req.content.requesting_device_id, forward_key); - } catch (std::exception &e) { - nhlog::crypto()->error("Failed to forward session key: {}", e.what()); - } + send_megolm_key_to_device(req.sender, req.content.requesting_device_id, forward_key); + } catch (std::exception &e) { + nhlog::crypto()->error("Failed to forward session key: {}", e.what()); + } } void @@ -1195,14 +1101,14 @@ send_megolm_key_to_device(const std::string &user_id, const std::string &device_id, const mtx::events::msg::ForwardedRoomKey &payload) { - mtx::events::DeviceEvent room_key; - room_key.content = payload; - room_key.type = mtx::events::EventType::ForwardedRoomKey; + mtx::events::DeviceEvent room_key; + room_key.content = payload; + room_key.type = mtx::events::EventType::ForwardedRoomKey; - std::map> targets; - targets[user_id] = {device_id}; - send_encrypted_to_device_messages(targets, room_key); - nhlog::crypto()->debug("Forwarded key to {}:{}", user_id, device_id); + std::map> targets; + targets[user_id] = {device_id}; + send_encrypted_to_device_messages(targets, room_key); + nhlog::crypto()->debug("Forwarded key to {}:{}", user_id, device_id); } DecryptionResult @@ -1210,79 +1116,74 @@ decryptEvent(const MegolmSessionIndex &index, const mtx::events::EncryptedEvent &event, bool dont_write_db) { - try { - if (!cache::client()->inboundMegolmSessionExists(index)) { - return {DecryptionErrorCode::MissingSession, std::nullopt, std::nullopt}; - } - } catch (const lmdb::error &e) { - return {DecryptionErrorCode::DbError, e.what(), std::nullopt}; + try { + if (!cache::client()->inboundMegolmSessionExists(index)) { + return {DecryptionErrorCode::MissingSession, std::nullopt, std::nullopt}; } + } catch (const lmdb::error &e) { + return {DecryptionErrorCode::DbError, e.what(), std::nullopt}; + } - // TODO: Lookup index,event_id,origin_server_ts tuple for replay attack errors + // TODO: Lookup index,event_id,origin_server_ts tuple for replay attack errors - std::string msg_str; - try { - auto session = cache::client()->getInboundMegolmSession(index); - auto sessionData = - cache::client()->getMegolmSessionData(index).value_or(GroupSessionData{}); + std::string msg_str; + try { + auto session = cache::client()->getInboundMegolmSession(index); + auto sessionData = + cache::client()->getMegolmSessionData(index).value_or(GroupSessionData{}); - auto res = - olm::client()->decrypt_group_message(session.get(), event.content.ciphertext); - msg_str = std::string((char *)res.data.data(), res.data.size()); + auto res = olm::client()->decrypt_group_message(session.get(), event.content.ciphertext); + msg_str = std::string((char *)res.data.data(), res.data.size()); - if (!event.event_id.empty() && event.event_id[0] == '$') { - auto oldIdx = sessionData.indices.find(res.message_index); - if (oldIdx != sessionData.indices.end()) { - if (oldIdx->second != event.event_id) - return {DecryptionErrorCode::ReplayAttack, - std::nullopt, - std::nullopt}; - } else if (!dont_write_db) { - sessionData.indices[res.message_index] = event.event_id; - cache::client()->saveInboundMegolmSession( - index, std::move(session), sessionData); - } - } - } catch (const lmdb::error &e) { - return {DecryptionErrorCode::DbError, e.what(), std::nullopt}; - } catch (const mtx::crypto::olm_exception &e) { - if (e.error_code() == mtx::crypto::OlmErrorCode::UNKNOWN_MESSAGE_INDEX) - return {DecryptionErrorCode::MissingSessionIndex, e.what(), std::nullopt}; - return {DecryptionErrorCode::DecryptionFailed, e.what(), std::nullopt}; + if (!event.event_id.empty() && event.event_id[0] == '$') { + auto oldIdx = sessionData.indices.find(res.message_index); + if (oldIdx != sessionData.indices.end()) { + if (oldIdx->second != event.event_id) + return {DecryptionErrorCode::ReplayAttack, std::nullopt, std::nullopt}; + } else if (!dont_write_db) { + sessionData.indices[res.message_index] = event.event_id; + cache::client()->saveInboundMegolmSession(index, std::move(session), sessionData); + } } + } catch (const lmdb::error &e) { + return {DecryptionErrorCode::DbError, e.what(), std::nullopt}; + } catch (const mtx::crypto::olm_exception &e) { + if (e.error_code() == mtx::crypto::OlmErrorCode::UNKNOWN_MESSAGE_INDEX) + return {DecryptionErrorCode::MissingSessionIndex, e.what(), std::nullopt}; + return {DecryptionErrorCode::DecryptionFailed, e.what(), std::nullopt}; + } - try { - // Add missing fields for the event. - json body = json::parse(msg_str); - body["event_id"] = event.event_id; - body["sender"] = event.sender; - body["origin_server_ts"] = event.origin_server_ts; - body["unsigned"] = event.unsigned_data; + try { + // Add missing fields for the event. + json body = json::parse(msg_str); + body["event_id"] = event.event_id; + body["sender"] = event.sender; + body["origin_server_ts"] = event.origin_server_ts; + body["unsigned"] = event.unsigned_data; - // relations are unencrypted in content... - mtx::common::add_relations(body["content"], event.content.relations); + // relations are unencrypted in content... + mtx::common::add_relations(body["content"], event.content.relations); - mtx::events::collections::TimelineEvent te; - mtx::events::collections::from_json(body, te); + mtx::events::collections::TimelineEvent te; + mtx::events::collections::from_json(body, te); - return {DecryptionErrorCode::NoError, std::nullopt, std::move(te.data)}; - } catch (std::exception &e) { - return {DecryptionErrorCode::ParsingFailed, e.what(), std::nullopt}; - } + return {DecryptionErrorCode::NoError, std::nullopt, std::move(te.data)}; + } catch (std::exception &e) { + return {DecryptionErrorCode::ParsingFailed, e.what(), std::nullopt}; + } } crypto::Trust calculate_trust(const std::string &user_id, const MegolmSessionIndex &index) { - auto status = cache::client()->verificationStatus(user_id); - auto megolmData = cache::client()->getMegolmSessionData(index); - crypto::Trust trustlevel = crypto::Trust::Unverified; + auto status = cache::client()->verificationStatus(user_id); + auto megolmData = cache::client()->getMegolmSessionData(index); + crypto::Trust trustlevel = crypto::Trust::Unverified; - if (megolmData && megolmData->trusted && - status.verified_device_keys.count(index.sender_key)) - trustlevel = status.verified_device_keys.at(index.sender_key); + if (megolmData && megolmData->trusted && status.verified_device_keys.count(index.sender_key)) + trustlevel = status.verified_device_keys.at(index.sender_key); - return trustlevel; + return trustlevel; } //! Send encrypted to device messages, targets is a map from userid to device ids or {} for all @@ -1292,397 +1193,352 @@ send_encrypted_to_device_messages(const std::map, qint64> rateLimit; + static QMap, qint64> rateLimit; - nlohmann::json ev_json = std::visit([](const auto &e) { return json(e); }, event); + nlohmann::json ev_json = std::visit([](const auto &e) { return json(e); }, event); - std::map> keysToQuery; - mtx::requests::ClaimKeys claims; - std::map> - messages; - std::map> pks; + std::map> keysToQuery; + mtx::requests::ClaimKeys claims; + std::map> + messages; + std::map> pks; - auto our_curve = olm::client()->identity_keys().curve25519; + auto our_curve = olm::client()->identity_keys().curve25519; - for (const auto &[user, devices] : targets) { - auto deviceKeys = cache::client()->userKeys(user); + for (const auto &[user, devices] : targets) { + auto deviceKeys = cache::client()->userKeys(user); - // no keys for user, query them - if (!deviceKeys) { - keysToQuery[user] = devices; - continue; - } - - auto deviceTargets = devices; - if (devices.empty()) { - deviceTargets.clear(); - for (const auto &[device, keys] : deviceKeys->device_keys) { - (void)keys; - deviceTargets.push_back(device); - } - } - - for (const auto &device : deviceTargets) { - if (!deviceKeys->device_keys.count(device)) { - keysToQuery[user] = {}; - break; - } - - auto d = deviceKeys->device_keys.at(device); - - if (!d.keys.count("curve25519:" + device) || - !d.keys.count("ed25519:" + device)) { - nhlog::crypto()->warn("Skipping device {} since it has no keys!", - device); - continue; - } - - auto device_curve = d.keys.at("curve25519:" + device); - if (device_curve == our_curve) { - nhlog::crypto()->warn("Skipping our own device, since sending " - "ourselves olm messages makes no sense."); - continue; - } - - auto session = cache::getLatestOlmSession(device_curve); - if (!session || force_new_session) { - auto currentTime = QDateTime::currentSecsSinceEpoch(); - if (rateLimit.value(QPair(user, device)) + 60 * 60 * 10 < - currentTime) { - claims.one_time_keys[user][device] = - mtx::crypto::SIGNED_CURVE25519; - pks[user][device].ed25519 = d.keys.at("ed25519:" + device); - pks[user][device].curve25519 = - d.keys.at("curve25519:" + device); - - rateLimit.insert(QPair(user, device), currentTime); - } else { - nhlog::crypto()->warn("Not creating new session with {}:{} " - "because of rate limit", - user, - device); - } - continue; - } - - messages[mtx::identifiers::parse(user)][device] = - olm::client() - ->create_olm_encrypted_content(session->get(), - ev_json, - UserId(user), - d.keys.at("ed25519:" + device), - device_curve) - .get(); - - try { - nhlog::crypto()->debug("Updated olm session: {}", - mtx::crypto::session_id(session->get())); - cache::saveOlmSession(d.keys.at("curve25519:" + device), - std::move(*session), - QDateTime::currentMSecsSinceEpoch()); - } catch (const lmdb::error &e) { - nhlog::db()->critical("failed to save outbound olm session: {}", - e.what()); - } catch (const mtx::crypto::olm_exception &e) { - nhlog::crypto()->critical( - "failed to pickle outbound olm session: {}", e.what()); - } - } + // no keys for user, query them + if (!deviceKeys) { + keysToQuery[user] = devices; + continue; } - if (!messages.empty()) + auto deviceTargets = devices; + if (devices.empty()) { + deviceTargets.clear(); + for (const auto &[device, keys] : deviceKeys->device_keys) { + (void)keys; + deviceTargets.push_back(device); + } + } + + for (const auto &device : deviceTargets) { + if (!deviceKeys->device_keys.count(device)) { + keysToQuery[user] = {}; + break; + } + + auto d = deviceKeys->device_keys.at(device); + + if (!d.keys.count("curve25519:" + device) || !d.keys.count("ed25519:" + device)) { + nhlog::crypto()->warn("Skipping device {} since it has no keys!", device); + continue; + } + + auto device_curve = d.keys.at("curve25519:" + device); + if (device_curve == our_curve) { + nhlog::crypto()->warn("Skipping our own device, since sending " + "ourselves olm messages makes no sense."); + continue; + } + + auto session = cache::getLatestOlmSession(device_curve); + if (!session || force_new_session) { + auto currentTime = QDateTime::currentSecsSinceEpoch(); + if (rateLimit.value(QPair(user, device)) + 60 * 60 * 10 < currentTime) { + claims.one_time_keys[user][device] = mtx::crypto::SIGNED_CURVE25519; + pks[user][device].ed25519 = d.keys.at("ed25519:" + device); + pks[user][device].curve25519 = d.keys.at("curve25519:" + device); + + rateLimit.insert(QPair(user, device), currentTime); + } else { + nhlog::crypto()->warn("Not creating new session with {}:{} " + "because of rate limit", + user, + device); + } + continue; + } + + messages[mtx::identifiers::parse(user)][device] = + olm::client() + ->create_olm_encrypted_content(session->get(), + ev_json, + UserId(user), + d.keys.at("ed25519:" + device), + device_curve) + .get(); + + try { + nhlog::crypto()->debug("Updated olm session: {}", + mtx::crypto::session_id(session->get())); + cache::saveOlmSession(d.keys.at("curve25519:" + device), + std::move(*session), + QDateTime::currentMSecsSinceEpoch()); + } catch (const lmdb::error &e) { + nhlog::db()->critical("failed to save outbound olm session: {}", e.what()); + } catch (const mtx::crypto::olm_exception &e) { + nhlog::crypto()->critical("failed to pickle outbound olm session: {}", e.what()); + } + } + } + + if (!messages.empty()) + http::client()->send_to_device( + http::client()->generate_txn_id(), messages, [](mtx::http::RequestErr err) { + if (err) { + nhlog::net()->warn("failed to send " + "send_to_device " + "message: {}", + err->matrix_error.error); + } + }); + + auto BindPks = [ev_json](decltype(pks) pks_temp) { + return [pks = pks_temp, ev_json](const mtx::responses::ClaimKeys &res, + mtx::http::RequestErr) { + std::map> + messages; + for (const auto &[user_id, retrieved_devices] : res.one_time_keys) { + nhlog::net()->debug("claimed keys for {}", user_id); + if (retrieved_devices.size() == 0) { + nhlog::net()->debug("no one-time keys found for user_id: {}", user_id); + continue; + } + + for (const auto &rd : retrieved_devices) { + const auto device_id = rd.first; + + nhlog::net()->debug("{} : \n {}", device_id, rd.second.dump(2)); + + if (rd.second.empty() || !rd.second.begin()->contains("key")) { + nhlog::net()->warn("Skipping device {} as it has no key.", device_id); + continue; + } + + auto otk = rd.second.begin()->at("key"); + + auto sign_key = pks.at(user_id).at(device_id).ed25519; + auto id_key = pks.at(user_id).at(device_id).curve25519; + + // Verify signature + { + auto signedKey = *rd.second.begin(); + std::string signature = + signedKey["signatures"][user_id].value("ed25519:" + device_id, ""); + + if (signature.empty() || !mtx::crypto::ed25519_verify_signature( + sign_key, signedKey, signature)) { + nhlog::net()->warn("Skipping device {} as its one time key " + "has an invalid signature.", + device_id); + continue; + } + } + + auto session = olm::client()->create_outbound_session(id_key, otk); + + messages[mtx::identifiers::parse(user_id)][device_id] = + olm::client() + ->create_olm_encrypted_content( + session.get(), ev_json, UserId(user_id), sign_key, id_key) + .get(); + + try { + nhlog::crypto()->debug("Updated olm session: {}", + mtx::crypto::session_id(session.get())); + cache::saveOlmSession( + id_key, std::move(session), QDateTime::currentMSecsSinceEpoch()); + } catch (const lmdb::error &e) { + nhlog::db()->critical("failed to save outbound olm session: {}", e.what()); + } catch (const mtx::crypto::olm_exception &e) { + nhlog::crypto()->critical("failed to pickle outbound olm session: {}", + e.what()); + } + } + nhlog::net()->info("send_to_device: {}", user_id); + } + + if (!messages.empty()) http::client()->send_to_device( http::client()->generate_txn_id(), messages, [](mtx::http::RequestErr err) { - if (err) { - nhlog::net()->warn("failed to send " - "send_to_device " - "message: {}", - err->matrix_error.error); - } + if (err) { + nhlog::net()->warn("failed to send " + "send_to_device " + "message: {}", + err->matrix_error.error); + } }); - - auto BindPks = [ev_json](decltype(pks) pks_temp) { - return [pks = pks_temp, ev_json](const mtx::responses::ClaimKeys &res, - mtx::http::RequestErr) { - std::map> - messages; - for (const auto &[user_id, retrieved_devices] : res.one_time_keys) { - nhlog::net()->debug("claimed keys for {}", user_id); - if (retrieved_devices.size() == 0) { - nhlog::net()->debug( - "no one-time keys found for user_id: {}", user_id); - continue; - } - - for (const auto &rd : retrieved_devices) { - const auto device_id = rd.first; - - nhlog::net()->debug( - "{} : \n {}", device_id, rd.second.dump(2)); - - if (rd.second.empty() || - !rd.second.begin()->contains("key")) { - nhlog::net()->warn( - "Skipping device {} as it has no key.", - device_id); - continue; - } - - auto otk = rd.second.begin()->at("key"); - - auto sign_key = pks.at(user_id).at(device_id).ed25519; - auto id_key = pks.at(user_id).at(device_id).curve25519; - - // Verify signature - { - auto signedKey = *rd.second.begin(); - std::string signature = - signedKey["signatures"][user_id].value( - "ed25519:" + device_id, ""); - - if (signature.empty() || - !mtx::crypto::ed25519_verify_signature( - sign_key, signedKey, signature)) { - nhlog::net()->warn( - "Skipping device {} as its one time key " - "has an invalid signature.", - device_id); - continue; - } - } - - auto session = - olm::client()->create_outbound_session(id_key, otk); - - messages[mtx::identifiers::parse( - user_id)][device_id] = - olm::client() - ->create_olm_encrypted_content(session.get(), - ev_json, - UserId(user_id), - sign_key, - id_key) - .get(); - - try { - nhlog::crypto()->debug( - "Updated olm session: {}", - mtx::crypto::session_id(session.get())); - cache::saveOlmSession( - id_key, - std::move(session), - QDateTime::currentMSecsSinceEpoch()); - } catch (const lmdb::error &e) { - nhlog::db()->critical( - "failed to save outbound olm session: {}", - e.what()); - } catch (const mtx::crypto::olm_exception &e) { - nhlog::crypto()->critical( - "failed to pickle outbound olm session: {}", - e.what()); - } - } - nhlog::net()->info("send_to_device: {}", user_id); - } - - if (!messages.empty()) - http::client()->send_to_device( - http::client()->generate_txn_id(), - messages, - [](mtx::http::RequestErr err) { - if (err) { - nhlog::net()->warn("failed to send " - "send_to_device " - "message: {}", - err->matrix_error.error); - } - }); - }; }; + }; - if (!claims.one_time_keys.empty()) - http::client()->claim_keys(claims, BindPks(pks)); + if (!claims.one_time_keys.empty()) + http::client()->claim_keys(claims, BindPks(pks)); - if (!keysToQuery.empty()) { - mtx::requests::QueryKeys req; - req.device_keys = keysToQuery; - http::client()->query_keys( - req, - [ev_json, BindPks, our_curve](const mtx::responses::QueryKeys &res, - mtx::http::RequestErr err) { - if (err) { - nhlog::net()->warn("failed to query device keys: {} {}", - err->matrix_error.error, - static_cast(err->status_code)); - return; + if (!keysToQuery.empty()) { + mtx::requests::QueryKeys req; + req.device_keys = keysToQuery; + http::client()->query_keys( + req, + [ev_json, BindPks, our_curve](const mtx::responses::QueryKeys &res, + mtx::http::RequestErr err) { + if (err) { + nhlog::net()->warn("failed to query device keys: {} {}", + err->matrix_error.error, + static_cast(err->status_code)); + return; + } + + nhlog::net()->info("queried keys"); + + cache::client()->updateUserKeys(cache::nextBatchToken(), res); + + mtx::requests::ClaimKeys claim_keys; + + std::map> deviceKeys; + + for (const auto &user : res.device_keys) { + for (const auto &dev : user.second) { + const auto user_id = ::UserId(dev.second.user_id); + const auto device_id = DeviceId(dev.second.device_id); + + if (user_id.get() == http::client()->user_id().to_string() && + device_id.get() == http::client()->device_id()) + continue; + + const auto device_keys = dev.second.keys; + const auto curveKey = "curve25519:" + device_id.get(); + const auto edKey = "ed25519:" + device_id.get(); + + if ((device_keys.find(curveKey) == device_keys.end()) || + (device_keys.find(edKey) == device_keys.end())) { + nhlog::net()->debug("ignoring malformed keys for device {}", + device_id.get()); + continue; + } + + DevicePublicKeys pks; + pks.ed25519 = device_keys.at(edKey); + pks.curve25519 = device_keys.at(curveKey); + + if (pks.curve25519 == our_curve) { + nhlog::crypto()->warn("Skipping our own device, since sending " + "ourselves olm messages makes no sense."); + continue; + } + + try { + if (!mtx::crypto::verify_identity_signature( + dev.second, device_id, user_id)) { + nhlog::crypto()->warn("failed to verify identity keys: {}", + json(dev.second).dump(2)); + continue; } + } catch (const json::exception &e) { + nhlog::crypto()->warn("failed to parse device key json: {}", e.what()); + continue; + } catch (const mtx::crypto::olm_exception &e) { + nhlog::crypto()->warn("failed to verify device key json: {}", e.what()); + continue; + } - nhlog::net()->info("queried keys"); + auto currentTime = QDateTime::currentSecsSinceEpoch(); + if (rateLimit.value(QPair(user.first, device_id.get())) + 60 * 60 * 10 < + currentTime) { + deviceKeys[user_id].emplace(device_id, pks); + claim_keys.one_time_keys[user.first][device_id] = + mtx::crypto::SIGNED_CURVE25519; - cache::client()->updateUserKeys(cache::nextBatchToken(), res); + rateLimit.insert(QPair(user.first, device_id.get()), currentTime); + } else { + nhlog::crypto()->warn("Not creating new session with {}:{} " + "because of rate limit", + user.first, + device_id.get()); + continue; + } - mtx::requests::ClaimKeys claim_keys; + nhlog::net()->info("{}", device_id.get()); + nhlog::net()->info(" curve25519 {}", pks.curve25519); + nhlog::net()->info(" ed25519 {}", pks.ed25519); + } + } - std::map> deviceKeys; - - for (const auto &user : res.device_keys) { - for (const auto &dev : user.second) { - const auto user_id = ::UserId(dev.second.user_id); - const auto device_id = DeviceId(dev.second.device_id); - - if (user_id.get() == - http::client()->user_id().to_string() && - device_id.get() == http::client()->device_id()) - continue; - - const auto device_keys = dev.second.keys; - const auto curveKey = "curve25519:" + device_id.get(); - const auto edKey = "ed25519:" + device_id.get(); - - if ((device_keys.find(curveKey) == device_keys.end()) || - (device_keys.find(edKey) == device_keys.end())) { - nhlog::net()->debug( - "ignoring malformed keys for device {}", - device_id.get()); - continue; - } - - DevicePublicKeys pks; - pks.ed25519 = device_keys.at(edKey); - pks.curve25519 = device_keys.at(curveKey); - - if (pks.curve25519 == our_curve) { - nhlog::crypto()->warn( - "Skipping our own device, since sending " - "ourselves olm messages makes no sense."); - continue; - } - - try { - if (!mtx::crypto::verify_identity_signature( - dev.second, device_id, user_id)) { - nhlog::crypto()->warn( - "failed to verify identity keys: {}", - json(dev.second).dump(2)); - continue; - } - } catch (const json::exception &e) { - nhlog::crypto()->warn( - "failed to parse device key json: {}", - e.what()); - continue; - } catch (const mtx::crypto::olm_exception &e) { - nhlog::crypto()->warn( - "failed to verify device key json: {}", - e.what()); - continue; - } - - auto currentTime = QDateTime::currentSecsSinceEpoch(); - if (rateLimit.value(QPair(user.first, device_id.get())) + - 60 * 60 * 10 < - currentTime) { - deviceKeys[user_id].emplace(device_id, pks); - claim_keys.one_time_keys[user.first][device_id] = - mtx::crypto::SIGNED_CURVE25519; - - rateLimit.insert( - QPair(user.first, device_id.get()), - currentTime); - } else { - nhlog::crypto()->warn( - "Not creating new session with {}:{} " - "because of rate limit", - user.first, - device_id.get()); - continue; - } - - nhlog::net()->info("{}", device_id.get()); - nhlog::net()->info(" curve25519 {}", pks.curve25519); - nhlog::net()->info(" ed25519 {}", pks.ed25519); - } - } - - if (!claim_keys.one_time_keys.empty()) - http::client()->claim_keys(claim_keys, BindPks(deviceKeys)); - }); - } + if (!claim_keys.one_time_keys.empty()) + http::client()->claim_keys(claim_keys, BindPks(deviceKeys)); + }); + } } void request_cross_signing_keys() { - mtx::events::msg::SecretRequest secretRequest{}; - secretRequest.action = mtx::events::msg::RequestAction::Request; - secretRequest.requesting_device_id = http::client()->device_id(); + mtx::events::msg::SecretRequest secretRequest{}; + secretRequest.action = mtx::events::msg::RequestAction::Request; + secretRequest.requesting_device_id = http::client()->device_id(); - auto local_user = http::client()->user_id(); + auto local_user = http::client()->user_id(); - auto verificationStatus = cache::verificationStatus(local_user.to_string()); + auto verificationStatus = cache::verificationStatus(local_user.to_string()); - if (!verificationStatus) - return; + if (!verificationStatus) + return; - auto request = [&](std::string secretName) { - secretRequest.name = secretName; - secretRequest.request_id = "ss." + http::client()->generate_txn_id(); + auto request = [&](std::string secretName) { + secretRequest.name = secretName; + secretRequest.request_id = "ss." + http::client()->generate_txn_id(); - request_id_to_secret_name[secretRequest.request_id] = secretRequest.name; + request_id_to_secret_name[secretRequest.request_id] = secretRequest.name; - std::map> - body; + std::map> + body; - for (const auto &dev : verificationStatus->verified_devices) { - if (dev != secretRequest.requesting_device_id) - body[local_user][dev] = secretRequest; - } + for (const auto &dev : verificationStatus->verified_devices) { + if (dev != secretRequest.requesting_device_id) + body[local_user][dev] = secretRequest; + } + http::client()->send_to_device( + http::client()->generate_txn_id(), + body, + [request_id = secretRequest.request_id, secretName](mtx::http::RequestErr err) { + if (err) { + nhlog::net()->error("Failed to send request for secrect '{}'", secretName); + // Cancel request on UI thread + QTimer::singleShot(1, cache::client(), [request_id]() { + request_id_to_secret_name.erase(request_id); + }); + return; + } + }); + + for (const auto &dev : verificationStatus->verified_devices) { + if (dev != secretRequest.requesting_device_id) + body[local_user][dev].action = mtx::events::msg::RequestAction::Cancellation; + } + + // timeout after 15 min + QTimer::singleShot(15 * 60 * 1000, [secretRequest, body]() { + if (request_id_to_secret_name.count(secretRequest.request_id)) { + request_id_to_secret_name.erase(secretRequest.request_id); http::client()->send_to_device( http::client()->generate_txn_id(), body, - [request_id = secretRequest.request_id, secretName](mtx::http::RequestErr err) { - if (err) { - nhlog::net()->error("Failed to send request for secrect '{}'", - secretName); - // Cancel request on UI thread - QTimer::singleShot(1, cache::client(), [request_id]() { - request_id_to_secret_name.erase(request_id); - }); - return; - } + [secretRequest](mtx::http::RequestErr err) { + if (err) { + nhlog::net()->error("Failed to cancel request for secrect '{}'", + secretRequest.name); + return; + } }); + } + }); + }; - for (const auto &dev : verificationStatus->verified_devices) { - if (dev != secretRequest.requesting_device_id) - body[local_user][dev].action = - mtx::events::msg::RequestAction::Cancellation; - } - - // timeout after 15 min - QTimer::singleShot(15 * 60 * 1000, [secretRequest, body]() { - if (request_id_to_secret_name.count(secretRequest.request_id)) { - request_id_to_secret_name.erase(secretRequest.request_id); - http::client()->send_to_device( - http::client()->generate_txn_id(), - body, - [secretRequest](mtx::http::RequestErr err) { - if (err) { - nhlog::net()->error( - "Failed to cancel request for secrect '{}'", - secretRequest.name); - return; - } - }); - } - }); - }; - - request(mtx::secret_storage::secrets::cross_signing_self_signing); - request(mtx::secret_storage::secrets::cross_signing_user_signing); - request(mtx::secret_storage::secrets::megolm_backup_v1); + request(mtx::secret_storage::secrets::cross_signing_self_signing); + request(mtx::secret_storage::secrets::cross_signing_user_signing); + request(mtx::secret_storage::secrets::megolm_backup_v1); } namespace { @@ -1690,67 +1546,63 @@ void unlock_secrets(const std::string &key, const std::map &secrets) { - http::client()->secret_storage_key( - key, - [secrets](mtx::secret_storage::AesHmacSha2KeyDescription keyDesc, - mtx::http::RequestErr err) { - if (err) { - nhlog::net()->error("Failed to download secret storage key"); - return; - } + http::client()->secret_storage_key( + key, + [secrets](mtx::secret_storage::AesHmacSha2KeyDescription keyDesc, mtx::http::RequestErr err) { + if (err) { + nhlog::net()->error("Failed to download secret storage key"); + return; + } - emit ChatPage::instance()->downloadedSecrets(keyDesc, secrets); - }); + emit ChatPage::instance()->downloadedSecrets(keyDesc, secrets); + }); } } void download_cross_signing_keys() { - using namespace mtx::secret_storage; - http::client()->secret_storage_secret( - secrets::megolm_backup_v1, [](Secret secret, mtx::http::RequestErr err) { - std::optional backup_key; - if (!err) - backup_key = secret; + using namespace mtx::secret_storage; + http::client()->secret_storage_secret( + secrets::megolm_backup_v1, [](Secret secret, mtx::http::RequestErr err) { + std::optional backup_key; + if (!err) + backup_key = secret; - http::client()->secret_storage_secret( - secrets::cross_signing_self_signing, - [backup_key](Secret secret, mtx::http::RequestErr err) { - std::optional self_signing_key; - if (!err) - self_signing_key = secret; + http::client()->secret_storage_secret( + secrets::cross_signing_self_signing, + [backup_key](Secret secret, mtx::http::RequestErr err) { + std::optional self_signing_key; + if (!err) + self_signing_key = secret; - http::client()->secret_storage_secret( - secrets::cross_signing_user_signing, - [backup_key, self_signing_key](Secret secret, - mtx::http::RequestErr err) { - std::optional user_signing_key; - if (!err) - user_signing_key = secret; + http::client()->secret_storage_secret( + secrets::cross_signing_user_signing, + [backup_key, self_signing_key](Secret secret, mtx::http::RequestErr err) { + std::optional user_signing_key; + if (!err) + user_signing_key = secret; - std::map> - secrets; + std::map> + secrets; - if (backup_key && !backup_key->encrypted.empty()) - secrets[backup_key->encrypted.begin()->first] - [secrets::megolm_backup_v1] = - backup_key->encrypted.begin()->second; - if (self_signing_key && !self_signing_key->encrypted.empty()) - secrets[self_signing_key->encrypted.begin()->first] - [secrets::cross_signing_self_signing] = - self_signing_key->encrypted.begin()->second; - if (user_signing_key && !user_signing_key->encrypted.empty()) - secrets[user_signing_key->encrypted.begin()->first] - [secrets::cross_signing_user_signing] = - user_signing_key->encrypted.begin()->second; + if (backup_key && !backup_key->encrypted.empty()) + secrets[backup_key->encrypted.begin()->first][secrets::megolm_backup_v1] = + backup_key->encrypted.begin()->second; + if (self_signing_key && !self_signing_key->encrypted.empty()) + secrets[self_signing_key->encrypted.begin()->first] + [secrets::cross_signing_self_signing] = + self_signing_key->encrypted.begin()->second; + if (user_signing_key && !user_signing_key->encrypted.empty()) + secrets[user_signing_key->encrypted.begin()->first] + [secrets::cross_signing_user_signing] = + user_signing_key->encrypted.begin()->second; - for (const auto &[key, secrets] : secrets) - unlock_secrets(key, secrets); - }); - }); - }); + for (const auto &[key, secrets] : secrets) + unlock_secrets(key, secrets); + }); + }); + }); } } // namespace olm diff --git a/src/Olm.h b/src/Olm.h index eb60ae3a..44e2b8ed 100644 --- a/src/Olm.h +++ b/src/Olm.h @@ -18,32 +18,32 @@ Q_NAMESPACE enum DecryptionErrorCode { - NoError, - MissingSession, // Session was not found, retrieve from backup or request from other devices - // and try again - MissingSessionIndex, // Session was found, but it does not reach back enough to this index, - // retrieve from backup or request from other devices and try again - DbError, // DB read failed - DecryptionFailed, // libolm error - ParsingFailed, // Failed to parse the actual event - ReplayAttack, // Megolm index reused + NoError, + MissingSession, // Session was not found, retrieve from backup or request from other devices + // and try again + MissingSessionIndex, // Session was found, but it does not reach back enough to this index, + // retrieve from backup or request from other devices and try again + DbError, // DB read failed + DecryptionFailed, // libolm error + ParsingFailed, // Failed to parse the actual event + ReplayAttack, // Megolm index reused }; Q_ENUM_NS(DecryptionErrorCode) struct DecryptionResult { - DecryptionErrorCode error; - std::optional error_message; - std::optional event; + DecryptionErrorCode error; + std::optional error_message; + std::optional event; }; struct OlmMessage { - std::string sender_key; - std::string sender; + std::string sender_key; + std::string sender; - using RecipientKey = std::string; - std::map ciphertext; + using RecipientKey = std::string; + std::map ciphertext; }; void diff --git a/src/ReadReceiptsModel.cpp b/src/ReadReceiptsModel.cpp index 25262c59..ff93f7d8 100644 --- a/src/ReadReceiptsModel.cpp +++ b/src/ReadReceiptsModel.cpp @@ -16,116 +16,115 @@ ReadReceiptsModel::ReadReceiptsModel(QString event_id, QString room_id, QObject , event_id_{event_id} , room_id_{room_id} { - try { - addUsers(cache::readReceipts(event_id_, room_id_)); - } catch (const lmdb::error &) { - nhlog::db()->warn("failed to retrieve read receipts for {} {}", - event_id_.toStdString(), - room_id_.toStdString()); + try { + addUsers(cache::readReceipts(event_id_, room_id_)); + } catch (const lmdb::error &) { + nhlog::db()->warn("failed to retrieve read receipts for {} {}", + event_id_.toStdString(), + room_id_.toStdString()); - return; - } + return; + } - connect(cache::client(), &Cache::newReadReceipts, this, &ReadReceiptsModel::update); + connect(cache::client(), &Cache::newReadReceipts, this, &ReadReceiptsModel::update); } void ReadReceiptsModel::update() { - try { - addUsers(cache::readReceipts(event_id_, room_id_)); - } catch (const lmdb::error &) { - nhlog::db()->warn("failed to retrieve read receipts for {} {}", - event_id_.toStdString(), - room_id_.toStdString()); + try { + addUsers(cache::readReceipts(event_id_, room_id_)); + } catch (const lmdb::error &) { + nhlog::db()->warn("failed to retrieve read receipts for {} {}", + event_id_.toStdString(), + room_id_.toStdString()); - return; - } + return; + } } QHash ReadReceiptsModel::roleNames() const { - // Note: RawTimestamp is purposely not included here - return { - {Mxid, "mxid"}, - {DisplayName, "displayName"}, - {AvatarUrl, "avatarUrl"}, - {Timestamp, "timestamp"}, - }; + // Note: RawTimestamp is purposely not included here + return { + {Mxid, "mxid"}, + {DisplayName, "displayName"}, + {AvatarUrl, "avatarUrl"}, + {Timestamp, "timestamp"}, + }; } QVariant ReadReceiptsModel::data(const QModelIndex &index, int role) const { - if (!index.isValid() || index.row() >= (int)readReceipts_.size() || index.row() < 0) - return {}; + if (!index.isValid() || index.row() >= (int)readReceipts_.size() || index.row() < 0) + return {}; - switch (role) { - case Mxid: - return readReceipts_[index.row()].first; - case DisplayName: - return cache::displayName(room_id_, readReceipts_[index.row()].first); - case AvatarUrl: - return cache::avatarUrl(room_id_, readReceipts_[index.row()].first); - case Timestamp: - return dateFormat(readReceipts_[index.row()].second); - case RawTimestamp: - return readReceipts_[index.row()].second; - default: - return {}; - } + switch (role) { + case Mxid: + return readReceipts_[index.row()].first; + case DisplayName: + return cache::displayName(room_id_, readReceipts_[index.row()].first); + case AvatarUrl: + return cache::avatarUrl(room_id_, readReceipts_[index.row()].first); + case Timestamp: + return dateFormat(readReceipts_[index.row()].second); + case RawTimestamp: + return readReceipts_[index.row()].second; + default: + return {}; + } } void ReadReceiptsModel::addUsers( const std::multimap> &users) { - auto newReceipts = users.size() - readReceipts_.size(); + auto newReceipts = users.size() - readReceipts_.size(); - if (newReceipts > 0) { - beginInsertRows( - QModelIndex{}, readReceipts_.size(), readReceipts_.size() + newReceipts - 1); + if (newReceipts > 0) { + beginInsertRows( + QModelIndex{}, readReceipts_.size(), readReceipts_.size() + newReceipts - 1); - for (const auto &user : users) { - QPair item = { - QString::fromStdString(user.second), - QDateTime::fromMSecsSinceEpoch(user.first)}; - if (!readReceipts_.contains(item)) - readReceipts_.push_back(item); - } - - endInsertRows(); + for (const auto &user : users) { + QPair item = {QString::fromStdString(user.second), + QDateTime::fromMSecsSinceEpoch(user.first)}; + if (!readReceipts_.contains(item)) + readReceipts_.push_back(item); } + + endInsertRows(); + } } QString ReadReceiptsModel::dateFormat(const QDateTime &then) const { - auto now = QDateTime::currentDateTime(); - auto days = then.daysTo(now); - - if (days == 0) - return QLocale::system().toString(then.time(), QLocale::ShortFormat); - else if (days < 2) - return tr("Yesterday, %1") - .arg(QLocale::system().toString(then.time(), QLocale::ShortFormat)); - else if (days < 7) - //: %1 is the name of the current day, %2 is the time the read receipt was read. The - //: result may look like this: Monday, 7:15 - return QString("%1, %2") - .arg(then.toString("dddd")) - .arg(QLocale::system().toString(then.time(), QLocale::ShortFormat)); + auto now = QDateTime::currentDateTime(); + auto days = then.daysTo(now); + if (days == 0) return QLocale::system().toString(then.time(), QLocale::ShortFormat); + else if (days < 2) + return tr("Yesterday, %1") + .arg(QLocale::system().toString(then.time(), QLocale::ShortFormat)); + else if (days < 7) + //: %1 is the name of the current day, %2 is the time the read receipt was read. The + //: result may look like this: Monday, 7:15 + return QString("%1, %2") + .arg(then.toString("dddd")) + .arg(QLocale::system().toString(then.time(), QLocale::ShortFormat)); + + return QLocale::system().toString(then.time(), QLocale::ShortFormat); } ReadReceiptsProxy::ReadReceiptsProxy(QString event_id, QString room_id, QObject *parent) : QSortFilterProxyModel{parent} , model_{event_id, room_id, this} { - setSourceModel(&model_); - setSortRole(ReadReceiptsModel::RawTimestamp); - sort(0, Qt::DescendingOrder); - setDynamicSortFilter(true); + setSourceModel(&model_); + setSortRole(ReadReceiptsModel::RawTimestamp); + sort(0, Qt::DescendingOrder); + setDynamicSortFilter(true); } diff --git a/src/ReadReceiptsModel.h b/src/ReadReceiptsModel.h index 3b45716c..7487fff8 100644 --- a/src/ReadReceiptsModel.h +++ b/src/ReadReceiptsModel.h @@ -13,61 +13,61 @@ class ReadReceiptsModel : public QAbstractListModel { - Q_OBJECT + Q_OBJECT public: - enum Roles - { - Mxid, - DisplayName, - AvatarUrl, - Timestamp, - RawTimestamp, - }; + enum Roles + { + Mxid, + DisplayName, + AvatarUrl, + Timestamp, + RawTimestamp, + }; - explicit ReadReceiptsModel(QString event_id, QString room_id, QObject *parent = nullptr); + explicit ReadReceiptsModel(QString event_id, QString room_id, QObject *parent = nullptr); - QString eventId() const { return event_id_; } - QString roomId() const { return room_id_; } + QString eventId() const { return event_id_; } + QString roomId() const { return room_id_; } - QHash roleNames() const override; - int rowCount(const QModelIndex &parent) const override - { - Q_UNUSED(parent) - return readReceipts_.size(); - } - QVariant data(const QModelIndex &index, int role) const override; + QHash roleNames() const override; + int rowCount(const QModelIndex &parent) const override + { + Q_UNUSED(parent) + return readReceipts_.size(); + } + QVariant data(const QModelIndex &index, int role) const override; public slots: - void addUsers(const std::multimap> &users); - void update(); + void addUsers(const std::multimap> &users); + void update(); private: - QString dateFormat(const QDateTime &then) const; + QString dateFormat(const QDateTime &then) const; - QString event_id_; - QString room_id_; - QVector> readReceipts_; + QString event_id_; + QString room_id_; + QVector> readReceipts_; }; class ReadReceiptsProxy : public QSortFilterProxyModel { - Q_OBJECT + Q_OBJECT - Q_PROPERTY(QString eventId READ eventId CONSTANT) - Q_PROPERTY(QString roomId READ roomId CONSTANT) + Q_PROPERTY(QString eventId READ eventId CONSTANT) + Q_PROPERTY(QString roomId READ roomId CONSTANT) public: - explicit ReadReceiptsProxy(QString event_id, QString room_id, QObject *parent = nullptr); + explicit ReadReceiptsProxy(QString event_id, QString room_id, QObject *parent = nullptr); - QString eventId() const { return event_id_; } - QString roomId() const { return room_id_; } + QString eventId() const { return event_id_; } + QString roomId() const { return room_id_; } private: - QString event_id_; - QString room_id_; + QString event_id_; + QString room_id_; - ReadReceiptsModel model_; + ReadReceiptsModel model_; }; #endif // READRECEIPTSMODEL_H diff --git a/src/RegisterPage.cpp b/src/RegisterPage.cpp index fb6a1b97..0204a307 100644 --- a/src/RegisterPage.cpp +++ b/src/RegisterPage.cpp @@ -33,496 +33,483 @@ Q_DECLARE_METATYPE(mtx::user_interactive::Auth) RegisterPage::RegisterPage(QWidget *parent) : QWidget(parent) { - qRegisterMetaType(); - qRegisterMetaType(); - top_layout_ = new QVBoxLayout(); + qRegisterMetaType(); + qRegisterMetaType(); + top_layout_ = new QVBoxLayout(); - back_layout_ = new QHBoxLayout(); - back_layout_->setSpacing(0); - back_layout_->setContentsMargins(5, 5, -1, -1); + back_layout_ = new QHBoxLayout(); + back_layout_->setSpacing(0); + back_layout_->setContentsMargins(5, 5, -1, -1); - back_button_ = new FlatButton(this); - back_button_->setMinimumSize(QSize(30, 30)); + back_button_ = new FlatButton(this); + back_button_->setMinimumSize(QSize(30, 30)); - QIcon icon; - icon.addFile(":/icons/icons/ui/angle-pointing-to-left.png"); + QIcon icon; + icon.addFile(":/icons/icons/ui/angle-pointing-to-left.png"); - back_button_->setIcon(icon); - back_button_->setIconSize(QSize(32, 32)); + back_button_->setIcon(icon); + back_button_->setIconSize(QSize(32, 32)); - back_layout_->addWidget(back_button_, 0, Qt::AlignLeft | Qt::AlignVCenter); - back_layout_->addStretch(1); + back_layout_->addWidget(back_button_, 0, Qt::AlignLeft | Qt::AlignVCenter); + back_layout_->addStretch(1); - QIcon logo; - logo.addFile(":/logos/register.png"); + QIcon logo; + logo.addFile(":/logos/register.png"); - logo_ = new QLabel(this); - logo_->setPixmap(logo.pixmap(128)); + logo_ = new QLabel(this); + logo_->setPixmap(logo.pixmap(128)); - logo_layout_ = new QHBoxLayout(); - logo_layout_->setMargin(0); - logo_layout_->addWidget(logo_, 0, Qt::AlignHCenter); + logo_layout_ = new QHBoxLayout(); + logo_layout_->setMargin(0); + logo_layout_->addWidget(logo_, 0, Qt::AlignHCenter); - form_wrapper_ = new QHBoxLayout(); - form_widget_ = new QWidget(); - form_widget_->setMinimumSize(QSize(350, 300)); + form_wrapper_ = new QHBoxLayout(); + form_widget_ = new QWidget(); + form_widget_->setMinimumSize(QSize(350, 300)); - form_layout_ = new QVBoxLayout(); - form_layout_->setSpacing(20); - form_layout_->setContentsMargins(0, 0, 0, 40); - form_widget_->setLayout(form_layout_); + form_layout_ = new QVBoxLayout(); + form_layout_->setSpacing(20); + form_layout_->setContentsMargins(0, 0, 0, 40); + form_widget_->setLayout(form_layout_); - form_wrapper_->addStretch(1); - form_wrapper_->addWidget(form_widget_); - form_wrapper_->addStretch(1); + form_wrapper_->addStretch(1); + form_wrapper_->addWidget(form_widget_); + form_wrapper_->addStretch(1); - username_input_ = new TextField(); - username_input_->setLabel(tr("Username")); - username_input_->setRegexp(QRegularExpression("[a-z0-9._=/-]+")); - username_input_->setToolTip(tr("The username must not be empty, and must contain only the " - "characters a-z, 0-9, ., _, =, -, and /.")); + username_input_ = new TextField(); + username_input_->setLabel(tr("Username")); + username_input_->setRegexp(QRegularExpression("[a-z0-9._=/-]+")); + username_input_->setToolTip(tr("The username must not be empty, and must contain only the " + "characters a-z, 0-9, ., _, =, -, and /.")); - password_input_ = new TextField(); - password_input_->setLabel(tr("Password")); - password_input_->setRegexp(QRegularExpression("^.{8,}$")); - password_input_->setEchoMode(QLineEdit::Password); - password_input_->setToolTip(tr("Please choose a secure password. The exact requirements " - "for password strength may depend on your server.")); + password_input_ = new TextField(); + password_input_->setLabel(tr("Password")); + password_input_->setRegexp(QRegularExpression("^.{8,}$")); + password_input_->setEchoMode(QLineEdit::Password); + password_input_->setToolTip(tr("Please choose a secure password. The exact requirements " + "for password strength may depend on your server.")); - password_confirmation_ = new TextField(); - password_confirmation_->setLabel(tr("Password confirmation")); - password_confirmation_->setEchoMode(QLineEdit::Password); + password_confirmation_ = new TextField(); + password_confirmation_->setLabel(tr("Password confirmation")); + password_confirmation_->setEchoMode(QLineEdit::Password); - server_input_ = new TextField(); - server_input_->setLabel(tr("Homeserver")); - server_input_->setRegexp(QRegularExpression(".+")); - server_input_->setToolTip( - tr("A server that allows registration. Since matrix is decentralized, you need to first " - "find a server you can register on or host your own.")); + server_input_ = new TextField(); + server_input_->setLabel(tr("Homeserver")); + server_input_->setRegexp(QRegularExpression(".+")); + server_input_->setToolTip( + tr("A server that allows registration. Since matrix is decentralized, you need to first " + "find a server you can register on or host your own.")); - error_username_label_ = new QLabel(this); - error_username_label_->setWordWrap(true); - error_username_label_->hide(); + error_username_label_ = new QLabel(this); + error_username_label_->setWordWrap(true); + error_username_label_->hide(); - error_password_label_ = new QLabel(this); - error_password_label_->setWordWrap(true); - error_password_label_->hide(); + error_password_label_ = new QLabel(this); + error_password_label_->setWordWrap(true); + error_password_label_->hide(); - error_password_confirmation_label_ = new QLabel(this); - error_password_confirmation_label_->setWordWrap(true); - error_password_confirmation_label_->hide(); + error_password_confirmation_label_ = new QLabel(this); + error_password_confirmation_label_->setWordWrap(true); + error_password_confirmation_label_->hide(); - error_server_label_ = new QLabel(this); - error_server_label_->setWordWrap(true); - error_server_label_->hide(); + error_server_label_ = new QLabel(this); + error_server_label_->setWordWrap(true); + error_server_label_->hide(); - form_layout_->addWidget(username_input_, Qt::AlignHCenter); - form_layout_->addWidget(error_username_label_, Qt::AlignHCenter); - form_layout_->addWidget(password_input_, Qt::AlignHCenter); - form_layout_->addWidget(error_password_label_, Qt::AlignHCenter); - form_layout_->addWidget(password_confirmation_, Qt::AlignHCenter); - form_layout_->addWidget(error_password_confirmation_label_, Qt::AlignHCenter); - form_layout_->addWidget(server_input_, Qt::AlignHCenter); - form_layout_->addWidget(error_server_label_, Qt::AlignHCenter); + form_layout_->addWidget(username_input_, Qt::AlignHCenter); + form_layout_->addWidget(error_username_label_, Qt::AlignHCenter); + form_layout_->addWidget(password_input_, Qt::AlignHCenter); + form_layout_->addWidget(error_password_label_, Qt::AlignHCenter); + form_layout_->addWidget(password_confirmation_, Qt::AlignHCenter); + form_layout_->addWidget(error_password_confirmation_label_, Qt::AlignHCenter); + form_layout_->addWidget(server_input_, Qt::AlignHCenter); + form_layout_->addWidget(error_server_label_, Qt::AlignHCenter); - button_layout_ = new QHBoxLayout(); - button_layout_->setSpacing(0); - button_layout_->setMargin(0); + button_layout_ = new QHBoxLayout(); + button_layout_->setSpacing(0); + button_layout_->setMargin(0); - error_label_ = new QLabel(this); - error_label_->setWordWrap(true); + error_label_ = new QLabel(this); + error_label_->setWordWrap(true); - register_button_ = new RaisedButton(tr("REGISTER"), this); - register_button_->setMinimumSize(350, 65); - register_button_->setFontSize(conf::btn::fontSize); - register_button_->setCornerRadius(conf::btn::cornerRadius); + register_button_ = new RaisedButton(tr("REGISTER"), this); + register_button_->setMinimumSize(350, 65); + register_button_->setFontSize(conf::btn::fontSize); + register_button_->setCornerRadius(conf::btn::cornerRadius); - button_layout_->addStretch(1); - button_layout_->addWidget(register_button_); - button_layout_->addStretch(1); + button_layout_->addStretch(1); + button_layout_->addWidget(register_button_); + button_layout_->addStretch(1); - top_layout_->addLayout(back_layout_); - top_layout_->addLayout(logo_layout_); - top_layout_->addLayout(form_wrapper_); - top_layout_->addStretch(1); - top_layout_->addLayout(button_layout_); - top_layout_->addWidget(error_label_, 0, Qt::AlignHCenter); - top_layout_->addStretch(1); - setLayout(top_layout_); + top_layout_->addLayout(back_layout_); + top_layout_->addLayout(logo_layout_); + top_layout_->addLayout(form_wrapper_); + top_layout_->addStretch(1); + top_layout_->addLayout(button_layout_); + top_layout_->addWidget(error_label_, 0, Qt::AlignHCenter); + top_layout_->addStretch(1); + setLayout(top_layout_); - connect(back_button_, SIGNAL(clicked()), this, SLOT(onBackButtonClicked())); - connect(register_button_, SIGNAL(clicked()), this, SLOT(onRegisterButtonClicked())); + connect(back_button_, SIGNAL(clicked()), this, SLOT(onBackButtonClicked())); + connect(register_button_, SIGNAL(clicked()), this, SLOT(onRegisterButtonClicked())); - connect(username_input_, SIGNAL(returnPressed()), register_button_, SLOT(click())); - connect(username_input_, &TextField::editingFinished, this, &RegisterPage::checkUsername); - connect(password_input_, SIGNAL(returnPressed()), register_button_, SLOT(click())); - connect(password_input_, &TextField::editingFinished, this, &RegisterPage::checkPassword); - connect(password_confirmation_, SIGNAL(returnPressed()), register_button_, SLOT(click())); - connect(password_confirmation_, - &TextField::editingFinished, - this, - &RegisterPage::checkPasswordConfirmation); - connect(server_input_, SIGNAL(returnPressed()), register_button_, SLOT(click())); - connect(server_input_, &TextField::editingFinished, this, &RegisterPage::checkServer); + connect(username_input_, SIGNAL(returnPressed()), register_button_, SLOT(click())); + connect(username_input_, &TextField::editingFinished, this, &RegisterPage::checkUsername); + connect(password_input_, SIGNAL(returnPressed()), register_button_, SLOT(click())); + connect(password_input_, &TextField::editingFinished, this, &RegisterPage::checkPassword); + connect(password_confirmation_, SIGNAL(returnPressed()), register_button_, SLOT(click())); + connect(password_confirmation_, + &TextField::editingFinished, + this, + &RegisterPage::checkPasswordConfirmation); + connect(server_input_, SIGNAL(returnPressed()), register_button_, SLOT(click())); + connect(server_input_, &TextField::editingFinished, this, &RegisterPage::checkServer); - connect( - this, - &RegisterPage::serverError, - this, - [this](const QString &msg) { - server_input_->setValid(false); - showError(error_server_label_, msg); - }, - Qt::QueuedConnection); + connect( + this, + &RegisterPage::serverError, + this, + [this](const QString &msg) { + server_input_->setValid(false); + showError(error_server_label_, msg); + }, + Qt::QueuedConnection); - connect(this, &RegisterPage::wellKnownLookup, this, &RegisterPage::doWellKnownLookup); - connect(this, &RegisterPage::versionsCheck, this, &RegisterPage::doVersionsCheck); - connect(this, &RegisterPage::registration, this, &RegisterPage::doRegistration); - connect(this, &RegisterPage::UIA, this, &RegisterPage::doUIA); - connect( - this, &RegisterPage::registrationWithAuth, this, &RegisterPage::doRegistrationWithAuth); + connect(this, &RegisterPage::wellKnownLookup, this, &RegisterPage::doWellKnownLookup); + connect(this, &RegisterPage::versionsCheck, this, &RegisterPage::doVersionsCheck); + connect(this, &RegisterPage::registration, this, &RegisterPage::doRegistration); + connect(this, &RegisterPage::UIA, this, &RegisterPage::doUIA); + connect(this, &RegisterPage::registrationWithAuth, this, &RegisterPage::doRegistrationWithAuth); } void RegisterPage::onBackButtonClicked() { - emit backButtonClicked(); + emit backButtonClicked(); } void RegisterPage::showError(const QString &msg) { - emit errorOccurred(); - auto rect = QFontMetrics(font()).boundingRect(msg); - int width = rect.width(); - int height = rect.height(); - error_label_->setFixedHeight(qCeil(width / 200.0) * height); - error_label_->setText(msg); + emit errorOccurred(); + auto rect = QFontMetrics(font()).boundingRect(msg); + int width = rect.width(); + int height = rect.height(); + error_label_->setFixedHeight(qCeil(width / 200.0) * height); + error_label_->setText(msg); } void RegisterPage::showError(QLabel *label, const QString &msg) { - emit errorOccurred(); - auto rect = QFontMetrics(font()).boundingRect(msg); - int width = rect.width(); - int height = rect.height(); - label->setFixedHeight((int)qCeil(width / 200.0) * height); - label->setText(msg); - label->show(); + emit errorOccurred(); + auto rect = QFontMetrics(font()).boundingRect(msg); + int width = rect.width(); + int height = rect.height(); + label->setFixedHeight((int)qCeil(width / 200.0) * height); + label->setText(msg); + label->show(); } bool RegisterPage::checkOneField(QLabel *label, const TextField *t_field, const QString &msg) { - if (t_field->isValid()) { - label->hide(); - return true; - } else { - showError(label, msg); - return false; - } + if (t_field->isValid()) { + label->hide(); + return true; + } else { + showError(label, msg); + return false; + } } bool RegisterPage::checkUsername() { - return checkOneField(error_username_label_, - username_input_, - tr("The username must not be empty, and must contain only the " - "characters a-z, 0-9, ., _, =, -, and /.")); + return checkOneField(error_username_label_, + username_input_, + tr("The username must not be empty, and must contain only the " + "characters a-z, 0-9, ., _, =, -, and /.")); } bool RegisterPage::checkPassword() { - return checkOneField( - error_password_label_, password_input_, tr("Password is not long enough (min 8 chars)")); + return checkOneField( + error_password_label_, password_input_, tr("Password is not long enough (min 8 chars)")); } bool RegisterPage::checkPasswordConfirmation() { - if (password_input_->text() == password_confirmation_->text()) { - error_password_confirmation_label_->hide(); - password_confirmation_->setValid(true); - return true; - } else { - showError(error_password_confirmation_label_, tr("Passwords don't match")); - password_confirmation_->setValid(false); - return false; - } + if (password_input_->text() == password_confirmation_->text()) { + error_password_confirmation_label_->hide(); + password_confirmation_->setValid(true); + return true; + } else { + showError(error_password_confirmation_label_, tr("Passwords don't match")); + password_confirmation_->setValid(false); + return false; + } } bool RegisterPage::checkServer() { - // This doesn't check that the server is reachable, - // just that the input is not obviously wrong. - return checkOneField(error_server_label_, server_input_, tr("Invalid server name")); + // This doesn't check that the server is reachable, + // just that the input is not obviously wrong. + return checkOneField(error_server_label_, server_input_, tr("Invalid server name")); } void RegisterPage::onRegisterButtonClicked() { - if (checkUsername() && checkPassword() && checkPasswordConfirmation() && checkServer()) { - auto server = server_input_->text().toStdString(); + if (checkUsername() && checkPassword() && checkPasswordConfirmation() && checkServer()) { + auto server = server_input_->text().toStdString(); - http::client()->set_server(server); - http::client()->verify_certificates( - !UserSettings::instance()->disableCertificateValidation()); + http::client()->set_server(server); + http::client()->verify_certificates( + !UserSettings::instance()->disableCertificateValidation()); - // This starts a chain of `emit`s which ends up doing the - // registration. Signals are used rather than normal function - // calls so that the dialogs used in UIA work correctly. - // - // The sequence of events looks something like this: - // - // dowellKnownLookup - // v - // doVersionsCheck - // v - // doRegistration - // v - // doUIA <-----------------+ - // v | More auth required - // doRegistrationWithAuth -+ - // | Success - // v - // registering + // This starts a chain of `emit`s which ends up doing the + // registration. Signals are used rather than normal function + // calls so that the dialogs used in UIA work correctly. + // + // The sequence of events looks something like this: + // + // dowellKnownLookup + // v + // doVersionsCheck + // v + // doRegistration + // v + // doUIA <-----------------+ + // v | More auth required + // doRegistrationWithAuth -+ + // | Success + // v + // registering - emit wellKnownLookup(); + emit wellKnownLookup(); - emit registering(); - } + emit registering(); + } } void RegisterPage::doWellKnownLookup() { - http::client()->well_known( - [this](const mtx::responses::WellKnown &res, mtx::http::RequestErr err) { - if (err) { - if (err->status_code == 404) { - nhlog::net()->info("Autodiscovery: No .well-known."); - // Check that the homeserver can be reached - emit versionsCheck(); - return; - } - - if (!err->parse_error.empty()) { - emit serverError( - tr("Autodiscovery failed. Received malformed response.")); - nhlog::net()->error( - "Autodiscovery failed. Received malformed response."); - return; - } - - emit serverError(tr("Autodiscovery failed. Unknown error when " - "requesting .well-known.")); - nhlog::net()->error("Autodiscovery failed. Unknown error when " - "requesting .well-known. {} {}", - err->status_code, - err->error_code); - return; - } - - nhlog::net()->info("Autodiscovery: Discovered '" + res.homeserver.base_url + "'"); - http::client()->set_server(res.homeserver.base_url); + http::client()->well_known( + [this](const mtx::responses::WellKnown &res, mtx::http::RequestErr err) { + if (err) { + if (err->status_code == 404) { + nhlog::net()->info("Autodiscovery: No .well-known."); // Check that the homeserver can be reached emit versionsCheck(); - }); + return; + } + + if (!err->parse_error.empty()) { + emit serverError(tr("Autodiscovery failed. Received malformed response.")); + nhlog::net()->error("Autodiscovery failed. Received malformed response."); + return; + } + + emit serverError(tr("Autodiscovery failed. Unknown error when " + "requesting .well-known.")); + nhlog::net()->error("Autodiscovery failed. Unknown error when " + "requesting .well-known. {} {}", + err->status_code, + err->error_code); + return; + } + + nhlog::net()->info("Autodiscovery: Discovered '" + res.homeserver.base_url + "'"); + http::client()->set_server(res.homeserver.base_url); + // Check that the homeserver can be reached + emit versionsCheck(); + }); } void RegisterPage::doVersionsCheck() { - // Make a request to /_matrix/client/versions to check the address - // given is a Matrix homeserver. - http::client()->versions( - [this](const mtx::responses::Versions &, mtx::http::RequestErr err) { - if (err) { - if (err->status_code == 404) { - emit serverError( - tr("The required endpoints were not found. Possibly " - "not a Matrix server.")); - return; - } + // Make a request to /_matrix/client/versions to check the address + // given is a Matrix homeserver. + http::client()->versions([this](const mtx::responses::Versions &, mtx::http::RequestErr err) { + if (err) { + if (err->status_code == 404) { + emit serverError(tr("The required endpoints were not found. Possibly " + "not a Matrix server.")); + return; + } - if (!err->parse_error.empty()) { - emit serverError( - tr("Received malformed response. Make sure the homeserver " - "domain is valid.")); - return; - } + if (!err->parse_error.empty()) { + emit serverError(tr("Received malformed response. Make sure the homeserver " + "domain is valid.")); + return; + } - emit serverError(tr("An unknown error occured. Make sure the " - "homeserver domain is valid.")); - return; - } + emit serverError(tr("An unknown error occured. Make sure the " + "homeserver domain is valid.")); + return; + } - // Attempt registration without an `auth` dict - emit registration(); - }); + // Attempt registration without an `auth` dict + emit registration(); + }); } void RegisterPage::doRegistration() { - // These inputs should still be alright, but check just in case - if (checkUsername() && checkPassword() && checkPasswordConfirmation()) { - auto username = username_input_->text().toStdString(); - auto password = password_input_->text().toStdString(); - http::client()->registration(username, password, registrationCb()); - } + // These inputs should still be alright, but check just in case + if (checkUsername() && checkPassword() && checkPasswordConfirmation()) { + auto username = username_input_->text().toStdString(); + auto password = password_input_->text().toStdString(); + http::client()->registration(username, password, registrationCb()); + } } void RegisterPage::doRegistrationWithAuth(const mtx::user_interactive::Auth &auth) { - // These inputs should still be alright, but check just in case - if (checkUsername() && checkPassword() && checkPasswordConfirmation()) { - auto username = username_input_->text().toStdString(); - auto password = password_input_->text().toStdString(); - http::client()->registration(username, password, auth, registrationCb()); - } + // These inputs should still be alright, but check just in case + if (checkUsername() && checkPassword() && checkPasswordConfirmation()) { + auto username = username_input_->text().toStdString(); + auto password = password_input_->text().toStdString(); + http::client()->registration(username, password, auth, registrationCb()); + } } mtx::http::Callback RegisterPage::registrationCb() { - // Return a function to be used as the callback when an attempt at - // registration is made. - return [this](const mtx::responses::Register &res, mtx::http::RequestErr err) { - if (!err) { - http::client()->set_user(res.user_id); - http::client()->set_access_token(res.access_token); - emit registerOk(); - return; - } - - // The server requires registration flows. - if (err->status_code == 401) { - if (err->matrix_error.unauthorized.flows.empty()) { - nhlog::net()->warn("failed to retrieve registration flows: " - "status_code({}), matrix_error({}) ", - static_cast(err->status_code), - err->matrix_error.error); - showError(QString::fromStdString(err->matrix_error.error)); - return; - } - - // Attempt to complete a UIA stage - emit UIA(err->matrix_error.unauthorized); - return; - } - - nhlog::net()->error("failed to register: status_code ({}), matrix_error({})", - static_cast(err->status_code), - err->matrix_error.error); + // Return a function to be used as the callback when an attempt at + // registration is made. + return [this](const mtx::responses::Register &res, mtx::http::RequestErr err) { + if (!err) { + http::client()->set_user(res.user_id); + http::client()->set_access_token(res.access_token); + emit registerOk(); + return; + } + // The server requires registration flows. + if (err->status_code == 401) { + if (err->matrix_error.unauthorized.flows.empty()) { + nhlog::net()->warn("failed to retrieve registration flows: " + "status_code({}), matrix_error({}) ", + static_cast(err->status_code), + err->matrix_error.error); showError(QString::fromStdString(err->matrix_error.error)); - }; + return; + } + + // Attempt to complete a UIA stage + emit UIA(err->matrix_error.unauthorized); + return; + } + + nhlog::net()->error("failed to register: status_code ({}), matrix_error({})", + static_cast(err->status_code), + err->matrix_error.error); + + showError(QString::fromStdString(err->matrix_error.error)); + }; } void RegisterPage::doUIA(const mtx::user_interactive::Unauthorized &unauthorized) { - auto completed_stages = unauthorized.completed; - auto flows = unauthorized.flows; - auto session = - unauthorized.session.empty() ? http::client()->generate_txn_id() : unauthorized.session; + auto completed_stages = unauthorized.completed; + auto flows = unauthorized.flows; + auto session = + unauthorized.session.empty() ? http::client()->generate_txn_id() : unauthorized.session; - nhlog::ui()->info("Completed stages: {}", completed_stages.size()); + nhlog::ui()->info("Completed stages: {}", completed_stages.size()); - if (!completed_stages.empty()) { - // Get rid of all flows which don't start with the sequence of - // stages that have already been completed. - flows.erase( - std::remove_if(flows.begin(), - flows.end(), - [completed_stages](auto flow) { - if (completed_stages.size() > flow.stages.size()) - return true; - for (size_t f = 0; f < completed_stages.size(); f++) - if (completed_stages[f] != flow.stages[f]) - return true; - return false; - }), - flows.end()); - } + if (!completed_stages.empty()) { + // Get rid of all flows which don't start with the sequence of + // stages that have already been completed. + flows.erase(std::remove_if(flows.begin(), + flows.end(), + [completed_stages](auto flow) { + if (completed_stages.size() > flow.stages.size()) + return true; + for (size_t f = 0; f < completed_stages.size(); f++) + if (completed_stages[f] != flow.stages[f]) + return true; + return false; + }), + flows.end()); + } - if (flows.empty()) { - nhlog::ui()->error("No available registration flows!"); - showError(tr("No supported registration flows!")); - return; - } + if (flows.empty()) { + nhlog::ui()->error("No available registration flows!"); + showError(tr("No supported registration flows!")); + return; + } - auto current_stage = flows.front().stages.at(completed_stages.size()); + auto current_stage = flows.front().stages.at(completed_stages.size()); - if (current_stage == mtx::user_interactive::auth_types::recaptcha) { - auto captchaDialog = new dialogs::ReCaptcha(QString::fromStdString(session), this); + if (current_stage == mtx::user_interactive::auth_types::recaptcha) { + auto captchaDialog = new dialogs::ReCaptcha(QString::fromStdString(session), this); - connect(captchaDialog, - &dialogs::ReCaptcha::confirmation, - this, - [this, session, captchaDialog]() { - captchaDialog->close(); - captchaDialog->deleteLater(); - doRegistrationWithAuth(mtx::user_interactive::Auth{ - session, mtx::user_interactive::auth::Fallback{}}); - }); + connect( + captchaDialog, &dialogs::ReCaptcha::confirmation, this, [this, session, captchaDialog]() { + captchaDialog->close(); + captchaDialog->deleteLater(); + doRegistrationWithAuth( + mtx::user_interactive::Auth{session, mtx::user_interactive::auth::Fallback{}}); + }); - connect( - captchaDialog, &dialogs::ReCaptcha::cancel, this, &RegisterPage::errorOccurred); + connect(captchaDialog, &dialogs::ReCaptcha::cancel, this, &RegisterPage::errorOccurred); - QTimer::singleShot(1000, this, [captchaDialog]() { captchaDialog->show(); }); + QTimer::singleShot(1000, this, [captchaDialog]() { captchaDialog->show(); }); - } else if (current_stage == mtx::user_interactive::auth_types::dummy) { - doRegistrationWithAuth( - mtx::user_interactive::Auth{session, mtx::user_interactive::auth::Dummy{}}); + } else if (current_stage == mtx::user_interactive::auth_types::dummy) { + doRegistrationWithAuth( + mtx::user_interactive::Auth{session, mtx::user_interactive::auth::Dummy{}}); - } else if (current_stage == mtx::user_interactive::auth_types::registration_token) { - bool ok; - QString token = - QInputDialog::getText(this, - tr("Registration token"), - tr("Please enter a valid registration token."), - QLineEdit::Normal, - QString(), - &ok); + } else if (current_stage == mtx::user_interactive::auth_types::registration_token) { + bool ok; + QString token = QInputDialog::getText(this, + tr("Registration token"), + tr("Please enter a valid registration token."), + QLineEdit::Normal, + QString(), + &ok); - if (ok) { - emit registrationWithAuth(mtx::user_interactive::Auth{ - session, - mtx::user_interactive::auth::RegistrationToken{token.toStdString()}}); - } else { - emit errorOccurred(); - } + if (ok) { + emit registrationWithAuth(mtx::user_interactive::Auth{ + session, mtx::user_interactive::auth::RegistrationToken{token.toStdString()}}); } else { - // use fallback - auto dialog = new dialogs::FallbackAuth( - QString::fromStdString(current_stage), QString::fromStdString(session), this); - - connect( - dialog, &dialogs::FallbackAuth::confirmation, this, [this, session, dialog]() { - dialog->close(); - dialog->deleteLater(); - emit registrationWithAuth(mtx::user_interactive::Auth{ - session, mtx::user_interactive::auth::Fallback{}}); - }); - - connect(dialog, &dialogs::FallbackAuth::cancel, this, &RegisterPage::errorOccurred); - - dialog->show(); + emit errorOccurred(); } + } else { + // use fallback + auto dialog = new dialogs::FallbackAuth( + QString::fromStdString(current_stage), QString::fromStdString(session), this); + + connect(dialog, &dialogs::FallbackAuth::confirmation, this, [this, session, dialog]() { + dialog->close(); + dialog->deleteLater(); + emit registrationWithAuth( + mtx::user_interactive::Auth{session, mtx::user_interactive::auth::Fallback{}}); + }); + + connect(dialog, &dialogs::FallbackAuth::cancel, this, &RegisterPage::errorOccurred); + + dialog->show(); + } } void RegisterPage::paintEvent(QPaintEvent *) { - QStyleOption opt; - opt.init(this); - QPainter p(this); - style()->drawPrimitive(QStyle::PE_Widget, &opt, &p, this); + QStyleOption opt; + opt.init(this); + QPainter p(this); + style()->drawPrimitive(QStyle::PE_Widget, &opt, &p, this); } diff --git a/src/RegisterPage.h b/src/RegisterPage.h index 42ea00cb..b88808f9 100644 --- a/src/RegisterPage.h +++ b/src/RegisterPage.h @@ -21,76 +21,76 @@ class QHBoxLayout; class RegisterPage : public QWidget { - Q_OBJECT + Q_OBJECT public: - RegisterPage(QWidget *parent = nullptr); + RegisterPage(QWidget *parent = nullptr); protected: - void paintEvent(QPaintEvent *event) override; + void paintEvent(QPaintEvent *event) override; signals: - void backButtonClicked(); - void errorOccurred(); + void backButtonClicked(); + void errorOccurred(); - //! Used to trigger the corresponding slot outside of the main thread. - void serverError(const QString &err); + //! Used to trigger the corresponding slot outside of the main thread. + void serverError(const QString &err); - void wellKnownLookup(); - void versionsCheck(); - void registration(); - void UIA(const mtx::user_interactive::Unauthorized &unauthorized); - void registrationWithAuth(const mtx::user_interactive::Auth &auth); + void wellKnownLookup(); + void versionsCheck(); + void registration(); + void UIA(const mtx::user_interactive::Unauthorized &unauthorized); + void registrationWithAuth(const mtx::user_interactive::Auth &auth); - void registering(); - void registerOk(); + void registering(); + void registerOk(); private slots: - void onBackButtonClicked(); - void onRegisterButtonClicked(); + void onBackButtonClicked(); + void onRegisterButtonClicked(); - // function for showing different errors - void showError(const QString &msg); - void showError(QLabel *label, const QString &msg); + // function for showing different errors + void showError(const QString &msg); + void showError(QLabel *label, const QString &msg); - bool checkOneField(QLabel *label, const TextField *t_field, const QString &msg); - bool checkUsername(); - bool checkPassword(); - bool checkPasswordConfirmation(); - bool checkServer(); + bool checkOneField(QLabel *label, const TextField *t_field, const QString &msg); + bool checkUsername(); + bool checkPassword(); + bool checkPasswordConfirmation(); + bool checkServer(); - void doWellKnownLookup(); - void doVersionsCheck(); - void doRegistration(); - void doUIA(const mtx::user_interactive::Unauthorized &unauthorized); - void doRegistrationWithAuth(const mtx::user_interactive::Auth &auth); - mtx::http::Callback registrationCb(); + void doWellKnownLookup(); + void doVersionsCheck(); + void doRegistration(); + void doUIA(const mtx::user_interactive::Unauthorized &unauthorized); + void doRegistrationWithAuth(const mtx::user_interactive::Auth &auth); + mtx::http::Callback registrationCb(); private: - QVBoxLayout *top_layout_; + QVBoxLayout *top_layout_; - QHBoxLayout *back_layout_; - QHBoxLayout *logo_layout_; - QHBoxLayout *button_layout_; + QHBoxLayout *back_layout_; + QHBoxLayout *logo_layout_; + QHBoxLayout *button_layout_; - QLabel *logo_; - QLabel *error_label_; - QLabel *error_username_label_; - QLabel *error_password_label_; - QLabel *error_password_confirmation_label_; - QLabel *error_server_label_; - QLabel *error_registration_token_label_; + QLabel *logo_; + QLabel *error_label_; + QLabel *error_username_label_; + QLabel *error_password_label_; + QLabel *error_password_confirmation_label_; + QLabel *error_server_label_; + QLabel *error_registration_token_label_; - FlatButton *back_button_; - RaisedButton *register_button_; + FlatButton *back_button_; + RaisedButton *register_button_; - QWidget *form_widget_; - QHBoxLayout *form_wrapper_; - QVBoxLayout *form_layout_; + QWidget *form_widget_; + QHBoxLayout *form_wrapper_; + QVBoxLayout *form_layout_; - TextField *username_input_; - TextField *password_input_; - TextField *password_confirmation_; - TextField *server_input_; - TextField *registration_token_input_; + TextField *username_input_; + TextField *password_input_; + TextField *password_confirmation_; + TextField *server_input_; + TextField *registration_token_input_; }; diff --git a/src/RoomDirectoryModel.cpp b/src/RoomDirectoryModel.cpp index cfa2b623..707571d6 100644 --- a/src/RoomDirectoryModel.cpp +++ b/src/RoomDirectoryModel.cpp @@ -12,207 +12,205 @@ RoomDirectoryModel::RoomDirectoryModel(QObject *parent, const std::string &serve : QAbstractListModel(parent) , server_(server) { - connect(ChatPage::instance(), &ChatPage::newRoom, this, [this](const QString &roomid) { - auto roomid_ = roomid.toStdString(); + connect(ChatPage::instance(), &ChatPage::newRoom, this, [this](const QString &roomid) { + auto roomid_ = roomid.toStdString(); - int i = 0; - for (const auto &room : publicRoomsData_) { - if (room.room_id == roomid_) { - emit dataChanged(index(i), index(i), {Roles::CanJoin}); - break; - } - i++; - } - }); + int i = 0; + for (const auto &room : publicRoomsData_) { + if (room.room_id == roomid_) { + emit dataChanged(index(i), index(i), {Roles::CanJoin}); + break; + } + i++; + } + }); - connect(this, - &RoomDirectoryModel::fetchedRoomsBatch, - this, - &RoomDirectoryModel::displayRooms, - Qt::QueuedConnection); + connect(this, + &RoomDirectoryModel::fetchedRoomsBatch, + this, + &RoomDirectoryModel::displayRooms, + Qt::QueuedConnection); } QHash RoomDirectoryModel::roleNames() const { - return { - {Roles::Name, "name"}, - {Roles::Id, "roomid"}, - {Roles::AvatarUrl, "avatarUrl"}, - {Roles::Topic, "topic"}, - {Roles::MemberCount, "numMembers"}, - {Roles::Previewable, "canPreview"}, - {Roles::CanJoin, "canJoin"}, - }; + return { + {Roles::Name, "name"}, + {Roles::Id, "roomid"}, + {Roles::AvatarUrl, "avatarUrl"}, + {Roles::Topic, "topic"}, + {Roles::MemberCount, "numMembers"}, + {Roles::Previewable, "canPreview"}, + {Roles::CanJoin, "canJoin"}, + }; } void RoomDirectoryModel::resetDisplayedData() { - beginResetModel(); + beginResetModel(); - prevBatch_ = ""; - nextBatch_ = ""; - canFetchMore_ = true; + prevBatch_ = ""; + nextBatch_ = ""; + canFetchMore_ = true; - publicRoomsData_.clear(); + publicRoomsData_.clear(); - endResetModel(); + endResetModel(); } void RoomDirectoryModel::setMatrixServer(const QString &s) { - server_ = s.toStdString(); + server_ = s.toStdString(); - nhlog::ui()->debug("Received matrix server: {}", server_); + nhlog::ui()->debug("Received matrix server: {}", server_); - resetDisplayedData(); + resetDisplayedData(); } void RoomDirectoryModel::setSearchTerm(const QString &f) { - userSearchString_ = f.toStdString(); + userSearchString_ = f.toStdString(); - nhlog::ui()->debug("Received user query: {}", userSearchString_); + nhlog::ui()->debug("Received user query: {}", userSearchString_); - resetDisplayedData(); + resetDisplayedData(); } bool RoomDirectoryModel::canJoinRoom(const QString &room) const { - return !room.isEmpty() && cache::getRoomInfo({room.toStdString()}).empty(); + return !room.isEmpty() && cache::getRoomInfo({room.toStdString()}).empty(); } std::vector RoomDirectoryModel::getViasForRoom(const std::vector &aliases) { - std::vector vias; + std::vector vias; - vias.reserve(aliases.size()); + vias.reserve(aliases.size()); - std::transform(aliases.begin(), - aliases.end(), - std::back_inserter(vias), - [](const auto &alias) { return alias.substr(alias.find(":") + 1); }); + std::transform(aliases.begin(), aliases.end(), std::back_inserter(vias), [](const auto &alias) { + return alias.substr(alias.find(":") + 1); + }); - // When joining a room hosted on a homeserver other than the one the - // account has been registered on, the room's server has to be explicitly - // specified in the "server_name=..." URL parameter of the Matrix Join Room - // request. For more details consult the specs: - // https://matrix.org/docs/spec/client_server/r0.6.1#post-matrix-client-r0-join-roomidoralias - if (!server_.empty()) { - vias.push_back(server_); - } + // When joining a room hosted on a homeserver other than the one the + // account has been registered on, the room's server has to be explicitly + // specified in the "server_name=..." URL parameter of the Matrix Join Room + // request. For more details consult the specs: + // https://matrix.org/docs/spec/client_server/r0.6.1#post-matrix-client-r0-join-roomidoralias + if (!server_.empty()) { + vias.push_back(server_); + } - return vias; + return vias; } void RoomDirectoryModel::joinRoom(const int &index) { - if (index >= 0 && static_cast(index) < publicRoomsData_.size()) { - const auto &chunk = publicRoomsData_[index]; - nhlog::ui()->debug("'Joining room {}", chunk.room_id); - ChatPage::instance()->joinRoomVia(chunk.room_id, getViasForRoom(chunk.aliases)); - } + if (index >= 0 && static_cast(index) < publicRoomsData_.size()) { + const auto &chunk = publicRoomsData_[index]; + nhlog::ui()->debug("'Joining room {}", chunk.room_id); + ChatPage::instance()->joinRoomVia(chunk.room_id, getViasForRoom(chunk.aliases)); + } } QVariant RoomDirectoryModel::data(const QModelIndex &index, int role) const { - if (hasIndex(index.row(), index.column(), index.parent())) { - const auto &room_chunk = publicRoomsData_[index.row()]; - switch (role) { - case Roles::Name: - return QString::fromStdString(room_chunk.name); - case Roles::Id: - return QString::fromStdString(room_chunk.room_id); - case Roles::AvatarUrl: - return QString::fromStdString(room_chunk.avatar_url); - case Roles::Topic: - return QString::fromStdString(room_chunk.topic); - case Roles::MemberCount: - return QVariant::fromValue(room_chunk.num_joined_members); - case Roles::Previewable: - return QVariant::fromValue(room_chunk.world_readable); - case Roles::CanJoin: - return canJoinRoom(QString::fromStdString(room_chunk.room_id)); - } + if (hasIndex(index.row(), index.column(), index.parent())) { + const auto &room_chunk = publicRoomsData_[index.row()]; + switch (role) { + case Roles::Name: + return QString::fromStdString(room_chunk.name); + case Roles::Id: + return QString::fromStdString(room_chunk.room_id); + case Roles::AvatarUrl: + return QString::fromStdString(room_chunk.avatar_url); + case Roles::Topic: + return QString::fromStdString(room_chunk.topic); + case Roles::MemberCount: + return QVariant::fromValue(room_chunk.num_joined_members); + case Roles::Previewable: + return QVariant::fromValue(room_chunk.world_readable); + case Roles::CanJoin: + return canJoinRoom(QString::fromStdString(room_chunk.room_id)); } - return {}; + } + return {}; } void RoomDirectoryModel::fetchMore(const QModelIndex &) { - if (!canFetchMore_) - return; + if (!canFetchMore_) + return; - nhlog::net()->debug("Fetching more rooms from mtxclient..."); + nhlog::net()->debug("Fetching more rooms from mtxclient..."); - mtx::requests::PublicRooms req; - req.limit = limit_; - req.since = prevBatch_; - req.filter.generic_search_term = userSearchString_; - // req.third_party_instance_id = third_party_instance_id; - auto requested_server = server_; + mtx::requests::PublicRooms req; + req.limit = limit_; + req.since = prevBatch_; + req.filter.generic_search_term = userSearchString_; + // req.third_party_instance_id = third_party_instance_id; + auto requested_server = server_; - reachedEndOfPagination_ = false; - emit reachedEndOfPaginationChanged(); + reachedEndOfPagination_ = false; + emit reachedEndOfPaginationChanged(); - loadingMoreRooms_ = true; - emit loadingMoreRoomsChanged(); + loadingMoreRooms_ = true; + emit loadingMoreRoomsChanged(); - http::client()->post_public_rooms( - req, - [requested_server, this, req](const mtx::responses::PublicRooms &res, - mtx::http::RequestErr err) { - loadingMoreRooms_ = false; - emit loadingMoreRoomsChanged(); + http::client()->post_public_rooms( + req, + [requested_server, this, req](const mtx::responses::PublicRooms &res, + mtx::http::RequestErr err) { + loadingMoreRooms_ = false; + emit loadingMoreRoomsChanged(); - if (err) { - nhlog::net()->error( - "Failed to retrieve rooms from mtxclient - {} - {} - {}", - mtx::errors::to_string(err->matrix_error.errcode), - err->matrix_error.error, - err->parse_error); - } else if (req.filter.generic_search_term == this->userSearchString_ && - req.since == this->prevBatch_ && requested_server == this->server_) { - nhlog::net()->debug("signalling chunk to GUI thread"); - emit fetchedRoomsBatch(res.chunk, res.next_batch); - } - }, - requested_server); + if (err) { + nhlog::net()->error("Failed to retrieve rooms from mtxclient - {} - {} - {}", + mtx::errors::to_string(err->matrix_error.errcode), + err->matrix_error.error, + err->parse_error); + } else if (req.filter.generic_search_term == this->userSearchString_ && + req.since == this->prevBatch_ && requested_server == this->server_) { + nhlog::net()->debug("signalling chunk to GUI thread"); + emit fetchedRoomsBatch(res.chunk, res.next_batch); + } + }, + requested_server); } void RoomDirectoryModel::displayRooms(std::vector fetched_rooms, const std::string &next_batch) { - nhlog::net()->debug("Prev batch: {} | Next batch: {}", prevBatch_, next_batch); + nhlog::net()->debug("Prev batch: {} | Next batch: {}", prevBatch_, next_batch); - if (fetched_rooms.empty()) { - nhlog::net()->error("mtxclient helper thread yielded empty chunk!"); - return; - } + if (fetched_rooms.empty()) { + nhlog::net()->error("mtxclient helper thread yielded empty chunk!"); + return; + } - beginInsertRows(QModelIndex(), - static_cast(publicRoomsData_.size()), - static_cast(publicRoomsData_.size() + fetched_rooms.size()) - 1); - this->publicRoomsData_.insert( - this->publicRoomsData_.end(), fetched_rooms.begin(), fetched_rooms.end()); - endInsertRows(); + beginInsertRows(QModelIndex(), + static_cast(publicRoomsData_.size()), + static_cast(publicRoomsData_.size() + fetched_rooms.size()) - 1); + this->publicRoomsData_.insert( + this->publicRoomsData_.end(), fetched_rooms.begin(), fetched_rooms.end()); + endInsertRows(); - if (next_batch.empty()) { - canFetchMore_ = false; - reachedEndOfPagination_ = true; - emit reachedEndOfPaginationChanged(); - } + if (next_batch.empty()) { + canFetchMore_ = false; + reachedEndOfPagination_ = true; + emit reachedEndOfPaginationChanged(); + } - prevBatch_ = next_batch; + prevBatch_ = next_batch; - nhlog::ui()->debug("Finished loading rooms"); + nhlog::ui()->debug("Finished loading rooms"); } diff --git a/src/RoomDirectoryModel.h b/src/RoomDirectoryModel.h index 80c04612..4699474b 100644 --- a/src/RoomDirectoryModel.h +++ b/src/RoomDirectoryModel.h @@ -25,74 +25,74 @@ struct PublicRooms; class RoomDirectoryModel : public QAbstractListModel { - Q_OBJECT + Q_OBJECT - Q_PROPERTY(bool loadingMoreRooms READ loadingMoreRooms NOTIFY loadingMoreRoomsChanged) - Q_PROPERTY(bool reachedEndOfPagination READ reachedEndOfPagination NOTIFY - reachedEndOfPaginationChanged) + Q_PROPERTY(bool loadingMoreRooms READ loadingMoreRooms NOTIFY loadingMoreRoomsChanged) + Q_PROPERTY( + bool reachedEndOfPagination READ reachedEndOfPagination NOTIFY reachedEndOfPaginationChanged) public: - explicit RoomDirectoryModel(QObject *parent = nullptr, const std::string &server = ""); + explicit RoomDirectoryModel(QObject *parent = nullptr, const std::string &server = ""); - enum Roles - { - Name = Qt::UserRole, - Id, - AvatarUrl, - Topic, - MemberCount, - Previewable, - CanJoin, - }; - QHash roleNames() const override; + enum Roles + { + Name = Qt::UserRole, + Id, + AvatarUrl, + Topic, + MemberCount, + Previewable, + CanJoin, + }; + QHash roleNames() const override; - QVariant data(const QModelIndex &index, int role) const override; + QVariant data(const QModelIndex &index, int role) const override; - inline int rowCount(const QModelIndex &parent = QModelIndex()) const override - { - (void)parent; - return static_cast(publicRoomsData_.size()); - } + inline int rowCount(const QModelIndex &parent = QModelIndex()) const override + { + (void)parent; + return static_cast(publicRoomsData_.size()); + } - bool canFetchMore(const QModelIndex &) const override { return canFetchMore_; } + bool canFetchMore(const QModelIndex &) const override { return canFetchMore_; } - bool loadingMoreRooms() const { return loadingMoreRooms_; } + bool loadingMoreRooms() const { return loadingMoreRooms_; } - bool reachedEndOfPagination() const { return reachedEndOfPagination_; } + bool reachedEndOfPagination() const { return reachedEndOfPagination_; } - void fetchMore(const QModelIndex &) override; + void fetchMore(const QModelIndex &) override; - Q_INVOKABLE void joinRoom(const int &index = -1); + Q_INVOKABLE void joinRoom(const int &index = -1); signals: - void fetchedRoomsBatch(std::vector rooms, - const std::string &next_batch); - void loadingMoreRoomsChanged(); - void reachedEndOfPaginationChanged(); + void fetchedRoomsBatch(std::vector rooms, + const std::string &next_batch); + void loadingMoreRoomsChanged(); + void reachedEndOfPaginationChanged(); public slots: - void setMatrixServer(const QString &s = ""); - void setSearchTerm(const QString &f); + void setMatrixServer(const QString &s = ""); + void setSearchTerm(const QString &f); private slots: - void displayRooms(std::vector rooms, - const std::string &next_batch); + void displayRooms(std::vector rooms, + const std::string &next_batch); private: - bool canJoinRoom(const QString &room) const; + bool canJoinRoom(const QString &room) const; - static constexpr size_t limit_ = 50; + static constexpr size_t limit_ = 50; - std::string server_; - std::string userSearchString_; - std::string prevBatch_; - std::string nextBatch_; - bool canFetchMore_{true}; - bool loadingMoreRooms_{false}; - bool reachedEndOfPagination_{false}; - std::vector publicRoomsData_; + std::string server_; + std::string userSearchString_; + std::string prevBatch_; + std::string nextBatch_; + bool canFetchMore_{true}; + bool loadingMoreRooms_{false}; + bool reachedEndOfPagination_{false}; + std::vector publicRoomsData_; - std::vector getViasForRoom(const std::vector &room); - void resetDisplayedData(); + std::vector getViasForRoom(const std::vector &room); + void resetDisplayedData(); }; diff --git a/src/RoomsModel.cpp b/src/RoomsModel.cpp index 656a0deb..8c05b7bb 100644 --- a/src/RoomsModel.cpp +++ b/src/RoomsModel.cpp @@ -14,71 +14,67 @@ RoomsModel::RoomsModel(bool showOnlyRoomWithAliases, QObject *parent) : QAbstractListModel(parent) , showOnlyRoomWithAliases_(showOnlyRoomWithAliases) { - std::vector rooms_ = cache::joinedRooms(); - roomInfos = cache::getRoomInfo(rooms_); - if (!showOnlyRoomWithAliases_) { - roomids.reserve(rooms_.size()); - roomAliases.reserve(rooms_.size()); - } + std::vector rooms_ = cache::joinedRooms(); + roomInfos = cache::getRoomInfo(rooms_); + if (!showOnlyRoomWithAliases_) { + roomids.reserve(rooms_.size()); + roomAliases.reserve(rooms_.size()); + } - for (const auto &r : rooms_) { - auto roomAliasesList = cache::client()->getRoomAliases(r); + for (const auto &r : rooms_) { + auto roomAliasesList = cache::client()->getRoomAliases(r); - if (showOnlyRoomWithAliases_) { - if (roomAliasesList && !roomAliasesList->alias.empty()) { - roomids.push_back(QString::fromStdString(r)); - roomAliases.push_back( - QString::fromStdString(roomAliasesList->alias)); - } - } else { - roomids.push_back(QString::fromStdString(r)); - roomAliases.push_back( - roomAliasesList ? QString::fromStdString(roomAliasesList->alias) : ""); - } + if (showOnlyRoomWithAliases_) { + if (roomAliasesList && !roomAliasesList->alias.empty()) { + roomids.push_back(QString::fromStdString(r)); + roomAliases.push_back(QString::fromStdString(roomAliasesList->alias)); + } + } else { + roomids.push_back(QString::fromStdString(r)); + roomAliases.push_back(roomAliasesList ? QString::fromStdString(roomAliasesList->alias) + : ""); } + } } QHash RoomsModel::roleNames() const { - return {{CompletionModel::CompletionRole, "completionRole"}, - {CompletionModel::SearchRole, "searchRole"}, - {CompletionModel::SearchRole2, "searchRole2"}, - {Roles::RoomAlias, "roomAlias"}, - {Roles::AvatarUrl, "avatarUrl"}, - {Roles::RoomID, "roomid"}, - {Roles::RoomName, "roomName"}}; + return {{CompletionModel::CompletionRole, "completionRole"}, + {CompletionModel::SearchRole, "searchRole"}, + {CompletionModel::SearchRole2, "searchRole2"}, + {Roles::RoomAlias, "roomAlias"}, + {Roles::AvatarUrl, "avatarUrl"}, + {Roles::RoomID, "roomid"}, + {Roles::RoomName, "roomName"}}; } QVariant RoomsModel::data(const QModelIndex &index, int role) const { - if (hasIndex(index.row(), index.column(), index.parent())) { - switch (role) { - case CompletionModel::CompletionRole: { - if (UserSettings::instance()->markdown()) { - QString percentEncoding = - QUrl::toPercentEncoding(roomAliases[index.row()]); - return QString("[%1](https://matrix.to/#/%2)") - .arg(roomAliases[index.row()], percentEncoding); - } else { - return roomAliases[index.row()]; - } - } - case CompletionModel::SearchRole: - case Qt::DisplayRole: - case Roles::RoomAlias: - return roomAliases[index.row()].toHtmlEscaped(); - case CompletionModel::SearchRole2: - case Roles::RoomName: - return QString::fromStdString(roomInfos.at(roomids[index.row()]).name) - .toHtmlEscaped(); - case Roles::AvatarUrl: - return QString::fromStdString( - roomInfos.at(roomids[index.row()]).avatar_url); - case Roles::RoomID: - return roomids[index.row()].toHtmlEscaped(); - } + if (hasIndex(index.row(), index.column(), index.parent())) { + switch (role) { + case CompletionModel::CompletionRole: { + if (UserSettings::instance()->markdown()) { + QString percentEncoding = QUrl::toPercentEncoding(roomAliases[index.row()]); + return QString("[%1](https://matrix.to/#/%2)") + .arg(roomAliases[index.row()], percentEncoding); + } else { + return roomAliases[index.row()]; + } } - return {}; + case CompletionModel::SearchRole: + case Qt::DisplayRole: + case Roles::RoomAlias: + return roomAliases[index.row()].toHtmlEscaped(); + case CompletionModel::SearchRole2: + case Roles::RoomName: + return QString::fromStdString(roomInfos.at(roomids[index.row()]).name).toHtmlEscaped(); + case Roles::AvatarUrl: + return QString::fromStdString(roomInfos.at(roomids[index.row()]).avatar_url); + case Roles::RoomID: + return roomids[index.row()].toHtmlEscaped(); + } + } + return {}; } diff --git a/src/RoomsModel.h b/src/RoomsModel.h index 255f207c..b6e29974 100644 --- a/src/RoomsModel.h +++ b/src/RoomsModel.h @@ -12,26 +12,26 @@ class RoomsModel : public QAbstractListModel { public: - enum Roles - { - AvatarUrl = Qt::UserRole, - RoomAlias, - RoomID, - RoomName, - }; + enum Roles + { + AvatarUrl = Qt::UserRole, + RoomAlias, + RoomID, + RoomName, + }; - RoomsModel(bool showOnlyRoomWithAliases = false, QObject *parent = nullptr); - QHash roleNames() const override; - int rowCount(const QModelIndex &parent = QModelIndex()) const override - { - (void)parent; - return (int)roomids.size(); - } - QVariant data(const QModelIndex &index, int role) const override; + RoomsModel(bool showOnlyRoomWithAliases = false, QObject *parent = nullptr); + QHash roleNames() const override; + int rowCount(const QModelIndex &parent = QModelIndex()) const override + { + (void)parent; + return (int)roomids.size(); + } + QVariant data(const QModelIndex &index, int role) const override; private: - std::vector roomids; - std::vector roomAliases; - std::map roomInfos; - bool showOnlyRoomWithAliases_; + std::vector roomids; + std::vector roomAliases; + std::map roomInfos; + bool showOnlyRoomWithAliases_; }; diff --git a/src/SSOHandler.cpp b/src/SSOHandler.cpp index 8fd0828c..a6f7ba11 100644 --- a/src/SSOHandler.cpp +++ b/src/SSOHandler.cpp @@ -12,46 +12,46 @@ SSOHandler::SSOHandler(QObject *) { - QTimer::singleShot(120000, this, &SSOHandler::ssoFailed); + QTimer::singleShot(120000, this, &SSOHandler::ssoFailed); - using namespace httplib; + using namespace httplib; - svr.set_logger([](const Request &req, const Response &res) { - nhlog::net()->info("req: {}, res: {}", req.path, res.status); - }); + svr.set_logger([](const Request &req, const Response &res) { + nhlog::net()->info("req: {}, res: {}", req.path, res.status); + }); - svr.Get("/sso", [this](const Request &req, Response &res) { - if (req.has_param("loginToken")) { - auto val = req.get_param_value("loginToken"); - res.set_content("SSO success", "text/plain"); - emit ssoSuccess(val); - } else { - res.set_content("Missing loginToken for SSO login!", "text/plain"); - emit ssoFailed(); - } - }); - - std::thread t([this]() { - this->port = svr.bind_to_any_port("localhost"); - svr.listen_after_bind(); - }); - t.detach(); - - while (!svr.is_running()) { - std::this_thread::sleep_for(std::chrono::milliseconds(1)); + svr.Get("/sso", [this](const Request &req, Response &res) { + if (req.has_param("loginToken")) { + auto val = req.get_param_value("loginToken"); + res.set_content("SSO success", "text/plain"); + emit ssoSuccess(val); + } else { + res.set_content("Missing loginToken for SSO login!", "text/plain"); + emit ssoFailed(); } + }); + + std::thread t([this]() { + this->port = svr.bind_to_any_port("localhost"); + svr.listen_after_bind(); + }); + t.detach(); + + while (!svr.is_running()) { + std::this_thread::sleep_for(std::chrono::milliseconds(1)); + } } SSOHandler::~SSOHandler() { - svr.stop(); - while (svr.is_running()) { - std::this_thread::sleep_for(std::chrono::milliseconds(1)); - } + svr.stop(); + while (svr.is_running()) { + std::this_thread::sleep_for(std::chrono::milliseconds(1)); + } } std::string SSOHandler::url() const { - return "http://localhost:" + std::to_string(port) + "/sso"; + return "http://localhost:" + std::to_string(port) + "/sso"; } diff --git a/src/SSOHandler.h b/src/SSOHandler.h index bd0d424d..ab652a06 100644 --- a/src/SSOHandler.h +++ b/src/SSOHandler.h @@ -9,20 +9,20 @@ class SSOHandler : public QObject { - Q_OBJECT + Q_OBJECT public: - SSOHandler(QObject *parent = nullptr); + SSOHandler(QObject *parent = nullptr); - ~SSOHandler(); + ~SSOHandler(); - std::string url() const; + std::string url() const; signals: - void ssoSuccess(std::string token); - void ssoFailed(); + void ssoSuccess(std::string token); + void ssoFailed(); private: - httplib::Server svr; - int port = 0; + httplib::Server svr; + int port = 0; }; diff --git a/src/SingleImagePackModel.cpp b/src/SingleImagePackModel.cpp index 6d0f0ad9..978a0480 100644 --- a/src/SingleImagePackModel.cpp +++ b/src/SingleImagePackModel.cpp @@ -24,344 +24,336 @@ SingleImagePackModel::SingleImagePackModel(ImagePackInfo pack_, QObject *parent) , old_statekey_(statekey_) , pack(std::move(pack_.pack)) { - [[maybe_unused]] static auto imageInfoType = qRegisterMetaType(); + [[maybe_unused]] static auto imageInfoType = qRegisterMetaType(); - if (!pack.pack) - pack.pack = mtx::events::msc2545::ImagePack::PackDescription{}; + if (!pack.pack) + pack.pack = mtx::events::msc2545::ImagePack::PackDescription{}; - for (const auto &e : pack.images) - shortcodes.push_back(e.first); + for (const auto &e : pack.images) + shortcodes.push_back(e.first); - connect(this, &SingleImagePackModel::addImage, this, &SingleImagePackModel::addImageCb); + connect(this, &SingleImagePackModel::addImage, this, &SingleImagePackModel::addImageCb); } int SingleImagePackModel::rowCount(const QModelIndex &) const { - return (int)shortcodes.size(); + return (int)shortcodes.size(); } QHash SingleImagePackModel::roleNames() const { - return { - {Roles::Url, "url"}, - {Roles::ShortCode, "shortCode"}, - {Roles::Body, "body"}, - {Roles::IsEmote, "isEmote"}, - {Roles::IsSticker, "isSticker"}, - }; + return { + {Roles::Url, "url"}, + {Roles::ShortCode, "shortCode"}, + {Roles::Body, "body"}, + {Roles::IsEmote, "isEmote"}, + {Roles::IsSticker, "isSticker"}, + }; } QVariant SingleImagePackModel::data(const QModelIndex &index, int role) const { - if (hasIndex(index.row(), index.column(), index.parent())) { - const auto &img = pack.images.at(shortcodes.at(index.row())); - switch (role) { - case Url: - return QString::fromStdString(img.url); - case ShortCode: - return QString::fromStdString(shortcodes.at(index.row())); - case Body: - return QString::fromStdString(img.body); - case IsEmote: - return img.overrides_usage() ? img.is_emoji() : pack.pack->is_emoji(); - case IsSticker: - return img.overrides_usage() ? img.is_sticker() : pack.pack->is_sticker(); - default: - return {}; - } + if (hasIndex(index.row(), index.column(), index.parent())) { + const auto &img = pack.images.at(shortcodes.at(index.row())); + switch (role) { + case Url: + return QString::fromStdString(img.url); + case ShortCode: + return QString::fromStdString(shortcodes.at(index.row())); + case Body: + return QString::fromStdString(img.body); + case IsEmote: + return img.overrides_usage() ? img.is_emoji() : pack.pack->is_emoji(); + case IsSticker: + return img.overrides_usage() ? img.is_sticker() : pack.pack->is_sticker(); + default: + return {}; } - return {}; + } + return {}; } bool SingleImagePackModel::setData(const QModelIndex &index, const QVariant &value, int role) { - using mtx::events::msc2545::PackUsage; + using mtx::events::msc2545::PackUsage; - if (hasIndex(index.row(), index.column(), index.parent())) { - auto &img = pack.images.at(shortcodes.at(index.row())); - switch (role) { - case ShortCode: { - auto newCode = value.toString().toStdString(); + if (hasIndex(index.row(), index.column(), index.parent())) { + auto &img = pack.images.at(shortcodes.at(index.row())); + switch (role) { + case ShortCode: { + auto newCode = value.toString().toStdString(); - // otherwise we delete this by accident - if (pack.images.count(newCode)) - return false; + // otherwise we delete this by accident + if (pack.images.count(newCode)) + return false; - auto tmp = img; - auto oldCode = shortcodes.at(index.row()); - pack.images.erase(oldCode); - shortcodes[index.row()] = newCode; - pack.images.insert({newCode, tmp}); + auto tmp = img; + auto oldCode = shortcodes.at(index.row()); + pack.images.erase(oldCode); + shortcodes[index.row()] = newCode; + pack.images.insert({newCode, tmp}); - emit dataChanged( - this->index(index.row()), this->index(index.row()), {Roles::ShortCode}); - return true; - } - case Body: - img.body = value.toString().toStdString(); - emit dataChanged( - this->index(index.row()), this->index(index.row()), {Roles::Body}); - return true; - case IsEmote: { - bool isEmote = value.toBool(); - bool isSticker = - img.overrides_usage() ? img.is_sticker() : pack.pack->is_sticker(); - - img.usage.set(PackUsage::Emoji, isEmote); - img.usage.set(PackUsage::Sticker, isSticker); - - if (img.usage == pack.pack->usage) - img.usage.reset(); - - emit dataChanged( - this->index(index.row()), this->index(index.row()), {Roles::IsEmote}); - - return true; - } - case IsSticker: { - bool isEmote = - img.overrides_usage() ? img.is_emoji() : pack.pack->is_emoji(); - bool isSticker = value.toBool(); - - img.usage.set(PackUsage::Emoji, isEmote); - img.usage.set(PackUsage::Sticker, isSticker); - - if (img.usage == pack.pack->usage) - img.usage.reset(); - - emit dataChanged( - this->index(index.row()), this->index(index.row()), {Roles::IsSticker}); - - return true; - } - } + emit dataChanged( + this->index(index.row()), this->index(index.row()), {Roles::ShortCode}); + return true; } - return false; + case Body: + img.body = value.toString().toStdString(); + emit dataChanged(this->index(index.row()), this->index(index.row()), {Roles::Body}); + return true; + case IsEmote: { + bool isEmote = value.toBool(); + bool isSticker = img.overrides_usage() ? img.is_sticker() : pack.pack->is_sticker(); + + img.usage.set(PackUsage::Emoji, isEmote); + img.usage.set(PackUsage::Sticker, isSticker); + + if (img.usage == pack.pack->usage) + img.usage.reset(); + + emit dataChanged(this->index(index.row()), this->index(index.row()), {Roles::IsEmote}); + + return true; + } + case IsSticker: { + bool isEmote = img.overrides_usage() ? img.is_emoji() : pack.pack->is_emoji(); + bool isSticker = value.toBool(); + + img.usage.set(PackUsage::Emoji, isEmote); + img.usage.set(PackUsage::Sticker, isSticker); + + if (img.usage == pack.pack->usage) + img.usage.reset(); + + emit dataChanged( + this->index(index.row()), this->index(index.row()), {Roles::IsSticker}); + + return true; + } + } + } + return false; } bool SingleImagePackModel::isGloballyEnabled() const { - if (auto roomPacks = - cache::client()->getAccountData(mtx::events::EventType::ImagePackRooms)) { - if (auto tmp = std::get_if< - mtx::events::EphemeralEvent>( - &*roomPacks)) { - if (tmp->content.rooms.count(roomid_) && - tmp->content.rooms.at(roomid_).count(statekey_)) - return true; - } + if (auto roomPacks = cache::client()->getAccountData(mtx::events::EventType::ImagePackRooms)) { + if (auto tmp = + std::get_if>( + &*roomPacks)) { + if (tmp->content.rooms.count(roomid_) && + tmp->content.rooms.at(roomid_).count(statekey_)) + return true; } - return false; + } + return false; } void SingleImagePackModel::setGloballyEnabled(bool enabled) { - mtx::events::msc2545::ImagePackRooms content{}; - if (auto roomPacks = - cache::client()->getAccountData(mtx::events::EventType::ImagePackRooms)) { - if (auto tmp = std::get_if< - mtx::events::EphemeralEvent>( - &*roomPacks)) { - content = tmp->content; - } + mtx::events::msc2545::ImagePackRooms content{}; + if (auto roomPacks = cache::client()->getAccountData(mtx::events::EventType::ImagePackRooms)) { + if (auto tmp = + std::get_if>( + &*roomPacks)) { + content = tmp->content; } + } - if (enabled) - content.rooms[roomid_][statekey_] = {}; - else - content.rooms[roomid_].erase(statekey_); + if (enabled) + content.rooms[roomid_][statekey_] = {}; + else + content.rooms[roomid_].erase(statekey_); - http::client()->put_account_data(content, [](mtx::http::RequestErr) { - // emit this->globallyEnabledChanged(); - }); + http::client()->put_account_data(content, [](mtx::http::RequestErr) { + // emit this->globallyEnabledChanged(); + }); } bool SingleImagePackModel::canEdit() const { - if (roomid_.empty()) - return true; - else - return Permissions(QString::fromStdString(roomid_)) - .canChange(qml_mtx_events::ImagePackInRoom); + if (roomid_.empty()) + return true; + else + return Permissions(QString::fromStdString(roomid_)) + .canChange(qml_mtx_events::ImagePackInRoom); } void SingleImagePackModel::setPackname(QString val) { - auto val_ = val.toStdString(); - if (val_ != this->pack.pack->display_name) { - this->pack.pack->display_name = val_; - emit packnameChanged(); - } + auto val_ = val.toStdString(); + if (val_ != this->pack.pack->display_name) { + this->pack.pack->display_name = val_; + emit packnameChanged(); + } } void SingleImagePackModel::setAttribution(QString val) { - auto val_ = val.toStdString(); - if (val_ != this->pack.pack->attribution) { - this->pack.pack->attribution = val_; - emit attributionChanged(); - } + auto val_ = val.toStdString(); + if (val_ != this->pack.pack->attribution) { + this->pack.pack->attribution = val_; + emit attributionChanged(); + } } void SingleImagePackModel::setAvatarUrl(QString val) { - auto val_ = val.toStdString(); - if (val_ != this->pack.pack->avatar_url) { - this->pack.pack->avatar_url = val_; - emit avatarUrlChanged(); - } + auto val_ = val.toStdString(); + if (val_ != this->pack.pack->avatar_url) { + this->pack.pack->avatar_url = val_; + emit avatarUrlChanged(); + } } void SingleImagePackModel::setStatekey(QString val) { - auto val_ = val.toStdString(); - if (val_ != statekey_) { - statekey_ = val_; - emit statekeyChanged(); - } + auto val_ = val.toStdString(); + if (val_ != statekey_) { + statekey_ = val_; + emit statekeyChanged(); + } } void SingleImagePackModel::setIsStickerPack(bool val) { - using mtx::events::msc2545::PackUsage; - if (val != pack.pack->is_sticker()) { - pack.pack->usage.set(PackUsage::Sticker, val); - emit isStickerPackChanged(); - } + using mtx::events::msc2545::PackUsage; + if (val != pack.pack->is_sticker()) { + pack.pack->usage.set(PackUsage::Sticker, val); + emit isStickerPackChanged(); + } } void SingleImagePackModel::setIsEmotePack(bool val) { - using mtx::events::msc2545::PackUsage; - if (val != pack.pack->is_emoji()) { - pack.pack->usage.set(PackUsage::Emoji, val); - emit isEmotePackChanged(); - } + using mtx::events::msc2545::PackUsage; + if (val != pack.pack->is_emoji()) { + pack.pack->usage.set(PackUsage::Emoji, val); + emit isEmotePackChanged(); + } } void SingleImagePackModel::save() { - if (roomid_.empty()) { - http::client()->put_account_data(pack, [](mtx::http::RequestErr e) { - if (e) - ChatPage::instance()->showNotification( - tr("Failed to update image pack: %1") - .arg(QString::fromStdString(e->matrix_error.error))); - }); - } else { - if (old_statekey_ != statekey_) { - http::client()->send_state_event( - roomid_, - to_string(mtx::events::EventType::ImagePackInRoom), - old_statekey_, - nlohmann::json::object(), - [](const mtx::responses::EventId &, mtx::http::RequestErr e) { - if (e) - ChatPage::instance()->showNotification( - tr("Failed to delete old image pack: %1") - .arg(QString::fromStdString(e->matrix_error.error))); - }); - } - - http::client()->send_state_event( - roomid_, - statekey_, - pack, - [this](const mtx::responses::EventId &, mtx::http::RequestErr e) { - if (e) - ChatPage::instance()->showNotification( - tr("Failed to update image pack: %1") - .arg(QString::fromStdString(e->matrix_error.error))); - - nhlog::net()->info("Uploaded image pack: %1", statekey_); - }); + if (roomid_.empty()) { + http::client()->put_account_data(pack, [](mtx::http::RequestErr e) { + if (e) + ChatPage::instance()->showNotification( + tr("Failed to update image pack: %1") + .arg(QString::fromStdString(e->matrix_error.error))); + }); + } else { + if (old_statekey_ != statekey_) { + http::client()->send_state_event( + roomid_, + to_string(mtx::events::EventType::ImagePackInRoom), + old_statekey_, + nlohmann::json::object(), + [](const mtx::responses::EventId &, mtx::http::RequestErr e) { + if (e) + ChatPage::instance()->showNotification( + tr("Failed to delete old image pack: %1") + .arg(QString::fromStdString(e->matrix_error.error))); + }); } + + http::client()->send_state_event( + roomid_, + statekey_, + pack, + [this](const mtx::responses::EventId &, mtx::http::RequestErr e) { + if (e) + ChatPage::instance()->showNotification( + tr("Failed to update image pack: %1") + .arg(QString::fromStdString(e->matrix_error.error))); + + nhlog::net()->info("Uploaded image pack: %1", statekey_); + }); + } } void SingleImagePackModel::addStickers(QList files) { - for (const auto &f : files) { - auto file = QFile(f.toLocalFile()); - if (!file.open(QFile::ReadOnly)) { - ChatPage::instance()->showNotification( - tr("Failed to open image: %1").arg(f.toLocalFile())); - return; - } - - auto bytes = file.readAll(); - auto img = utils::readImage(bytes); - - mtx::common::ImageInfo info{}; - - auto sz = img.size() / 2; - if (sz.width() > 512 || sz.height() > 512) { - sz.scale(512, 512, Qt::AspectRatioMode::KeepAspectRatio); - } else if (img.height() < 128 && img.width() < 128) { - sz = img.size(); - } - - info.h = sz.height(); - info.w = sz.width(); - info.size = bytes.size(); - info.mimetype = - QMimeDatabase().mimeTypeForFile(f.toLocalFile()).name().toStdString(); - - auto filename = f.fileName().toStdString(); - http::client()->upload( - bytes.toStdString(), - QMimeDatabase().mimeTypeForFile(f.toLocalFile()).name().toStdString(), - filename, - [this, filename, info](const mtx::responses::ContentURI &uri, - mtx::http::RequestErr e) { - if (e) { - ChatPage::instance()->showNotification( - tr("Failed to upload image: %1") - .arg(QString::fromStdString(e->matrix_error.error))); - return; - } - - emit addImage(uri.content_uri, filename, info); - }); + for (const auto &f : files) { + auto file = QFile(f.toLocalFile()); + if (!file.open(QFile::ReadOnly)) { + ChatPage::instance()->showNotification( + tr("Failed to open image: %1").arg(f.toLocalFile())); + return; } + + auto bytes = file.readAll(); + auto img = utils::readImage(bytes); + + mtx::common::ImageInfo info{}; + + auto sz = img.size() / 2; + if (sz.width() > 512 || sz.height() > 512) { + sz.scale(512, 512, Qt::AspectRatioMode::KeepAspectRatio); + } else if (img.height() < 128 && img.width() < 128) { + sz = img.size(); + } + + info.h = sz.height(); + info.w = sz.width(); + info.size = bytes.size(); + info.mimetype = QMimeDatabase().mimeTypeForFile(f.toLocalFile()).name().toStdString(); + + auto filename = f.fileName().toStdString(); + http::client()->upload( + bytes.toStdString(), + QMimeDatabase().mimeTypeForFile(f.toLocalFile()).name().toStdString(), + filename, + [this, filename, info](const mtx::responses::ContentURI &uri, mtx::http::RequestErr e) { + if (e) { + ChatPage::instance()->showNotification( + tr("Failed to upload image: %1") + .arg(QString::fromStdString(e->matrix_error.error))); + return; + } + + emit addImage(uri.content_uri, filename, info); + }); + } } void SingleImagePackModel::remove(int idx) { - if (idx < (int)shortcodes.size() && idx >= 0) { - beginRemoveRows(QModelIndex(), idx, idx); - auto s = shortcodes.at(idx); - shortcodes.erase(shortcodes.begin() + idx); - pack.images.erase(s); - endRemoveRows(); - } + if (idx < (int)shortcodes.size() && idx >= 0) { + beginRemoveRows(QModelIndex(), idx, idx); + auto s = shortcodes.at(idx); + shortcodes.erase(shortcodes.begin() + idx); + pack.images.erase(s); + endRemoveRows(); + } } void SingleImagePackModel::addImageCb(std::string uri, std::string filename, mtx::common::ImageInfo info) { - mtx::events::msc2545::PackImage img{}; - img.url = uri; - img.info = info; - beginInsertRows( - QModelIndex(), static_cast(shortcodes.size()), static_cast(shortcodes.size())); + mtx::events::msc2545::PackImage img{}; + img.url = uri; + img.info = info; + beginInsertRows( + QModelIndex(), static_cast(shortcodes.size()), static_cast(shortcodes.size())); - pack.images[filename] = img; - shortcodes.push_back(filename); + pack.images[filename] = img; + shortcodes.push_back(filename); - endInsertRows(); + endInsertRows(); } diff --git a/src/SingleImagePackModel.h b/src/SingleImagePackModel.h index 60138d36..cd8b0547 100644 --- a/src/SingleImagePackModel.h +++ b/src/SingleImagePackModel.h @@ -14,81 +14,78 @@ class SingleImagePackModel : public QAbstractListModel { - Q_OBJECT + Q_OBJECT - Q_PROPERTY(QString roomid READ roomid CONSTANT) - Q_PROPERTY(QString statekey READ statekey WRITE setStatekey NOTIFY statekeyChanged) - Q_PROPERTY( - QString attribution READ attribution WRITE setAttribution NOTIFY attributionChanged) - Q_PROPERTY(QString packname READ packname WRITE setPackname NOTIFY packnameChanged) - Q_PROPERTY(QString avatarUrl READ avatarUrl WRITE setAvatarUrl NOTIFY avatarUrlChanged) - Q_PROPERTY( - bool isStickerPack READ isStickerPack WRITE setIsStickerPack NOTIFY isStickerPackChanged) - Q_PROPERTY(bool isEmotePack READ isEmotePack WRITE setIsEmotePack NOTIFY isEmotePackChanged) - Q_PROPERTY(bool isGloballyEnabled READ isGloballyEnabled WRITE setGloballyEnabled NOTIFY - globallyEnabledChanged) - Q_PROPERTY(bool canEdit READ canEdit CONSTANT) + Q_PROPERTY(QString roomid READ roomid CONSTANT) + Q_PROPERTY(QString statekey READ statekey WRITE setStatekey NOTIFY statekeyChanged) + Q_PROPERTY(QString attribution READ attribution WRITE setAttribution NOTIFY attributionChanged) + Q_PROPERTY(QString packname READ packname WRITE setPackname NOTIFY packnameChanged) + Q_PROPERTY(QString avatarUrl READ avatarUrl WRITE setAvatarUrl NOTIFY avatarUrlChanged) + Q_PROPERTY( + bool isStickerPack READ isStickerPack WRITE setIsStickerPack NOTIFY isStickerPackChanged) + Q_PROPERTY(bool isEmotePack READ isEmotePack WRITE setIsEmotePack NOTIFY isEmotePackChanged) + Q_PROPERTY(bool isGloballyEnabled READ isGloballyEnabled WRITE setGloballyEnabled NOTIFY + globallyEnabledChanged) + Q_PROPERTY(bool canEdit READ canEdit CONSTANT) public: - enum Roles - { - Url = Qt::UserRole, - ShortCode, - Body, - IsEmote, - IsSticker, - }; - Q_ENUM(Roles); + enum Roles + { + Url = Qt::UserRole, + ShortCode, + Body, + IsEmote, + IsSticker, + }; + Q_ENUM(Roles); - SingleImagePackModel(ImagePackInfo pack_, QObject *parent = nullptr); - QHash roleNames() const override; - int rowCount(const QModelIndex &parent = QModelIndex()) const override; - QVariant data(const QModelIndex &index, int role) const override; - bool setData(const QModelIndex &index, - const QVariant &value, - int role = Qt::EditRole) override; + SingleImagePackModel(ImagePackInfo pack_, QObject *parent = nullptr); + QHash roleNames() const override; + int rowCount(const QModelIndex &parent = QModelIndex()) const override; + QVariant data(const QModelIndex &index, int role) const override; + bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) override; - QString roomid() const { return QString::fromStdString(roomid_); } - QString statekey() const { return QString::fromStdString(statekey_); } - QString packname() const { return QString::fromStdString(pack.pack->display_name); } - QString attribution() const { return QString::fromStdString(pack.pack->attribution); } - QString avatarUrl() const { return QString::fromStdString(pack.pack->avatar_url); } - bool isStickerPack() const { return pack.pack->is_sticker(); } - bool isEmotePack() const { return pack.pack->is_emoji(); } + QString roomid() const { return QString::fromStdString(roomid_); } + QString statekey() const { return QString::fromStdString(statekey_); } + QString packname() const { return QString::fromStdString(pack.pack->display_name); } + QString attribution() const { return QString::fromStdString(pack.pack->attribution); } + QString avatarUrl() const { return QString::fromStdString(pack.pack->avatar_url); } + bool isStickerPack() const { return pack.pack->is_sticker(); } + bool isEmotePack() const { return pack.pack->is_emoji(); } - bool isGloballyEnabled() const; - bool canEdit() const; - void setGloballyEnabled(bool enabled); + bool isGloballyEnabled() const; + bool canEdit() const; + void setGloballyEnabled(bool enabled); - void setPackname(QString val); - void setAttribution(QString val); - void setAvatarUrl(QString val); - void setStatekey(QString val); - void setIsStickerPack(bool val); - void setIsEmotePack(bool val); + void setPackname(QString val); + void setAttribution(QString val); + void setAvatarUrl(QString val); + void setStatekey(QString val); + void setIsStickerPack(bool val); + void setIsEmotePack(bool val); - Q_INVOKABLE void save(); - Q_INVOKABLE void addStickers(QList files); - Q_INVOKABLE void remove(int index); + Q_INVOKABLE void save(); + Q_INVOKABLE void addStickers(QList files); + Q_INVOKABLE void remove(int index); signals: - void globallyEnabledChanged(); - void statekeyChanged(); - void attributionChanged(); - void packnameChanged(); - void avatarUrlChanged(); - void isEmotePackChanged(); - void isStickerPackChanged(); + void globallyEnabledChanged(); + void statekeyChanged(); + void attributionChanged(); + void packnameChanged(); + void avatarUrlChanged(); + void isEmotePackChanged(); + void isStickerPackChanged(); - void addImage(std::string uri, std::string filename, mtx::common::ImageInfo info); + void addImage(std::string uri, std::string filename, mtx::common::ImageInfo info); private slots: - void addImageCb(std::string uri, std::string filename, mtx::common::ImageInfo info); + void addImageCb(std::string uri, std::string filename, mtx::common::ImageInfo info); private: - std::string roomid_; - std::string statekey_, old_statekey_; + std::string roomid_; + std::string statekey_, old_statekey_; - mtx::events::msc2545::ImagePack pack; - std::vector shortcodes; + mtx::events::msc2545::ImagePack pack; + std::vector shortcodes; }; diff --git a/src/TrayIcon.cpp b/src/TrayIcon.cpp index db0130c8..98a1d242 100644 --- a/src/TrayIcon.cpp +++ b/src/TrayIcon.cpp @@ -19,7 +19,7 @@ MsgCountComposedIcon::MsgCountComposedIcon(const QString &filename) : QIconEngine() { - icon_ = QIcon(filename); + icon_ = QIcon(filename); } void @@ -28,95 +28,95 @@ MsgCountComposedIcon::paint(QPainter *painter, QIcon::Mode mode, QIcon::State state) { - painter->setRenderHint(QPainter::TextAntialiasing); - painter->setRenderHint(QPainter::SmoothPixmapTransform); - painter->setRenderHint(QPainter::Antialiasing); + painter->setRenderHint(QPainter::TextAntialiasing); + painter->setRenderHint(QPainter::SmoothPixmapTransform); + painter->setRenderHint(QPainter::Antialiasing); - icon_.paint(painter, rect, Qt::AlignCenter, mode, state); + icon_.paint(painter, rect, Qt::AlignCenter, mode, state); - if (msgCount <= 0) - return; + if (msgCount <= 0) + return; - QColor backgroundColor("red"); - QColor textColor("white"); + QColor backgroundColor("red"); + QColor textColor("white"); - QBrush brush; - brush.setStyle(Qt::SolidPattern); - brush.setColor(backgroundColor); + QBrush brush; + brush.setStyle(Qt::SolidPattern); + brush.setColor(backgroundColor); - QFont f; - f.setPointSizeF(8); - f.setWeight(QFont::Thin); + QFont f; + f.setPointSizeF(8); + f.setWeight(QFont::Thin); - painter->setBrush(brush); - painter->setPen(Qt::NoPen); - painter->setFont(f); + painter->setBrush(brush); + painter->setPen(Qt::NoPen); + painter->setFont(f); - QRectF bubble(rect.width() - BubbleDiameter, - rect.height() - BubbleDiameter, - BubbleDiameter, - BubbleDiameter); - painter->drawEllipse(bubble); - painter->setPen(QPen(textColor)); - painter->setBrush(Qt::NoBrush); - painter->drawText(bubble, Qt::AlignCenter, QString::number(msgCount)); + QRectF bubble(rect.width() - BubbleDiameter, + rect.height() - BubbleDiameter, + BubbleDiameter, + BubbleDiameter); + painter->drawEllipse(bubble); + painter->setPen(QPen(textColor)); + painter->setBrush(Qt::NoBrush); + painter->drawText(bubble, Qt::AlignCenter, QString::number(msgCount)); } QIconEngine * MsgCountComposedIcon::clone() const { - return new MsgCountComposedIcon(*this); + return new MsgCountComposedIcon(*this); } QList MsgCountComposedIcon::availableSizes(QIcon::Mode mode, QIcon::State state) const { - Q_UNUSED(mode); - Q_UNUSED(state); - QList sizes; - sizes.append(QSize(24, 24)); - sizes.append(QSize(32, 32)); - sizes.append(QSize(48, 48)); - sizes.append(QSize(64, 64)); - sizes.append(QSize(128, 128)); - sizes.append(QSize(256, 256)); - return sizes; + Q_UNUSED(mode); + Q_UNUSED(state); + QList sizes; + sizes.append(QSize(24, 24)); + sizes.append(QSize(32, 32)); + sizes.append(QSize(48, 48)); + sizes.append(QSize(64, 64)); + sizes.append(QSize(128, 128)); + sizes.append(QSize(256, 256)); + return sizes; } QPixmap MsgCountComposedIcon::pixmap(const QSize &size, QIcon::Mode mode, QIcon::State state) { - QImage img(size, QImage::Format_ARGB32); - img.fill(qRgba(0, 0, 0, 0)); - QPixmap result = QPixmap::fromImage(img, Qt::NoFormatConversion); - { - QPainter painter(&result); - paint(&painter, QRect(QPoint(0, 0), size), mode, state); - } - return result; + QImage img(size, QImage::Format_ARGB32); + img.fill(qRgba(0, 0, 0, 0)); + QPixmap result = QPixmap::fromImage(img, Qt::NoFormatConversion); + { + QPainter painter(&result); + paint(&painter, QRect(QPoint(0, 0), size), mode, state); + } + return result; } TrayIcon::TrayIcon(const QString &filename, QWidget *parent) : QSystemTrayIcon(parent) { #if defined(Q_OS_MAC) || defined(Q_OS_WIN) - setIcon(QIcon(filename)); + setIcon(QIcon(filename)); #else - icon_ = new MsgCountComposedIcon(filename); - setIcon(QIcon(icon_)); + icon_ = new MsgCountComposedIcon(filename); + setIcon(QIcon(icon_)); #endif - QMenu *menu = new QMenu(parent); - setContextMenu(menu); + QMenu *menu = new QMenu(parent); + setContextMenu(menu); - viewAction_ = new QAction(tr("Show"), this); - quitAction_ = new QAction(tr("Quit"), this); + viewAction_ = new QAction(tr("Show"), this); + quitAction_ = new QAction(tr("Quit"), this); - connect(viewAction_, SIGNAL(triggered()), parent, SLOT(show())); - connect(quitAction_, &QAction::triggered, this, QApplication::quit); + connect(viewAction_, SIGNAL(triggered()), parent, SLOT(show())); + connect(quitAction_, &QAction::triggered, this, QApplication::quit); - menu->addAction(viewAction_); - menu->addAction(quitAction_); + menu->addAction(viewAction_); + menu->addAction(quitAction_); } void @@ -127,25 +127,25 @@ TrayIcon::setUnreadCount(int count) // currently, to avoid writing obj-c code, ignore deprecated warnings on the badge functions #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wdeprecated-declarations" - auto labelText = count == 0 ? "" : QString::number(count); + auto labelText = count == 0 ? "" : QString::number(count); - if (labelText == QtMac::badgeLabelText()) - return; + if (labelText == QtMac::badgeLabelText()) + return; - QtMac::setBadgeLabelText(labelText); + QtMac::setBadgeLabelText(labelText); #pragma clang diagnostic pop #elif defined(Q_OS_WIN) // FIXME: Find a way to use Windows apis for the badge counter (if any). #else - if (count == icon_->msgCount) - return; + if (count == icon_->msgCount) + return; - // Custom drawing on Linux. - MsgCountComposedIcon *tmp = static_cast(icon_->clone()); - tmp->msgCount = count; + // Custom drawing on Linux. + MsgCountComposedIcon *tmp = static_cast(icon_->clone()); + tmp->msgCount = count; - setIcon(QIcon(tmp)); + setIcon(QIcon(tmp)); - icon_ = tmp; + icon_ = tmp; #endif } diff --git a/src/TrayIcon.h b/src/TrayIcon.h index 10dfafc5..1ce7fb0b 100644 --- a/src/TrayIcon.h +++ b/src/TrayIcon.h @@ -16,33 +16,33 @@ class QPainter; class MsgCountComposedIcon : public QIconEngine { public: - MsgCountComposedIcon(const QString &filename); + MsgCountComposedIcon(const QString &filename); - void paint(QPainter *p, const QRect &rect, QIcon::Mode mode, QIcon::State state) override; - QIconEngine *clone() const override; - QList availableSizes(QIcon::Mode mode, QIcon::State state) const override; - QPixmap pixmap(const QSize &size, QIcon::Mode mode, QIcon::State state) override; + void paint(QPainter *p, const QRect &rect, QIcon::Mode mode, QIcon::State state) override; + QIconEngine *clone() const override; + QList availableSizes(QIcon::Mode mode, QIcon::State state) const override; + QPixmap pixmap(const QSize &size, QIcon::Mode mode, QIcon::State state) override; - int msgCount = 0; + int msgCount = 0; private: - const int BubbleDiameter = 17; + const int BubbleDiameter = 17; - QIcon icon_; + QIcon icon_; }; class TrayIcon : public QSystemTrayIcon { - Q_OBJECT + Q_OBJECT public: - TrayIcon(const QString &filename, QWidget *parent); + TrayIcon(const QString &filename, QWidget *parent); public slots: - void setUnreadCount(int count); + void setUnreadCount(int count); private: - QAction *viewAction_; - QAction *quitAction_; + QAction *viewAction_; + QAction *quitAction_; - MsgCountComposedIcon *icon_; + MsgCountComposedIcon *icon_; }; diff --git a/src/UserSettingsPage.cpp b/src/UserSettingsPage.cpp index 7b01b0b8..cc1f8206 100644 --- a/src/UserSettingsPage.cpp +++ b/src/UserSettingsPage.cpp @@ -41,1499 +41,1484 @@ QSharedPointer UserSettings::instance_; UserSettings::UserSettings() { - connect(QCoreApplication::instance(), &QCoreApplication::aboutToQuit, []() { - instance_.clear(); - }); + connect( + QCoreApplication::instance(), &QCoreApplication::aboutToQuit, []() { instance_.clear(); }); } QSharedPointer UserSettings::instance() { - return instance_; + return instance_; } void UserSettings::initialize(std::optional profile) { - instance_.reset(new UserSettings()); - instance_->load(profile); + instance_.reset(new UserSettings()); + instance_->load(profile); } void UserSettings::load(std::optional profile) { - tray_ = settings.value("user/window/tray", false).toBool(); - startInTray_ = settings.value("user/window/start_in_tray", false).toBool(); + tray_ = settings.value("user/window/tray", false).toBool(); + startInTray_ = settings.value("user/window/start_in_tray", false).toBool(); - roomListWidth_ = settings.value("user/sidebar/room_list_width", -1).toInt(); - communityListWidth_ = settings.value("user/sidebar/community_list_width", -1).toInt(); + roomListWidth_ = settings.value("user/sidebar/room_list_width", -1).toInt(); + communityListWidth_ = settings.value("user/sidebar/community_list_width", -1).toInt(); - hasDesktopNotifications_ = settings.value("user/desktop_notifications", true).toBool(); - hasAlertOnNotification_ = settings.value("user/alert_on_notification", false).toBool(); - groupView_ = settings.value("user/group_view", true).toBool(); - hiddenTags_ = settings.value("user/hidden_tags", QStringList{}).toStringList(); - buttonsInTimeline_ = settings.value("user/timeline/buttons", true).toBool(); - timelineMaxWidth_ = settings.value("user/timeline/max_width", 0).toInt(); - messageHoverHighlight_ = - settings.value("user/timeline/message_hover_highlight", false).toBool(); - enlargeEmojiOnlyMessages_ = - settings.value("user/timeline/enlarge_emoji_only_msg", false).toBool(); - markdown_ = settings.value("user/markdown_enabled", true).toBool(); - animateImagesOnHover_ = settings.value("user/animate_images_on_hover", false).toBool(); - typingNotifications_ = settings.value("user/typing_notifications", true).toBool(); - sortByImportance_ = settings.value("user/sort_by_unread", true).toBool(); - readReceipts_ = settings.value("user/read_receipts", true).toBool(); - theme_ = settings.value("user/theme", defaultTheme_).toString(); - font_ = settings.value("user/font_family", "default").toString(); - avatarCircles_ = settings.value("user/avatar_circles", true).toBool(); - useIdenticon_ = settings.value("user/use_identicon", true).toBool(); - decryptSidebar_ = settings.value("user/decrypt_sidebar", true).toBool(); - privacyScreen_ = settings.value("user/privacy_screen", false).toBool(); - privacyScreenTimeout_ = settings.value("user/privacy_screen_timeout", 0).toInt(); - mobileMode_ = settings.value("user/mobile_mode", false).toBool(); - emojiFont_ = settings.value("user/emoji_font_family", "default").toString(); - baseFontSize_ = settings.value("user/font_size", QFont().pointSizeF()).toDouble(); - auto tempPresence = settings.value("user/presence", "").toString().toStdString(); - auto presenceValue = QMetaEnum::fromType().keyToValue(tempPresence.c_str()); - if (presenceValue < 0) - presenceValue = 0; - presence_ = static_cast(presenceValue); - ringtone_ = settings.value("user/ringtone", "Default").toString(); - microphone_ = settings.value("user/microphone", QString()).toString(); - camera_ = settings.value("user/camera", QString()).toString(); - cameraResolution_ = settings.value("user/camera_resolution", QString()).toString(); - cameraFrameRate_ = settings.value("user/camera_frame_rate", QString()).toString(); - screenShareFrameRate_ = settings.value("user/screen_share_frame_rate", 5).toInt(); - screenSharePiP_ = settings.value("user/screen_share_pip", true).toBool(); - screenShareRemoteVideo_ = settings.value("user/screen_share_remote_video", false).toBool(); - screenShareHideCursor_ = settings.value("user/screen_share_hide_cursor", false).toBool(); - useStunServer_ = settings.value("user/use_stun_server", false).toBool(); + hasDesktopNotifications_ = settings.value("user/desktop_notifications", true).toBool(); + hasAlertOnNotification_ = settings.value("user/alert_on_notification", false).toBool(); + groupView_ = settings.value("user/group_view", true).toBool(); + hiddenTags_ = settings.value("user/hidden_tags", QStringList{}).toStringList(); + buttonsInTimeline_ = settings.value("user/timeline/buttons", true).toBool(); + timelineMaxWidth_ = settings.value("user/timeline/max_width", 0).toInt(); + messageHoverHighlight_ = + settings.value("user/timeline/message_hover_highlight", false).toBool(); + enlargeEmojiOnlyMessages_ = + settings.value("user/timeline/enlarge_emoji_only_msg", false).toBool(); + markdown_ = settings.value("user/markdown_enabled", true).toBool(); + animateImagesOnHover_ = settings.value("user/animate_images_on_hover", false).toBool(); + typingNotifications_ = settings.value("user/typing_notifications", true).toBool(); + sortByImportance_ = settings.value("user/sort_by_unread", true).toBool(); + readReceipts_ = settings.value("user/read_receipts", true).toBool(); + theme_ = settings.value("user/theme", defaultTheme_).toString(); + font_ = settings.value("user/font_family", "default").toString(); + avatarCircles_ = settings.value("user/avatar_circles", true).toBool(); + useIdenticon_ = settings.value("user/use_identicon", true).toBool(); + decryptSidebar_ = settings.value("user/decrypt_sidebar", true).toBool(); + privacyScreen_ = settings.value("user/privacy_screen", false).toBool(); + privacyScreenTimeout_ = settings.value("user/privacy_screen_timeout", 0).toInt(); + mobileMode_ = settings.value("user/mobile_mode", false).toBool(); + emojiFont_ = settings.value("user/emoji_font_family", "default").toString(); + baseFontSize_ = settings.value("user/font_size", QFont().pointSizeF()).toDouble(); + auto tempPresence = settings.value("user/presence", "").toString().toStdString(); + auto presenceValue = QMetaEnum::fromType().keyToValue(tempPresence.c_str()); + if (presenceValue < 0) + presenceValue = 0; + presence_ = static_cast(presenceValue); + ringtone_ = settings.value("user/ringtone", "Default").toString(); + microphone_ = settings.value("user/microphone", QString()).toString(); + camera_ = settings.value("user/camera", QString()).toString(); + cameraResolution_ = settings.value("user/camera_resolution", QString()).toString(); + cameraFrameRate_ = settings.value("user/camera_frame_rate", QString()).toString(); + screenShareFrameRate_ = settings.value("user/screen_share_frame_rate", 5).toInt(); + screenSharePiP_ = settings.value("user/screen_share_pip", true).toBool(); + screenShareRemoteVideo_ = settings.value("user/screen_share_remote_video", false).toBool(); + screenShareHideCursor_ = settings.value("user/screen_share_hide_cursor", false).toBool(); + useStunServer_ = settings.value("user/use_stun_server", false).toBool(); - if (profile) // set to "" if it's the default to maintain compatibility - profile_ = (*profile == "default") ? "" : *profile; - else - profile_ = settings.value("user/currentProfile", "").toString(); + if (profile) // set to "" if it's the default to maintain compatibility + profile_ = (*profile == "default") ? "" : *profile; + else + profile_ = settings.value("user/currentProfile", "").toString(); - QString prefix = - (profile_ != "" && profile_ != "default") ? "profile/" + profile_ + "/" : ""; - accessToken_ = settings.value(prefix + "auth/access_token", "").toString(); - homeserver_ = settings.value(prefix + "auth/home_server", "").toString(); - userId_ = settings.value(prefix + "auth/user_id", "").toString(); - deviceId_ = settings.value(prefix + "auth/device_id", "").toString(); + QString prefix = (profile_ != "" && profile_ != "default") ? "profile/" + profile_ + "/" : ""; + accessToken_ = settings.value(prefix + "auth/access_token", "").toString(); + homeserver_ = settings.value(prefix + "auth/home_server", "").toString(); + userId_ = settings.value(prefix + "auth/user_id", "").toString(); + deviceId_ = settings.value(prefix + "auth/device_id", "").toString(); - shareKeysWithTrustedUsers_ = - settings.value(prefix + "user/automatically_share_keys_with_trusted_users", false) - .toBool(); - onlyShareKeysWithVerifiedUsers_ = - settings.value(prefix + "user/only_share_keys_with_verified_users", false).toBool(); - useOnlineKeyBackup_ = settings.value(prefix + "user/online_key_backup", false).toBool(); + shareKeysWithTrustedUsers_ = + settings.value(prefix + "user/automatically_share_keys_with_trusted_users", false).toBool(); + onlyShareKeysWithVerifiedUsers_ = + settings.value(prefix + "user/only_share_keys_with_verified_users", false).toBool(); + useOnlineKeyBackup_ = settings.value(prefix + "user/online_key_backup", false).toBool(); - disableCertificateValidation_ = - settings.value("disable_certificate_validation", false).toBool(); + disableCertificateValidation_ = + settings.value("disable_certificate_validation", false).toBool(); - applyTheme(); + applyTheme(); } void UserSettings::setMessageHoverHighlight(bool state) { - if (state == messageHoverHighlight_) - return; - messageHoverHighlight_ = state; - emit messageHoverHighlightChanged(state); - save(); + if (state == messageHoverHighlight_) + return; + messageHoverHighlight_ = state; + emit messageHoverHighlightChanged(state); + save(); } void UserSettings::setEnlargeEmojiOnlyMessages(bool state) { - if (state == enlargeEmojiOnlyMessages_) - return; - enlargeEmojiOnlyMessages_ = state; - emit enlargeEmojiOnlyMessagesChanged(state); - save(); + if (state == enlargeEmojiOnlyMessages_) + return; + enlargeEmojiOnlyMessages_ = state; + emit enlargeEmojiOnlyMessagesChanged(state); + save(); } void UserSettings::setTray(bool state) { - if (state == tray_) - return; - tray_ = state; - emit trayChanged(state); - save(); + if (state == tray_) + return; + tray_ = state; + emit trayChanged(state); + save(); } void UserSettings::setStartInTray(bool state) { - if (state == startInTray_) - return; - startInTray_ = state; - emit startInTrayChanged(state); - save(); + if (state == startInTray_) + return; + startInTray_ = state; + emit startInTrayChanged(state); + save(); } void UserSettings::setMobileMode(bool state) { - if (state == mobileMode_) - return; - mobileMode_ = state; - emit mobileModeChanged(state); - save(); + if (state == mobileMode_) + return; + mobileMode_ = state; + emit mobileModeChanged(state); + save(); } void UserSettings::setGroupView(bool state) { - if (groupView_ == state) - return; + if (groupView_ == state) + return; - groupView_ = state; - emit groupViewStateChanged(state); - save(); + groupView_ = state; + emit groupViewStateChanged(state); + save(); } void UserSettings::setHiddenTags(QStringList hiddenTags) { - hiddenTags_ = hiddenTags; - save(); + hiddenTags_ = hiddenTags; + save(); } void UserSettings::setMarkdown(bool state) { - if (state == markdown_) - return; - markdown_ = state; - emit markdownChanged(state); - save(); + if (state == markdown_) + return; + markdown_ = state; + emit markdownChanged(state); + save(); } void UserSettings::setAnimateImagesOnHover(bool state) { - if (state == animateImagesOnHover_) - return; - animateImagesOnHover_ = state; - emit animateImagesOnHoverChanged(state); - save(); + if (state == animateImagesOnHover_) + return; + animateImagesOnHover_ = state; + emit animateImagesOnHoverChanged(state); + save(); } void UserSettings::setReadReceipts(bool state) { - if (state == readReceipts_) - return; - readReceipts_ = state; - emit readReceiptsChanged(state); - save(); + if (state == readReceipts_) + return; + readReceipts_ = state; + emit readReceiptsChanged(state); + save(); } void UserSettings::setTypingNotifications(bool state) { - if (state == typingNotifications_) - return; - typingNotifications_ = state; - emit typingNotificationsChanged(state); - save(); + if (state == typingNotifications_) + return; + typingNotifications_ = state; + emit typingNotificationsChanged(state); + save(); } void UserSettings::setSortByImportance(bool state) { - if (state == sortByImportance_) - return; - sortByImportance_ = state; - emit roomSortingChanged(state); - save(); + if (state == sortByImportance_) + return; + sortByImportance_ = state; + emit roomSortingChanged(state); + save(); } void UserSettings::setButtonsInTimeline(bool state) { - if (state == buttonsInTimeline_) - return; - buttonsInTimeline_ = state; - emit buttonInTimelineChanged(state); - save(); + if (state == buttonsInTimeline_) + return; + buttonsInTimeline_ = state; + emit buttonInTimelineChanged(state); + save(); } void UserSettings::setTimelineMaxWidth(int state) { - if (state == timelineMaxWidth_) - return; - timelineMaxWidth_ = state; - emit timelineMaxWidthChanged(state); - save(); + if (state == timelineMaxWidth_) + return; + timelineMaxWidth_ = state; + emit timelineMaxWidthChanged(state); + save(); } void UserSettings::setCommunityListWidth(int state) { - if (state == communityListWidth_) - return; - communityListWidth_ = state; - emit communityListWidthChanged(state); - save(); + if (state == communityListWidth_) + return; + communityListWidth_ = state; + emit communityListWidthChanged(state); + save(); } void UserSettings::setRoomListWidth(int state) { - if (state == roomListWidth_) - return; - roomListWidth_ = state; - emit roomListWidthChanged(state); - save(); + if (state == roomListWidth_) + return; + roomListWidth_ = state; + emit roomListWidthChanged(state); + save(); } void UserSettings::setDesktopNotifications(bool state) { - if (state == hasDesktopNotifications_) - return; - hasDesktopNotifications_ = state; - emit desktopNotificationsChanged(state); - save(); + if (state == hasDesktopNotifications_) + return; + hasDesktopNotifications_ = state; + emit desktopNotificationsChanged(state); + save(); } void UserSettings::setAlertOnNotification(bool state) { - if (state == hasAlertOnNotification_) - return; - hasAlertOnNotification_ = state; - emit alertOnNotificationChanged(state); - save(); + if (state == hasAlertOnNotification_) + return; + hasAlertOnNotification_ = state; + emit alertOnNotificationChanged(state); + save(); } void UserSettings::setAvatarCircles(bool state) { - if (state == avatarCircles_) - return; - avatarCircles_ = state; - emit avatarCirclesChanged(state); - save(); + if (state == avatarCircles_) + return; + avatarCircles_ = state; + emit avatarCirclesChanged(state); + save(); } void UserSettings::setDecryptSidebar(bool state) { - if (state == decryptSidebar_) - return; - decryptSidebar_ = state; - emit decryptSidebarChanged(state); - save(); + if (state == decryptSidebar_) + return; + decryptSidebar_ = state; + emit decryptSidebarChanged(state); + save(); } void UserSettings::setPrivacyScreen(bool state) { - if (state == privacyScreen_) { - return; - } - privacyScreen_ = state; - emit privacyScreenChanged(state); - save(); + if (state == privacyScreen_) { + return; + } + privacyScreen_ = state; + emit privacyScreenChanged(state); + save(); } void UserSettings::setPrivacyScreenTimeout(int state) { - if (state == privacyScreenTimeout_) { - return; - } - privacyScreenTimeout_ = state; - emit privacyScreenTimeoutChanged(state); - save(); + if (state == privacyScreenTimeout_) { + return; + } + privacyScreenTimeout_ = state; + emit privacyScreenTimeoutChanged(state); + save(); } void UserSettings::setFontSize(double size) { - if (size == baseFontSize_) - return; - baseFontSize_ = size; - emit fontSizeChanged(size); - save(); + if (size == baseFontSize_) + return; + baseFontSize_ = size; + emit fontSizeChanged(size); + save(); } void UserSettings::setFontFamily(QString family) { - if (family == font_) - return; - font_ = family; - emit fontChanged(family); - save(); + if (family == font_) + return; + font_ = family; + emit fontChanged(family); + save(); } void UserSettings::setEmojiFontFamily(QString family) { - if (family == emojiFont_) - return; + if (family == emojiFont_) + return; - if (family == tr("Default")) { - emojiFont_ = "default"; - } else { - emojiFont_ = family; - } + if (family == tr("Default")) { + emojiFont_ = "default"; + } else { + emojiFont_ = family; + } - emit emojiFontChanged(family); - save(); + emit emojiFontChanged(family); + save(); } void UserSettings::setPresence(Presence state) { - if (state == presence_) - return; - presence_ = state; - emit presenceChanged(state); - save(); + if (state == presence_) + return; + presence_ = state; + emit presenceChanged(state); + save(); } void UserSettings::setTheme(QString theme) { - if (theme == theme_) - return; - theme_ = theme; - save(); - applyTheme(); - emit themeChanged(theme); + if (theme == theme_) + return; + theme_ = theme; + save(); + applyTheme(); + emit themeChanged(theme); } void UserSettings::setUseStunServer(bool useStunServer) { - if (useStunServer == useStunServer_) - return; - useStunServer_ = useStunServer; - emit useStunServerChanged(useStunServer); - save(); + if (useStunServer == useStunServer_) + return; + useStunServer_ = useStunServer; + emit useStunServerChanged(useStunServer); + save(); } void UserSettings::setOnlyShareKeysWithVerifiedUsers(bool shareKeys) { - if (shareKeys == onlyShareKeysWithVerifiedUsers_) - return; + if (shareKeys == onlyShareKeysWithVerifiedUsers_) + return; - onlyShareKeysWithVerifiedUsers_ = shareKeys; - emit onlyShareKeysWithVerifiedUsersChanged(shareKeys); - save(); + onlyShareKeysWithVerifiedUsers_ = shareKeys; + emit onlyShareKeysWithVerifiedUsersChanged(shareKeys); + save(); } void UserSettings::setShareKeysWithTrustedUsers(bool shareKeys) { - if (shareKeys == shareKeysWithTrustedUsers_) - return; + if (shareKeys == shareKeysWithTrustedUsers_) + return; - shareKeysWithTrustedUsers_ = shareKeys; - emit shareKeysWithTrustedUsersChanged(shareKeys); - save(); + shareKeysWithTrustedUsers_ = shareKeys; + emit shareKeysWithTrustedUsersChanged(shareKeys); + save(); } void UserSettings::setUseOnlineKeyBackup(bool useBackup) { - if (useBackup == useOnlineKeyBackup_) - return; + if (useBackup == useOnlineKeyBackup_) + return; - useOnlineKeyBackup_ = useBackup; - emit useOnlineKeyBackupChanged(useBackup); - save(); + useOnlineKeyBackup_ = useBackup; + emit useOnlineKeyBackupChanged(useBackup); + save(); } void UserSettings::setRingtone(QString ringtone) { - if (ringtone == ringtone_) - return; - ringtone_ = ringtone; - emit ringtoneChanged(ringtone); - save(); + if (ringtone == ringtone_) + return; + ringtone_ = ringtone; + emit ringtoneChanged(ringtone); + save(); } void UserSettings::setMicrophone(QString microphone) { - if (microphone == microphone_) - return; - microphone_ = microphone; - emit microphoneChanged(microphone); - save(); + if (microphone == microphone_) + return; + microphone_ = microphone; + emit microphoneChanged(microphone); + save(); } void UserSettings::setCamera(QString camera) { - if (camera == camera_) - return; - camera_ = camera; - emit cameraChanged(camera); - save(); + if (camera == camera_) + return; + camera_ = camera; + emit cameraChanged(camera); + save(); } void UserSettings::setCameraResolution(QString resolution) { - if (resolution == cameraResolution_) - return; - cameraResolution_ = resolution; - emit cameraResolutionChanged(resolution); - save(); + if (resolution == cameraResolution_) + return; + cameraResolution_ = resolution; + emit cameraResolutionChanged(resolution); + save(); } void UserSettings::setCameraFrameRate(QString frameRate) { - if (frameRate == cameraFrameRate_) - return; - cameraFrameRate_ = frameRate; - emit cameraFrameRateChanged(frameRate); - save(); + if (frameRate == cameraFrameRate_) + return; + cameraFrameRate_ = frameRate; + emit cameraFrameRateChanged(frameRate); + save(); } void UserSettings::setScreenShareFrameRate(int frameRate) { - if (frameRate == screenShareFrameRate_) - return; - screenShareFrameRate_ = frameRate; - emit screenShareFrameRateChanged(frameRate); - save(); + if (frameRate == screenShareFrameRate_) + return; + screenShareFrameRate_ = frameRate; + emit screenShareFrameRateChanged(frameRate); + save(); } void UserSettings::setScreenSharePiP(bool state) { - if (state == screenSharePiP_) - return; - screenSharePiP_ = state; - emit screenSharePiPChanged(state); - save(); + if (state == screenSharePiP_) + return; + screenSharePiP_ = state; + emit screenSharePiPChanged(state); + save(); } void UserSettings::setScreenShareRemoteVideo(bool state) { - if (state == screenShareRemoteVideo_) - return; - screenShareRemoteVideo_ = state; - emit screenShareRemoteVideoChanged(state); - save(); + if (state == screenShareRemoteVideo_) + return; + screenShareRemoteVideo_ = state; + emit screenShareRemoteVideoChanged(state); + save(); } void UserSettings::setScreenShareHideCursor(bool state) { - if (state == screenShareHideCursor_) - return; - screenShareHideCursor_ = state; - emit screenShareHideCursorChanged(state); - save(); + if (state == screenShareHideCursor_) + return; + screenShareHideCursor_ = state; + emit screenShareHideCursorChanged(state); + save(); } void UserSettings::setProfile(QString profile) { - if (profile == profile_) - return; - profile_ = profile; - emit profileChanged(profile_); - save(); + if (profile == profile_) + return; + profile_ = profile; + emit profileChanged(profile_); + save(); } void UserSettings::setUserId(QString userId) { - if (userId == userId_) - return; - userId_ = userId; - emit userIdChanged(userId_); - save(); + if (userId == userId_) + return; + userId_ = userId; + emit userIdChanged(userId_); + save(); } void UserSettings::setAccessToken(QString accessToken) { - if (accessToken == accessToken_) - return; - accessToken_ = accessToken; - emit accessTokenChanged(accessToken_); - save(); + if (accessToken == accessToken_) + return; + accessToken_ = accessToken; + emit accessTokenChanged(accessToken_); + save(); } void UserSettings::setDeviceId(QString deviceId) { - if (deviceId == deviceId_) - return; - deviceId_ = deviceId; - emit deviceIdChanged(deviceId_); - save(); + if (deviceId == deviceId_) + return; + deviceId_ = deviceId; + emit deviceIdChanged(deviceId_); + save(); } void UserSettings::setHomeserver(QString homeserver) { - if (homeserver == homeserver_) - return; - homeserver_ = homeserver; - emit homeserverChanged(homeserver_); - save(); + if (homeserver == homeserver_) + return; + homeserver_ = homeserver; + emit homeserverChanged(homeserver_); + save(); } void UserSettings::setDisableCertificateValidation(bool disabled) { - if (disabled == disableCertificateValidation_) - return; - disableCertificateValidation_ = disabled; - http::client()->verify_certificates(!disabled); - emit disableCertificateValidationChanged(disabled); + if (disabled == disableCertificateValidation_) + return; + disableCertificateValidation_ = disabled; + http::client()->verify_certificates(!disabled); + emit disableCertificateValidationChanged(disabled); } void UserSettings::setUseIdenticon(bool state) { - if (state == useIdenticon_) - return; - useIdenticon_ = state; - emit useIdenticonChanged(useIdenticon_); - save(); + if (state == useIdenticon_) + return; + useIdenticon_ = state; + emit useIdenticonChanged(useIdenticon_); + save(); } void UserSettings::applyTheme() { - QFile stylefile; + QFile stylefile; - if (this->theme() == "light") { - stylefile.setFileName(":/styles/styles/nheko.qss"); - } else if (this->theme() == "dark") { - stylefile.setFileName(":/styles/styles/nheko-dark.qss"); - } else { - stylefile.setFileName(":/styles/styles/system.qss"); - } - QApplication::setPalette(Theme::paletteFromTheme(this->theme().toStdString())); + if (this->theme() == "light") { + stylefile.setFileName(":/styles/styles/nheko.qss"); + } else if (this->theme() == "dark") { + stylefile.setFileName(":/styles/styles/nheko-dark.qss"); + } else { + stylefile.setFileName(":/styles/styles/system.qss"); + } + QApplication::setPalette(Theme::paletteFromTheme(this->theme().toStdString())); - stylefile.open(QFile::ReadOnly); - QString stylesheet = QString(stylefile.readAll()); + stylefile.open(QFile::ReadOnly); + QString stylesheet = QString(stylefile.readAll()); - qobject_cast(QApplication::instance())->setStyleSheet(stylesheet); + qobject_cast(QApplication::instance())->setStyleSheet(stylesheet); } void UserSettings::save() { - settings.beginGroup("user"); + settings.beginGroup("user"); - settings.beginGroup("window"); - settings.setValue("tray", tray_); - settings.setValue("start_in_tray", startInTray_); - settings.endGroup(); // window + settings.beginGroup("window"); + settings.setValue("tray", tray_); + settings.setValue("start_in_tray", startInTray_); + settings.endGroup(); // window - settings.beginGroup("sidebar"); - settings.setValue("community_list_width", communityListWidth_); - settings.setValue("room_list_width", roomListWidth_); - settings.endGroup(); // window + settings.beginGroup("sidebar"); + settings.setValue("community_list_width", communityListWidth_); + settings.setValue("room_list_width", roomListWidth_); + settings.endGroup(); // window - settings.beginGroup("timeline"); - settings.setValue("buttons", buttonsInTimeline_); - settings.setValue("message_hover_highlight", messageHoverHighlight_); - settings.setValue("enlarge_emoji_only_msg", enlargeEmojiOnlyMessages_); - settings.setValue("max_width", timelineMaxWidth_); - settings.endGroup(); // timeline + settings.beginGroup("timeline"); + settings.setValue("buttons", buttonsInTimeline_); + settings.setValue("message_hover_highlight", messageHoverHighlight_); + settings.setValue("enlarge_emoji_only_msg", enlargeEmojiOnlyMessages_); + settings.setValue("max_width", timelineMaxWidth_); + settings.endGroup(); // timeline - settings.setValue("avatar_circles", avatarCircles_); - settings.setValue("decrypt_sidebar", decryptSidebar_); - settings.setValue("privacy_screen", privacyScreen_); - settings.setValue("privacy_screen_timeout", privacyScreenTimeout_); - settings.setValue("mobile_mode", mobileMode_); - settings.setValue("font_size", baseFontSize_); - settings.setValue("typing_notifications", typingNotifications_); - settings.setValue("sort_by_unread", sortByImportance_); - settings.setValue("minor_events", sortByImportance_); - settings.setValue("read_receipts", readReceipts_); - settings.setValue("group_view", groupView_); - settings.setValue("hidden_tags", hiddenTags_); - settings.setValue("markdown_enabled", markdown_); - settings.setValue("animate_images_on_hover", animateImagesOnHover_); - settings.setValue("desktop_notifications", hasDesktopNotifications_); - settings.setValue("alert_on_notification", hasAlertOnNotification_); - settings.setValue("theme", theme()); - settings.setValue("font_family", font_); - settings.setValue("emoji_font_family", emojiFont_); - settings.setValue("presence", - QString::fromUtf8(QMetaEnum::fromType().valueToKey( - static_cast(presence_)))); - settings.setValue("ringtone", ringtone_); - settings.setValue("microphone", microphone_); - settings.setValue("camera", camera_); - settings.setValue("camera_resolution", cameraResolution_); - settings.setValue("camera_frame_rate", cameraFrameRate_); - settings.setValue("screen_share_frame_rate", screenShareFrameRate_); - settings.setValue("screen_share_pip", screenSharePiP_); - settings.setValue("screen_share_remote_video", screenShareRemoteVideo_); - settings.setValue("screen_share_hide_cursor", screenShareHideCursor_); - settings.setValue("use_stun_server", useStunServer_); - settings.setValue("currentProfile", profile_); - settings.setValue("use_identicon", useIdenticon_); + settings.setValue("avatar_circles", avatarCircles_); + settings.setValue("decrypt_sidebar", decryptSidebar_); + settings.setValue("privacy_screen", privacyScreen_); + settings.setValue("privacy_screen_timeout", privacyScreenTimeout_); + settings.setValue("mobile_mode", mobileMode_); + settings.setValue("font_size", baseFontSize_); + settings.setValue("typing_notifications", typingNotifications_); + settings.setValue("sort_by_unread", sortByImportance_); + settings.setValue("minor_events", sortByImportance_); + settings.setValue("read_receipts", readReceipts_); + settings.setValue("group_view", groupView_); + settings.setValue("hidden_tags", hiddenTags_); + settings.setValue("markdown_enabled", markdown_); + settings.setValue("animate_images_on_hover", animateImagesOnHover_); + settings.setValue("desktop_notifications", hasDesktopNotifications_); + settings.setValue("alert_on_notification", hasAlertOnNotification_); + settings.setValue("theme", theme()); + settings.setValue("font_family", font_); + settings.setValue("emoji_font_family", emojiFont_); + settings.setValue( + "presence", + QString::fromUtf8(QMetaEnum::fromType().valueToKey(static_cast(presence_)))); + settings.setValue("ringtone", ringtone_); + settings.setValue("microphone", microphone_); + settings.setValue("camera", camera_); + settings.setValue("camera_resolution", cameraResolution_); + settings.setValue("camera_frame_rate", cameraFrameRate_); + settings.setValue("screen_share_frame_rate", screenShareFrameRate_); + settings.setValue("screen_share_pip", screenSharePiP_); + settings.setValue("screen_share_remote_video", screenShareRemoteVideo_); + settings.setValue("screen_share_hide_cursor", screenShareHideCursor_); + settings.setValue("use_stun_server", useStunServer_); + settings.setValue("currentProfile", profile_); + settings.setValue("use_identicon", useIdenticon_); - settings.endGroup(); // user + settings.endGroup(); // user - QString prefix = - (profile_ != "" && profile_ != "default") ? "profile/" + profile_ + "/" : ""; - settings.setValue(prefix + "auth/access_token", accessToken_); - settings.setValue(prefix + "auth/home_server", homeserver_); - settings.setValue(prefix + "auth/user_id", userId_); - settings.setValue(prefix + "auth/device_id", deviceId_); + QString prefix = (profile_ != "" && profile_ != "default") ? "profile/" + profile_ + "/" : ""; + settings.setValue(prefix + "auth/access_token", accessToken_); + settings.setValue(prefix + "auth/home_server", homeserver_); + settings.setValue(prefix + "auth/user_id", userId_); + settings.setValue(prefix + "auth/device_id", deviceId_); - settings.setValue(prefix + "user/automatically_share_keys_with_trusted_users", - shareKeysWithTrustedUsers_); - settings.setValue(prefix + "user/only_share_keys_with_verified_users", - onlyShareKeysWithVerifiedUsers_); - settings.setValue(prefix + "user/online_key_backup", useOnlineKeyBackup_); + settings.setValue(prefix + "user/automatically_share_keys_with_trusted_users", + shareKeysWithTrustedUsers_); + settings.setValue(prefix + "user/only_share_keys_with_verified_users", + onlyShareKeysWithVerifiedUsers_); + settings.setValue(prefix + "user/online_key_backup", useOnlineKeyBackup_); - settings.setValue("disable_certificate_validation", disableCertificateValidation_); + settings.setValue("disable_certificate_validation", disableCertificateValidation_); - settings.sync(); + settings.sync(); } HorizontalLine::HorizontalLine(QWidget *parent) : QFrame{parent} { - setFrameShape(QFrame::HLine); - setFrameShadow(QFrame::Sunken); + setFrameShape(QFrame::HLine); + setFrameShadow(QFrame::Sunken); } UserSettingsPage::UserSettingsPage(QSharedPointer settings, QWidget *parent) : QWidget{parent} , settings_{settings} { - topLayout_ = new QVBoxLayout{this}; + topLayout_ = new QVBoxLayout{this}; - QIcon icon; - icon.addFile(":/icons/icons/ui/angle-pointing-to-left.png"); + QIcon icon; + icon.addFile(":/icons/icons/ui/angle-pointing-to-left.png"); - auto backBtn_ = new FlatButton{this}; - backBtn_->setMinimumSize(QSize(24, 24)); - backBtn_->setIcon(icon); - backBtn_->setIconSize(QSize(24, 24)); + auto backBtn_ = new FlatButton{this}; + backBtn_->setMinimumSize(QSize(24, 24)); + backBtn_->setIcon(icon); + backBtn_->setIconSize(QSize(24, 24)); - QFont font; - font.setPointSizeF(font.pointSizeF() * 1.1); + QFont font; + font.setPointSizeF(font.pointSizeF() * 1.1); - auto versionInfo = new QLabel(QString("%1 | %2").arg(nheko::version).arg(nheko::build_os)); - if (QCoreApplication::applicationName() != "nheko") - versionInfo->setText(versionInfo->text() + " | " + - tr("profile: %1").arg(QCoreApplication::applicationName())); - versionInfo->setTextInteractionFlags(Qt::TextBrowserInteraction); + auto versionInfo = new QLabel(QString("%1 | %2").arg(nheko::version).arg(nheko::build_os)); + if (QCoreApplication::applicationName() != "nheko") + versionInfo->setText(versionInfo->text() + " | " + + tr("profile: %1").arg(QCoreApplication::applicationName())); + versionInfo->setTextInteractionFlags(Qt::TextBrowserInteraction); - topBarLayout_ = new QHBoxLayout; - topBarLayout_->setSpacing(0); - topBarLayout_->setMargin(0); - topBarLayout_->addWidget(backBtn_, 1, Qt::AlignLeft | Qt::AlignVCenter); - topBarLayout_->addStretch(1); + topBarLayout_ = new QHBoxLayout; + topBarLayout_->setSpacing(0); + topBarLayout_->setMargin(0); + topBarLayout_->addWidget(backBtn_, 1, Qt::AlignLeft | Qt::AlignVCenter); + topBarLayout_->addStretch(1); - formLayout_ = new QFormLayout; + formLayout_ = new QFormLayout; - formLayout_->setLabelAlignment(Qt::AlignLeft); - formLayout_->setFormAlignment(Qt::AlignRight); - formLayout_->setFieldGrowthPolicy(QFormLayout::AllNonFixedFieldsGrow); - formLayout_->setRowWrapPolicy(QFormLayout::WrapLongRows); - formLayout_->setHorizontalSpacing(0); + formLayout_->setLabelAlignment(Qt::AlignLeft); + formLayout_->setFormAlignment(Qt::AlignRight); + formLayout_->setFieldGrowthPolicy(QFormLayout::AllNonFixedFieldsGrow); + formLayout_->setRowWrapPolicy(QFormLayout::WrapLongRows); + formLayout_->setHorizontalSpacing(0); - auto general_ = new QLabel{tr("GENERAL"), this}; - general_->setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Fixed); - general_->setFont(font); + auto general_ = new QLabel{tr("GENERAL"), this}; + general_->setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Fixed); + general_->setFont(font); - trayToggle_ = new Toggle{this}; - startInTrayToggle_ = new Toggle{this}; - avatarCircles_ = new Toggle{this}; - useIdenticon_ = new Toggle{this}; - decryptSidebar_ = new Toggle(this); - privacyScreen_ = new Toggle{this}; - onlyShareKeysWithVerifiedUsers_ = new Toggle(this); - shareKeysWithTrustedUsers_ = new Toggle(this); - useOnlineKeyBackup_ = new Toggle(this); - groupViewToggle_ = new Toggle{this}; - timelineButtonsToggle_ = new Toggle{this}; - typingNotifications_ = new Toggle{this}; - messageHoverHighlight_ = new Toggle{this}; - enlargeEmojiOnlyMessages_ = new Toggle{this}; - sortByImportance_ = new Toggle{this}; - readReceipts_ = new Toggle{this}; - markdown_ = new Toggle{this}; - animateImagesOnHover_ = new Toggle{this}; - desktopNotifications_ = new Toggle{this}; - alertOnNotification_ = new Toggle{this}; - useStunServer_ = new Toggle{this}; - mobileMode_ = new Toggle{this}; - scaleFactorCombo_ = new QComboBox{this}; - fontSizeCombo_ = new QComboBox{this}; - fontSelectionCombo_ = new QFontComboBox{this}; - emojiFontSelectionCombo_ = new QComboBox{this}; - ringtoneCombo_ = new QComboBox{this}; - microphoneCombo_ = new QComboBox{this}; - cameraCombo_ = new QComboBox{this}; - cameraResolutionCombo_ = new QComboBox{this}; - cameraFrameRateCombo_ = new QComboBox{this}; - timelineMaxWidthSpin_ = new QSpinBox{this}; - privacyScreenTimeout_ = new QSpinBox{this}; + trayToggle_ = new Toggle{this}; + startInTrayToggle_ = new Toggle{this}; + avatarCircles_ = new Toggle{this}; + useIdenticon_ = new Toggle{this}; + decryptSidebar_ = new Toggle(this); + privacyScreen_ = new Toggle{this}; + onlyShareKeysWithVerifiedUsers_ = new Toggle(this); + shareKeysWithTrustedUsers_ = new Toggle(this); + useOnlineKeyBackup_ = new Toggle(this); + groupViewToggle_ = new Toggle{this}; + timelineButtonsToggle_ = new Toggle{this}; + typingNotifications_ = new Toggle{this}; + messageHoverHighlight_ = new Toggle{this}; + enlargeEmojiOnlyMessages_ = new Toggle{this}; + sortByImportance_ = new Toggle{this}; + readReceipts_ = new Toggle{this}; + markdown_ = new Toggle{this}; + animateImagesOnHover_ = new Toggle{this}; + desktopNotifications_ = new Toggle{this}; + alertOnNotification_ = new Toggle{this}; + useStunServer_ = new Toggle{this}; + mobileMode_ = new Toggle{this}; + scaleFactorCombo_ = new QComboBox{this}; + fontSizeCombo_ = new QComboBox{this}; + fontSelectionCombo_ = new QFontComboBox{this}; + emojiFontSelectionCombo_ = new QComboBox{this}; + ringtoneCombo_ = new QComboBox{this}; + microphoneCombo_ = new QComboBox{this}; + cameraCombo_ = new QComboBox{this}; + cameraResolutionCombo_ = new QComboBox{this}; + cameraFrameRateCombo_ = new QComboBox{this}; + timelineMaxWidthSpin_ = new QSpinBox{this}; + privacyScreenTimeout_ = new QSpinBox{this}; - trayToggle_->setChecked(settings_->tray()); - startInTrayToggle_->setChecked(settings_->startInTray()); - avatarCircles_->setChecked(settings_->avatarCircles()); - useIdenticon_->setChecked(settings_->useIdenticon()); - decryptSidebar_->setChecked(settings_->decryptSidebar()); - privacyScreen_->setChecked(settings_->privacyScreen()); - onlyShareKeysWithVerifiedUsers_->setChecked(settings_->onlyShareKeysWithVerifiedUsers()); - shareKeysWithTrustedUsers_->setChecked(settings_->shareKeysWithTrustedUsers()); - useOnlineKeyBackup_->setChecked(settings_->useOnlineKeyBackup()); - groupViewToggle_->setChecked(settings_->groupView()); - timelineButtonsToggle_->setChecked(settings_->buttonsInTimeline()); - typingNotifications_->setChecked(settings_->typingNotifications()); - messageHoverHighlight_->setChecked(settings_->messageHoverHighlight()); - enlargeEmojiOnlyMessages_->setChecked(settings_->enlargeEmojiOnlyMessages()); - sortByImportance_->setChecked(settings_->sortByImportance()); - readReceipts_->setChecked(settings_->readReceipts()); - markdown_->setChecked(settings_->markdown()); - animateImagesOnHover_->setChecked(settings_->animateImagesOnHover()); - desktopNotifications_->setChecked(settings_->hasDesktopNotifications()); - alertOnNotification_->setChecked(settings_->hasAlertOnNotification()); - useStunServer_->setChecked(settings_->useStunServer()); - mobileMode_->setChecked(settings_->mobileMode()); + trayToggle_->setChecked(settings_->tray()); + startInTrayToggle_->setChecked(settings_->startInTray()); + avatarCircles_->setChecked(settings_->avatarCircles()); + useIdenticon_->setChecked(settings_->useIdenticon()); + decryptSidebar_->setChecked(settings_->decryptSidebar()); + privacyScreen_->setChecked(settings_->privacyScreen()); + onlyShareKeysWithVerifiedUsers_->setChecked(settings_->onlyShareKeysWithVerifiedUsers()); + shareKeysWithTrustedUsers_->setChecked(settings_->shareKeysWithTrustedUsers()); + useOnlineKeyBackup_->setChecked(settings_->useOnlineKeyBackup()); + groupViewToggle_->setChecked(settings_->groupView()); + timelineButtonsToggle_->setChecked(settings_->buttonsInTimeline()); + typingNotifications_->setChecked(settings_->typingNotifications()); + messageHoverHighlight_->setChecked(settings_->messageHoverHighlight()); + enlargeEmojiOnlyMessages_->setChecked(settings_->enlargeEmojiOnlyMessages()); + sortByImportance_->setChecked(settings_->sortByImportance()); + readReceipts_->setChecked(settings_->readReceipts()); + markdown_->setChecked(settings_->markdown()); + animateImagesOnHover_->setChecked(settings_->animateImagesOnHover()); + desktopNotifications_->setChecked(settings_->hasDesktopNotifications()); + alertOnNotification_->setChecked(settings_->hasAlertOnNotification()); + useStunServer_->setChecked(settings_->useStunServer()); + mobileMode_->setChecked(settings_->mobileMode()); - if (!settings_->tray()) { - startInTrayToggle_->setState(false); - startInTrayToggle_->setDisabled(true); + if (!settings_->tray()) { + startInTrayToggle_->setState(false); + startInTrayToggle_->setDisabled(true); + } + + if (!settings_->privacyScreen()) { + privacyScreenTimeout_->setDisabled(true); + } + + avatarCircles_->setFixedSize(64, 48); + + auto uiLabel_ = new QLabel{tr("INTERFACE"), this}; + uiLabel_->setFixedHeight(uiLabel_->minimumHeight() + LayoutTopMargin); + uiLabel_->setAlignment(Qt::AlignBottom); + uiLabel_->setFont(font); + + for (double option = 1; option <= 3; option += 0.25) + scaleFactorCombo_->addItem(QString::number(option)); + for (double option = 6; option <= 24; option += 0.5) + fontSizeCombo_->addItem(QString("%1 ").arg(QString::number(option))); + + QFontDatabase fontDb; + + // TODO: Is there a way to limit to just emojis, rather than + // all emoji fonts? + auto emojiFamilies = fontDb.families(QFontDatabase::Symbol); + emojiFontSelectionCombo_->addItem(tr("Default")); + for (const auto &family : emojiFamilies) { + emojiFontSelectionCombo_->addItem(family); + } + + QString currentFont = settings_->font(); + if (currentFont != "default" || currentFont != "") { + fontSelectionCombo_->setCurrentIndex(fontSelectionCombo_->findText(currentFont)); + } + + emojiFontSelectionCombo_->setCurrentIndex( + emojiFontSelectionCombo_->findText(settings_->emojiFont())); + + themeCombo_ = new QComboBox{this}; + themeCombo_->addItem("Light"); + themeCombo_->addItem("Dark"); + themeCombo_->addItem("System"); + + QString themeStr = settings_->theme(); + themeStr.replace(0, 1, themeStr[0].toUpper()); + int themeIndex = themeCombo_->findText(themeStr); + themeCombo_->setCurrentIndex(themeIndex); + + timelineMaxWidthSpin_->setMinimum(0); + timelineMaxWidthSpin_->setMaximum(100'000'000); + timelineMaxWidthSpin_->setSingleStep(10); + + privacyScreenTimeout_->setMinimum(0); + privacyScreenTimeout_->setMaximum(3600); + privacyScreenTimeout_->setSingleStep(10); + + auto callsLabel = new QLabel{tr("CALLS"), this}; + callsLabel->setFixedHeight(callsLabel->minimumHeight() + LayoutTopMargin); + callsLabel->setAlignment(Qt::AlignBottom); + callsLabel->setFont(font); + + auto encryptionLabel_ = new QLabel{tr("ENCRYPTION"), this}; + encryptionLabel_->setFixedHeight(encryptionLabel_->minimumHeight() + LayoutTopMargin); + encryptionLabel_->setAlignment(Qt::AlignBottom); + encryptionLabel_->setFont(font); + + QFont monospaceFont; + monospaceFont.setFamily("Monospace"); + monospaceFont.setStyleHint(QFont::Monospace); + monospaceFont.setPointSizeF(monospaceFont.pointSizeF() * 0.9); + + deviceIdValue_ = new QLabel{this}; + deviceIdValue_->setTextInteractionFlags(Qt::TextSelectableByMouse); + deviceIdValue_->setFont(monospaceFont); + + deviceFingerprintValue_ = new QLabel{this}; + deviceFingerprintValue_->setTextInteractionFlags(Qt::TextSelectableByMouse); + deviceFingerprintValue_->setFont(monospaceFont); + + deviceFingerprintValue_->setText(utils::humanReadableFingerprint(QString(44, 'X'))); + + backupSecretCached = new QLabel{this}; + masterSecretCached = new QLabel{this}; + selfSigningSecretCached = new QLabel{this}; + userSigningSecretCached = new QLabel{this}; + backupSecretCached->setFont(monospaceFont); + masterSecretCached->setFont(monospaceFont); + selfSigningSecretCached->setFont(monospaceFont); + userSigningSecretCached->setFont(monospaceFont); + + auto sessionKeysLabel = new QLabel{tr("Session Keys"), this}; + sessionKeysLabel->setFont(font); + sessionKeysLabel->setMargin(OptionMargin); + + auto sessionKeysImportBtn = new QPushButton{tr("IMPORT"), this}; + auto sessionKeysExportBtn = new QPushButton{tr("EXPORT"), this}; + + auto sessionKeysLayout = new QHBoxLayout; + sessionKeysLayout->addWidget(new QLabel{"", this}, 1, Qt::AlignRight); + sessionKeysLayout->addWidget(sessionKeysExportBtn, 0, Qt::AlignRight); + sessionKeysLayout->addWidget(sessionKeysImportBtn, 0, Qt::AlignRight); + + auto crossSigningKeysLabel = new QLabel{tr("Cross Signing Keys"), this}; + crossSigningKeysLabel->setFont(font); + crossSigningKeysLabel->setMargin(OptionMargin); + + auto crossSigningRequestBtn = new QPushButton{tr("REQUEST"), this}; + auto crossSigningDownloadBtn = new QPushButton{tr("DOWNLOAD"), this}; + + auto crossSigningKeysLayout = new QHBoxLayout; + crossSigningKeysLayout->addWidget(new QLabel{"", this}, 1, Qt::AlignRight); + crossSigningKeysLayout->addWidget(crossSigningRequestBtn, 0, Qt::AlignRight); + crossSigningKeysLayout->addWidget(crossSigningDownloadBtn, 0, Qt::AlignRight); + + auto boxWrap = [this, &font](QString labelText, QWidget *field, QString tooltipText = "") { + auto label = new QLabel{labelText, this}; + label->setFont(font); + label->setMargin(OptionMargin); + + if (!tooltipText.isEmpty()) { + label->setToolTip(tooltipText); } - if (!settings_->privacyScreen()) { - privacyScreenTimeout_->setDisabled(true); - } + auto layout = new QHBoxLayout; + layout->addWidget(field, 0, Qt::AlignRight); - avatarCircles_->setFixedSize(64, 48); + formLayout_->addRow(label, layout); + }; - auto uiLabel_ = new QLabel{tr("INTERFACE"), this}; - uiLabel_->setFixedHeight(uiLabel_->minimumHeight() + LayoutTopMargin); - uiLabel_->setAlignment(Qt::AlignBottom); - uiLabel_->setFont(font); + formLayout_->addRow(general_); + formLayout_->addRow(new HorizontalLine{this}); + boxWrap(tr("Minimize to tray"), + trayToggle_, + tr("Keep the application running in the background after closing the client window.")); + boxWrap(tr("Start in tray"), + startInTrayToggle_, + tr("Start the application in the background without showing the client window.")); + formLayout_->addRow(new HorizontalLine{this}); + boxWrap(tr("Circular Avatars"), + avatarCircles_, + tr("Change the appearance of user avatars in chats.\nOFF - square, ON - Circle.")); + boxWrap(tr("Use identicons"), + useIdenticon_, + tr("Display an identicon instead of a letter when a user has not set an avatar.")); + boxWrap(tr("Group's sidebar"), + groupViewToggle_, + tr("Show a column containing groups and tags next to the room list.")); + boxWrap(tr("Decrypt messages in sidebar"), + decryptSidebar_, + tr("Decrypt the messages shown in the sidebar.\nOnly affects messages in " + "encrypted chats.")); + boxWrap(tr("Privacy Screen"), + privacyScreen_, + tr("When the window loses focus, the timeline will\nbe blurred.")); + boxWrap(tr("Privacy screen timeout (in seconds [0 - 3600])"), + privacyScreenTimeout_, + tr("Set timeout (in seconds) for how long after window loses\nfocus before the screen" + " will be blurred.\nSet to 0 to blur immediately after focus loss. Max value of 1 " + "hour (3600 seconds)")); + boxWrap(tr("Show buttons in timeline"), + timelineButtonsToggle_, + tr("Show buttons to quickly reply, react or access additional options next to each " + "message.")); + boxWrap(tr("Limit width of timeline"), + timelineMaxWidthSpin_, + tr("Set the max width of messages in the timeline (in pixels). This can help " + "readability on wide screen, when Nheko is maximised")); + boxWrap(tr("Typing notifications"), + typingNotifications_, + tr("Show who is typing in a room.\nThis will also enable or disable sending typing " + "notifications to others.")); + boxWrap( + tr("Sort rooms by unreads"), + sortByImportance_, + tr("Display rooms with new messages first.\nIf this is off, the list of rooms will only " + "be sorted by the timestamp of the last message in a room.\nIf this is on, rooms which " + "have active notifications (the small circle with a number in it) will be sorted on " + "top. Rooms, that you have muted, will still be sorted by timestamp, since you don't " + "seem to consider them as important as the other rooms.")); + formLayout_->addRow(new HorizontalLine{this}); + boxWrap(tr("Read receipts"), + readReceipts_, + tr("Show if your message was read.\nStatus is displayed next to timestamps.")); + boxWrap(tr("Send messages as Markdown"), + markdown_, + tr("Allow using markdown in messages.\nWhen disabled, all messages are sent as a plain " + "text.")); + boxWrap(tr("Play animated images only on hover"), + animateImagesOnHover_, + tr("Plays media like GIFs or WEBPs only when explicitly hovering over them.")); + boxWrap(tr("Desktop notifications"), + desktopNotifications_, + tr("Notify about received message when the client is not currently focused.")); + boxWrap(tr("Alert on notification"), + alertOnNotification_, + tr("Show an alert when a message is received.\nThis usually causes the application " + "icon in the task bar to animate in some fashion.")); + boxWrap(tr("Highlight message on hover"), + messageHoverHighlight_, + tr("Change the background color of messages when you hover over them.")); + boxWrap(tr("Large Emoji in timeline"), + enlargeEmojiOnlyMessages_, + tr("Make font size larger if messages with only a few emojis are displayed.")); + formLayout_->addRow(uiLabel_); + formLayout_->addRow(new HorizontalLine{this}); - for (double option = 1; option <= 3; option += 0.25) - scaleFactorCombo_->addItem(QString::number(option)); - for (double option = 6; option <= 24; option += 0.5) - fontSizeCombo_->addItem(QString("%1 ").arg(QString::number(option))); - - QFontDatabase fontDb; - - // TODO: Is there a way to limit to just emojis, rather than - // all emoji fonts? - auto emojiFamilies = fontDb.families(QFontDatabase::Symbol); - emojiFontSelectionCombo_->addItem(tr("Default")); - for (const auto &family : emojiFamilies) { - emojiFontSelectionCombo_->addItem(family); - } - - QString currentFont = settings_->font(); - if (currentFont != "default" || currentFont != "") { - fontSelectionCombo_->setCurrentIndex(fontSelectionCombo_->findText(currentFont)); - } - - emojiFontSelectionCombo_->setCurrentIndex( - emojiFontSelectionCombo_->findText(settings_->emojiFont())); - - themeCombo_ = new QComboBox{this}; - themeCombo_->addItem("Light"); - themeCombo_->addItem("Dark"); - themeCombo_->addItem("System"); - - QString themeStr = settings_->theme(); - themeStr.replace(0, 1, themeStr[0].toUpper()); - int themeIndex = themeCombo_->findText(themeStr); - themeCombo_->setCurrentIndex(themeIndex); - - timelineMaxWidthSpin_->setMinimum(0); - timelineMaxWidthSpin_->setMaximum(100'000'000); - timelineMaxWidthSpin_->setSingleStep(10); - - privacyScreenTimeout_->setMinimum(0); - privacyScreenTimeout_->setMaximum(3600); - privacyScreenTimeout_->setSingleStep(10); - - auto callsLabel = new QLabel{tr("CALLS"), this}; - callsLabel->setFixedHeight(callsLabel->minimumHeight() + LayoutTopMargin); - callsLabel->setAlignment(Qt::AlignBottom); - callsLabel->setFont(font); - - auto encryptionLabel_ = new QLabel{tr("ENCRYPTION"), this}; - encryptionLabel_->setFixedHeight(encryptionLabel_->minimumHeight() + LayoutTopMargin); - encryptionLabel_->setAlignment(Qt::AlignBottom); - encryptionLabel_->setFont(font); - - QFont monospaceFont; - monospaceFont.setFamily("Monospace"); - monospaceFont.setStyleHint(QFont::Monospace); - monospaceFont.setPointSizeF(monospaceFont.pointSizeF() * 0.9); - - deviceIdValue_ = new QLabel{this}; - deviceIdValue_->setTextInteractionFlags(Qt::TextSelectableByMouse); - deviceIdValue_->setFont(monospaceFont); - - deviceFingerprintValue_ = new QLabel{this}; - deviceFingerprintValue_->setTextInteractionFlags(Qt::TextSelectableByMouse); - deviceFingerprintValue_->setFont(monospaceFont); - - deviceFingerprintValue_->setText(utils::humanReadableFingerprint(QString(44, 'X'))); - - backupSecretCached = new QLabel{this}; - masterSecretCached = new QLabel{this}; - selfSigningSecretCached = new QLabel{this}; - userSigningSecretCached = new QLabel{this}; - backupSecretCached->setFont(monospaceFont); - masterSecretCached->setFont(monospaceFont); - selfSigningSecretCached->setFont(monospaceFont); - userSigningSecretCached->setFont(monospaceFont); - - auto sessionKeysLabel = new QLabel{tr("Session Keys"), this}; - sessionKeysLabel->setFont(font); - sessionKeysLabel->setMargin(OptionMargin); - - auto sessionKeysImportBtn = new QPushButton{tr("IMPORT"), this}; - auto sessionKeysExportBtn = new QPushButton{tr("EXPORT"), this}; - - auto sessionKeysLayout = new QHBoxLayout; - sessionKeysLayout->addWidget(new QLabel{"", this}, 1, Qt::AlignRight); - sessionKeysLayout->addWidget(sessionKeysExportBtn, 0, Qt::AlignRight); - sessionKeysLayout->addWidget(sessionKeysImportBtn, 0, Qt::AlignRight); - - auto crossSigningKeysLabel = new QLabel{tr("Cross Signing Keys"), this}; - crossSigningKeysLabel->setFont(font); - crossSigningKeysLabel->setMargin(OptionMargin); - - auto crossSigningRequestBtn = new QPushButton{tr("REQUEST"), this}; - auto crossSigningDownloadBtn = new QPushButton{tr("DOWNLOAD"), this}; - - auto crossSigningKeysLayout = new QHBoxLayout; - crossSigningKeysLayout->addWidget(new QLabel{"", this}, 1, Qt::AlignRight); - crossSigningKeysLayout->addWidget(crossSigningRequestBtn, 0, Qt::AlignRight); - crossSigningKeysLayout->addWidget(crossSigningDownloadBtn, 0, Qt::AlignRight); - - auto boxWrap = [this, &font](QString labelText, QWidget *field, QString tooltipText = "") { - auto label = new QLabel{labelText, this}; - label->setFont(font); - label->setMargin(OptionMargin); - - if (!tooltipText.isEmpty()) { - label->setToolTip(tooltipText); - } - - auto layout = new QHBoxLayout; - layout->addWidget(field, 0, Qt::AlignRight); - - formLayout_->addRow(label, layout); - }; - - formLayout_->addRow(general_); - formLayout_->addRow(new HorizontalLine{this}); - boxWrap( - tr("Minimize to tray"), - trayToggle_, - tr("Keep the application running in the background after closing the client window.")); - boxWrap(tr("Start in tray"), - startInTrayToggle_, - tr("Start the application in the background without showing the client window.")); - formLayout_->addRow(new HorizontalLine{this}); - boxWrap(tr("Circular Avatars"), - avatarCircles_, - tr("Change the appearance of user avatars in chats.\nOFF - square, ON - Circle.")); - boxWrap(tr("Use identicons"), - useIdenticon_, - tr("Display an identicon instead of a letter when a user has not set an avatar.")); - boxWrap(tr("Group's sidebar"), - groupViewToggle_, - tr("Show a column containing groups and tags next to the room list.")); - boxWrap(tr("Decrypt messages in sidebar"), - decryptSidebar_, - tr("Decrypt the messages shown in the sidebar.\nOnly affects messages in " - "encrypted chats.")); - boxWrap(tr("Privacy Screen"), - privacyScreen_, - tr("When the window loses focus, the timeline will\nbe blurred.")); - boxWrap( - tr("Privacy screen timeout (in seconds [0 - 3600])"), - privacyScreenTimeout_, - tr("Set timeout (in seconds) for how long after window loses\nfocus before the screen" - " will be blurred.\nSet to 0 to blur immediately after focus loss. Max value of 1 " - "hour (3600 seconds)")); - boxWrap(tr("Show buttons in timeline"), - timelineButtonsToggle_, - tr("Show buttons to quickly reply, react or access additional options next to each " - "message.")); - boxWrap(tr("Limit width of timeline"), - timelineMaxWidthSpin_, - tr("Set the max width of messages in the timeline (in pixels). This can help " - "readability on wide screen, when Nheko is maximised")); - boxWrap(tr("Typing notifications"), - typingNotifications_, - tr("Show who is typing in a room.\nThis will also enable or disable sending typing " - "notifications to others.")); - boxWrap( - tr("Sort rooms by unreads"), - sortByImportance_, - tr( - "Display rooms with new messages first.\nIf this is off, the list of rooms will only " - "be sorted by the timestamp of the last message in a room.\nIf this is on, rooms which " - "have active notifications (the small circle with a number in it) will be sorted on " - "top. Rooms, that you have muted, will still be sorted by timestamp, since you don't " - "seem to consider them as important as the other rooms.")); - formLayout_->addRow(new HorizontalLine{this}); - boxWrap(tr("Read receipts"), - readReceipts_, - tr("Show if your message was read.\nStatus is displayed next to timestamps.")); - boxWrap( - tr("Send messages as Markdown"), - markdown_, - tr("Allow using markdown in messages.\nWhen disabled, all messages are sent as a plain " - "text.")); - boxWrap(tr("Play animated images only on hover"), - animateImagesOnHover_, - tr("Plays media like GIFs or WEBPs only when explicitly hovering over them.")); - boxWrap(tr("Desktop notifications"), - desktopNotifications_, - tr("Notify about received message when the client is not currently focused.")); - boxWrap(tr("Alert on notification"), - alertOnNotification_, - tr("Show an alert when a message is received.\nThis usually causes the application " - "icon in the task bar to animate in some fashion.")); - boxWrap(tr("Highlight message on hover"), - messageHoverHighlight_, - tr("Change the background color of messages when you hover over them.")); - boxWrap(tr("Large Emoji in timeline"), - enlargeEmojiOnlyMessages_, - tr("Make font size larger if messages with only a few emojis are displayed.")); - formLayout_->addRow(uiLabel_); - formLayout_->addRow(new HorizontalLine{this}); - - boxWrap(tr("Touchscreen mode"), - mobileMode_, - tr("Will prevent text selection in the timeline to make touch scrolling easier.")); + boxWrap(tr("Touchscreen mode"), + mobileMode_, + tr("Will prevent text selection in the timeline to make touch scrolling easier.")); #if !defined(Q_OS_MAC) - boxWrap(tr("Scale factor"), - scaleFactorCombo_, - tr("Change the scale factor of the whole user interface.")); + boxWrap(tr("Scale factor"), + scaleFactorCombo_, + tr("Change the scale factor of the whole user interface.")); #else - scaleFactorCombo_->hide(); + scaleFactorCombo_->hide(); #endif - boxWrap(tr("Font size"), fontSizeCombo_); - boxWrap(tr("Font Family"), fontSelectionCombo_); + boxWrap(tr("Font size"), fontSizeCombo_); + boxWrap(tr("Font Family"), fontSelectionCombo_); #if !defined(Q_OS_MAC) - boxWrap(tr("Emoji Font Family"), emojiFontSelectionCombo_); + boxWrap(tr("Emoji Font Family"), emojiFontSelectionCombo_); #else - emojiFontSelectionCombo_->hide(); + emojiFontSelectionCombo_->hide(); #endif - boxWrap(tr("Theme"), themeCombo_); + boxWrap(tr("Theme"), themeCombo_); - formLayout_->addRow(callsLabel); - formLayout_->addRow(new HorizontalLine{this}); - boxWrap(tr("Ringtone"), - ringtoneCombo_, - tr("Set the notification sound to play when a call invite arrives")); - boxWrap(tr("Microphone"), microphoneCombo_); - boxWrap(tr("Camera"), cameraCombo_); - boxWrap(tr("Camera resolution"), cameraResolutionCombo_); - boxWrap(tr("Camera frame rate"), cameraFrameRateCombo_); + formLayout_->addRow(callsLabel); + formLayout_->addRow(new HorizontalLine{this}); + boxWrap(tr("Ringtone"), + ringtoneCombo_, + tr("Set the notification sound to play when a call invite arrives")); + boxWrap(tr("Microphone"), microphoneCombo_); + boxWrap(tr("Camera"), cameraCombo_); + boxWrap(tr("Camera resolution"), cameraResolutionCombo_); + boxWrap(tr("Camera frame rate"), cameraFrameRateCombo_); - ringtoneCombo_->setSizeAdjustPolicy(QComboBox::AdjustToContents); - ringtoneCombo_->addItem("Mute"); - ringtoneCombo_->addItem("Default"); - ringtoneCombo_->addItem("Other..."); - const QString &ringtone = settings_->ringtone(); - if (!ringtone.isEmpty() && ringtone != "Mute" && ringtone != "Default") - ringtoneCombo_->addItem(ringtone); - microphoneCombo_->setSizeAdjustPolicy(QComboBox::AdjustToContents); - cameraCombo_->setSizeAdjustPolicy(QComboBox::AdjustToContents); - cameraResolutionCombo_->setSizeAdjustPolicy(QComboBox::AdjustToContents); - cameraFrameRateCombo_->setSizeAdjustPolicy(QComboBox::AdjustToContents); + ringtoneCombo_->setSizeAdjustPolicy(QComboBox::AdjustToContents); + ringtoneCombo_->addItem("Mute"); + ringtoneCombo_->addItem("Default"); + ringtoneCombo_->addItem("Other..."); + const QString &ringtone = settings_->ringtone(); + if (!ringtone.isEmpty() && ringtone != "Mute" && ringtone != "Default") + ringtoneCombo_->addItem(ringtone); + microphoneCombo_->setSizeAdjustPolicy(QComboBox::AdjustToContents); + cameraCombo_->setSizeAdjustPolicy(QComboBox::AdjustToContents); + cameraResolutionCombo_->setSizeAdjustPolicy(QComboBox::AdjustToContents); + cameraFrameRateCombo_->setSizeAdjustPolicy(QComboBox::AdjustToContents); - boxWrap(tr("Allow fallback call assist server"), - useStunServer_, - tr("Will use turn.matrix.org as assist when your home server does not offer one.")); + boxWrap(tr("Allow fallback call assist server"), + useStunServer_, + tr("Will use turn.matrix.org as assist when your home server does not offer one.")); - formLayout_->addRow(encryptionLabel_); - formLayout_->addRow(new HorizontalLine{this}); - boxWrap(tr("Device ID"), deviceIdValue_); - boxWrap(tr("Device Fingerprint"), deviceFingerprintValue_); - boxWrap(tr("Send encrypted messages to verified users only"), - onlyShareKeysWithVerifiedUsers_, - tr("Requires a user to be verified to send encrypted messages to them. This " - "improves safety but makes E2EE more tedious.")); - boxWrap(tr("Share keys with verified users and devices"), - shareKeysWithTrustedUsers_, - tr("Automatically replies to key requests from other users, if they are verified, " - "even if that device shouldn't have access to those keys otherwise.")); - boxWrap(tr("Online Key Backup"), - useOnlineKeyBackup_, - tr("Download message encryption keys from and upload to the encrypted online key " - "backup.")); - formLayout_->addRow(new HorizontalLine{this}); - formLayout_->addRow(sessionKeysLabel, sessionKeysLayout); - formLayout_->addRow(crossSigningKeysLabel, crossSigningKeysLayout); + formLayout_->addRow(encryptionLabel_); + formLayout_->addRow(new HorizontalLine{this}); + boxWrap(tr("Device ID"), deviceIdValue_); + boxWrap(tr("Device Fingerprint"), deviceFingerprintValue_); + boxWrap(tr("Send encrypted messages to verified users only"), + onlyShareKeysWithVerifiedUsers_, + tr("Requires a user to be verified to send encrypted messages to them. This " + "improves safety but makes E2EE more tedious.")); + boxWrap(tr("Share keys with verified users and devices"), + shareKeysWithTrustedUsers_, + tr("Automatically replies to key requests from other users, if they are verified, " + "even if that device shouldn't have access to those keys otherwise.")); + boxWrap(tr("Online Key Backup"), + useOnlineKeyBackup_, + tr("Download message encryption keys from and upload to the encrypted online key " + "backup.")); + formLayout_->addRow(new HorizontalLine{this}); + formLayout_->addRow(sessionKeysLabel, sessionKeysLayout); + formLayout_->addRow(crossSigningKeysLabel, crossSigningKeysLayout); - boxWrap(tr("Master signing key"), - masterSecretCached, - tr("Your most important key. You don't need to have it cached, since not caching " - "it makes it less likely it can be stolen and it is only needed to rotate your " - "other signing keys.")); - boxWrap(tr("User signing key"), - userSigningSecretCached, - tr("The key to verify other users. If it is cached, verifying a user will verify " - "all their devices.")); - boxWrap( - tr("Self signing key"), - selfSigningSecretCached, - tr("The key to verify your own devices. If it is cached, verifying one of your devices " - "will mark it verified for all your other devices and for users, that have verified " - "you.")); - boxWrap(tr("Backup key"), - backupSecretCached, - tr("The key to decrypt online key backups. If it is cached, you can enable online " - "key backup to store encryption keys securely encrypted on the server.")); - updateSecretStatus(); + boxWrap(tr("Master signing key"), + masterSecretCached, + tr("Your most important key. You don't need to have it cached, since not caching " + "it makes it less likely it can be stolen and it is only needed to rotate your " + "other signing keys.")); + boxWrap(tr("User signing key"), + userSigningSecretCached, + tr("The key to verify other users. If it is cached, verifying a user will verify " + "all their devices.")); + boxWrap(tr("Self signing key"), + selfSigningSecretCached, + tr("The key to verify your own devices. If it is cached, verifying one of your devices " + "will mark it verified for all your other devices and for users, that have verified " + "you.")); + boxWrap(tr("Backup key"), + backupSecretCached, + tr("The key to decrypt online key backups. If it is cached, you can enable online " + "key backup to store encryption keys securely encrypted on the server.")); + updateSecretStatus(); - auto scrollArea_ = new QScrollArea{this}; - scrollArea_->setFrameShape(QFrame::NoFrame); - scrollArea_->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); - scrollArea_->setSizeAdjustPolicy(QAbstractScrollArea::AdjustToContents); - scrollArea_->setWidgetResizable(true); - scrollArea_->setAlignment(Qt::AlignTop | Qt::AlignVCenter); + auto scrollArea_ = new QScrollArea{this}; + scrollArea_->setFrameShape(QFrame::NoFrame); + scrollArea_->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + scrollArea_->setSizeAdjustPolicy(QAbstractScrollArea::AdjustToContents); + scrollArea_->setWidgetResizable(true); + scrollArea_->setAlignment(Qt::AlignTop | Qt::AlignVCenter); - QScroller::grabGesture(scrollArea_, QScroller::TouchGesture); + QScroller::grabGesture(scrollArea_, QScroller::TouchGesture); - auto spacingAroundForm = new QHBoxLayout; - spacingAroundForm->addStretch(1); - spacingAroundForm->addLayout(formLayout_, 0); - spacingAroundForm->addStretch(1); + auto spacingAroundForm = new QHBoxLayout; + spacingAroundForm->addStretch(1); + spacingAroundForm->addLayout(formLayout_, 0); + spacingAroundForm->addStretch(1); - auto scrollAreaContents_ = new QWidget{this}; - scrollAreaContents_->setObjectName("UserSettingScrollWidget"); - scrollAreaContents_->setLayout(spacingAroundForm); + auto scrollAreaContents_ = new QWidget{this}; + scrollAreaContents_->setObjectName("UserSettingScrollWidget"); + scrollAreaContents_->setLayout(spacingAroundForm); - scrollArea_->setWidget(scrollAreaContents_); - topLayout_->addLayout(topBarLayout_); - topLayout_->addWidget(scrollArea_, Qt::AlignTop); - topLayout_->addStretch(1); - topLayout_->addWidget(versionInfo); + scrollArea_->setWidget(scrollAreaContents_); + topLayout_->addLayout(topBarLayout_); + topLayout_->addWidget(scrollArea_, Qt::AlignTop); + topLayout_->addStretch(1); + topLayout_->addWidget(versionInfo); - connect(themeCombo_, - static_cast(&QComboBox::currentTextChanged), - [this](const QString &text) { - settings_->setTheme(text.toLower()); - emit themeChanged(); - }); - connect(scaleFactorCombo_, - static_cast(&QComboBox::currentTextChanged), - [](const QString &factor) { utils::setScaleFactor(factor.toFloat()); }); - connect(fontSizeCombo_, - static_cast(&QComboBox::currentTextChanged), - [this](const QString &size) { settings_->setFontSize(size.trimmed().toDouble()); }); - connect(fontSelectionCombo_, - static_cast(&QComboBox::currentTextChanged), - [this](const QString &family) { settings_->setFontFamily(family.trimmed()); }); - connect(emojiFontSelectionCombo_, - static_cast(&QComboBox::currentTextChanged), - [this](const QString &family) { settings_->setEmojiFontFamily(family.trimmed()); }); + connect(themeCombo_, + static_cast(&QComboBox::currentTextChanged), + [this](const QString &text) { + settings_->setTheme(text.toLower()); + emit themeChanged(); + }); + connect(scaleFactorCombo_, + static_cast(&QComboBox::currentTextChanged), + [](const QString &factor) { utils::setScaleFactor(factor.toFloat()); }); + connect(fontSizeCombo_, + static_cast(&QComboBox::currentTextChanged), + [this](const QString &size) { settings_->setFontSize(size.trimmed().toDouble()); }); + connect(fontSelectionCombo_, + static_cast(&QComboBox::currentTextChanged), + [this](const QString &family) { settings_->setFontFamily(family.trimmed()); }); + connect(emojiFontSelectionCombo_, + static_cast(&QComboBox::currentTextChanged), + [this](const QString &family) { settings_->setEmojiFontFamily(family.trimmed()); }); - connect(ringtoneCombo_, - static_cast(&QComboBox::currentTextChanged), - [this](const QString &ringtone) { - if (ringtone == "Other...") { - QString homeFolder = - QStandardPaths::writableLocation(QStandardPaths::HomeLocation); - auto filepath = QFileDialog::getOpenFileName( - this, tr("Select a file"), homeFolder, tr("All Files (*)")); - if (!filepath.isEmpty()) { - const auto &oldSetting = settings_->ringtone(); - if (oldSetting != "Mute" && oldSetting != "Default") - ringtoneCombo_->removeItem( - ringtoneCombo_->findText(oldSetting)); - settings_->setRingtone(filepath); - ringtoneCombo_->addItem(filepath); - ringtoneCombo_->setCurrentText(filepath); - } else { - ringtoneCombo_->setCurrentText(settings_->ringtone()); - } - } else if (ringtone == "Mute" || ringtone == "Default") { - const auto &oldSetting = settings_->ringtone(); - if (oldSetting != "Mute" && oldSetting != "Default") - ringtoneCombo_->removeItem( - ringtoneCombo_->findText(oldSetting)); - settings_->setRingtone(ringtone); - } - }); - - connect(microphoneCombo_, - static_cast(&QComboBox::currentTextChanged), - [this](const QString µphone) { settings_->setMicrophone(microphone); }); - - connect(cameraCombo_, - static_cast(&QComboBox::currentTextChanged), - [this](const QString &camera) { - settings_->setCamera(camera); - std::vector resolutions = - CallDevices::instance().resolutions(camera.toStdString()); - cameraResolutionCombo_->clear(); - for (const auto &resolution : resolutions) - cameraResolutionCombo_->addItem(QString::fromStdString(resolution)); - }); - - connect(cameraResolutionCombo_, - static_cast(&QComboBox::currentTextChanged), - [this](const QString &resolution) { - settings_->setCameraResolution(resolution); - std::vector frameRates = CallDevices::instance().frameRates( - settings_->camera().toStdString(), resolution.toStdString()); - cameraFrameRateCombo_->clear(); - for (const auto &frameRate : frameRates) - cameraFrameRateCombo_->addItem(QString::fromStdString(frameRate)); - }); - - connect(cameraFrameRateCombo_, - static_cast(&QComboBox::currentTextChanged), - [this](const QString &frameRate) { settings_->setCameraFrameRate(frameRate); }); - - connect(trayToggle_, &Toggle::toggled, this, [this](bool enabled) { - settings_->setTray(enabled); - if (enabled) { - startInTrayToggle_->setChecked(false); - startInTrayToggle_->setEnabled(true); - startInTrayToggle_->setState(false); - settings_->setStartInTray(false); - } else { - startInTrayToggle_->setChecked(false); - startInTrayToggle_->setState(false); - startInTrayToggle_->setDisabled(true); - settings_->setStartInTray(false); + connect(ringtoneCombo_, + static_cast(&QComboBox::currentTextChanged), + [this](const QString &ringtone) { + if (ringtone == "Other...") { + QString homeFolder = + QStandardPaths::writableLocation(QStandardPaths::HomeLocation); + auto filepath = QFileDialog::getOpenFileName( + this, tr("Select a file"), homeFolder, tr("All Files (*)")); + if (!filepath.isEmpty()) { + const auto &oldSetting = settings_->ringtone(); + if (oldSetting != "Mute" && oldSetting != "Default") + ringtoneCombo_->removeItem(ringtoneCombo_->findText(oldSetting)); + settings_->setRingtone(filepath); + ringtoneCombo_->addItem(filepath); + ringtoneCombo_->setCurrentText(filepath); + } else { + ringtoneCombo_->setCurrentText(settings_->ringtone()); + } + } else if (ringtone == "Mute" || ringtone == "Default") { + const auto &oldSetting = settings_->ringtone(); + if (oldSetting != "Mute" && oldSetting != "Default") + ringtoneCombo_->removeItem(ringtoneCombo_->findText(oldSetting)); + settings_->setRingtone(ringtone); } - emit trayOptionChanged(enabled); + }); + + connect(microphoneCombo_, + static_cast(&QComboBox::currentTextChanged), + [this](const QString µphone) { settings_->setMicrophone(microphone); }); + + connect(cameraCombo_, + static_cast(&QComboBox::currentTextChanged), + [this](const QString &camera) { + settings_->setCamera(camera); + std::vector resolutions = + CallDevices::instance().resolutions(camera.toStdString()); + cameraResolutionCombo_->clear(); + for (const auto &resolution : resolutions) + cameraResolutionCombo_->addItem(QString::fromStdString(resolution)); + }); + + connect(cameraResolutionCombo_, + static_cast(&QComboBox::currentTextChanged), + [this](const QString &resolution) { + settings_->setCameraResolution(resolution); + std::vector frameRates = CallDevices::instance().frameRates( + settings_->camera().toStdString(), resolution.toStdString()); + cameraFrameRateCombo_->clear(); + for (const auto &frameRate : frameRates) + cameraFrameRateCombo_->addItem(QString::fromStdString(frameRate)); + }); + + connect(cameraFrameRateCombo_, + static_cast(&QComboBox::currentTextChanged), + [this](const QString &frameRate) { settings_->setCameraFrameRate(frameRate); }); + + connect(trayToggle_, &Toggle::toggled, this, [this](bool enabled) { + settings_->setTray(enabled); + if (enabled) { + startInTrayToggle_->setChecked(false); + startInTrayToggle_->setEnabled(true); + startInTrayToggle_->setState(false); + settings_->setStartInTray(false); + } else { + startInTrayToggle_->setChecked(false); + startInTrayToggle_->setState(false); + startInTrayToggle_->setDisabled(true); + settings_->setStartInTray(false); + } + emit trayOptionChanged(enabled); + }); + + connect(startInTrayToggle_, &Toggle::toggled, this, [this](bool enabled) { + settings_->setStartInTray(enabled); + }); + + connect(mobileMode_, &Toggle::toggled, this, [this](bool enabled) { + settings_->setMobileMode(enabled); + }); + + connect(groupViewToggle_, &Toggle::toggled, this, [this](bool enabled) { + settings_->setGroupView(enabled); + }); + + connect(decryptSidebar_, &Toggle::toggled, this, [this](bool enabled) { + settings_->setDecryptSidebar(enabled); + }); + + connect(privacyScreen_, &Toggle::toggled, this, [this](bool enabled) { + settings_->setPrivacyScreen(enabled); + if (enabled) { + privacyScreenTimeout_->setEnabled(true); + } else { + privacyScreenTimeout_->setDisabled(true); + } + }); + + connect(onlyShareKeysWithVerifiedUsers_, &Toggle::toggled, this, [this](bool enabled) { + settings_->setOnlyShareKeysWithVerifiedUsers(enabled); + }); + + connect(shareKeysWithTrustedUsers_, &Toggle::toggled, this, [this](bool enabled) { + settings_->setShareKeysWithTrustedUsers(enabled); + }); + + connect(useOnlineKeyBackup_, &Toggle::toggled, this, [this](bool enabled) { + if (enabled) { + if (QMessageBox::question( + this, + tr("Enable online key backup"), + tr("The Nheko authors recommend not enabling online key backup until " + "symmetric online key backup is available. Enable anyway?")) != + QMessageBox::StandardButton::Yes) { + useOnlineKeyBackup_->setState(false); + return; + } + } + settings_->setUseOnlineKeyBackup(enabled); + }); + + connect(avatarCircles_, &Toggle::toggled, this, [this](bool enabled) { + settings_->setAvatarCircles(enabled); + }); + + if (JdenticonProvider::isAvailable()) + connect(useIdenticon_, &Toggle::toggled, this, [this](bool enabled) { + settings_->setUseIdenticon(enabled); }); + else + useIdenticon_->setDisabled(true); - connect(startInTrayToggle_, &Toggle::toggled, this, [this](bool enabled) { - settings_->setStartInTray(enabled); - }); + connect( + markdown_, &Toggle::toggled, this, [this](bool enabled) { settings_->setMarkdown(enabled); }); - connect(mobileMode_, &Toggle::toggled, this, [this](bool enabled) { - settings_->setMobileMode(enabled); - }); + connect(animateImagesOnHover_, &Toggle::toggled, this, [this](bool enabled) { + settings_->setAnimateImagesOnHover(enabled); + }); - connect(groupViewToggle_, &Toggle::toggled, this, [this](bool enabled) { - settings_->setGroupView(enabled); - }); + connect(typingNotifications_, &Toggle::toggled, this, [this](bool enabled) { + settings_->setTypingNotifications(enabled); + }); - connect(decryptSidebar_, &Toggle::toggled, this, [this](bool enabled) { - settings_->setDecryptSidebar(enabled); - }); + connect(sortByImportance_, &Toggle::toggled, this, [this](bool enabled) { + settings_->setSortByImportance(enabled); + }); - connect(privacyScreen_, &Toggle::toggled, this, [this](bool enabled) { - settings_->setPrivacyScreen(enabled); - if (enabled) { - privacyScreenTimeout_->setEnabled(true); - } else { - privacyScreenTimeout_->setDisabled(true); - } - }); + connect(timelineButtonsToggle_, &Toggle::toggled, this, [this](bool enabled) { + settings_->setButtonsInTimeline(enabled); + }); - connect(onlyShareKeysWithVerifiedUsers_, &Toggle::toggled, this, [this](bool enabled) { - settings_->setOnlyShareKeysWithVerifiedUsers(enabled); - }); + connect(readReceipts_, &Toggle::toggled, this, [this](bool enabled) { + settings_->setReadReceipts(enabled); + }); - connect(shareKeysWithTrustedUsers_, &Toggle::toggled, this, [this](bool enabled) { - settings_->setShareKeysWithTrustedUsers(enabled); - }); + connect(desktopNotifications_, &Toggle::toggled, this, [this](bool enabled) { + settings_->setDesktopNotifications(enabled); + }); - connect(useOnlineKeyBackup_, &Toggle::toggled, this, [this](bool enabled) { - if (enabled) { - if (QMessageBox::question( - this, - tr("Enable online key backup"), - tr("The Nheko authors recommend not enabling online key backup until " - "symmetric online key backup is available. Enable anyway?")) != - QMessageBox::StandardButton::Yes) { - useOnlineKeyBackup_->setState(false); - return; - } - } - settings_->setUseOnlineKeyBackup(enabled); - }); + connect(alertOnNotification_, &Toggle::toggled, this, [this](bool enabled) { + settings_->setAlertOnNotification(enabled); + }); - connect(avatarCircles_, &Toggle::toggled, this, [this](bool enabled) { - settings_->setAvatarCircles(enabled); - }); + connect(messageHoverHighlight_, &Toggle::toggled, this, [this](bool enabled) { + settings_->setMessageHoverHighlight(enabled); + }); - if (JdenticonProvider::isAvailable()) - connect(useIdenticon_, &Toggle::toggled, this, [this](bool enabled) { - settings_->setUseIdenticon(enabled); - }); - else - useIdenticon_->setDisabled(true); + connect(enlargeEmojiOnlyMessages_, &Toggle::toggled, this, [this](bool enabled) { + settings_->setEnlargeEmojiOnlyMessages(enabled); + }); - connect(markdown_, &Toggle::toggled, this, [this](bool enabled) { - settings_->setMarkdown(enabled); - }); + connect(useStunServer_, &Toggle::toggled, this, [this](bool enabled) { + settings_->setUseStunServer(enabled); + }); - connect(animateImagesOnHover_, &Toggle::toggled, this, [this](bool enabled) { - settings_->setAnimateImagesOnHover(enabled); - }); + connect(timelineMaxWidthSpin_, + qOverload(&QSpinBox::valueChanged), + this, + [this](int newValue) { settings_->setTimelineMaxWidth(newValue); }); - connect(typingNotifications_, &Toggle::toggled, this, [this](bool enabled) { - settings_->setTypingNotifications(enabled); - }); + connect(privacyScreenTimeout_, + qOverload(&QSpinBox::valueChanged), + this, + [this](int newValue) { settings_->setPrivacyScreenTimeout(newValue); }); - connect(sortByImportance_, &Toggle::toggled, this, [this](bool enabled) { - settings_->setSortByImportance(enabled); - }); + connect( + sessionKeysImportBtn, &QPushButton::clicked, this, &UserSettingsPage::importSessionKeys); - connect(timelineButtonsToggle_, &Toggle::toggled, this, [this](bool enabled) { - settings_->setButtonsInTimeline(enabled); - }); + connect( + sessionKeysExportBtn, &QPushButton::clicked, this, &UserSettingsPage::exportSessionKeys); - connect(readReceipts_, &Toggle::toggled, this, [this](bool enabled) { - settings_->setReadReceipts(enabled); - }); + connect(crossSigningRequestBtn, &QPushButton::clicked, this, []() { + olm::request_cross_signing_keys(); + }); - connect(desktopNotifications_, &Toggle::toggled, this, [this](bool enabled) { - settings_->setDesktopNotifications(enabled); - }); + connect(crossSigningDownloadBtn, &QPushButton::clicked, this, []() { + olm::download_cross_signing_keys(); + }); - connect(alertOnNotification_, &Toggle::toggled, this, [this](bool enabled) { - settings_->setAlertOnNotification(enabled); - }); - - connect(messageHoverHighlight_, &Toggle::toggled, this, [this](bool enabled) { - settings_->setMessageHoverHighlight(enabled); - }); - - connect(enlargeEmojiOnlyMessages_, &Toggle::toggled, this, [this](bool enabled) { - settings_->setEnlargeEmojiOnlyMessages(enabled); - }); - - connect(useStunServer_, &Toggle::toggled, this, [this](bool enabled) { - settings_->setUseStunServer(enabled); - }); - - connect(timelineMaxWidthSpin_, - qOverload(&QSpinBox::valueChanged), - this, - [this](int newValue) { settings_->setTimelineMaxWidth(newValue); }); - - connect(privacyScreenTimeout_, - qOverload(&QSpinBox::valueChanged), - this, - [this](int newValue) { settings_->setPrivacyScreenTimeout(newValue); }); - - connect( - sessionKeysImportBtn, &QPushButton::clicked, this, &UserSettingsPage::importSessionKeys); - - connect( - sessionKeysExportBtn, &QPushButton::clicked, this, &UserSettingsPage::exportSessionKeys); - - connect(crossSigningRequestBtn, &QPushButton::clicked, this, []() { - olm::request_cross_signing_keys(); - }); - - connect(crossSigningDownloadBtn, &QPushButton::clicked, this, []() { - olm::download_cross_signing_keys(); - }); - - connect(backBtn_, &QPushButton::clicked, this, [this]() { - settings_->save(); - emit moveBack(); - }); + connect(backBtn_, &QPushButton::clicked, this, [this]() { + settings_->save(); + emit moveBack(); + }); } void UserSettingsPage::showEvent(QShowEvent *) { - // FIXME macOS doesn't show the full option unless a space is added. - utils::restoreCombobox(fontSizeCombo_, QString::number(settings_->fontSize()) + " "); - utils::restoreCombobox(scaleFactorCombo_, QString::number(utils::scaleFactor())); - utils::restoreCombobox(themeCombo_, settings_->theme()); - utils::restoreCombobox(ringtoneCombo_, settings_->ringtone()); + // FIXME macOS doesn't show the full option unless a space is added. + utils::restoreCombobox(fontSizeCombo_, QString::number(settings_->fontSize()) + " "); + utils::restoreCombobox(scaleFactorCombo_, QString::number(utils::scaleFactor())); + utils::restoreCombobox(themeCombo_, settings_->theme()); + utils::restoreCombobox(ringtoneCombo_, settings_->ringtone()); - trayToggle_->setState(settings_->tray()); - startInTrayToggle_->setState(settings_->startInTray()); - groupViewToggle_->setState(settings_->groupView()); - decryptSidebar_->setState(settings_->decryptSidebar()); - privacyScreen_->setState(settings_->privacyScreen()); - onlyShareKeysWithVerifiedUsers_->setState(settings_->onlyShareKeysWithVerifiedUsers()); - shareKeysWithTrustedUsers_->setState(settings_->shareKeysWithTrustedUsers()); - useOnlineKeyBackup_->setState(settings_->useOnlineKeyBackup()); - avatarCircles_->setState(settings_->avatarCircles()); - typingNotifications_->setState(settings_->typingNotifications()); - sortByImportance_->setState(settings_->sortByImportance()); - timelineButtonsToggle_->setState(settings_->buttonsInTimeline()); - mobileMode_->setState(settings_->mobileMode()); - readReceipts_->setState(settings_->readReceipts()); - markdown_->setState(settings_->markdown()); - desktopNotifications_->setState(settings_->hasDesktopNotifications()); - alertOnNotification_->setState(settings_->hasAlertOnNotification()); - messageHoverHighlight_->setState(settings_->messageHoverHighlight()); - enlargeEmojiOnlyMessages_->setState(settings_->enlargeEmojiOnlyMessages()); - deviceIdValue_->setText(QString::fromStdString(http::client()->device_id())); - timelineMaxWidthSpin_->setValue(settings_->timelineMaxWidth()); - privacyScreenTimeout_->setValue(settings_->privacyScreenTimeout()); + trayToggle_->setState(settings_->tray()); + startInTrayToggle_->setState(settings_->startInTray()); + groupViewToggle_->setState(settings_->groupView()); + decryptSidebar_->setState(settings_->decryptSidebar()); + privacyScreen_->setState(settings_->privacyScreen()); + onlyShareKeysWithVerifiedUsers_->setState(settings_->onlyShareKeysWithVerifiedUsers()); + shareKeysWithTrustedUsers_->setState(settings_->shareKeysWithTrustedUsers()); + useOnlineKeyBackup_->setState(settings_->useOnlineKeyBackup()); + avatarCircles_->setState(settings_->avatarCircles()); + typingNotifications_->setState(settings_->typingNotifications()); + sortByImportance_->setState(settings_->sortByImportance()); + timelineButtonsToggle_->setState(settings_->buttonsInTimeline()); + mobileMode_->setState(settings_->mobileMode()); + readReceipts_->setState(settings_->readReceipts()); + markdown_->setState(settings_->markdown()); + desktopNotifications_->setState(settings_->hasDesktopNotifications()); + alertOnNotification_->setState(settings_->hasAlertOnNotification()); + messageHoverHighlight_->setState(settings_->messageHoverHighlight()); + enlargeEmojiOnlyMessages_->setState(settings_->enlargeEmojiOnlyMessages()); + deviceIdValue_->setText(QString::fromStdString(http::client()->device_id())); + timelineMaxWidthSpin_->setValue(settings_->timelineMaxWidth()); + privacyScreenTimeout_->setValue(settings_->privacyScreenTimeout()); - auto mics = CallDevices::instance().names(false, settings_->microphone().toStdString()); - microphoneCombo_->clear(); - for (const auto &m : mics) - microphoneCombo_->addItem(QString::fromStdString(m)); + auto mics = CallDevices::instance().names(false, settings_->microphone().toStdString()); + microphoneCombo_->clear(); + for (const auto &m : mics) + microphoneCombo_->addItem(QString::fromStdString(m)); - auto cameraResolution = settings_->cameraResolution(); - auto cameraFrameRate = settings_->cameraFrameRate(); + auto cameraResolution = settings_->cameraResolution(); + auto cameraFrameRate = settings_->cameraFrameRate(); - auto cameras = CallDevices::instance().names(true, settings_->camera().toStdString()); - cameraCombo_->clear(); - for (const auto &c : cameras) - cameraCombo_->addItem(QString::fromStdString(c)); + auto cameras = CallDevices::instance().names(true, settings_->camera().toStdString()); + cameraCombo_->clear(); + for (const auto &c : cameras) + cameraCombo_->addItem(QString::fromStdString(c)); - utils::restoreCombobox(cameraResolutionCombo_, cameraResolution); - utils::restoreCombobox(cameraFrameRateCombo_, cameraFrameRate); + utils::restoreCombobox(cameraResolutionCombo_, cameraResolution); + utils::restoreCombobox(cameraFrameRateCombo_, cameraFrameRate); - useStunServer_->setState(settings_->useStunServer()); + useStunServer_->setState(settings_->useStunServer()); - deviceFingerprintValue_->setText( - utils::humanReadableFingerprint(olm::client()->identity_keys().ed25519)); + deviceFingerprintValue_->setText( + utils::humanReadableFingerprint(olm::client()->identity_keys().ed25519)); } void UserSettingsPage::paintEvent(QPaintEvent *) { - QStyleOption opt; - opt.init(this); - QPainter p(this); - style()->drawPrimitive(QStyle::PE_Widget, &opt, &p, this); + QStyleOption opt; + opt.init(this); + QPainter p(this); + style()->drawPrimitive(QStyle::PE_Widget, &opt, &p, this); } void UserSettingsPage::importSessionKeys() { - const QString homeFolder = QStandardPaths::writableLocation(QStandardPaths::HomeLocation); - const QString fileName = - QFileDialog::getOpenFileName(this, tr("Open Sessions File"), homeFolder, ""); + const QString homeFolder = QStandardPaths::writableLocation(QStandardPaths::HomeLocation); + const QString fileName = + QFileDialog::getOpenFileName(this, tr("Open Sessions File"), homeFolder, ""); - QFile file(fileName); - if (!file.open(QIODevice::ReadOnly)) { - QMessageBox::warning(this, tr("Error"), file.errorString()); - return; - } + QFile file(fileName); + if (!file.open(QIODevice::ReadOnly)) { + QMessageBox::warning(this, tr("Error"), file.errorString()); + return; + } - auto bin = file.peek(file.size()); - auto payload = std::string(bin.data(), bin.size()); + auto bin = file.peek(file.size()); + auto payload = std::string(bin.data(), bin.size()); - bool ok; - auto password = QInputDialog::getText(this, - tr("File Password"), - tr("Enter the passphrase to decrypt the file:"), - QLineEdit::Password, - "", - &ok); - if (!ok) - return; + bool ok; + auto password = QInputDialog::getText(this, + tr("File Password"), + tr("Enter the passphrase to decrypt the file:"), + QLineEdit::Password, + "", + &ok); + if (!ok) + return; - if (password.isEmpty()) { - QMessageBox::warning(this, tr("Error"), tr("The password cannot be empty")); - return; - } + if (password.isEmpty()) { + QMessageBox::warning(this, tr("Error"), tr("The password cannot be empty")); + return; + } - try { - auto sessions = - mtx::crypto::decrypt_exported_sessions(payload, password.toStdString()); - cache::importSessionKeys(std::move(sessions)); - } catch (const std::exception &e) { - QMessageBox::warning(this, tr("Error"), e.what()); - } + try { + auto sessions = mtx::crypto::decrypt_exported_sessions(payload, password.toStdString()); + cache::importSessionKeys(std::move(sessions)); + } catch (const std::exception &e) { + QMessageBox::warning(this, tr("Error"), e.what()); + } } void UserSettingsPage::exportSessionKeys() { - // Open password dialog. - bool ok; - auto password = QInputDialog::getText(this, - tr("File Password"), - tr("Enter passphrase to encrypt your session keys:"), - QLineEdit::Password, - "", - &ok); - if (!ok) - return; + // Open password dialog. + bool ok; + auto password = QInputDialog::getText(this, + tr("File Password"), + tr("Enter passphrase to encrypt your session keys:"), + QLineEdit::Password, + "", + &ok); + if (!ok) + return; - if (password.isEmpty()) { - QMessageBox::warning(this, tr("Error"), tr("The password cannot be empty")); - return; - } + if (password.isEmpty()) { + QMessageBox::warning(this, tr("Error"), tr("The password cannot be empty")); + return; + } - // Open file dialog to save the file. - const QString homeFolder = QStandardPaths::writableLocation(QStandardPaths::HomeLocation); - const QString fileName = - QFileDialog::getSaveFileName(this, tr("File to save the exported session keys"), "", ""); + // Open file dialog to save the file. + const QString homeFolder = QStandardPaths::writableLocation(QStandardPaths::HomeLocation); + const QString fileName = + QFileDialog::getSaveFileName(this, tr("File to save the exported session keys"), "", ""); - QFile file(fileName); - if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) { - QMessageBox::warning(this, tr("Error"), file.errorString()); - return; - } + QFile file(fileName); + if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) { + QMessageBox::warning(this, tr("Error"), file.errorString()); + return; + } - // Export sessions & save to file. - try { - auto encrypted_blob = mtx::crypto::encrypt_exported_sessions( - cache::exportSessionKeys(), password.toStdString()); + // Export sessions & save to file. + try { + auto encrypted_blob = mtx::crypto::encrypt_exported_sessions(cache::exportSessionKeys(), + password.toStdString()); - QString b64 = QString::fromStdString(mtx::crypto::bin2base64(encrypted_blob)); + QString b64 = QString::fromStdString(mtx::crypto::bin2base64(encrypted_blob)); - QString prefix("-----BEGIN MEGOLM SESSION DATA-----"); - QString suffix("-----END MEGOLM SESSION DATA-----"); - QString newline("\n"); - QTextStream out(&file); - out << prefix << newline << b64 << newline << suffix << newline; - file.close(); - } catch (const std::exception &e) { - QMessageBox::warning(this, tr("Error"), e.what()); - } + QString prefix("-----BEGIN MEGOLM SESSION DATA-----"); + QString suffix("-----END MEGOLM SESSION DATA-----"); + QString newline("\n"); + QTextStream out(&file); + out << prefix << newline << b64 << newline << suffix << newline; + file.close(); + } catch (const std::exception &e) { + QMessageBox::warning(this, tr("Error"), e.what()); + } } void UserSettingsPage::updateSecretStatus() { - QString ok = "QLabel { color : #00cc66; }"; - QString notSoOk = "QLabel { color : #ff9933; }"; + QString ok = "QLabel { color : #00cc66; }"; + QString notSoOk = "QLabel { color : #ff9933; }"; - auto updateLabel = [&ok, ¬SoOk](QLabel *label, const std::string &secretName) { - if (cache::secret(secretName)) { - label->setStyleSheet(ok); - label->setText(tr("CACHED")); - } else { - if (secretName == mtx::secret_storage::secrets::cross_signing_master) - label->setStyleSheet(ok); - else - label->setStyleSheet(notSoOk); - label->setText(tr("NOT CACHED")); - } - }; + auto updateLabel = [&ok, ¬SoOk](QLabel *label, const std::string &secretName) { + if (cache::secret(secretName)) { + label->setStyleSheet(ok); + label->setText(tr("CACHED")); + } else { + if (secretName == mtx::secret_storage::secrets::cross_signing_master) + label->setStyleSheet(ok); + else + label->setStyleSheet(notSoOk); + label->setText(tr("NOT CACHED")); + } + }; - updateLabel(masterSecretCached, mtx::secret_storage::secrets::cross_signing_master); - updateLabel(userSigningSecretCached, - mtx::secret_storage::secrets::cross_signing_user_signing); - updateLabel(selfSigningSecretCached, - mtx::secret_storage::secrets::cross_signing_self_signing); - updateLabel(backupSecretCached, mtx::secret_storage::secrets::megolm_backup_v1); + updateLabel(masterSecretCached, mtx::secret_storage::secrets::cross_signing_master); + updateLabel(userSigningSecretCached, mtx::secret_storage::secrets::cross_signing_user_signing); + updateLabel(selfSigningSecretCached, mtx::secret_storage::secrets::cross_signing_self_signing); + updateLabel(backupSecretCached, mtx::secret_storage::secrets::megolm_backup_v1); } diff --git a/src/UserSettingsPage.h b/src/UserSettingsPage.h index bcd9439b..31e28db2 100644 --- a/src/UserSettingsPage.h +++ b/src/UserSettingsPage.h @@ -30,402 +30,395 @@ constexpr int LayoutBottomMargin = LayoutTopMargin; class UserSettings : public QObject { - Q_OBJECT + Q_OBJECT - Q_PROPERTY(QString theme READ theme WRITE setTheme NOTIFY themeChanged) - Q_PROPERTY(bool messageHoverHighlight READ messageHoverHighlight WRITE - setMessageHoverHighlight NOTIFY messageHoverHighlightChanged) - Q_PROPERTY(bool enlargeEmojiOnlyMessages READ enlargeEmojiOnlyMessages WRITE - setEnlargeEmojiOnlyMessages NOTIFY enlargeEmojiOnlyMessagesChanged) - Q_PROPERTY(bool tray READ tray WRITE setTray NOTIFY trayChanged) - Q_PROPERTY(bool startInTray READ startInTray WRITE setStartInTray NOTIFY startInTrayChanged) - Q_PROPERTY(bool groupView READ groupView WRITE setGroupView NOTIFY groupViewStateChanged) - Q_PROPERTY(bool markdown READ markdown WRITE setMarkdown NOTIFY markdownChanged) - Q_PROPERTY(bool animateImagesOnHover READ animateImagesOnHover WRITE setAnimateImagesOnHover - NOTIFY animateImagesOnHoverChanged) - Q_PROPERTY(bool typingNotifications READ typingNotifications WRITE setTypingNotifications - NOTIFY typingNotificationsChanged) - Q_PROPERTY(bool sortByImportance READ sortByImportance WRITE setSortByImportance NOTIFY - roomSortingChanged) - Q_PROPERTY(bool buttonsInTimeline READ buttonsInTimeline WRITE setButtonsInTimeline NOTIFY - buttonInTimelineChanged) - Q_PROPERTY( - bool readReceipts READ readReceipts WRITE setReadReceipts NOTIFY readReceiptsChanged) - Q_PROPERTY(bool desktopNotifications READ hasDesktopNotifications WRITE - setDesktopNotifications NOTIFY desktopNotificationsChanged) - Q_PROPERTY(bool alertOnNotification READ hasAlertOnNotification WRITE setAlertOnNotification - NOTIFY alertOnNotificationChanged) - Q_PROPERTY( - bool avatarCircles READ avatarCircles WRITE setAvatarCircles NOTIFY avatarCirclesChanged) - Q_PROPERTY(bool decryptSidebar READ decryptSidebar WRITE setDecryptSidebar NOTIFY - decryptSidebarChanged) - Q_PROPERTY( - bool privacyScreen READ privacyScreen WRITE setPrivacyScreen NOTIFY privacyScreenChanged) - Q_PROPERTY(int privacyScreenTimeout READ privacyScreenTimeout WRITE setPrivacyScreenTimeout - NOTIFY privacyScreenTimeoutChanged) - Q_PROPERTY(int timelineMaxWidth READ timelineMaxWidth WRITE setTimelineMaxWidth NOTIFY - timelineMaxWidthChanged) - Q_PROPERTY( - int roomListWidth READ roomListWidth WRITE setRoomListWidth NOTIFY roomListWidthChanged) - Q_PROPERTY(int communityListWidth READ communityListWidth WRITE setCommunityListWidth NOTIFY - communityListWidthChanged) - Q_PROPERTY(bool mobileMode READ mobileMode WRITE setMobileMode NOTIFY mobileModeChanged) - Q_PROPERTY(double fontSize READ fontSize WRITE setFontSize NOTIFY fontSizeChanged) - Q_PROPERTY(QString font READ font WRITE setFontFamily NOTIFY fontChanged) - Q_PROPERTY( - QString emojiFont READ emojiFont WRITE setEmojiFontFamily NOTIFY emojiFontChanged) - Q_PROPERTY(Presence presence READ presence WRITE setPresence NOTIFY presenceChanged) - Q_PROPERTY(QString ringtone READ ringtone WRITE setRingtone NOTIFY ringtoneChanged) - Q_PROPERTY(QString microphone READ microphone WRITE setMicrophone NOTIFY microphoneChanged) - Q_PROPERTY(QString camera READ camera WRITE setCamera NOTIFY cameraChanged) - Q_PROPERTY(QString cameraResolution READ cameraResolution WRITE setCameraResolution NOTIFY - cameraResolutionChanged) - Q_PROPERTY(QString cameraFrameRate READ cameraFrameRate WRITE setCameraFrameRate NOTIFY - cameraFrameRateChanged) - Q_PROPERTY(int screenShareFrameRate READ screenShareFrameRate WRITE setScreenShareFrameRate - NOTIFY screenShareFrameRateChanged) - Q_PROPERTY(bool screenSharePiP READ screenSharePiP WRITE setScreenSharePiP NOTIFY - screenSharePiPChanged) - Q_PROPERTY(bool screenShareRemoteVideo READ screenShareRemoteVideo WRITE - setScreenShareRemoteVideo NOTIFY screenShareRemoteVideoChanged) - Q_PROPERTY(bool screenShareHideCursor READ screenShareHideCursor WRITE - setScreenShareHideCursor NOTIFY screenShareHideCursorChanged) - Q_PROPERTY( - bool useStunServer READ useStunServer WRITE setUseStunServer NOTIFY useStunServerChanged) - Q_PROPERTY(bool onlyShareKeysWithVerifiedUsers READ onlyShareKeysWithVerifiedUsers WRITE - setOnlyShareKeysWithVerifiedUsers NOTIFY onlyShareKeysWithVerifiedUsersChanged) - Q_PROPERTY(bool shareKeysWithTrustedUsers READ shareKeysWithTrustedUsers WRITE - setShareKeysWithTrustedUsers NOTIFY shareKeysWithTrustedUsersChanged) - Q_PROPERTY(bool useOnlineKeyBackup READ useOnlineKeyBackup WRITE setUseOnlineKeyBackup - NOTIFY useOnlineKeyBackupChanged) - Q_PROPERTY(QString profile READ profile WRITE setProfile NOTIFY profileChanged) - Q_PROPERTY(QString userId READ userId WRITE setUserId NOTIFY userIdChanged) - Q_PROPERTY( - QString accessToken READ accessToken WRITE setAccessToken NOTIFY accessTokenChanged) - Q_PROPERTY(QString deviceId READ deviceId WRITE setDeviceId NOTIFY deviceIdChanged) - Q_PROPERTY(QString homeserver READ homeserver WRITE setHomeserver NOTIFY homeserverChanged) - Q_PROPERTY(bool disableCertificateValidation READ disableCertificateValidation WRITE - setDisableCertificateValidation NOTIFY disableCertificateValidationChanged) - Q_PROPERTY( - bool useIdenticon READ useIdenticon WRITE setUseIdenticon NOTIFY useIdenticonChanged) + Q_PROPERTY(QString theme READ theme WRITE setTheme NOTIFY themeChanged) + Q_PROPERTY(bool messageHoverHighlight READ messageHoverHighlight WRITE setMessageHoverHighlight + NOTIFY messageHoverHighlightChanged) + Q_PROPERTY(bool enlargeEmojiOnlyMessages READ enlargeEmojiOnlyMessages WRITE + setEnlargeEmojiOnlyMessages NOTIFY enlargeEmojiOnlyMessagesChanged) + Q_PROPERTY(bool tray READ tray WRITE setTray NOTIFY trayChanged) + Q_PROPERTY(bool startInTray READ startInTray WRITE setStartInTray NOTIFY startInTrayChanged) + Q_PROPERTY(bool groupView READ groupView WRITE setGroupView NOTIFY groupViewStateChanged) + Q_PROPERTY(bool markdown READ markdown WRITE setMarkdown NOTIFY markdownChanged) + Q_PROPERTY(bool animateImagesOnHover READ animateImagesOnHover WRITE setAnimateImagesOnHover + NOTIFY animateImagesOnHoverChanged) + Q_PROPERTY(bool typingNotifications READ typingNotifications WRITE setTypingNotifications NOTIFY + typingNotificationsChanged) + Q_PROPERTY(bool sortByImportance READ sortByImportance WRITE setSortByImportance NOTIFY + roomSortingChanged) + Q_PROPERTY(bool buttonsInTimeline READ buttonsInTimeline WRITE setButtonsInTimeline NOTIFY + buttonInTimelineChanged) + Q_PROPERTY(bool readReceipts READ readReceipts WRITE setReadReceipts NOTIFY readReceiptsChanged) + Q_PROPERTY(bool desktopNotifications READ hasDesktopNotifications WRITE setDesktopNotifications + NOTIFY desktopNotificationsChanged) + Q_PROPERTY(bool alertOnNotification READ hasAlertOnNotification WRITE setAlertOnNotification + NOTIFY alertOnNotificationChanged) + Q_PROPERTY( + bool avatarCircles READ avatarCircles WRITE setAvatarCircles NOTIFY avatarCirclesChanged) + Q_PROPERTY( + bool decryptSidebar READ decryptSidebar WRITE setDecryptSidebar NOTIFY decryptSidebarChanged) + Q_PROPERTY( + bool privacyScreen READ privacyScreen WRITE setPrivacyScreen NOTIFY privacyScreenChanged) + Q_PROPERTY(int privacyScreenTimeout READ privacyScreenTimeout WRITE setPrivacyScreenTimeout + NOTIFY privacyScreenTimeoutChanged) + Q_PROPERTY(int timelineMaxWidth READ timelineMaxWidth WRITE setTimelineMaxWidth NOTIFY + timelineMaxWidthChanged) + Q_PROPERTY( + int roomListWidth READ roomListWidth WRITE setRoomListWidth NOTIFY roomListWidthChanged) + Q_PROPERTY(int communityListWidth READ communityListWidth WRITE setCommunityListWidth NOTIFY + communityListWidthChanged) + Q_PROPERTY(bool mobileMode READ mobileMode WRITE setMobileMode NOTIFY mobileModeChanged) + Q_PROPERTY(double fontSize READ fontSize WRITE setFontSize NOTIFY fontSizeChanged) + Q_PROPERTY(QString font READ font WRITE setFontFamily NOTIFY fontChanged) + Q_PROPERTY(QString emojiFont READ emojiFont WRITE setEmojiFontFamily NOTIFY emojiFontChanged) + Q_PROPERTY(Presence presence READ presence WRITE setPresence NOTIFY presenceChanged) + Q_PROPERTY(QString ringtone READ ringtone WRITE setRingtone NOTIFY ringtoneChanged) + Q_PROPERTY(QString microphone READ microphone WRITE setMicrophone NOTIFY microphoneChanged) + Q_PROPERTY(QString camera READ camera WRITE setCamera NOTIFY cameraChanged) + Q_PROPERTY(QString cameraResolution READ cameraResolution WRITE setCameraResolution NOTIFY + cameraResolutionChanged) + Q_PROPERTY(QString cameraFrameRate READ cameraFrameRate WRITE setCameraFrameRate NOTIFY + cameraFrameRateChanged) + Q_PROPERTY(int screenShareFrameRate READ screenShareFrameRate WRITE setScreenShareFrameRate + NOTIFY screenShareFrameRateChanged) + Q_PROPERTY( + bool screenSharePiP READ screenSharePiP WRITE setScreenSharePiP NOTIFY screenSharePiPChanged) + Q_PROPERTY(bool screenShareRemoteVideo READ screenShareRemoteVideo WRITE + setScreenShareRemoteVideo NOTIFY screenShareRemoteVideoChanged) + Q_PROPERTY(bool screenShareHideCursor READ screenShareHideCursor WRITE setScreenShareHideCursor + NOTIFY screenShareHideCursorChanged) + Q_PROPERTY( + bool useStunServer READ useStunServer WRITE setUseStunServer NOTIFY useStunServerChanged) + Q_PROPERTY(bool onlyShareKeysWithVerifiedUsers READ onlyShareKeysWithVerifiedUsers WRITE + setOnlyShareKeysWithVerifiedUsers NOTIFY onlyShareKeysWithVerifiedUsersChanged) + Q_PROPERTY(bool shareKeysWithTrustedUsers READ shareKeysWithTrustedUsers WRITE + setShareKeysWithTrustedUsers NOTIFY shareKeysWithTrustedUsersChanged) + Q_PROPERTY(bool useOnlineKeyBackup READ useOnlineKeyBackup WRITE setUseOnlineKeyBackup NOTIFY + useOnlineKeyBackupChanged) + Q_PROPERTY(QString profile READ profile WRITE setProfile NOTIFY profileChanged) + Q_PROPERTY(QString userId READ userId WRITE setUserId NOTIFY userIdChanged) + Q_PROPERTY(QString accessToken READ accessToken WRITE setAccessToken NOTIFY accessTokenChanged) + Q_PROPERTY(QString deviceId READ deviceId WRITE setDeviceId NOTIFY deviceIdChanged) + Q_PROPERTY(QString homeserver READ homeserver WRITE setHomeserver NOTIFY homeserverChanged) + Q_PROPERTY(bool disableCertificateValidation READ disableCertificateValidation WRITE + setDisableCertificateValidation NOTIFY disableCertificateValidationChanged) + Q_PROPERTY(bool useIdenticon READ useIdenticon WRITE setUseIdenticon NOTIFY useIdenticonChanged) - UserSettings(); + UserSettings(); public: - static QSharedPointer instance(); - static void initialize(std::optional profile); + static QSharedPointer instance(); + static void initialize(std::optional profile); - QSettings *qsettings() { return &settings; } + QSettings *qsettings() { return &settings; } - enum class Presence - { - AutomaticPresence, - Online, - Unavailable, - Offline, - }; - Q_ENUM(Presence) + enum class Presence + { + AutomaticPresence, + Online, + Unavailable, + Offline, + }; + Q_ENUM(Presence) - void save(); - void load(std::optional profile); - void applyTheme(); - void setTheme(QString theme); - void setMessageHoverHighlight(bool state); - void setEnlargeEmojiOnlyMessages(bool state); - void setTray(bool state); - void setStartInTray(bool state); - void setMobileMode(bool mode); - void setFontSize(double size); - void setFontFamily(QString family); - void setEmojiFontFamily(QString family); - void setGroupView(bool state); - void setMarkdown(bool state); - void setAnimateImagesOnHover(bool state); - void setReadReceipts(bool state); - void setTypingNotifications(bool state); - void setSortByImportance(bool state); - void setButtonsInTimeline(bool state); - void setTimelineMaxWidth(int state); - void setCommunityListWidth(int state); - void setRoomListWidth(int state); - void setDesktopNotifications(bool state); - void setAlertOnNotification(bool state); - void setAvatarCircles(bool state); - void setDecryptSidebar(bool state); - void setPrivacyScreen(bool state); - void setPrivacyScreenTimeout(int state); - void setPresence(Presence state); - void setRingtone(QString ringtone); - void setMicrophone(QString microphone); - void setCamera(QString camera); - void setCameraResolution(QString resolution); - void setCameraFrameRate(QString frameRate); - void setScreenShareFrameRate(int frameRate); - void setScreenSharePiP(bool state); - void setScreenShareRemoteVideo(bool state); - void setScreenShareHideCursor(bool state); - void setUseStunServer(bool state); - void setOnlyShareKeysWithVerifiedUsers(bool state); - void setShareKeysWithTrustedUsers(bool state); - void setUseOnlineKeyBackup(bool state); - void setProfile(QString profile); - void setUserId(QString userId); - void setAccessToken(QString accessToken); - void setDeviceId(QString deviceId); - void setHomeserver(QString homeserver); - void setDisableCertificateValidation(bool disabled); - void setHiddenTags(QStringList hiddenTags); - void setUseIdenticon(bool state); + void save(); + void load(std::optional profile); + void applyTheme(); + void setTheme(QString theme); + void setMessageHoverHighlight(bool state); + void setEnlargeEmojiOnlyMessages(bool state); + void setTray(bool state); + void setStartInTray(bool state); + void setMobileMode(bool mode); + void setFontSize(double size); + void setFontFamily(QString family); + void setEmojiFontFamily(QString family); + void setGroupView(bool state); + void setMarkdown(bool state); + void setAnimateImagesOnHover(bool state); + void setReadReceipts(bool state); + void setTypingNotifications(bool state); + void setSortByImportance(bool state); + void setButtonsInTimeline(bool state); + void setTimelineMaxWidth(int state); + void setCommunityListWidth(int state); + void setRoomListWidth(int state); + void setDesktopNotifications(bool state); + void setAlertOnNotification(bool state); + void setAvatarCircles(bool state); + void setDecryptSidebar(bool state); + void setPrivacyScreen(bool state); + void setPrivacyScreenTimeout(int state); + void setPresence(Presence state); + void setRingtone(QString ringtone); + void setMicrophone(QString microphone); + void setCamera(QString camera); + void setCameraResolution(QString resolution); + void setCameraFrameRate(QString frameRate); + void setScreenShareFrameRate(int frameRate); + void setScreenSharePiP(bool state); + void setScreenShareRemoteVideo(bool state); + void setScreenShareHideCursor(bool state); + void setUseStunServer(bool state); + void setOnlyShareKeysWithVerifiedUsers(bool state); + void setShareKeysWithTrustedUsers(bool state); + void setUseOnlineKeyBackup(bool state); + void setProfile(QString profile); + void setUserId(QString userId); + void setAccessToken(QString accessToken); + void setDeviceId(QString deviceId); + void setHomeserver(QString homeserver); + void setDisableCertificateValidation(bool disabled); + void setHiddenTags(QStringList hiddenTags); + void setUseIdenticon(bool state); - QString theme() const { return !theme_.isEmpty() ? theme_ : defaultTheme_; } - bool messageHoverHighlight() const { return messageHoverHighlight_; } - bool enlargeEmojiOnlyMessages() const { return enlargeEmojiOnlyMessages_; } - bool tray() const { return tray_; } - bool startInTray() const { return startInTray_; } - bool groupView() const { return groupView_; } - bool avatarCircles() const { return avatarCircles_; } - bool decryptSidebar() const { return decryptSidebar_; } - bool privacyScreen() const { return privacyScreen_; } - int privacyScreenTimeout() const { return privacyScreenTimeout_; } - bool markdown() const { return markdown_; } - bool animateImagesOnHover() const { return animateImagesOnHover_; } - bool typingNotifications() const { return typingNotifications_; } - bool sortByImportance() const { return sortByImportance_; } - bool buttonsInTimeline() const { return buttonsInTimeline_; } - bool mobileMode() const { return mobileMode_; } - bool readReceipts() const { return readReceipts_; } - bool hasDesktopNotifications() const { return hasDesktopNotifications_; } - bool hasAlertOnNotification() const { return hasAlertOnNotification_; } - bool hasNotifications() const - { - return hasDesktopNotifications() || hasAlertOnNotification(); + QString theme() const { return !theme_.isEmpty() ? theme_ : defaultTheme_; } + bool messageHoverHighlight() const { return messageHoverHighlight_; } + bool enlargeEmojiOnlyMessages() const { return enlargeEmojiOnlyMessages_; } + bool tray() const { return tray_; } + bool startInTray() const { return startInTray_; } + bool groupView() const { return groupView_; } + bool avatarCircles() const { return avatarCircles_; } + bool decryptSidebar() const { return decryptSidebar_; } + bool privacyScreen() const { return privacyScreen_; } + int privacyScreenTimeout() const { return privacyScreenTimeout_; } + bool markdown() const { return markdown_; } + bool animateImagesOnHover() const { return animateImagesOnHover_; } + bool typingNotifications() const { return typingNotifications_; } + bool sortByImportance() const { return sortByImportance_; } + bool buttonsInTimeline() const { return buttonsInTimeline_; } + bool mobileMode() const { return mobileMode_; } + bool readReceipts() const { return readReceipts_; } + bool hasDesktopNotifications() const { return hasDesktopNotifications_; } + bool hasAlertOnNotification() const { return hasAlertOnNotification_; } + bool hasNotifications() const { return hasDesktopNotifications() || hasAlertOnNotification(); } + int timelineMaxWidth() const { return timelineMaxWidth_; } + int communityListWidth() const { return communityListWidth_; } + int roomListWidth() const { return roomListWidth_; } + double fontSize() const { return baseFontSize_; } + QString font() const { return font_; } + QString emojiFont() const + { + if (emojiFont_ == "Default") { + return tr("Default"); } - int timelineMaxWidth() const { return timelineMaxWidth_; } - int communityListWidth() const { return communityListWidth_; } - int roomListWidth() const { return roomListWidth_; } - double fontSize() const { return baseFontSize_; } - QString font() const { return font_; } - QString emojiFont() const - { - if (emojiFont_ == "Default") { - return tr("Default"); - } - return emojiFont_; - } - Presence presence() const { return presence_; } - QString ringtone() const { return ringtone_; } - QString microphone() const { return microphone_; } - QString camera() const { return camera_; } - QString cameraResolution() const { return cameraResolution_; } - QString cameraFrameRate() const { return cameraFrameRate_; } - int screenShareFrameRate() const { return screenShareFrameRate_; } - bool screenSharePiP() const { return screenSharePiP_; } - bool screenShareRemoteVideo() const { return screenShareRemoteVideo_; } - bool screenShareHideCursor() const { return screenShareHideCursor_; } - bool useStunServer() const { return useStunServer_; } - bool shareKeysWithTrustedUsers() const { return shareKeysWithTrustedUsers_; } - bool onlyShareKeysWithVerifiedUsers() const { return onlyShareKeysWithVerifiedUsers_; } - bool useOnlineKeyBackup() const { return useOnlineKeyBackup_; } - QString profile() const { return profile_; } - QString userId() const { return userId_; } - QString accessToken() const { return accessToken_; } - QString deviceId() const { return deviceId_; } - QString homeserver() const { return homeserver_; } - bool disableCertificateValidation() const { return disableCertificateValidation_; } - QStringList hiddenTags() const { return hiddenTags_; } - bool useIdenticon() const { return useIdenticon_ && JdenticonProvider::isAvailable(); } + return emojiFont_; + } + Presence presence() const { return presence_; } + QString ringtone() const { return ringtone_; } + QString microphone() const { return microphone_; } + QString camera() const { return camera_; } + QString cameraResolution() const { return cameraResolution_; } + QString cameraFrameRate() const { return cameraFrameRate_; } + int screenShareFrameRate() const { return screenShareFrameRate_; } + bool screenSharePiP() const { return screenSharePiP_; } + bool screenShareRemoteVideo() const { return screenShareRemoteVideo_; } + bool screenShareHideCursor() const { return screenShareHideCursor_; } + bool useStunServer() const { return useStunServer_; } + bool shareKeysWithTrustedUsers() const { return shareKeysWithTrustedUsers_; } + bool onlyShareKeysWithVerifiedUsers() const { return onlyShareKeysWithVerifiedUsers_; } + bool useOnlineKeyBackup() const { return useOnlineKeyBackup_; } + QString profile() const { return profile_; } + QString userId() const { return userId_; } + QString accessToken() const { return accessToken_; } + QString deviceId() const { return deviceId_; } + QString homeserver() const { return homeserver_; } + bool disableCertificateValidation() const { return disableCertificateValidation_; } + QStringList hiddenTags() const { return hiddenTags_; } + bool useIdenticon() const { return useIdenticon_ && JdenticonProvider::isAvailable(); } signals: - void groupViewStateChanged(bool state); - void roomSortingChanged(bool state); - void themeChanged(QString state); - void messageHoverHighlightChanged(bool state); - void enlargeEmojiOnlyMessagesChanged(bool state); - void trayChanged(bool state); - void startInTrayChanged(bool state); - void markdownChanged(bool state); - void animateImagesOnHoverChanged(bool state); - void typingNotificationsChanged(bool state); - void buttonInTimelineChanged(bool state); - void readReceiptsChanged(bool state); - void desktopNotificationsChanged(bool state); - void alertOnNotificationChanged(bool state); - void avatarCirclesChanged(bool state); - void decryptSidebarChanged(bool state); - void privacyScreenChanged(bool state); - void privacyScreenTimeoutChanged(int state); - void timelineMaxWidthChanged(int state); - void roomListWidthChanged(int state); - void communityListWidthChanged(int state); - void mobileModeChanged(bool mode); - void fontSizeChanged(double state); - void fontChanged(QString state); - void emojiFontChanged(QString state); - void presenceChanged(Presence state); - void ringtoneChanged(QString ringtone); - void microphoneChanged(QString microphone); - void cameraChanged(QString camera); - void cameraResolutionChanged(QString resolution); - void cameraFrameRateChanged(QString frameRate); - void screenShareFrameRateChanged(int frameRate); - void screenSharePiPChanged(bool state); - void screenShareRemoteVideoChanged(bool state); - void screenShareHideCursorChanged(bool state); - void useStunServerChanged(bool state); - void onlyShareKeysWithVerifiedUsersChanged(bool state); - void shareKeysWithTrustedUsersChanged(bool state); - void useOnlineKeyBackupChanged(bool state); - void profileChanged(QString profile); - void userIdChanged(QString userId); - void accessTokenChanged(QString accessToken); - void deviceIdChanged(QString deviceId); - void homeserverChanged(QString homeserver); - void disableCertificateValidationChanged(bool disabled); - void useIdenticonChanged(bool state); + void groupViewStateChanged(bool state); + void roomSortingChanged(bool state); + void themeChanged(QString state); + void messageHoverHighlightChanged(bool state); + void enlargeEmojiOnlyMessagesChanged(bool state); + void trayChanged(bool state); + void startInTrayChanged(bool state); + void markdownChanged(bool state); + void animateImagesOnHoverChanged(bool state); + void typingNotificationsChanged(bool state); + void buttonInTimelineChanged(bool state); + void readReceiptsChanged(bool state); + void desktopNotificationsChanged(bool state); + void alertOnNotificationChanged(bool state); + void avatarCirclesChanged(bool state); + void decryptSidebarChanged(bool state); + void privacyScreenChanged(bool state); + void privacyScreenTimeoutChanged(int state); + void timelineMaxWidthChanged(int state); + void roomListWidthChanged(int state); + void communityListWidthChanged(int state); + void mobileModeChanged(bool mode); + void fontSizeChanged(double state); + void fontChanged(QString state); + void emojiFontChanged(QString state); + void presenceChanged(Presence state); + void ringtoneChanged(QString ringtone); + void microphoneChanged(QString microphone); + void cameraChanged(QString camera); + void cameraResolutionChanged(QString resolution); + void cameraFrameRateChanged(QString frameRate); + void screenShareFrameRateChanged(int frameRate); + void screenSharePiPChanged(bool state); + void screenShareRemoteVideoChanged(bool state); + void screenShareHideCursorChanged(bool state); + void useStunServerChanged(bool state); + void onlyShareKeysWithVerifiedUsersChanged(bool state); + void shareKeysWithTrustedUsersChanged(bool state); + void useOnlineKeyBackupChanged(bool state); + void profileChanged(QString profile); + void userIdChanged(QString userId); + void accessTokenChanged(QString accessToken); + void deviceIdChanged(QString deviceId); + void homeserverChanged(QString homeserver); + void disableCertificateValidationChanged(bool disabled); + void useIdenticonChanged(bool state); private: - // Default to system theme if QT_QPA_PLATFORMTHEME var is set. - QString defaultTheme_ = - QProcessEnvironment::systemEnvironment().value("QT_QPA_PLATFORMTHEME", "").isEmpty() - ? "light" - : "system"; - QString theme_; - bool messageHoverHighlight_; - bool enlargeEmojiOnlyMessages_; - bool tray_; - bool startInTray_; - bool groupView_; - bool markdown_; - bool animateImagesOnHover_; - bool typingNotifications_; - bool sortByImportance_; - bool buttonsInTimeline_; - bool readReceipts_; - bool hasDesktopNotifications_; - bool hasAlertOnNotification_; - bool avatarCircles_; - bool decryptSidebar_; - bool privacyScreen_; - int privacyScreenTimeout_; - bool shareKeysWithTrustedUsers_; - bool onlyShareKeysWithVerifiedUsers_; - bool useOnlineKeyBackup_; - bool mobileMode_; - int timelineMaxWidth_; - int roomListWidth_; - int communityListWidth_; - double baseFontSize_; - QString font_; - QString emojiFont_; - Presence presence_; - QString ringtone_; - QString microphone_; - QString camera_; - QString cameraResolution_; - QString cameraFrameRate_; - int screenShareFrameRate_; - bool screenSharePiP_; - bool screenShareRemoteVideo_; - bool screenShareHideCursor_; - bool useStunServer_; - bool disableCertificateValidation_ = false; - QString profile_; - QString userId_; - QString accessToken_; - QString deviceId_; - QString homeserver_; - QStringList hiddenTags_; - bool useIdenticon_; + // Default to system theme if QT_QPA_PLATFORMTHEME var is set. + QString defaultTheme_ = + QProcessEnvironment::systemEnvironment().value("QT_QPA_PLATFORMTHEME", "").isEmpty() + ? "light" + : "system"; + QString theme_; + bool messageHoverHighlight_; + bool enlargeEmojiOnlyMessages_; + bool tray_; + bool startInTray_; + bool groupView_; + bool markdown_; + bool animateImagesOnHover_; + bool typingNotifications_; + bool sortByImportance_; + bool buttonsInTimeline_; + bool readReceipts_; + bool hasDesktopNotifications_; + bool hasAlertOnNotification_; + bool avatarCircles_; + bool decryptSidebar_; + bool privacyScreen_; + int privacyScreenTimeout_; + bool shareKeysWithTrustedUsers_; + bool onlyShareKeysWithVerifiedUsers_; + bool useOnlineKeyBackup_; + bool mobileMode_; + int timelineMaxWidth_; + int roomListWidth_; + int communityListWidth_; + double baseFontSize_; + QString font_; + QString emojiFont_; + Presence presence_; + QString ringtone_; + QString microphone_; + QString camera_; + QString cameraResolution_; + QString cameraFrameRate_; + int screenShareFrameRate_; + bool screenSharePiP_; + bool screenShareRemoteVideo_; + bool screenShareHideCursor_; + bool useStunServer_; + bool disableCertificateValidation_ = false; + QString profile_; + QString userId_; + QString accessToken_; + QString deviceId_; + QString homeserver_; + QStringList hiddenTags_; + bool useIdenticon_; - QSettings settings; + QSettings settings; - static QSharedPointer instance_; + static QSharedPointer instance_; }; class HorizontalLine : public QFrame { - Q_OBJECT + Q_OBJECT public: - HorizontalLine(QWidget *parent = nullptr); + HorizontalLine(QWidget *parent = nullptr); }; class UserSettingsPage : public QWidget { - Q_OBJECT + Q_OBJECT public: - UserSettingsPage(QSharedPointer settings, QWidget *parent = nullptr); + UserSettingsPage(QSharedPointer settings, QWidget *parent = nullptr); protected: - void showEvent(QShowEvent *event) override; - void paintEvent(QPaintEvent *event) override; + void showEvent(QShowEvent *event) override; + void paintEvent(QPaintEvent *event) override; signals: - void moveBack(); - void trayOptionChanged(bool value); - void themeChanged(); - void decryptSidebarChanged(); + void moveBack(); + void trayOptionChanged(bool value); + void themeChanged(); + void decryptSidebarChanged(); public slots: - void updateSecretStatus(); + void updateSecretStatus(); private slots: - void importSessionKeys(); - void exportSessionKeys(); + void importSessionKeys(); + void exportSessionKeys(); private: - // Layouts - QVBoxLayout *topLayout_; - QHBoxLayout *topBarLayout_; - QFormLayout *formLayout_; + // Layouts + QVBoxLayout *topLayout_; + QHBoxLayout *topBarLayout_; + QFormLayout *formLayout_; - // Shared settings object. - QSharedPointer settings_; + // Shared settings object. + QSharedPointer settings_; - Toggle *trayToggle_; - Toggle *startInTrayToggle_; - Toggle *groupViewToggle_; - Toggle *timelineButtonsToggle_; - Toggle *typingNotifications_; - Toggle *messageHoverHighlight_; - Toggle *enlargeEmojiOnlyMessages_; - Toggle *sortByImportance_; - Toggle *readReceipts_; - Toggle *markdown_; - Toggle *animateImagesOnHover_; - Toggle *desktopNotifications_; - Toggle *alertOnNotification_; - Toggle *avatarCircles_; - Toggle *useIdenticon_; - Toggle *useStunServer_; - Toggle *decryptSidebar_; - Toggle *privacyScreen_; - QSpinBox *privacyScreenTimeout_; - Toggle *shareKeysWithTrustedUsers_; - Toggle *onlyShareKeysWithVerifiedUsers_; - Toggle *useOnlineKeyBackup_; - Toggle *mobileMode_; - QLabel *deviceFingerprintValue_; - QLabel *deviceIdValue_; - QLabel *backupSecretCached; - QLabel *masterSecretCached; - QLabel *selfSigningSecretCached; - QLabel *userSigningSecretCached; + Toggle *trayToggle_; + Toggle *startInTrayToggle_; + Toggle *groupViewToggle_; + Toggle *timelineButtonsToggle_; + Toggle *typingNotifications_; + Toggle *messageHoverHighlight_; + Toggle *enlargeEmojiOnlyMessages_; + Toggle *sortByImportance_; + Toggle *readReceipts_; + Toggle *markdown_; + Toggle *animateImagesOnHover_; + Toggle *desktopNotifications_; + Toggle *alertOnNotification_; + Toggle *avatarCircles_; + Toggle *useIdenticon_; + Toggle *useStunServer_; + Toggle *decryptSidebar_; + Toggle *privacyScreen_; + QSpinBox *privacyScreenTimeout_; + Toggle *shareKeysWithTrustedUsers_; + Toggle *onlyShareKeysWithVerifiedUsers_; + Toggle *useOnlineKeyBackup_; + Toggle *mobileMode_; + QLabel *deviceFingerprintValue_; + QLabel *deviceIdValue_; + QLabel *backupSecretCached; + QLabel *masterSecretCached; + QLabel *selfSigningSecretCached; + QLabel *userSigningSecretCached; - QComboBox *themeCombo_; - QComboBox *scaleFactorCombo_; - QComboBox *fontSizeCombo_; - QFontComboBox *fontSelectionCombo_; - QComboBox *emojiFontSelectionCombo_; - QComboBox *ringtoneCombo_; - QComboBox *microphoneCombo_; - QComboBox *cameraCombo_; - QComboBox *cameraResolutionCombo_; - QComboBox *cameraFrameRateCombo_; + QComboBox *themeCombo_; + QComboBox *scaleFactorCombo_; + QComboBox *fontSizeCombo_; + QFontComboBox *fontSelectionCombo_; + QComboBox *emojiFontSelectionCombo_; + QComboBox *ringtoneCombo_; + QComboBox *microphoneCombo_; + QComboBox *cameraCombo_; + QComboBox *cameraResolutionCombo_; + QComboBox *cameraFrameRateCombo_; - QSpinBox *timelineMaxWidthSpin_; + QSpinBox *timelineMaxWidthSpin_; - int sideMargin_ = 0; + int sideMargin_ = 0; }; diff --git a/src/UsersModel.cpp b/src/UsersModel.cpp index 13b05f0e..f82353cc 100644 --- a/src/UsersModel.cpp +++ b/src/UsersModel.cpp @@ -14,51 +14,51 @@ UsersModel::UsersModel(const std::string &roomId, QObject *parent) : QAbstractListModel(parent) , room_id(roomId) { - roomMembers_ = cache::roomMembers(roomId); - for (const auto &m : roomMembers_) { - displayNames.push_back(QString::fromStdString(cache::displayName(room_id, m))); - userids.push_back(QString::fromStdString(m)); - } + roomMembers_ = cache::roomMembers(roomId); + for (const auto &m : roomMembers_) { + displayNames.push_back(QString::fromStdString(cache::displayName(room_id, m))); + userids.push_back(QString::fromStdString(m)); + } } QHash UsersModel::roleNames() const { - return { - {CompletionModel::CompletionRole, "completionRole"}, - {CompletionModel::SearchRole, "searchRole"}, - {CompletionModel::SearchRole2, "searchRole2"}, - {Roles::DisplayName, "displayName"}, - {Roles::AvatarUrl, "avatarUrl"}, - {Roles::UserID, "userid"}, - }; + return { + {CompletionModel::CompletionRole, "completionRole"}, + {CompletionModel::SearchRole, "searchRole"}, + {CompletionModel::SearchRole2, "searchRole2"}, + {Roles::DisplayName, "displayName"}, + {Roles::AvatarUrl, "avatarUrl"}, + {Roles::UserID, "userid"}, + }; } QVariant UsersModel::data(const QModelIndex &index, int role) const { - if (hasIndex(index.row(), index.column(), index.parent())) { - switch (role) { - case CompletionModel::CompletionRole: - if (UserSettings::instance()->markdown()) - return QString("[%1](https://matrix.to/#/%2)") - .arg(displayNames[index.row()].toHtmlEscaped()) - .arg(QString(QUrl::toPercentEncoding(userids[index.row()]))); - else - return displayNames[index.row()]; - case CompletionModel::SearchRole: - return displayNames[index.row()]; - case Qt::DisplayRole: - case Roles::DisplayName: - return displayNames[index.row()].toHtmlEscaped(); - case CompletionModel::SearchRole2: - return userids[index.row()]; - case Roles::AvatarUrl: - return cache::avatarUrl(QString::fromStdString(room_id), - QString::fromStdString(roomMembers_[index.row()])); - case Roles::UserID: - return userids[index.row()].toHtmlEscaped(); - } + if (hasIndex(index.row(), index.column(), index.parent())) { + switch (role) { + case CompletionModel::CompletionRole: + if (UserSettings::instance()->markdown()) + return QString("[%1](https://matrix.to/#/%2)") + .arg(displayNames[index.row()].toHtmlEscaped()) + .arg(QString(QUrl::toPercentEncoding(userids[index.row()]))); + else + return displayNames[index.row()]; + case CompletionModel::SearchRole: + return displayNames[index.row()]; + case Qt::DisplayRole: + case Roles::DisplayName: + return displayNames[index.row()].toHtmlEscaped(); + case CompletionModel::SearchRole2: + return userids[index.row()]; + case Roles::AvatarUrl: + return cache::avatarUrl(QString::fromStdString(room_id), + QString::fromStdString(roomMembers_[index.row()])); + case Roles::UserID: + return userids[index.row()].toHtmlEscaped(); } - return {}; + } + return {}; } diff --git a/src/UsersModel.h b/src/UsersModel.h index 5bc94b0f..e719a8bd 100644 --- a/src/UsersModel.h +++ b/src/UsersModel.h @@ -9,25 +9,25 @@ class UsersModel : public QAbstractListModel { public: - enum Roles - { - AvatarUrl = Qt::UserRole, - DisplayName, - UserID, - }; + enum Roles + { + AvatarUrl = Qt::UserRole, + DisplayName, + UserID, + }; - UsersModel(const std::string &roomId, QObject *parent = nullptr); - QHash roleNames() const override; - int rowCount(const QModelIndex &parent = QModelIndex()) const override - { - (void)parent; - return (int)roomMembers_.size(); - } - QVariant data(const QModelIndex &index, int role) const override; + UsersModel(const std::string &roomId, QObject *parent = nullptr); + QHash roleNames() const override; + int rowCount(const QModelIndex &parent = QModelIndex()) const override + { + (void)parent; + return (int)roomMembers_.size(); + } + QVariant data(const QModelIndex &index, int role) const override; private: - std::string room_id; - std::vector roomMembers_; - std::vector displayNames; - std::vector userids; + std::string room_id; + std::vector roomMembers_; + std::vector displayNames; + std::vector userids; }; diff --git a/src/Utils.cpp b/src/Utils.cpp index 3f524c6c..b0fb01b1 100644 --- a/src/Utils.cpp +++ b/src/Utils.cpp @@ -38,155 +38,154 @@ template static DescInfo createDescriptionInfo(const Event &event, const QString &localUser, const QString &displayName) { - const auto msg = std::get(event); - const auto sender = QString::fromStdString(msg.sender); + const auto msg = std::get(event); + const auto sender = QString::fromStdString(msg.sender); - const auto username = displayName; - const auto ts = QDateTime::fromMSecsSinceEpoch(msg.origin_server_ts); - auto body = utils::event_body(event).trimmed(); - if (mtx::accessors::relations(event).reply_to()) - body = QString::fromStdString(utils::stripReplyFromBody(body.toStdString())); + const auto username = displayName; + const auto ts = QDateTime::fromMSecsSinceEpoch(msg.origin_server_ts); + auto body = utils::event_body(event).trimmed(); + if (mtx::accessors::relations(event).reply_to()) + body = QString::fromStdString(utils::stripReplyFromBody(body.toStdString())); - return DescInfo{QString::fromStdString(msg.event_id), - sender, - utils::messageDescription(username, body, sender == localUser), - utils::descriptiveTime(ts), - msg.origin_server_ts, - ts}; + return DescInfo{QString::fromStdString(msg.event_id), + sender, + utils::messageDescription(username, body, sender == localUser), + utils::descriptiveTime(ts), + msg.origin_server_ts, + ts}; } std::string utils::stripReplyFromBody(const std::string &bodyi) { - QString body = QString::fromStdString(bodyi); - QRegularExpression plainQuote("^>.*?$\n?", QRegularExpression::MultilineOption); - while (body.startsWith(">")) - body.remove(plainQuote); - if (body.startsWith("\n")) - body.remove(0, 1); + QString body = QString::fromStdString(bodyi); + QRegularExpression plainQuote("^>.*?$\n?", QRegularExpression::MultilineOption); + while (body.startsWith(">")) + body.remove(plainQuote); + if (body.startsWith("\n")) + body.remove(0, 1); - body.replace("@room", QString::fromUtf8("@\u2060room")); - return body.toStdString(); + body.replace("@room", QString::fromUtf8("@\u2060room")); + return body.toStdString(); } std::string utils::stripReplyFromFormattedBody(const std::string &formatted_bodyi) { - QString formatted_body = QString::fromStdString(formatted_bodyi); - formatted_body.remove(QRegularExpression(".*", - QRegularExpression::DotMatchesEverythingOption)); - formatted_body.replace("@room", QString::fromUtf8("@\u2060room")); - return formatted_body.toStdString(); + QString formatted_body = QString::fromStdString(formatted_bodyi); + formatted_body.remove(QRegularExpression(".*", + QRegularExpression::DotMatchesEverythingOption)); + formatted_body.replace("@room", QString::fromUtf8("@\u2060room")); + return formatted_body.toStdString(); } RelatedInfo utils::stripReplyFallbacks(const TimelineEvent &event, std::string id, QString room_id_) { - RelatedInfo related = {}; - related.quoted_user = QString::fromStdString(mtx::accessors::sender(event)); - related.related_event = std::move(id); - related.type = mtx::accessors::msg_type(event); + RelatedInfo related = {}; + related.quoted_user = QString::fromStdString(mtx::accessors::sender(event)); + related.related_event = std::move(id); + related.type = mtx::accessors::msg_type(event); - // get body, strip reply fallback, then transform the event to text, if it is a media event - // etc - related.quoted_body = QString::fromStdString(mtx::accessors::body(event)); - related.quoted_body = - QString::fromStdString(stripReplyFromBody(related.quoted_body.toStdString())); - related.quoted_body = utils::getQuoteBody(related); + // get body, strip reply fallback, then transform the event to text, if it is a media event + // etc + related.quoted_body = QString::fromStdString(mtx::accessors::body(event)); + related.quoted_body = + QString::fromStdString(stripReplyFromBody(related.quoted_body.toStdString())); + related.quoted_body = utils::getQuoteBody(related); - // get quoted body and strip reply fallback - related.quoted_formatted_body = mtx::accessors::formattedBodyWithFallback(event); - related.quoted_formatted_body = QString::fromStdString( - stripReplyFromFormattedBody(related.quoted_formatted_body.toStdString())); - related.room = room_id_; + // get quoted body and strip reply fallback + related.quoted_formatted_body = mtx::accessors::formattedBodyWithFallback(event); + related.quoted_formatted_body = QString::fromStdString( + stripReplyFromFormattedBody(related.quoted_formatted_body.toStdString())); + related.room = room_id_; - return related; + return related; } QString utils::localUser() { - return QString::fromStdString(http::client()->user_id().to_string()); + return QString::fromStdString(http::client()->user_id().to_string()); } bool utils::codepointIsEmoji(uint code) { - // TODO: Be more precise here. - return (code >= 0x2600 && code <= 0x27bf) || (code >= 0x2b00 && code <= 0x2bff) || - (code >= 0x1f000 && code <= 0x1faff) || code == 0x200d || code == 0xfe0f; + // TODO: Be more precise here. + return (code >= 0x2600 && code <= 0x27bf) || (code >= 0x2b00 && code <= 0x2bff) || + (code >= 0x1f000 && code <= 0x1faff) || code == 0x200d || code == 0xfe0f; } QString utils::replaceEmoji(const QString &body) { - QString fmtBody; - fmtBody.reserve(body.size()); + QString fmtBody; + fmtBody.reserve(body.size()); - QVector utf32_string = body.toUcs4(); + QVector utf32_string = body.toUcs4(); - bool insideFontBlock = false; - for (auto &code : utf32_string) { - if (utils::codepointIsEmoji(code)) { - if (!insideFontBlock) { - fmtBody += QStringLiteral("emojiFont() % - QStringLiteral("\">"); - insideFontBlock = true; - } + bool insideFontBlock = false; + for (auto &code : utf32_string) { + if (utils::codepointIsEmoji(code)) { + if (!insideFontBlock) { + fmtBody += QStringLiteral("emojiFont() % + QStringLiteral("\">"); + insideFontBlock = true; + } - } else { - if (insideFontBlock) { - fmtBody += QStringLiteral(""); - insideFontBlock = false; - } - } - if (QChar::requiresSurrogates(code)) { - QChar emoji[] = {static_cast(QChar::highSurrogate(code)), - static_cast(QChar::lowSurrogate(code))}; - fmtBody.append(emoji, 2); - } else { - fmtBody.append(QChar(static_cast(code))); - } - } - if (insideFontBlock) { + } else { + if (insideFontBlock) { fmtBody += QStringLiteral(""); + insideFontBlock = false; + } } + if (QChar::requiresSurrogates(code)) { + QChar emoji[] = {static_cast(QChar::highSurrogate(code)), + static_cast(QChar::lowSurrogate(code))}; + fmtBody.append(emoji, 2); + } else { + fmtBody.append(QChar(static_cast(code))); + } + } + if (insideFontBlock) { + fmtBody += QStringLiteral(""); + } - return fmtBody; + return fmtBody; } void utils::setScaleFactor(float factor) { - if (factor < 1 || factor > 3) - return; + if (factor < 1 || factor > 3) + return; - QSettings settings; - settings.setValue("settings/scale_factor", factor); + QSettings settings; + settings.setValue("settings/scale_factor", factor); } float utils::scaleFactor() { - QSettings settings; - return settings.value("settings/scale_factor", -1).toFloat(); + QSettings settings; + return settings.value("settings/scale_factor", -1).toFloat(); } QString utils::descriptiveTime(const QDateTime &then) { - const auto now = QDateTime::currentDateTime(); - const auto days = then.daysTo(now); + const auto now = QDateTime::currentDateTime(); + const auto days = then.daysTo(now); - if (days == 0) - return QLocale::system().toString(then.time(), QLocale::ShortFormat); - else if (days < 2) - return QString(QCoreApplication::translate("descriptiveTime", "Yesterday")); - else if (days < 7) - return then.toString("dddd"); + if (days == 0) + return QLocale::system().toString(then.time(), QLocale::ShortFormat); + else if (days < 2) + return QString(QCoreApplication::translate("descriptiveTime", "Yesterday")); + else if (days < 7) + return then.toString("dddd"); - return QLocale::system().toString(then.date(), QLocale::ShortFormat); + return QLocale::system().toString(then.date(), QLocale::ShortFormat); } DescInfo @@ -194,630 +193,622 @@ utils::getMessageDescription(const TimelineEvent &event, const QString &localUser, const QString &displayName) { - using Audio = mtx::events::RoomEvent; - using Emote = mtx::events::RoomEvent; - using File = mtx::events::RoomEvent; - using Image = mtx::events::RoomEvent; - using Notice = mtx::events::RoomEvent; - using Text = mtx::events::RoomEvent; - using Video = mtx::events::RoomEvent; - using CallInvite = mtx::events::RoomEvent; - using CallAnswer = mtx::events::RoomEvent; - using CallHangUp = mtx::events::RoomEvent; - using Encrypted = mtx::events::EncryptedEvent; + using Audio = mtx::events::RoomEvent; + using Emote = mtx::events::RoomEvent; + using File = mtx::events::RoomEvent; + using Image = mtx::events::RoomEvent; + using Notice = mtx::events::RoomEvent; + using Text = mtx::events::RoomEvent; + using Video = mtx::events::RoomEvent; + using CallInvite = mtx::events::RoomEvent; + using CallAnswer = mtx::events::RoomEvent; + using CallHangUp = mtx::events::RoomEvent; + using Encrypted = mtx::events::EncryptedEvent; - if (std::holds_alternative
@@ -76,42 +76,42 @@ Video Call - + Połączenie Wideo Voice Call - + Połączenie Głosowe Devices - Urządzenia + Urządzenia Accept - Akceptuj + Akceptuj Unknown microphone: %1 - + Nieznany mikrofon: %1 Unknown camera: %1 - + Nieznana kamera: %1 Decline - Odrzuć + Odrzuć No microphone found. - + Nie znaleziono mikrofonu. @@ -119,7 +119,7 @@ Entire screen - + Cały ekran @@ -133,33 +133,33 @@ Invited user: %1 - + Zaproszono użytkownika Migrating the cache to the current version failed. This can have different reasons. Please open an issue and try to use an older version in the mean time. Alternatively you can try deleting the cache manually. - + Migracja cachu do obecnej wersji nieudana. Przyczyny mogą być różne. Proszę zgłosić błąd i w miedzyczasie używać starszej wersji. Możesz również spróbuwać usunąć cache ręcznie. Confirm join - + Potwierdź dołączenie Do you really want to join %1? - + Czy na pewno chcesz dołączyć do %1? Room %1 created. - + Utworzono pokój %1. Confirm invite - + Potwierdź zaproszenie @@ -169,7 +169,7 @@ Failed to invite %1 to %2: %3 - + Zaproszenie %1 do %2 nieudane: %3 @@ -199,7 +199,7 @@ Failed to ban %1 in %2: %3 - + Nie udało się zbanować %1 w %2: %3 @@ -219,7 +219,7 @@ Failed to unban %1 in %2: %3 - + Nie udało się odbanować %1 w %2: %3 @@ -229,7 +229,7 @@ Do you really want to start a private chat with %1? - + Czy na pewno chcesz rozpocząć prywatny czat z %1? @@ -297,7 +297,7 @@ Failed to kick %1 from %2: %3 - + Nie udało się wykopać %1 z %2: %3 @@ -305,7 +305,7 @@ Hide rooms with this tag or from this space by default. - + Domyślnie ukryj pokoje oznaczone tym tagiem z tej przestrzeni. @@ -313,42 +313,42 @@ All rooms - Wszystkie pokoje + Wszystkie pokoje Shows all rooms without filtering. - + Pokazuj wszystkie pokoje bez filtrowania. Favourites - + Ulubione Rooms you have favourited. - + Pokoje dodane przez ciebie do ulubionych. Low Priority - + Niski Priorytet Rooms with low priority. - + Pokoje o niskim priorytecie Server Notices - Ogłoszenia serwera + Ogłoszenia Serwera Messages from your server or administrator. - + Wiadomości od twojego serwera lub administratora. @@ -356,27 +356,27 @@ Decrypt secrets - + Odszyfruj sekrety Enter your recovery key or passphrase to decrypt your secrets: - + Wprowadź swój klucz odzyskiwania lub frazę-klucz by odszyfrować swoje sekrety: Enter your recovery key or passphrase called %1 to decrypt your secrets: - + Wprowadź swój klucz odzyskiwania lub frazę klucz nazwaną: %1 by odszyfrować swoje sekrety: Decryption failed - + Odszyfrowywanie nieudane Failed to decrypt secrets with the provided recovery key or passphrase - + Nie udało się odszyfrować sekretów przy pomocy podanego klucza odzyskiwania albo frazy-klucz. @@ -450,7 +450,7 @@ Activity - Aktywność + Aktywność @@ -460,17 +460,17 @@ Objects - Przedmioty + Przedmioty Symbols - Symbole + Symbole Flags - Flagi + Flagi @@ -501,42 +501,42 @@ There is no key to unlock this message. We requested the key automatically, but you can try requesting it again if you are impatient. - + Brakuje klucza do odblokowania tej wiadomości. Poprosiliśmy o klucz automatycznie, ale możesz poprosić ręcznie jeszcze raz, jeśli jesteś niecierpliwy(a). This message couldn't be decrypted, because we only have a key for newer messages. You can try requesting access to this message. - + Ta wiadomość nie mogła zostać odszyfrowana, ponieważ mamy klucz wyłącznie dla nowszych wiadomości. Możesz spróbować poprosić o dostęp do tej wiadomości. There was an internal error reading the decryption key from the database. - + Wystąpił wewnętrzny błąd podczas próby odczytu klucza do odszyfrowywania z bazy danych. There was an error decrypting this message. - + Wystąpił błąd podczas odszyfrowywania tej wiadomości. The message couldn't be parsed. - + Wystąpił błąd podczas przetwarzania tej wiadomości. The encryption key was reused! Someone is possibly trying to insert false messages into this chat! - + Ten klucz szyfrowania został już użyty! Być może ktoś próbuje umieścić fałszywe wiadomości w tym czacie! Unknown decryption error - + Niezidentyfikowany błąd odszyfrowywania Request key - + Poproś o klucz @@ -549,17 +549,17 @@ Encrypted by a verified device - + Zaszyfrowane przez zweryfikowane urządzenie Encrypted by an unverified device, but you have trusted that user so far. - + Zaszyfrowane przez niezweryfikowane urządzenie, ale pochodzące od zaufanego użytkownika. Encrypted by an unverified device or the key is from an untrusted source like the key backup. - + Zaszyfrowane przez niezweryfikowane urządzenie, albo klucz pochodzi z niezaufanego źródła, np. backup-u klucza. @@ -601,7 +601,7 @@ Forward Message - + Prześlij wiadomość dalej @@ -609,17 +609,17 @@ Editing image pack - + Edytowanie paczki obrazów Add images - + Dodaj obrazy Stickers (*.png *.webp *.gif) - + Naklejki (*.png *.webp *.gif) @@ -640,13 +640,13 @@ Use as Emoji - + Użyj jako Emoji Use as Sticker - + Użyj jako Naklejki @@ -661,22 +661,22 @@ Remove from pack - + Usuń z paczki Remove - + Usuń Cancel - Anuluj + Anuluj Save - + Zapisz @@ -684,52 +684,52 @@ Image pack settings - + Ustawienia paczki obrazów Create account pack - + Utwórz paczkę konta New room pack - + Nowa paczka pokoju Private pack - + Prywatna paczka Pack from this room - + Paczka z tego pokoju Globally enabled pack - + Paczka włączona globalnie Enable globally - + Włącz globalnie Enables this pack to be used in all rooms - + Umożliw używanie tej paczki we wszystkich pokojach Edit - + Edytuj Close - Zamknij + Zamknij @@ -737,17 +737,17 @@ Select a file - Wybierz plik + Wybierz plik All Files (*) - Wszystkie pliki (*) + Wszystkie pliki (*) Failed to upload media. Please try again. - + Wysłanie mediów nie powiodło się. Spróbuj ponownie. @@ -755,33 +755,33 @@ Invite users to %1 - + Zaproś użytkowników do %1 User ID to invite - ID użytkownika do zaproszenia + ID użytkownika do zaproszenia @joe:matrix.org Example user id. The name 'joe' can be localized however you want. - + @ania:matrix.org Add - + Dodaj Invite - + Zaproś Cancel - Anuluj + Anuluj @@ -814,7 +814,7 @@ Jeżeli Nheko nie odnajdzie Twojego serwera domowego, wyświetli formularz umoż Your password. - + Twoje hasło @@ -829,18 +829,19 @@ Jeżeli Nheko nie odnajdzie Twojego serwera domowego, wyświetli formularz umoż Homeserver address - + Adres Homeserwer-a server.my:8787 - + server.my:8787 The address that can be used to contact you homeservers client API. Example: https://server.my:8787 - + Adres który może być użyty do komunikacji z klienckim API homeserwer-a. +Przykład: https://server.my:8787 @@ -853,7 +854,7 @@ Example: https://server.my:8787 You have entered an invalid Matrix ID e.g @joe:matrix.org - + Wprowadzono nieprawidłowe Matrix ID. Przykład prawidłowego ID: @ania:matrix.org @@ -902,7 +903,7 @@ Example: https://server.my:8787 removed - + usunięto @@ -932,7 +933,7 @@ Example: https://server.my:8787 %1 changed the room avatar - + %1 zmienił avatar pokoju @@ -972,7 +973,7 @@ Example: https://server.my:8787 Allow them in - + Wpuść @@ -980,42 +981,42 @@ Example: https://server.my:8787 Hang up - + Rozłącz się Place a call - + Zadzwoń Send a file - Wyślij plik + Wyślij plik Write a message... - Napisz wiadomość… + Napisz wiadomość… Stickers - + Naklejki Emoji - Emoji + Emoji Send - + Wyślij You don't have permission to send messages in this room - + Nie masz uprawnień do wysyłania wiadomości w tym pokoju @@ -1023,99 +1024,99 @@ Example: https://server.my:8787 Edit - + Edytuj React - + Zareaguj Reply - + Odpisz Options - + Opcje &Copy - + &Kopiuj Copy &link location - + Kopiuj &adres odnośnika Re&act - + Zar&eaguj Repl&y - + Odp&isz &Edit - + &Edytuj Read receip&ts - + Sprawdź &odbiorców &Forward - + &Przekaż dalej &Mark as read - + &Oznacz jako przeczytane View raw message - + Wyświetl nowe wiadomości View decrypted raw message - + Wyświetl odszyfrowaną nową wiadomość Remo&ve message - + &Usuń wiadomość &Save as - + &Zapisz jako &Open in external program - + Otwórz w &zewnętrznym programie Copy link to eve&nt - + Skopiuj link do z&darzenia &Go to quoted message - + Idź do zacytowanej wiado&mości @@ -1123,37 +1124,37 @@ Example: https://server.my:8787 Send Verification Request - + Wyślij prośbę o weryfikację Received Verification Request - + Otrzymano prośbę o weryfikację To allow other users to see, which of your devices actually belong to you, you can verify them. This also allows key backup to work automatically. Verify %1 now? - + Aby umośliwić innym użytkownikom identyfikację, które urządzenia faktycznie należą do Ciebie, możesz wykonać ich weryfikację. To również umożliwi automatyczny backup kluczy. Zweryfikować %1 teraz? To ensure that no malicious user can eavesdrop on your encrypted communications you can verify the other party. - + Aby upewnić się, że nikt nie podsłuchuje twojej komunikacji możesz wykonać proces weryfikacji rozmówcy. %1 has requested to verify their device %2. - + %1 poprosił Cię o weryfikację jego/jej urządzenia: %2. %1 using the device %2 has requested to be verified. - + Użytkownik %1 poprosił Cię o weryfikację swojego urządzenia: %2. Your device (%1) has requested to be verified. - + Twoje urządzenie (%1) poprosiło o weryfikację. @@ -1181,7 +1182,7 @@ Example: https://server.my:8787 You will be pinging the whole room - + Będziesz pingował cały pokój @@ -1191,41 +1192,44 @@ Example: https://server.my:8787 %1 sent an encrypted message - + %1 wysłał(a) zaszyfrowaną wiadomość * %1 %2 Format an emote message in a notification, %1 is the sender, %2 the message - + Format wiadomości emote w powiadomieniu, %1 to nadawca, %2 to wiadomość. + * %1 %2 %1 replied: %2 Format a reply in a notification. %1 is the sender, %2 the message - + Format wiadomości w powiadomieniu. %1 to nadawca, %2 to wiadomość. + %1 odpisał(a): %2 %1: %2 Format a normal message in a notification. %1 is the sender, %2 the message - + Format normalnej wiadomości w powiadomieniu. %1 to nadawca, %2 to wiadomość. + %1: %2 %1 replied with an encrypted message - + %1 odpisał(a) zaszyfrowaną wiadomością %1 replied to a message - + %1 odpisał(a) na wiadomość %1 sent a message - + %1 wysłał(a) wiadomość @@ -1233,32 +1237,32 @@ Example: https://server.my:8787 Place a call to %1? - + Rozpocząć połączenie głosowe? No microphone found. - + Nie znaleziono mikrofonu. Voice - + Dźwięk Video - + Wideo Screen - + Ekran Cancel - Anuluj + Anuluj @@ -1292,7 +1296,7 @@ Example: https://server.my:8787 Read receipts - Potwierdzenia przeczytania + Potwierdzenia przeczytania @@ -1300,7 +1304,7 @@ Example: https://server.my:8787 Yesterday, %1 - + Wczoraj, %1 @@ -1354,37 +1358,37 @@ Example: https://server.my:8787 Registration token - + Token rejestracji Please enter a valid registration token. - + Proszę wprowadzić prawidłowy token rejestracji. Autodiscovery failed. Received malformed response. - Automatyczne odkrywanie zakończone niepowodzeniem. Otrzymano nieprawidłową odpowiedź. + Automatyczne odkrywanie zakończone niepowodzeniem. Otrzymano nieprawidłową odpowiedź. Autodiscovery failed. Unknown error when requesting .well-known. - Automatyczne odkrywanie zakończone niepowodzeniem. Napotkano nieznany błąd. .well-known. + Automatyczne odkrywanie zakończone niepowodzeniem. Napotkano nieznany błąd. .well-known. The required endpoints were not found. Possibly not a Matrix server. - Nie odnaleziono wymaganych punktów końcowych. To może nie być serwer Matriksa. + Nie odnaleziono wymaganych interfejsów. To może nie być serwer Matrix. Received malformed response. Make sure the homeserver domain is valid. - Otrzymano nieprawidłową odpowiedź. Upewnij się, że domena serwera domowego jest prawidłowa. + Otrzymano nieprawidłową odpowiedź. Upewnij się, że domena homeserver-a jest prawidłowa. An unknown error occured. Make sure the homeserver domain is valid. - Wystąpił nieznany błąd. Upewnij się, że domena serwera domowego jest prawidłowa. + Wystąpił nieznany błąd. Upewnij się, że domena homeserver-a jest prawidłowa. @@ -1412,7 +1416,7 @@ Example: https://server.my:8787 Cancel edit - + Anuluj edytowanie @@ -1420,12 +1424,12 @@ Example: https://server.my:8787 Explore Public Rooms - + Przeglądaj Pokoje Publiczne Search for public rooms - + Szukaj publicznych pokojów @@ -1433,7 +1437,7 @@ Example: https://server.my:8787 no version stored - + wersja nie została zachowana @@ -1441,102 +1445,102 @@ Example: https://server.my:8787 New tag - + Nowy tag Enter the tag you want to use: - + Wprowadź tag, którego chcesz użyć: Leave Room - + Opuść Pokój Are you sure you want to leave this room? - + Na pewno chcesz opuścić ten pokój? Leave room - Opuść pokój + Opuść pokój Tag room as: - + Oznacz (tag) pokój jako: Favourite - + Ulubione Low priority - + Niski priorytet Server notice - + Powiadomienie serwera Create new tag... - + Utwórz nowy tag... Status Message - + Wiadomość Statusowa Enter your status message: - + Wprowadź swoją wiadomość statusową: Profile settings - + Ustawienia profilu Set status message - + Ustaw wiadomość statusową Logout - Wyloguj + Wyloguj Start a new chat - Utwórz nowy czat + Utwórz nowy czat Join a room - Dołącz do pokoju + Dołącz do pokoju Create a new room - + Utwórz nowy pokój Room directory - Katalog pokojów + Katalog pokojów User settings - Ustawienia użytkownika + Ustawienia użytkownika @@ -1544,42 +1548,42 @@ Example: https://server.my:8787 Members of %1 - + Obecni w %1 %n people in %1 Summary above list of members - - - - + + %n osoba w %1 + Podsumowanie + %n ludzi w %1 Invite more people - + Zaproś więcej ludzi This room is not encrypted! - + Ten pokój jest zaszyfrowany This user is verified. - + Ten użytkownik został zweryfikowany. This user isn't verified, but is still using the same master key from the first time you met. - + Ten użytkownik nie został zweryfikowany, ale wciąż używa tego samego klucza głównego którego używał podczas waszej pierwszej rozmowy. This user has unverified devices! - + Ten użytkownik ma urządzenie, które nie zostały zweryfikowane! @@ -1587,144 +1591,145 @@ Example: https://server.my:8787 Room Settings - + Ustawienia Pokoju %1 member(s) - + %1 użytkownik(ów) SETTINGS - + USTAWIENIA Notifications - + Powiadomienia Muted - + Wyciszony Mentions only - + Tylko wzmianki All messages - + Wszystkie wiadomości Room access - + Dostęp do pokoju Anyone and guests - + Każdy oraz goście Anyone - + Każdy Invited users - + Zaproszeni użytkownicy By knocking - + Pukając Restricted by membership in other rooms - + Zastrzeżone poprzez członkostwo w innych pokojach Encryption - + Szyfrowanie End-to-End Encryption - Szyfrowanie end-to-end + Szyfrowanie end-to-end Encryption is currently experimental and things might break unexpectedly. <br> Please take note that it can't be disabled afterwards. - + Szyfrowanie w chwili obecnej jest eksperymentalne i może popsuć się w dowolnym momencie.<br> + Proszę pamiętaj, że szyfrowanie nie będzie mogło zostać później wyłączone. Sticker & Emote Settings - + Naklejki i Ustawienia Emote Change - + Zmień Change what packs are enabled, remove packs or create new ones - + Wybież, które paczki są włączone, usuń paczki, lub utwórz nowe INFO - + INFO Internal ID - + Wewnętrzne ID Room Version - + Wersja Pokoju Failed to enable encryption: %1 - Nie udało się włączyć szyfrowania: %1 + Nie udało się włączyć szyfrowania: %1 Select an avatar - Wybierz awatar + Wybierz awatar All Files (*) - Wszystkie pliki (*) + Wszystkie pliki (*) The selected file is not an image - + Wybrany plik nie jest obrazem Error while reading file: %1 - + Błąd czytania pliku: %1 Failed to upload image: %s - Nie udało się wysłać obrazu: %s + Nie udało się wysłać obrazu: %s @@ -1732,17 +1737,17 @@ Example: https://server.my:8787 Pending invite. - + Oczekujące zaproszenie. Previewing this room - + Podgląd tego pokoju No preview available - + Podgląd pokoju niedostępny @@ -1750,53 +1755,53 @@ Example: https://server.my:8787 Share desktop with %1? - + Udostępnić pulpit (desktop) użytkownikowi: %? Window: - + Okno: Frame rate: - + Klatek na sekundę: Include your camera picture-in-picture - + Włącz funkcję picture-in-picture kamery Request remote camera - + Poproś rozmówcę o włączenie kamery View your callee's camera like a regular video call - + Wyświetl widok kamery rozmówcy jak podczas zwykłej rozmoowy wideo Hide mouse cursor - + Ukryj kursor myszy Share - + Udostępnij Preview - + Podgląd Cancel - Anuluj + Anuluj @@ -1804,12 +1809,12 @@ Example: https://server.my:8787 Failed to connect to secret storage - + Błąd połączenia do menadżera sekretów Nheko could not connect to the secure storage to save encryption secrets to. This can have multiple reasons. Check if your D-Bus service is running and you have configured a service like KWallet, Gnome Secrets or the equivalent for your platform. If you are having trouble, feel free to open an issue here: https://github.com/Nheko-Reborn/nheko/issues - + Nheko nie udało się połączyć z menadżerem sekretów w celu zapisania sekretów służących do szyfrowania. Mogło to zostać spowodowane różnymi czynnikami. Sprawdź czy serwis D-Bus działa oraz czy został skonfigurowany i uruchomiony jeden z następujących serwisów: KWallet, Gnome Secrets lub ekwiwalent dla twojej platformy. Jeśli nadal będziesz miał(a) problem, możesz zgłosić błąd tutaj: https://github.com/Nheko-Reborn/nheko/issues @@ -1818,22 +1823,22 @@ Example: https://server.my:8787 Failed to update image pack: %1 - + Nie udało się uaktualnić paczki obrazów: %1 Failed to delete old image pack: %1 - + Nie udało się usunąć starej paczki obrazów: %1 Failed to open image: %1 - + Nie udało się otworzyć obrazu: %1 Failed to upload image: %1 - + Nie udało się wysłać obrazu: %1 @@ -1841,22 +1846,22 @@ Example: https://server.my:8787 Failed - + Błąd Sent - + Wysłano Received - + Otrzymano Read - + Przeczytano @@ -1864,7 +1869,7 @@ Example: https://server.my:8787 Search - Szukaj + Szukaj @@ -1872,17 +1877,17 @@ Example: https://server.my:8787 Successful Verification - + Weryfikacja udana Verification successful! Both sides verified their devices! - + Weryfikacja udana! Obaj rozmówcy zweryfikowali swoje urządzenia! Close - Zamknij + Zamknij @@ -1890,40 +1895,40 @@ Example: https://server.my:8787 Message redaction failed: %1 - Redagowanie wiadomości nie powiodło się: %1 + Cenzurowanie wiadomości nie powiodło się: %1 Failed to encrypt event, sending aborted! - + Szyfrowanie event-u nie powiodło się, wysyłanie anulowane! Save image - Zapisz obraz + Zapisz obraz Save video - + Zapisz wideo Save audio - + Zapisz audio Save file - + Zapisz plik %1 and %2 are typing. Multiple users are typing. First argument is a comma separated list of potentially multiple users. Second argument is the last user of that list. (If only one user is typing, %1 is empty. You should still use it in your string though to silence Qt warnings.) - - + + %1 i %2 piszą. @@ -1931,153 +1936,153 @@ Example: https://server.my:8787 %1 opened the room to the public. - + %1 zmienił(a) status pokoju na publiczny. %1 made this room require and invitation to join. - + %1 oznaczył(a) pokój jako wymagający zaproszenia aby do niego dołączyć. %1 allowed to join this room by knocking. - + %1 zazwolił(a) na dołączenie do tego pokoju poprzez pukanie. %1 allowed members of the following rooms to automatically join this room: %2 - + %1 zezwolił(a) członkom następujących pokojów na automatyczne dołączenie do tego pokoju: %2 %1 made the room open to guests. - + %1 pozwoliła na dostęp gościom do tego pokoju. %1 has closed the room to guest access. - + %1 zabronił(a) gościom dostępu do tego pokoju. %1 made the room history world readable. Events may be now read by non-joined people. - + %1 zezwoliła każdemu na dostęp do historii tego pokoju. Eventy mogą teraz zostać przeczytane przez osoby nie będące członkami tego pokoju. %1 set the room history visible to members from this point on. - + %1 umożliwił członkom tego pokoju na dostęp od teraz. %1 set the room history visible to members since they were invited. - + %1 umożliwił dostęp do historii tego pokoju członkom od momentu kiedy zostali zaproszeni. %1 set the room history visible to members since they joined the room. - + %1 umożliwił dostęp do historii tego pokoju członkom od momentu kiedy dołączyli do pokoju. %1 has changed the room's permissions. - + %1 zmienił(a) uprawnienia pokoju. %1 was invited. - + %1 został(a) zaproszona/y. %1 changed their avatar. - + %1 zmienił(a) swój awatar. %1 changed some profile info. - + %1 zmodyfikował(a) dane profilu. %1 joined. - + %1 dołączył(a). %1 joined via authorisation from %2's server. - + %1 dołączyła dzięki autoryzacji serwera użytkownika %2. %1 rejected their invite. - + %1 odrzucił(a) zaproszenie. Revoked the invite to %1. - + Unieważniono zaproszenie dla %1. %1 left the room. - + %1 opuścił(a) pokój. Kicked %1. - + Wykopano użytkownika %1. Unbanned %1. - + Odbanowano użytkownika %1. %1 was banned. - + Użytkownik %1 został zbanowany. Reason: %1 - + Powód: %1 %1 redacted their knock. - + Użytkownik %1 ocenzurował własne pukanie. You joined this room. - Dołączyłeś(-łaś) do tego pokoju. + Dołączyłeś(-łaś) do tego pokoju. %1 has changed their avatar and changed their display name to %2. - + Użytkownik %1 zmienił swojego awatara i zmienił swoją nazwę wyświetlaną na %2. %1 has changed their display name to %2. - + Użytkownik %1 zmienił swoją nazwę wyświetlaną na %2. Rejected the knock from %1. - + Odrzucono pukanie użytkownika %2. %1 left after having already left! This is a leave event after the user already left and shouldn't happen apart from state resets - + %1 opuścił(a) pokój po raz kolejny! %1 knocked. - + %1 zapukał(a). @@ -2085,7 +2090,7 @@ Example: https://server.my:8787 Edited - + Edytowane @@ -2093,32 +2098,32 @@ Example: https://server.my:8787 No room open - + Brak otwartych pokojów %1 member(s) - + %1 obeczny(ch) join the conversation - + Dołącz do rozmowy accept invite - + zaakceptój zaproszenie decline invite - + odrzuć zaproszenie Back to room list - + Wróc do listy pokoi @@ -2126,7 +2131,7 @@ Example: https://server.my:8787 No encrypted private chat found with this user. Create an encrypted private chat with this user and try again. - + Nie znaleziono zaszyfrowanego prywatnego czatu z tym użytkownikiem. Utwórz nowy zaszyfrowany prywatny czat z tym użytkownikiem i spróbuj ponownie. @@ -2134,47 +2139,47 @@ Example: https://server.my:8787 Back to room list - + Wróć do listy pokoi No room selected - + Nie wybrano pokoju This room is not encrypted! - + Ten pokój nie jest szyfrowany! This room contains only verified devices. - + Ten pokój zawiera wyłącznie zweryfikowane urządzenia. This rooms contain verified devices and devices which have never changed their master key. - + Ten pokój zawiera wyłącznie zweryfikowane urządzenie które nigdy nie zmieniły swojego klucza głównego. This room contains unverified devices! - + Ten pokój zawiera niezweryfikowane urządzenia! Room options - Ustawienia pokoju + Ustawienia pokoju Invite users - Zaproś użytkowników + Zaproś użytkowników Members - Członkowie + Członkowie @@ -2205,93 +2210,93 @@ Example: https://server.my:8787 Global User Profile - + Globalny Profil Użytkownika Room User Profile - + Profil Użytkownika Pokoju Change avatar globally. - + Zmień awatar globalnie. Change avatar. Will only apply to this room. - + Zmień awatar wyłącznie dla bieżącego pokoju. Change display name globally. - + Zmień nazwę wyświetlaną globalnie. Change display name. Will only apply to this room. - + Zmień nazwę wyświetlaną wyłącznie dla bieżącego pokoju. Room: %1 - + Pokój: %1 This is a room-specific profile. The user's name and avatar may be different from their global versions. - + To profil specyficzny dla pokoju. Nazwa użytkownika oraz awatar mogą być inne niż globalna nazwa użytkownika i globalny awatar. Open the global profile for this user. - + Otwórz globalny profil tego użytkownika. Verify - + Zweryfikuj Start a private chat. - + Rozpocznij prywatny czat. Kick the user. - + Wykop użytkownika. Ban the user. - + Zbanuj użytkownika. Unverify - + Udweryfikuj Select an avatar - Wybierz awatar + Wybierz awatar All Files (*) - Wszystkie pliki (*) + Wszystkie pliki (*) The selected file is not an image - + Wybrany plik nie jest obrazem Error while reading file: %1 - + Błąd odczytu pliku: %1 @@ -2300,7 +2305,7 @@ Example: https://server.my:8787 Default - + Domyślne @@ -2328,17 +2333,17 @@ Example: https://server.my:8787 profile: %1 - + profil: %1 Default - + Domyślny CALLS - + POŁĄCZENIA @@ -2353,28 +2358,29 @@ Example: https://server.my:8787 DOWNLOAD - + POBIERZ Keep the application running in the background after closing the client window. - + Pozostaw aplikację działającą w tle po zamknięciu okna. Start the application in the background without showing the client window. - + Uruchamiaj aplikację w tle bez wyświetlania okna głównego. Change the appearance of user avatars in chats. OFF - square, ON - Circle. - + Zmień wygląd awatarów w czasie. +OFF - kwadrat, ON - koło. Show a column containing groups and tags next to the room list. - + Pokazuj kolumnę zawierającą grupy i tagi obok listy pokoi. @@ -2396,7 +2402,7 @@ Only affects messages in encrypted chats. When the window loses focus, the timeline will be blurred. - + Kiedy okno traci fokus, historia zostanie rozmyta. @@ -2408,27 +2414,29 @@ be blurred. Set timeout (in seconds) for how long after window loses focus before the screen will be blurred. Set to 0 to blur immediately after focus loss. Max value of 1 hour (3600 seconds) - + Ustaw czas (w sekundach) po którym okno zostanie rozmyte po +stracie fokusu. +Ustaw na 3 aby rozmywać natychmiast po stracie fokusu. Maksymalna wartość to 1 godz (3600 sekund) Show buttons in timeline - + Pokazuj przyciski w historii Show buttons to quickly reply, react or access additional options next to each message. - + Pokazuj przyciski do reakcji albo dostępu do dodatkowych opcji obok każdej wiadomości. Limit width of timeline - + Ogranicz szerokość historii Set the max width of messages in the timeline (in pixels). This can help readability on wide screen, when Nheko is maximised - + Ustaw maksymalną szerokość wiadomości w historii (w pikselach). Może to poprawić czytelność gdy Nheko zostanie zmaksymalizowany na szerokim ekranie @@ -2439,19 +2447,21 @@ Set to 0 to blur immediately after focus loss. Max value of 1 hour (3600 seconds Show who is typing in a room. This will also enable or disable sending typing notifications to others. - + To również włączy lub wyłączy wysyłanie powiadomień o pisaniu do innych. Sort rooms by unreads - + Sortuj pokoje po nieprzeczytanych wiadomościach Display rooms with new messages first. If this is off, the list of rooms will only be sorted by the timestamp of the last message in a room. If this is on, rooms which have active notifications (the small circle with a number in it) will be sorted on top. Rooms, that you have muted, will still be sorted by timestamp, since you don't seem to consider them as important as the other rooms. - + Wyświetlaj wiadomości z nieprzeczytanymi wiadomościami w pierwszej kolejności. +Gdy ta opcja jest wyłączona, pokoje będą sortowane wyłącznie po znaczniku czasowym ostatniej wiadomości w pokoju. +Gdy ta opcja jest włączona, pokoje z aktywnymi powiadomieniami (mało kółko z numerkiem w środku) będą na początku. Pokoje, które są wyciszone, będą sortowane po znaczniku czasowym, ponieważ zdaje się, że nie uważasz ich za równie wazne co pozostałe pokoje. @@ -2462,23 +2472,25 @@ If this is on, rooms which have active notifications (the small circle with a nu Show if your message was read. Status is displayed next to timestamps. - + Pokaż czy twoja wiadomość została przeczytana. +Status jest wyświetlany obok znacznika czasu. Send messages as Markdown - + Wysyłaj wiadomości używając Markdown-u Allow using markdown in messages. When disabled, all messages are sent as a plain text. - + Pozwól na używanie markdown-u w wiadomościach. +Gdy ta opcja jest wyłączona, wszystkie wiadomości będą wysyłane gołym tekstem. Play animated images only on hover - + Odtwarzaj animacje obrazów tylko gdy kursor myszy jest nad nimi @@ -2488,63 +2500,64 @@ When disabled, all messages are sent as a plain text. Notify about received message when the client is not currently focused. - + Powiadamiaj o odrzymanych wiadomościach gdy klient nie jest obecnie zfokusowany. Alert on notification - + Alert podczas notyfikacji Show an alert when a message is received. This usually causes the application icon in the task bar to animate in some fashion. - + Pokazuj alert gdy przychodzi wiadomość. +To zwykle sprawia, że ikona aplikacji w tacce systemowej jest animowana. Highlight message on hover - + Wyróżnij wiadomości gdy kursor myszy znajduje się nad nimi. Change the background color of messages when you hover over them. - + Zmień tło wiadomości kiedy kursor myszy znajduje się nad nimi. Large Emoji in timeline - + Duże emotikony w historii Make font size larger if messages with only a few emojis are displayed. - + Zwiększ rozmiar czcionki gdy wiadomości zawierają tylko kilka emotikon. Send encrypted messages to verified users only - + Wysyłaj zaszyfrowane wiadomości wyłącznie do zweryfikowanych użytkowników Requires a user to be verified to send encrypted messages to them. This improves safety but makes E2EE more tedious. - + Wymaga zweryfikowania użytkownika zanim będzie możliwe wysłanie zaszyfrowanych wiadomości do niego. To zwiększa bezpieczeństwo, ale sprawia, że szyfrowanie E2E jest bardziej niewygodne w użyciu. Share keys with verified users and devices - + Udostępnij klucze zweryfikowanym użytkownikom i urządzeniom Automatically replies to key requests from other users, if they are verified, even if that device shouldn't have access to those keys otherwise. - + Automatycznie odpowiada na prośby o klucze od zweryfikowanych użytkowników, nawet gdy ci nie powinni mieć dostępu do tych kluczy. Online Key Backup - + Backup Kluczy Online @@ -2794,22 +2807,22 @@ This usually causes the application icon in the task bar to animate in some fash Waiting for other party… - + Oczekiwanie na rozmówcę... Waiting for other side to accept the verification request. - + Oczekiwanie za zaakceptowanie prośby o weryfikację przez rozmówcę. Waiting for other side to continue the verification process. - + Oczekiwanie na kontynuowanie weryfikacji przez rozmówcę. Waiting for other side to complete the verification process. - + Oczekiwanie na zakończenie procesu weryfikacji przez rozmówcę. @@ -2845,7 +2858,7 @@ This usually causes the application icon in the task bar to animate in some fash Yesterday - + Wczoraj @@ -2853,7 +2866,7 @@ This usually causes the application icon in the task bar to animate in some fash Create room - + Utwórz pokój @@ -2906,7 +2919,7 @@ This usually causes the application icon in the task bar to animate in some fash Confirm - + Potwierdź @@ -2919,7 +2932,7 @@ This usually causes the application icon in the task bar to animate in some fash Join - + Dołącz @@ -2990,7 +3003,7 @@ Rozmiar multimediów: %2 Confirm - + Potwierdź @@ -3116,7 +3129,7 @@ Rozmiar multimediów: %2 Unknown Message Type - + Nieznany Typ Wiadomości From 297e550b6cdd5335642c55168b3b14f3097cb3c7 Mon Sep 17 00:00:00 2001 From: Loren Burkholder Date: Fri, 24 Sep 2021 21:33:50 -0400 Subject: [PATCH 159/232] Fix margins on input dialog --- resources/qml/dialogs/InputDialog.qml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/resources/qml/dialogs/InputDialog.qml b/resources/qml/dialogs/InputDialog.qml index e0f17851..1efdbcde 100644 --- a/resources/qml/dialogs/InputDialog.qml +++ b/resources/qml/dialogs/InputDialog.qml @@ -21,7 +21,8 @@ ApplicationWindow { height: fontMetrics.lineSpacing * 7 ColumnLayout { - anchors.margins: Nheko.paddingLarge + spacing: Nheko.paddingMedium + anchors.margins: Nheko.paddingMedium anchors.fill: parent Label { From 526c1cdcc495398b028e4b7c920f0df5d1c142d5 Mon Sep 17 00:00:00 2001 From: Joseph Donofry Date: Sat, 25 Sep 2021 02:19:44 -0400 Subject: [PATCH 160/232] Add Unicode 14.0 emoji --- resources/emoji-test.txt | 156 ++++++++++++--- scripts/emoji_codegen.py | 2 +- src/emoji/Provider.cpp | 396 ++++++++++++++++++++++++++++++++++----- 3 files changed, 484 insertions(+), 70 deletions(-) diff --git a/resources/emoji-test.txt b/resources/emoji-test.txt index d3c6d12b..dd549336 100644 --- a/resources/emoji-test.txt +++ b/resources/emoji-test.txt @@ -1,11 +1,11 @@ # emoji-test.txt -# Date: 2020-09-12, 22:19:50 GMT -# © 2020 Unicode®, Inc. +# Date: 2021-08-26, 17:22:23 GMT +# © 2021 Unicode®, Inc. # Unicode and the Unicode Logo are registered trademarks of Unicode, Inc. in the U.S. and other countries. # For terms of use, see http://www.unicode.org/terms_of_use.html # # Emoji Keyboard/Display Test Data for UTS #51 -# Version: 13.1 +# Version: 14.0 # # For documentation and usage, see http://www.unicode.org/reports/tr51 # @@ -43,6 +43,7 @@ 1F602 ; fully-qualified # 😂 E0.6 face with tears of joy 1F642 ; fully-qualified # 🙂 E1.0 slightly smiling face 1F643 ; fully-qualified # 🙃 E1.0 upside-down face +1FAE0 ; fully-qualified # 🫠 E14.0 melting face 1F609 ; fully-qualified # 😉 E0.6 winking face 1F60A ; fully-qualified # 😊 E0.6 smiling face with smiling eyes 1F607 ; fully-qualified # 😇 E1.0 smiling face with halo @@ -68,10 +69,13 @@ 1F911 ; fully-qualified # 🤑 E1.0 money-mouth face # subgroup: face-hand -1F917 ; fully-qualified # 🤗 E1.0 hugging face +1F917 ; fully-qualified # 🤗 E1.0 smiling face with open hands 1F92D ; fully-qualified # 🤭 E5.0 face with hand over mouth +1FAE2 ; fully-qualified # 🫢 E14.0 face with open eyes and hand over mouth +1FAE3 ; fully-qualified # 🫣 E14.0 face with peeking eye 1F92B ; fully-qualified # 🤫 E5.0 shushing face 1F914 ; fully-qualified # 🤔 E1.0 thinking face +1FAE1 ; fully-qualified # 🫡 E14.0 saluting face # subgroup: face-neutral-skeptical 1F910 ; fully-qualified # 🤐 E1.0 zipper-mouth face @@ -79,6 +83,7 @@ 1F610 ; fully-qualified # 😐 E0.7 neutral face 1F611 ; fully-qualified # 😑 E1.0 expressionless face 1F636 ; fully-qualified # 😶 E1.0 face without mouth +1FAE5 ; fully-qualified # 🫥 E14.0 dotted line face 1F636 200D 1F32B FE0F ; fully-qualified # 😶‍🌫️ E13.1 face in clouds 1F636 200D 1F32B ; minimally-qualified # 😶‍🌫 E13.1 face in clouds 1F60F ; fully-qualified # 😏 E0.6 smirking face @@ -105,7 +110,7 @@ 1F975 ; fully-qualified # 🥵 E11.0 hot face 1F976 ; fully-qualified # 🥶 E11.0 cold face 1F974 ; fully-qualified # 🥴 E11.0 woozy face -1F635 ; fully-qualified # 😵 E0.6 knocked-out face +1F635 ; fully-qualified # 😵 E0.6 face with crossed-out eyes 1F635 200D 1F4AB ; fully-qualified # 😵‍💫 E13.1 face with spiral eyes 1F92F ; fully-qualified # 🤯 E5.0 exploding head @@ -121,6 +126,7 @@ # subgroup: face-concerned 1F615 ; fully-qualified # 😕 E1.0 confused face +1FAE4 ; fully-qualified # 🫤 E14.0 face with diagonal mouth 1F61F ; fully-qualified # 😟 E1.0 worried face 1F641 ; fully-qualified # 🙁 E1.0 slightly frowning face 2639 FE0F ; fully-qualified # ☹️ E0.7 frowning face @@ -130,6 +136,7 @@ 1F632 ; fully-qualified # 😲 E0.6 astonished face 1F633 ; fully-qualified # 😳 E0.6 flushed face 1F97A ; fully-qualified # 🥺 E11.0 pleading face +1F979 ; fully-qualified # 🥹 E14.0 face holding back tears 1F626 ; fully-qualified # 😦 E1.0 frowning face with open mouth 1F627 ; fully-qualified # 😧 E1.0 anguished face 1F628 ; fully-qualified # 😨 E0.6 fearful face @@ -232,8 +239,8 @@ 1F4AD ; fully-qualified # 💭 E1.0 thought balloon 1F4A4 ; fully-qualified # 💤 E0.6 zzz -# Smileys & Emotion subtotal: 170 -# Smileys & Emotion subtotal: 170 w/o modifiers +# Smileys & Emotion subtotal: 177 +# Smileys & Emotion subtotal: 177 w/o modifiers # group: People & Body @@ -269,6 +276,30 @@ 1F596 1F3FD ; fully-qualified # 🖖🏽 E1.0 vulcan salute: medium skin tone 1F596 1F3FE ; fully-qualified # 🖖🏾 E1.0 vulcan salute: medium-dark skin tone 1F596 1F3FF ; fully-qualified # 🖖🏿 E1.0 vulcan salute: dark skin tone +1FAF1 ; fully-qualified # 🫱 E14.0 rightwards hand +1FAF1 1F3FB ; fully-qualified # 🫱🏻 E14.0 rightwards hand: light skin tone +1FAF1 1F3FC ; fully-qualified # 🫱🏼 E14.0 rightwards hand: medium-light skin tone +1FAF1 1F3FD ; fully-qualified # 🫱🏽 E14.0 rightwards hand: medium skin tone +1FAF1 1F3FE ; fully-qualified # 🫱🏾 E14.0 rightwards hand: medium-dark skin tone +1FAF1 1F3FF ; fully-qualified # 🫱🏿 E14.0 rightwards hand: dark skin tone +1FAF2 ; fully-qualified # 🫲 E14.0 leftwards hand +1FAF2 1F3FB ; fully-qualified # 🫲🏻 E14.0 leftwards hand: light skin tone +1FAF2 1F3FC ; fully-qualified # 🫲🏼 E14.0 leftwards hand: medium-light skin tone +1FAF2 1F3FD ; fully-qualified # 🫲🏽 E14.0 leftwards hand: medium skin tone +1FAF2 1F3FE ; fully-qualified # 🫲🏾 E14.0 leftwards hand: medium-dark skin tone +1FAF2 1F3FF ; fully-qualified # 🫲🏿 E14.0 leftwards hand: dark skin tone +1FAF3 ; fully-qualified # 🫳 E14.0 palm down hand +1FAF3 1F3FB ; fully-qualified # 🫳🏻 E14.0 palm down hand: light skin tone +1FAF3 1F3FC ; fully-qualified # 🫳🏼 E14.0 palm down hand: medium-light skin tone +1FAF3 1F3FD ; fully-qualified # 🫳🏽 E14.0 palm down hand: medium skin tone +1FAF3 1F3FE ; fully-qualified # 🫳🏾 E14.0 palm down hand: medium-dark skin tone +1FAF3 1F3FF ; fully-qualified # 🫳🏿 E14.0 palm down hand: dark skin tone +1FAF4 ; fully-qualified # 🫴 E14.0 palm up hand +1FAF4 1F3FB ; fully-qualified # 🫴🏻 E14.0 palm up hand: light skin tone +1FAF4 1F3FC ; fully-qualified # 🫴🏼 E14.0 palm up hand: medium-light skin tone +1FAF4 1F3FD ; fully-qualified # 🫴🏽 E14.0 palm up hand: medium skin tone +1FAF4 1F3FE ; fully-qualified # 🫴🏾 E14.0 palm up hand: medium-dark skin tone +1FAF4 1F3FF ; fully-qualified # 🫴🏿 E14.0 palm up hand: dark skin tone # subgroup: hand-fingers-partial 1F44C ; fully-qualified # 👌 E0.6 OK hand @@ -302,6 +333,12 @@ 1F91E 1F3FD ; fully-qualified # 🤞🏽 E3.0 crossed fingers: medium skin tone 1F91E 1F3FE ; fully-qualified # 🤞🏾 E3.0 crossed fingers: medium-dark skin tone 1F91E 1F3FF ; fully-qualified # 🤞🏿 E3.0 crossed fingers: dark skin tone +1FAF0 ; fully-qualified # 🫰 E14.0 hand with index finger and thumb crossed +1FAF0 1F3FB ; fully-qualified # 🫰🏻 E14.0 hand with index finger and thumb crossed: light skin tone +1FAF0 1F3FC ; fully-qualified # 🫰🏼 E14.0 hand with index finger and thumb crossed: medium-light skin tone +1FAF0 1F3FD ; fully-qualified # 🫰🏽 E14.0 hand with index finger and thumb crossed: medium skin tone +1FAF0 1F3FE ; fully-qualified # 🫰🏾 E14.0 hand with index finger and thumb crossed: medium-dark skin tone +1FAF0 1F3FF ; fully-qualified # 🫰🏿 E14.0 hand with index finger and thumb crossed: dark skin tone 1F91F ; fully-qualified # 🤟 E5.0 love-you gesture 1F91F 1F3FB ; fully-qualified # 🤟🏻 E5.0 love-you gesture: light skin tone 1F91F 1F3FC ; fully-qualified # 🤟🏼 E5.0 love-you gesture: medium-light skin tone @@ -359,6 +396,12 @@ 261D 1F3FD ; fully-qualified # ☝🏽 E1.0 index pointing up: medium skin tone 261D 1F3FE ; fully-qualified # ☝🏾 E1.0 index pointing up: medium-dark skin tone 261D 1F3FF ; fully-qualified # ☝🏿 E1.0 index pointing up: dark skin tone +1FAF5 ; fully-qualified # 🫵 E14.0 index pointing at the viewer +1FAF5 1F3FB ; fully-qualified # 🫵🏻 E14.0 index pointing at the viewer: light skin tone +1FAF5 1F3FC ; fully-qualified # 🫵🏼 E14.0 index pointing at the viewer: medium-light skin tone +1FAF5 1F3FD ; fully-qualified # 🫵🏽 E14.0 index pointing at the viewer: medium skin tone +1FAF5 1F3FE ; fully-qualified # 🫵🏾 E14.0 index pointing at the viewer: medium-dark skin tone +1FAF5 1F3FF ; fully-qualified # 🫵🏿 E14.0 index pointing at the viewer: dark skin tone # subgroup: hand-fingers-closed 1F44D ; fully-qualified # 👍 E0.6 thumbs up @@ -411,6 +454,12 @@ 1F64C 1F3FD ; fully-qualified # 🙌🏽 E1.0 raising hands: medium skin tone 1F64C 1F3FE ; fully-qualified # 🙌🏾 E1.0 raising hands: medium-dark skin tone 1F64C 1F3FF ; fully-qualified # 🙌🏿 E1.0 raising hands: dark skin tone +1FAF6 ; fully-qualified # 🫶 E14.0 heart hands +1FAF6 1F3FB ; fully-qualified # 🫶🏻 E14.0 heart hands: light skin tone +1FAF6 1F3FC ; fully-qualified # 🫶🏼 E14.0 heart hands: medium-light skin tone +1FAF6 1F3FD ; fully-qualified # 🫶🏽 E14.0 heart hands: medium skin tone +1FAF6 1F3FE ; fully-qualified # 🫶🏾 E14.0 heart hands: medium-dark skin tone +1FAF6 1F3FF ; fully-qualified # 🫶🏿 E14.0 heart hands: dark skin tone 1F450 ; fully-qualified # 👐 E0.6 open hands 1F450 1F3FB ; fully-qualified # 👐🏻 E1.0 open hands: light skin tone 1F450 1F3FC ; fully-qualified # 👐🏼 E1.0 open hands: medium-light skin tone @@ -424,6 +473,31 @@ 1F932 1F3FE ; fully-qualified # 🤲🏾 E5.0 palms up together: medium-dark skin tone 1F932 1F3FF ; fully-qualified # 🤲🏿 E5.0 palms up together: dark skin tone 1F91D ; fully-qualified # 🤝 E3.0 handshake +1F91D 1F3FB ; fully-qualified # 🤝🏻 E3.0 handshake: light skin tone +1F91D 1F3FC ; fully-qualified # 🤝🏼 E3.0 handshake: medium-light skin tone +1F91D 1F3FD ; fully-qualified # 🤝🏽 E3.0 handshake: medium skin tone +1F91D 1F3FE ; fully-qualified # 🤝🏾 E3.0 handshake: medium-dark skin tone +1F91D 1F3FF ; fully-qualified # 🤝🏿 E3.0 handshake: dark skin tone +1FAF1 1F3FB 200D 1FAF2 1F3FC ; fully-qualified # 🫱🏻‍🫲🏼 E14.0 handshake: light skin tone, medium-light skin tone +1FAF1 1F3FB 200D 1FAF2 1F3FD ; fully-qualified # 🫱🏻‍🫲🏽 E14.0 handshake: light skin tone, medium skin tone +1FAF1 1F3FB 200D 1FAF2 1F3FE ; fully-qualified # 🫱🏻‍🫲🏾 E14.0 handshake: light skin tone, medium-dark skin tone +1FAF1 1F3FB 200D 1FAF2 1F3FF ; fully-qualified # 🫱🏻‍🫲🏿 E14.0 handshake: light skin tone, dark skin tone +1FAF1 1F3FC 200D 1FAF2 1F3FB ; fully-qualified # 🫱🏼‍🫲🏻 E14.0 handshake: medium-light skin tone, light skin tone +1FAF1 1F3FC 200D 1FAF2 1F3FD ; fully-qualified # 🫱🏼‍🫲🏽 E14.0 handshake: medium-light skin tone, medium skin tone +1FAF1 1F3FC 200D 1FAF2 1F3FE ; fully-qualified # 🫱🏼‍🫲🏾 E14.0 handshake: medium-light skin tone, medium-dark skin tone +1FAF1 1F3FC 200D 1FAF2 1F3FF ; fully-qualified # 🫱🏼‍🫲🏿 E14.0 handshake: medium-light skin tone, dark skin tone +1FAF1 1F3FD 200D 1FAF2 1F3FB ; fully-qualified # 🫱🏽‍🫲🏻 E14.0 handshake: medium skin tone, light skin tone +1FAF1 1F3FD 200D 1FAF2 1F3FC ; fully-qualified # 🫱🏽‍🫲🏼 E14.0 handshake: medium skin tone, medium-light skin tone +1FAF1 1F3FD 200D 1FAF2 1F3FE ; fully-qualified # 🫱🏽‍🫲🏾 E14.0 handshake: medium skin tone, medium-dark skin tone +1FAF1 1F3FD 200D 1FAF2 1F3FF ; fully-qualified # 🫱🏽‍🫲🏿 E14.0 handshake: medium skin tone, dark skin tone +1FAF1 1F3FE 200D 1FAF2 1F3FB ; fully-qualified # 🫱🏾‍🫲🏻 E14.0 handshake: medium-dark skin tone, light skin tone +1FAF1 1F3FE 200D 1FAF2 1F3FC ; fully-qualified # 🫱🏾‍🫲🏼 E14.0 handshake: medium-dark skin tone, medium-light skin tone +1FAF1 1F3FE 200D 1FAF2 1F3FD ; fully-qualified # 🫱🏾‍🫲🏽 E14.0 handshake: medium-dark skin tone, medium skin tone +1FAF1 1F3FE 200D 1FAF2 1F3FF ; fully-qualified # 🫱🏾‍🫲🏿 E14.0 handshake: medium-dark skin tone, dark skin tone +1FAF1 1F3FF 200D 1FAF2 1F3FB ; fully-qualified # 🫱🏿‍🫲🏻 E14.0 handshake: dark skin tone, light skin tone +1FAF1 1F3FF 200D 1FAF2 1F3FC ; fully-qualified # 🫱🏿‍🫲🏼 E14.0 handshake: dark skin tone, medium-light skin tone +1FAF1 1F3FF 200D 1FAF2 1F3FD ; fully-qualified # 🫱🏿‍🫲🏽 E14.0 handshake: dark skin tone, medium skin tone +1FAF1 1F3FF 200D 1FAF2 1F3FE ; fully-qualified # 🫱🏿‍🫲🏾 E14.0 handshake: dark skin tone, medium-dark skin tone 1F64F ; fully-qualified # 🙏 E0.6 folded hands 1F64F 1F3FB ; fully-qualified # 🙏🏻 E1.0 folded hands: light skin tone 1F64F 1F3FC ; fully-qualified # 🙏🏼 E1.0 folded hands: medium-light skin tone @@ -501,6 +575,7 @@ 1F441 ; unqualified # 👁 E0.7 eye 1F445 ; fully-qualified # 👅 E0.6 tongue 1F444 ; fully-qualified # 👄 E0.6 mouth +1FAE6 ; fully-qualified # 🫦 E14.0 biting lip # subgroup: person 1F476 ; fully-qualified # 👶 E0.6 baby @@ -1472,6 +1547,12 @@ 1F477 1F3FE 200D 2640 ; minimally-qualified # 👷🏾‍♀ E4.0 woman construction worker: medium-dark skin tone 1F477 1F3FF 200D 2640 FE0F ; fully-qualified # 👷🏿‍♀️ E4.0 woman construction worker: dark skin tone 1F477 1F3FF 200D 2640 ; minimally-qualified # 👷🏿‍♀ E4.0 woman construction worker: dark skin tone +1FAC5 ; fully-qualified # 🫅 E14.0 person with crown +1FAC5 1F3FB ; fully-qualified # 🫅🏻 E14.0 person with crown: light skin tone +1FAC5 1F3FC ; fully-qualified # 🫅🏼 E14.0 person with crown: medium-light skin tone +1FAC5 1F3FD ; fully-qualified # 🫅🏽 E14.0 person with crown: medium skin tone +1FAC5 1F3FE ; fully-qualified # 🫅🏾 E14.0 person with crown: medium-dark skin tone +1FAC5 1F3FF ; fully-qualified # 🫅🏿 E14.0 person with crown: dark skin tone 1F934 ; fully-qualified # 🤴 E3.0 prince 1F934 1F3FB ; fully-qualified # 🤴🏻 E3.0 prince: light skin tone 1F934 1F3FC ; fully-qualified # 🤴🏼 E3.0 prince: medium-light skin tone @@ -1592,6 +1673,18 @@ 1F930 1F3FD ; fully-qualified # 🤰🏽 E3.0 pregnant woman: medium skin tone 1F930 1F3FE ; fully-qualified # 🤰🏾 E3.0 pregnant woman: medium-dark skin tone 1F930 1F3FF ; fully-qualified # 🤰🏿 E3.0 pregnant woman: dark skin tone +1FAC3 ; fully-qualified # 🫃 E14.0 pregnant man +1FAC3 1F3FB ; fully-qualified # 🫃🏻 E14.0 pregnant man: light skin tone +1FAC3 1F3FC ; fully-qualified # 🫃🏼 E14.0 pregnant man: medium-light skin tone +1FAC3 1F3FD ; fully-qualified # 🫃🏽 E14.0 pregnant man: medium skin tone +1FAC3 1F3FE ; fully-qualified # 🫃🏾 E14.0 pregnant man: medium-dark skin tone +1FAC3 1F3FF ; fully-qualified # 🫃🏿 E14.0 pregnant man: dark skin tone +1FAC4 ; fully-qualified # 🫄 E14.0 pregnant person +1FAC4 1F3FB ; fully-qualified # 🫄🏻 E14.0 pregnant person: light skin tone +1FAC4 1F3FC ; fully-qualified # 🫄🏼 E14.0 pregnant person: medium-light skin tone +1FAC4 1F3FD ; fully-qualified # 🫄🏽 E14.0 pregnant person: medium skin tone +1FAC4 1F3FE ; fully-qualified # 🫄🏾 E14.0 pregnant person: medium-dark skin tone +1FAC4 1F3FF ; fully-qualified # 🫄🏿 E14.0 pregnant person: dark skin tone 1F931 ; fully-qualified # 🤱 E5.0 breast-feeding 1F931 1F3FB ; fully-qualified # 🤱🏻 E5.0 breast-feeding: light skin tone 1F931 1F3FC ; fully-qualified # 🤱🏼 E5.0 breast-feeding: medium-light skin tone @@ -1862,6 +1955,7 @@ 1F9DF 200D 2642 ; minimally-qualified # 🧟‍♂ E5.0 man zombie 1F9DF 200D 2640 FE0F ; fully-qualified # 🧟‍♀️ E5.0 woman zombie 1F9DF 200D 2640 ; minimally-qualified # 🧟‍♀ E5.0 woman zombie +1F9CC ; fully-qualified # 🧌 E14.0 troll # subgroup: person-activity 1F486 ; fully-qualified # 💆 E0.6 person getting massage @@ -3168,8 +3262,8 @@ 1FAC2 ; fully-qualified # 🫂 E13.0 people hugging 1F463 ; fully-qualified # 👣 E0.6 footprints -# People & Body subtotal: 2899 -# People & Body subtotal: 494 w/o modifiers +# People & Body subtotal: 2986 +# People & Body subtotal: 506 w/o modifiers # group: Component @@ -3304,6 +3398,7 @@ 1F988 ; fully-qualified # 🦈 E3.0 shark 1F419 ; fully-qualified # 🐙 E0.6 octopus 1F41A ; fully-qualified # 🐚 E0.6 spiral shell +1FAB8 ; fully-qualified # 🪸 E14.0 coral # subgroup: animal-bug 1F40C ; fully-qualified # 🐌 E0.6 snail @@ -3329,6 +3424,7 @@ 1F490 ; fully-qualified # 💐 E0.6 bouquet 1F338 ; fully-qualified # 🌸 E0.6 cherry blossom 1F4AE ; fully-qualified # 💮 E0.6 white flower +1FAB7 ; fully-qualified # 🪷 E14.0 lotus 1F3F5 FE0F ; fully-qualified # 🏵️ E0.7 rosette 1F3F5 ; unqualified # 🏵 E0.7 rosette 1F339 ; fully-qualified # 🌹 E0.6 rose @@ -3353,9 +3449,11 @@ 1F341 ; fully-qualified # 🍁 E0.6 maple leaf 1F342 ; fully-qualified # 🍂 E0.6 fallen leaf 1F343 ; fully-qualified # 🍃 E0.6 leaf fluttering in wind +1FAB9 ; fully-qualified # 🪹 E14.0 empty nest +1FABA ; fully-qualified # 🪺 E14.0 nest with eggs -# Animals & Nature subtotal: 147 -# Animals & Nature subtotal: 147 w/o modifiers +# Animals & Nature subtotal: 151 +# Animals & Nature subtotal: 151 w/o modifiers # group: Food & Drink @@ -3396,6 +3494,7 @@ 1F9C5 ; fully-qualified # 🧅 E12.0 onion 1F344 ; fully-qualified # 🍄 E0.6 mushroom 1F95C ; fully-qualified # 🥜 E3.0 peanuts +1FAD8 ; fully-qualified # 🫘 E14.0 beans 1F330 ; fully-qualified # 🌰 E0.6 chestnut # subgroup: food-prepared @@ -3491,6 +3590,7 @@ 1F37B ; fully-qualified # 🍻 E0.6 clinking beer mugs 1F942 ; fully-qualified # 🥂 E3.0 clinking glasses 1F943 ; fully-qualified # 🥃 E3.0 tumbler glass +1FAD7 ; fully-qualified # 🫗 E14.0 pouring liquid 1F964 ; fully-qualified # 🥤 E5.0 cup with straw 1F9CB ; fully-qualified # 🧋 E13.0 bubble tea 1F9C3 ; fully-qualified # 🧃 E12.0 beverage box @@ -3504,10 +3604,11 @@ 1F374 ; fully-qualified # 🍴 E0.6 fork and knife 1F944 ; fully-qualified # 🥄 E3.0 spoon 1F52A ; fully-qualified # 🔪 E0.6 kitchen knife +1FAD9 ; fully-qualified # 🫙 E14.0 jar 1F3FA ; fully-qualified # 🏺 E1.0 amphora -# Food & Drink subtotal: 131 -# Food & Drink subtotal: 131 w/o modifiers +# Food & Drink subtotal: 134 +# Food & Drink subtotal: 134 w/o modifiers # group: Travel & Places @@ -3597,6 +3698,7 @@ 2668 FE0F ; fully-qualified # ♨️ E0.6 hot springs 2668 ; unqualified # ♨ E0.6 hot springs 1F3A0 ; fully-qualified # 🎠 E0.6 carousel horse +1F6DD ; fully-qualified # 🛝 E14.0 playground slide 1F3A1 ; fully-qualified # 🎡 E0.6 ferris wheel 1F3A2 ; fully-qualified # 🎢 E0.6 roller coaster 1F488 ; fully-qualified # 💈 E0.6 barber pole @@ -3652,6 +3754,7 @@ 1F6E2 FE0F ; fully-qualified # 🛢️ E0.7 oil drum 1F6E2 ; unqualified # 🛢 E0.7 oil drum 26FD ; fully-qualified # ⛽ E0.6 fuel pump +1F6DE ; fully-qualified # 🛞 E14.0 wheel 1F6A8 ; fully-qualified # 🚨 E0.6 police car light 1F6A5 ; fully-qualified # 🚥 E0.6 horizontal traffic light 1F6A6 ; fully-qualified # 🚦 E1.0 vertical traffic light @@ -3660,6 +3763,7 @@ # subgroup: transport-water 2693 ; fully-qualified # ⚓ E0.6 anchor +1F6DF ; fully-qualified # 🛟 E14.0 ring buoy 26F5 ; fully-qualified # ⛵ E0.6 sailboat 1F6F6 ; fully-qualified # 🛶 E3.0 canoe 1F6A4 ; fully-qualified # 🚤 E0.6 speedboat @@ -3797,8 +3901,8 @@ 1F4A7 ; fully-qualified # 💧 E0.6 droplet 1F30A ; fully-qualified # 🌊 E0.6 water wave -# Travel & Places subtotal: 264 -# Travel & Places subtotal: 264 w/o modifiers +# Travel & Places subtotal: 267 +# Travel & Places subtotal: 267 w/o modifiers # group: Activities @@ -3874,6 +3978,7 @@ 1F52E ; fully-qualified # 🔮 E0.6 crystal ball 1FA84 ; fully-qualified # 🪄 E13.0 magic wand 1F9FF ; fully-qualified # 🧿 E11.0 nazar amulet +1FAAC ; fully-qualified # 🪬 E14.0 hamsa 1F3AE ; fully-qualified # 🎮 E0.6 video game 1F579 FE0F ; fully-qualified # 🕹️ E0.7 joystick 1F579 ; unqualified # 🕹 E0.7 joystick @@ -3882,6 +3987,7 @@ 1F9E9 ; fully-qualified # 🧩 E11.0 puzzle piece 1F9F8 ; fully-qualified # 🧸 E11.0 teddy bear 1FA85 ; fully-qualified # 🪅 E13.0 piñata +1FAA9 ; fully-qualified # 🪩 E14.0 mirror ball 1FA86 ; fully-qualified # 🪆 E13.0 nesting dolls 2660 FE0F ; fully-qualified # ♠️ E0.6 spade suit 2660 ; unqualified # ♠ E0.6 spade suit @@ -3907,8 +4013,8 @@ 1F9F6 ; fully-qualified # 🧶 E11.0 yarn 1FAA2 ; fully-qualified # 🪢 E13.0 knot -# Activities subtotal: 95 -# Activities subtotal: 95 w/o modifiers +# Activities subtotal: 97 +# Activities subtotal: 97 w/o modifiers # group: Objects @@ -4009,6 +4115,7 @@ # subgroup: computer 1F50B ; fully-qualified # 🔋 E0.6 battery +1FAAB ; fully-qualified # 🪫 E14.0 low battery 1F50C ; fully-qualified # 🔌 E0.6 electric plug 1F4BB ; fully-qualified # 💻 E0.6 laptop 1F5A5 FE0F ; fully-qualified # 🖥️ E0.7 desktop computer @@ -4207,7 +4314,9 @@ 1FA78 ; fully-qualified # 🩸 E12.0 drop of blood 1F48A ; fully-qualified # 💊 E0.6 pill 1FA79 ; fully-qualified # 🩹 E12.0 adhesive bandage +1FA7C ; fully-qualified # 🩼 E14.0 crutch 1FA7A ; fully-qualified # 🩺 E12.0 stethoscope +1FA7B ; fully-qualified # 🩻 E14.0 x-ray # subgroup: household 1F6AA ; fully-qualified # 🚪 E0.6 door @@ -4232,6 +4341,7 @@ 1F9FB ; fully-qualified # 🧻 E11.0 roll of paper 1FAA3 ; fully-qualified # 🪣 E13.0 bucket 1F9FC ; fully-qualified # 🧼 E11.0 soap +1FAE7 ; fully-qualified # 🫧 E14.0 bubbles 1FAA5 ; fully-qualified # 🪥 E13.0 toothbrush 1F9FD ; fully-qualified # 🧽 E11.0 sponge 1F9EF ; fully-qualified # 🧯 E11.0 fire extinguisher @@ -4246,9 +4356,10 @@ 26B1 ; unqualified # ⚱ E1.0 funeral urn 1F5FF ; fully-qualified # 🗿 E0.6 moai 1FAA7 ; fully-qualified # 🪧 E13.0 placard +1FAAA ; fully-qualified # 🪪 E14.0 identification card -# Objects subtotal: 299 -# Objects subtotal: 299 w/o modifiers +# Objects subtotal: 304 +# Objects subtotal: 304 w/o modifiers # group: Symbols @@ -4409,6 +4520,7 @@ 2795 ; fully-qualified # ➕ E0.6 plus 2796 ; fully-qualified # ➖ E0.6 minus 2797 ; fully-qualified # ➗ E0.6 divide +1F7F0 ; fully-qualified # 🟰 E14.0 heavy equals sign 267E FE0F ; fully-qualified # ♾️ E11.0 infinity 267E ; unqualified # ♾ E11.0 infinity @@ -4581,8 +4693,8 @@ 1F533 ; fully-qualified # 🔳 E0.6 white square button 1F532 ; fully-qualified # 🔲 E0.6 black square button -# Symbols subtotal: 301 -# Symbols subtotal: 301 w/o modifiers +# Symbols subtotal: 302 +# Symbols subtotal: 302 w/o modifiers # group: Flags @@ -4871,7 +4983,7 @@ # Flags subtotal: 275 w/o modifiers # Status Counts -# fully-qualified : 3512 +# fully-qualified : 3624 # minimally-qualified : 817 # unqualified : 252 # component : 9 diff --git a/scripts/emoji_codegen.py b/scripts/emoji_codegen.py index df581036..88f711f3 100755 --- a/scripts/emoji_codegen.py +++ b/scripts/emoji_codegen.py @@ -54,7 +54,7 @@ if __name__ == '__main__': } current_category = '' - for line in open(filename, 'r'): + for line in open(filename, 'r', encoding="utf8"): if line.startswith('# group:'): current_category = line.split(':', 1)[1].strip() diff --git a/src/emoji/Provider.cpp b/src/emoji/Provider.cpp index 70ac474e..d62eeee4 100644 --- a/src/emoji/Provider.cpp +++ b/src/emoji/Provider.cpp @@ -34,6 +34,7 @@ const QVector emoji::Provider::emoji = { "slightly smiling face", emoji::Emoji::Category::People}, Emoji{QString::fromUtf8("\xf0\x9f\x99\x83"), "upside-down face", emoji::Emoji::Category::People}, + Emoji{QString::fromUtf8("\xf0\x9f\xab\xa0"), "melting face", emoji::Emoji::Category::People}, Emoji{QString::fromUtf8("\xf0\x9f\x98\x89"), "winking face", emoji::Emoji::Category::People}, Emoji{QString::fromUtf8("\xf0\x9f\x98\x8a"), "smiling face with smiling eyes", @@ -74,12 +75,21 @@ const QVector emoji::Provider::emoji = { "squinting face with tongue", emoji::Emoji::Category::People}, Emoji{QString::fromUtf8("\xf0\x9f\xa4\x91"), "money-mouth face", emoji::Emoji::Category::People}, - Emoji{QString::fromUtf8("\xf0\x9f\xa4\x97"), "hugging face", emoji::Emoji::Category::People}, + Emoji{QString::fromUtf8("\xf0\x9f\xa4\x97"), + "smiling face with open hands", + emoji::Emoji::Category::People}, Emoji{QString::fromUtf8("\xf0\x9f\xa4\xad"), "face with hand over mouth", emoji::Emoji::Category::People}, + Emoji{QString::fromUtf8("\xf0\x9f\xab\xa2"), + "face with open eyes and hand over mouth", + emoji::Emoji::Category::People}, + Emoji{QString::fromUtf8("\xf0\x9f\xab\xa3"), + "face with peeking eye", + emoji::Emoji::Category::People}, Emoji{QString::fromUtf8("\xf0\x9f\xa4\xab"), "shushing face", emoji::Emoji::Category::People}, Emoji{QString::fromUtf8("\xf0\x9f\xa4\x94"), "thinking face", emoji::Emoji::Category::People}, + Emoji{QString::fromUtf8("\xf0\x9f\xab\xa1"), "saluting face", emoji::Emoji::Category::People}, Emoji{QString::fromUtf8("\xf0\x9f\xa4\x90"), "zipper-mouth face", emoji::Emoji::Category::People}, Emoji{QString::fromUtf8("\xf0\x9f\xa4\xa8"), "face with raised eyebrow", @@ -91,6 +101,7 @@ const QVector emoji::Provider::emoji = { Emoji{QString::fromUtf8("\xf0\x9f\x98\xb6"), "face without mouth", emoji::Emoji::Category::People}, + Emoji{QString::fromUtf8("\xf0\x9f\xab\xa5"), "dotted line face", emoji::Emoji::Category::People}, Emoji{QString::fromUtf8("\xf0\x9f\x98\xb6\xe2\x80\x8d\xf0\x9f\x8c\xab"), "face in clouds", emoji::Emoji::Category::People}, @@ -124,7 +135,9 @@ const QVector emoji::Provider::emoji = { Emoji{QString::fromUtf8("\xf0\x9f\xa5\xb5"), "hot face", emoji::Emoji::Category::People}, Emoji{QString::fromUtf8("\xf0\x9f\xa5\xb6"), "cold face", emoji::Emoji::Category::People}, Emoji{QString::fromUtf8("\xf0\x9f\xa5\xb4"), "woozy face", emoji::Emoji::Category::People}, - Emoji{QString::fromUtf8("\xf0\x9f\x98\xb5"), "knocked-out face", emoji::Emoji::Category::People}, + Emoji{QString::fromUtf8("\xf0\x9f\x98\xb5"), + "face with crossed-out eyes", + emoji::Emoji::Category::People}, Emoji{QString::fromUtf8("\xf0\x9f\x98\xb5\xe2\x80\x8d\xf0\x9f\x92\xab"), "face with spiral eyes", emoji::Emoji::Category::People}, @@ -138,6 +151,9 @@ const QVector emoji::Provider::emoji = { Emoji{QString::fromUtf8("\xf0\x9f\xa4\x93"), "nerd face", emoji::Emoji::Category::People}, Emoji{QString::fromUtf8("\xf0\x9f\xa7\x90"), "face with monocle", emoji::Emoji::Category::People}, Emoji{QString::fromUtf8("\xf0\x9f\x98\x95"), "confused face", emoji::Emoji::Category::People}, + Emoji{QString::fromUtf8("\xf0\x9f\xab\xa4"), + "face with diagonal mouth", + emoji::Emoji::Category::People}, Emoji{QString::fromUtf8("\xf0\x9f\x98\x9f"), "worried face", emoji::Emoji::Category::People}, Emoji{QString::fromUtf8("\xf0\x9f\x99\x81"), "slightly frowning face", @@ -150,6 +166,9 @@ const QVector emoji::Provider::emoji = { Emoji{QString::fromUtf8("\xf0\x9f\x98\xb2"), "astonished face", emoji::Emoji::Category::People}, Emoji{QString::fromUtf8("\xf0\x9f\x98\xb3"), "flushed face", emoji::Emoji::Category::People}, Emoji{QString::fromUtf8("\xf0\x9f\xa5\xba"), "pleading face", emoji::Emoji::Category::People}, + Emoji{QString::fromUtf8("\xf0\x9f\xa5\xb9"), + "face holding back tears", + emoji::Emoji::Category::People}, Emoji{QString::fromUtf8("\xf0\x9f\x98\xa6"), "frowning face with open mouth", emoji::Emoji::Category::People}, @@ -367,6 +386,70 @@ const QVector emoji::Provider::emoji = { Emoji{QString::fromUtf8("\xf0\x9f\x96\x96\xf0\x9f\x8f\xbf"), "vulcan salute: dark skin tone", emoji::Emoji::Category::People}, + Emoji{QString::fromUtf8("\xf0\x9f\xab\xb1"), "rightwards hand", emoji::Emoji::Category::People}, + Emoji{QString::fromUtf8("\xf0\x9f\xab\xb1\xf0\x9f\x8f\xbb"), + "rightwards hand: light skin tone", + emoji::Emoji::Category::People}, + Emoji{QString::fromUtf8("\xf0\x9f\xab\xb1\xf0\x9f\x8f\xbc"), + "rightwards hand: medium-light skin tone", + emoji::Emoji::Category::People}, + Emoji{QString::fromUtf8("\xf0\x9f\xab\xb1\xf0\x9f\x8f\xbd"), + "rightwards hand: medium skin tone", + emoji::Emoji::Category::People}, + Emoji{QString::fromUtf8("\xf0\x9f\xab\xb1\xf0\x9f\x8f\xbe"), + "rightwards hand: medium-dark skin tone", + emoji::Emoji::Category::People}, + Emoji{QString::fromUtf8("\xf0\x9f\xab\xb1\xf0\x9f\x8f\xbf"), + "rightwards hand: dark skin tone", + emoji::Emoji::Category::People}, + Emoji{QString::fromUtf8("\xf0\x9f\xab\xb2"), "leftwards hand", emoji::Emoji::Category::People}, + Emoji{QString::fromUtf8("\xf0\x9f\xab\xb2\xf0\x9f\x8f\xbb"), + "leftwards hand: light skin tone", + emoji::Emoji::Category::People}, + Emoji{QString::fromUtf8("\xf0\x9f\xab\xb2\xf0\x9f\x8f\xbc"), + "leftwards hand: medium-light skin tone", + emoji::Emoji::Category::People}, + Emoji{QString::fromUtf8("\xf0\x9f\xab\xb2\xf0\x9f\x8f\xbd"), + "leftwards hand: medium skin tone", + emoji::Emoji::Category::People}, + Emoji{QString::fromUtf8("\xf0\x9f\xab\xb2\xf0\x9f\x8f\xbe"), + "leftwards hand: medium-dark skin tone", + emoji::Emoji::Category::People}, + Emoji{QString::fromUtf8("\xf0\x9f\xab\xb2\xf0\x9f\x8f\xbf"), + "leftwards hand: dark skin tone", + emoji::Emoji::Category::People}, + Emoji{QString::fromUtf8("\xf0\x9f\xab\xb3"), "palm down hand", emoji::Emoji::Category::People}, + Emoji{QString::fromUtf8("\xf0\x9f\xab\xb3\xf0\x9f\x8f\xbb"), + "palm down hand: light skin tone", + emoji::Emoji::Category::People}, + Emoji{QString::fromUtf8("\xf0\x9f\xab\xb3\xf0\x9f\x8f\xbc"), + "palm down hand: medium-light skin tone", + emoji::Emoji::Category::People}, + Emoji{QString::fromUtf8("\xf0\x9f\xab\xb3\xf0\x9f\x8f\xbd"), + "palm down hand: medium skin tone", + emoji::Emoji::Category::People}, + Emoji{QString::fromUtf8("\xf0\x9f\xab\xb3\xf0\x9f\x8f\xbe"), + "palm down hand: medium-dark skin tone", + emoji::Emoji::Category::People}, + Emoji{QString::fromUtf8("\xf0\x9f\xab\xb3\xf0\x9f\x8f\xbf"), + "palm down hand: dark skin tone", + emoji::Emoji::Category::People}, + Emoji{QString::fromUtf8("\xf0\x9f\xab\xb4"), "palm up hand", emoji::Emoji::Category::People}, + Emoji{QString::fromUtf8("\xf0\x9f\xab\xb4\xf0\x9f\x8f\xbb"), + "palm up hand: light skin tone", + emoji::Emoji::Category::People}, + Emoji{QString::fromUtf8("\xf0\x9f\xab\xb4\xf0\x9f\x8f\xbc"), + "palm up hand: medium-light skin tone", + emoji::Emoji::Category::People}, + Emoji{QString::fromUtf8("\xf0\x9f\xab\xb4\xf0\x9f\x8f\xbd"), + "palm up hand: medium skin tone", + emoji::Emoji::Category::People}, + Emoji{QString::fromUtf8("\xf0\x9f\xab\xb4\xf0\x9f\x8f\xbe"), + "palm up hand: medium-dark skin tone", + emoji::Emoji::Category::People}, + Emoji{QString::fromUtf8("\xf0\x9f\xab\xb4\xf0\x9f\x8f\xbf"), + "palm up hand: dark skin tone", + emoji::Emoji::Category::People}, Emoji{QString::fromUtf8("\xf0\x9f\x91\x8c"), "OK hand", emoji::Emoji::Category::People}, Emoji{QString::fromUtf8("\xf0\x9f\x91\x8c\xf0\x9f\x8f\xbb"), "OK hand: light skin tone", @@ -447,6 +530,24 @@ const QVector emoji::Provider::emoji = { Emoji{QString::fromUtf8("\xf0\x9f\xa4\x9e\xf0\x9f\x8f\xbf"), "crossed fingers: dark skin tone", emoji::Emoji::Category::People}, + Emoji{QString::fromUtf8("\xf0\x9f\xab\xb0"), + "hand with index finger and thumb crossed", + emoji::Emoji::Category::People}, + Emoji{QString::fromUtf8("\xf0\x9f\xab\xb0\xf0\x9f\x8f\xbb"), + "hand with index finger and thumb crossed: light skin tone", + emoji::Emoji::Category::People}, + Emoji{QString::fromUtf8("\xf0\x9f\xab\xb0\xf0\x9f\x8f\xbc"), + "hand with index finger and thumb crossed: medium-light skin tone", + emoji::Emoji::Category::People}, + Emoji{QString::fromUtf8("\xf0\x9f\xab\xb0\xf0\x9f\x8f\xbd"), + "hand with index finger and thumb crossed: medium skin tone", + emoji::Emoji::Category::People}, + Emoji{QString::fromUtf8("\xf0\x9f\xab\xb0\xf0\x9f\x8f\xbe"), + "hand with index finger and thumb crossed: medium-dark skin tone", + emoji::Emoji::Category::People}, + Emoji{QString::fromUtf8("\xf0\x9f\xab\xb0\xf0\x9f\x8f\xbf"), + "hand with index finger and thumb crossed: dark skin tone", + emoji::Emoji::Category::People}, Emoji{QString::fromUtf8("\xf0\x9f\xa4\x9f"), "love-you gesture", emoji::Emoji::Category::People}, Emoji{QString::fromUtf8("\xf0\x9f\xa4\x9f\xf0\x9f\x8f\xbb"), "love-you gesture: light skin tone", @@ -599,6 +700,24 @@ const QVector emoji::Provider::emoji = { Emoji{QString::fromUtf8("\xe2\x98\x9d\xf0\x9f\x8f\xbf"), "index pointing up: dark skin tone", emoji::Emoji::Category::People}, + Emoji{QString::fromUtf8("\xf0\x9f\xab\xb5"), + "index pointing at the viewer", + emoji::Emoji::Category::People}, + Emoji{QString::fromUtf8("\xf0\x9f\xab\xb5\xf0\x9f\x8f\xbb"), + "index pointing at the viewer: light skin tone", + emoji::Emoji::Category::People}, + Emoji{QString::fromUtf8("\xf0\x9f\xab\xb5\xf0\x9f\x8f\xbc"), + "index pointing at the viewer: medium-light skin tone", + emoji::Emoji::Category::People}, + Emoji{QString::fromUtf8("\xf0\x9f\xab\xb5\xf0\x9f\x8f\xbd"), + "index pointing at the viewer: medium skin tone", + emoji::Emoji::Category::People}, + Emoji{QString::fromUtf8("\xf0\x9f\xab\xb5\xf0\x9f\x8f\xbe"), + "index pointing at the viewer: medium-dark skin tone", + emoji::Emoji::Category::People}, + Emoji{QString::fromUtf8("\xf0\x9f\xab\xb5\xf0\x9f\x8f\xbf"), + "index pointing at the viewer: dark skin tone", + emoji::Emoji::Category::People}, Emoji{QString::fromUtf8("\xf0\x9f\x91\x8d"), "thumbs up", emoji::Emoji::Category::People}, Emoji{QString::fromUtf8("\xf0\x9f\x91\x8d\xf0\x9f\x8f\xbb"), "thumbs up: light skin tone", @@ -727,6 +846,22 @@ const QVector emoji::Provider::emoji = { Emoji{QString::fromUtf8("\xf0\x9f\x99\x8c\xf0\x9f\x8f\xbf"), "raising hands: dark skin tone", emoji::Emoji::Category::People}, + Emoji{QString::fromUtf8("\xf0\x9f\xab\xb6"), "heart hands", emoji::Emoji::Category::People}, + Emoji{QString::fromUtf8("\xf0\x9f\xab\xb6\xf0\x9f\x8f\xbb"), + "heart hands: light skin tone", + emoji::Emoji::Category::People}, + Emoji{QString::fromUtf8("\xf0\x9f\xab\xb6\xf0\x9f\x8f\xbc"), + "heart hands: medium-light skin tone", + emoji::Emoji::Category::People}, + Emoji{QString::fromUtf8("\xf0\x9f\xab\xb6\xf0\x9f\x8f\xbd"), + "heart hands: medium skin tone", + emoji::Emoji::Category::People}, + Emoji{QString::fromUtf8("\xf0\x9f\xab\xb6\xf0\x9f\x8f\xbe"), + "heart hands: medium-dark skin tone", + emoji::Emoji::Category::People}, + Emoji{QString::fromUtf8("\xf0\x9f\xab\xb6\xf0\x9f\x8f\xbf"), + "heart hands: dark skin tone", + emoji::Emoji::Category::People}, Emoji{QString::fromUtf8("\xf0\x9f\x91\x90"), "open hands", emoji::Emoji::Category::People}, Emoji{QString::fromUtf8("\xf0\x9f\x91\x90\xf0\x9f\x8f\xbb"), "open hands: light skin tone", @@ -760,6 +895,101 @@ const QVector emoji::Provider::emoji = { "palms up together: dark skin tone", emoji::Emoji::Category::People}, Emoji{QString::fromUtf8("\xf0\x9f\xa4\x9d"), "handshake", emoji::Emoji::Category::People}, + Emoji{QString::fromUtf8("\xf0\x9f\xa4\x9d\xf0\x9f\x8f\xbb"), + "handshake: light skin tone", + emoji::Emoji::Category::People}, + Emoji{QString::fromUtf8("\xf0\x9f\xa4\x9d\xf0\x9f\x8f\xbc"), + "handshake: medium-light skin tone", + emoji::Emoji::Category::People}, + Emoji{QString::fromUtf8("\xf0\x9f\xa4\x9d\xf0\x9f\x8f\xbd"), + "handshake: medium skin tone", + emoji::Emoji::Category::People}, + Emoji{QString::fromUtf8("\xf0\x9f\xa4\x9d\xf0\x9f\x8f\xbe"), + "handshake: medium-dark skin tone", + emoji::Emoji::Category::People}, + Emoji{QString::fromUtf8("\xf0\x9f\xa4\x9d\xf0\x9f\x8f\xbf"), + "handshake: dark skin tone", + emoji::Emoji::Category::People}, + Emoji{QString::fromUtf8( + "\xf0\x9f\xab\xb1\xf0\x9f\x8f\xbb\xe2\x80\x8d\xf0\x9f\xab\xb2\xf0\x9f\x8f\xbc"), + "handshake: light skin tone, medium-light skin tone", + emoji::Emoji::Category::People}, + Emoji{QString::fromUtf8( + "\xf0\x9f\xab\xb1\xf0\x9f\x8f\xbb\xe2\x80\x8d\xf0\x9f\xab\xb2\xf0\x9f\x8f\xbd"), + "handshake: light skin tone, medium skin tone", + emoji::Emoji::Category::People}, + Emoji{QString::fromUtf8( + "\xf0\x9f\xab\xb1\xf0\x9f\x8f\xbb\xe2\x80\x8d\xf0\x9f\xab\xb2\xf0\x9f\x8f\xbe"), + "handshake: light skin tone, medium-dark skin tone", + emoji::Emoji::Category::People}, + Emoji{QString::fromUtf8( + "\xf0\x9f\xab\xb1\xf0\x9f\x8f\xbb\xe2\x80\x8d\xf0\x9f\xab\xb2\xf0\x9f\x8f\xbf"), + "handshake: light skin tone, dark skin tone", + emoji::Emoji::Category::People}, + Emoji{QString::fromUtf8( + "\xf0\x9f\xab\xb1\xf0\x9f\x8f\xbc\xe2\x80\x8d\xf0\x9f\xab\xb2\xf0\x9f\x8f\xbb"), + "handshake: medium-light skin tone, light skin tone", + emoji::Emoji::Category::People}, + Emoji{QString::fromUtf8( + "\xf0\x9f\xab\xb1\xf0\x9f\x8f\xbc\xe2\x80\x8d\xf0\x9f\xab\xb2\xf0\x9f\x8f\xbd"), + "handshake: medium-light skin tone, medium skin tone", + emoji::Emoji::Category::People}, + Emoji{QString::fromUtf8( + "\xf0\x9f\xab\xb1\xf0\x9f\x8f\xbc\xe2\x80\x8d\xf0\x9f\xab\xb2\xf0\x9f\x8f\xbe"), + "handshake: medium-light skin tone, medium-dark skin tone", + emoji::Emoji::Category::People}, + Emoji{QString::fromUtf8( + "\xf0\x9f\xab\xb1\xf0\x9f\x8f\xbc\xe2\x80\x8d\xf0\x9f\xab\xb2\xf0\x9f\x8f\xbf"), + "handshake: medium-light skin tone, dark skin tone", + emoji::Emoji::Category::People}, + Emoji{QString::fromUtf8( + "\xf0\x9f\xab\xb1\xf0\x9f\x8f\xbd\xe2\x80\x8d\xf0\x9f\xab\xb2\xf0\x9f\x8f\xbb"), + "handshake: medium skin tone, light skin tone", + emoji::Emoji::Category::People}, + Emoji{QString::fromUtf8( + "\xf0\x9f\xab\xb1\xf0\x9f\x8f\xbd\xe2\x80\x8d\xf0\x9f\xab\xb2\xf0\x9f\x8f\xbc"), + "handshake: medium skin tone, medium-light skin tone", + emoji::Emoji::Category::People}, + Emoji{QString::fromUtf8( + "\xf0\x9f\xab\xb1\xf0\x9f\x8f\xbd\xe2\x80\x8d\xf0\x9f\xab\xb2\xf0\x9f\x8f\xbe"), + "handshake: medium skin tone, medium-dark skin tone", + emoji::Emoji::Category::People}, + Emoji{QString::fromUtf8( + "\xf0\x9f\xab\xb1\xf0\x9f\x8f\xbd\xe2\x80\x8d\xf0\x9f\xab\xb2\xf0\x9f\x8f\xbf"), + "handshake: medium skin tone, dark skin tone", + emoji::Emoji::Category::People}, + Emoji{QString::fromUtf8( + "\xf0\x9f\xab\xb1\xf0\x9f\x8f\xbe\xe2\x80\x8d\xf0\x9f\xab\xb2\xf0\x9f\x8f\xbb"), + "handshake: medium-dark skin tone, light skin tone", + emoji::Emoji::Category::People}, + Emoji{QString::fromUtf8( + "\xf0\x9f\xab\xb1\xf0\x9f\x8f\xbe\xe2\x80\x8d\xf0\x9f\xab\xb2\xf0\x9f\x8f\xbc"), + "handshake: medium-dark skin tone, medium-light skin tone", + emoji::Emoji::Category::People}, + Emoji{QString::fromUtf8( + "\xf0\x9f\xab\xb1\xf0\x9f\x8f\xbe\xe2\x80\x8d\xf0\x9f\xab\xb2\xf0\x9f\x8f\xbd"), + "handshake: medium-dark skin tone, medium skin tone", + emoji::Emoji::Category::People}, + Emoji{QString::fromUtf8( + "\xf0\x9f\xab\xb1\xf0\x9f\x8f\xbe\xe2\x80\x8d\xf0\x9f\xab\xb2\xf0\x9f\x8f\xbf"), + "handshake: medium-dark skin tone, dark skin tone", + emoji::Emoji::Category::People}, + Emoji{QString::fromUtf8( + "\xf0\x9f\xab\xb1\xf0\x9f\x8f\xbf\xe2\x80\x8d\xf0\x9f\xab\xb2\xf0\x9f\x8f\xbb"), + "handshake: dark skin tone, light skin tone", + emoji::Emoji::Category::People}, + Emoji{QString::fromUtf8( + "\xf0\x9f\xab\xb1\xf0\x9f\x8f\xbf\xe2\x80\x8d\xf0\x9f\xab\xb2\xf0\x9f\x8f\xbc"), + "handshake: dark skin tone, medium-light skin tone", + emoji::Emoji::Category::People}, + Emoji{QString::fromUtf8( + "\xf0\x9f\xab\xb1\xf0\x9f\x8f\xbf\xe2\x80\x8d\xf0\x9f\xab\xb2\xf0\x9f\x8f\xbd"), + "handshake: dark skin tone, medium skin tone", + emoji::Emoji::Category::People}, + Emoji{QString::fromUtf8( + "\xf0\x9f\xab\xb1\xf0\x9f\x8f\xbf\xe2\x80\x8d\xf0\x9f\xab\xb2\xf0\x9f\x8f\xbe"), + "handshake: dark skin tone, medium-dark skin tone", + emoji::Emoji::Category::People}, Emoji{QString::fromUtf8("\xf0\x9f\x99\x8f"), "folded hands", emoji::Emoji::Category::People}, Emoji{QString::fromUtf8("\xf0\x9f\x99\x8f\xf0\x9f\x8f\xbb"), "folded hands: light skin tone", @@ -933,6 +1163,7 @@ const QVector emoji::Provider::emoji = { Emoji{QString::fromUtf8("\xf0\x9f\x91\x81"), "eye", emoji::Emoji::Category::People}, Emoji{QString::fromUtf8("\xf0\x9f\x91\x85"), "tongue", emoji::Emoji::Category::People}, Emoji{QString::fromUtf8("\xf0\x9f\x91\x84"), "mouth", emoji::Emoji::Category::People}, + Emoji{QString::fromUtf8("\xf0\x9f\xab\xa6"), "biting lip", emoji::Emoji::Category::People}, Emoji{QString::fromUtf8("\xf0\x9f\x91\xb6"), "baby", emoji::Emoji::Category::People}, Emoji{QString::fromUtf8("\xf0\x9f\x91\xb6\xf0\x9f\x8f\xbb"), "baby: light skin tone", @@ -3041,6 +3272,22 @@ const QVector emoji::Provider::emoji = { Emoji{QString::fromUtf8("\xf0\x9f\x91\xb7\xf0\x9f\x8f\xbf\xe2\x80\x8d\xe2\x99\x80"), "woman construction worker: dark skin tone", emoji::Emoji::Category::People}, + Emoji{QString::fromUtf8("\xf0\x9f\xab\x85"), "person with crown", emoji::Emoji::Category::People}, + Emoji{QString::fromUtf8("\xf0\x9f\xab\x85\xf0\x9f\x8f\xbb"), + "person with crown: light skin tone", + emoji::Emoji::Category::People}, + Emoji{QString::fromUtf8("\xf0\x9f\xab\x85\xf0\x9f\x8f\xbc"), + "person with crown: medium-light skin tone", + emoji::Emoji::Category::People}, + Emoji{QString::fromUtf8("\xf0\x9f\xab\x85\xf0\x9f\x8f\xbd"), + "person with crown: medium skin tone", + emoji::Emoji::Category::People}, + Emoji{QString::fromUtf8("\xf0\x9f\xab\x85\xf0\x9f\x8f\xbe"), + "person with crown: medium-dark skin tone", + emoji::Emoji::Category::People}, + Emoji{QString::fromUtf8("\xf0\x9f\xab\x85\xf0\x9f\x8f\xbf"), + "person with crown: dark skin tone", + emoji::Emoji::Category::People}, Emoji{QString::fromUtf8("\xf0\x9f\xa4\xb4"), "prince", emoji::Emoji::Category::People}, Emoji{QString::fromUtf8("\xf0\x9f\xa4\xb4\xf0\x9f\x8f\xbb"), "prince: light skin tone", @@ -3283,6 +3530,38 @@ const QVector emoji::Provider::emoji = { Emoji{QString::fromUtf8("\xf0\x9f\xa4\xb0\xf0\x9f\x8f\xbf"), "pregnant woman: dark skin tone", emoji::Emoji::Category::People}, + Emoji{QString::fromUtf8("\xf0\x9f\xab\x83"), "pregnant man", emoji::Emoji::Category::People}, + Emoji{QString::fromUtf8("\xf0\x9f\xab\x83\xf0\x9f\x8f\xbb"), + "pregnant man: light skin tone", + emoji::Emoji::Category::People}, + Emoji{QString::fromUtf8("\xf0\x9f\xab\x83\xf0\x9f\x8f\xbc"), + "pregnant man: medium-light skin tone", + emoji::Emoji::Category::People}, + Emoji{QString::fromUtf8("\xf0\x9f\xab\x83\xf0\x9f\x8f\xbd"), + "pregnant man: medium skin tone", + emoji::Emoji::Category::People}, + Emoji{QString::fromUtf8("\xf0\x9f\xab\x83\xf0\x9f\x8f\xbe"), + "pregnant man: medium-dark skin tone", + emoji::Emoji::Category::People}, + Emoji{QString::fromUtf8("\xf0\x9f\xab\x83\xf0\x9f\x8f\xbf"), + "pregnant man: dark skin tone", + emoji::Emoji::Category::People}, + Emoji{QString::fromUtf8("\xf0\x9f\xab\x84"), "pregnant person", emoji::Emoji::Category::People}, + Emoji{QString::fromUtf8("\xf0\x9f\xab\x84\xf0\x9f\x8f\xbb"), + "pregnant person: light skin tone", + emoji::Emoji::Category::People}, + Emoji{QString::fromUtf8("\xf0\x9f\xab\x84\xf0\x9f\x8f\xbc"), + "pregnant person: medium-light skin tone", + emoji::Emoji::Category::People}, + Emoji{QString::fromUtf8("\xf0\x9f\xab\x84\xf0\x9f\x8f\xbd"), + "pregnant person: medium skin tone", + emoji::Emoji::Category::People}, + Emoji{QString::fromUtf8("\xf0\x9f\xab\x84\xf0\x9f\x8f\xbe"), + "pregnant person: medium-dark skin tone", + emoji::Emoji::Category::People}, + Emoji{QString::fromUtf8("\xf0\x9f\xab\x84\xf0\x9f\x8f\xbf"), + "pregnant person: dark skin tone", + emoji::Emoji::Category::People}, Emoji{QString::fromUtf8("\xf0\x9f\xa4\xb1"), "breast-feeding", emoji::Emoji::Category::People}, Emoji{QString::fromUtf8("\xf0\x9f\xa4\xb1\xf0\x9f\x8f\xbb"), "breast-feeding: light skin tone", @@ -3797,6 +4076,7 @@ const QVector emoji::Provider::emoji = { Emoji{QString::fromUtf8("\xf0\x9f\xa7\x9f\xe2\x80\x8d\xe2\x99\x80"), "woman zombie", emoji::Emoji::Category::People}, + Emoji{QString::fromUtf8("\xf0\x9f\xa7\x8c"), "troll", emoji::Emoji::Category::People}, Emoji{QString::fromUtf8("\xf0\x9f\x92\x86"), "person getting massage", emoji::Emoji::Category::People}, @@ -7432,6 +7712,7 @@ const QVector emoji::Provider::emoji = { Emoji{QString::fromUtf8("\xf0\x9f\xa6\x88"), "shark", emoji::Emoji::Category::Nature}, Emoji{QString::fromUtf8("\xf0\x9f\x90\x99"), "octopus", emoji::Emoji::Category::Nature}, Emoji{QString::fromUtf8("\xf0\x9f\x90\x9a"), "spiral shell", emoji::Emoji::Category::Nature}, + Emoji{QString::fromUtf8("\xf0\x9f\xaa\xb8"), "coral", emoji::Emoji::Category::Nature}, Emoji{QString::fromUtf8("\xf0\x9f\x90\x8c"), "snail", emoji::Emoji::Category::Nature}, Emoji{QString::fromUtf8("\xf0\x9f\xa6\x8b"), "butterfly", emoji::Emoji::Category::Nature}, Emoji{QString::fromUtf8("\xf0\x9f\x90\x9b"), "bug", emoji::Emoji::Category::Nature}, @@ -7451,6 +7732,7 @@ const QVector emoji::Provider::emoji = { Emoji{QString::fromUtf8("\xf0\x9f\x92\x90"), "bouquet", emoji::Emoji::Category::Nature}, Emoji{QString::fromUtf8("\xf0\x9f\x8c\xb8"), "cherry blossom", emoji::Emoji::Category::Nature}, Emoji{QString::fromUtf8("\xf0\x9f\x92\xae"), "white flower", emoji::Emoji::Category::Nature}, + Emoji{QString::fromUtf8("\xf0\x9f\xaa\xb7"), "lotus", emoji::Emoji::Category::Nature}, Emoji{QString::fromUtf8("\xf0\x9f\x8f\xb5"), "rosette", emoji::Emoji::Category::Nature}, Emoji{QString::fromUtf8("\xf0\x9f\x8c\xb9"), "rose", emoji::Emoji::Category::Nature}, Emoji{QString::fromUtf8("\xf0\x9f\xa5\x80"), "wilted flower", emoji::Emoji::Category::Nature}, @@ -7473,6 +7755,8 @@ const QVector emoji::Provider::emoji = { Emoji{QString::fromUtf8("\xf0\x9f\x8d\x83"), "leaf fluttering in wind", emoji::Emoji::Category::Nature}, + Emoji{QString::fromUtf8("\xf0\x9f\xaa\xb9"), "empty nest", emoji::Emoji::Category::Nature}, + Emoji{QString::fromUtf8("\xf0\x9f\xaa\xba"), "nest with eggs", emoji::Emoji::Category::Nature}, // Food Emoji{QString::fromUtf8("\xf0\x9f\x8d\x87"), "grapes", emoji::Emoji::Category::Food}, Emoji{QString::fromUtf8("\xf0\x9f\x8d\x88"), "melon", emoji::Emoji::Category::Food}, @@ -7507,6 +7791,7 @@ const QVector emoji::Provider::emoji = { Emoji{QString::fromUtf8("\xf0\x9f\xa7\x85"), "onion", emoji::Emoji::Category::Food}, Emoji{QString::fromUtf8("\xf0\x9f\x8d\x84"), "mushroom", emoji::Emoji::Category::Food}, Emoji{QString::fromUtf8("\xf0\x9f\xa5\x9c"), "peanuts", emoji::Emoji::Category::Food}, + Emoji{QString::fromUtf8("\xf0\x9f\xab\x98"), "beans", emoji::Emoji::Category::Food}, Emoji{QString::fromUtf8("\xf0\x9f\x8c\xb0"), "chestnut", emoji::Emoji::Category::Food}, Emoji{QString::fromUtf8("\xf0\x9f\x8d\x9e"), "bread", emoji::Emoji::Category::Food}, Emoji{QString::fromUtf8("\xf0\x9f\xa5\x90"), "croissant", emoji::Emoji::Category::Food}, @@ -7600,6 +7885,7 @@ const QVector emoji::Provider::emoji = { Emoji{QString::fromUtf8("\xf0\x9f\x8d\xbb"), "clinking beer mugs", emoji::Emoji::Category::Food}, Emoji{QString::fromUtf8("\xf0\x9f\xa5\x82"), "clinking glasses", emoji::Emoji::Category::Food}, Emoji{QString::fromUtf8("\xf0\x9f\xa5\x83"), "tumbler glass", emoji::Emoji::Category::Food}, + Emoji{QString::fromUtf8("\xf0\x9f\xab\x97"), "pouring liquid", emoji::Emoji::Category::Food}, Emoji{QString::fromUtf8("\xf0\x9f\xa5\xa4"), "cup with straw", emoji::Emoji::Category::Food}, Emoji{QString::fromUtf8("\xf0\x9f\xa7\x8b"), "bubble tea", emoji::Emoji::Category::Food}, Emoji{QString::fromUtf8("\xf0\x9f\xa7\x83"), "beverage box", emoji::Emoji::Category::Food}, @@ -7612,6 +7898,7 @@ const QVector emoji::Provider::emoji = { Emoji{QString::fromUtf8("\xf0\x9f\x8d\xb4"), "fork and knife", emoji::Emoji::Category::Food}, Emoji{QString::fromUtf8("\xf0\x9f\xa5\x84"), "spoon", emoji::Emoji::Category::Food}, Emoji{QString::fromUtf8("\xf0\x9f\x94\xaa"), "kitchen knife", emoji::Emoji::Category::Food}, + Emoji{QString::fromUtf8("\xf0\x9f\xab\x99"), "jar", emoji::Emoji::Category::Food}, Emoji{QString::fromUtf8("\xf0\x9f\x8f\xba"), "amphora", emoji::Emoji::Category::Food}, // Activity Emoji{QString::fromUtf8("\xf0\x9f\x8e\x83"), "jack-o-lantern", emoji::Emoji::Category::Activity}, @@ -7683,13 +7970,15 @@ const QVector emoji::Provider::emoji = { Emoji{QString::fromUtf8("\xf0\x9f\x94\xae"), "crystal ball", emoji::Emoji::Category::Activity}, Emoji{QString::fromUtf8("\xf0\x9f\xaa\x84"), "magic wand", emoji::Emoji::Category::Activity}, Emoji{QString::fromUtf8("\xf0\x9f\xa7\xbf"), "nazar amulet", emoji::Emoji::Category::Activity}, + Emoji{QString::fromUtf8("\xf0\x9f\xaa\xac"), "hamsa", emoji::Emoji::Category::Activity}, Emoji{QString::fromUtf8("\xf0\x9f\x8e\xae"), "video game", emoji::Emoji::Category::Activity}, Emoji{QString::fromUtf8("\xf0\x9f\x95\xb9"), "joystick", emoji::Emoji::Category::Activity}, Emoji{QString::fromUtf8("\xf0\x9f\x8e\xb0"), "slot machine", emoji::Emoji::Category::Activity}, Emoji{QString::fromUtf8("\xf0\x9f\x8e\xb2"), "game die", emoji::Emoji::Category::Activity}, Emoji{QString::fromUtf8("\xf0\x9f\xa7\xa9"), "puzzle piece", emoji::Emoji::Category::Activity}, Emoji{QString::fromUtf8("\xf0\x9f\xa7\xb8"), "teddy bear", emoji::Emoji::Category::Activity}, - Emoji{QString::fromUtf8("\xf0\x9f\xaa\x85"), "piñata", emoji::Emoji::Category::Activity}, + Emoji{QString::fromUtf8("\xf0\x9f\xaa\x85"), "pi�ata", emoji::Emoji::Category::Activity}, + Emoji{QString::fromUtf8("\xf0\x9f\xaa\xa9"), "mirror ball", emoji::Emoji::Category::Activity}, Emoji{QString::fromUtf8("\xf0\x9f\xaa\x86"), "nesting dolls", emoji::Emoji::Category::Activity}, Emoji{QString::fromUtf8("\xe2\x99\xa0"), "spade suit", emoji::Emoji::Category::Activity}, Emoji{QString::fromUtf8("\xe2\x99\xa5"), "heart suit", emoji::Emoji::Category::Activity}, @@ -7792,6 +8081,7 @@ const QVector emoji::Provider::emoji = { Emoji{QString::fromUtf8("\xf0\x9f\x8c\x89"), "bridge at night", emoji::Emoji::Category::Travel}, Emoji{QString::fromUtf8("\xe2\x99\xa8"), "hot springs", emoji::Emoji::Category::Travel}, Emoji{QString::fromUtf8("\xf0\x9f\x8e\xa0"), "carousel horse", emoji::Emoji::Category::Travel}, + Emoji{QString::fromUtf8("\xf0\x9f\x9b\x9d"), "playground slide", emoji::Emoji::Category::Travel}, Emoji{QString::fromUtf8("\xf0\x9f\x8e\xa1"), "ferris wheel", emoji::Emoji::Category::Travel}, Emoji{QString::fromUtf8("\xf0\x9f\x8e\xa2"), "roller coaster", emoji::Emoji::Category::Travel}, Emoji{QString::fromUtf8("\xf0\x9f\x92\x88"), "barber pole", emoji::Emoji::Category::Travel}, @@ -7848,6 +8138,7 @@ const QVector emoji::Provider::emoji = { Emoji{QString::fromUtf8("\xf0\x9f\x9b\xa4"), "railway track", emoji::Emoji::Category::Travel}, Emoji{QString::fromUtf8("\xf0\x9f\x9b\xa2"), "oil drum", emoji::Emoji::Category::Travel}, Emoji{QString::fromUtf8("\xe2\x9b\xbd"), "fuel pump", emoji::Emoji::Category::Travel}, + Emoji{QString::fromUtf8("\xf0\x9f\x9b\x9e"), "wheel", emoji::Emoji::Category::Travel}, Emoji{QString::fromUtf8("\xf0\x9f\x9a\xa8"), "police car light", emoji::Emoji::Category::Travel}, Emoji{QString::fromUtf8("\xf0\x9f\x9a\xa5"), "horizontal traffic light", @@ -7858,6 +8149,7 @@ const QVector emoji::Provider::emoji = { Emoji{QString::fromUtf8("\xf0\x9f\x9b\x91"), "stop sign", emoji::Emoji::Category::Travel}, Emoji{QString::fromUtf8("\xf0\x9f\x9a\xa7"), "construction", emoji::Emoji::Category::Travel}, Emoji{QString::fromUtf8("\xe2\x9a\x93"), "anchor", emoji::Emoji::Category::Travel}, + Emoji{QString::fromUtf8("\xf0\x9f\x9b\x9f"), "ring buoy", emoji::Emoji::Category::Travel}, Emoji{QString::fromUtf8("\xe2\x9b\xb5"), "sailboat", emoji::Emoji::Category::Travel}, Emoji{QString::fromUtf8("\xf0\x9f\x9b\xb6"), "canoe", emoji::Emoji::Category::Travel}, Emoji{QString::fromUtf8("\xf0\x9f\x9a\xa4"), "speedboat", emoji::Emoji::Category::Travel}, @@ -7891,29 +8183,29 @@ const QVector emoji::Provider::emoji = { Emoji{QString::fromUtf8("\xe2\x8f\xb1"), "stopwatch", emoji::Emoji::Category::Travel}, Emoji{QString::fromUtf8("\xe2\x8f\xb2"), "timer clock", emoji::Emoji::Category::Travel}, Emoji{QString::fromUtf8("\xf0\x9f\x95\xb0"), "mantelpiece clock", emoji::Emoji::Category::Travel}, - Emoji{QString::fromUtf8("\xf0\x9f\x95\x9b"), "twelve o’clock", emoji::Emoji::Category::Travel}, + Emoji{QString::fromUtf8("\xf0\x9f\x95\x9b"), "twelve o�clock", emoji::Emoji::Category::Travel}, Emoji{QString::fromUtf8("\xf0\x9f\x95\xa7"), "twelve-thirty", emoji::Emoji::Category::Travel}, - Emoji{QString::fromUtf8("\xf0\x9f\x95\x90"), "one o’clock", emoji::Emoji::Category::Travel}, + Emoji{QString::fromUtf8("\xf0\x9f\x95\x90"), "one o�clock", emoji::Emoji::Category::Travel}, Emoji{QString::fromUtf8("\xf0\x9f\x95\x9c"), "one-thirty", emoji::Emoji::Category::Travel}, - Emoji{QString::fromUtf8("\xf0\x9f\x95\x91"), "two o’clock", emoji::Emoji::Category::Travel}, + Emoji{QString::fromUtf8("\xf0\x9f\x95\x91"), "two o�clock", emoji::Emoji::Category::Travel}, Emoji{QString::fromUtf8("\xf0\x9f\x95\x9d"), "two-thirty", emoji::Emoji::Category::Travel}, - Emoji{QString::fromUtf8("\xf0\x9f\x95\x92"), "three o’clock", emoji::Emoji::Category::Travel}, + Emoji{QString::fromUtf8("\xf0\x9f\x95\x92"), "three o�clock", emoji::Emoji::Category::Travel}, Emoji{QString::fromUtf8("\xf0\x9f\x95\x9e"), "three-thirty", emoji::Emoji::Category::Travel}, - Emoji{QString::fromUtf8("\xf0\x9f\x95\x93"), "four o’clock", emoji::Emoji::Category::Travel}, + Emoji{QString::fromUtf8("\xf0\x9f\x95\x93"), "four o�clock", emoji::Emoji::Category::Travel}, Emoji{QString::fromUtf8("\xf0\x9f\x95\x9f"), "four-thirty", emoji::Emoji::Category::Travel}, - Emoji{QString::fromUtf8("\xf0\x9f\x95\x94"), "five o’clock", emoji::Emoji::Category::Travel}, + Emoji{QString::fromUtf8("\xf0\x9f\x95\x94"), "five o�clock", emoji::Emoji::Category::Travel}, Emoji{QString::fromUtf8("\xf0\x9f\x95\xa0"), "five-thirty", emoji::Emoji::Category::Travel}, - Emoji{QString::fromUtf8("\xf0\x9f\x95\x95"), "six o’clock", emoji::Emoji::Category::Travel}, + Emoji{QString::fromUtf8("\xf0\x9f\x95\x95"), "six o�clock", emoji::Emoji::Category::Travel}, Emoji{QString::fromUtf8("\xf0\x9f\x95\xa1"), "six-thirty", emoji::Emoji::Category::Travel}, - Emoji{QString::fromUtf8("\xf0\x9f\x95\x96"), "seven o’clock", emoji::Emoji::Category::Travel}, + Emoji{QString::fromUtf8("\xf0\x9f\x95\x96"), "seven o�clock", emoji::Emoji::Category::Travel}, Emoji{QString::fromUtf8("\xf0\x9f\x95\xa2"), "seven-thirty", emoji::Emoji::Category::Travel}, - Emoji{QString::fromUtf8("\xf0\x9f\x95\x97"), "eight o’clock", emoji::Emoji::Category::Travel}, + Emoji{QString::fromUtf8("\xf0\x9f\x95\x97"), "eight o�clock", emoji::Emoji::Category::Travel}, Emoji{QString::fromUtf8("\xf0\x9f\x95\xa3"), "eight-thirty", emoji::Emoji::Category::Travel}, - Emoji{QString::fromUtf8("\xf0\x9f\x95\x98"), "nine o’clock", emoji::Emoji::Category::Travel}, + Emoji{QString::fromUtf8("\xf0\x9f\x95\x98"), "nine o�clock", emoji::Emoji::Category::Travel}, Emoji{QString::fromUtf8("\xf0\x9f\x95\xa4"), "nine-thirty", emoji::Emoji::Category::Travel}, - Emoji{QString::fromUtf8("\xf0\x9f\x95\x99"), "ten o’clock", emoji::Emoji::Category::Travel}, + Emoji{QString::fromUtf8("\xf0\x9f\x95\x99"), "ten o�clock", emoji::Emoji::Category::Travel}, Emoji{QString::fromUtf8("\xf0\x9f\x95\xa5"), "ten-thirty", emoji::Emoji::Category::Travel}, - Emoji{QString::fromUtf8("\xf0\x9f\x95\x9a"), "eleven o’clock", emoji::Emoji::Category::Travel}, + Emoji{QString::fromUtf8("\xf0\x9f\x95\x9a"), "eleven o�clock", emoji::Emoji::Category::Travel}, Emoji{QString::fromUtf8("\xf0\x9f\x95\xa6"), "eleven-thirty", emoji::Emoji::Category::Travel}, Emoji{QString::fromUtf8("\xf0\x9f\x8c\x91"), "new moon", emoji::Emoji::Category::Travel}, Emoji{QString::fromUtf8("\xf0\x9f\x8c\x92"), @@ -8010,29 +8302,29 @@ const QVector emoji::Provider::emoji = { Emoji{QString::fromUtf8("\xf0\x9f\xa9\xb2"), "briefs", emoji::Emoji::Category::Objects}, Emoji{QString::fromUtf8("\xf0\x9f\xa9\xb3"), "shorts", emoji::Emoji::Category::Objects}, Emoji{QString::fromUtf8("\xf0\x9f\x91\x99"), "bikini", emoji::Emoji::Category::Objects}, - Emoji{QString::fromUtf8("\xf0\x9f\x91\x9a"), "woman’s clothes", emoji::Emoji::Category::Objects}, + Emoji{QString::fromUtf8("\xf0\x9f\x91\x9a"), "woman�s clothes", emoji::Emoji::Category::Objects}, Emoji{QString::fromUtf8("\xf0\x9f\x91\x9b"), "purse", emoji::Emoji::Category::Objects}, Emoji{QString::fromUtf8("\xf0\x9f\x91\x9c"), "handbag", emoji::Emoji::Category::Objects}, Emoji{QString::fromUtf8("\xf0\x9f\x91\x9d"), "clutch bag", emoji::Emoji::Category::Objects}, Emoji{QString::fromUtf8("\xf0\x9f\x9b\x8d"), "shopping bags", emoji::Emoji::Category::Objects}, Emoji{QString::fromUtf8("\xf0\x9f\x8e\x92"), "backpack", emoji::Emoji::Category::Objects}, Emoji{QString::fromUtf8("\xf0\x9f\xa9\xb4"), "thong sandal", emoji::Emoji::Category::Objects}, - Emoji{QString::fromUtf8("\xf0\x9f\x91\x9e"), "man’s shoe", emoji::Emoji::Category::Objects}, + Emoji{QString::fromUtf8("\xf0\x9f\x91\x9e"), "man�s shoe", emoji::Emoji::Category::Objects}, Emoji{QString::fromUtf8("\xf0\x9f\x91\x9f"), "running shoe", emoji::Emoji::Category::Objects}, Emoji{QString::fromUtf8("\xf0\x9f\xa5\xbe"), "hiking boot", emoji::Emoji::Category::Objects}, Emoji{QString::fromUtf8("\xf0\x9f\xa5\xbf"), "flat shoe", emoji::Emoji::Category::Objects}, Emoji{QString::fromUtf8("\xf0\x9f\x91\xa0"), "high-heeled shoe", emoji::Emoji::Category::Objects}, - Emoji{QString::fromUtf8("\xf0\x9f\x91\xa1"), "woman’s sandal", emoji::Emoji::Category::Objects}, + Emoji{QString::fromUtf8("\xf0\x9f\x91\xa1"), "woman�s sandal", emoji::Emoji::Category::Objects}, Emoji{QString::fromUtf8("\xf0\x9f\xa9\xb0"), "ballet shoes", emoji::Emoji::Category::Objects}, - Emoji{QString::fromUtf8("\xf0\x9f\x91\xa2"), "woman’s boot", emoji::Emoji::Category::Objects}, + Emoji{QString::fromUtf8("\xf0\x9f\x91\xa2"), "woman�s boot", emoji::Emoji::Category::Objects}, Emoji{QString::fromUtf8("\xf0\x9f\x91\x91"), "crown", emoji::Emoji::Category::Objects}, - Emoji{QString::fromUtf8("\xf0\x9f\x91\x92"), "woman’s hat", emoji::Emoji::Category::Objects}, + Emoji{QString::fromUtf8("\xf0\x9f\x91\x92"), "woman�s hat", emoji::Emoji::Category::Objects}, Emoji{QString::fromUtf8("\xf0\x9f\x8e\xa9"), "top hat", emoji::Emoji::Category::Objects}, Emoji{QString::fromUtf8("\xf0\x9f\x8e\x93"), "graduation cap", emoji::Emoji::Category::Objects}, Emoji{QString::fromUtf8("\xf0\x9f\xa7\xa2"), "billed cap", emoji::Emoji::Category::Objects}, Emoji{QString::fromUtf8("\xf0\x9f\xaa\x96"), "military helmet", emoji::Emoji::Category::Objects}, Emoji{QString::fromUtf8("\xe2\x9b\x91"), - "rescue worker’s helmet", + "rescue worker�s helmet", emoji::Emoji::Category::Objects}, Emoji{QString::fromUtf8("\xf0\x9f\x93\xbf"), "prayer beads", emoji::Emoji::Category::Objects}, Emoji{QString::fromUtf8("\xf0\x9f\x92\x84"), "lipstick", emoji::Emoji::Category::Objects}, @@ -8084,6 +8376,7 @@ const QVector emoji::Provider::emoji = { Emoji{QString::fromUtf8("\xf0\x9f\x93\x9f"), "pager", emoji::Emoji::Category::Objects}, Emoji{QString::fromUtf8("\xf0\x9f\x93\xa0"), "fax machine", emoji::Emoji::Category::Objects}, Emoji{QString::fromUtf8("\xf0\x9f\x94\x8b"), "battery", emoji::Emoji::Category::Objects}, + Emoji{QString::fromUtf8("\xf0\x9f\xaa\xab"), "low battery", emoji::Emoji::Category::Objects}, Emoji{QString::fromUtf8("\xf0\x9f\x94\x8c"), "electric plug", emoji::Emoji::Category::Objects}, Emoji{QString::fromUtf8("\xf0\x9f\x92\xbb"), "laptop", emoji::Emoji::Category::Objects}, Emoji{QString::fromUtf8("\xf0\x9f\x96\xa5"), "desktop computer", emoji::Emoji::Category::Objects}, @@ -8262,7 +8555,9 @@ const QVector emoji::Provider::emoji = { Emoji{QString::fromUtf8("\xf0\x9f\xa9\xb8"), "drop of blood", emoji::Emoji::Category::Objects}, Emoji{QString::fromUtf8("\xf0\x9f\x92\x8a"), "pill", emoji::Emoji::Category::Objects}, Emoji{QString::fromUtf8("\xf0\x9f\xa9\xb9"), "adhesive bandage", emoji::Emoji::Category::Objects}, + Emoji{QString::fromUtf8("\xf0\x9f\xa9\xbc"), "crutch", emoji::Emoji::Category::Objects}, Emoji{QString::fromUtf8("\xf0\x9f\xa9\xba"), "stethoscope", emoji::Emoji::Category::Objects}, + Emoji{QString::fromUtf8("\xf0\x9f\xa9\xbb"), "x-ray", emoji::Emoji::Category::Objects}, Emoji{QString::fromUtf8("\xf0\x9f\x9a\xaa"), "door", emoji::Emoji::Category::Objects}, Emoji{QString::fromUtf8("\xf0\x9f\x9b\x97"), "elevator", emoji::Emoji::Category::Objects}, Emoji{QString::fromUtf8("\xf0\x9f\xaa\x9e"), "mirror", emoji::Emoji::Category::Objects}, @@ -8283,6 +8578,7 @@ const QVector emoji::Provider::emoji = { Emoji{QString::fromUtf8("\xf0\x9f\xa7\xbb"), "roll of paper", emoji::Emoji::Category::Objects}, Emoji{QString::fromUtf8("\xf0\x9f\xaa\xa3"), "bucket", emoji::Emoji::Category::Objects}, Emoji{QString::fromUtf8("\xf0\x9f\xa7\xbc"), "soap", emoji::Emoji::Category::Objects}, + Emoji{QString::fromUtf8("\xf0\x9f\xab\xa7"), "bubbles", emoji::Emoji::Category::Objects}, Emoji{QString::fromUtf8("\xf0\x9f\xaa\xa5"), "toothbrush", emoji::Emoji::Category::Objects}, Emoji{QString::fromUtf8("\xf0\x9f\xa7\xbd"), "sponge", emoji::Emoji::Category::Objects}, Emoji{QString::fromUtf8("\xf0\x9f\xa7\xaf"), @@ -8295,6 +8591,9 @@ const QVector emoji::Provider::emoji = { Emoji{QString::fromUtf8("\xe2\x9a\xb1"), "funeral urn", emoji::Emoji::Category::Objects}, Emoji{QString::fromUtf8("\xf0\x9f\x97\xbf"), "moai", emoji::Emoji::Category::Objects}, Emoji{QString::fromUtf8("\xf0\x9f\xaa\xa7"), "placard", emoji::Emoji::Category::Objects}, + Emoji{QString::fromUtf8("\xf0\x9f\xaa\xaa"), + "identification card", + emoji::Emoji::Category::Objects}, // Symbols Emoji{QString::fromUtf8("\xf0\x9f\x8f\xa7"), "ATM sign", emoji::Emoji::Category::Symbols}, Emoji{QString::fromUtf8("\xf0\x9f\x9a\xae"), @@ -8302,8 +8601,8 @@ const QVector emoji::Provider::emoji = { emoji::Emoji::Category::Symbols}, Emoji{QString::fromUtf8("\xf0\x9f\x9a\xb0"), "potable water", emoji::Emoji::Category::Symbols}, Emoji{QString::fromUtf8("\xe2\x99\xbf"), "wheelchair symbol", emoji::Emoji::Category::Symbols}, - Emoji{QString::fromUtf8("\xf0\x9f\x9a\xb9"), "men’s room", emoji::Emoji::Category::Symbols}, - Emoji{QString::fromUtf8("\xf0\x9f\x9a\xba"), "women’s room", emoji::Emoji::Category::Symbols}, + Emoji{QString::fromUtf8("\xf0\x9f\x9a\xb9"), "men�s room", emoji::Emoji::Category::Symbols}, + Emoji{QString::fromUtf8("\xf0\x9f\x9a\xba"), "women�s room", emoji::Emoji::Category::Symbols}, Emoji{QString::fromUtf8("\xf0\x9f\x9a\xbb"), "restroom", emoji::Emoji::Category::Symbols}, Emoji{QString::fromUtf8("\xf0\x9f\x9a\xbc"), "baby symbol", emoji::Emoji::Category::Symbols}, Emoji{QString::fromUtf8("\xf0\x9f\x9a\xbe"), "water closet", emoji::Emoji::Category::Symbols}, @@ -8425,6 +8724,9 @@ const QVector emoji::Provider::emoji = { Emoji{QString::fromUtf8("\xe2\x9e\x95"), "plus", emoji::Emoji::Category::Symbols}, Emoji{QString::fromUtf8("\xe2\x9e\x96"), "minus", emoji::Emoji::Category::Symbols}, Emoji{QString::fromUtf8("\xe2\x9e\x97"), "divide", emoji::Emoji::Category::Symbols}, + Emoji{QString::fromUtf8("\xf0\x9f\x9f\xb0"), + "heavy equals sign", + emoji::Emoji::Category::Symbols}, Emoji{QString::fromUtf8("\xe2\x99\xbe"), "infinity", emoji::Emoji::Category::Symbols}, Emoji{QString::fromUtf8("\xe2\x80\xbc"), "double exclamation mark", @@ -8558,55 +8860,55 @@ const QVector emoji::Provider::emoji = { Emoji{QString::fromUtf8("\xf0\x9f\x86\x99"), "UP! button", emoji::Emoji::Category::Symbols}, Emoji{QString::fromUtf8("\xf0\x9f\x86\x9a"), "VS button", emoji::Emoji::Category::Symbols}, Emoji{QString::fromUtf8("\xf0\x9f\x88\x81"), - "Japanese “here” button", + "Japanese �here� button", emoji::Emoji::Category::Symbols}, Emoji{QString::fromUtf8("\xf0\x9f\x88\x82"), - "Japanese “service charge” button", + "Japanese �service charge� button", emoji::Emoji::Category::Symbols}, Emoji{QString::fromUtf8("\xf0\x9f\x88\xb7"), - "Japanese “monthly amount” button", + "Japanese �monthly amount� button", emoji::Emoji::Category::Symbols}, Emoji{QString::fromUtf8("\xf0\x9f\x88\xb6"), - "Japanese “not free of charge” button", + "Japanese �not free of charge� button", emoji::Emoji::Category::Symbols}, Emoji{QString::fromUtf8("\xf0\x9f\x88\xaf"), - "Japanese “reserved” button", + "Japanese �reserved� button", emoji::Emoji::Category::Symbols}, Emoji{QString::fromUtf8("\xf0\x9f\x89\x90"), - "Japanese “bargain” button", + "Japanese �bargain� button", emoji::Emoji::Category::Symbols}, Emoji{QString::fromUtf8("\xf0\x9f\x88\xb9"), - "Japanese “discount” button", + "Japanese �discount� button", emoji::Emoji::Category::Symbols}, Emoji{QString::fromUtf8("\xf0\x9f\x88\x9a"), - "Japanese “free of charge” button", + "Japanese �free of charge� button", emoji::Emoji::Category::Symbols}, Emoji{QString::fromUtf8("\xf0\x9f\x88\xb2"), - "Japanese “prohibited” button", + "Japanese �prohibited� button", emoji::Emoji::Category::Symbols}, Emoji{QString::fromUtf8("\xf0\x9f\x89\x91"), - "Japanese “acceptable” button", + "Japanese �acceptable� button", emoji::Emoji::Category::Symbols}, Emoji{QString::fromUtf8("\xf0\x9f\x88\xb8"), - "Japanese “application” button", + "Japanese �application� button", emoji::Emoji::Category::Symbols}, Emoji{QString::fromUtf8("\xf0\x9f\x88\xb4"), - "Japanese “passing grade” button", + "Japanese �passing grade� button", emoji::Emoji::Category::Symbols}, Emoji{QString::fromUtf8("\xf0\x9f\x88\xb3"), - "Japanese “vacancy” button", + "Japanese �vacancy� button", emoji::Emoji::Category::Symbols}, Emoji{QString::fromUtf8("\xe3\x8a\x97"), - "Japanese “congratulations” button", + "Japanese �congratulations� button", emoji::Emoji::Category::Symbols}, Emoji{QString::fromUtf8("\xe3\x8a\x99"), - "Japanese “secret” button", + "Japanese �secret� button", emoji::Emoji::Category::Symbols}, Emoji{QString::fromUtf8("\xf0\x9f\x88\xba"), - "Japanese “open for business” button", + "Japanese �open for business� button", emoji::Emoji::Category::Symbols}, Emoji{QString::fromUtf8("\xf0\x9f\x88\xb5"), - "Japanese “no vacancy” button", + "Japanese �no vacancy� button", emoji::Emoji::Category::Symbols}, Emoji{QString::fromUtf8("\xf0\x9f\x94\xb4"), "red circle", emoji::Emoji::Category::Symbols}, Emoji{QString::fromUtf8("\xf0\x9f\x9f\xa0"), "orange circle", emoji::Emoji::Category::Symbols}, @@ -8731,7 +9033,7 @@ const QVector emoji::Provider::emoji = { "flag: Aruba", emoji::Emoji::Category::Flags}, Emoji{QString::fromUtf8("\xf0\x9f\x87\xa6\xf0\x9f\x87\xbd"), - "flag: Åland Islands", + "flag: �land Islands", emoji::Emoji::Category::Flags}, Emoji{QString::fromUtf8("\xf0\x9f\x87\xa6\xf0\x9f\x87\xbf"), "flag: Azerbaijan", @@ -8764,7 +9066,7 @@ const QVector emoji::Provider::emoji = { "flag: Benin", emoji::Emoji::Category::Flags}, Emoji{QString::fromUtf8("\xf0\x9f\x87\xa7\xf0\x9f\x87\xb1"), - "flag: St. Barthélemy", + "flag: St. Barth�lemy", emoji::Emoji::Category::Flags}, Emoji{QString::fromUtf8("\xf0\x9f\x87\xa7\xf0\x9f\x87\xb2"), "flag: Bermuda", @@ -8818,7 +9120,7 @@ const QVector emoji::Provider::emoji = { "flag: Switzerland", emoji::Emoji::Category::Flags}, Emoji{QString::fromUtf8("\xf0\x9f\x87\xa8\xf0\x9f\x87\xae"), - "flag: Côte d’Ivoire", + "flag: C�te d�Ivoire", emoji::Emoji::Category::Flags}, Emoji{QString::fromUtf8("\xf0\x9f\x87\xa8\xf0\x9f\x87\xb0"), "flag: Cook Islands", @@ -8848,7 +9150,7 @@ const QVector emoji::Provider::emoji = { "flag: Cape Verde", emoji::Emoji::Category::Flags}, Emoji{QString::fromUtf8("\xf0\x9f\x87\xa8\xf0\x9f\x87\xbc"), - "flag: Curaçao", + "flag: Cura�ao", emoji::Emoji::Category::Flags}, Emoji{QString::fromUtf8("\xf0\x9f\x87\xa8\xf0\x9f\x87\xbd"), "flag: Christmas Island", @@ -9265,7 +9567,7 @@ const QVector emoji::Provider::emoji = { "flag: Qatar", emoji::Emoji::Category::Flags}, Emoji{QString::fromUtf8("\xf0\x9f\x87\xb7\xf0\x9f\x87\xaa"), - "flag: Réunion", + "flag: R�union", emoji::Emoji::Category::Flags}, Emoji{QString::fromUtf8("\xf0\x9f\x87\xb7\xf0\x9f\x87\xb4"), "flag: Romania", @@ -9328,7 +9630,7 @@ const QVector emoji::Provider::emoji = { "flag: South Sudan", emoji::Emoji::Category::Flags}, Emoji{QString::fromUtf8("\xf0\x9f\x87\xb8\xf0\x9f\x87\xb9"), - "flag: São Tomé & Príncipe", + "flag: S�o Tom� & Pr�ncipe", emoji::Emoji::Category::Flags}, Emoji{QString::fromUtf8("\xf0\x9f\x87\xb8\xf0\x9f\x87\xbb"), "flag: El Salvador", @@ -9471,4 +9773,4 @@ const QVector emoji::Provider::emoji = { "\x81\xac\xf3\xa0\x81\xb3\xf3\xa0\x81\xbf"), "flag: Wales", emoji::Emoji::Category::Flags}, -}; +}; \ No newline at end of file From 2d59d53eeb1bb4f5979d19674754f41043b8e5b3 Mon Sep 17 00:00:00 2001 From: Weblate Date: Mon, 27 Sep 2021 10:05:54 -0400 Subject: [PATCH 161/232] Update translation files MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Updated by "Cleanup translation files" hook in Weblate. Translated using Weblate (Dutch) Currently translated at 100.0% (569 of 569 strings) Co-authored-by: Jaron Viëtor Co-authored-by: Weblate Translate-URL: https://weblate.nheko.im/projects/nheko/nheko-master/ Translate-URL: https://weblate.nheko.im/projects/nheko/nheko-master/nl/ Translation: Nheko/nheko --- resources/langs/nheko_nl.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/resources/langs/nheko_nl.ts b/resources/langs/nheko_nl.ts index 432af6c9..c8764c0c 100644 --- a/resources/langs/nheko_nl.ts +++ b/resources/langs/nheko_nl.ts @@ -3125,4 +3125,7 @@ Mediagrootte: %2 Onbekend berichttype
+ + + From 6941c3d3d7086ffac55b96e6fdc186b868094b2d Mon Sep 17 00:00:00 2001 From: Thulinma Date: Mon, 27 Sep 2021 23:20:25 +0200 Subject: [PATCH 162/232] Fix --help and --version command line options when Nheko is already running. Also adds an info message when it sends a URI to another instance --- src/main.cpp | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/src/main.cpp b/src/main.cpp index cf7e29e6..f6373d2a 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -176,12 +176,6 @@ main(int argc, char *argv[]) 100, userdata); - if (app.isSecondary()) { - // open uri in main instance - app.sendMessage(matrixUri.toUtf8()); - return 0; - } - QCommandLineParser parser; parser.addHelpOption(); parser.addVersionOption(); @@ -202,6 +196,15 @@ main(int argc, char *argv[]) parser.process(app); + // This check needs to happen _after_ process(), so that we actually print help for --help when + // Nheko is already running. + if (app.isSecondary()) { + nhlog::ui()->info("Sending Matrix URL to main application: {}", matrixUri.toStdString()); + // open uri in main instance + app.sendMessage(matrixUri.toUtf8()); + return 0; + } + app.setWindowIcon(QIcon::fromTheme("nheko", QIcon{":/logos/nheko.png"})); http::init(); From c4b788917f9db8674fabf23b3972179b745b7700 Mon Sep 17 00:00:00 2001 From: Thulinma Date: Mon, 27 Sep 2021 23:19:43 +0200 Subject: [PATCH 163/232] Fixes for pasting images, especially under windows where the image mime type detection doesn't work as expected --- src/timeline/InputBar.cpp | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/src/timeline/InputBar.cpp b/src/timeline/InputBar.cpp index f0c38c84..c83bb19c 100644 --- a/src/timeline/InputBar.cpp +++ b/src/timeline/InputBar.cpp @@ -45,10 +45,8 @@ InputBar::paste(bool fromMouse) { const QMimeData *md = nullptr; - if (fromMouse) { - if (QGuiApplication::clipboard()->supportsSelection()) { - md = QGuiApplication::clipboard()->mimeData(QClipboard::Selection); - } + if (fromMouse && QGuiApplication::clipboard()->supportsSelection()) { + md = QGuiApplication::clipboard()->mimeData(QClipboard::Selection); } else { md = QGuiApplication::clipboard()->mimeData(QClipboard::Clipboard); } @@ -69,7 +67,7 @@ InputBar::insertMimeData(const QMimeData *md) const auto audio = formats.filter("audio/", Qt::CaseInsensitive); const auto video = formats.filter("video/", Qt::CaseInsensitive); - if (!image.empty() && md->hasImage()) { + if (md->hasImage()) { showPreview(*md, "", image); } else if (!audio.empty()) { showPreview(*md, "", audio); @@ -653,9 +651,15 @@ InputBar::showPreview(const QMimeData &source, QString path, const QStringList & new dialogs::PreviewUploadOverlay(ChatPage::instance()); previewDialog_->setAttribute(Qt::WA_DeleteOnClose); - if (source.hasImage()) - previewDialog_->setPreview(qvariant_cast(source.imageData()), formats.front()); - else if (!path.isEmpty()) + if (source.hasImage()) { + if (formats.size() && formats.front().startsWith("image/")) { + // known format, keep as-is + previewDialog_->setPreview(qvariant_cast(source.imageData()), formats.front()); + } else { + // unknown image format, default to image/png + previewDialog_->setPreview(qvariant_cast(source.imageData()), "image/png"); + } + } else if (!path.isEmpty()) previewDialog_->setPreview(path); else if (!formats.isEmpty()) { auto mime = formats.first(); From 94441e68fde86977a70d60c735b1363c8b61ba08 Mon Sep 17 00:00:00 2001 From: Thulinma Date: Tue, 28 Sep 2021 01:42:35 +0200 Subject: [PATCH 164/232] Support pasting image/svg+xml format straight from supporting applications --- src/dialogs/PreviewUploadOverlay.cpp | 7 +++++++ src/timeline/InputBar.cpp | 15 +++++++++++++-- 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/src/dialogs/PreviewUploadOverlay.cpp b/src/dialogs/PreviewUploadOverlay.cpp index e850c03b..2e95bd91 100644 --- a/src/dialogs/PreviewUploadOverlay.cpp +++ b/src/dialogs/PreviewUploadOverlay.cpp @@ -158,6 +158,8 @@ PreviewUploadOverlay::setPreview(const QImage &src, const QString &mime) void PreviewUploadOverlay::setPreview(const QByteArray data, const QString &mime) { + nhlog::ui()->info("Pasting {} bytes of data, mimetype {}", data.size(), mime.toStdString()); + auto const &split = mime.split('/'); auto const &type = split[1]; @@ -166,6 +168,11 @@ PreviewUploadOverlay::setPreview(const QByteArray data, const QString &mime) filePath_ = "clipboard." + type; isImage_ = false; + if (mime == "image/svg+xml") { + isImage_ = true; + image_.loadFromData(data_, mediaType_.toStdString().c_str()); + } + setLabels(type, mime, data_.size()); init(); } diff --git a/src/timeline/InputBar.cpp b/src/timeline/InputBar.cpp index c83bb19c..f518248b 100644 --- a/src/timeline/InputBar.cpp +++ b/src/timeline/InputBar.cpp @@ -68,7 +68,11 @@ InputBar::insertMimeData(const QMimeData *md) const auto video = formats.filter("video/", Qt::CaseInsensitive); if (md->hasImage()) { - showPreview(*md, "", image); + if (formats.contains("image/svg+xml", Qt::CaseInsensitive)) { + showPreview(*md, "", QStringList("image/svg+xml")); + } else { + showPreview(*md, "", image); + } } else if (!audio.empty()) { showPreview(*md, "", audio); } else if (!video.empty()) { @@ -651,7 +655,8 @@ InputBar::showPreview(const QMimeData &source, QString path, const QStringList & new dialogs::PreviewUploadOverlay(ChatPage::instance()); previewDialog_->setAttribute(Qt::WA_DeleteOnClose); - if (source.hasImage()) { + // Force SVG to _not_ be handled as an image, but as raw data + if (source.hasImage() && (!formats.size() || formats.front() != "image/svg+xml")) { if (formats.size() && formats.front().startsWith("image/")) { // known format, keep as-is previewDialog_->setPreview(qvariant_cast(source.imageData()), formats.front()); @@ -679,6 +684,12 @@ InputBar::showPreview(const QMimeData &source, QString path, const QStringList & &dialogs::PreviewUploadOverlay::confirmUpload, this, [this](const QByteArray data, const QString &mime, const QString &fn) { + if (!data.size()) { + nhlog::ui()->warn("Attempted to upload zero-byte file?! Mimetype {}, filename {}", + mime.toStdString(), + fn.toStdString()); + return; + } setUploading(true); setText(""); From 98042433543dc182a14062668b6d0bd4907ce2cb Mon Sep 17 00:00:00 2001 From: Weblate Date: Tue, 28 Sep 2021 07:36:56 -0400 Subject: [PATCH 165/232] Translated using Weblate (Finnish) Currently translated at 96.8% (551 of 569 strings) Co-authored-by: -- Translate-URL: https://weblate.nheko.im/projects/nheko/nheko-master/fi/ Translation: Nheko/nheko --- resources/langs/nheko_fi.ts | 150 ++++++++++++++++++------------------ 1 file changed, 75 insertions(+), 75 deletions(-) diff --git a/resources/langs/nheko_fi.ts b/resources/langs/nheko_fi.ts index b67b1489..77a5a553 100644 --- a/resources/langs/nheko_fi.ts +++ b/resources/langs/nheko_fi.ts @@ -501,42 +501,42 @@ There is no key to unlock this message. We requested the key automatically, but you can try requesting it again if you are impatient. - + Tämän viestin avaamista varten ei ole avainta. Pyysimme avainta automaattisesti, mutta voit yrittää pyytää sitä uudestaan jos olet kärsimätön. This message couldn't be decrypted, because we only have a key for newer messages. You can try requesting access to this message. - + Tämän viestin salausta ei voitu purkaa, koska meillä on avain vain uudemmille viesteille. Voit yrittää pyytää pääsyä tähän viestiin. There was an internal error reading the decryption key from the database. - + Sisäinen virhe tapahtui kun salausavainta yritettiin lukea tietokannasta. There was an error decrypting this message. - + Tämän viestin salauksen purkamisessa tapahtui virhe. The message couldn't be parsed. - + Tätä viestiä ei voitu jäsentää. The encryption key was reused! Someone is possibly trying to insert false messages into this chat! - + Salausavainta käytettiin uudelleen! Joku yrittää mahdollisesti tuoda vääriä viestejä tähän keskusteluun! Unknown decryption error - + Tuntematon virhe salauksen purkamisessa Request key - + Pyydä avainta
@@ -559,7 +559,7 @@ Encrypted by an unverified device or the key is from an untrusted source like the key backup. - + Vahvistamattoman laitteen salaama tai tämä avain on epäluotettavasta lähteestä kuten avaimen varmuuskopiosta. @@ -609,12 +609,12 @@ Editing image pack - + Muokataan kuvapakkausta Add images - + Lisää kuvia @@ -624,44 +624,44 @@ State key - + TIla-avain Packname - + Pakkauksen nimi Attribution - + Osoitus Use as Emoji - + Käytä emojina Use as Sticker - + Käytä tarrana Shortcode - + Lyhyt koodi Body - + Runko Remove from pack - + Poista pakkauksesta @@ -676,7 +676,7 @@ Save - + Tallenna @@ -684,42 +684,42 @@ Image pack settings - + Kuvapakkauksen asetukset Create account pack - + Luo tilipakkaus New room pack - + Uusi huonepakkaus Private pack - + Yksityinen pakkaus Pack from this room - + Pakkaus tälle huoneelle Globally enabled pack - + Kaikkialla käytössä oleva pakkaus Enable globally - + Salli käytettäväksi kaikkialla Enables this pack to be used in all rooms - + Sallii tämän pakkauksen käytettäväksi kaikissa huoneissa @@ -755,7 +755,7 @@ Invite users to %1 - + Kutsu käyttäjiä %1 @@ -766,17 +766,17 @@ @joe:matrix.org Example user id. The name 'joe' can be localized however you want. - + @matti:matrix.org Add - + Lisää Invite - + Kutsu @@ -974,7 +974,7 @@ Esimerkki: https://server.my:8787 Allow them in - + Salli heidät sisään @@ -1002,7 +1002,7 @@ Esimerkki: https://server.my:8787 Stickers - + Tarrat @@ -1117,7 +1117,7 @@ Esimerkki: https://server.my:8787 &Go to quoted message - + &Mene lainattuun viestiin @@ -1183,7 +1183,7 @@ Esimerkki: https://server.my:8787 You will be pinging the whole room - + Hälytät koko huonetta @@ -1302,7 +1302,7 @@ Esimerkki: https://server.my:8787 Yesterday, %1 - + Eilen, &1 @@ -1356,12 +1356,12 @@ Esimerkki: https://server.my:8787 Registration token - + Rekisteröitymispoletti Please enter a valid registration token. - + Anna kelvollinen rekisteröitymispoletti. @@ -1422,12 +1422,12 @@ Esimerkki: https://server.my:8787 Explore Public Rooms - + Tutki julkisia huoneita Search for public rooms - + Etsi julkisia huoneita @@ -1453,12 +1453,12 @@ Esimerkki: https://server.my:8787 Leave Room - + Poistu huoneesta Are you sure you want to leave this room? - + Oletko varma, että haluat poistua tästä huoneesta? @@ -1546,41 +1546,41 @@ Esimerkki: https://server.my:8787 Members of %1 - + &1 jäsenet %n people in %1 Summary above list of members - - - + + %n henkilö huoneessa %1 + %n henkilöä huonessa %1 Invite more people - + Kutsu lisää ihmisiä This room is not encrypted! - + Tämä huone ei ole salattu! This user is verified. - + Tämä käyttäjä on vahvistettu. This user isn't verified, but is still using the same master key from the first time you met. - + Tätä käyttäjää ei ole vahvistettu, mutta hän käyttää edelleen samaa päävavainta kuin ensimmäisellä tapaamiskerralla. This user has unverified devices! - + Tällä käyttäjällä on vahvistamattomia laitteita! @@ -1623,7 +1623,7 @@ Esimerkki: https://server.my:8787 Room access - + Huoneeseen pääsy @@ -1643,12 +1643,12 @@ Esimerkki: https://server.my:8787 By knocking - + Koputtamalla Restricted by membership in other rooms - + Rajoitettu jäsenyyden perusteella muissa huoneissa @@ -1669,17 +1669,17 @@ Esimerkki: https://server.my:8787 Sticker & Emote Settings - + Tarra- ja emojiasetukset Change - + Muuta Change what packs are enabled, remove packs or create new ones - + Muuta mitkä pakkaukset ovat sallittuja, poista pakkauksia tai luo uusia @@ -1733,17 +1733,17 @@ Esimerkki: https://server.my:8787 Pending invite. - + Kutsua odotetaan. Previewing this room - + Esikatsellaan tätä huonetta No preview available - + Esikatselu ei saatavilla @@ -1805,12 +1805,12 @@ Esimerkki: https://server.my:8787 Failed to connect to secret storage - + Salattuun tallennustilaan ei saatu yhteyttä Nheko could not connect to the secure storage to save encryption secrets to. This can have multiple reasons. Check if your D-Bus service is running and you have configured a service like KWallet, Gnome Secrets or the equivalent for your platform. If you are having trouble, feel free to open an issue here: https://github.com/Nheko-Reborn/nheko/issues - + Nheko ei pystynyt yhdistämään salattuun tallennustilaan tallentamaan salaukseen kuuluvia salaisuuksia. Tämä voi johtua useasta syystä. Tarkista, onko D-Bus-palvelu käynnissä ja oletko määrittänyt alustallesi palvelun kuten KWallet, Gnome Secrets tai vastaavan. Jos sinulla on ongelmia, voit luoda vikailmoituksen täällä: https://github.com/Nheko-Reborn/nheko/issues @@ -1819,22 +1819,22 @@ Esimerkki: https://server.my:8787 Failed to update image pack: %1 - + Kuvapakkausta %1 ei onnistuttu päivittämään Failed to delete old image pack: %1 - + Vanhaa kuvapakkausta %1 ei onnistuttu poistamaan Failed to open image: %1 - + Kuvaa %1 ei onnistuttu avaamaan Failed to upload image: %1 - + Kuvan lähetys epäonnistui: %s @@ -1941,12 +1941,12 @@ Esimerkki: https://server.my:8787 %1 allowed to join this room by knocking. - + Käyttäjän %1 annettiin liittyä tähän huoneeseen koputtamalla. %1 allowed members of the following rooms to automatically join this room: %2 - + %1 salli seuraavien huoneiden jäsenten liittyä automaattisesti tähän huoneeseen: %2 @@ -2006,7 +2006,7 @@ Esimerkki: https://server.my:8787 %1 joined via authorisation from %2's server. - + %1 liittyi käyttäjän %2 palvelimen suomalla vahvistuksella. @@ -2103,17 +2103,17 @@ Esimerkki: https://server.my:8787 join the conversation - + liity keskusteluun accept invite - + salli kutsu decline invite - + peru kutsu @@ -2144,12 +2144,12 @@ Esimerkki: https://server.my:8787 This room is not encrypted! - + Tämä huone ei ole salattu! This room contains only verified devices. - + Tämä huone sisältää vain vahvistettuja laitteita. From 49228c4128c89a9a9d041073c95ec9893fd82121 Mon Sep 17 00:00:00 2001 From: Patryk Cisek Date: Tue, 28 Sep 2021 12:16:33 -0700 Subject: [PATCH 166/232] Added more Polish translations. --- resources/langs/nheko_pl.ts | 143 ++++++++++++++++++------------------ 1 file changed, 72 insertions(+), 71 deletions(-) diff --git a/resources/langs/nheko_pl.ts b/resources/langs/nheko_pl.ts index 9684e4ed..cadd3e3d 100644 --- a/resources/langs/nheko_pl.ts +++ b/resources/langs/nheko_pl.ts @@ -2385,18 +2385,19 @@ OFF - kwadrat, ON - koło. Decrypt messages in sidebar - + Odszyfruj wiadomości na pasku bocznym Decrypt the messages shown in the sidebar. Only affects messages in encrypted chats. - + Odszyfruj wiadomości na pasku bocznym. +Dotyczy wyłącznie czatów z włączonym szyfrowaniem. Privacy Screen - + Ekran prywatności @@ -2407,7 +2408,7 @@ be blurred. Privacy screen timeout (in seconds [0 - 3600]) - + Opóźnienie ekranu prywatności (w sekundach [0 - 3600]) @@ -2562,47 +2563,47 @@ To zwykle sprawia, że ikona aplikacji w tacce systemowej jest animowana. Download message encryption keys from and upload to the encrypted online key backup. - + Pobierz klucze szyfrowania wiadomości i umieść w szyfrowanym backup-ie kluczy online. Enable online key backup - + Włącz backup kluczy online The Nheko authors recommend not enabling online key backup until symmetric online key backup is available. Enable anyway? - + Autorzy Nheko nie zalecają włączenia backup-u kluczy online dopóki symetryczny backup kluczy online nie jest dostępny. Włączyć mimo to? CACHED - + ZAPISANE W CACHE-U NOT CACHED - + NIE ZAPISANE W CACHE-U Scale factor - + Współczynnik skalowania Change the scale factor of the whole user interface. - + Zmień współczynnik skalowania całego interfejsu użytkownika. Font size - + Wielkość czcionki Font Family - + Rodzina czcionki @@ -2612,42 +2613,42 @@ To zwykle sprawia, że ikona aplikacji w tacce systemowej jest animowana. Ringtone - + Dzwonek Set the notification sound to play when a call invite arrives - + Ustaw dźwięk powiadomienia odtwarzanego podczas zaproszenia do połączenia Microphone - + Mikrofon Camera - + Kamera Camera resolution - + Rozdzielczość kamery Camera frame rate - + Ilość klatek na sekundę kamery Allow fallback call assist server - + Pozwól na korzystanie z serwera pomocniczego do nawiązywania połączeń głosowych/wideo Will use turn.matrix.org as assist when your home server does not offer one. - + Używaj serwera pomocniczego turn.matrix.org gdy twój homeserver nie udostępnia własnego serwera pomocniczego. @@ -2662,17 +2663,17 @@ To zwykle sprawia, że ikona aplikacji w tacce systemowej jest animowana. Session Keys - + Klucze sesji IMPORT - + IMPORTUJ EXPORT - + EKSPORTUS @@ -2687,82 +2688,82 @@ To zwykle sprawia, że ikona aplikacji w tacce systemowej jest animowana. INTERFACE - + INTERFEJS Plays media like GIFs or WEBPs only when explicitly hovering over them. - + Odtwarzaj media takie jak GIF czy WEBP tylko gdy wskazane przy użyciu myszy. Touchscreen mode - + Tryb ekranu dotykowego Will prevent text selection in the timeline to make touch scrolling easier. - + Zapobiega zaznaczaniu tekstu w historii rozmów by ułatwić przewijanie przy użyciu interfejsu dotykowego. Emoji Font Family - + Rodzina czcionki emotikon Master signing key - + Główny klucz podpisywania Your most important key. You don't need to have it cached, since not caching it makes it less likely it can be stolen and it is only needed to rotate your other signing keys. - + Twój najważniejszy klucz. Nie musi być zapisany w cache-u -- nie zapisując go w cache-u zmniejszasz ryzyko, że ten klucz zostanie wykradzony. Ponadto, ten klucz jest potrzebny wyłącznie podczas odświerzania kluczy podpisywania sesji. User signing key - + Klucz podpisywania użytkownika The key to verify other users. If it is cached, verifying a user will verify all their devices. - + Klucz używany do weryfikacji innych użytkowników. Gdy zapisany w cache-u, weryfikacja użytkownika dokona weryfikacji wszystkich jego urządzeń. Self signing key - + Klucz samo-podpisywania The key to verify your own devices. If it is cached, verifying one of your devices will mark it verified for all your other devices and for users, that have verified you. - + Klucz służący do weryfikacji twoich własnych urządzeń. Jeśli zapisany w cache-u, weryfikacja jednego z twoich urządzeń sprawi, że będzie ono zweryfikowane dla wszystkich twoich pozostałych urządzeń oraz dla tych użytkowników, którzy zweryfikowali Ciebie. Backup key - + Klucz backup-u The key to decrypt online key backups. If it is cached, you can enable online key backup to store encryption keys securely encrypted on the server. - + Klucz służący do odszyfrowania backup-u kluczy online. Jeśli zapisany w cache-u, możesz włączyć backup kluczy online by zapisać w klucze zaszyfrowane bezpiecznie w backup-ie na serwerze. Select a file - Wybierz plik + Wybierz plik All Files (*) - Wszystkie pliki (*) + Wszystkie pliki (*) Open Sessions File - + Otwórz Plik Sesji @@ -2772,34 +2773,34 @@ To zwykle sprawia, że ikona aplikacji w tacce systemowej jest animowana. Error - + Błąd File Password - + Hasło Pliku Enter the passphrase to decrypt the file: - + Wpisz frazę do odszyfrowania pliku: The password cannot be empty - + Hasło nie może być puste Enter passphrase to encrypt your session keys: - + Wpisz frazę do odszyfrowania twoich kluczy sesji: File to save the exported session keys - + Plik do którego zostaną wyeksportowane klucze sesji @@ -2909,7 +2910,7 @@ To zwykle sprawia, że ikona aplikacji w tacce systemowej jest animowana. Open Fallback in Browser - + W razie konieczności otwórz w przeglądarce @@ -2919,12 +2920,12 @@ To zwykle sprawia, że ikona aplikacji w tacce systemowej jest animowana. Confirm - Potwierdź + Potwierdź Open the fallback, follow the steps and confirm after completing them. - + Otwórz w przeglądarce i postępuj zgodnie z instrukcją. @@ -3016,112 +3017,112 @@ Rozmiar multimediów: %2 You sent an audio clip - + Wysłałeś(aś) klip audio %1 sent an audio clip - + %1 wysłał(a) klip audio You sent an image - + Wysłałeś(aś) obraz %1 sent an image - + %1 wysłał(a) obraz You sent a file - + Wysłałeś(aś) plik %1 sent a file - + %1 wysłał(a) plik You sent a video - + Wysłałeś(aś) wideo %1 sent a video - + %1 wysłał(a) wideo You sent a sticker - + Wysłałeś(aś) naklejkę %1 sent a sticker - + %1 wysłał(a) naklejkę You sent a notification - + Wysłałeś(aś) powiadomienie %1 sent a notification - + %1 wysłał(a) powiadomienie You: %1 - + Ty: %1 %1: %2 - + %1: %< You sent an encrypted message - + Wysłałeś(aś) zaszyfrowaną wiadomość %1 sent an encrypted message - + %1 wysłał(a) zaszyfrowaną wiadomość You placed a call - + Wykonujesz telefon %1 placed a call - + %1 wykonuje telefon You answered a call - + Odebrałeś(aś) połączenie %1 answered a call - + %1 odebrał(a) połączenie You ended a call - + Zakończyłeś(aś) połączenie %1 ended a call - + %1 zakończył(a) połączenie From d5ee86c29392e87285fb0710db931530f8da93e6 Mon Sep 17 00:00:00 2001 From: Weblate Date: Tue, 28 Sep 2021 16:02:58 -0400 Subject: [PATCH 167/232] Translated using Weblate (Finnish) Currently translated at 99.8% (568 of 569 strings) Co-authored-by: -- Translate-URL: https://weblate.nheko.im/projects/nheko/nheko-master/fi/ Translation: Nheko/nheko --- resources/langs/nheko_fi.ts | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/resources/langs/nheko_fi.ts b/resources/langs/nheko_fi.ts index 77a5a553..6e260ea7 100644 --- a/resources/langs/nheko_fi.ts +++ b/resources/langs/nheko_fi.ts @@ -2154,12 +2154,12 @@ Esimerkki: https://server.my:8787 This rooms contain verified devices and devices which have never changed their master key. - + Tämä huone sisältää vahvistettuja laitteita ja laitteita, jotka eivät ole koskaan vaihtaneet pääavainta. This room contains unverified devices! - + Tämä huone sisältää varmentamattomia laitteita! @@ -2215,37 +2215,37 @@ Esimerkki: https://server.my:8787 Change avatar globally. - + Vaihda avataria kaikkialla. Change avatar. Will only apply to this room. - + Muuta avataria. Toimii vain tässä huoneessa. Change display name globally. - + Muuta näyttönimeä kaikkialla. Change display name. Will only apply to this room. - + Muuta näyttönimeä. Toimii vain tässä huoneessa. Room: %1 - + Huone: %1 This is a room-specific profile. The user's name and avatar may be different from their global versions. - + Tämä on huoneelle erityinen profiili. Käyttäjän nimi ja avatar voivat erota niiden kaikkialla käytössä olevista versioista. Open the global profile for this user. - + Avaa tämän käyttäjän yleinen profiili. @@ -2488,7 +2488,7 @@ Kun poissa päältä, kaikki viestit lähetetään tavallisena tekstinä. Play animated images only on hover - + Toista animoidut kuvat vain kun kohdistin on niiden päällä @@ -2535,12 +2535,12 @@ Tämä yleensä saa sovelluksen kuvakkeen liikkumaan jollain tapaa tehtäväpalk Send encrypted messages to verified users only - + Lähetä salatut viestit vain vahvistetuille käyttäjille Requires a user to be verified to send encrypted messages to them. This improves safety but makes E2EE more tedious. - + Vaatii käyttäjän olevan vahvistettu, jotta hänelle voi lähettää salattuja viestejä. Tämä parantaa turvallisuutta, mutta tekee päästä-päähän -salauksen hankalammaksi. @@ -2550,12 +2550,12 @@ Tämä yleensä saa sovelluksen kuvakkeen liikkumaan jollain tapaa tehtäväpalk Automatically replies to key requests from other users, if they are verified, even if that device shouldn't have access to those keys otherwise. - + Automaattisesti vastaa avainpyyntöihin, jos ne ovat vahvistettuja, vaikka tuolla laitteella ei tulisi muuten olla pääsyä noihin avaimiin. Online Key Backup - + Avaimen varmuuskopiointi verkkoon @@ -2565,12 +2565,12 @@ Tämä yleensä saa sovelluksen kuvakkeen liikkumaan jollain tapaa tehtäväpalk Enable online key backup - + Salli avaimen varmuuskopiointi verkkoon The Nheko authors recommend not enabling online key backup until symmetric online key backup is available. Enable anyway? - + Nhekon tekijät eivät suosittele avaimen varmuuskopiointia verkkoon kunnes avaimen symmetrinen varmuuskopiointi verkkoon on saatavilla. Sallitaanko se kuitenkin? @@ -2690,7 +2690,7 @@ Tämä yleensä saa sovelluksen kuvakkeen liikkumaan jollain tapaa tehtäväpalk Plays media like GIFs or WEBPs only when explicitly hovering over them. - + Soittaa mediaa kuten GIF- ja WEBP-tiedostoja vain kun kursori on niiden kohdalla. From 4edb125ec4d0badc2c06814a4692f0519129ac1b Mon Sep 17 00:00:00 2001 From: Patryk Cisek Date: Tue, 28 Sep 2021 14:45:23 -0700 Subject: [PATCH 168/232] Added the rest of Polish translations and fixed couple of warnings indicated by Qt Linguist. --- resources/langs/nheko_pl.ts | 46 ++++++++++++++++++------------------- 1 file changed, 23 insertions(+), 23 deletions(-) diff --git a/resources/langs/nheko_pl.ts b/resources/langs/nheko_pl.ts index cadd3e3d..bbc2eaaa 100644 --- a/resources/langs/nheko_pl.ts +++ b/resources/langs/nheko_pl.ts @@ -133,7 +133,7 @@ Invited user: %1 - Zaproszono użytkownika + Zaproszono użytkownika %1 @@ -338,7 +338,7 @@ Rooms with low priority. - Pokoje o niskim priorytecie + Pokoje o niskim priorytecie. @@ -376,7 +376,7 @@ Failed to decrypt secrets with the provided recovery key or passphrase - Nie udało się odszyfrować sekretów przy pomocy podanego klucza odzyskiwania albo frazy-klucz. + Nie udało się odszyfrować sekretów przy pomocy podanego klucza odzyskiwania albo frazy-klucz @@ -624,17 +624,17 @@ State key - + Unikalny klucz paczki Packname - + Nazwa paczki Attribution - + Źródło (autor/link) @@ -651,12 +651,12 @@ Shortcode - + Skrót Body - + Treść @@ -814,7 +814,7 @@ Jeżeli Nheko nie odnajdzie Twojego serwera domowego, wyświetli formularz umoż Your password. - Twoje hasło + Twoje hasło. @@ -948,7 +948,7 @@ Przykład: https://server.my:8787 %1 placed a video call. - %1 rozpoczął(-ęła) połączenie wideo + %1 rozpoczął(-ęła) połączenie wideo. @@ -1237,7 +1237,7 @@ Przykład: https://server.my:8787 Place a call to %1? - Rozpocząć połączenie głosowe? + Rozpocząć połączenie głosowe z %1? @@ -1556,8 +1556,8 @@ Przykład: https://server.my:8787 Summary above list of members %n osoba w %1 - Podsumowanie - %n ludzi w %1 + %n osób w %1 + %n osób w %1 @@ -1568,7 +1568,7 @@ Przykład: https://server.my:8787 This room is not encrypted! - Ten pokój jest zaszyfrowany + Ten pokój jest szyfrowany! @@ -1755,7 +1755,7 @@ Przykład: https://server.my:8787 Share desktop with %1? - Udostępnić pulpit (desktop) użytkownikowi: %? + Udostępnić pulpit (desktop) użytkownikowi: %1? @@ -1928,9 +1928,9 @@ Przykład: https://server.my:8787 %1 and %2 are typing. Multiple users are typing. First argument is a comma separated list of potentially multiple users. Second argument is the last user of that list. (If only one user is typing, %1 is empty. You should still use it in your string though to silence Qt warnings.) - %1 i %2 piszą. - - + %1 %2 pisze. + %1, oraz %2 piszą (w sumie %n osób). + %1, oraz %2 piszą (w sumie %n osób). @@ -2071,7 +2071,7 @@ Przykład: https://server.my:8787 Rejected the knock from %1. - Odrzucono pukanie użytkownika %2. + Odrzucono pukanie użytkownika %1. @@ -2328,7 +2328,7 @@ Przykład: https://server.my:8787 Circular Avatars - + Okrągłe awatary @@ -2353,7 +2353,7 @@ Przykład: https://server.my:8787 REQUEST - + POPROŚ O @@ -2784,7 +2784,7 @@ To zwykle sprawia, że ikona aplikacji w tacce systemowej jest animowana. Enter the passphrase to decrypt the file: - Wpisz frazę do odszyfrowania pliku: + Wpisz frazę do odszyfrowania pliku: @@ -3082,7 +3082,7 @@ Rozmiar multimediów: %2 %1: %2 - %1: %< + %1: %2 From b8dfbc60db75fd00f000539f5b4652d3057e0031 Mon Sep 17 00:00:00 2001 From: Weblate Date: Wed, 29 Sep 2021 10:11:00 -0400 Subject: [PATCH 169/232] Translated using Weblate (Dutch) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Currently translated at 100.0% (569 of 569 strings) Co-authored-by: Jaron Viëtor Translate-URL: https://weblate.nheko.im/projects/nheko/nheko-master/nl/ Translation: Nheko/nheko --- resources/langs/nheko_nl.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/resources/langs/nheko_nl.ts b/resources/langs/nheko_nl.ts index c8764c0c..8579ad5f 100644 --- a/resources/langs/nheko_nl.ts +++ b/resources/langs/nheko_nl.ts @@ -3127,5 +3127,10 @@ Mediagrootte: %2 + + + &Go to quoted message + &Ga naar geciteerd bericht + From 4e020645f118055b94eda5704573854e4b81cf80 Mon Sep 17 00:00:00 2001 From: Loren Burkholder Date: Fri, 24 Sep 2021 23:27:57 -0400 Subject: [PATCH 170/232] Reorganize all the dialogs into the dialogs folder --- resources/qml/{ => dialogs}/InviteDialog.qml | 1 + resources/qml/{ => dialogs}/RawMessageDialog.qml | 0 resources/qml/{ => dialogs}/ReadReceipts.qml | 1 + resources/qml/{ => dialogs}/RoomDirectory.qml | 3 ++- resources/qml/{ => dialogs}/RoomMembers.qml | 3 ++- resources/qml/{ => dialogs}/RoomSettings.qml | 3 ++- resources/qml/{ => dialogs}/UserProfile.qml | 5 +++-- resources/res.qrc | 14 +++++++------- 8 files changed, 18 insertions(+), 12 deletions(-) rename resources/qml/{ => dialogs}/InviteDialog.qml (99%) rename resources/qml/{ => dialogs}/RawMessageDialog.qml (100%) rename resources/qml/{ => dialogs}/ReadReceipts.qml (99%) rename resources/qml/{ => dialogs}/RoomDirectory.qml (99%) rename resources/qml/{ => dialogs}/RoomMembers.qml (99%) rename resources/qml/{ => dialogs}/RoomSettings.qml (99%) rename resources/qml/{ => dialogs}/UserProfile.qml (99%) diff --git a/resources/qml/InviteDialog.qml b/resources/qml/dialogs/InviteDialog.qml similarity index 99% rename from resources/qml/InviteDialog.qml rename to resources/qml/dialogs/InviteDialog.qml index 916bdd39..86c176be 100644 --- a/resources/qml/InviteDialog.qml +++ b/resources/qml/dialogs/InviteDialog.qml @@ -2,6 +2,7 @@ // // SPDX-License-Identifier: GPL-3.0-or-later +import ".." import QtQuick 2.12 import QtQuick.Controls 2.12 import QtQuick.Layouts 1.12 diff --git a/resources/qml/RawMessageDialog.qml b/resources/qml/dialogs/RawMessageDialog.qml similarity index 100% rename from resources/qml/RawMessageDialog.qml rename to resources/qml/dialogs/RawMessageDialog.qml diff --git a/resources/qml/ReadReceipts.qml b/resources/qml/dialogs/ReadReceipts.qml similarity index 99% rename from resources/qml/ReadReceipts.qml rename to resources/qml/dialogs/ReadReceipts.qml index e1dd7c00..e825dd81 100644 --- a/resources/qml/ReadReceipts.qml +++ b/resources/qml/dialogs/ReadReceipts.qml @@ -2,6 +2,7 @@ // // SPDX-License-Identifier: GPL-3.0-or-later +import ".." import QtQuick 2.15 import QtQuick.Controls 2.15 import QtQuick.Layouts 1.15 diff --git a/resources/qml/RoomDirectory.qml b/resources/qml/dialogs/RoomDirectory.qml similarity index 99% rename from resources/qml/RoomDirectory.qml rename to resources/qml/dialogs/RoomDirectory.qml index 54d405ff..5c27fc26 100644 --- a/resources/qml/RoomDirectory.qml +++ b/resources/qml/dialogs/RoomDirectory.qml @@ -2,7 +2,8 @@ // // SPDX-License-Identifier: GPL-3.0-or-later -import "./ui" +import ".." +import "../ui" import QtQuick 2.9 import QtQuick.Controls 2.3 import QtQuick.Layouts 1.3 diff --git a/resources/qml/RoomMembers.qml b/resources/qml/dialogs/RoomMembers.qml similarity index 99% rename from resources/qml/RoomMembers.qml rename to resources/qml/dialogs/RoomMembers.qml index 3376a4b6..b2806292 100644 --- a/resources/qml/RoomMembers.qml +++ b/resources/qml/dialogs/RoomMembers.qml @@ -2,7 +2,8 @@ // // SPDX-License-Identifier: GPL-3.0-or-later -import "./ui" +import ".." +import "../ui" import QtQuick 2.12 import QtQuick.Controls 2.12 import QtQuick.Layouts 1.12 diff --git a/resources/qml/RoomSettings.qml b/resources/qml/dialogs/RoomSettings.qml similarity index 99% rename from resources/qml/RoomSettings.qml rename to resources/qml/dialogs/RoomSettings.qml index 6caf8790..0e7749ce 100644 --- a/resources/qml/RoomSettings.qml +++ b/resources/qml/dialogs/RoomSettings.qml @@ -2,7 +2,8 @@ // // SPDX-License-Identifier: GPL-3.0-or-later -import "./ui" +import ".." +import "../ui" import Qt.labs.platform 1.1 as Platform import QtQuick 2.15 import QtQuick.Controls 2.3 diff --git a/resources/qml/UserProfile.qml b/resources/qml/dialogs/UserProfile.qml similarity index 99% rename from resources/qml/UserProfile.qml rename to resources/qml/dialogs/UserProfile.qml index f57a9441..9bf548e3 100644 --- a/resources/qml/UserProfile.qml +++ b/resources/qml/dialogs/UserProfile.qml @@ -2,8 +2,9 @@ // // SPDX-License-Identifier: GPL-3.0-or-later -import "./device-verification" -import "./ui" +import ".." +import "../device-verification" +import "../ui" import QtQuick 2.15 import QtQuick.Controls 2.15 import QtQuick.Layouts 1.2 diff --git a/resources/res.qrc b/resources/res.qrc index 3514ebca..a001a929 100644 --- a/resources/res.qrc +++ b/resources/res.qrc @@ -139,11 +139,8 @@ qml/ForwardCompleter.qml qml/TypingIndicator.qml qml/NotificationWarning.qml - qml/RoomSettings.qml qml/emoji/EmojiPicker.qml qml/emoji/StickerPicker.qml - qml/UserProfile.qml - qml/RoomDirectory.qml qml/delegates/MessageDelegate.qml qml/delegates/Encrypted.qml qml/delegates/FileMessage.qml @@ -179,10 +176,13 @@ qml/components/AdaptiveLayoutElement.qml qml/components/AvatarListTile.qml qml/components/FlatButton.qml - qml/RoomMembers.qml - qml/InviteDialog.qml - qml/ReadReceipts.qml - qml/RawMessageDialog.qml + qml/dialogs/InviteDialog.qml + qml/dialogs/RawMessageDialog.qml + qml/dialogs/ReadReceipts.qml + qml/dialogs/RoomDirectory.qml + qml/dialogs/RoomMembers.qml + qml/dialogs/RoomSettings.qml + qml/dialogs/UserProfile.qml media/ring.ogg From 5de070d8253862693a03b8f8309e2265095e335e Mon Sep 17 00:00:00 2001 From: Weblate Date: Thu, 30 Sep 2021 04:36:42 -0400 Subject: [PATCH 171/232] Translated using Weblate (Portuguese (Portugal)) Currently translated at 17.5% (100 of 569 strings) Co-authored-by: Xenovox Translate-URL: https://weblate.nheko.im/projects/nheko/nheko-master/pt_PT/ Translation: Nheko/nheko --- resources/langs/nheko_pt_PT.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/langs/nheko_pt_PT.ts b/resources/langs/nheko_pt_PT.ts index 77e9b432..b50b47a2 100644 --- a/resources/langs/nheko_pt_PT.ts +++ b/resources/langs/nheko_pt_PT.ts @@ -521,7 +521,7 @@ The message couldn't be parsed. - + Esta mensagem não pôde ser analisada. From 5aae18c6c1ab2f55eadca8e3206ce8042e164ba2 Mon Sep 17 00:00:00 2001 From: Weblate Date: Thu, 30 Sep 2021 04:37:33 -0400 Subject: [PATCH 172/232] Translated using Weblate (Portuguese (Portugal)) Currently translated at 17.7% (101 of 569 strings) Translated using Weblate (Portuguese (Portugal)) Currently translated at 17.7% (101 of 569 strings) Co-authored-by: Tnpod Co-authored-by: Weblate Co-authored-by: Xenovox Translate-URL: https://weblate.nheko.im/projects/nheko/nheko-master/pt_PT/ Translation: Nheko/nheko --- resources/langs/nheko_pt_PT.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/resources/langs/nheko_pt_PT.ts b/resources/langs/nheko_pt_PT.ts index b50b47a2..94afcfb9 100644 --- a/resources/langs/nheko_pt_PT.ts +++ b/resources/langs/nheko_pt_PT.ts @@ -521,7 +521,7 @@ The message couldn't be parsed. - Esta mensagem não pôde ser analisada. + Esta mensagem não pôde ser processada. @@ -559,7 +559,7 @@ Encrypted by an unverified device or the key is from an untrusted source like the key backup. - + Encriptado por um dispositivo não verificado ou a chave é de uma fonte não confiável como o backup da chave.
From a5dc75df00d68b1519adabf9f323b6b97efc992a Mon Sep 17 00:00:00 2001 From: Weblate Date: Thu, 30 Sep 2021 07:10:06 -0400 Subject: [PATCH 173/232] Translated using Weblate (Portuguese (Portugal)) Currently translated at 24.4% (139 of 569 strings) Co-authored-by: Tnpod Translate-URL: https://weblate.nheko.im/projects/nheko/nheko-master/pt_PT/ Translation: Nheko/nheko --- resources/langs/nheko_pt_PT.ts | 82 +++++++++++++++++----------------- 1 file changed, 41 insertions(+), 41 deletions(-) diff --git a/resources/langs/nheko_pt_PT.ts b/resources/langs/nheko_pt_PT.ts index 94afcfb9..bdd9473a 100644 --- a/resources/langs/nheko_pt_PT.ts +++ b/resources/langs/nheko_pt_PT.ts @@ -559,7 +559,7 @@ Encrypted by an unverified device or the key is from an untrusted source like the key backup. - Encriptado por um dispositivo não verificado ou a chave é de uma fonte não confiável como o backup da chave. + Encriptado por um dispositivo não verificado ou a chave é de uma fonte não confiável, como o backup da chave. @@ -567,33 +567,33 @@ Verification failed - + Falha ao verifcar Other client does not support our verification protocol. - + O outro cliente não suporta o nosso protocolo de verificação. Key mismatch detected! - + Detetada divergência de chaves! Device verification timed out. - + A verificação do dispositivo expirou. Other party canceled the verification. - + A outra parte cancelou a verificação. Close - + Fechar @@ -601,7 +601,7 @@ Forward Message - + Reencaminhar mensagem @@ -609,17 +609,17 @@ Editing image pack - + A editar pacote de imagens Add images - + Adicionar imagens Stickers (*.png *.webp *.gif) - + Autocolantes (*.png *.webp *.gif) @@ -629,7 +629,7 @@ Packname - + Nome do pacote @@ -640,43 +640,43 @@ Use as Emoji - + Usar como emoji Use as Sticker - + Usar como autocolante Shortcode - + Código Body - + Corpo Remove from pack - + Remover do pacote Remove - + Remover Cancel - Cancelar + Cancelar Save - + Guardar @@ -684,52 +684,52 @@ Image pack settings - + Definições do pacote de imagens Create account pack - + Criar pacote de conta New room pack - + Criar pacote de sala Private pack - + Pacote privado Pack from this room - + Pacote desta sala Globally enabled pack - + Pacote ativo globalmente Enable globally - + Ativar globalmente Enables this pack to be used in all rooms - + Permite que o pacote seja usado em todas as salas Edit - + Editar Close - + Fechar @@ -737,17 +737,17 @@ Select a file - + Selecionar um ficheiro All Files (*) - + Todos os ficheiros (*) Failed to upload media. Please try again. - + Falha ao carregar mídia. Por favor, tente novamente. @@ -755,33 +755,33 @@ Invite users to %1 - + Convidar utilizadores para %1 User ID to invite - + ID do utilizador a convidar @joe:matrix.org Example user id. The name 'joe' can be localized however you want. - + @ze:matrix.org Add - + Adicionar Invite - + Convidar Cancel - Cancelar + Cancelar @@ -789,12 +789,12 @@ Matrix ID - + ID Matrix e.g @joe:matrix.org - + p. ex. @ze:matrix.org From a7d164f645993c4583db209108017346ac086080 Mon Sep 17 00:00:00 2001 From: Weblate Date: Fri, 1 Oct 2021 00:06:10 -0400 Subject: [PATCH 174/232] Translated using Weblate (Portuguese (Portugal)) Currently translated at 59.9% (341 of 569 strings) Co-authored-by: Tnpod Translate-URL: https://weblate.nheko.im/projects/nheko/nheko-master/pt_PT/ Translation: Nheko/nheko --- resources/langs/nheko_pt_PT.ts | 418 +++++++++++++++++---------------- 1 file changed, 211 insertions(+), 207 deletions(-) diff --git a/resources/langs/nheko_pt_PT.ts b/resources/langs/nheko_pt_PT.ts index bdd9473a..79831eff 100644 --- a/resources/langs/nheko_pt_PT.ts +++ b/resources/langs/nheko_pt_PT.ts @@ -802,48 +802,52 @@ You can also put your homeserver address there, if your server doesn't support .well-known lookup. Example: @user:server.my If Nheko fails to discover your homeserver, it will show you a field to enter the server manually. - + O seu nome de utilizador. +Um ID Matrix (MXID) deve iniciar com um @ seguido pelo ID do utilizador, uns :, e por fim, o nome do seu servidor. Pode, também, colocar o seu endereço, caso este não suporte pesquisas ".well-known". +Exemplo: @utilizador:servidor.meu +Se o Nheko não conseguir encontrar o seu servidor, irá apresentar um campo onde deve inserir o endereço manualmente. Password - + Palavra-passe Your password. - + A sua palavra-passe Device name - + Nome do dispositivo A name for this device, which will be shown to others, when verifying your devices. If none is provided a default is used. - + Um nome para este dispositivo, que será exibido noutros quando os estiver a verificar. Caso nenhum seja fornecido, será usado um predefinido. Homeserver address - + Endereço do servidor server.my:8787 - + servidor.meu:8787 The address that can be used to contact you homeservers client API. Example: https://server.my:8787 - + O endereço que pode ser usado para contactar a API de clientes do seu servidor. +Exemplo: https://servidor.meu:8787 LOGIN - + INCIAR SESSÃO @@ -851,47 +855,47 @@ Example: https://server.my:8787 You have entered an invalid Matrix ID e.g @joe:matrix.org - + Inseriu um ID Matrix inválido p. ex. @ze:matrix.org Autodiscovery failed. Received malformed response. - + Falha na descoberta automática. Reposta mal formatada recebida. Autodiscovery failed. Unknown error when requesting .well-known. - + Falha na descoberta automática. Erro desconhecido ao solicitar ".well-known". The required endpoints were not found. Possibly not a Matrix server. - + Não foi possível encontrar os funções ("endpoints") necessárias. Possivelmente não é um servidor Matrix. Received malformed response. Make sure the homeserver domain is valid. - + Resposta mal formada recebida. Certifique-se que o domínio do servidor está correto. An unknown error occured. Make sure the homeserver domain is valid. - + Erro desconhecido. Certifique-se que o domínio do servidor é válido. SSO LOGIN - + ENTRAR COM ISU (SSO) Empty password - + Palavra-passe vazia SSO login failed - + Falha no ISU (SSO) @@ -899,78 +903,78 @@ Example: https://server.my:8787 Encryption enabled - + Encriptação ativada room name changed to: %1 - + nome da sala alterado para: %1 removed room name - + nome da sala removido topic changed to: %1 - + tópico da sala alterado para: %1 removed topic - + tópico da sala removido %1 changed the room avatar - + %1 alterou o ícone da sala %1 created and configured room: %2 - + %1 criou e configurou a sala: %2 %1 placed a voice call. - + %1 iniciou uma chamada de voz. %1 placed a video call. - + %1 iniciou uma chamada de vídeo. %1 placed a call. - + %1 iniciou uma chamada. Negotiating call... - + A negociar chamada… Allow them in - + Permitir a entrada %1 answered the call. - + %1 atendeu a chamada. removed - + removida %1 ended the call. - + %1 terminou a chamada. @@ -978,42 +982,42 @@ Example: https://server.my:8787 Hang up - + Desligar Place a call - + Iniciar chamada Send a file - + Enviar um ficheiro Write a message... - + Escreva uma mensagem… Stickers - + Autocolantes Emoji - + Emoji Send - + Enviar You don't have permission to send messages in this room - + Não tem permissão para enviar mensagens nesta sala @@ -1021,99 +1025,99 @@ Example: https://server.my:8787 Edit - + Editar React - + Reagir Reply - + Responder Options - + Opções &Copy - + &Copiar Copy &link location - + Copiar localização da &ligação Re&act - + Re&agir Repl&y - + Responde&r &Edit - + &Editar Read receip&ts - + Recibos de &leitura &Forward - + Reen&caminhar &Mark as read - + &Marcar como lida View raw message - + Ver mensagem bruta View decrypted raw message - + Ver mensagem bruta desencriptada Remo&ve message - + Remo&ver mensagem &Save as - + &Guardar como &Open in external program - + Abrir num &programa externo Copy link to eve&nt - + Copiar ligação para o eve&nto &Go to quoted message - + Ir para mensagem &citada @@ -1121,57 +1125,57 @@ Example: https://server.my:8787 Send Verification Request - + Enviar pedido de verificação Received Verification Request - + Pedido de verificação recebido To allow other users to see, which of your devices actually belong to you, you can verify them. This also allows key backup to work automatically. Verify %1 now? - + Para que outros possam ver que dispositivos pertencem realmente a si, pode verificá-los. Isso permite, também, que a cópia de segurança de chaves funcione automaticamente. Verificar %1 agora? To ensure that no malicious user can eavesdrop on your encrypted communications you can verify the other party. - + Para garantir que nenhum utilizador mal-intencionado possa intercetar as suas comunicações encriptadas, pode verificar a outra parte. %1 has requested to verify their device %2. - + %1 requisitou a verificação do seu dispositivo %2. %1 using the device %2 has requested to be verified. - + %1, usando o dispositivo %2, requisitou a sua verificação. Your device (%1) has requested to be verified. - + O seu dispositivo (%1) requisitou a sua verificação. Cancel - Cancelar + Cancelar Deny - + Recusar Start verification - + Iniciar verificação Accept - Aceitar + Aceitar @@ -1179,7 +1183,7 @@ Example: https://server.my:8787 You will be pinging the whole room - + Irá causar uma notificação para toda a sala @@ -1189,41 +1193,41 @@ Example: https://server.my:8787 %1 sent an encrypted message - + %1 enviou uma mensagem encriptada * %1 %2 Format an emote message in a notification, %1 is the sender, %2 the message - + * %1 %2 %1 replied: %2 Format a reply in a notification. %1 is the sender, %2 the message - + %1 respondeu: %2 %1: %2 Format a normal message in a notification. %1 is the sender, %2 the message - + %1: %2 %1 replied with an encrypted message - + %1 respondeu com uma mensagem encriptada %1 replied to a message - + %1 respondeu a uma mensagem %1 sent a message - + %1 enviou uma mensagem @@ -1231,32 +1235,32 @@ Example: https://server.my:8787 Place a call to %1? - + Iniciar chamada para %1? No microphone found. - Nenhum microfone encontrado. + Nenhum microfone encontrado. Voice - + Voz Video - + Vídeo Screen - + Ecrã Cancel - Cancelar + Cancelar @@ -1264,7 +1268,7 @@ Example: https://server.my:8787 unimplemented event: - + evento não implementado: @@ -1272,17 +1276,17 @@ Example: https://server.my:8787 Create a unique profile, which allows you to log into several accounts at the same time and start multiple instances of nheko. - + Crie um perfil único que lhe permite entrar em várias contas simultaneamente e iniciar várias instâncias do Nheko. profile - + perfil profile name - + nome de perfil @@ -1290,7 +1294,7 @@ Example: https://server.my:8787 Read receipts - + Recibos de leitura @@ -1298,7 +1302,7 @@ Example: https://server.my:8787 Yesterday, %1 - + Ontem, %1 @@ -1306,98 +1310,98 @@ Example: https://server.my:8787 Username - + Nome de utilizador The username must not be empty, and must contain only the characters a-z, 0-9, ., _, =, -, and /. - + O nome de utilizador não pode ser vazio e tem que conter apenas os caracteres a-z, 0-9, ., _, =, - e /. Password - + Palavra-passe Please choose a secure password. The exact requirements for password strength may depend on your server. - + Por favor, escolha uma palavra-passe segura. Os requisitos exatos para a força da palavra-passe poderão depender no seu servidor. Password confirmation - + Confirmação da palavra-passe Homeserver - + Servidor A server that allows registration. Since matrix is decentralized, you need to first find a server you can register on or host your own. - + Um servidor que permita registos. Uma vez que a Matrix é descentralizada, o utilizador precisa primeiro de encontrar um servidor onde se possa registar, ou alojar o seu próprio. REGISTER - + REGISTAR No supported registration flows! - + Nenhum percurso de registo suportado! Registration token - + Testemunho de registo Please enter a valid registration token. - + Por favor, insira um testemunho de registo válido. Autodiscovery failed. Received malformed response. - + Falha na descoberta automática. Resposta mal formada recebida. Autodiscovery failed. Unknown error when requesting .well-known. - + Falha na descoberta automática. Erro desconhecido ao requisitar ".well-known". The required endpoints were not found. Possibly not a Matrix server. - + Não foi possível encontrar os funções ("endpoints") necessárias. Possivelmente não é um servidor Matrix. Received malformed response. Make sure the homeserver domain is valid. - + Resposta mal formada recebida. Certifique-se que o domínio do servidor está correto. An unknown error occured. Make sure the homeserver domain is valid. - + Erro desconhecido. Certifique-se que o domínio do servidor é válido. Password is not long enough (min 8 chars) - + A palavra-passe não é longa o suficiente (mín, 8 caracteres) Passwords don't match - + As palavras-passe não coincidem Invalid server name - + Nome do servidor inválido @@ -1405,12 +1409,12 @@ Example: https://server.my:8787 Close - + Fechar Cancel edit - + Cancelar edição @@ -1418,12 +1422,12 @@ Example: https://server.my:8787 Explore Public Rooms - + Explorar salas públicas Search for public rooms - + Procurar por salas públicas @@ -1431,7 +1435,7 @@ Example: https://server.my:8787 no version stored - + nenhuma versão guardada @@ -1439,102 +1443,102 @@ Example: https://server.my:8787 New tag - + Nova etiqueta Enter the tag you want to use: - + Insira a etiqueta que quer usar: Leave Room - + Sair da sala Are you sure you want to leave this room? - + Tem a certeza que quer sair desta sala? Leave room - + Sair da sala Tag room as: - + Etiquetar sala com: Favourite - + Favoritos Low priority - + Prioridade baixa Server notice - + Avisos do servidor Create new tag... - + Criar nova etiqueta... Status Message - + Mensagem de estado Enter your status message: - + Insira a sua mensagem de estado: Profile settings - + Definições de perfil Set status message - + Definir mensagem de estado Logout - + Terminar sessão Start a new chat - + Iniciar uma nova conversa Join a room - + Entrar numa sala Create a new room - + Criar uma nova sala Room directory - + Diretório de salas User settings - + Definições de utilizador @@ -1542,41 +1546,41 @@ Example: https://server.my:8787 Members of %1 - + Membros de %1 %n people in %1 Summary above list of members - - - + + %n pessoa em %1 + %n pessoas em %1 Invite more people - + Convidar mais pessoas This room is not encrypted! - + Esta sala não está encriptada! This user is verified. - + Este utilizador está verificado. This user isn't verified, but is still using the same master key from the first time you met. - + Este utilizador não está verificado, mas continua a usar a mesma chave-mestra da primeira vez que se conheceram. This user has unverified devices! - + Este utilizador tem dispositivos não verificados! @@ -1584,144 +1588,144 @@ Example: https://server.my:8787 Room Settings - + Definições de sala %1 member(s) - + %1 membro(s) SETTINGS - + DEFINIÇŎES Notifications - + Notificações Muted - + Silenciada Mentions only - + Apenas menções All messages - + Todas as mensagens Room access - + Acesso à sala Anyone and guests - + Qualquer pessoa e visitantes Anyone - + Qualquer pessoa Invited users - + Utilizadores convidados By knocking - + "Batendo à porta" Restricted by membership in other rooms - + Impedido por participação noutras salas Encryption - + Encriptação End-to-End Encryption - + Encriptação ponta-a-ponta Encryption is currently experimental and things might break unexpectedly. <br> Please take note that it can't be disabled afterwards. - + A encriptação é, de momento, experimental e certas coisas podem partir-se inesperadamente.<br>Por favor, tome nota de que depois não pode ser desativada. Sticker & Emote Settings - + Definições de autocolantes e emojis Change - + Alterar Change what packs are enabled, remove packs or create new ones - + Alterar a seleção de pacotes ativos, remover e criar novos pacotes INFO - + INFO Internal ID - + ID interno Room Version - + Versão da sala Failed to enable encryption: %1 - + Falha ao ativar encriptação: %1 Select an avatar - + Selecionar um ícone All Files (*) - + Todos os ficheiros (*) The selected file is not an image - + O ficheiro selecionado não é uma imagem Error while reading file: %1 - + Erro ao ler ficheiro: %1 Failed to upload image: %s - + Falha ao carregar imagem: %s @@ -1729,17 +1733,17 @@ Example: https://server.my:8787 Pending invite. - + Convite pendente. Previewing this room - + A pré-visualizar esta sala No preview available - + Pré-visualização não disponível @@ -1747,27 +1751,27 @@ Example: https://server.my:8787 Share desktop with %1? - + Partilhar ambiente de trabalho com %1? Window: - + Janela: Frame rate: - + Taxa de fotogramas: Include your camera picture-in-picture - + Incluir a sua câmara em miniatura Request remote camera - + Requisitar câmara remota @@ -1778,22 +1782,22 @@ Example: https://server.my:8787 Hide mouse cursor - + Esconder cursor do rato Share - + Partilhar Preview - + Pré-visualizar Cancel - Cancelar + Cancelar @@ -1801,12 +1805,12 @@ Example: https://server.my:8787 Failed to connect to secret storage - + Falha ao ligar ao armazenamento secreto Nheko could not connect to the secure storage to save encryption secrets to. This can have multiple reasons. Check if your D-Bus service is running and you have configured a service like KWallet, Gnome Secrets or the equivalent for your platform. If you are having trouble, feel free to open an issue here: https://github.com/Nheko-Reborn/nheko/issues - + O Nheko não foi capaz de ligar ao armazenamento seguro para guardar os segredos de encriptação. Isto pode se dever a várias razões. Confirme se o serviço "D-Bus" está a correr e se configurou um serviço como o "KWallet", "Gnome Secrets" ou algo equivalente na sua plataforma. Se continuar com dificuldades, não hesite em abrir um problema aqui: https://github.com/Nheko-Reborn/nheko/issues (em Inglês) @@ -1815,22 +1819,22 @@ Example: https://server.my:8787 Failed to update image pack: %1 - + Falha ao atualizar pacote de imagem: %1 Failed to delete old image pack: %1 - + Falha ao eliminar pacote de imagem antigo: %1 Failed to open image: %1 - + Falha ao abrir imagem: %1 Failed to upload image: %1 - + Falha ao carregar imagem: %1 @@ -1838,22 +1842,22 @@ Example: https://server.my:8787 Failed - + Falhou Sent - + Enviado Received - + Recebido Read - + Lido @@ -1861,7 +1865,7 @@ Example: https://server.my:8787 Search - Procurar + Procurar @@ -1869,17 +1873,17 @@ Example: https://server.my:8787 Successful Verification - + Verificação bem sucedida Verification successful! Both sides verified their devices! - + Verificação bem sucedida! Ambos os lados verificaram os seus dispositivos! Close - + Fechar @@ -1887,41 +1891,41 @@ Example: https://server.my:8787 Message redaction failed: %1 - + Falha ao eliminar mensagem: %1 Failed to encrypt event, sending aborted! - + Falha ao encriptar evento, envio abortado! Save image - + Guardar imagem Save video - + Guardar vídeo Save audio - + Guardar áudio Save file - + Guardar ficheiro %1 and %2 are typing. Multiple users are typing. First argument is a comma separated list of potentially multiple users. Second argument is the last user of that list. (If only one user is typing, %1 is empty. You should still use it in your string though to silence Qt warnings.) - - - + + %1%2 está a escrever... + %1 e %2 estão a escrever... From 95397f5aafccb9f2e1dc9efbde9b611409805a64 Mon Sep 17 00:00:00 2001 From: Weblate Date: Fri, 1 Oct 2021 10:20:22 -0400 Subject: [PATCH 175/232] Added translation using Weblate (Indonesian) Co-authored-by: Linerly --- resources/langs/nheko_id.ts | 3114 +++++++++++++++++++++++++++++++++++ 1 file changed, 3114 insertions(+) create mode 100644 resources/langs/nheko_id.ts diff --git a/resources/langs/nheko_id.ts b/resources/langs/nheko_id.ts new file mode 100644 index 00000000..5efb37fb --- /dev/null +++ b/resources/langs/nheko_id.ts @@ -0,0 +1,3114 @@ + + + + + ActiveCallBar + + + Calling... + + + + + + Connecting... + + + + + You are screen sharing + + + + + Hide/Show Picture-in-Picture + + + + + Unmute Mic + + + + + Mute Mic + + + + + AwaitingVerificationConfirmation + + + Awaiting Confirmation + + + + + Waiting for other side to complete verification. + + + + + Cancel + + + + + CallInvite + + + Video Call + + + + + Voice Call + + + + + No microphone found. + + + + + CallInviteBar + + + Video Call + + + + + Voice Call + + + + + Devices + + + + + Accept + + + + + Unknown microphone: %1 + + + + + Unknown camera: %1 + + + + + Decline + + + + + No microphone found. + + + + + CallManager + + + Entire screen + + + + + ChatPage + + + Failed to invite user: %1 + + + + + + Invited user: %1 + + + + + Migrating the cache to the current version failed. This can have different reasons. Please open an issue and try to use an older version in the mean time. Alternatively you can try deleting the cache manually. + + + + + Confirm join + + + + + Do you really want to join %1? + + + + + Room %1 created. + + + + + + Confirm invite + + + + + Do you really want to invite %1 (%2)? + + + + + Failed to invite %1 to %2: %3 + + + + + Confirm kick + + + + + Do you really want to kick %1 (%2)? + + + + + Kicked user: %1 + + + + + Confirm ban + + + + + Do you really want to ban %1 (%2)? + + + + + Failed to ban %1 in %2: %3 + + + + + Banned user: %1 + + + + + Confirm unban + + + + + Do you really want to unban %1 (%2)? + + + + + Failed to unban %1 in %2: %3 + + + + + Unbanned user: %1 + + + + + Do you really want to start a private chat with %1? + + + + + Cache migration failed! + + + + + Incompatible cache version + + + + + The cache on your disk is newer than this version of Nheko supports. Please update or clear your cache. + + + + + Failed to restore OLM account. Please login again. + + + + + + + Failed to restore save data. Please login again. + + + + + Failed to setup encryption keys. Server response: %1 %2. Please try again later. + + + + + + Please try to login again: %1 + + + + + Failed to join room: %1 + + + + + You joined the room + + + + + Failed to remove invite: %1 + + + + + Room creation failed: %1 + + + + + Failed to leave room: %1 + + + + + Failed to kick %1 from %2: %3 + + + + + CommunitiesList + + + Hide rooms with this tag or from this space by default. + + + + + CommunitiesModel + + + All rooms + + + + + Shows all rooms without filtering. + + + + + Favourites + + + + + Rooms you have favourited. + + + + + Low Priority + + + + + Rooms with low priority. + + + + + Server Notices + + + + + Messages from your server or administrator. + + + + + CrossSigningSecrets + + + Decrypt secrets + + + + + Enter your recovery key or passphrase to decrypt your secrets: + + + + + Enter your recovery key or passphrase called %1 to decrypt your secrets: + + + + + Decryption failed + + + + + Failed to decrypt secrets with the provided recovery key or passphrase + + + + + DigitVerification + + + Verification Code + + + + + Please verify the following digits. You should see the same numbers on both sides. If they differ, please press 'They do not match!' to abort verification! + + + + + They do not match! + + + + + They match! + + + + + EditModal + + + Apply + + + + + Cancel + + + + + Name + + + + + Topic + + + + + EmojiPicker + + + Search + + + + + People + + + + + Nature + + + + + Food + + + + + Activity + + + + + Travel + + + + + Objects + + + + + Symbols + + + + + Flags + + + + + EmojiVerification + + + Verification Code + + + + + Please verify the following emoji. You should see the same emoji on both sides. If they differ, please press 'They do not match!' to abort verification! + + + + + They do not match! + + + + + They match! + + + + + Encrypted + + + There is no key to unlock this message. We requested the key automatically, but you can try requesting it again if you are impatient. + + + + + This message couldn't be decrypted, because we only have a key for newer messages. You can try requesting access to this message. + + + + + There was an internal error reading the decryption key from the database. + + + + + There was an error decrypting this message. + + + + + The message couldn't be parsed. + + + + + The encryption key was reused! Someone is possibly trying to insert false messages into this chat! + + + + + Unknown decryption error + + + + + Request key + + + + + EncryptionIndicator + + + This message is not encrypted! + + + + + Encrypted by a verified device + + + + + Encrypted by an unverified device, but you have trusted that user so far. + + + + + Encrypted by an unverified device or the key is from an untrusted source like the key backup. + + + + + Failed + + + Verification failed + + + + + Other client does not support our verification protocol. + + + + + Key mismatch detected! + + + + + + Device verification timed out. + + + + + Other party canceled the verification. + + + + + Close + + + + + ForwardCompleter + + + Forward Message + + + + + ImagePackEditorDialog + + + Editing image pack + + + + + Add images + + + + + Stickers (*.png *.webp *.gif) + + + + + State key + + + + + Packname + + + + + Attribution + + + + + + Use as Emoji + + + + + + Use as Sticker + + + + + Shortcode + + + + + Body + + + + + Remove from pack + + + + + Remove + + + + + Cancel + + + + + Save + + + + + ImagePackSettingsDialog + + + Image pack settings + + + + + Create account pack + + + + + New room pack + + + + + Private pack + + + + + Pack from this room + + + + + Globally enabled pack + + + + + Enable globally + + + + + Enables this pack to be used in all rooms + + + + + Edit + + + + + Close + + + + + InputBar + + + Select a file + + + + + All Files (*) + + + + + Failed to upload media. Please try again. + + + + + InviteDialog + + + Invite users to %1 + + + + + User ID to invite + + + + + @joe:matrix.org + Example user id. The name 'joe' can be localized however you want. + + + + + Add + + + + + Invite + + + + + Cancel + + + + + LoginPage + + + Matrix ID + + + + + e.g @joe:matrix.org + + + + + Your login name. A mxid should start with @ followed by the user id. After the user id you need to include your server name after a :. +You can also put your homeserver address there, if your server doesn't support .well-known lookup. +Example: @user:server.my +If Nheko fails to discover your homeserver, it will show you a field to enter the server manually. + + + + + Password + + + + + Your password. + + + + + Device name + + + + + A name for this device, which will be shown to others, when verifying your devices. If none is provided a default is used. + + + + + Homeserver address + + + + + server.my:8787 + + + + + The address that can be used to contact you homeservers client API. +Example: https://server.my:8787 + + + + + LOGIN + + + + + + + + You have entered an invalid Matrix ID e.g @joe:matrix.org + + + + + Autodiscovery failed. Received malformed response. + + + + + Autodiscovery failed. Unknown error when requesting .well-known. + + + + + The required endpoints were not found. Possibly not a Matrix server. + + + + + Received malformed response. Make sure the homeserver domain is valid. + + + + + An unknown error occured. Make sure the homeserver domain is valid. + + + + + SSO LOGIN + + + + + Empty password + + + + + SSO login failed + + + + + MessageDelegate + + + Encryption enabled + + + + + room name changed to: %1 + + + + + removed room name + + + + + topic changed to: %1 + + + + + removed topic + + + + + %1 changed the room avatar + + + + + %1 created and configured room: %2 + + + + + %1 placed a voice call. + + + + + %1 placed a video call. + + + + + %1 placed a call. + + + + + Negotiating call... + + + + + Allow them in + + + + + %1 answered the call. + + + + + + removed + + + + + %1 ended the call. + + + + + MessageInput + + + Hang up + + + + + Place a call + + + + + Send a file + + + + + Write a message... + + + + + Stickers + + + + + Emoji + + + + + Send + + + + + You don't have permission to send messages in this room + + + + + MessageView + + + Edit + + + + + React + + + + + Reply + + + + + Options + + + + + + &Copy + + + + + + Copy &link location + + + + + Re&act + + + + + Repl&y + + + + + &Edit + + + + + Read receip&ts + + + + + &Forward + + + + + &Mark as read + + + + + View raw message + + + + + View decrypted raw message + + + + + Remo&ve message + + + + + &Save as + + + + + &Open in external program + + + + + Copy link to eve&nt + + + + + &Go to quoted message + + + + + NewVerificationRequest + + + Send Verification Request + + + + + Received Verification Request + + + + + To allow other users to see, which of your devices actually belong to you, you can verify them. This also allows key backup to work automatically. Verify %1 now? + + + + + To ensure that no malicious user can eavesdrop on your encrypted communications you can verify the other party. + + + + + %1 has requested to verify their device %2. + + + + + %1 using the device %2 has requested to be verified. + + + + + Your device (%1) has requested to be verified. + + + + + Cancel + + + + + Deny + + + + + Start verification + + + + + Accept + + + + + NotificationWarning + + + You will be pinging the whole room + + + + + NotificationsManager + + + + + %1 sent an encrypted message + + + + + * %1 %2 + Format an emote message in a notification, %1 is the sender, %2 the message + + + + + %1 replied: %2 + Format a reply in a notification. %1 is the sender, %2 the message + + + + + %1: %2 + Format a normal message in a notification. %1 is the sender, %2 the message + + + + + + %1 replied with an encrypted message + + + + + %1 replied to a message + + + + + %1 sent a message + + + + + PlaceCall + + + Place a call to %1? + + + + + No microphone found. + + + + + Voice + + + + + Video + + + + + Screen + + + + + Cancel + + + + + Placeholder + + + unimplemented event: + + + + + QCoreApplication + + + Create a unique profile, which allows you to log into several accounts at the same time and start multiple instances of nheko. + + + + + profile + + + + + profile name + + + + + ReadReceipts + + + Read receipts + + + + + ReadReceiptsModel + + + Yesterday, %1 + + + + + RegisterPage + + + Username + + + + + + The username must not be empty, and must contain only the characters a-z, 0-9, ., _, =, -, and /. + + + + + Password + + + + + Please choose a secure password. The exact requirements for password strength may depend on your server. + + + + + Password confirmation + + + + + Homeserver + + + + + A server that allows registration. Since matrix is decentralized, you need to first find a server you can register on or host your own. + + + + + REGISTER + + + + + No supported registration flows! + + + + + Registration token + + + + + Please enter a valid registration token. + + + + + Autodiscovery failed. Received malformed response. + + + + + Autodiscovery failed. Unknown error when requesting .well-known. + + + + + The required endpoints were not found. Possibly not a Matrix server. + + + + + Received malformed response. Make sure the homeserver domain is valid. + + + + + An unknown error occured. Make sure the homeserver domain is valid. + + + + + Password is not long enough (min 8 chars) + + + + + Passwords don't match + + + + + Invalid server name + + + + + ReplyPopup + + + Close + + + + + Cancel edit + + + + + RoomDirectory + + + Explore Public Rooms + + + + + Search for public rooms + + + + + RoomInfo + + + no version stored + + + + + RoomList + + + New tag + + + + + Enter the tag you want to use: + + + + + Leave Room + + + + + Are you sure you want to leave this room? + + + + + Leave room + + + + + Tag room as: + + + + + Favourite + + + + + Low priority + + + + + Server notice + + + + + Create new tag... + + + + + Status Message + + + + + Enter your status message: + + + + + Profile settings + + + + + Set status message + + + + + Logout + + + + + Start a new chat + + + + + Join a room + + + + + Create a new room + + + + + Room directory + + + + + User settings + + + + + RoomMembers + + + Members of %1 + + + + + %n people in %1 + Summary above list of members + + + + + + + Invite more people + + + + + This room is not encrypted! + + + + + This user is verified. + + + + + This user isn't verified, but is still using the same master key from the first time you met. + + + + + This user has unverified devices! + + + + + RoomSettings + + + Room Settings + + + + + %1 member(s) + + + + + SETTINGS + + + + + Notifications + + + + + Muted + + + + + Mentions only + + + + + All messages + + + + + Room access + + + + + Anyone and guests + + + + + Anyone + + + + + Invited users + + + + + By knocking + + + + + Restricted by membership in other rooms + + + + + Encryption + + + + + End-to-End Encryption + + + + + Encryption is currently experimental and things might break unexpectedly. <br> + Please take note that it can't be disabled afterwards. + + + + + Sticker & Emote Settings + + + + + Change + + + + + Change what packs are enabled, remove packs or create new ones + + + + + INFO + + + + + Internal ID + + + + + Room Version + + + + + Failed to enable encryption: %1 + + + + + Select an avatar + + + + + All Files (*) + + + + + The selected file is not an image + + + + + Error while reading file: %1 + + + + + + Failed to upload image: %s + + + + + RoomlistModel + + + Pending invite. + + + + + Previewing this room + + + + + No preview available + + + + + ScreenShare + + + Share desktop with %1? + + + + + Window: + + + + + Frame rate: + + + + + Include your camera picture-in-picture + + + + + Request remote camera + + + + + + View your callee's camera like a regular video call + + + + + Hide mouse cursor + + + + + Share + + + + + Preview + + + + + Cancel + + + + + SecretStorage + + + Failed to connect to secret storage + + + + + Nheko could not connect to the secure storage to save encryption secrets to. This can have multiple reasons. Check if your D-Bus service is running and you have configured a service like KWallet, Gnome Secrets or the equivalent for your platform. If you are having trouble, feel free to open an issue here: https://github.com/Nheko-Reborn/nheko/issues + + + + + SingleImagePackModel + + + + Failed to update image pack: %1 + + + + + Failed to delete old image pack: %1 + + + + + Failed to open image: %1 + + + + + Failed to upload image: %1 + + + + + StatusIndicator + + + Failed + + + + + Sent + + + + + Received + + + + + Read + + + + + StickerPicker + + + Search + + + + + Success + + + Successful Verification + + + + + Verification successful! Both sides verified their devices! + + + + + Close + + + + + TimelineModel + + + Message redaction failed: %1 + + + + + + Failed to encrypt event, sending aborted! + + + + + Save image + + + + + Save video + + + + + Save audio + + + + + Save file + + + + + %1 and %2 are typing. + Multiple users are typing. First argument is a comma separated list of potentially multiple users. Second argument is the last user of that list. (If only one user is typing, %1 is empty. You should still use it in your string though to silence Qt warnings.) + + + + + + + %1 opened the room to the public. + + + + + %1 made this room require and invitation to join. + + + + + %1 allowed to join this room by knocking. + + + + + %1 allowed members of the following rooms to automatically join this room: %2 + + + + + %1 made the room open to guests. + + + + + %1 has closed the room to guest access. + + + + + %1 made the room history world readable. Events may be now read by non-joined people. + + + + + %1 set the room history visible to members from this point on. + + + + + %1 set the room history visible to members since they were invited. + + + + + %1 set the room history visible to members since they joined the room. + + + + + %1 has changed the room's permissions. + + + + + %1 was invited. + + + + + %1 changed their avatar. + + + + + %1 changed some profile info. + + + + + %1 joined. + + + + + %1 joined via authorisation from %2's server. + + + + + %1 rejected their invite. + + + + + Revoked the invite to %1. + + + + + %1 left the room. + + + + + Kicked %1. + + + + + Unbanned %1. + + + + + %1 was banned. + + + + + Reason: %1 + + + + + %1 redacted their knock. + + + + + You joined this room. + + + + + %1 has changed their avatar and changed their display name to %2. + + + + + %1 has changed their display name to %2. + + + + + Rejected the knock from %1. + + + + + %1 left after having already left! + This is a leave event after the user already left and shouldn't happen apart from state resets + + + + + %1 knocked. + + + + + TimelineRow + + + Edited + + + + + TimelineView + + + No room open + + + + + %1 member(s) + + + + + join the conversation + + + + + accept invite + + + + + decline invite + + + + + Back to room list + + + + + TimelineViewManager + + + No encrypted private chat found with this user. Create an encrypted private chat with this user and try again. + + + + + TopBar + + + Back to room list + + + + + No room selected + + + + + This room is not encrypted! + + + + + This room contains only verified devices. + + + + + This rooms contain verified devices and devices which have never changed their master key. + + + + + This room contains unverified devices! + + + + + Room options + + + + + Invite users + + + + + Members + + + + + Leave room + + + + + Settings + + + + + TrayIcon + + + Show + + + + + Quit + + + + + UserProfile + + + Global User Profile + + + + + Room User Profile + + + + + Change avatar globally. + + + + + Change avatar. Will only apply to this room. + + + + + Change display name globally. + + + + + Change display name. Will only apply to this room. + + + + + Room: %1 + + + + + This is a room-specific profile. The user's name and avatar may be different from their global versions. + + + + + Open the global profile for this user. + + + + + + Verify + + + + + Start a private chat. + + + + + Kick the user. + + + + + Ban the user. + + + + + Unverify + + + + + Select an avatar + + + + + All Files (*) + + + + + The selected file is not an image + + + + + Error while reading file: %1 + + + + + UserSettings + + + + Default + + + + + UserSettingsPage + + + Minimize to tray + + + + + Start in tray + + + + + Group's sidebar + + + + + Circular Avatars + + + + + profile: %1 + + + + + Default + + + + + CALLS + + + + + Cross Signing Keys + + + + + REQUEST + + + + + DOWNLOAD + + + + + Keep the application running in the background after closing the client window. + + + + + Start the application in the background without showing the client window. + + + + + Change the appearance of user avatars in chats. +OFF - square, ON - Circle. + + + + + Show a column containing groups and tags next to the room list. + + + + + Decrypt messages in sidebar + + + + + Decrypt the messages shown in the sidebar. +Only affects messages in encrypted chats. + + + + + Privacy Screen + + + + + When the window loses focus, the timeline will +be blurred. + + + + + Privacy screen timeout (in seconds [0 - 3600]) + + + + + Set timeout (in seconds) for how long after window loses +focus before the screen will be blurred. +Set to 0 to blur immediately after focus loss. Max value of 1 hour (3600 seconds) + + + + + Show buttons in timeline + + + + + Show buttons to quickly reply, react or access additional options next to each message. + + + + + Limit width of timeline + + + + + Set the max width of messages in the timeline (in pixels). This can help readability on wide screen, when Nheko is maximised + + + + + Typing notifications + + + + + Show who is typing in a room. +This will also enable or disable sending typing notifications to others. + + + + + Sort rooms by unreads + + + + + Display rooms with new messages first. +If this is off, the list of rooms will only be sorted by the timestamp of the last message in a room. +If this is on, rooms which have active notifications (the small circle with a number in it) will be sorted on top. Rooms, that you have muted, will still be sorted by timestamp, since you don't seem to consider them as important as the other rooms. + + + + + Read receipts + + + + + Show if your message was read. +Status is displayed next to timestamps. + + + + + Send messages as Markdown + + + + + Allow using markdown in messages. +When disabled, all messages are sent as a plain text. + + + + + Play animated images only on hover + + + + + Desktop notifications + + + + + Notify about received message when the client is not currently focused. + + + + + Alert on notification + + + + + Show an alert when a message is received. +This usually causes the application icon in the task bar to animate in some fashion. + + + + + Highlight message on hover + + + + + Change the background color of messages when you hover over them. + + + + + Large Emoji in timeline + + + + + Make font size larger if messages with only a few emojis are displayed. + + + + + Send encrypted messages to verified users only + + + + + Requires a user to be verified to send encrypted messages to them. This improves safety but makes E2EE more tedious. + + + + + Share keys with verified users and devices + + + + + Automatically replies to key requests from other users, if they are verified, even if that device shouldn't have access to those keys otherwise. + + + + + Online Key Backup + + + + + Download message encryption keys from and upload to the encrypted online key backup. + + + + + Enable online key backup + + + + + The Nheko authors recommend not enabling online key backup until symmetric online key backup is available. Enable anyway? + + + + + CACHED + + + + + NOT CACHED + + + + + Scale factor + + + + + Change the scale factor of the whole user interface. + + + + + Font size + + + + + Font Family + + + + + Theme + + + + + Ringtone + + + + + Set the notification sound to play when a call invite arrives + + + + + Microphone + + + + + Camera + + + + + Camera resolution + + + + + Camera frame rate + + + + + Allow fallback call assist server + + + + + Will use turn.matrix.org as assist when your home server does not offer one. + + + + + Device ID + + + + + Device Fingerprint + + + + + Session Keys + + + + + IMPORT + + + + + EXPORT + + + + + ENCRYPTION + + + + + GENERAL + + + + + INTERFACE + + + + + Plays media like GIFs or WEBPs only when explicitly hovering over them. + + + + + Touchscreen mode + + + + + Will prevent text selection in the timeline to make touch scrolling easier. + + + + + Emoji Font Family + + + + + Master signing key + + + + + Your most important key. You don't need to have it cached, since not caching it makes it less likely it can be stolen and it is only needed to rotate your other signing keys. + + + + + User signing key + + + + + The key to verify other users. If it is cached, verifying a user will verify all their devices. + + + + + Self signing key + + + + + The key to verify your own devices. If it is cached, verifying one of your devices will mark it verified for all your other devices and for users, that have verified you. + + + + + Backup key + + + + + The key to decrypt online key backups. If it is cached, you can enable online key backup to store encryption keys securely encrypted on the server. + + + + + Select a file + + + + + All Files (*) + + + + + Open Sessions File + + + + + + + + + + Error + + + + + + File Password + + + + + Enter the passphrase to decrypt the file: + + + + + + The password cannot be empty + + + + + Enter passphrase to encrypt your session keys: + + + + + File to save the exported session keys + + + + + Waiting + + + Waiting for other party… + + + + + Waiting for other side to accept the verification request. + + + + + Waiting for other side to continue the verification process. + + + + + Waiting for other side to complete the verification process. + + + + + Cancel + + + + + WelcomePage + + + Welcome to nheko! The desktop client for the Matrix protocol. + + + + + Enjoy your stay! + + + + + REGISTER + + + + + LOGIN + + + + + descriptiveTime + + + Yesterday + + + + + dialogs::CreateRoom + + + Create room + + + + + Cancel + + + + + Name + + + + + Topic + + + + + Alias + + + + + Room Visibility + + + + + Room Preset + + + + + Direct Chat + + + + + dialogs::FallbackAuth + + + Open Fallback in Browser + + + + + Cancel + + + + + Confirm + + + + + Open the fallback, follow the steps and confirm after completing them. + + + + + dialogs::JoinRoom + + + Join + + + + + Cancel + + + + + Room ID or alias + + + + + dialogs::LeaveRoom + + + Cancel + + + + + Are you sure you want to leave? + + + + + dialogs::Logout + + + Cancel + + + + + Logout. Are you sure? + + + + + dialogs::PreviewUploadOverlay + + + Upload + + + + + Cancel + + + + + Media type: %1 +Media size: %2 + + + + + + dialogs::ReCaptcha + + + Cancel + + + + + Confirm + + + + + Solve the reCAPTCHA and press the confirm button + + + + + message-description sent: + + + You sent an audio clip + + + + + %1 sent an audio clip + + + + + You sent an image + + + + + %1 sent an image + + + + + You sent a file + + + + + %1 sent a file + + + + + You sent a video + + + + + %1 sent a video + + + + + You sent a sticker + + + + + %1 sent a sticker + + + + + You sent a notification + + + + + %1 sent a notification + + + + + You: %1 + + + + + %1: %2 + + + + + You sent an encrypted message + + + + + %1 sent an encrypted message + + + + + You placed a call + + + + + %1 placed a call + + + + + You answered a call + + + + + %1 answered a call + + + + + You ended a call + + + + + %1 ended a call + + + + + utils + + + Unknown Message Type + + + + From a6c3e3562cdb1519326cf6237d0aa4beaaebbbae Mon Sep 17 00:00:00 2001 From: Loren Burkholder Date: Thu, 30 Sep 2021 21:24:28 -0400 Subject: [PATCH 176/232] Fix some strings and translation stuff --- resources/qml/NotificationWarning.qml | 2 +- src/notifications/Manager.cpp | 10 ++-------- 2 files changed, 3 insertions(+), 9 deletions(-) diff --git a/resources/qml/NotificationWarning.qml b/resources/qml/NotificationWarning.qml index 95ca210b..75ef5f17 100644 --- a/resources/qml/NotificationWarning.qml +++ b/resources/qml/NotificationWarning.qml @@ -29,7 +29,7 @@ Item { anchors.rightMargin: 10 anchors.bottom: parent.bottom color: Nheko.theme.red - text: qsTr("You will be pinging the whole room") + text: qsTr("You are about to notify the whole room") textFormat: Text.PlainText } diff --git a/src/notifications/Manager.cpp b/src/notifications/Manager.cpp index f24a48ff..5d51c6c8 100644 --- a/src/notifications/Manager.cpp +++ b/src/notifications/Manager.cpp @@ -23,18 +23,12 @@ NotificationsManager::getMessageTemplate(const mtx::responses::Notification ¬ } if (mtx::accessors::msg_type(notification.event) == mtx::events::MessageType::Emote) { - return tr("* %1 %2", - "Format an emote message in a notification, %1 is the sender, %2 the " - "message") - .arg(sender); + return QString("* %1 %2").arg(sender); } else if (utils::isReply(notification.event)) { return tr("%1 replied: %2", "Format a reply in a notification. %1 is the sender, %2 the message") .arg(sender); } else { - return tr("%1: %2", - "Format a normal message in a notification. %1 is the sender, %2 the " - "message") - .arg(sender); + return QString("%1: %2").arg(sender); } } From ed396bfcdc94e9abc143725ca1bc115d8c159d1a Mon Sep 17 00:00:00 2001 From: Weblate Date: Fri, 1 Oct 2021 15:19:34 -0400 Subject: [PATCH 177/232] Translated using Weblate (Esperanto) Currently translated at 99.8% (568 of 569 strings) Co-authored-by: Tirifto Translate-URL: https://weblate.nheko.im/projects/nheko/nheko-master/eo/ Translation: Nheko/nheko --- resources/langs/nheko_eo.ts | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/resources/langs/nheko_eo.ts b/resources/langs/nheko_eo.ts index 192e1a36..1dd14d54 100644 --- a/resources/langs/nheko_eo.ts +++ b/resources/langs/nheko_eo.ts @@ -1121,7 +1121,7 @@ Ekzemplo: https://servilo.mia:8787 &Go to quoted message - + &Iri al citita mesaĝo @@ -2222,37 +2222,37 @@ Ekzemplo: https://servilo.mia:8787 Change avatar globally. - + Ŝanĝi bildon ĉie. Change avatar. Will only apply to this room. - + Ŝanĝi bildon. Efektiviĝos nur en ĉi tiu ĉambro. Change display name globally. - + Ŝanĝi prezentan nomon ĉie. Change display name. Will only apply to this room. - + Ŝanĝi prezentan nomon. Efektiviĝos nur en ĉi tiu ĉambro. Room: %1 - + Ĉambro: %1 This is a room-specific profile. The user's name and avatar may be different from their global versions. - + Ĉi tio estas profilo speciala por ĉambro. La nomo kaj profilbildo de la uzanto povas esti malsamaj de siaj ĉieaj versioj. Open the global profile for this user. - + Malfermi la ĉiean profilon de ĉi tiu uzanto. @@ -2263,17 +2263,17 @@ Ekzemplo: https://servilo.mia:8787 Start a private chat. - + Komenci privatan babilon. Kick the user. - + Forpeli la uzanton. Ban the user. - + Forbari la uzanton. From 9da10b1848063e45540d34750fd89bfffa192fa7 Mon Sep 17 00:00:00 2001 From: Weblate Date: Fri, 1 Oct 2021 15:19:35 -0400 Subject: [PATCH 178/232] Translated using Weblate (Portuguese (Brazil)) Currently translated at 8.9% (51 of 569 strings) Translated using Weblate (Portuguese (Brazil)) Currently translated at 8.9% (51 of 569 strings) Co-authored-by: Terry Cukerberg Co-authored-by: zerowhy Translate-URL: https://weblate.nheko.im/projects/nheko/nheko-master/pt_BR/ Translation: Nheko/nheko --- resources/langs/nheko_pt_BR.ts | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/resources/langs/nheko_pt_BR.ts b/resources/langs/nheko_pt_BR.ts index 775667c0..ef3d1f2d 100644 --- a/resources/langs/nheko_pt_BR.ts +++ b/resources/langs/nheko_pt_BR.ts @@ -40,7 +40,7 @@ Awaiting Confirmation - + Aguardando Confirmação @@ -138,7 +138,7 @@ Migrating the cache to the current version failed. This can have different reasons. Please open an issue and try to use an older version in the mean time. Alternatively you can try deleting the cache manually. - + Falha ao migrar cache para versão atual. Isso pode ter diferentes razões. Por favor reporte o problema e tente usar uma versão antiga no meio tempo. Alternativamente, você pode tentar excluir o cache manualmente. @@ -148,7 +148,7 @@ Do you really want to join %1? - + Deseja realmente entrar em %1? @@ -164,7 +164,7 @@ Do you really want to invite %1 (%2)? - + Deseja realmente convidar %1 (%2)? @@ -179,7 +179,7 @@ Do you really want to kick %1 (%2)? - + Deseja realmente expulsar %1 (%2)? @@ -194,7 +194,7 @@ Do you really want to ban %1 (%2)? - + Deseja realmente banir %1 (%2)? @@ -214,7 +214,7 @@ Do you really want to unban %1 (%2)? - + Deseja realmente desbanir %1 (%2)? @@ -229,7 +229,7 @@ Do you really want to start a private chat with %1? - + Deseja realmente iniciar uma conversa privada com %1? @@ -239,12 +239,12 @@ Incompatible cache version - + Versão de cache incompatível The cache on your disk is newer than this version of Nheko supports. Please update or clear your cache. - + O cache em seu disco é mais recente do que essa versão do Nheko suporta. Por favor atualize ou limpe o cache. @@ -261,7 +261,7 @@ Failed to setup encryption keys. Server response: %1 %2. Please try again later. - + Falha ao configurar chaves de criptografia. Resposta do servidor: %1 %2. Por favor tente novamente mais tarde. @@ -272,17 +272,17 @@ Failed to join room: %1 - + Falha ao entrar na sala: %1 You joined the room - + Você entrou na sala Failed to remove invite: %1 - + Falha ao remover o convite: %1 @@ -292,12 +292,12 @@ Failed to leave room: %1 - + Falha ao sair da sala: %1 Failed to kick %1 from %2: %3 - + Falha ao expulsar %1 de %2: %3 From 59e665e931b2050db6f580733d8a3e95c002d19d Mon Sep 17 00:00:00 2001 From: Weblate Date: Sat, 2 Oct 2021 10:14:43 -0400 Subject: [PATCH 179/232] Translated using Weblate (Portuguese (Portugal)) Currently translated at 72.5% (413 of 569 strings) Co-authored-by: Tnpod Translate-URL: https://weblate.nheko.im/projects/nheko/nheko-master/pt_PT/ Translation: Nheko/nheko --- resources/langs/nheko_pt_PT.ts | 160 ++++++++++++++++----------------- 1 file changed, 80 insertions(+), 80 deletions(-) diff --git a/resources/langs/nheko_pt_PT.ts b/resources/langs/nheko_pt_PT.ts index 79831eff..72239549 100644 --- a/resources/langs/nheko_pt_PT.ts +++ b/resources/langs/nheko_pt_PT.ts @@ -305,7 +305,7 @@ Hide rooms with this tag or from this space by default. - Ocultar salas com esta etiqueta ou pertencentes a este espaço por defeito. + Ocultar, por defeito, salas com esta etiqueta ou pertencentes a este espaço. @@ -825,7 +825,7 @@ Se o Nheko não conseguir encontrar o seu servidor, irá apresentar um campo ond A name for this device, which will be shown to others, when verifying your devices. If none is provided a default is used. - Um nome para este dispositivo, que será exibido noutros quando os estiver a verificar. Caso nenhum seja fornecido, será usado um predefinido. + Um nome para este dispositivo, que será exibido noutros quando os estiver a verificar. Caso nenhum seja fornecido, será usado um pré-definido. @@ -1931,153 +1931,153 @@ Exemplo: https://servidor.meu:8787 %1 opened the room to the public. - + %1 abriu a sala ao público. %1 made this room require and invitation to join. - + %1 tornou esta sala acessível apenas por convite. %1 allowed to join this room by knocking. - + %1 tornou possível entrar na sala "batendo à porta". %1 allowed members of the following rooms to automatically join this room: %2 - + %1 autorizou os membros das seguintes salas a juntarem-se à sala automaticamente: %2 %1 made the room open to guests. - + %1 tornou a sala aberta a visitantes. %1 has closed the room to guest access. - + %1 fechou o acesso à sala a visitantes. %1 made the room history world readable. Events may be now read by non-joined people. - + %1 tornou o histórico da sala visível a qualquer pessoa. Eventos podem agora ser lidos por não-membros. %1 set the room history visible to members from this point on. - + %1 tornou o histórico da sala, a partir deste momento, visível a membros %1 set the room history visible to members since they were invited. - + %1 tornou o histórico visível a membros desde o seu convite. %1 set the room history visible to members since they joined the room. - + %1 tornou o histórico da sala visível ao membros desde a sua entrada. %1 has changed the room's permissions. - + %1 alterou as permissões da sala. %1 was invited. - + %1 foi convidado. %1 changed their avatar. - + %1 alterou o seu avatar. %1 changed some profile info. - + %1 alterou alguma informação de perfil. %1 joined. - + %1 entrou. %1 joined via authorisation from %2's server. - + %1 entrou com autorização do servidor de %2. %1 rejected their invite. - + %1 recusou o seu convite. Revoked the invite to %1. - + Convite de %1 cancelado. %1 left the room. - + %1 saiu da sala. Kicked %1. - + %1 foi expulso. Unbanned %1. - + %1 foi perdoado. %1 was banned. - + %1 foi banido. Reason: %1 - + Razão: %1 %1 redacted their knock. - + %1 eliminou a sua "batida à porta". You joined this room. - + Entrou na sala. %1 has changed their avatar and changed their display name to %2. - + %1 alterou o seu avatar e também o seu nome de exibição para %2. %1 has changed their display name to %2. - + %1 alterou o seu nome de exibição para %2. Rejected the knock from %1. - + Recusada a batida de %1. %1 left after having already left! This is a leave event after the user already left and shouldn't happen apart from state resets - + %1 saiu depois de já ter saído! %1 knocked. - + %1 bateu à porta. @@ -2085,7 +2085,7 @@ Exemplo: https://servidor.meu:8787 Edited - + Editada @@ -2093,32 +2093,32 @@ Exemplo: https://servidor.meu:8787 No room open - + Nenhuma sala aberta %1 member(s) - + %1 membro(s) join the conversation - + juntar-se à conversa accept invite - + aceitar convite decline invite - + recusar convite Back to room list - + Voltar à lista de salas @@ -2126,7 +2126,7 @@ Exemplo: https://servidor.meu:8787 No encrypted private chat found with this user. Create an encrypted private chat with this user and try again. - + Não foi encontrada nenhuma conversa privada e encriptada com este utilizador. Crie uma e tente novamente. @@ -2134,57 +2134,57 @@ Exemplo: https://servidor.meu:8787 Back to room list - + Voltar à lista de salas No room selected - + Nenhuma sala selecionada This room is not encrypted! - + Esta sala não é encriptada! This room contains only verified devices. - + Esta sala contém apenas dispositivos verificados. This rooms contain verified devices and devices which have never changed their master key. - + Esta sala contém dispositivos verificados e outros que nunca alteraram a sua chave-mestra. This room contains unverified devices! - + Esta sala contém dispositivos não verificados! Room options - + Opções da sala Invite users - + Convidar utilizadores Members - + Membros Leave room - + Sair da sala Settings - + Definições @@ -2192,12 +2192,12 @@ Exemplo: https://servidor.meu:8787 Show - + Mostrar Quit - + Sair @@ -2205,93 +2205,93 @@ Exemplo: https://servidor.meu:8787 Global User Profile - + Perfil de utilizador global Room User Profile - + Perfil de utilizador na sala Change avatar globally. - + Alterar avatar globalmente. Change avatar. Will only apply to this room. - + Alterar avatar. Irá apenas afetar esta sala. Change display name globally. - + Alterar nome de exibição globalmente. Change display name. Will only apply to this room. - + Alterar nome de exibição. Irá apenas afetar esta sala. Room: %1 - + Sala: %1 This is a room-specific profile. The user's name and avatar may be different from their global versions. - + Este é um perfil específico desta sala. O nome e avatar do utilizador poderão ser diferentes dos seus homólogos globais. Open the global profile for this user. - + Abrir o perfil global deste utilizador. Verify - + Verificar Start a private chat. - + Iniciar uma conversa privada. Kick the user. - + Expulsar o utilizador. Ban the user. - + Banir o utilizador. Unverify - + Anular verificação Select an avatar - + Selecionar um avatar All Files (*) - + Todos os ficheiros (*) The selected file is not an image - + O ficheiro selecionado não é uma imagem Error while reading file: %1 - + Erro ao ler ficheiro: %1 @@ -2300,7 +2300,7 @@ Exemplo: https://servidor.meu:8787 Default - + Padrão @@ -2308,42 +2308,42 @@ Exemplo: https://servidor.meu:8787 Minimize to tray - + Minimizar para bandeja Start in tray - + Iniciar na bandeja Group's sidebar - + Barra lateral do grupo Circular Avatars - + Avatares circulares profile: %1 - + perfil: %1 Default - + Padrão CALLS - + CHAMADAS Cross Signing Keys - + Chaves de assinatura cruzada From 01357c8bc46832b2ebef850f5d4c57572e58fdaa Mon Sep 17 00:00:00 2001 From: Weblate Date: Sat, 2 Oct 2021 22:02:12 -0400 Subject: [PATCH 180/232] Translated using Weblate (Portuguese (Portugal)) Currently translated at 72.9% (415 of 569 strings) Co-authored-by: Tnpod Translate-URL: https://weblate.nheko.im/projects/nheko/nheko-master/pt_PT/ Translation: Nheko/nheko --- resources/langs/nheko_pt_PT.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/resources/langs/nheko_pt_PT.ts b/resources/langs/nheko_pt_PT.ts index 72239549..c280d17f 100644 --- a/resources/langs/nheko_pt_PT.ts +++ b/resources/langs/nheko_pt_PT.ts @@ -2353,12 +2353,12 @@ Exemplo: https://servidor.meu:8787 DOWNLOAD - + DESCARREGAR Keep the application running in the background after closing the client window. - + Manter a aplicação a correr em segundo plano depois de fechar a janela. From c43c2bd4b9481f761e9f1b16453f0e7f5b1beabe Mon Sep 17 00:00:00 2001 From: Weblate Date: Sun, 3 Oct 2021 09:49:01 -0400 Subject: [PATCH 181/232] Translated using Weblate (Indonesian) Currently translated at 100.0% (569 of 569 strings) Co-authored-by: Linerly Translate-URL: https://weblate.nheko.im/projects/nheko/nheko-master/id/ Translation: Nheko/nheko --- resources/langs/nheko_id.ts | 1159 ++++++++++++++++++----------------- 1 file changed, 588 insertions(+), 571 deletions(-) diff --git a/resources/langs/nheko_id.ts b/resources/langs/nheko_id.ts index 5efb37fb..37387cd7 100644 --- a/resources/langs/nheko_id.ts +++ b/resources/langs/nheko_id.ts @@ -6,33 +6,33 @@ Calling... - + Memanggil... Connecting... - + Menghubungkan... You are screen sharing - + Anda sedang membagikan layar Hide/Show Picture-in-Picture - + Sembunyikan/Tampilkan Picture-in-Picture Unmute Mic - + Bunyikan Mikrofon Mute Mic - + Bisukan Mikrofon @@ -40,17 +40,17 @@ Awaiting Confirmation - + Menunggu Konfirmasi Waiting for other side to complete verification. - + Menunggu untuk pengguna yang lain untuk menyelesaikan verifikasi. Cancel - + Batal @@ -58,17 +58,17 @@ Video Call - + Panggilan Video Voice Call - + Panggilan Suara No microphone found. - + Tidak ada mikrofon yang ditemukan. @@ -76,42 +76,42 @@ Video Call - + Panggilan Video Voice Call - + Panggilan Suara Devices - + Perangkat Accept - + Terima Unknown microphone: %1 - + Mikrofon tidak dikenal: %1 Unknown camera: %1 - + Kamera tidak dikenal: %1 Decline - + Tolak No microphone found. - + Tidak ada mikrofon yang ditemukan. @@ -119,7 +119,7 @@ Entire screen - + Semua layar @@ -127,177 +127,177 @@ Failed to invite user: %1 - + Gagal mengundang pengguna: %1 Invited user: %1 - + Pengguna yang diundang: %1 Migrating the cache to the current version failed. This can have different reasons. Please open an issue and try to use an older version in the mean time. Alternatively you can try deleting the cache manually. - + Migrasi cache ke versi saat ini gagal. Ini dapat memiliki alasan yang berbeda. Silakan buka masalah dan coba gunakan versi yang lebih lama untuk sementara. Alternatifnya Anda dapat mencoba menghapus cache secara manual. Confirm join - + Konfirmasi untuk bergabung Do you really want to join %1? - + Apakah Anda ingin bergabung %1? Room %1 created. - + Ruangan %1 telah dibuat. Confirm invite - + Konfirmasi undangan Do you really want to invite %1 (%2)? - + Apakah Anda ingin menundang %1 (%2)? Failed to invite %1 to %2: %3 - + Gagal mengundang %1 ke %2: %3 Confirm kick - + Konfirmasi pengeluaran Do you really want to kick %1 (%2)? - + Apakah Anda ingin mengeluarkan %1 (%2)? Kicked user: %1 - + Pengguna yang dikeluarkan: %1 Confirm ban - + Konfirmasi cekalan Do you really want to ban %1 (%2)? - + Apakah Anda ingin mencekal %1 (%2)? Failed to ban %1 in %2: %3 - + Gagal mencekal %1 di %2: %3 Banned user: %1 - + Pengguna yang dicekal: %1 Confirm unban - + Konfirmasi menghilangkan cekalan Do you really want to unban %1 (%2)? - + Apakah Anda ingin menghilangkan cekalan %1 (%2)? Failed to unban %1 in %2: %3 - + Gagal menghilangkan pencekalan %1 di %2: %3 Unbanned user: %1 - + Menghilangkan cekalan pengguna: %1 Do you really want to start a private chat with %1? - + Apakah Anda ingin memulai chat privat dengan %1? Cache migration failed! - + Migrasi cache gagal! Incompatible cache version - + Versi cache tidak kompatibel The cache on your disk is newer than this version of Nheko supports. Please update or clear your cache. - + Cache pada disk Anda lebih baru daripada versi yang didukung Nheko ini. Harap perbarui atau kosongkan cache Anda. Failed to restore OLM account. Please login again. - + Gagal memulihkan akun OLM. Mohon masuk lagi. Failed to restore save data. Please login again. - + Gagal memulihkan data simpanan. Mohon masuk lagi. Failed to setup encryption keys. Server response: %1 %2. Please try again later. - + Gagal menyiapkan kunci enkripsi. Respons server: %1 %2. Silakan coba lagi nanti. Please try to login again: %1 - + Mohon mencoba masuk lagi: %1 Failed to join room: %1 - + Gagal bergabung ruangan: %1 You joined the room - + Anda bergabung ruangan ini Failed to remove invite: %1 - + Gagal menghapus undangan: %1 Room creation failed: %1 - + Pembuatan ruangan gagal: %1 Failed to leave room: %1 - + Gagal meninggalkan ruangan: %1 Failed to kick %1 from %2: %3 - + Gagal mengeluarkan %1 dari %2: %3 @@ -305,7 +305,7 @@ Hide rooms with this tag or from this space by default. - + Sembunyikan ruangan dengan tanda ini atau dari space ini secara default. @@ -313,42 +313,42 @@ All rooms - + Semua ruangan Shows all rooms without filtering. - + Menampilkan semua ruangan tanpa penyaringan. Favourites - + Favorit Rooms you have favourited. - + Ruangan yang Anda favoritkan. Low Priority - + Prioritas Rendah Rooms with low priority. - + Ruangan dengan prioritas rendah. Server Notices - + Pemberitahuan Server Messages from your server or administrator. - + Pesan dari server Anda atau administrator. @@ -356,27 +356,27 @@ Decrypt secrets - + Dekripsi rahasia Enter your recovery key or passphrase to decrypt your secrets: - + Masukkan kunci pemulihan Anda atau frasa sandi untuk mendekripsikan rahasia Anda: Enter your recovery key or passphrase called %1 to decrypt your secrets: - + Masukkan kunci pemulihan Anda atau frasa sandi yang bernama %1 untuk mendekripsikan rahasia Anda: Decryption failed - + Gagal mendekripsi Failed to decrypt secrets with the provided recovery key or passphrase - + Gagal mendekripsi rahasia dengan kunci pemulihan atau frasa sandi yang diberikan @@ -384,22 +384,22 @@ Verification Code - + Kode Verifikasi Please verify the following digits. You should see the same numbers on both sides. If they differ, please press 'They do not match!' to abort verification! - + Harap verifikasi digit berikut. Anda seharusnya melihat angka yang sama di kedua sisi. Jika mereka berbeda, mohon tekan 'Mereka tidak cocok!' untuk membatalkan verifikasi! They do not match! - + Mereka tidak cocok! They match! - + Mereka cocok! @@ -407,22 +407,22 @@ Apply - + Terapkan Cancel - + Batalkan Name - + Nama Topic - + Topik @@ -430,47 +430,47 @@ Search - + Cari People - + Orang Nature - + Alam Food - + Makanan Activity - + Aktifitas Travel - + Tempat Objects - + Objek Symbols - + Simbol Flags - + Bendera @@ -478,22 +478,22 @@ Verification Code - + Kode Verifikasi Please verify the following emoji. You should see the same emoji on both sides. If they differ, please press 'They do not match!' to abort verification! - + Mohon verifikasi emoji berikut. Anda seharusnya melihat emoji yang sama di kedua sisi. Jika mereka berbeda, mohon tekan 'Mereka tidak cocok!' untuk membatalkan verifikasi! They do not match! - + Mereka tidak cocok! They match! - + Mereka cocok! @@ -501,42 +501,42 @@ There is no key to unlock this message. We requested the key automatically, but you can try requesting it again if you are impatient. - + Tidak ada kunci untuk mengakses pesan ini. Kami telah meminta untuk kunci secara otomatis, tetapi Anda dapat meminta lagi jika Anda tidak sabar. This message couldn't be decrypted, because we only have a key for newer messages. You can try requesting access to this message. - + Pesan ini tidak dapat didekripsikan, karena kami hanya memiliki kunci untuk pesan baru. Anda dapat meminta akses ke pesan ini. There was an internal error reading the decryption key from the database. - + Sebuah kesalahan internal terjadi saat membaca kunci dekripsi dari basis data. There was an error decrypting this message. - + Sebuah error terjadi saat mendekripsikan pesan ini. The message couldn't be parsed. - + Pesan ini tidak dapat diuraikan. The encryption key was reused! Someone is possibly trying to insert false messages into this chat! - + Kunci enkripsi telah digunakan lagi! Seseorang mungkin mencoba memasukkan pesan palsu ke chat ini! Unknown decryption error - + Error dekripsi yang tidak dikenal Request key - + Minta kunci @@ -544,22 +544,22 @@ This message is not encrypted! - + Pesan ini tidak terenkripsi! Encrypted by a verified device - + Terenkripsi oleh perangkat yang terverifikasi Encrypted by an unverified device, but you have trusted that user so far. - + Terenkripsi oleh perangkat yang tidak diverifikasi, tetapi Anda mempercayai pengguna itu sejauh ini. Encrypted by an unverified device or the key is from an untrusted source like the key backup. - + Terenkripsi oleh perangkat yang tidak diverifikasi atau kuncinya dari sumber yang tidak dipercayai seperti cadangan kunci. @@ -567,33 +567,33 @@ Verification failed - + Verifikasi gagal Other client does not support our verification protocol. - + Client yang lain tidak mendukung protokol verifikasi kami. Key mismatch detected! - + Ketidakcocokan kunci terdeteksi! Device verification timed out. - + Waktu verifikasi perangkat habis. Other party canceled the verification. - + Pengguna yang lain membatalkan proses verifikasi ini. Close - + Tutup @@ -601,7 +601,7 @@ Forward Message - + Teruskan Pesan @@ -609,74 +609,74 @@ Editing image pack - + Mengedit paket gambar Add images - + Tambahkan gambar Stickers (*.png *.webp *.gif) - + Stiker (*.png *.webp *.gif) State key - + Kunci keadaan Packname - + Nama Paket Attribution - + Atribusi Use as Emoji - + Gunakan sebagai Emoji Use as Sticker - + Gunakan sebagai Stiker Shortcode - + Kode Pendek Body - + Body Remove from pack - + Hapus dari paket Remove - + Hapus Cancel - + Batalkan Save - + Simpan @@ -684,52 +684,52 @@ Image pack settings - + Pengaturan paket gambar Create account pack - + Buat paket untuk akun New room pack - + Paket ruangan baru Private pack - + Paket privat Pack from this room - + Paket dari ruangan ini Globally enabled pack - + Paket yang diaktifkan secara global Enable globally - + Aktifkan secara global Enables this pack to be used in all rooms - + Mengaktifkan paket ini untuk digunakan di semua ruangan Edit - + Edit Close - + Tutup @@ -737,17 +737,17 @@ Select a file - + Pilih sebuah file All Files (*) - + Semua File (*) Failed to upload media. Please try again. - + Gagal untuk mengunggah media. Silakan coba lagi. @@ -755,33 +755,33 @@ Invite users to %1 - + Undang pengguna ke %1 User ID to invite - + ID Pengguna untuk diundang @joe:matrix.org Example user id. The name 'joe' can be localized however you want. - + @pengguna:matrix.org Add - + Tambahkan Invite - + Undang Cancel - + Batalkan @@ -789,12 +789,12 @@ Matrix ID - + ID Matrix e.g @joe:matrix.org - + mis. @pengguna:matrix.org @@ -802,48 +802,52 @@ You can also put your homeserver address there, if your server doesn't support .well-known lookup. Example: @user:server.my If Nheko fails to discover your homeserver, it will show you a field to enter the server manually. - + Nama login Anda. Sebuah MXID harus mulai dengan @ diikuti dengan ID pengguna. Setelah ID penggunanya Anda harus menambahkan nama server setelah :. +Anda juga dapat memasukkan alamat homeserver Anda, jika server Anda tidak mendukung pencarian .well-known. +Misalnya: @pengguna:server.my +Jika Nheko gagal menemukan homeserver Anda, Nheko akan menampilkan kolom untuk memasukkan servernya secara manual. Password - + Kata Sandi Your password. - + Kata sandi Anda. Device name - + Nama perangkat A name for this device, which will be shown to others, when verifying your devices. If none is provided a default is used. - + Sebuah nama perangkat untuk perangkat ini, yang akan ditampilkan untuk yang lain, ketika memverifikasi perangkat Anda. Jika tidak dimasukkan nama perangkat yang default akan digunakan. Homeserver address - + Alamat homeserver server.my:8787 - + server.my:8787 The address that can be used to contact you homeservers client API. Example: https://server.my:8787 - + Alamat yang dapat digunakan untuk mengkontak API client homeserver Anda. +Misalnya: https://server.my:8787 LOGIN - + MASUK @@ -851,47 +855,47 @@ Example: https://server.my:8787 You have entered an invalid Matrix ID e.g @joe:matrix.org - + Anda telah memasukkan ID Matrix yang tidak valid mis. @pengguna:matrix.org Autodiscovery failed. Received malformed response. - + Penemuan otomatis gagal. Menerima respons cacat. Autodiscovery failed. Unknown error when requesting .well-known. - + Penemuan otomatis gagal. Kesalahan yang tidak diketahu saat meminta .well-known. The required endpoints were not found. Possibly not a Matrix server. - + Titik akhir yang dibutuhkan tidak dapat ditemukan. Kemungkinan bukan server Matrix. Received malformed response. Make sure the homeserver domain is valid. - + Menerima respons cacat. Pastikan domain homeservernya valid. An unknown error occured. Make sure the homeserver domain is valid. - + Terjadi kesalahan yang tidak diketahui. Pastikan domain homeservernya valid. SSO LOGIN - + LOGIN SSO Empty password - + Kata sandi kosong SSO login failed - + Login SSO gagal @@ -899,78 +903,78 @@ Example: https://server.my:8787 Encryption enabled - + Enkripsi diaktifkan room name changed to: %1 - + nama ruangan diganti ke: %1 removed room name - + nama ruangan dihapus topic changed to: %1 - + topik diganti ke: %1 removed topic - + topik dihapus %1 changed the room avatar - + %1 mengubah avatar ruangan %1 created and configured room: %2 - + %1 membuat dan mengkonfigurasikan ruangan: %2 %1 placed a voice call. - + %1 melakukan panggilan suara. %1 placed a video call. - + %1 melakukan panggilan suara. %1 placed a call. - + %1 melakukan panggilan. Negotiating call... - + Negosiasi panggilan… Allow them in - + Izinkan mereka untuk masuk %1 answered the call. - + %1 menjawab panggilan. removed - + dihapus %1 ended the call. - + %1 mengakhir panggilan. @@ -978,42 +982,42 @@ Example: https://server.my:8787 Hang up - + Tutup panggilan Place a call - + Lakukan panggilan Send a file - + Kirim sebuah file Write a message... - + Ketik pesan… Stickers - + Stiker Emoji - + Emoji Send - + Kirim You don't have permission to send messages in this room - + Anda tidak memiliki izin untuk mengirim pesan di ruangan ini @@ -1021,99 +1025,99 @@ Example: https://server.my:8787 Edit - + Edit React - + Reaksi Reply - + Balas Options - + Opsi &Copy - + &Salin Copy &link location - + Salin lokasi &tautan Re&act - + Re&aksi Repl&y - + Bala&s &Edit - + &Edit Read receip&ts - + Lapor&an terbaca &Forward - + &Teruskan &Mark as read - + &Tandai sebagai dibaca View raw message - + Tampilkan pesan mentah View decrypted raw message - + Tampilkan pesan terdekripsi mentah Remo&ve message - + Hap&us pesan &Save as - + &Simpan sebagai &Open in external program - + &Buka di program eksternal Copy link to eve&nt - + Salin tautan ke peristi&wa &Go to quoted message - + &Pergi ke pesan yang dikutip @@ -1121,57 +1125,57 @@ Example: https://server.my:8787 Send Verification Request - + Kirim Permintaan Verifikasi Received Verification Request - + Menerima Permintaan Verifikasi To allow other users to see, which of your devices actually belong to you, you can verify them. This also allows key backup to work automatically. Verify %1 now? - + Untuk mengizinkan pengguna yang lain untuk melihat, perangkat apa saja yang sebenarnya milik Anda, verifiksi mereka. Ini juga dapat membuat kunci cadangan bekerja secara otomatis. Verifikasi %1 sekarang? To ensure that no malicious user can eavesdrop on your encrypted communications you can verify the other party. - + Supaya tidak ada pengguna yang jahat yang bisa melihat komunikasi yang terenkripsi Anda dapat memverifikasi pengguna yang lain. %1 has requested to verify their device %2. - + %1 telah meminta untuk memverifikasi perangkat %2 mereka. %1 using the device %2 has requested to be verified. - + %1 yang menggunakan perangkat %2 meminta untuk diverifikasi. Your device (%1) has requested to be verified. - + Perangkat Anda (%1) meminta untuk diverifikasi. Cancel - + Batalkan Deny - + Tolak Start verification - + Mulai verifikasi Accept - + Terima @@ -1179,7 +1183,7 @@ Example: https://server.my:8787 You will be pinging the whole room - + Anda akan memberitahukan seluruh ruangan @@ -1189,41 +1193,41 @@ Example: https://server.my:8787 %1 sent an encrypted message - + %1 mengirim pesan terenkripsi * %1 %2 Format an emote message in a notification, %1 is the sender, %2 the message - + * %1 %2 %1 replied: %2 Format a reply in a notification. %1 is the sender, %2 the message - + %1 membalas: %2 %1: %2 Format a normal message in a notification. %1 is the sender, %2 the message - + %1: %2 %1 replied with an encrypted message - + %1 membalas dengan pesan terenkripsi %1 replied to a message - + %1 membalas pesan %1 sent a message - + %1 mengirim gambar @@ -1231,32 +1235,32 @@ Example: https://server.my:8787 Place a call to %1? - + Lakukan panggilan ke %1? No microphone found. - + Tidak ada mikrofon yang ditemukan. Voice - + Suara Video - + Video Screen - + Layar Cancel - + Batalkan @@ -1264,7 +1268,7 @@ Example: https://server.my:8787 unimplemented event: - + peristiwa yang belum diimplementasikan: @@ -1272,17 +1276,17 @@ Example: https://server.my:8787 Create a unique profile, which allows you to log into several accounts at the same time and start multiple instances of nheko. - + Membuat profil yang unik, yang mengizinkan Anda untuk masuk ke beberapa akun pada waktu bersamaan dan mulai beberapa instansi nheko. profile - + profil profile name - + nama profil @@ -1290,7 +1294,7 @@ Example: https://server.my:8787 Read receipts - + Laporan dibaca @@ -1298,7 +1302,7 @@ Example: https://server.my:8787 Yesterday, %1 - + Kemarin, %1 @@ -1306,98 +1310,98 @@ Example: https://server.my:8787 Username - + Nama pengguna The username must not be empty, and must contain only the characters a-z, 0-9, ., _, =, -, and /. - + Nama pengguna tidak boleh kosong, dan hanya mengandung karakter a-z, 0-9, ., _, =, -, dan /. Password - + Kata sandi Please choose a secure password. The exact requirements for password strength may depend on your server. - + Mohon memilih kata sandi yang aman. Persyaratan untuk kekuatan sandi mungkin bergantung pada server Anda. Password confirmation - + Konfirmasi kata sandi Homeserver - + Homeserver A server that allows registration. Since matrix is decentralized, you need to first find a server you can register on or host your own. - + Sebuah server yang mengizinkan pendaftaran. Karena Matrix itu terdecentralisasi, Anda pertama harus mencari server yang Anda daftar atau host server Anda sendiri. REGISTER - + DAFTAR No supported registration flows! - + Tidak ada aliran pendaftaran yang didukung! Registration token - + Token pendaftaran Please enter a valid registration token. - + Mohon masukkan token pendaftaran yang valid. Autodiscovery failed. Received malformed response. - + Penemuan otomatis gagal. Menerima respons cacat. Autodiscovery failed. Unknown error when requesting .well-known. - + Penemuan otomatis gagal. Terjadi kesalahan yang tidak diketahui saat meminta .well-known. The required endpoints were not found. Possibly not a Matrix server. - + Titik akhir yang dibutuhkan tidak dapat ditemukan. Kemungkinan bukan server Matrix. Received malformed response. Make sure the homeserver domain is valid. - + Menerima respons cacat. Pastikan domain homeservernya valid. An unknown error occured. Make sure the homeserver domain is valid. - + Terjadi kesalahan yang tidak diketahui. Pastikan domain homeservernya valid. Password is not long enough (min 8 chars) - + Kata sandi kurang panjang (min. 8 karakter) Passwords don't match - + Kata sandi tidak cocok Invalid server name - + Nama server tidak valid @@ -1405,12 +1409,12 @@ Example: https://server.my:8787 Close - + Tutup Cancel edit - + Batalkan pengeditan @@ -1418,12 +1422,12 @@ Example: https://server.my:8787 Explore Public Rooms - + Temukan Ruangan Publik Search for public rooms - + Cari ruangan publik @@ -1431,7 +1435,7 @@ Example: https://server.my:8787 no version stored - + tidak ada versi yang disimpan @@ -1439,102 +1443,102 @@ Example: https://server.my:8787 New tag - + Tanda baru Enter the tag you want to use: - + Masukkan tanda yang Anda ingin gunakan: Leave Room - + Tinggalkan ruangan Are you sure you want to leave this room? - + Apakah Anda yakin untuk meninggalkan ruangan ini? Leave room - + Tinggalkan ruangan Tag room as: - + Tandai ruangan sebagai: Favourite - + Favorit Low priority - + Prioritas rendah Server notice - + Pemberitahuan server Create new tag... - + Membuat tanda baru… Status Message - + Pesan Status Enter your status message: - + Masukkan pesan status Anda: Profile settings - + Pengaturan profil Set status message - + Tetapkan pesan status Logout - + Keluar Start a new chat - + Mulai chat baru Join a room - + Bergabung sebuah ruangan Create a new room - + Buat ruangan baru Room directory - + Direktori ruangan User settings - + Pengaturan pengguna @@ -1542,40 +1546,40 @@ Example: https://server.my:8787 Members of %1 - + Anggota dari %1 %n people in %1 Summary above list of members - - + + %n orang di %1 Invite more people - + Undang banyak orang This room is not encrypted! - + Ruangan ini tidak terenkripsi! This user is verified. - + Pengguna ini sudah diverifikasi. This user isn't verified, but is still using the same master key from the first time you met. - + Pengguna ini belum diverifikasi, tetapi masih menggunakan kunci utama dari pertama kali Anda bertemu. This user has unverified devices! - + Pengguna ini memiliki perangkat yang belum diverifikasi! @@ -1583,144 +1587,144 @@ Example: https://server.my:8787 Room Settings - + Pengaturan Ruangan %1 member(s) - + %1 anggota SETTINGS - + PENGATURAN Notifications - + Notifikasi Muted - + Bisukan Mentions only - + Sebutan saja All messages - + Semua pesan Room access - + Akses ruangan Anyone and guests - + Siapa saja dan tamu Anyone - + Siapa saja Invited users - + Pengguna yang diundang By knocking - + Dengan mengetuk Restricted by membership in other rooms - + Dibatasi oleh keanggotaan di ruangan lain Encryption - + Enkripsi End-to-End Encryption - + Enkripsi Ujung-ke-Ujung Encryption is currently experimental and things might break unexpectedly. <br> Please take note that it can't be disabled afterwards. - + Enkripsi saat ini masih eksperimental dan apapun dapat rusak secara tiba-tiba.<br>Mohon dicatat bahwa enkripsi tidak dapat dinonaktifkan setelah ini. Sticker & Emote Settings - + Pengaturan Stiker & Emote Change - + Ubah Change what packs are enabled, remove packs or create new ones - + Ubah paket apa yang diaktifkan, hapus paket atau buat yang baru INFO - + INFO Internal ID - + ID Internal Room Version - + Versi Ruangan Failed to enable encryption: %1 - + Gagal mengaktifkan enkripsi: %1 Select an avatar - + Pilih sebuah avatar All Files (*) - + Semua File (*) The selected file is not an image - + File yang dipilih bukan sebuah gambar Error while reading file: %1 - + Terjadi kesalahan saat membaca file: %1 Failed to upload image: %s - + Gagal mengunggah gambar: %s @@ -1728,17 +1732,17 @@ Example: https://server.my:8787 Pending invite. - + Undangan tertunda. Previewing this room - + Menampilkan ruangan ini No preview available - + Tidak ada tampilan yang tersedia @@ -1746,53 +1750,53 @@ Example: https://server.my:8787 Share desktop with %1? - + Bagikan desktop dengan %1? Window: - + Jendela: Frame rate: - + Frame rate: Include your camera picture-in-picture - + Tambahkan kamera Anda dalam picture-in-picture Request remote camera - + Minta kamera jarak jauh View your callee's camera like a regular video call - + Tampilkan kamera pengguna yang menerima panggilan seperti panggilan video biasa Hide mouse cursor - + Sembunyikan kursor mouse Share - + Bagikan Preview - + Tampilkan Cancel - + Batalkan @@ -1800,12 +1804,12 @@ Example: https://server.my:8787 Failed to connect to secret storage - + Gagal menghubungkan ke penyimpanan rahasia Nheko could not connect to the secure storage to save encryption secrets to. This can have multiple reasons. Check if your D-Bus service is running and you have configured a service like KWallet, Gnome Secrets or the equivalent for your platform. If you are having trouble, feel free to open an issue here: https://github.com/Nheko-Reborn/nheko/issues - + Nheko tidak dapat terhubung ke penyimpanan aman untuk menyimpan rahasia enkripsi. Ini dapat memiliki beberapa alasan. Periksa apakah layanan D-Bus Anda berjalan dan Anda telah mengonfigurasi layanan seperti KWallet, Gnome Secrets atau yang setara untuk platform Anda. Jika Anda mengalami masalah, silakan membuka masalah di sini: https://github.com/Nheko-Reborn/nheko/issues @@ -1814,22 +1818,22 @@ Example: https://server.my:8787 Failed to update image pack: %1 - + Gagal memperbarui paket gambar: %1 Failed to delete old image pack: %1 - + Gagal menghapus paket gambar yang lama: %1 Failed to open image: %1 - + Gagal membuka gambar: %1 Failed to upload image: %1 - + Gagal mengunggah gambar: %1 @@ -1837,22 +1841,22 @@ Example: https://server.my:8787 Failed - + Gagal Sent - + Terkirim Received - + Diterima Read - + Dibaca @@ -1860,7 +1864,7 @@ Example: https://server.my:8787 Search - + Cari @@ -1868,17 +1872,17 @@ Example: https://server.my:8787 Successful Verification - + Verifikasi Berhasil Verification successful! Both sides verified their devices! - + Verifikasi berhasil! Kedua sisi telah memverifikasi perangkat mereka! Close - + Tutup @@ -1886,192 +1890,192 @@ Example: https://server.my:8787 Message redaction failed: %1 - + Reaksi pesan gagal: %1 Failed to encrypt event, sending aborted! - + Gagal mendekripsikan peristiwa, pengiriman dihentikan! Save image - + Simpan gambar Save video - + Simpan video Save audio - + Simpan audio Save file - + Simpan file %1 and %2 are typing. Multiple users are typing. First argument is a comma separated list of potentially multiple users. Second argument is the last user of that list. (If only one user is typing, %1 is empty. You should still use it in your string though to silence Qt warnings.) - - + + %1 dan %2 sedang mengetik. %1 opened the room to the public. - + %1 membuka ruangan ke publik. %1 made this room require and invitation to join. - + %1 membuat ruangan ini membutuhkan undangan untuk bergabung. %1 allowed to join this room by knocking. - + %1 mengizinkan siapa saja untuk bergabung ke ruangan ini dengan mengetuk. %1 allowed members of the following rooms to automatically join this room: %2 - + %1 mengizinkan anggota dari ruangan berikut untuk bergabung ke ruangan ini secara otomatis: %2 %1 made the room open to guests. - + %1 membuat ruangan ini terbuka ke tamu. %1 has closed the room to guest access. - + %1 telah menutup ruangan ke akses tamu. %1 made the room history world readable. Events may be now read by non-joined people. - + %1 membuat sejarah ruangan dibaca oleh siapa saja. Peristiwa mungkin bisa dibaca oleh orang yang tidak bergabung. %1 set the room history visible to members from this point on. - + %1 membuat sejarah ruangan bisa dilihat oleh anggota dari titik sekarang. %1 set the room history visible to members since they were invited. - + %1 membuat sejarah ruangan bisa dilihat oleh anggota yang telah diundang. %1 set the room history visible to members since they joined the room. - + %1 membuat sejarah ruangan bisa dilihat oleh anggota yang telah bergabung ke ruangan ini. %1 has changed the room's permissions. - + %1 telah mengubah izin ruangan. %1 was invited. - + %1 diundang. %1 changed their avatar. - + %1 mengubah avatarnya. %1 changed some profile info. - + %1 mengubah info profil. %1 joined. - + %1 bergabung. %1 joined via authorisation from %2's server. - + %1 bergabung via otorisasi dari servernya %2. %1 rejected their invite. - + %1 menolak undangannya. Revoked the invite to %1. - + Menghapus undangan ke %1. %1 left the room. - + %1 meninggalkan ruangan. Kicked %1. - + %1 dikeluarkan. Unbanned %1. - + Cekalan %1 dihilangkan. %1 was banned. - + %1 telah dicekal. Reason: %1 - + Alasan: %1 %1 redacted their knock. - + %1 menolak ketukannya. You joined this room. - + Anda bergabung ruangan ini. %1 has changed their avatar and changed their display name to %2. - + %1 mengubah avatarnya dan ubah nama tampilannya ke %2. %1 has changed their display name to %2. - + %1 mengubah nama tampilannya ke %2. Rejected the knock from %1. - + Menolak ketukan dari %1. %1 left after having already left! This is a leave event after the user already left and shouldn't happen apart from state resets - + %1 keluar setelah sudah keluar! %1 knocked. - + %1 mengetuk. @@ -2079,7 +2083,7 @@ Example: https://server.my:8787 Edited - + Diedit @@ -2087,32 +2091,32 @@ Example: https://server.my:8787 No room open - + Tidak ada ruangan yang dibuka %1 member(s) - + %1 anggota join the conversation - + bergabung ke percakapan accept invite - + terima undangan decline invite - + tolak undangan Back to room list - + Kembali ke daftar ruangan @@ -2120,7 +2124,7 @@ Example: https://server.my:8787 No encrypted private chat found with this user. Create an encrypted private chat with this user and try again. - + Tidak ada chat privat terenkripsi ditemukan dengan pengguna ini. Buat chat privat terenkripsi dengan pengguna ini dan coba lagi. @@ -2128,57 +2132,57 @@ Example: https://server.my:8787 Back to room list - + Kembali ke daftar ruangan No room selected - + Tidak ada ruangan yang dipilih This room is not encrypted! - + Ruangan ini tidak dienkripsi! This room contains only verified devices. - + Ruangan ini hanya berisi perangkat yang telah diverifikasi. This rooms contain verified devices and devices which have never changed their master key. - + Ruangan ini berisi verifikasi yang telah diverifikasi dan perangkat yang tidak pernah mengubah kunci utamanya. This room contains unverified devices! - + Ruangan ini berisi perangkat yang belum diverifikasi! Room options - + Opsi ruangan Invite users - + Undang pengguna Members - + Anggota Leave room - + Tinggalkan ruangan Settings - + Pengaturan @@ -2186,12 +2190,12 @@ Example: https://server.my:8787 Show - + Tampilkan Quit - + Tutup @@ -2199,93 +2203,93 @@ Example: https://server.my:8787 Global User Profile - + Profil Pengguna Global Room User Profile - + Profil Pengguna di Ruangan Change avatar globally. - + Ubah avatar secara global. Change avatar. Will only apply to this room. - + Ubah avatar. Hanya diterapkan di ruangan ini. Change display name globally. - + Ubah nama tampilan secara global. Change display name. Will only apply to this room. - + Ubah nama tampilan. Hanya diterapkan di ruangan ini. Room: %1 - + Ruangan: %1 This is a room-specific profile. The user's name and avatar may be different from their global versions. - + Ini adalah profile specifik ruangan. Nama pengguna dan avatar mungkin berbeda dari versi globalnya. Open the global profile for this user. - + Buka profil global untuk pengguna ini. Verify - + Verifikasi Start a private chat. - + Mulai chat privat. Kick the user. - + Keluarkan pengguna ini. Ban the user. - + Cekal pengguna ini. Unverify - + Hilangkan verifikasi Select an avatar - + Pilih sebuah avatar All Files (*) - + Semua File (*) The selected file is not an image - + File yang dipilih bukan sebuah gambar Error while reading file: %1 - + Terjadi kesalahan saat membaca file: %1 @@ -2294,7 +2298,7 @@ Example: https://server.my:8787 Default - + Default @@ -2302,448 +2306,459 @@ Example: https://server.my:8787 Minimize to tray - + Perkecil ke baki Start in tray - + Mulai di baki Group's sidebar - + Bilah samping grup Circular Avatars - + Avatar Bundar profile: %1 - + profil: %1 Default - + Default CALLS - + PANGGILAN Cross Signing Keys - + Kunci Tanda Tangan Silang REQUEST - + MINTA DOWNLOAD - + UNDUH Keep the application running in the background after closing the client window. - + Membiarkan aplikasi berjalan di latar belakang setelah menutup jendela client. Start the application in the background without showing the client window. - + Mulai aplikasinya di latar belakang tanpa menunjukkan jendela clientnya. Change the appearance of user avatars in chats. OFF - square, ON - Circle. - + Ubah penampilan avatar pengguna di chat. +MATI - persegi, NYALA - Lingkaran. Show a column containing groups and tags next to the room list. - + Menampilkan kolom yang berisi grup dan tanda di sebelah daftar ruangan. Decrypt messages in sidebar - + Dekripsikan pesan di bilah samping Decrypt the messages shown in the sidebar. Only affects messages in encrypted chats. - + Dekripsi pesan yang ditampilkan di bilah samping. +Hanya mempengaruhi pesan di chat terenkripsi. Privacy Screen - + Layar Privasi When the window loses focus, the timeline will be blurred. - + Ketika jendela kehilangan fokus, linimasanya +akan diburamkan. Privacy screen timeout (in seconds [0 - 3600]) - + Waktu kehabisan layar privasi (dalam detik [0 - 3600]) Set timeout (in seconds) for how long after window loses focus before the screen will be blurred. Set to 0 to blur immediately after focus loss. Max value of 1 hour (3600 seconds) - + Tetapkan waktu kehabisan (dalam detik) untuk berapa lama setelah jendela kehilangan +fokus sebelum layarnya akan diburamkan. +Tetapkan ke 0 untuk memburamkan secara langsung setelah kehilangan fokus. Nilai maksimum adalah 1 jam (3600 detik) Show buttons in timeline - + Tampilkan tombol di linimasa Show buttons to quickly reply, react or access additional options next to each message. - + Tampilkan tombol untuk membalas, merekasi, atau mengakses opsi tambahan di sebelah pesan dengan cepat. Limit width of timeline - + Batasi lebar linimasa Set the max width of messages in the timeline (in pixels). This can help readability on wide screen, when Nheko is maximised - + Tetapkan lebar maksimum pesan di linimasa (dalam pixel). Ini bisa membantu keterbacaan di layar lebar, ketika Nheko dimaksimalkan. Typing notifications - + Notifikasi mengetik Show who is typing in a room. This will also enable or disable sending typing notifications to others. - + Tampilkan siapa yang sedang mengetik dalam ruangan. +Ini akan mengaktifkan atau menonaktifkan pengiriman notifikasi pengetikan ke yang lain. Sort rooms by unreads - + Urutkan ruangan bedasarkan yang belum dibaca Display rooms with new messages first. If this is off, the list of rooms will only be sorted by the timestamp of the last message in a room. If this is on, rooms which have active notifications (the small circle with a number in it) will be sorted on top. Rooms, that you have muted, will still be sorted by timestamp, since you don't seem to consider them as important as the other rooms. - + Menampilkan ruangan dengan pesan baru pertama. +Jika ini dimatikan, daftar ruangan hanya diurutkan dari waktu pesan terakhir di ruangan. +Jika ini dinyalakan, ruangan yang mempunyai notifikasi aktif (lingkaran kecil dengan nomor didalam) akan diurutkan di atas. Ruangan, yang dibisukan, masih diurutkan dari waktu, karena Anda tidak pertimangkan mereka sebagai penting dengan ruangan yang lain. Read receipts - + Laporan dibaca Show if your message was read. Status is displayed next to timestamps. - + Menampilkan jika pesan Anda telah dibaca. +Status akan ditampilkan disebelah waktu menerima pesan. Send messages as Markdown - + Kirim pesan sebagai Markdown Allow using markdown in messages. When disabled, all messages are sent as a plain text. - + Memperbolehkan menggunakan Markdown di pesan. +Ketika dinonaktifkan, semua pesan akan dikirim sebagai teks biasa. Play animated images only on hover - + Mainkan gambar beranimasi hanya saat kursor diarahkan ke gambar Desktop notifications - + Notifikasi desktop Notify about received message when the client is not currently focused. - + Memberitahukan tentang pesan yang diterima ketika clientnya tidak difokuskan. Alert on notification - + Beritahu saat ada notifikasi Show an alert when a message is received. This usually causes the application icon in the task bar to animate in some fashion. - + Menampilkan pemberitahuan saat sebuah pesan diterima. +Ini biasanya menyebabkan ikon aplikasi di bilah tugas untuk beranimasi. Highlight message on hover - + Highlight pesan saat kursor di atas pesan Change the background color of messages when you hover over them. - + Mengubah warna background pesan ketika kursor Anda di atas pesannya. Large Emoji in timeline - + Emoji besar di linimasa Make font size larger if messages with only a few emojis are displayed. - + Membuat ukuran font lebih besar jika pesan dengan beberapa emoji ditampilkan. Send encrypted messages to verified users only - + Kirim pesan terenkripsi ke pengguna yang telah diverifikasi saja Requires a user to be verified to send encrypted messages to them. This improves safety but makes E2EE more tedious. - + Memerlukan pengguna diverifikasi untuk mengirim pesan terenkripsi ke pengguna. Ini meningkatkan keamanan tetapi membuat enkripsi ujung-ke-ujung lebih susah. Share keys with verified users and devices - + Bagikan kunci dengan pengguna dan perangkat yang telah diverifikasi Automatically replies to key requests from other users, if they are verified, even if that device shouldn't have access to those keys otherwise. - + Secara otomatis membalas ke permintaan kunci dari pengguna lain, jika mereka terverifikasi, bahkan jika perangkat itu seharusnya tidak mempunyai akses ke kunci itu secara lain. Online Key Backup - + Cadangan Kunci Online Download message encryption keys from and upload to the encrypted online key backup. - + Unduh kunci enkripsi pesan dari dan unggah ke cadangan kunci online terenkripsi. Enable online key backup - + Aktifkan cadangan kunci online The Nheko authors recommend not enabling online key backup until symmetric online key backup is available. Enable anyway? - + Pengembang Nheko merekomendasikan untuk tidak mengaktifkan pencadangan kunci online hingga pencadangan kunci online simetris tersedia. Tetap mengaktifkan? CACHED - + DICACHE NOT CACHED - + TIDAK DICACHE Scale factor - + Faktor skala Change the scale factor of the whole user interface. - + Mengubah faktor skala antarmuka pengguna. Font size - + Ukuran font Font Family - + Keluarga Font Theme - + Tema Ringtone - + Nada Dering Set the notification sound to play when a call invite arrives - + Tetapkan suara notifikasi untuk dimainkan ketika ada panggilan Microphone - + Mikrofon Camera - + Kamera Camera resolution - + Resolusi kamera Camera frame rate - + Frame rate kamera Allow fallback call assist server - + Izinkan panggilan menggunakan bantuan server sebagai cadangan Will use turn.matrix.org as assist when your home server does not offer one. - + Akan menggunakan turn.matrix.org sebagai bantuan jika homeserver Anda tidak menawarkannya. Device ID - + ID Perangkat Device Fingerprint - + Sidik Jari Perangkat Session Keys - + Kunci Perangkat IMPORT - + IMPOR EXPORT - + EKSPOR ENCRYPTION - + ENKRIPSI GENERAL - + UMUM INTERFACE - + ANTARMUKA Plays media like GIFs or WEBPs only when explicitly hovering over them. - + Memainkan media seperti GIF atau WEBP ketika kursor di atas medianya. Touchscreen mode - + Mode layar sentuh Will prevent text selection in the timeline to make touch scrolling easier. - + Akan mencegah seleksi teks di linimasi untuk membuat guliran mudah. Emoji Font Family - + Keluarga Font Emoji Master signing key - + Kunci penandatanganan utama Your most important key. You don't need to have it cached, since not caching it makes it less likely it can be stolen and it is only needed to rotate your other signing keys. - + Kunci Anda yang paling penting. Anda tidak perlu menyimpannya dalam cache, karena tidak menyimpannya akan memperkecil kemungkinannya untuk dicuri dan hanya diperlukan untuk memutar kunci penandatanganan Anda yang lain. User signing key - + Kunci penandatanganan pengguna The key to verify other users. If it is cached, verifying a user will verify all their devices. - + Kunci untuk memverifikasi pengguna lain. Jika dicache, memverifikasi sebuah pengguna akan memverifikasi semua perangkat mereka. Self signing key - + Kunci penandatanganan diri The key to verify your own devices. If it is cached, verifying one of your devices will mark it verified for all your other devices and for users, that have verified you. - + Kunci untuk memverifikasi perangkat Anda. Jika dicache, memverifikasi salah satu perangkat Anda akan menandainya sebagai terverifikasi untuk semua perangkat Anda yang lain dan untuk pengguna yang telah memverifikasi Anda. Backup key - + Kunci cadangan The key to decrypt online key backups. If it is cached, you can enable online key backup to store encryption keys securely encrypted on the server. - + Kunci untuk mendekripsikan cadangan kunci online. Jika dicache, Anda dapat mengaktifkan kunci cadangan online untuk menyimpan kunci enkripsi yang dienkripsi secara aman di servernya. Select a file - + Pilih sebuah file All Files (*) - + Semua File (*) Open Sessions File - + Buka File Sesi @@ -2753,34 +2768,34 @@ This usually causes the application icon in the task bar to animate in some fash Error - + Kesalahan File Password - + Kata Sandi File Enter the passphrase to decrypt the file: - + Masukkan kata sandi untuk mendekripsi filenya: The password cannot be empty - + Kata sandi tidak boleh kosong Enter passphrase to encrypt your session keys: - + Masukkan frasa sandi untuk mengenkripsikan kunci sesi Anda: File to save the exported session keys - + File untuk menyimpan kunci sesi yang telah diekspor @@ -2788,27 +2803,27 @@ This usually causes the application icon in the task bar to animate in some fash Waiting for other party… - + Menunggu untuk mengguna lain… Waiting for other side to accept the verification request. - + Menunggu untuk pengguna yang lain untuk menerima permintaan verifikasi. Waiting for other side to continue the verification process. - + Menunggu untuk pengguna lain untuk melanjutkan proses verifikasi. Waiting for other side to complete the verification process. - + Menunggu untuk pengguna lain untuk menyelesaikan proses verifikasi. Cancel - + Batalkan @@ -2816,22 +2831,22 @@ This usually causes the application icon in the task bar to animate in some fash Welcome to nheko! The desktop client for the Matrix protocol. - + Selamat datang ke nheko! Sebuah client desktop untuk protokol Matrix. Enjoy your stay! - + Nikmati masa tinggal Anda di sini! REGISTER - + DAFTAR LOGIN - + MASUK @@ -2839,7 +2854,7 @@ This usually causes the application icon in the task bar to animate in some fash Yesterday - + Kemarin @@ -2847,42 +2862,42 @@ This usually causes the application icon in the task bar to animate in some fash Create room - + Buat ruangan Cancel - + Batalkan Name - + Nama Topic - + Topik Alias - + Alias Room Visibility - + Visibilitas Ruangan Room Preset - + Preset Ruangan Direct Chat - + Chat Langsung @@ -2890,22 +2905,22 @@ This usually causes the application icon in the task bar to animate in some fash Open Fallback in Browser - + Buka Fallback di Peramban Cancel - + Batalkan Confirm - + Konfirmasi Open the fallback, follow the steps and confirm after completing them. - + Buka fallbacknya, ikuti petunjuknya dan konfirmasi setelah menyelesaikannya. @@ -2913,17 +2928,17 @@ This usually causes the application icon in the task bar to animate in some fash Join - + Bergabung Cancel - + Batalkan Room ID or alias - + ID ruangan atau alias @@ -2931,12 +2946,12 @@ This usually causes the application icon in the task bar to animate in some fash Cancel - + Batalkan Are you sure you want to leave? - + Apakah Anda yakin untuk meninggalkan ruangan? @@ -2944,12 +2959,12 @@ This usually causes the application icon in the task bar to animate in some fash Cancel - + Batalkan Logout. Are you sure? - + Keluar. Apakah Anda yakin? @@ -2957,19 +2972,21 @@ This usually causes the application icon in the task bar to animate in some fash Upload - + Unggah Cancel - + Batalkan Media type: %1 Media size: %2 - + Tipe media: %1 +Ukuran media: %2 + @@ -2977,17 +2994,17 @@ Media size: %2 Cancel - + Batalkan Confirm - + Konfirmasi Solve the reCAPTCHA and press the confirm button - + Selesaikan reCAPTCHAnya dan tekan tombol konfirmasi @@ -2995,112 +3012,112 @@ Media size: %2 You sent an audio clip - + Anda mengirim klip audio %1 sent an audio clip - + %1 mengirim klip audio You sent an image - + Anda mengirim sebuah pesan %1 sent an image - + %1 mengirim sebuah gambar You sent a file - + Anda mengirim sebuah file %1 sent a file - + %1 mengirim sebuah file You sent a video - + Anda mengirim sebuah video %1 sent a video - + %1 mengirim sebuah video You sent a sticker - + Anda mengirim sebuah stiker %1 sent a sticker - + %1 mengirim sebuah stiker You sent a notification - + Anda mengirim sebuah notifikasi %1 sent a notification - + %1 mengirim sebuah notifikasi You: %1 - + Anda: %1 %1: %2 - + %1: %2 You sent an encrypted message - + Anda mengirim sebuah pesan terenkripsi %1 sent an encrypted message - + %1 mengirim sebuah pesan terenkripsi You placed a call - + Anda melakukan panggilan %1 placed a call - + %1 melakukan panggilan You answered a call - + Anda menjawab panggilan %1 answered a call - + %1 menjawab panggilan You ended a call - + Anda mengakhiri panggilan %1 ended a call - + %1 mengakhiri panggilan @@ -3108,7 +3125,7 @@ Media size: %2 Unknown Message Type - + Tipe Pesan Tidak Dikenal From 4dd5f9841dcc9d3e43f8376b748ab1fa115c54a6 Mon Sep 17 00:00:00 2001 From: Nicolas Werner Date: Mon, 4 Oct 2021 21:46:31 +0200 Subject: [PATCH 182/232] Add additional check for invalid megolm sessions --- CMakeLists.txt | 2 +- io.github.NhekoReborn.Nheko.yaml | 2 +- src/Olm.cpp | 4 ++++ 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 85726fed..a3b344b8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -385,7 +385,7 @@ if(USE_BUNDLED_MTXCLIENT) FetchContent_Declare( MatrixClient GIT_REPOSITORY https://github.com/Nheko-Reborn/mtxclient.git - GIT_TAG b452a984b0fc522c21bb8df7d320bf13960974d0 + GIT_TAG 4a598632f432953f4dbfacf6cfed4f85a1c59c5a ) set(BUILD_LIB_EXAMPLES OFF CACHE INTERNAL "") set(BUILD_LIB_TESTS OFF CACHE INTERNAL "") diff --git a/io.github.NhekoReborn.Nheko.yaml b/io.github.NhekoReborn.Nheko.yaml index 4b0f7bf3..e4b6ed8f 100644 --- a/io.github.NhekoReborn.Nheko.yaml +++ b/io.github.NhekoReborn.Nheko.yaml @@ -163,7 +163,7 @@ modules: buildsystem: cmake-ninja name: mtxclient sources: - - commit: b452a984b0fc522c21bb8df7d320bf13960974d0 + - commit: 4a598632f432953f4dbfacf6cfed4f85a1c59c5a type: git url: https://github.com/Nheko-Reborn/mtxclient.git - config-opts: diff --git a/src/Olm.cpp b/src/Olm.cpp index 60460b5c..14c97984 100644 --- a/src/Olm.cpp +++ b/src/Olm.cpp @@ -1129,6 +1129,10 @@ decryptEvent(const MegolmSessionIndex &index, std::string msg_str; try { auto session = cache::client()->getInboundMegolmSession(index); + if (!session) { + return {DecryptionErrorCode::MissingSession, std::nullopt, std::nullopt}; + } + auto sessionData = cache::client()->getMegolmSessionData(index).value_or(GroupSessionData{}); From acc257ea065ca5aa4e620aea6262ecf0e5fbdfe7 Mon Sep 17 00:00:00 2001 From: Nicolas Werner Date: Mon, 4 Oct 2021 21:52:16 +0200 Subject: [PATCH 183/232] Fix appimage builder url --- .gitlab-ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index e82e72d6..1012c690 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -191,7 +191,7 @@ appimage-amd64: - apt-get install -y git wget curl # update appimage-builder (optional) - - pip3 install --upgrade git+https://www.opencode.net/azubieta/appimagecraft.git + - pip3 install --upgrade git+https://github.com/AppImageCrafters/appimage-builder.git - apt-get update && apt-get -y install --no-install-recommends g++-7 build-essential ninja-build qt${QT_PKG}{base,declarative,tools,multimedia,script,quickcontrols2,svg} liblmdb-dev libssl-dev git ninja-build qt5keychain-dev libgtest-dev ccache libevent-dev libcurl4-openssl-dev libgl1-mesa-dev - wget https://github.com/Kitware/CMake/releases/download/v3.19.0/cmake-3.19.0-Linux-x86_64.sh && sh cmake-3.19.0-Linux-x86_64.sh --skip-license --prefix=/usr/local From 81406171a0d6b6751e9eb1777faf6ea895affcc7 Mon Sep 17 00:00:00 2001 From: Loren Burkholder Date: Wed, 29 Sep 2021 20:15:25 -0400 Subject: [PATCH 184/232] QML the logout dialog --- resources/qml/Root.qml | 16 ++++++++++++++++ resources/qml/dialogs/LogoutDialog.qml | 19 +++++++++++++++++++ resources/res.qrc | 1 + src/MainWindow.cpp | 19 ------------------- src/MainWindow.h | 1 - src/ui/NhekoGlobalObject.cpp | 5 +++-- src/ui/NhekoGlobalObject.h | 4 +++- 7 files changed, 42 insertions(+), 23 deletions(-) create mode 100644 resources/qml/dialogs/LogoutDialog.qml diff --git a/resources/qml/Root.qml b/resources/qml/Root.qml index 4207f35b..29da45eb 100644 --- a/resources/qml/Root.qml +++ b/resources/qml/Root.qml @@ -111,6 +111,13 @@ Page { } + Component { + id: logoutDialog + + LogoutDialog { + } + } + Shortcut { sequence: "Ctrl+K" onActivated: { @@ -135,6 +142,15 @@ Page { onActivated: Rooms.previousRoom() } + Connections { + function onOpenLogoutDialog() { + var dialog = logoutDialog.createObject(timelineRoot); + dialog.open(); + } + + target: Nheko + } + Connections { function onNewDeviceVerificationRequest(flow) { var dialog = deviceVerificationDialog.createObject(timelineRoot, { diff --git a/resources/qml/dialogs/LogoutDialog.qml b/resources/qml/dialogs/LogoutDialog.qml new file mode 100644 index 00000000..9e107097 --- /dev/null +++ b/resources/qml/dialogs/LogoutDialog.qml @@ -0,0 +1,19 @@ +// SPDX-FileCopyrightText: 2021 Nheko Contributors +// +// SPDX-License-Identifier: GPL-3.0-or-later + +import QtQuick 2.15 +import QtQuick.Controls 2.15 +import Qt.labs.platform 1.1 +import im.nheko 1.0 + +MessageDialog { + id: logoutRoot + + title: qsTr("Log out") + text: CallManager.isOnCall ? qsTr("A call is in progress. Log out?") : qsTr("Are you sure you want to log out?") + modality: Qt.WindowModal + flags: Qt.Tool | Qt.WindowStaysOnTopHint | Qt.WindowCloseButtonHint | Qt.WindowTitleHint + buttons: Dialog.Ok | Dialog.Cancel + onAccepted: Nheko.logout() +} diff --git a/resources/res.qrc b/resources/res.qrc index a001a929..af684fc0 100644 --- a/resources/res.qrc +++ b/resources/res.qrc @@ -183,6 +183,7 @@ qml/dialogs/RoomMembers.qml qml/dialogs/RoomSettings.qml qml/dialogs/UserProfile.qml + qml/dialogs/LogoutDialog.qml media/ring.ogg diff --git a/src/MainWindow.cpp b/src/MainWindow.cpp index bc53b906..0978fc25 100644 --- a/src/MainWindow.cpp +++ b/src/MainWindow.cpp @@ -35,7 +35,6 @@ #include "dialogs/CreateRoom.h" #include "dialogs/JoinRoom.h" #include "dialogs/LeaveRoom.h" -#include "dialogs/Logout.h" MainWindow *MainWindow::instance_ = nullptr; @@ -372,24 +371,6 @@ MainWindow::showSolidOverlayModal(QWidget *content, QFlags fl modal_->show(); } -void -MainWindow::openLogoutDialog() -{ - auto dialog = new dialogs::Logout(this); - connect(dialog, &dialogs::Logout::loggingOut, this, [this]() { - if (WebRTCSession::instance().state() != webrtc::State::DISCONNECTED) { - if (QMessageBox::question(this, "nheko", "A call is in progress. Log out?") != - QMessageBox::Yes) { - return; - } - WebRTCSession::instance().end(); - } - chat_page_->initiateLogout(); - }); - - showDialog(dialog); -} - bool MainWindow::hasActiveDialogs() const { diff --git a/src/MainWindow.h b/src/MainWindow.h index eff8fbe7..a3c3c767 100644 --- a/src/MainWindow.h +++ b/src/MainWindow.h @@ -64,7 +64,6 @@ public: void openCreateRoomDialog( std::function callback); void openJoinRoomDialog(std::function callback); - void openLogoutDialog(); void hideOverlay(); void showSolidOverlayModal(QWidget *content, QFlags flags = Qt::AlignCenter); diff --git a/src/ui/NhekoGlobalObject.cpp b/src/ui/NhekoGlobalObject.cpp index d6824996..1a20cbc2 100644 --- a/src/ui/NhekoGlobalObject.cpp +++ b/src/ui/NhekoGlobalObject.cpp @@ -13,6 +13,7 @@ #include "Logging.h" #include "MainWindow.h" #include "UserSettingsPage.h" +#include "WebRTCSession.h" #include "Utils.h" Nheko::Nheko() @@ -83,9 +84,9 @@ Nheko::showUserSettingsPage() const } void -Nheko::openLogoutDialog() const +Nheko::logout() const { - MainWindow::instance()->openLogoutDialog(); + ChatPage::instance()->initiateLogout(); } void diff --git a/src/ui/NhekoGlobalObject.h b/src/ui/NhekoGlobalObject.h index aa8435d1..64aad941 100644 --- a/src/ui/NhekoGlobalObject.h +++ b/src/ui/NhekoGlobalObject.h @@ -48,7 +48,7 @@ public: Q_INVOKABLE void openLink(QString link) const; Q_INVOKABLE void setStatusMessage(QString msg) const; Q_INVOKABLE void showUserSettingsPage() const; - Q_INVOKABLE void openLogoutDialog() const; + Q_INVOKABLE void logout() const; Q_INVOKABLE void openCreateRoomDialog() const; Q_INVOKABLE void openJoinRoomDialog() const; Q_INVOKABLE void reparent(QWindow *win) const; @@ -60,6 +60,8 @@ signals: void colorsChanged(); void profileChanged(); + void openLogoutDialog(); + private: QScopedPointer currentUser_; }; From 456a41bcdf9c0bff99a20bb77d1039c89f2101eb Mon Sep 17 00:00:00 2001 From: Thulinma Date: Sun, 19 Sep 2021 22:55:12 +0200 Subject: [PATCH 185/232] Added support for refreshing the device list, marking current device with a checkmark instead of a lock --- resources/icons/ui/refresh.png | Bin 0 -> 1241 bytes resources/icons/ui/refresh.svg | 16 +++++++++++ resources/qml/dialogs/UserProfile.qml | 38 ++++++++++++++++++++------ resources/res.qrc | 1 + src/Cache.cpp | 10 +++++++ src/Cache_p.h | 1 + src/ui/UserProfile.cpp | 12 ++++++++ src/ui/UserProfile.h | 2 ++ 8 files changed, 72 insertions(+), 8 deletions(-) create mode 100644 resources/icons/ui/refresh.png create mode 100644 resources/icons/ui/refresh.svg diff --git a/resources/icons/ui/refresh.png b/resources/icons/ui/refresh.png new file mode 100644 index 0000000000000000000000000000000000000000..642682032ab6d6ff73b0769bd2c420e89281d8a9 GIT binary patch literal 1241 zcmV;~1Sb25P)V3TT4R7iuwNQoe|g32h; zFiD}n2%lZ(wU0ocn5#rbO9T| zK0GTVva4B$jX0{9=vMYE9>N6$;#`C~aY+FmW4H#}F)W?%dKZD*#)BO z?N8iO7w;r&YqKshld-i6gtoF#tQWqhX19H=1WdzcT_7;4ZO2;~uWd^|KJNliNo;Z( z@l)_I?#Pm1`tVIoXFG9KdAi4O zx$tvJnt;c0I@%-@9-UyN&?%OlfSE#19^+sb52R4pf{OC$k}*6Vxp-7~NOh75Hb=&p zDFM^O_wwsfscb^o;>RT5@yLZ==-h9fG~&l3;QNS0gF?a5DN>0)lYmo_WS zJu<%N1Z;>{a8mQlhZL-dh%I*lVs8Jp(`jwUuMx53O28avHwv@ZO-bdBXCNZBSUWrk zS8BhhSYs#lmmn&J*An{PSQEZRcze@|W5UDlP{M$ku^e~dqLTcbzz%#=lD{53di3bg zqeqV(6OLLboTAj+i4RNi7sJ(fTG-%i#)$9&^;5#ANvdvZb(zXCvLj<`hieN`_S?TD z6xxUzWN+>F5H>aEBn)UIq3=dKDSK_>q+c1-=Cnzw1k~&l zb_v5*i?trAn2a0oaCOap1wVTPcefH>sb1JghHy~$R*wr0&RJDksNDI&)p(`dMXB$A z^JVL^W+{uaE%=}dM3pW+O9HkzdjMg57l>MtFRt_Ilnr26tazH2 zla0jJSRnsAaSGcd%n|2eU!?dNeZok6>}YM7c0@Q^P>7|%iL28>x3ocAV>3&b9QmsR zL0N53I8IySEW#LO3&%;XE8t_ga6lsEdBUIYR5jkNW(i(Z&Pk7hLZ`V*g3;Vg^zd_V zyD(mNtx)aF77j0+200000NkvXXu0mjf D1CLgK literal 0 HcmV?d00001 diff --git a/resources/icons/ui/refresh.svg b/resources/icons/ui/refresh.svg new file mode 100644 index 00000000..17c41496 --- /dev/null +++ b/resources/icons/ui/refresh.svg @@ -0,0 +1,16 @@ + + + + + diff --git a/resources/qml/dialogs/UserProfile.qml b/resources/qml/dialogs/UserProfile.qml index 9bf548e3..86822bdf 100644 --- a/resources/qml/dialogs/UserProfile.qml +++ b/resources/qml/dialogs/UserProfile.qml @@ -249,6 +249,14 @@ ApplicationWindow { visible: !profile.isGlobalUserProfile && profile.room.permissions.canBan() } + ImageButton { + image: ":/icons/icons/ui/refresh.png" + hoverEnabled: true + ToolTip.visible: hovered + ToolTip.text: qsTr("Refresh device list.") + onClicked: profile.refreshDevices(); + } + } } @@ -264,6 +272,9 @@ ApplicationWindow { delegate: RowLayout { + required property int verificationStatus + required property string deviceId + required property string deviceName width: devicelist.width spacing: 4 @@ -276,7 +287,7 @@ ApplicationWindow { elide: Text.ElideRight font.bold: true color: Nheko.colors.text - text: model.deviceId + text: deviceId } Text { @@ -284,7 +295,7 @@ ApplicationWindow { Layout.alignment: Qt.AlignRight elide: Text.ElideRight color: Nheko.colors.text - text: model.deviceName + text: deviceName } } @@ -292,19 +303,30 @@ ApplicationWindow { Image { Layout.preferredHeight: 16 Layout.preferredWidth: 16 - source: ((model.verificationStatus == VerificationStatus.VERIFIED) ? "image://colorimage/:/icons/icons/ui/lock.png?green" : ((model.verificationStatus == VerificationStatus.UNVERIFIED) ? "image://colorimage/:/icons/icons/ui/unlock.png?yellow" : "image://colorimage/:/icons/icons/ui/unlock.png?red")) + source: { + switch (verificationStatus){ + case VerificationStatus.VERIFIED: + return "image://colorimage/:/icons/icons/ui/lock.png?green"; + case VerificationStatus.UNVERIFIED: + return "image://colorimage/:/icons/icons/ui/unlock.png?yellow"; + case VerificationStatus.SELF: + return "image://colorimage/:/icons/icons/ui/checkmark.png?green"; + default: + return "image://colorimage/:/icons/icons/ui/unlock.png?red"; + } + } } Button { id: verifyButton - visible: (!profile.userVerificationEnabled && !profile.isSelf) || (profile.isSelf && (model.verificationStatus != VerificationStatus.VERIFIED || !profile.userVerificationEnabled)) - text: (model.verificationStatus != VerificationStatus.VERIFIED) ? qsTr("Verify") : qsTr("Unverify") + visible: (!profile.userVerificationEnabled && !profile.isSelf) || (profile.isSelf && (verificationStatus == VerificationStatus.UNVERIFIED || !profile.userVerificationEnabled)) + text: (verificationStatus != VerificationStatus.VERIFIED) ? qsTr("Verify") : qsTr("Unverify") onClicked: { - if (model.verificationStatus == VerificationStatus.VERIFIED) - profile.unverify(model.deviceId); + if (verificationStatus == VerificationStatus.VERIFIED) + profile.unverify(deviceId); else - profile.verify(model.deviceId); + profile.verify(deviceId); } } diff --git a/resources/res.qrc b/resources/res.qrc index a001a929..3bd301f7 100644 --- a/resources/res.qrc +++ b/resources/res.qrc @@ -72,6 +72,7 @@ icons/ui/screen-share.png icons/ui/toggle-camera-view.png icons/ui/video-call.png + icons/ui/refresh.png icons/emoji-categories/people.png icons/emoji-categories/people@2x.png icons/emoji-categories/nature.png diff --git a/src/Cache.cpp b/src/Cache.cpp index b124fe5e..ee0ca0c2 100644 --- a/src/Cache.cpp +++ b/src/Cache.cpp @@ -4045,6 +4045,16 @@ Cache::updateUserKeys(const std::string &sync_token, const mtx::responses::Query } } +void +Cache::markUserKeysOutOfDate(const std::vector &user_ids) +{ + auto currentBatchToken = nextBatchToken(); + auto txn = lmdb::txn::begin(env_); + auto db = getUserKeysDb(txn); + markUserKeysOutOfDate(txn, db, user_ids, currentBatchToken); + txn.commit(); +} + void Cache::markUserKeysOutOfDate(lmdb::txn &txn, lmdb::dbi &db, diff --git a/src/Cache_p.h b/src/Cache_p.h index a15010e6..52375d38 100644 --- a/src/Cache_p.h +++ b/src/Cache_p.h @@ -50,6 +50,7 @@ public: const std::string &room_id, bool verified_only); void updateUserKeys(const std::string &sync_token, const mtx::responses::QueryKeys &keyQuery); + void markUserKeysOutOfDate(const std::vector &user_ids); void markUserKeysOutOfDate(lmdb::txn &txn, lmdb::dbi &db, const std::vector &user_ids, diff --git a/src/ui/UserProfile.cpp b/src/ui/UserProfile.cpp index 58150ed7..62488954 100644 --- a/src/ui/UserProfile.cpp +++ b/src/ui/UserProfile.cpp @@ -151,6 +151,15 @@ UserProfile::isSelf() const return this->userid_ == utils::localUser(); } +void +UserProfile::refreshDevices() +{ + std::vector keysToRequest; + keysToRequest.push_back(this->userid_.toStdString()); + cache::client()->markUserKeysOutOfDate(keysToRequest); + fetchDeviceList(this->userid_); +} + void UserProfile::fetchDeviceList(const QString &userID) { @@ -205,6 +214,9 @@ UserProfile::fetchDeviceList(const QString &userID) device, DeviceId(device.device_id), UserId(other_user_id))) verified = verification::Status::VERIFIED; + if (isSelf() && device.device_id == ::http::client()->device_id()) + verified = verification::Status::SELF; + deviceInfo.push_back( {QString::fromStdString(d.first), QString::fromStdString(device.unsigned_info.device_display_name), diff --git a/src/ui/UserProfile.h b/src/ui/UserProfile.h index a148c431..c83ec01e 100644 --- a/src/ui/UserProfile.h +++ b/src/ui/UserProfile.h @@ -18,6 +18,7 @@ Q_NAMESPACE enum Status { + SELF, VERIFIED, UNVERIFIED, BLOCKED @@ -118,6 +119,7 @@ public: Q_INVOKABLE void verify(QString device = ""); Q_INVOKABLE void unverify(QString device = ""); Q_INVOKABLE void fetchDeviceList(const QString &userID); + Q_INVOKABLE void refreshDevices(); Q_INVOKABLE void banUser(); // Q_INVOKABLE void ignoreUser(); Q_INVOKABLE void kickUser(); From 47db1e5c65eb5cb8b9b0d8d50fb33f69f9665f30 Mon Sep 17 00:00:00 2001 From: Nicolas Werner Date: Thu, 7 Oct 2021 19:55:27 +0200 Subject: [PATCH 186/232] Remove duplicated verification status calculation --- src/ui/UserProfile.cpp | 102 ++++++++++++++++++++--------------------- src/ui/UserProfile.h | 6 ++- 2 files changed, 55 insertions(+), 53 deletions(-) diff --git a/src/ui/UserProfile.cpp b/src/ui/UserProfile.cpp index 62488954..5ddf011f 100644 --- a/src/ui/UserProfile.cpp +++ b/src/ui/UserProfile.cpp @@ -34,6 +34,7 @@ UserProfile::UserProfile(QString roomid, this, &UserProfile::setGlobalUsername, Qt::QueuedConnection); + connect(this, &UserProfile::verificationStatiChanged, &UserProfile::updateVerificationStatus); if (isGlobalUserProfile()) { getGlobalProfileData(); @@ -48,22 +49,7 @@ UserProfile::UserProfile(QString roomid, if (user_id != this->userid_.toStdString()) return; - auto status = cache::verificationStatus(user_id); - if (!status) - return; - this->isUserVerified = status->user_verified; - emit userStatusChanged(); - - for (auto &deviceInfo : deviceList_.deviceList_) { - deviceInfo.verification_status = - std::find(status->verified_devices.begin(), - status->verified_devices.end(), - deviceInfo.device_id.toStdString()) == status->verified_devices.end() - ? verification::UNVERIFIED - : verification::VERIFIED; - } - deviceList_.reset(deviceList_.deviceList_); - emit devicesChanged(); + emit verificationStatiChanged(); }); fetchDeviceList(this->userid_); } @@ -170,20 +156,18 @@ UserProfile::fetchDeviceList(const QString &userID) cache::client()->query_keys( userID.toStdString(), - [other_user_id = userID.toStdString(), this](const UserKeyCache &other_user_keys, + [other_user_id = userID.toStdString(), this](const UserKeyCache &, mtx::http::RequestErr err) { if (err) { nhlog::net()->warn("failed to query device keys: {},{}", mtx::errors::to_string(err->matrix_error.errcode), static_cast(err->status_code)); - return; } // Ensure local key cache is up to date cache::client()->query_keys( utils::localUser().toStdString(), - [other_user_id, other_user_keys, this](const UserKeyCache &, - mtx::http::RequestErr err) { + [this](const UserKeyCache &, mtx::http::RequestErr err) { using namespace mtx; std::string local_user_id = utils::localUser().toStdString(); @@ -191,44 +175,58 @@ UserProfile::fetchDeviceList(const QString &userID) nhlog::net()->warn("failed to query device keys: {},{}", mtx::errors::to_string(err->matrix_error.errcode), static_cast(err->status_code)); - return; } - this->hasMasterKey = !other_user_keys.master_keys.keys.empty(); - - std::vector deviceInfo; - auto devices = other_user_keys.device_keys; - auto verificationStatus = cache::client()->verificationStatus(other_user_id); - - isUserVerified = verificationStatus.user_verified; - emit userStatusChanged(); - - for (const auto &d : devices) { - auto device = d.second; - verification::Status verified = verification::Status::UNVERIFIED; - - if (std::find(verificationStatus.verified_devices.begin(), - verificationStatus.verified_devices.end(), - device.device_id) != verificationStatus.verified_devices.end() && - mtx::crypto::verify_identity_signature( - device, DeviceId(device.device_id), UserId(other_user_id))) - verified = verification::Status::VERIFIED; - - if (isSelf() && device.device_id == ::http::client()->device_id()) - verified = verification::Status::SELF; - - deviceInfo.push_back( - {QString::fromStdString(d.first), - QString::fromStdString(device.unsigned_info.device_display_name), - verified}); - } - - this->deviceList_.queueReset(std::move(deviceInfo)); - emit devicesChanged(); + emit verificationStatiChanged(); }); }); } +void +UserProfile::updateVerificationStatus() +{ + if (!cache::client() || !cache::client()->isDatabaseReady()) + return; + + auto user_keys = cache::client()->userKeys(userid_.toStdString()); + if (!user_keys) { + this->hasMasterKey = false; + this->isUserVerified = crypto::Trust::Unverified; + this->deviceList_.reset({}); + emit userStatusChanged(); + return; + } + + this->hasMasterKey = !user_keys->master_keys.keys.empty(); + + std::vector deviceInfo; + auto devices = user_keys->device_keys; + auto verificationStatus = cache::client()->verificationStatus(userid_.toStdString()); + + this->isUserVerified = verificationStatus.user_verified; + emit userStatusChanged(); + + for (const auto &d : devices) { + auto device = d.second; + verification::Status verified = + std::find(verificationStatus.verified_devices.begin(), + verificationStatus.verified_devices.end(), + device.device_id) == verificationStatus.verified_devices.end() + ? verification::UNVERIFIED + : verification::VERIFIED; + + if (isSelf() && device.device_id == ::http::client()->device_id()) + verified = verification::Status::SELF; + + deviceInfo.push_back({QString::fromStdString(d.first), + QString::fromStdString(device.unsigned_info.device_display_name), + verified}); + } + + this->deviceList_.queueReset(std::move(deviceInfo)); + emit devicesChanged(); +} + void UserProfile::banUser() { diff --git a/src/ui/UserProfile.h b/src/ui/UserProfile.h index c83ec01e..68f9c21b 100644 --- a/src/ui/UserProfile.h +++ b/src/ui/UserProfile.h @@ -137,11 +137,15 @@ signals: void globalUsernameRetrieved(const QString &globalUser); void devicesChanged(); + // internal + void verificationStatiChanged(); + public slots: void updateAvatarUrl(); -protected slots: +private slots: void setGlobalUsername(const QString &globalUser); + void updateVerificationStatus(); private: void updateRoomMemberState(mtx::events::state::Member member); From 569606f35b7b0ebbeba5eb5625a8f47fde4eb980 Mon Sep 17 00:00:00 2001 From: "DeepBlueV7.X" Date: Thu, 7 Oct 2021 17:59:03 +0000 Subject: [PATCH 187/232] Simplify device list refresh logic --- resources/qml/dialogs/UserProfile.qml | 2 +- src/ui/UserProfile.cpp | 4 +--- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/resources/qml/dialogs/UserProfile.qml b/resources/qml/dialogs/UserProfile.qml index 86822bdf..d5442382 100644 --- a/resources/qml/dialogs/UserProfile.qml +++ b/resources/qml/dialogs/UserProfile.qml @@ -320,7 +320,7 @@ ApplicationWindow { Button { id: verifyButton - visible: (!profile.userVerificationEnabled && !profile.isSelf) || (profile.isSelf && (verificationStatus == VerificationStatus.UNVERIFIED || !profile.userVerificationEnabled)) + visible: verificationStatus == VerificationStatus.UNVERIFIED && (profile.isSelf || !profile.userVerificationEnabled) text: (verificationStatus != VerificationStatus.VERIFIED) ? qsTr("Verify") : qsTr("Unverify") onClicked: { if (verificationStatus == VerificationStatus.VERIFIED) diff --git a/src/ui/UserProfile.cpp b/src/ui/UserProfile.cpp index 5ddf011f..31ae9f8b 100644 --- a/src/ui/UserProfile.cpp +++ b/src/ui/UserProfile.cpp @@ -140,9 +140,7 @@ UserProfile::isSelf() const void UserProfile::refreshDevices() { - std::vector keysToRequest; - keysToRequest.push_back(this->userid_.toStdString()); - cache::client()->markUserKeysOutOfDate(keysToRequest); + cache::client()->markUserKeysOutOfDate({this->userid_.toStdString()}); fetchDeviceList(this->userid_); } From ad1e6c82986fd92d34fdc3d7f27a152a80d05e81 Mon Sep 17 00:00:00 2001 From: Nicolas Werner Date: Sat, 18 Sep 2021 00:21:14 +0200 Subject: [PATCH 188/232] Support bootstrapping crosssigning Showing the bootstrap state and showing there are unverified devices is still missing. --- CMakeLists.txt | 6 +- io.github.NhekoReborn.Nheko.yaml | 2 +- resources/qml/Avatar.qml | 1 + resources/qml/ChatPage.qml | 9 +- resources/qml/MessageView.qml | 9 +- resources/qml/Root.qml | 23 +++ resources/qml/SelfVerificationCheck.qml | 261 ++++++++++++++++++++++++ resources/qml/delegates/Reply.qml | 14 +- resources/qml/delegates/TextMessage.qml | 1 + resources/qml/dialogs/InputDialog.qml | 1 + resources/qml/dialogs/RoomDirectory.qml | 2 +- resources/qml/dialogs/UserProfile.qml | 47 +++-- resources/res.qrc | 1 + src/Cache.cpp | 12 ++ src/Cache_p.h | 1 + src/SelfVerificationStatus.cpp | 249 ++++++++++++++++++++++ src/SelfVerificationStatus.h | 43 ++++ src/timeline/TimelineViewManager.cpp | 44 ++-- src/ui/UIA.cpp | 136 ++++++++++++ src/ui/UIA.h | 40 ++++ 20 files changed, 834 insertions(+), 68 deletions(-) create mode 100644 resources/qml/SelfVerificationCheck.qml create mode 100644 src/SelfVerificationStatus.cpp create mode 100644 src/SelfVerificationStatus.h create mode 100644 src/ui/UIA.cpp create mode 100644 src/ui/UIA.h diff --git a/CMakeLists.txt b/CMakeLists.txt index a3b344b8..3e5c2f09 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -328,6 +328,7 @@ set(SRC_FILES src/ui/Theme.cpp src/ui/ThemeManager.cpp src/ui/ToggleButton.cpp + src/ui/UIA.cpp src/ui/UserProfile.cpp # Generic notification stuff @@ -365,6 +366,7 @@ set(SRC_FILES src/RoomDirectoryModel.cpp src/RoomsModel.cpp src/Utils.cpp + src/SelfVerificationStatus.cpp src/WebRTCSession.cpp src/WelcomePage.cpp src/main.cpp @@ -385,7 +387,7 @@ if(USE_BUNDLED_MTXCLIENT) FetchContent_Declare( MatrixClient GIT_REPOSITORY https://github.com/Nheko-Reborn/mtxclient.git - GIT_TAG 4a598632f432953f4dbfacf6cfed4f85a1c59c5a + GIT_TAG 8b56b466dbacde501ed9087d53bb4f51b297eca8 ) set(BUILD_LIB_EXAMPLES OFF CACHE INTERNAL "") set(BUILD_LIB_TESTS OFF CACHE INTERNAL "") @@ -541,6 +543,7 @@ qt5_wrap_cpp(MOC_HEADERS src/ui/Theme.h src/ui/ThemeManager.h src/ui/ToggleButton.h + src/ui/UIA.h src/ui/UserProfile.h src/notifications/Manager.h @@ -573,6 +576,7 @@ qt5_wrap_cpp(MOC_HEADERS src/UsersModel.h src/RoomDirectoryModel.h src/RoomsModel.h + src/SelfVerificationStatus.h src/WebRTCSession.h src/WelcomePage.h src/ReadReceiptsModel.h diff --git a/io.github.NhekoReborn.Nheko.yaml b/io.github.NhekoReborn.Nheko.yaml index e4b6ed8f..ce272ea7 100644 --- a/io.github.NhekoReborn.Nheko.yaml +++ b/io.github.NhekoReborn.Nheko.yaml @@ -163,7 +163,7 @@ modules: buildsystem: cmake-ninja name: mtxclient sources: - - commit: 4a598632f432953f4dbfacf6cfed4f85a1c59c5a + - commit: 8b56b466dbacde501ed9087d53bb4f51b297eca8 type: git url: https://github.com/Nheko-Reborn/mtxclient.git - config-opts: diff --git a/resources/qml/Avatar.qml b/resources/qml/Avatar.qml index 5d2b583a..58b22863 100644 --- a/resources/qml/Avatar.qml +++ b/resources/qml/Avatar.qml @@ -42,6 +42,7 @@ Rectangle { Image { id: identicon + anchors.fill: parent visible: Settings.useIdenticon && img.status != Image.Ready source: Settings.useIdenticon ? ("image://jdenticon/" + (userid !== "" ? userid : roomid) + "?radius=" + (Settings.avatarCircles ? 100 : 25)) : "" diff --git a/resources/qml/ChatPage.qml b/resources/qml/ChatPage.qml index e56d7d46..082fa8d6 100644 --- a/resources/qml/ChatPage.qml +++ b/resources/qml/ChatPage.qml @@ -2,12 +2,15 @@ // // SPDX-License-Identifier: GPL-3.0-or-later -import QtQuick 2.9 -import QtQuick.Controls 2.5 +import QtQuick 2.15 +import QtQuick.Controls 2.15 import QtQuick.Layouts 1.3 import "components" import im.nheko 1.0 +// this needs to be last +import QtQml 2.15 + Rectangle { id: chatPage @@ -41,6 +44,7 @@ Rectangle { value: communityListC.preferredWidth when: !adaptiveView.singlePageMode delayed: true + restoreMode: Binding.RestoreBindingOrValue } } @@ -66,6 +70,7 @@ Rectangle { value: roomListC.preferredWidth when: !adaptiveView.singlePageMode delayed: true + restoreMode: Binding.RestoreBindingOrValue } } diff --git a/resources/qml/MessageView.qml b/resources/qml/MessageView.qml index 60c04098..7ed30112 100644 --- a/resources/qml/MessageView.qml +++ b/resources/qml/MessageView.qml @@ -20,12 +20,11 @@ ScrollView { ListView { id: chat - - displayMarginBeginning: height/2 - displayMarginEnd: height/2 property int delegateMaxWidth: ((Settings.timelineMaxWidth > 100 && Settings.timelineMaxWidth < parent.availableWidth) ? Settings.timelineMaxWidth : parent.availableWidth) - parent.padding * 2 + displayMarginBeginning: height / 2 + displayMarginEnd: height / 2 model: room // reuseItems still has a few bugs, see https://bugreports.qt.io/browse/QTBUG-95105 https://bugreports.qt.io/browse/QTBUG-95107 //onModelChanged: if (room) room.sendReset() @@ -415,8 +414,6 @@ ScrollView { Loader { id: section - z: 4 - property int parentWidth: parent.width property string userId: wrapper.userId property string previousMessageUserId: wrapper.previousMessageUserId @@ -425,6 +422,7 @@ ScrollView { property string userName: wrapper.userName property var timestamp: wrapper.timestamp + z: 4 active: previousMessageUserId !== undefined && previousMessageUserId !== userId || previousMessageDay !== day //asynchronous: true sourceComponent: sectionHeader @@ -685,6 +683,7 @@ ScrollView { text: qsTr("&Go to quoted message") onTriggered: chat.model.showEvent(eventId) } + } } diff --git a/resources/qml/Root.qml b/resources/qml/Root.qml index 4207f35b..45825f80 100644 --- a/resources/qml/Root.qml +++ b/resources/qml/Root.qml @@ -195,6 +195,29 @@ Page { target: CallManager } + SelfVerificationCheck { + } + + InputDialog { + id: uiaPassPrompt + + echoMode: TextInput.Password + title: UIA.title + prompt: qsTr("Please enter your login password to continue:") + onAccepted: (t) => { + return UIA.continuePassword(t); + } + } + + Connections { + function onPassword() { + console.log("UIA: password needed"); + uiaPassPrompt.show(); + } + + target: UIA + } + ChatPage { anchors.fill: parent } diff --git a/resources/qml/SelfVerificationCheck.qml b/resources/qml/SelfVerificationCheck.qml new file mode 100644 index 00000000..a12bfc61 --- /dev/null +++ b/resources/qml/SelfVerificationCheck.qml @@ -0,0 +1,261 @@ +// SPDX-FileCopyrightText: 2021 Nheko Contributors +// +// SPDX-License-Identifier: GPL-3.0-or-later + +import Qt.labs.platform 1.1 as P +import QtQuick 2.15 +import QtQuick.Controls 2.15 +import QtQuick.Layouts 1.3 +import im.nheko 1.0 + +Item { + visible: false + enabled: false + + Dialog { + id: showRecoverKeyDialog + + property string recoveryKey: "" + + parent: Overlay.overlay + anchors.centerIn: parent + height: content.height + implicitFooterHeight + implicitHeaderHeight + width: content.width + padding: 0 + modal: true + standardButtons: Dialog.Ok + closePolicy: Popup.NoAutoClose + + ColumnLayout { + id: content + + spacing: 0 + + Label { + Layout.margins: Nheko.paddingMedium + Layout.maximumWidth: (Overlay.overlay ? Overlay.overlay.width : 400) - Nheko.paddingMedium * 4 + Layout.fillWidth: true + text: qsTr("This is your recovery key. You will need it to restore access to your encrypted messages and verification keys. Keep this safe. Don't share it with anyone and don't lose it! Don't go to start! Don't draw $200 from the bank!") + color: Nheko.colors.text + wrapMode: Text.Wrap + } + + TextEdit { + Layout.maximumWidth: (Overlay.overlay ? Overlay.overlay.width : 400) - Nheko.paddingMedium * 4 + Layout.alignment: Qt.AlignHCenter + horizontalAlignment: TextEdit.AlignHCenter + verticalAlignment: TextEdit.AlignVCenter + readOnly: true + selectByMouse: true + text: showRecoverKeyDialog.recoveryKey + color: Nheko.colors.text + font.bold: true + wrapMode: TextEdit.Wrap + } + + } + + background: Rectangle { + color: Nheko.colors.window + border.color: Nheko.theme.separator + border.width: 1 + radius: Nheko.paddingSmall + } + + } + + P.MessageDialog { + id: successDialog + + buttons: P.MessageDialog.Ok + text: qsTr("Encryption setup successfully") + } + + P.MessageDialog { + id: failureDialog + + property string errorMessage + + buttons: P.MessageDialog.Ok + text: qsTr("Failed to setup encryption: %1").arg(errorMessage) + } + + Dialog { + id: bootstrapCrosssigning + + parent: Overlay.overlay + anchors.centerIn: parent + height: (Math.floor(parent.height / 2) - Nheko.paddingLarge) * 2 + width: (Math.floor(parent.width / 2) - Nheko.paddingLarge) * 2 + padding: 0 + modal: true + standardButtons: Dialog.Ok | Dialog.Cancel + closePolicy: Popup.NoAutoClose + onAccepted: SelfVerificationStatus.setupCrosssigning(storeSecretsOnline.checked, usePassword.checked ? passwordField.text : "", useOnlineKeyBackup.checked) + + ScrollView { + id: scroll + + clip: true + anchors.fill: parent + ScrollBar.horizontal.visible: false + ScrollBar.vertical.visible: true + + GridLayout { + id: grid + + width: scroll.width - scroll.ScrollBar.vertical.width + columns: 2 + rowSpacing: 0 + columnSpacing: 0 + + Label { + Layout.margins: Nheko.paddingMedium + Layout.alignment: Qt.AlignHCenter + Layout.columnSpan: 2 + font.pointSize: fontMetrics.font.pointSize * 2 + text: qsTr("Setup Encryption") + color: Nheko.colors.text + wrapMode: Text.Wrap + } + + Label { + Layout.margins: Nheko.paddingMedium + Layout.alignment: Qt.AlignLeft + Layout.columnSpan: 2 + Layout.maximumWidth: grid.width - Nheko.paddingMedium * 2 + text: qsTr("Hello and welcome to Matrix!\nIt seems like you are new. Before you can securely encrypt your messages, we need to setup a few small things. You can either press accept immediately or adjust a few basic options. We also try to explain a few of the basics. You can skip those parts, but they might prove to be helpful!") + color: Nheko.colors.text + wrapMode: Text.Wrap + } + + Label { + Layout.margins: Nheko.paddingMedium + Layout.alignment: Qt.AlignLeft + Layout.columnSpan: 1 + Layout.maximumWidth: Math.floor(grid.width / 2) - Nheko.paddingMedium * 2 + text: "Store secrets online.\nYou have a few secrets to make all the encryption magic work. While you can keep them stored only locally, we recommend storing them encrypted on the server. Otherwise it will be painful to recover them. Only disable this if you are paranoid and like losing your data!" + color: Nheko.colors.text + wrapMode: Text.Wrap + } + + Item { + Layout.margins: Nheko.paddingMedium + Layout.preferredHeight: storeSecretsOnline.height + Layout.alignment: Qt.AlignLeft | Qt.AlignVCenter + Layout.fillWidth: true + + ToggleButton { + id: storeSecretsOnline + + checked: true + onClicked: console.log("Store secrets toggled: " + checked) + } + + } + + Label { + Layout.margins: Nheko.paddingMedium + Layout.alignment: Qt.AlignLeft + Layout.columnSpan: 1 + Layout.rowSpan: 2 + Layout.maximumWidth: Math.floor(grid.width / 2) - Nheko.paddingMedium * 2 + visible: storeSecretsOnline.checked + text: "Set an online backup password.\nWe recommend you DON'T set a password and instead only rely on the recovery key. You will get a recovery key in any case when storing the cross-signing secrets online, but passwords are usually not very random, so they are easier to attack than a completely random recovery key. If you choose to use a password, DON'T make it the same as your login password, otherwise your server can read all your encrypted messages. (You don't want that.)" + color: Nheko.colors.text + wrapMode: Text.Wrap + } + + Item { + Layout.margins: Nheko.paddingMedium + Layout.topMargin: Nheko.paddingLarge + Layout.preferredHeight: storeSecretsOnline.height + Layout.alignment: Qt.AlignLeft | Qt.AlignTop + Layout.rowSpan: usePassword.checked ? 1 : 2 + Layout.fillWidth: true + visible: storeSecretsOnline.checked + + ToggleButton { + id: usePassword + + checked: false + } + + } + + MatrixTextField { + id: passwordField + + Layout.margins: Nheko.paddingMedium + Layout.maximumWidth: Math.floor(grid.width / 2) - Nheko.paddingMedium * 2 + Layout.alignment: Qt.AlignLeft | Qt.AlignTop + Layout.columnSpan: 1 + Layout.fillWidth: true + visible: storeSecretsOnline.checked && usePassword.checked + echoMode: TextInput.Password + } + + Label { + Layout.margins: Nheko.paddingMedium + Layout.alignment: Qt.AlignLeft + Layout.columnSpan: 1 + Layout.maximumWidth: Math.floor(grid.width / 2) - Nheko.paddingMedium * 2 + text: "Use online key backup.\nStore the keys for your messages securely encrypted online. In general you do want this, because it protects your messages from becoming unreadable, if you log out by accident. It does however carry a small security risk, if you ever share your recovery key by accident. Currently this also has some other weaknesses, that might allow the server to insert new keys into your backup. The server will however never be able to read your messages." + color: Nheko.colors.text + wrapMode: Text.Wrap + } + + Item { + Layout.margins: Nheko.paddingMedium + Layout.preferredHeight: storeSecretsOnline.height + Layout.alignment: Qt.AlignLeft | Qt.AlignVCenter + Layout.fillWidth: true + + ToggleButton { + id: useOnlineKeyBackup + + checked: true + onClicked: console.log("Online key backup toggled: " + checked) + } + + } + + } + + } + + background: Rectangle { + color: Nheko.colors.window + border.color: Nheko.theme.separator + border.width: 1 + radius: Nheko.paddingSmall + } + + } + + Connections { + function onStatusChanged() { + console.log("STATUS CHANGED: " + SelfVerificationStatus.status); + if (SelfVerificationStatus.status == SelfVerificationStatus.NoMasterKey) + bootstrapCrosssigning.open(); + + } + + function onShowRecoveryKey(key) { + showRecoverKeyDialog.recoveryKey = key; + showRecoverKeyDialog.open(); + } + + function onSetupCompleted() { + successDialog.open(); + } + + function onSetupFailed(m) { + failureDialog.errorMessage = m; + failureDialog.open(); + } + + target: SelfVerificationStatus + } + +} diff --git a/resources/qml/delegates/Reply.qml b/resources/qml/delegates/Reply.qml index 6c0a6da4..60154837 100644 --- a/resources/qml/delegates/Reply.qml +++ b/resources/qml/delegates/Reply.qml @@ -2,12 +2,12 @@ // // SPDX-License-Identifier: GPL-3.0-or-later +import Qt.labs.platform 1.1 as Platform import QtQuick 2.12 import QtQuick.Controls 2.3 import QtQuick.Layouts 1.2 import QtQuick.Window 2.13 import im.nheko 1.0 -import Qt.labs.platform 1.1 as Platform Item { id: r @@ -66,14 +66,8 @@ Item { TapHandler { acceptedButtons: Qt.RightButton - onLongPressed: replyContextMenu.show( - reply.child.copyText, - reply.child.linkAt(eventPoint.position.x, eventPoint.position.y - userName_.implicitHeight) - ) - onSingleTapped: replyContextMenu.show( - reply.child.copyText, - reply.child.linkAt(eventPoint.position.x, eventPoint.position.y - userName_.implicitHeight) - ) + onLongPressed: replyContextMenu.show(reply.child.copyText, reply.child.linkAt(eventPoint.position.x, eventPoint.position.y - userName_.implicitHeight)) + onSingleTapped: replyContextMenu.show(reply.child.copyText, reply.child.linkAt(eventPoint.position.x, eventPoint.position.y - userName_.implicitHeight)) gesturePolicy: TapHandler.ReleaseWithinBounds } @@ -88,6 +82,7 @@ Item { onSingleTapped: chat.model.openUserProfile(userId) gesturePolicy: TapHandler.ReleaseWithinBounds } + } MessageDelegate { @@ -118,6 +113,7 @@ Item { width: parent.width isReply: true } + } Rectangle { diff --git a/resources/qml/delegates/TextMessage.qml b/resources/qml/delegates/TextMessage.qml index c37314fd..11ad3aeb 100644 --- a/resources/qml/delegates/TextMessage.qml +++ b/resources/qml/delegates/TextMessage.qml @@ -43,4 +43,5 @@ MatrixText { anchors.fill: parent cursorShape: Qt.PointingHandCursor } + } diff --git a/resources/qml/dialogs/InputDialog.qml b/resources/qml/dialogs/InputDialog.qml index 1efdbcde..12211c60 100644 --- a/resources/qml/dialogs/InputDialog.qml +++ b/resources/qml/dialogs/InputDialog.qml @@ -12,6 +12,7 @@ ApplicationWindow { id: inputDialog property alias prompt: promptLabel.text + property alias echoMode: statusInput.echoMode property var onAccepted: undefined modality: Qt.NonModal diff --git a/resources/qml/dialogs/RoomDirectory.qml b/resources/qml/dialogs/RoomDirectory.qml index 5c27fc26..67842720 100644 --- a/resources/qml/dialogs/RoomDirectory.qml +++ b/resources/qml/dialogs/RoomDirectory.qml @@ -195,9 +195,9 @@ ApplicationWindow { MatrixTextField { id: chooseServer + Layout.minimumWidth: 0.3 * header.width Layout.maximumWidth: 0.3 * header.width - padding: Nheko.paddingMedium color: Nheko.colors.text placeholderText: qsTr("Choose custom homeserver") diff --git a/resources/qml/dialogs/UserProfile.qml b/resources/qml/dialogs/UserProfile.qml index d5442382..c921278e 100644 --- a/resources/qml/dialogs/UserProfile.qml +++ b/resources/qml/dialogs/UserProfile.qml @@ -35,8 +35,18 @@ ApplicationWindow { onActivated: userProfileDialog.close() } - ListView { + id: devicelist + + Layout.fillHeight: true + Layout.fillWidth: true + clip: true + spacing: 8 + boundsBehavior: Flickable.StopAtBounds + model: profile.deviceList + anchors.fill: parent + anchors.margins: 10 + footerPositioning: ListView.OverlayFooter ScrollHelper { flickable: parent @@ -46,16 +56,17 @@ ApplicationWindow { header: ColumnLayout { id: contentL - width: devicelist.width + width: devicelist.width spacing: 10 Avatar { + id: displayAvatar + url: profile.avatarUrl.replace("mxc://", "image://MxcImage/") height: 130 width: 130 displayName: profile.displayName - id: displayAvatar userid: profile.userid Layout.alignment: Qt.AlignHCenter onClicked: TimelineManager.openImageOverlay(profile.avatarUrl, "") @@ -72,6 +83,7 @@ ApplicationWindow { image: ":/icons/icons/ui/edit.png" onClicked: profile.changeAvatar() } + } Spinner { @@ -163,19 +175,22 @@ ApplicationWindow { Layout.alignment: Qt.AlignHCenter } - RowLayout { visible: !profile.isGlobalUserProfile Layout.alignment: Qt.AlignHCenter spacing: Nheko.paddingSmall + MatrixText { id: displayRoomname - text: qsTr("Room: %1").arg(profile.room?profile.room.roomName:"") + + text: qsTr("Room: %1").arg(profile.room ? profile.room.roomName : "") ToolTip.text: qsTr("This is a room-specific profile. The user's name and avatar may be different from their global versions.") ToolTip.visible: ma.hovered + HoverHandler { id: ma } + } ImageButton { @@ -185,6 +200,7 @@ ApplicationWindow { ToolTip.text: qsTr("Open the global profile for this user.") onClicked: profile.openGlobalProfile() } + } Button { @@ -254,27 +270,18 @@ ApplicationWindow { hoverEnabled: true ToolTip.visible: hovered ToolTip.text: qsTr("Refresh device list.") - onClicked: profile.refreshDevices(); + onClicked: profile.refreshDevices() } } + } - id: devicelist - Layout.fillHeight: true - Layout.fillWidth: true - clip: true - spacing: 8 - boundsBehavior: Flickable.StopAtBounds - model: profile.deviceList - anchors.fill: parent - anchors.margins: 10 - - delegate: RowLayout { required property int verificationStatus required property string deviceId required property string deviceName + width: devicelist.width spacing: 4 @@ -304,7 +311,7 @@ ApplicationWindow { Layout.preferredHeight: 16 Layout.preferredWidth: 16 source: { - switch (verificationStatus){ + switch (verificationStatus) { case VerificationStatus.VERIFIED: return "image://colorimage/:/icons/icons/ui/lock.png?green"; case VerificationStatus.UNVERIFIED: @@ -331,17 +338,19 @@ ApplicationWindow { } } - footerPositioning: ListView.OverlayFooter + footer: DialogButtonBox { z: 2 width: devicelist.width alignment: Qt.AlignRight standardButtons: DialogButtonBox.Ok onAccepted: userProfileDialog.close() + background: Rectangle { anchors.fill: parent color: Nheko.colors.window } + } } diff --git a/resources/res.qrc b/resources/res.qrc index 3bd301f7..3e5e6cc8 100644 --- a/resources/res.qrc +++ b/resources/res.qrc @@ -138,6 +138,7 @@ qml/TopBar.qml qml/QuickSwitcher.qml qml/ForwardCompleter.qml + qml/SelfVerificationCheck.qml qml/TypingIndicator.qml qml/NotificationWarning.qml qml/emoji/EmojiPicker.qml diff --git a/src/Cache.cpp b/src/Cache.cpp index ee0ca0c2..ea3dd525 100644 --- a/src/Cache.cpp +++ b/src/Cache.cpp @@ -201,6 +201,18 @@ Cache::Cache(const QString &userId, QObject *parent) { setup(); connect(this, &Cache::userKeysUpdate, this, &Cache::updateUserKeys, Qt::QueuedConnection); + connect( + this, + &Cache::verificationStatusChanged, + this, + [this](const std::string &u) { + if (u == localUserId_.toStdString()) { + auto status = verificationStatus(u); + if (status.unverified_device_count || !status.user_verified) + emit selfUnverified(); + } + }, + Qt::QueuedConnection); } void diff --git a/src/Cache_p.h b/src/Cache_p.h index 52375d38..f7db77d4 100644 --- a/src/Cache_p.h +++ b/src/Cache_p.h @@ -310,6 +310,7 @@ signals: void removeNotification(const QString &room_id, const QString &event_id); void userKeysUpdate(const std::string &sync_token, const mtx::responses::QueryKeys &keyQuery); void verificationStatusChanged(const std::string &userid); + void selfUnverified(); void secretChanged(const std::string name); private: diff --git a/src/SelfVerificationStatus.cpp b/src/SelfVerificationStatus.cpp new file mode 100644 index 00000000..d75a2109 --- /dev/null +++ b/src/SelfVerificationStatus.cpp @@ -0,0 +1,249 @@ +// SPDX-FileCopyrightText: 2021 Nheko Contributors +// +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "SelfVerificationStatus.h" + +#include "Cache_p.h" +#include "Logging.h" +#include "MainWindow.h" +#include "MatrixClient.h" +#include "Olm.h" +#include "ui/UIA.h" + +#include + +SelfVerificationStatus::SelfVerificationStatus(QObject *o) + : QObject(o) +{ + connect(MainWindow::instance(), &MainWindow::reload, this, [this] { + connect(cache::client(), + &Cache::selfUnverified, + this, + &SelfVerificationStatus::invalidate, + Qt::UniqueConnection); + invalidate(); + }); +} + +void +SelfVerificationStatus::setupCrosssigning(bool useSSSS, QString password, bool useOnlineKeyBackup) +{ + nhlog::db()->info("Clicked setup crossigning"); + + auto xsign_keys = olm::client()->create_crosssigning_keys(); + + if (!xsign_keys) { + nhlog::crypto()->critical("Failed to setup cross-signing keys!"); + emit setupFailed(tr("Failed to create keys for cross-signing!")); + return; + } + + cache::client()->storeSecret(mtx::secret_storage::secrets::cross_signing_master, + xsign_keys->private_master_key); + cache::client()->storeSecret(mtx::secret_storage::secrets::cross_signing_self_signing, + xsign_keys->private_self_signing_key); + cache::client()->storeSecret(mtx::secret_storage::secrets::cross_signing_user_signing, + xsign_keys->private_user_signing_key); + + std::optional okb; + if (useOnlineKeyBackup) { + okb = olm::client()->create_online_key_backup(xsign_keys->private_master_key); + if (!okb) { + nhlog::crypto()->critical("Failed to setup online key backup!"); + emit setupFailed(tr("Failed to create keys for online key backup!")); + return; + } + + cache::client()->storeSecret( + mtx::secret_storage::secrets::megolm_backup_v1, + mtx::crypto::bin2base64(mtx::crypto::to_string(okb->privateKey))); + + http::client()->post_backup_version( + okb->backupVersion.algorithm, + okb->backupVersion.auth_data, + [](const mtx::responses::Version &v, mtx::http::RequestErr e) { + if (e) { + nhlog::net()->error("error setting up online key backup: {} {} {} {}", + e->parse_error, + e->status_code, + e->error_code, + e->matrix_error.error); + } else { + nhlog::crypto()->info("Set up online key backup: '{}'", v.version); + } + }); + } + + std::optional ssss; + if (useSSSS) { + ssss = olm::client()->create_ssss_key(password.toStdString()); + if (!ssss) { + nhlog::crypto()->critical("Failed to setup secure server side secret storage!"); + emit setupFailed(tr("Failed to create keys secure server side secret storage!")); + return; + } + + auto master = mtx::crypto::PkSigning::from_seed(xsign_keys->private_master_key); + nlohmann::json j = ssss->keyDescription; + j.erase("signatures"); + ssss->keyDescription + .signatures[http::client()->user_id().to_string()]["ed25519:" + master.public_key()] = + master.sign(j.dump()); + + http::client()->upload_secret_storage_key( + ssss->keyDescription.name, ssss->keyDescription, [](mtx::http::RequestErr) {}); + http::client()->set_secret_storage_default_key(ssss->keyDescription.name, + [](mtx::http::RequestErr) {}); + + auto uploadSecret = [ssss](const std::string &key_name, const std::string &secret) { + mtx::secret_storage::Secret s; + s.encrypted[ssss->keyDescription.name] = + mtx::crypto::encrypt(secret, ssss->privateKey, key_name); + http::client()->upload_secret_storage_secret( + key_name, s, [key_name](mtx::http::RequestErr) { + nhlog::crypto()->info("Uploaded secret: {}", key_name); + }); + }; + + uploadSecret(mtx::secret_storage::secrets::cross_signing_master, + xsign_keys->private_master_key); + uploadSecret(mtx::secret_storage::secrets::cross_signing_self_signing, + xsign_keys->private_self_signing_key); + uploadSecret(mtx::secret_storage::secrets::cross_signing_user_signing, + xsign_keys->private_user_signing_key); + + if (okb) + uploadSecret(mtx::secret_storage::secrets::megolm_backup_v1, + mtx::crypto::bin2base64(mtx::crypto::to_string(okb->privateKey))); + } + + mtx::requests::DeviceSigningUpload device_sign{}; + device_sign.master_key = xsign_keys->master_key; + device_sign.self_signing_key = xsign_keys->self_signing_key; + device_sign.user_signing_key = xsign_keys->user_signing_key; + http::client()->device_signing_upload( + device_sign, + UIA::instance()->genericHandler(tr("Encryption Setup")), + [this, ssss, xsign_keys](mtx::http::RequestErr e) { + if (e) { + nhlog::crypto()->critical("Failed to upload cross signing keys: {}", + e->matrix_error.error); + + emit setupFailed(tr("Encryption setup failed: %1") + .arg(QString::fromStdString(e->matrix_error.error))); + return; + } + nhlog::crypto()->info("Crosssigning keys uploaded!"); + + auto deviceKeys = cache::client()->userKeys(http::client()->user_id().to_string()); + if (deviceKeys) { + auto myKey = deviceKeys->device_keys.at(http::client()->device_id()); + if (myKey.user_id == http::client()->user_id().to_string() && + myKey.device_id == http::client()->device_id() && + myKey.keys["ed25519:" + http::client()->device_id()] == + olm::client()->identity_keys().ed25519 && + myKey.keys["curve25519:" + http::client()->device_id()] == + olm::client()->identity_keys().curve25519) { + json j = myKey; + j.erase("signatures"); + j.erase("unsigned"); + + auto ssk = + mtx::crypto::PkSigning::from_seed(xsign_keys->private_self_signing_key); + myKey.signatures[http::client()->user_id().to_string()] + ["ed25519:" + ssk.public_key()] = ssk.sign(j.dump()); + mtx::requests::KeySignaturesUpload req; + req.signatures[http::client()->user_id().to_string()] + [http::client()->device_id()] = myKey; + + http::client()->keys_signatures_upload( + req, + [](const mtx::responses::KeySignaturesUpload &res, mtx::http::RequestErr err) { + if (err) { + nhlog::net()->error("failed to upload signatures: {},{}", + mtx::errors::to_string(err->matrix_error.errcode), + static_cast(err->status_code)); + } + + for (const auto &[user_id, tmp] : res.errors) + for (const auto &[key_id, e] : tmp) + nhlog::net()->error("signature error for user {} and key " + "id {}: {}, {}", + user_id, + key_id, + mtx::errors::to_string(e.errcode), + e.error); + }); + } + } + + if (ssss) { + auto k = QString::fromStdString(mtx::crypto::key_to_recoverykey(ssss->privateKey)); + + QString r; + for (int i = 0; i < k.size(); i += 4) + r += k.mid(i, 4) + " "; + + emit showRecoveryKey(r.trimmed()); + } else { + emit setupCompleted(); + } + }); +} + +void +SelfVerificationStatus::verifyMasterKey() +{ + nhlog::db()->info("Clicked verify master key"); +} + +void +SelfVerificationStatus::verifyUnverifiedDevices() +{ + nhlog::db()->info("Clicked verify unverified devices"); +} + +void +SelfVerificationStatus::invalidate() +{ + nhlog::db()->info("Invalidating self verification status"); + auto keys = cache::client()->userKeys(http::client()->user_id().to_string()); + if (!keys) { + cache::client()->query_keys(http::client()->user_id().to_string(), + [](const UserKeyCache &, mtx::http::RequestErr) {}); + return; + } + + if (keys->master_keys.keys.empty()) { + if (status_ != SelfVerificationStatus::NoMasterKey) { + this->status_ = SelfVerificationStatus::NoMasterKey; + emit statusChanged(); + } + return; + } + + auto verifStatus = cache::client()->verificationStatus(http::client()->user_id().to_string()); + + if (!verifStatus.user_verified) { + if (status_ != SelfVerificationStatus::UnverifiedMasterKey) { + this->status_ = SelfVerificationStatus::UnverifiedMasterKey; + emit statusChanged(); + } + return; + } + + if (verifStatus.unverified_device_count > 0) { + if (status_ != SelfVerificationStatus::UnverifiedDevices) { + this->status_ = SelfVerificationStatus::UnverifiedDevices; + emit statusChanged(); + } + return; + } + + if (status_ != SelfVerificationStatus::AllVerified) { + this->status_ = SelfVerificationStatus::AllVerified; + emit statusChanged(); + return; + } +} diff --git a/src/SelfVerificationStatus.h b/src/SelfVerificationStatus.h new file mode 100644 index 00000000..8cb54df6 --- /dev/null +++ b/src/SelfVerificationStatus.h @@ -0,0 +1,43 @@ +// SPDX-FileCopyrightText: 2021 Nheko Contributors +// +// SPDX-License-Identifier: GPL-3.0-or-later + +#pragma once + +#include + +class SelfVerificationStatus : public QObject +{ + Q_OBJECT + + Q_PROPERTY(Status status READ status NOTIFY statusChanged) + +public: + SelfVerificationStatus(QObject *o = nullptr); + enum Status + { + AllVerified, + NoMasterKey, + UnverifiedMasterKey, + UnverifiedDevices, + }; + Q_ENUM(Status) + + Q_INVOKABLE void setupCrosssigning(bool useSSSS, QString password, bool useOnlineKeyBackup); + Q_INVOKABLE void verifyMasterKey(); + Q_INVOKABLE void verifyUnverifiedDevices(); + + Status status() const { return status_; } + +signals: + void statusChanged(); + void setupCompleted(); + void showRecoveryKey(QString key); + void setupFailed(QString message); + +public slots: + void invalidate(); + +private: + Status status_ = AllVerified; +}; diff --git a/src/timeline/TimelineViewManager.cpp b/src/timeline/TimelineViewManager.cpp index 8a33dc2b..df8210d3 100644 --- a/src/timeline/TimelineViewManager.cpp +++ b/src/timeline/TimelineViewManager.cpp @@ -29,6 +29,7 @@ #include "ReadReceiptsModel.h" #include "RoomDirectoryModel.h" #include "RoomsModel.h" +#include "SelfVerificationStatus.h" #include "SingleImagePackModel.h" #include "UserSettingsPage.h" #include "UsersModel.h" @@ -40,6 +41,7 @@ #include "ui/NhekoCursorShape.h" #include "ui/NhekoDropArea.h" #include "ui/NhekoGlobalObject.h" +#include "ui/UIA.h" Q_DECLARE_METATYPE(mtx::events::collections::TimelineEvents) Q_DECLARE_METATYPE(std::vector) @@ -212,18 +214,9 @@ TimelineViewManager::TimelineViewManager(CallManager *callManager, ChatPage *par "ReadReceiptsProxy needs to be instantiated on the C++ side"); static auto self = this; - qmlRegisterSingletonType( - "im.nheko", 1, 0, "MainWindow", [](QQmlEngine *, QJSEngine *) -> QObject * { - auto ptr = MainWindow::instance(); - QQmlEngine::setObjectOwnership(ptr, QQmlEngine::CppOwnership); - return ptr; - }); - qmlRegisterSingletonType( - "im.nheko", 1, 0, "TimelineManager", [](QQmlEngine *, QJSEngine *) -> QObject * { - auto ptr = self; - QQmlEngine::setObjectOwnership(ptr, QQmlEngine::CppOwnership); - return ptr; - }); + qmlRegisterSingletonInstance("im.nheko", 1, 0, "MainWindow", MainWindow::instance()); + qmlRegisterSingletonInstance("im.nheko", 1, 0, "TimelineManager", self); + qmlRegisterSingletonInstance("im.nheko", 1, 0, "UIA", UIA::instance()); qmlRegisterSingletonType( "im.nheko", 1, 0, "Rooms", [](QQmlEngine *, QJSEngine *) -> QObject * { auto ptr = new FilteredRoomlistModel(self->rooms_); @@ -238,24 +231,11 @@ TimelineViewManager::TimelineViewManager(CallManager *callManager, ChatPage *par &FilteredRoomlistModel::updateHiddenTagsAndSpaces); return ptr; }); - qmlRegisterSingletonType( - "im.nheko", 1, 0, "Communities", [](QQmlEngine *, QJSEngine *) -> QObject * { - auto ptr = self->communities_; - QQmlEngine::setObjectOwnership(ptr, QQmlEngine::CppOwnership); - return ptr; - }); - qmlRegisterSingletonType( - "im.nheko", 1, 0, "Settings", [](QQmlEngine *, QJSEngine *) -> QObject * { - auto ptr = ChatPage::instance()->userSettings().data(); - QQmlEngine::setObjectOwnership(ptr, QQmlEngine::CppOwnership); - return ptr; - }); - qmlRegisterSingletonType( - "im.nheko", 1, 0, "CallManager", [](QQmlEngine *, QJSEngine *) -> QObject * { - auto ptr = ChatPage::instance()->callManager(); - QQmlEngine::setObjectOwnership(ptr, QQmlEngine::CppOwnership); - return ptr; - }); + qmlRegisterSingletonInstance("im.nheko", 1, 0, "Communities", self->communities_); + qmlRegisterSingletonInstance( + "im.nheko", 1, 0, "Settings", ChatPage::instance()->userSettings().data()); + qmlRegisterSingletonInstance( + "im.nheko", 1, 0, "CallManager", ChatPage::instance()->callManager()); qmlRegisterSingletonType( "im.nheko", 1, 0, "Clipboard", [](QQmlEngine *, QJSEngine *) -> QObject * { return new Clipboard(); @@ -264,6 +244,10 @@ TimelineViewManager::TimelineViewManager(CallManager *callManager, ChatPage *par "im.nheko", 1, 0, "Nheko", [](QQmlEngine *, QJSEngine *) -> QObject * { return new Nheko(); }); + qmlRegisterSingletonType( + "im.nheko", 1, 0, "SelfVerificationStatus", [](QQmlEngine *, QJSEngine *) -> QObject * { + return new SelfVerificationStatus(); + }); qRegisterMetaType(); qRegisterMetaType>(); diff --git a/src/ui/UIA.cpp b/src/ui/UIA.cpp new file mode 100644 index 00000000..29161382 --- /dev/null +++ b/src/ui/UIA.cpp @@ -0,0 +1,136 @@ +// SPDX-FileCopyrightText: 2021 Nheko Contributors +// +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "UIA.h" + +#include + +#include +#include + +#include "Logging.h" +#include "MainWindow.h" +#include "dialogs/FallbackAuth.h" +#include "dialogs/ReCaptcha.h" + +UIA * +UIA::instance() +{ + static UIA uia; + return &uia; +} + +mtx::http::UIAHandler +UIA::genericHandler(QString context) +{ + return mtx::http::UIAHandler([this, context](const mtx::http::UIAHandler &h, + const mtx::user_interactive::Unauthorized &u) { + QTimer::singleShot(0, this, [this, h, u, context]() { + this->currentHandler = h; + this->currentStatus = u; + this->title_ = context; + emit titleChanged(); + + std::vector flows = u.flows; + + nhlog::ui()->info("Completed stages: {}", u.completed.size()); + + if (!u.completed.empty()) { + // Get rid of all flows which don't start with the sequence of + // stages that have already been completed. + flows.erase(std::remove_if(flows.begin(), + flows.end(), + [completed_stages = u.completed](auto flow) { + if (completed_stages.size() > flow.stages.size()) + return true; + for (size_t f = 0; f < completed_stages.size(); f++) + if (completed_stages[f] != flow.stages[f]) + return true; + return false; + }), + flows.end()); + } + + if (flows.empty()) { + nhlog::ui()->error("No available registration flows!"); + return; + } + + auto current_stage = flows.front().stages.at(u.completed.size()); + + if (current_stage == mtx::user_interactive::auth_types::password) { + emit password(); + } else if (current_stage == mtx::user_interactive::auth_types::recaptcha) { + auto captchaDialog = + new dialogs::ReCaptcha(QString::fromStdString(u.session), MainWindow::instance()); + captchaDialog->setWindowTitle(context); + + connect( + captchaDialog, &dialogs::ReCaptcha::confirmation, this, [captchaDialog, h, u]() { + captchaDialog->close(); + captchaDialog->deleteLater(); + h.next(mtx::user_interactive::Auth{u.session, + mtx::user_interactive::auth::Fallback{}}); + }); + + // connect( + // captchaDialog, &dialogs::ReCaptcha::cancel, this, &RegisterPage::errorOccurred); + + QTimer::singleShot(0, this, [captchaDialog]() { captchaDialog->show(); }); + + } else if (current_stage == mtx::user_interactive::auth_types::dummy) { + h.next( + mtx::user_interactive::Auth{u.session, mtx::user_interactive::auth::Dummy{}}); + + } else if (current_stage == mtx::user_interactive::auth_types::registration_token) { + bool ok; + QString token = + QInputDialog::getText(MainWindow::instance(), + context, + tr("Please enter a valid registration token."), + QLineEdit::Normal, + QString(), + &ok); + + if (ok) { + h.next(mtx::user_interactive::Auth{ + u.session, + mtx::user_interactive::auth::RegistrationToken{token.toStdString()}}); + } else { + // emit errorOccurred(); + } + } else { + // use fallback + auto dialog = new dialogs::FallbackAuth(QString::fromStdString(current_stage), + QString::fromStdString(u.session), + MainWindow::instance()); + dialog->setWindowTitle(context); + + connect(dialog, &dialogs::FallbackAuth::confirmation, this, [h, u, dialog]() { + dialog->close(); + dialog->deleteLater(); + h.next(mtx::user_interactive::Auth{u.session, + mtx::user_interactive::auth::Fallback{}}); + }); + + // connect(dialog, &dialogs::FallbackAuth::cancel, this, + // &RegisterPage::errorOccurred); + + dialog->show(); + } + }); + }); +} + +void +UIA::continuePassword(QString password) +{ + mtx::user_interactive::auth::Password p{}; + p.identifier_type = mtx::user_interactive::auth::Password::UserId; + p.password = password.toStdString(); + p.identifier_user = http::client()->user_id().to_string(); + + if (currentHandler) + currentHandler->next(mtx::user_interactive::Auth{currentStatus.session, p}); +} diff --git a/src/ui/UIA.h b/src/ui/UIA.h new file mode 100644 index 00000000..fb047451 --- /dev/null +++ b/src/ui/UIA.h @@ -0,0 +1,40 @@ +// SPDX-FileCopyrightText: 2021 Nheko Contributors +// +// SPDX-License-Identifier: GPL-3.0-or-later + +#pragma once + +#include + +#include + +class UIA : public QObject +{ + Q_OBJECT + + Q_PROPERTY(QString title READ title NOTIFY titleChanged) + +public: + static UIA *instance(); + + UIA(QObject *parent = nullptr) + : QObject(parent) + {} + + mtx::http::UIAHandler genericHandler(QString context); + + QString title() const { return title_; } + +public slots: + void continuePassword(QString password); + +signals: + void password(); + + void titleChanged(); + +private: + std::optional currentHandler; + mtx::user_interactive::Unauthorized currentStatus; + QString title_; +}; From 2f7ce486667cff5492e0edb4dd392c188a59d79e Mon Sep 17 00:00:00 2001 From: Loren Burkholder Date: Sat, 9 Oct 2021 16:48:30 -0400 Subject: [PATCH 189/232] make lint --- src/ui/NhekoGlobalObject.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ui/NhekoGlobalObject.cpp b/src/ui/NhekoGlobalObject.cpp index 1a20cbc2..d687a308 100644 --- a/src/ui/NhekoGlobalObject.cpp +++ b/src/ui/NhekoGlobalObject.cpp @@ -13,8 +13,8 @@ #include "Logging.h" #include "MainWindow.h" #include "UserSettingsPage.h" -#include "WebRTCSession.h" #include "Utils.h" +#include "WebRTCSession.h" Nheko::Nheko() { From e9ed12e27bcbc4f4a256e8e3840c1022fd14872e Mon Sep 17 00:00:00 2001 From: Loren Burkholder Date: Fri, 24 Sep 2021 21:32:06 -0400 Subject: [PATCH 190/232] QML the join room dialog --- CMakeLists.txt | 2 - resources/qml/Root.qml | 12 ++++ resources/qml/dialogs/JoinRoomDialog.qml | 64 +++++++++++++++++++++ resources/res.qrc | 23 ++++---- src/MainWindow.cpp | 13 ----- src/MainWindow.h | 2 +- src/dialogs/JoinRoom.cpp | 73 ------------------------ src/dialogs/JoinRoom.h | 36 ------------ src/ui/NhekoGlobalObject.cpp | 8 +-- src/ui/NhekoGlobalObject.h | 3 +- 10 files changed, 92 insertions(+), 144 deletions(-) create mode 100644 resources/qml/dialogs/JoinRoomDialog.qml delete mode 100644 src/dialogs/JoinRoom.cpp delete mode 100644 src/dialogs/JoinRoom.h diff --git a/CMakeLists.txt b/CMakeLists.txt index a3b344b8..761e42be 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -281,7 +281,6 @@ set(SRC_FILES src/dialogs/CreateRoom.cpp src/dialogs/FallbackAuth.cpp src/dialogs/ImageOverlay.cpp - src/dialogs/JoinRoom.cpp src/dialogs/LeaveRoom.cpp src/dialogs/Logout.cpp src/dialogs/PreviewUploadOverlay.cpp @@ -496,7 +495,6 @@ qt5_wrap_cpp(MOC_HEADERS src/dialogs/CreateRoom.h src/dialogs/FallbackAuth.h src/dialogs/ImageOverlay.h - src/dialogs/JoinRoom.h src/dialogs/LeaveRoom.h src/dialogs/Logout.h src/dialogs/PreviewUploadOverlay.h diff --git a/resources/qml/Root.qml b/resources/qml/Root.qml index 29da45eb..2c4dac0d 100644 --- a/resources/qml/Root.qml +++ b/resources/qml/Root.qml @@ -118,6 +118,13 @@ Page { } } + Component { + id: joinRoomDialog + + JoinRoomDialog { + } + } + Shortcut { sequence: "Ctrl+K" onActivated: { @@ -148,6 +155,11 @@ Page { dialog.open(); } + function onOpenJoinRoomDialog() { + var dialog = joinRoomDialog.createObject(timelineRoot); + dialog.show(); + } + target: Nheko } diff --git a/resources/qml/dialogs/JoinRoomDialog.qml b/resources/qml/dialogs/JoinRoomDialog.qml new file mode 100644 index 00000000..25400e40 --- /dev/null +++ b/resources/qml/dialogs/JoinRoomDialog.qml @@ -0,0 +1,64 @@ +// SPDX-FileCopyrightText: 2021 Nheko Contributors +// +// SPDX-License-Identifier: GPL-3.0-or-later + +import ".." +import QtQuick 2.12 +import QtQuick.Controls 2.5 +import QtQuick.Layouts 1.3 +import im.nheko 1.0 + +ApplicationWindow { + id: joinRoomRoot + + title: qsTr("Join room") + modality: Qt.WindowModal + flags: Qt.Dialog | Qt.WindowCloseButtonHint | Qt.WindowTitleHint + palette: Nheko.colors + color: Nheko.colors.window + Component.onCompleted: Nheko.reparent(joinRoomRoot) + width: 350 + height: fontMetrics.lineSpacing * 7 + + ColumnLayout { + spacing: Nheko.paddingMedium + anchors.margins: Nheko.paddingMedium + anchors.fill: parent + + Label { + id: promptLabel + + text: qsTr("Room ID or alias") + color: Nheko.colors.text + } + + MatrixTextField { + id: input + + Layout.fillWidth: true + } + + } + + footer: DialogButtonBox { + onAccepted: { + Nheko.joinRoom(input.text); + joinRoomRoot.close(); + } + onRejected: { + joinRoomRoot.close(); + } + + Button { + text: "Join" + enabled: input.text.match("#.+?:.{3,}") + DialogButtonBox.buttonRole: DialogButtonBox.AcceptRole + } + + Button { + text: "Cancel" + DialogButtonBox.buttonRole: DialogButtonBox.RejectRole + } + } + +} diff --git a/resources/res.qrc b/resources/res.qrc index c18a6109..e017b789 100644 --- a/resources/res.qrc +++ b/resources/res.qrc @@ -158,10 +158,19 @@ qml/device-verification/EmojiVerification.qml qml/device-verification/NewVerificationRequest.qml qml/device-verification/Failed.qml - qml/device-verification/Success.qml - qml/dialogs/InputDialog.qml + qml/device-verification/Success.qml qml/dialogs/ImagePackSettingsDialog.qml qml/dialogs/ImagePackEditorDialog.qml + qml/dialogs/InputDialog.qml + qml/dialogs/InviteDialog.qml + qml/dialogs/JoinRoomDialog.qml + qml/dialogs/LogoutDialog.qml + qml/dialogs/RawMessageDialog.qml + qml/dialogs/ReadReceipts.qml + qml/dialogs/RoomDirectory.qml + qml/dialogs/RoomMembers.qml + qml/dialogs/RoomSettings.qml + qml/dialogs/UserProfile.qml qml/ui/Ripple.qml qml/ui/Spinner.qml qml/ui/animations/BlinkAnimation.qml @@ -176,15 +185,7 @@ qml/components/AdaptiveLayout.qml qml/components/AdaptiveLayoutElement.qml qml/components/AvatarListTile.qml - qml/components/FlatButton.qml - qml/dialogs/InviteDialog.qml - qml/dialogs/RawMessageDialog.qml - qml/dialogs/ReadReceipts.qml - qml/dialogs/RoomDirectory.qml - qml/dialogs/RoomMembers.qml - qml/dialogs/RoomSettings.qml - qml/dialogs/UserProfile.qml - qml/dialogs/LogoutDialog.qml + qml/components/FlatButton.qml media/ring.ogg diff --git a/src/MainWindow.cpp b/src/MainWindow.cpp index 0978fc25..777b5b22 100644 --- a/src/MainWindow.cpp +++ b/src/MainWindow.cpp @@ -33,7 +33,6 @@ #include "ui/SnackBar.h" #include "dialogs/CreateRoom.h" -#include "dialogs/JoinRoom.h" #include "dialogs/LeaveRoom.h" MainWindow *MainWindow::instance_ = nullptr; @@ -324,18 +323,6 @@ MainWindow::showOverlayProgressBar() showSolidOverlayModal(spinner_); } -void -MainWindow::openJoinRoomDialog(std::function callback) -{ - auto dialog = new dialogs::JoinRoom(this); - connect(dialog, &dialogs::JoinRoom::joinRoom, this, [callback](const QString &room) { - if (!room.isEmpty()) - callback(room); - }); - - showDialog(dialog); -} - void MainWindow::openCreateRoomDialog( std::function callback) diff --git a/src/MainWindow.h b/src/MainWindow.h index a3c3c767..01575a19 100644 --- a/src/MainWindow.h +++ b/src/MainWindow.h @@ -37,7 +37,6 @@ struct CreateRoom; namespace dialogs { class CreateRoom; class InviteUsers; -class JoinRoom; class LeaveRoom; class Logout; class MemberList; @@ -64,6 +63,7 @@ public: void openCreateRoomDialog( std::function callback); void openJoinRoomDialog(std::function callback); + void openLogoutDialog(); void hideOverlay(); void showSolidOverlayModal(QWidget *content, QFlags flags = Qt::AlignCenter); diff --git a/src/dialogs/JoinRoom.cpp b/src/dialogs/JoinRoom.cpp deleted file mode 100644 index 76baf857..00000000 --- a/src/dialogs/JoinRoom.cpp +++ /dev/null @@ -1,73 +0,0 @@ -// SPDX-FileCopyrightText: 2021 Nheko Contributors -// -// SPDX-License-Identifier: GPL-3.0-or-later - -#include -#include -#include - -#include "dialogs/JoinRoom.h" - -#include "Config.h" -#include "ui/TextField.h" - -using namespace dialogs; - -JoinRoom::JoinRoom(QWidget *parent) - : QFrame(parent) -{ - setAutoFillBackground(true); - setWindowFlags(Qt::Tool | Qt::WindowStaysOnTopHint); - setWindowModality(Qt::WindowModal); - setAttribute(Qt::WA_DeleteOnClose, true); - - setMinimumWidth(conf::modals::MIN_WIDGET_WIDTH); - setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Maximum); - - auto layout = new QVBoxLayout(this); - layout->setSpacing(conf::modals::WIDGET_SPACING); - layout->setMargin(conf::modals::WIDGET_MARGIN); - - auto buttonLayout = new QHBoxLayout(); - buttonLayout->setSpacing(15); - - confirmBtn_ = new QPushButton(tr("Join"), this); - confirmBtn_->setDefault(true); - cancelBtn_ = new QPushButton(tr("Cancel"), this); - - buttonLayout->addStretch(1); - buttonLayout->addWidget(cancelBtn_); - buttonLayout->addWidget(confirmBtn_); - - roomInput_ = new TextField(this); - roomInput_->setLabel(tr("Room ID or alias")); - - layout->addWidget(roomInput_); - layout->addLayout(buttonLayout); - layout->addStretch(1); - - connect(roomInput_, &QLineEdit::returnPressed, this, &JoinRoom::handleInput); - connect(confirmBtn_, &QPushButton::clicked, this, &JoinRoom::handleInput); - connect(cancelBtn_, &QPushButton::clicked, this, &JoinRoom::close); -} - -void -JoinRoom::handleInput() -{ - if (roomInput_->text().isEmpty()) - return; - - // TODO: input validation with error messages. - emit joinRoom(roomInput_->text()); - roomInput_->clear(); - - emit close(); -} - -void -JoinRoom::showEvent(QShowEvent *event) -{ - roomInput_->setFocus(); - - QFrame::showEvent(event); -} diff --git a/src/dialogs/JoinRoom.h b/src/dialogs/JoinRoom.h deleted file mode 100644 index 11c54d7c..00000000 --- a/src/dialogs/JoinRoom.h +++ /dev/null @@ -1,36 +0,0 @@ -// SPDX-FileCopyrightText: 2021 Nheko Contributors -// -// SPDX-License-Identifier: GPL-3.0-or-later - -#pragma once - -#include - -class QPushButton; -class TextField; - -namespace dialogs { - -class JoinRoom : public QFrame -{ - Q_OBJECT -public: - JoinRoom(QWidget *parent = nullptr); - -signals: - void joinRoom(const QString &room); - -protected: - void showEvent(QShowEvent *event) override; - -private slots: - void handleInput(); - -private: - QPushButton *confirmBtn_; - QPushButton *cancelBtn_; - - TextField *roomInput_; -}; - -} // dialogs diff --git a/src/ui/NhekoGlobalObject.cpp b/src/ui/NhekoGlobalObject.cpp index d687a308..11fc5681 100644 --- a/src/ui/NhekoGlobalObject.cpp +++ b/src/ui/NhekoGlobalObject.cpp @@ -21,6 +21,7 @@ Nheko::Nheko() connect( UserSettings::instance().get(), &UserSettings::themeChanged, this, &Nheko::colorsChanged); connect(ChatPage::instance(), &ChatPage::contentLoaded, this, &Nheko::updateUserProfile); + connect(this, &Nheko::joinRoom, ChatPage::instance(), &ChatPage::joinRoom); } void @@ -96,13 +97,6 @@ Nheko::openCreateRoomDialog() const [](const mtx::requests::CreateRoom &req) { ChatPage::instance()->createRoom(req); }); } -void -Nheko::openJoinRoomDialog() const -{ - MainWindow::instance()->openJoinRoomDialog( - [](const QString &room_id) { ChatPage::instance()->joinRoom(room_id); }); -} - void Nheko::reparent(QWindow *win) const { diff --git a/src/ui/NhekoGlobalObject.h b/src/ui/NhekoGlobalObject.h index 64aad941..c70813c5 100644 --- a/src/ui/NhekoGlobalObject.h +++ b/src/ui/NhekoGlobalObject.h @@ -50,7 +50,6 @@ public: Q_INVOKABLE void showUserSettingsPage() const; Q_INVOKABLE void logout() const; Q_INVOKABLE void openCreateRoomDialog() const; - Q_INVOKABLE void openJoinRoomDialog() const; Q_INVOKABLE void reparent(QWindow *win) const; public slots: @@ -61,6 +60,8 @@ signals: void profileChanged(); void openLogoutDialog(); + void openJoinRoomDialog(); + void joinRoom(QString roomId); private: QScopedPointer currentUser_; From 484845c130d1277294ac9ae3c3c9ebeefd437553 Mon Sep 17 00:00:00 2001 From: Loren Burkholder Date: Tue, 5 Oct 2021 19:53:39 -0400 Subject: [PATCH 191/232] Add handy keyboard shortcuts --- resources/qml/dialogs/JoinRoomDialog.qml | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/resources/qml/dialogs/JoinRoomDialog.qml b/resources/qml/dialogs/JoinRoomDialog.qml index 25400e40..2554215d 100644 --- a/resources/qml/dialogs/JoinRoomDialog.qml +++ b/resources/qml/dialogs/JoinRoomDialog.qml @@ -20,6 +20,19 @@ ApplicationWindow { width: 350 height: fontMetrics.lineSpacing * 7 + Shortcut { + sequence: "Return" + onActivated: { + if (input.text.match("#.+?:.{3,}")) + dbb.accepted(); + } + } + + Shortcut { + sequence: StandardKey.Cancel + onActivated: dbb.rejected() + } + ColumnLayout { spacing: Nheko.paddingMedium anchors.margins: Nheko.paddingMedium @@ -41,6 +54,8 @@ ApplicationWindow { } footer: DialogButtonBox { + id: dbb + onAccepted: { Nheko.joinRoom(input.text); joinRoomRoot.close(); From 6a327e0db39772fae090354c7e198cec5dc9ee78 Mon Sep 17 00:00:00 2001 From: Loren Burkholder Date: Tue, 5 Oct 2021 19:55:53 -0400 Subject: [PATCH 192/232] Auto-focus the input --- resources/qml/dialogs/JoinRoomDialog.qml | 1 + 1 file changed, 1 insertion(+) diff --git a/resources/qml/dialogs/JoinRoomDialog.qml b/resources/qml/dialogs/JoinRoomDialog.qml index 2554215d..8f53bfcb 100644 --- a/resources/qml/dialogs/JoinRoomDialog.qml +++ b/resources/qml/dialogs/JoinRoomDialog.qml @@ -48,6 +48,7 @@ ApplicationWindow { MatrixTextField { id: input + focus: true Layout.fillWidth: true } From 0841abead33978d098e146a6f33b2a9463ebc7ea Mon Sep 17 00:00:00 2001 From: Loren Burkholder Date: Sat, 9 Oct 2021 17:29:05 -0400 Subject: [PATCH 193/232] Use better close-on-Enter logic --- resources/qml/dialogs/JoinRoomDialog.qml | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/resources/qml/dialogs/JoinRoomDialog.qml b/resources/qml/dialogs/JoinRoomDialog.qml index 8f53bfcb..d3defa82 100644 --- a/resources/qml/dialogs/JoinRoomDialog.qml +++ b/resources/qml/dialogs/JoinRoomDialog.qml @@ -20,14 +20,6 @@ ApplicationWindow { width: 350 height: fontMetrics.lineSpacing * 7 - Shortcut { - sequence: "Return" - onActivated: { - if (input.text.match("#.+?:.{3,}")) - dbb.accepted(); - } - } - Shortcut { sequence: StandardKey.Cancel onActivated: dbb.rejected() @@ -50,6 +42,10 @@ ApplicationWindow { focus: true Layout.fillWidth: true + onAccepted: { + if (input.text.match("#.+?:.{3,}")) + dbb.accepted(); + } } } From cd39e015d44df3c7421849f44114fe7bf6985118 Mon Sep 17 00:00:00 2001 From: Loren Burkholder Date: Thu, 23 Sep 2021 18:08:11 -0400 Subject: [PATCH 194/232] Remove unimplemented function declaration --- src/MainWindow.h | 1 - 1 file changed, 1 deletion(-) diff --git a/src/MainWindow.h b/src/MainWindow.h index 01575a19..64606412 100644 --- a/src/MainWindow.h +++ b/src/MainWindow.h @@ -59,7 +59,6 @@ public: void saveCurrentWindowSize(); void openLeaveRoomDialog(const QString &room_id); - void openInviteUsersDialog(std::function callback); void openCreateRoomDialog( std::function callback); void openJoinRoomDialog(std::function callback); From 14488a8fe78f829dadf3899a1c50ab389d05dbe9 Mon Sep 17 00:00:00 2001 From: Loren Burkholder Date: Thu, 23 Sep 2021 21:18:48 -0400 Subject: [PATCH 195/232] Use the same leave room dialog everywhere This ports the leave room dialog to QML, everywhere. There are now no differences between the various leave dialogs. --- CMakeLists.txt | 2 - resources/qml/RoomList.qml | 12 +---- resources/qml/Root.qml | 14 ++++++ resources/qml/dialogs/LeaveRoomDialog.qml | 20 +++++++++ resources/res.qrc | 12 ++++- src/MainWindow.cpp | 12 ----- src/MainWindow.h | 2 - src/dialogs/LeaveRoom.cpp | 53 ----------------------- src/dialogs/LeaveRoom.h | 26 ----------- src/timeline/InputBar.cpp | 2 +- src/timeline/TimelineViewManager.cpp | 6 --- src/timeline/TimelineViewManager.h | 2 +- 12 files changed, 47 insertions(+), 116 deletions(-) create mode 100644 resources/qml/dialogs/LeaveRoomDialog.qml delete mode 100644 src/dialogs/LeaveRoom.cpp delete mode 100644 src/dialogs/LeaveRoom.h diff --git a/CMakeLists.txt b/CMakeLists.txt index dad34349..8ff3ca26 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -281,7 +281,6 @@ set(SRC_FILES src/dialogs/CreateRoom.cpp src/dialogs/FallbackAuth.cpp src/dialogs/ImageOverlay.cpp - src/dialogs/LeaveRoom.cpp src/dialogs/Logout.cpp src/dialogs/PreviewUploadOverlay.cpp src/dialogs/ReCaptcha.cpp @@ -497,7 +496,6 @@ qt5_wrap_cpp(MOC_HEADERS src/dialogs/CreateRoom.h src/dialogs/FallbackAuth.h src/dialogs/ImageOverlay.h - src/dialogs/LeaveRoom.h src/dialogs/Logout.h src/dialogs/PreviewUploadOverlay.h src/dialogs/ReCaptcha.h diff --git a/resources/qml/RoomList.qml b/resources/qml/RoomList.qml index addbf571..85087bc4 100644 --- a/resources/qml/RoomList.qml +++ b/resources/qml/RoomList.qml @@ -71,19 +71,9 @@ Page { } } - Platform.MessageDialog { - id: leaveRoomDialog - - title: qsTr("Leave Room") - text: qsTr("Are you sure you want to leave this room?") - modality: Qt.ApplicationModal - onAccepted: Rooms.leave(roomContextMenu.roomid) - buttons: Dialog.Ok | Dialog.Cancel - } - Platform.MenuItem { text: qsTr("Leave room") - onTriggered: leaveRoomDialog.open() + onTriggered: TimelineManager.openLeaveRoomDialog(roomContextMenu.roomid) } Platform.MenuSeparator { diff --git a/resources/qml/Root.qml b/resources/qml/Root.qml index c73d1f1d..18130469 100644 --- a/resources/qml/Root.qml +++ b/resources/qml/Root.qml @@ -125,6 +125,13 @@ Page { } } + Component { + id: leaveRoomComponent + + LeaveRoomDialog { + } + } + Shortcut { sequence: "Ctrl+K" onActivated: { @@ -209,6 +216,13 @@ Page { dialog.show(); } + function onOpenLeaveRoomDialog(roomid) { + var dialog = leaveRoomComponent.createObject(timelineRoot, { + "roomId": roomid + }); + dialog.open(); + } + target: TimelineManager } diff --git a/resources/qml/dialogs/LeaveRoomDialog.qml b/resources/qml/dialogs/LeaveRoomDialog.qml new file mode 100644 index 00000000..1341ad72 --- /dev/null +++ b/resources/qml/dialogs/LeaveRoomDialog.qml @@ -0,0 +1,20 @@ +// SPDX-FileCopyrightText: 2021 Nheko Contributors +// +// SPDX-License-Identifier: GPL-3.0-or-later + +import QtQuick 2.15 +import QtQuick.Controls 2.15 +import Qt.labs.platform 1.1 +import im.nheko 1.0 + +MessageDialog { + id: leaveRoomRoot + + required property string roomId + + title: qsTr("Leave room") + text: qsTr("Are you sure you want to leave?") + modality: Qt.ApplicationModal + buttons: Dialog.Ok | Dialog.Cancel + onAccepted: Rooms.leave(roomId) +} diff --git a/resources/res.qrc b/resources/res.qrc index 9303ab5f..ffbadd91 100644 --- a/resources/res.qrc +++ b/resources/res.qrc @@ -185,8 +185,16 @@ qml/voip/VideoCall.qml qml/components/AdaptiveLayout.qml qml/components/AdaptiveLayoutElement.qml - qml/components/AvatarListTile.qml - qml/components/FlatButton.qml + qml/components/AvatarListTile.qml + qml/components/FlatButton.qml + qml/dialogs/InviteDialog.qml + qml/dialogs/LeaveRoomDialog.qml + qml/dialogs/RawMessageDialog.qml + qml/dialogs/ReadReceipts.qml + qml/dialogs/RoomDirectory.qml + qml/dialogs/RoomMembers.qml + qml/dialogs/RoomSettings.qml + qml/dialogs/UserProfile.qml media/ring.ogg diff --git a/src/MainWindow.cpp b/src/MainWindow.cpp index 777b5b22..c8eb2d24 100644 --- a/src/MainWindow.cpp +++ b/src/MainWindow.cpp @@ -33,7 +33,6 @@ #include "ui/SnackBar.h" #include "dialogs/CreateRoom.h" -#include "dialogs/LeaveRoom.h" MainWindow *MainWindow::instance_ = nullptr; @@ -300,17 +299,6 @@ MainWindow::hasActiveUser() settings->contains(prefix + "auth/user_id"); } -void -MainWindow::openLeaveRoomDialog(const QString &room_id) -{ - auto dialog = new dialogs::LeaveRoom(this); - connect(dialog, &dialogs::LeaveRoom::leaving, this, [this, room_id]() { - chat_page_->leaveRoom(room_id); - }); - - showDialog(dialog); -} - void MainWindow::showOverlayProgressBar() { diff --git a/src/MainWindow.h b/src/MainWindow.h index 64606412..b9d2fe5f 100644 --- a/src/MainWindow.h +++ b/src/MainWindow.h @@ -37,7 +37,6 @@ struct CreateRoom; namespace dialogs { class CreateRoom; class InviteUsers; -class LeaveRoom; class Logout; class MemberList; class ReCaptcha; @@ -58,7 +57,6 @@ public: static MainWindow *instance() { return instance_; } void saveCurrentWindowSize(); - void openLeaveRoomDialog(const QString &room_id); void openCreateRoomDialog( std::function callback); void openJoinRoomDialog(std::function callback); diff --git a/src/dialogs/LeaveRoom.cpp b/src/dialogs/LeaveRoom.cpp deleted file mode 100644 index 9eb431da..00000000 --- a/src/dialogs/LeaveRoom.cpp +++ /dev/null @@ -1,53 +0,0 @@ -// SPDX-FileCopyrightText: 2021 Nheko Contributors -// -// SPDX-License-Identifier: GPL-3.0-or-later - -#include -#include -#include - -#include "dialogs/LeaveRoom.h" - -#include "Config.h" - -using namespace dialogs; - -LeaveRoom::LeaveRoom(QWidget *parent) - : QFrame(parent) -{ - setAutoFillBackground(true); - setWindowFlags(Qt::Tool | Qt::WindowStaysOnTopHint); - setWindowModality(Qt::WindowModal); - setAttribute(Qt::WA_DeleteOnClose, true); - - setMinimumWidth(conf::modals::MIN_WIDGET_WIDTH); - setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Maximum); - - auto layout = new QVBoxLayout(this); - layout->setSpacing(conf::modals::WIDGET_SPACING); - layout->setMargin(conf::modals::WIDGET_MARGIN); - - auto buttonLayout = new QHBoxLayout(); - buttonLayout->setSpacing(0); - buttonLayout->setMargin(0); - - confirmBtn_ = new QPushButton("Leave", this); - cancelBtn_ = new QPushButton(tr("Cancel"), this); - cancelBtn_->setDefault(true); - - buttonLayout->addStretch(1); - buttonLayout->setSpacing(15); - buttonLayout->addWidget(cancelBtn_); - buttonLayout->addWidget(confirmBtn_); - - auto label = new QLabel(tr("Are you sure you want to leave?"), this); - - layout->addWidget(label); - layout->addLayout(buttonLayout); - - connect(confirmBtn_, &QPushButton::clicked, this, [this]() { - emit leaving(); - emit close(); - }); - connect(cancelBtn_, &QPushButton::clicked, this, &LeaveRoom::close); -} diff --git a/src/dialogs/LeaveRoom.h b/src/dialogs/LeaveRoom.h deleted file mode 100644 index edf88282..00000000 --- a/src/dialogs/LeaveRoom.h +++ /dev/null @@ -1,26 +0,0 @@ -// SPDX-FileCopyrightText: 2021 Nheko Contributors -// -// SPDX-License-Identifier: GPL-3.0-or-later - -#pragma once - -#include - -class QPushButton; - -namespace dialogs { - -class LeaveRoom : public QFrame -{ - Q_OBJECT -public: - explicit LeaveRoom(QWidget *parent = nullptr); - -signals: - void leaving(); - -private: - QPushButton *confirmBtn_; - QPushButton *cancelBtn_; -}; -} // dialogs diff --git a/src/timeline/InputBar.cpp b/src/timeline/InputBar.cpp index f518248b..f33d1dfd 100644 --- a/src/timeline/InputBar.cpp +++ b/src/timeline/InputBar.cpp @@ -577,7 +577,7 @@ InputBar::command(QString command, QString args) } else if (command == "join") { ChatPage::instance()->joinRoom(args); } else if (command == "part" || command == "leave") { - MainWindow::instance()->openLeaveRoomDialog(room->roomId()); + ChatPage::instance()->timelineManager()->openLeaveRoomDialog(room->roomId()); } else if (command == "invite") { ChatPage::instance()->inviteUser(args.section(' ', 0, 0), args.section(' ', 1, -1)); } else if (command == "kick") { diff --git a/src/timeline/TimelineViewManager.cpp b/src/timeline/TimelineViewManager.cpp index df8210d3..a30a145d 100644 --- a/src/timeline/TimelineViewManager.cpp +++ b/src/timeline/TimelineViewManager.cpp @@ -475,12 +475,6 @@ TimelineViewManager::openImageOverlayInternal(QString eventId, QImage img) }); } -void -TimelineViewManager::openLeaveRoomDialog(QString roomid) const -{ - MainWindow::instance()->openLeaveRoomDialog(roomid); -} - void TimelineViewManager::verifyUser(QString userid) { diff --git a/src/timeline/TimelineViewManager.h b/src/timeline/TimelineViewManager.h index f7b01315..ab078aa7 100644 --- a/src/timeline/TimelineViewManager.h +++ b/src/timeline/TimelineViewManager.h @@ -73,7 +73,6 @@ public: Q_INVOKABLE void openGlobalUserProfile(QString userId); Q_INVOKABLE void focusMessageInput(); - Q_INVOKABLE void openLeaveRoomDialog(QString roomid) const; Q_INVOKABLE void removeVerificationFlow(DeviceVerificationFlow *flow); Q_INVOKABLE void fixImageRendering(QQuickTextDocument *t, QQuickItem *i); @@ -98,6 +97,7 @@ signals: void openInviteUsersDialog(InviteesModel *invitees); void openProfile(UserProfile *profile); void showImagePackSettings(ImagePackListModel *packlist); + void openLeaveRoomDialog(QString roomid); public slots: void updateReadReceipts(const QString &room_id, const std::vector &event_ids); From 0516892759e85e1d710f3eb1c5349139fbf9043a Mon Sep 17 00:00:00 2001 From: tastytea Date: Fri, 15 Oct 2021 16:51:20 +0200 Subject: [PATCH 196/232] Allow JPEG in image picker for stickers and emotes. The file ending list is translated, so all the translations are updated too. --- resources/langs/nheko_cs.ts | 2 +- resources/langs/nheko_de.ts | 4 ++-- resources/langs/nheko_el.ts | 2 +- resources/langs/nheko_en.ts | 4 ++-- resources/langs/nheko_eo.ts | 4 ++-- resources/langs/nheko_es.ts | 2 +- resources/langs/nheko_et.ts | 4 ++-- resources/langs/nheko_fi.ts | 4 ++-- resources/langs/nheko_fr.ts | 4 ++-- resources/langs/nheko_hu.ts | 2 +- resources/langs/nheko_id.ts | 4 ++-- resources/langs/nheko_it.ts | 2 +- resources/langs/nheko_ja.ts | 2 +- resources/langs/nheko_ml.ts | 4 ++-- resources/langs/nheko_nl.ts | 4 ++-- resources/langs/nheko_pl.ts | 4 ++-- resources/langs/nheko_pt_BR.ts | 2 +- resources/langs/nheko_pt_PT.ts | 4 ++-- resources/langs/nheko_ro.ts | 2 +- resources/langs/nheko_ru.ts | 2 +- resources/langs/nheko_si.ts | 2 +- resources/langs/nheko_sv.ts | 2 +- resources/langs/nheko_zh_CN.ts | 2 +- resources/qml/dialogs/ImagePackEditorDialog.qml | 2 +- 24 files changed, 35 insertions(+), 35 deletions(-) diff --git a/resources/langs/nheko_cs.ts b/resources/langs/nheko_cs.ts index 5b281e33..b0307278 100644 --- a/resources/langs/nheko_cs.ts +++ b/resources/langs/nheko_cs.ts @@ -618,7 +618,7 @@ - Stickers (*.png *.webp *.gif) + Stickers (*.png *.webp *.gif *.jpg *.jpeg) diff --git a/resources/langs/nheko_de.ts b/resources/langs/nheko_de.ts index cb169f2b..bf9068d2 100644 --- a/resources/langs/nheko_de.ts +++ b/resources/langs/nheko_de.ts @@ -618,8 +618,8 @@ - Stickers (*.png *.webp *.gif) - Sticker (*.png *.webp *.gif) + Stickers (*.png *.webp *.gif *.jpg *.jpeg) + Sticker (*.png *.webp *.gif *.jpg *.jpeg) diff --git a/resources/langs/nheko_el.ts b/resources/langs/nheko_el.ts index 3c21edbb..e74a53c0 100644 --- a/resources/langs/nheko_el.ts +++ b/resources/langs/nheko_el.ts @@ -618,7 +618,7 @@ - Stickers (*.png *.webp *.gif) + Stickers (*.png *.webp *.gif *.jpg *.jpeg) diff --git a/resources/langs/nheko_en.ts b/resources/langs/nheko_en.ts index 934c9e31..61264bb7 100644 --- a/resources/langs/nheko_en.ts +++ b/resources/langs/nheko_en.ts @@ -618,8 +618,8 @@ - Stickers (*.png *.webp *.gif) - Stickers (*.png *.webp *.gif) + Stickers (*.png *.webp *.gif *.jpg *.jpeg) + Stickers (*.png *.webp *.gif *.jpg *.jpeg) diff --git a/resources/langs/nheko_eo.ts b/resources/langs/nheko_eo.ts index 1dd14d54..021751ff 100644 --- a/resources/langs/nheko_eo.ts +++ b/resources/langs/nheko_eo.ts @@ -619,8 +619,8 @@ - Stickers (*.png *.webp *.gif) - Glumarkoj (*.png *.webp *.gif) + Stickers (*.png *.webp *.gif *.jpg *.jpeg) + Glumarkoj (*.png *.webp *.gif *.jpg *.jpeg) diff --git a/resources/langs/nheko_es.ts b/resources/langs/nheko_es.ts index 333eece3..80ccb676 100644 --- a/resources/langs/nheko_es.ts +++ b/resources/langs/nheko_es.ts @@ -618,7 +618,7 @@ - Stickers (*.png *.webp *.gif) + Stickers (*.png *.webp *.gif *.jpg *.jpeg) diff --git a/resources/langs/nheko_et.ts b/resources/langs/nheko_et.ts index bd772b55..7486656e 100644 --- a/resources/langs/nheko_et.ts +++ b/resources/langs/nheko_et.ts @@ -618,8 +618,8 @@ - Stickers (*.png *.webp *.gif) - Kleepsud (*.png *.webp *.gif) + Stickers (*.png *.webp *.gif *.jpg *.jpeg) + Kleepsud (*.png *.webp *.gif *.jpg *.jpeg) diff --git a/resources/langs/nheko_fi.ts b/resources/langs/nheko_fi.ts index 6e260ea7..c269fa36 100644 --- a/resources/langs/nheko_fi.ts +++ b/resources/langs/nheko_fi.ts @@ -618,8 +618,8 @@ - Stickers (*.png *.webp *.gif) - Tarrat (*.png *.webp *.gif) + Stickers (*.png *.webp *.gif *.jpg *.jpeg) + Tarrat (*.png *.webp *.gif *.jpg *.jpeg) diff --git a/resources/langs/nheko_fr.ts b/resources/langs/nheko_fr.ts index b882dc3d..2895fcef 100644 --- a/resources/langs/nheko_fr.ts +++ b/resources/langs/nheko_fr.ts @@ -618,8 +618,8 @@ - Stickers (*.png *.webp *.gif) - Autocollants (*.png *.webp *.gif) + Stickers (*.png *.webp *.gif *.jpg *.jpeg) + Autocollants (*.png *.webp *.gif *.jpg *.jpeg) diff --git a/resources/langs/nheko_hu.ts b/resources/langs/nheko_hu.ts index dd9392c7..a0cd1ab5 100644 --- a/resources/langs/nheko_hu.ts +++ b/resources/langs/nheko_hu.ts @@ -618,7 +618,7 @@ - Stickers (*.png *.webp *.gif) + Stickers (*.png *.webp *.gif *.jpg *.jpeg) diff --git a/resources/langs/nheko_id.ts b/resources/langs/nheko_id.ts index 37387cd7..c82111c7 100644 --- a/resources/langs/nheko_id.ts +++ b/resources/langs/nheko_id.ts @@ -618,8 +618,8 @@ - Stickers (*.png *.webp *.gif) - Stiker (*.png *.webp *.gif) + Stickers (*.png *.webp *.gif *.jpg *.jpeg) + Stiker (*.png *.webp *.gif *.jpg *.jpeg) diff --git a/resources/langs/nheko_it.ts b/resources/langs/nheko_it.ts index d4821cce..da8e2dc4 100644 --- a/resources/langs/nheko_it.ts +++ b/resources/langs/nheko_it.ts @@ -618,7 +618,7 @@ - Stickers (*.png *.webp *.gif) + Stickers (*.png *.webp *.gif *.jpg *.jpeg) diff --git a/resources/langs/nheko_ja.ts b/resources/langs/nheko_ja.ts index b25805d4..24817395 100644 --- a/resources/langs/nheko_ja.ts +++ b/resources/langs/nheko_ja.ts @@ -618,7 +618,7 @@ - Stickers (*.png *.webp *.gif) + Stickers (*.png *.webp *.gif *.jpg *.jpeg) diff --git a/resources/langs/nheko_ml.ts b/resources/langs/nheko_ml.ts index 85826cc0..ba69189e 100644 --- a/resources/langs/nheko_ml.ts +++ b/resources/langs/nheko_ml.ts @@ -618,8 +618,8 @@ - Stickers (*.png *.webp *.gif) - സ്റ്റിക്കറുകൾ(*.png *.webp *.gif) + Stickers (*.png *.webp *.gif *.jpg *.jpeg) + സ്റ്റിക്കറുകൾ(*.png *.webp *.gif *.jpg *.jpeg) diff --git a/resources/langs/nheko_nl.ts b/resources/langs/nheko_nl.ts index 8579ad5f..bc7a3e9e 100644 --- a/resources/langs/nheko_nl.ts +++ b/resources/langs/nheko_nl.ts @@ -618,8 +618,8 @@ - Stickers (*.png *.webp *.gif) - Stickers (*.png *.webp *.gif) + Stickers (*.png *.webp *.gif *.jpg *.jpeg) + Stickers (*.png *.webp *.gif *.jpg *.jpeg) diff --git a/resources/langs/nheko_pl.ts b/resources/langs/nheko_pl.ts index bbc2eaaa..a285359f 100644 --- a/resources/langs/nheko_pl.ts +++ b/resources/langs/nheko_pl.ts @@ -618,8 +618,8 @@ - Stickers (*.png *.webp *.gif) - Naklejki (*.png *.webp *.gif) + Stickers (*.png *.webp *.gif *.jpg *.jpeg) + Naklejki (*.png *.webp *.gif *.jpg *.jpeg) diff --git a/resources/langs/nheko_pt_BR.ts b/resources/langs/nheko_pt_BR.ts index ef3d1f2d..046d2254 100644 --- a/resources/langs/nheko_pt_BR.ts +++ b/resources/langs/nheko_pt_BR.ts @@ -618,7 +618,7 @@ - Stickers (*.png *.webp *.gif) + Stickers (*.png *.webp *.gif *.jpg *.jpeg) diff --git a/resources/langs/nheko_pt_PT.ts b/resources/langs/nheko_pt_PT.ts index c280d17f..2f2e4c2b 100644 --- a/resources/langs/nheko_pt_PT.ts +++ b/resources/langs/nheko_pt_PT.ts @@ -618,8 +618,8 @@ - Stickers (*.png *.webp *.gif) - Autocolantes (*.png *.webp *.gif) + Stickers (*.png *.webp *.gif *.jpg *.jpeg) + Autocolantes (*.png *.webp *.gif *.jpg *.jpeg) diff --git a/resources/langs/nheko_ro.ts b/resources/langs/nheko_ro.ts index e1fa6bcc..86afca01 100644 --- a/resources/langs/nheko_ro.ts +++ b/resources/langs/nheko_ro.ts @@ -618,7 +618,7 @@ - Stickers (*.png *.webp *.gif) + Stickers (*.png *.webp *.gif *.jpg *.jpeg) diff --git a/resources/langs/nheko_ru.ts b/resources/langs/nheko_ru.ts index 64762ab8..7cfaab56 100644 --- a/resources/langs/nheko_ru.ts +++ b/resources/langs/nheko_ru.ts @@ -618,7 +618,7 @@ - Stickers (*.png *.webp *.gif) + Stickers (*.png *.webp *.gif *.jpg *.jpeg) diff --git a/resources/langs/nheko_si.ts b/resources/langs/nheko_si.ts index 88607fb3..645bfede 100644 --- a/resources/langs/nheko_si.ts +++ b/resources/langs/nheko_si.ts @@ -618,7 +618,7 @@ - Stickers (*.png *.webp *.gif) + Stickers (*.png *.webp *.gif *.jpg *.jpeg) diff --git a/resources/langs/nheko_sv.ts b/resources/langs/nheko_sv.ts index d0f27e50..80863b80 100644 --- a/resources/langs/nheko_sv.ts +++ b/resources/langs/nheko_sv.ts @@ -618,7 +618,7 @@ - Stickers (*.png *.webp *.gif) + Stickers (*.png *.webp *.gif *.jpg *.jpeg) diff --git a/resources/langs/nheko_zh_CN.ts b/resources/langs/nheko_zh_CN.ts index c3f03a6f..167c4a06 100644 --- a/resources/langs/nheko_zh_CN.ts +++ b/resources/langs/nheko_zh_CN.ts @@ -618,7 +618,7 @@ - Stickers (*.png *.webp *.gif) + Stickers (*.png *.webp *.gif *.jpg *.jpeg) diff --git a/resources/qml/dialogs/ImagePackEditorDialog.qml b/resources/qml/dialogs/ImagePackEditorDialog.qml index c028f4a2..1db5d45f 100644 --- a/resources/qml/dialogs/ImagePackEditorDialog.qml +++ b/resources/qml/dialogs/ImagePackEditorDialog.qml @@ -91,7 +91,7 @@ ApplicationWindow { folder: StandardPaths.writableLocation(StandardPaths.PicturesLocation) fileMode: FileDialog.OpenFiles - nameFilters: [qsTr("Stickers (*.png *.webp *.gif)")] + nameFilters: [qsTr("Stickers (*.png *.webp *.gif *.jpg *.jpeg)")] onAccepted: imagePack.addStickers(files) } From 75022dee87d65bf9cd62e542b79cf5371ea38260 Mon Sep 17 00:00:00 2001 From: Weblate Date: Fri, 15 Oct 2021 12:55:46 -0400 Subject: [PATCH 197/232] Update translation files Updated by "Cleanup translation files" hook in Weblate. Co-authored-by: Weblate Translate-URL: https://weblate.nheko.im/projects/nheko/nheko-master/ Translation: Nheko/nheko --- resources/langs/nheko_nl.ts | 5 ----- 1 file changed, 5 deletions(-) diff --git a/resources/langs/nheko_nl.ts b/resources/langs/nheko_nl.ts index bc7a3e9e..8a0f9319 100644 --- a/resources/langs/nheko_nl.ts +++ b/resources/langs/nheko_nl.ts @@ -3127,10 +3127,5 @@ Mediagrootte: %2
- - - &Go to quoted message - &Ga naar geciteerd bericht - From a0e1f97f2c56b69e39922eb90eb33280b481ff8f Mon Sep 17 00:00:00 2001 From: Eldred Habert Date: Fri, 15 Oct 2021 19:14:57 +0200 Subject: [PATCH 198/232] Use correct Monopoly quote Hi if you're reading this commit message wondering "what the fuck?" --- resources/qml/SelfVerificationCheck.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/qml/SelfVerificationCheck.qml b/resources/qml/SelfVerificationCheck.qml index a12bfc61..98a5d2dc 100644 --- a/resources/qml/SelfVerificationCheck.qml +++ b/resources/qml/SelfVerificationCheck.qml @@ -35,7 +35,7 @@ Item { Layout.margins: Nheko.paddingMedium Layout.maximumWidth: (Overlay.overlay ? Overlay.overlay.width : 400) - Nheko.paddingMedium * 4 Layout.fillWidth: true - text: qsTr("This is your recovery key. You will need it to restore access to your encrypted messages and verification keys. Keep this safe. Don't share it with anyone and don't lose it! Don't go to start! Don't draw $200 from the bank!") + text: qsTr("This is your recovery key. You will need it to restore access to your encrypted messages and verification keys. Keep this safe. Don't share it with anyone and don't lose it! Do not pass go! Do not collect $200!") color: Nheko.colors.text wrapMode: Text.Wrap } From 649c5ff86d97ed7036822dc420405d84460d5e96 Mon Sep 17 00:00:00 2001 From: Thulinma Date: Sat, 16 Oct 2021 14:17:55 +0200 Subject: [PATCH 199/232] Add support for listing devices that do not support encryption, add support for logging out devices. Ticks off another box in #23! --- CMakeLists.txt | 2 +- io.github.NhekoReborn.Nheko.yaml | 2 +- resources/qml/dialogs/UserProfile.qml | 10 +++++ src/ui/UserProfile.cpp | 63 +++++++++++++++++++++++++++ src/ui/UserProfile.h | 18 +++++++- 5 files changed, 92 insertions(+), 3 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 8ff3ca26..439a4971 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -385,7 +385,7 @@ if(USE_BUNDLED_MTXCLIENT) FetchContent_Declare( MatrixClient GIT_REPOSITORY https://github.com/Nheko-Reborn/mtxclient.git - GIT_TAG 8b56b466dbacde501ed9087d53bb4f51b297eca8 + GIT_TAG e5284ccc9d902117bbe782b0be76fa272b7f0a90 ) set(BUILD_LIB_EXAMPLES OFF CACHE INTERNAL "") set(BUILD_LIB_TESTS OFF CACHE INTERNAL "") diff --git a/io.github.NhekoReborn.Nheko.yaml b/io.github.NhekoReborn.Nheko.yaml index ce272ea7..1ad5a0db 100644 --- a/io.github.NhekoReborn.Nheko.yaml +++ b/io.github.NhekoReborn.Nheko.yaml @@ -163,7 +163,7 @@ modules: buildsystem: cmake-ninja name: mtxclient sources: - - commit: 8b56b466dbacde501ed9087d53bb4f51b297eca8 + - commit: e5284ccc9d902117bbe782b0be76fa272b7f0a90 type: git url: https://github.com/Nheko-Reborn/mtxclient.git - config-opts: diff --git a/resources/qml/dialogs/UserProfile.qml b/resources/qml/dialogs/UserProfile.qml index c921278e..30052168 100644 --- a/resources/qml/dialogs/UserProfile.qml +++ b/resources/qml/dialogs/UserProfile.qml @@ -310,6 +310,7 @@ ApplicationWindow { Image { Layout.preferredHeight: 16 Layout.preferredWidth: 16 + visible: verificationStatus != VerificationStatus.NOT_APPLICABLE source: { switch (verificationStatus) { case VerificationStatus.VERIFIED: @@ -337,6 +338,15 @@ ApplicationWindow { } } + ImageButton { + image: ":/icons/icons/ui/power-button-off.png" + hoverEnabled: true + ToolTip.visible: hovered + ToolTip.text: qsTr("Sign out this device.") + onClicked: profile.signOutDevice(deviceId) + visible: profile.isSelf + } + } footer: DialogButtonBox { diff --git a/src/ui/UserProfile.cpp b/src/ui/UserProfile.cpp index 31ae9f8b..591110af 100644 --- a/src/ui/UserProfile.cpp +++ b/src/ui/UserProfile.cpp @@ -16,6 +16,7 @@ #include "mtx/responses/crypto.hpp" #include "timeline/TimelineModel.h" #include "timeline/TimelineViewManager.h" +#include "ui/UIA.h" UserProfile::UserProfile(QString roomid, QString userid, @@ -137,6 +138,27 @@ UserProfile::isSelf() const return this->userid_ == utils::localUser(); } +void +UserProfile::signOutDevice(const QString &deviceID) +{ + http::client()->delete_device( + deviceID.toStdString(), + UIA::instance()->genericHandler(tr("Sign out device %1").arg(deviceID)), + [this, deviceID](mtx::http::RequestErr e) { + if (e) { + nhlog::ui()->critical("Failure when attempting to sign out device {}", + deviceID.toStdString()); + return; + } + nhlog::ui()->info("Device {} successfully signed out!", deviceID.toStdString()); + // This is us. Let's update the interface accordingly + if (isSelf() && deviceID.toStdString() == ::http::client()->device_id()) { + ChatPage::instance()->dropToLoginPageCb(tr("You signed out this device.")); + } + refreshDevices(); + }); +} + void UserProfile::refreshDevices() { @@ -221,6 +243,47 @@ UserProfile::updateVerificationStatus() verified}); } + // For self, also query devices without keys + if (isSelf()) { + http::client()->query_devices( + [this, deviceInfo](const mtx::responses::QueryDevices &allDevs, + mtx::http::RequestErr err) mutable { + if (err) { + nhlog::net()->warn("failed to query devices: {} {}", + err->matrix_error.error, + static_cast(err->status_code)); + this->deviceList_.queueReset(std::move(deviceInfo)); + emit devicesChanged(); + return; + } + for (const auto &d : allDevs.devices) { + // First, check if we already have an entry for this device + bool found = false; + for (auto &e : deviceInfo) { + if (e.device_id.toStdString() == d.device_id) { + found = true; + // Gottem! Let's fill in the blanks + e.lastIp = QString::fromStdString(d.last_seen_ip); + e.lastTs = d.last_seen_ts; + break; + } + } + // No entry? Let's add one. + if (!found) { + deviceInfo.push_back({QString::fromStdString(d.device_id), + QString::fromStdString(d.display_name), + verification::NOT_APPLICABLE, + QString::fromStdString(d.last_seen_ip), + d.last_seen_ts}); + } + } + + this->deviceList_.queueReset(std::move(deviceInfo)); + emit devicesChanged(); + }); + return; + } + this->deviceList_.queueReset(std::move(deviceInfo)); emit devicesChanged(); } diff --git a/src/ui/UserProfile.h b/src/ui/UserProfile.h index 68f9c21b..e8bff6ba 100644 --- a/src/ui/UserProfile.h +++ b/src/ui/UserProfile.h @@ -21,7 +21,8 @@ enum Status SELF, VERIFIED, UNVERIFIED, - BLOCKED + BLOCKED, + NOT_APPLICABLE }; Q_ENUM_NS(Status) } @@ -33,12 +34,24 @@ class TimelineViewManager; class DeviceInfo { public: + DeviceInfo(const QString deviceID, + const QString displayName, + verification::Status verification_status_, + const QString lastIp_, + const size_t lastTs_) + : device_id(deviceID) + , display_name(displayName) + , verification_status(verification_status_) + , lastIp(lastIp_) + , lastTs(lastTs_) + {} DeviceInfo(const QString deviceID, const QString displayName, verification::Status verification_status_) : device_id(deviceID) , display_name(displayName) , verification_status(verification_status_) + , lastTs(0) {} DeviceInfo() : verification_status(verification::UNVERIFIED) @@ -48,6 +61,8 @@ public: QString display_name; verification::Status verification_status; + QString lastIp; + size_t lastTs; }; class DeviceInfoModel : public QAbstractListModel @@ -121,6 +136,7 @@ public: Q_INVOKABLE void fetchDeviceList(const QString &userID); Q_INVOKABLE void refreshDevices(); Q_INVOKABLE void banUser(); + Q_INVOKABLE void signOutDevice(const QString &deviceID); // Q_INVOKABLE void ignoreUser(); Q_INVOKABLE void kickUser(); Q_INVOKABLE void startChat(); From 550c80525a1633edc983a7fe0d1dae11220cb35f Mon Sep 17 00:00:00 2001 From: Nicolas Werner Date: Thu, 14 Oct 2021 22:53:11 +0200 Subject: [PATCH 200/232] Move voip and encryption stuff into their own directories --- CMakeLists.txt | 28 +++++++++++-------- src/Cache.cpp | 2 +- src/ChatPage.cpp | 6 ++-- src/MainWindow.cpp | 2 +- src/UserSettingsPage.cpp | 5 ++-- .../DeviceVerificationFlow.cpp | 0 src/{ => encryption}/DeviceVerificationFlow.h | 0 src/{ => encryption}/Olm.cpp | 0 src/{ => encryption}/Olm.h | 0 .../SelfVerificationStatus.cpp | 0 src/{ => encryption}/SelfVerificationStatus.h | 0 src/timeline/EventStore.cpp | 1 - src/timeline/EventStore.h | 2 +- src/timeline/InputBar.cpp | 1 - src/timeline/TimelineModel.cpp | 2 +- src/timeline/TimelineViewManager.cpp | 4 +-- src/timeline/TimelineViewManager.h | 4 +-- src/ui/NhekoGlobalObject.cpp | 2 +- src/ui/UserProfile.cpp | 2 +- src/{ => voip}/CallDevices.cpp | 0 src/{ => voip}/CallDevices.h | 0 src/{ => voip}/CallManager.cpp | 0 src/{ => voip}/CallManager.h | 0 src/{ => voip}/WebRTCSession.cpp | 0 src/{ => voip}/WebRTCSession.h | 0 25 files changed, 31 insertions(+), 30 deletions(-) rename src/{ => encryption}/DeviceVerificationFlow.cpp (100%) rename src/{ => encryption}/DeviceVerificationFlow.h (100%) rename src/{ => encryption}/Olm.cpp (100%) rename src/{ => encryption}/Olm.h (100%) rename src/{ => encryption}/SelfVerificationStatus.cpp (100%) rename src/{ => encryption}/SelfVerificationStatus.h (100%) rename src/{ => voip}/CallDevices.cpp (100%) rename src/{ => voip}/CallDevices.h (100%) rename src/{ => voip}/CallManager.cpp (100%) rename src/{ => voip}/CallManager.h (100%) rename src/{ => voip}/WebRTCSession.cpp (100%) rename src/{ => voip}/WebRTCSession.h (100%) diff --git a/CMakeLists.txt b/CMakeLists.txt index 439a4971..90cd3d67 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -329,19 +329,24 @@ set(SRC_FILES src/ui/UIA.cpp src/ui/UserProfile.cpp + src/voip/CallDevices.cpp + src/voip/CallManager.cpp + src/voip/WebRTCSession.cpp + + src/encryption/DeviceVerificationFlow.cpp + src/encryption/Olm.cpp + src/encryption/SelfVerificationStatus.cpp + # Generic notification stuff src/notifications/Manager.cpp src/AvatarProvider.cpp src/BlurhashProvider.cpp src/Cache.cpp - src/CallDevices.cpp - src/CallManager.cpp src/ChatPage.cpp src/Clipboard.cpp src/ColorImageProvider.cpp src/CompletionProxyModel.cpp - src/DeviceVerificationFlow.cpp src/EventAccessors.cpp src/InviteesModel.cpp src/JdenticonProvider.cpp @@ -351,7 +356,6 @@ set(SRC_FILES src/MatrixClient.cpp src/MemberList.cpp src/MxcImageProvider.cpp - src/Olm.cpp src/ReadReceiptsModel.cpp src/RegisterPage.cpp src/SSOHandler.cpp @@ -364,8 +368,6 @@ set(SRC_FILES src/RoomDirectoryModel.cpp src/RoomsModel.cpp src/Utils.cpp - src/SelfVerificationStatus.cpp - src/WebRTCSession.cpp src/WelcomePage.cpp src/main.cpp @@ -542,19 +544,24 @@ qt5_wrap_cpp(MOC_HEADERS src/ui/UIA.h src/ui/UserProfile.h + src/voip/CallDevices.h + src/voip/CallManager.h + src/voip/WebRTCSession.h + + src/encryption/SelfVerificationStatus.h + src/encryption/DeviceVerificationFlow.h + src/encryption/Olm.h + src/notifications/Manager.h src/AvatarProvider.h src/BlurhashProvider.h src/CacheCryptoStructs.h src/Cache_p.h - src/CallDevices.h - src/CallManager.h src/ChatPage.h src/Clipboard.h src/CombinedImagePackModel.h src/CompletionProxyModel.h - src/DeviceVerificationFlow.h src/ImagePackListModel.h src/InviteesModel.h src/JdenticonProvider.h @@ -562,7 +569,6 @@ qt5_wrap_cpp(MOC_HEADERS src/MainWindow.h src/MemberList.h src/MxcImageProvider.h - src/Olm.h src/RegisterPage.h src/RoomsModel.h src/SSOHandler.h @@ -572,8 +578,6 @@ qt5_wrap_cpp(MOC_HEADERS src/UsersModel.h src/RoomDirectoryModel.h src/RoomsModel.h - src/SelfVerificationStatus.h - src/WebRTCSession.h src/WelcomePage.h src/ReadReceiptsModel.h ) diff --git a/src/Cache.cpp b/src/Cache.cpp index ea3dd525..ecfbe6c9 100644 --- a/src/Cache.cpp +++ b/src/Cache.cpp @@ -30,9 +30,9 @@ #include "EventAccessors.h" #include "Logging.h" #include "MatrixClient.h" -#include "Olm.h" #include "UserSettingsPage.h" #include "Utils.h" +#include "encryption/Olm.h" //! Should be changed when a breaking change occurs in the cache format. //! This will reset client's data. diff --git a/src/ChatPage.cpp b/src/ChatPage.cpp index 673f39ee..9239e342 100644 --- a/src/ChatPage.cpp +++ b/src/ChatPage.cpp @@ -12,19 +12,19 @@ #include "AvatarProvider.h" #include "Cache.h" #include "Cache_p.h" -#include "CallManager.h" #include "ChatPage.h" -#include "DeviceVerificationFlow.h" #include "EventAccessors.h" #include "Logging.h" #include "MainWindow.h" #include "MatrixClient.h" -#include "Olm.h" #include "UserSettingsPage.h" #include "Utils.h" +#include "encryption/DeviceVerificationFlow.h" +#include "encryption/Olm.h" #include "ui/OverlayModal.h" #include "ui/Theme.h" #include "ui/UserProfile.h" +#include "voip/CallManager.h" #include "notifications/Manager.h" diff --git a/src/MainWindow.cpp b/src/MainWindow.cpp index c8eb2d24..34db0d1d 100644 --- a/src/MainWindow.cpp +++ b/src/MainWindow.cpp @@ -26,11 +26,11 @@ #include "TrayIcon.h" #include "UserSettingsPage.h" #include "Utils.h" -#include "WebRTCSession.h" #include "WelcomePage.h" #include "ui/LoadingIndicator.h" #include "ui/OverlayModal.h" #include "ui/SnackBar.h" +#include "voip/WebRTCSession.h" #include "dialogs/CreateRoom.h" diff --git a/src/UserSettingsPage.cpp b/src/UserSettingsPage.cpp index cc1f8206..340709a6 100644 --- a/src/UserSettingsPage.cpp +++ b/src/UserSettingsPage.cpp @@ -14,7 +14,6 @@ #include #include #include -#include #include #include #include @@ -26,14 +25,14 @@ #include #include "Cache.h" -#include "CallDevices.h" #include "Config.h" #include "MatrixClient.h" -#include "Olm.h" #include "UserSettingsPage.h" #include "Utils.h" +#include "encryption/Olm.h" #include "ui/FlatButton.h" #include "ui/ToggleButton.h" +#include "voip/CallDevices.h" #include "config/nheko.h" diff --git a/src/DeviceVerificationFlow.cpp b/src/encryption/DeviceVerificationFlow.cpp similarity index 100% rename from src/DeviceVerificationFlow.cpp rename to src/encryption/DeviceVerificationFlow.cpp diff --git a/src/DeviceVerificationFlow.h b/src/encryption/DeviceVerificationFlow.h similarity index 100% rename from src/DeviceVerificationFlow.h rename to src/encryption/DeviceVerificationFlow.h diff --git a/src/Olm.cpp b/src/encryption/Olm.cpp similarity index 100% rename from src/Olm.cpp rename to src/encryption/Olm.cpp diff --git a/src/Olm.h b/src/encryption/Olm.h similarity index 100% rename from src/Olm.h rename to src/encryption/Olm.h diff --git a/src/SelfVerificationStatus.cpp b/src/encryption/SelfVerificationStatus.cpp similarity index 100% rename from src/SelfVerificationStatus.cpp rename to src/encryption/SelfVerificationStatus.cpp diff --git a/src/SelfVerificationStatus.h b/src/encryption/SelfVerificationStatus.h similarity index 100% rename from src/SelfVerificationStatus.h rename to src/encryption/SelfVerificationStatus.h diff --git a/src/timeline/EventStore.cpp b/src/timeline/EventStore.cpp index 7144424a..d7296a7c 100644 --- a/src/timeline/EventStore.cpp +++ b/src/timeline/EventStore.cpp @@ -15,7 +15,6 @@ #include "EventAccessors.h" #include "Logging.h" #include "MatrixClient.h" -#include "Olm.h" #include "Utils.h" Q_DECLARE_METATYPE(Reaction) diff --git a/src/timeline/EventStore.h b/src/timeline/EventStore.h index 53dbaff4..9b857dcf 100644 --- a/src/timeline/EventStore.h +++ b/src/timeline/EventStore.h @@ -15,8 +15,8 @@ #include #include -#include "Olm.h" #include "Reaction.h" +#include "encryption/Olm.h" class EventStore : public QObject { diff --git a/src/timeline/InputBar.cpp b/src/timeline/InputBar.cpp index f33d1dfd..ed97a2ca 100644 --- a/src/timeline/InputBar.cpp +++ b/src/timeline/InputBar.cpp @@ -26,7 +26,6 @@ #include "Logging.h" #include "MainWindow.h" #include "MatrixClient.h" -#include "Olm.h" #include "RoomsModel.h" #include "TimelineModel.h" #include "TimelineViewManager.h" diff --git a/src/timeline/TimelineModel.cpp b/src/timeline/TimelineModel.cpp index 720a78fe..0e5ce510 100644 --- a/src/timeline/TimelineModel.cpp +++ b/src/timeline/TimelineModel.cpp @@ -27,10 +27,10 @@ #include "MatrixClient.h" #include "MemberList.h" #include "MxcImageProvider.h" -#include "Olm.h" #include "ReadReceiptsModel.h" #include "TimelineViewManager.h" #include "Utils.h" +#include "encryption/Olm.h" Q_DECLARE_METATYPE(QModelIndex) diff --git a/src/timeline/TimelineViewManager.cpp b/src/timeline/TimelineViewManager.cpp index a30a145d..86f59c52 100644 --- a/src/timeline/TimelineViewManager.cpp +++ b/src/timeline/TimelineViewManager.cpp @@ -18,7 +18,6 @@ #include "CombinedImagePackModel.h" #include "CompletionProxyModel.h" #include "DelegateChooser.h" -#include "DeviceVerificationFlow.h" #include "EventAccessors.h" #include "ImagePackListModel.h" #include "InviteesModel.h" @@ -29,13 +28,14 @@ #include "ReadReceiptsModel.h" #include "RoomDirectoryModel.h" #include "RoomsModel.h" -#include "SelfVerificationStatus.h" #include "SingleImagePackModel.h" #include "UserSettingsPage.h" #include "UsersModel.h" #include "dialogs/ImageOverlay.h" #include "emoji/EmojiModel.h" #include "emoji/Provider.h" +#include "encryption/DeviceVerificationFlow.h" +#include "encryption/SelfVerificationStatus.h" #include "ui/MxcAnimatedImage.h" #include "ui/MxcMediaProxy.h" #include "ui/NhekoCursorShape.h" diff --git a/src/timeline/TimelineViewManager.h b/src/timeline/TimelineViewManager.h index ab078aa7..723282d6 100644 --- a/src/timeline/TimelineViewManager.h +++ b/src/timeline/TimelineViewManager.h @@ -17,16 +17,16 @@ #include #include "Cache.h" -#include "CallManager.h" #include "JdenticonProvider.h" #include "Logging.h" #include "TimelineModel.h" #include "Utils.h" -#include "WebRTCSession.h" #include "emoji/EmojiModel.h" #include "emoji/Provider.h" #include "timeline/CommunitiesModel.h" #include "timeline/RoomlistModel.h" +#include "voip/CallManager.h" +#include "voip/WebRTCSession.h" class MxcImageProvider; class BlurhashProvider; diff --git a/src/ui/NhekoGlobalObject.cpp b/src/ui/NhekoGlobalObject.cpp index 11fc5681..15f2a5af 100644 --- a/src/ui/NhekoGlobalObject.cpp +++ b/src/ui/NhekoGlobalObject.cpp @@ -14,7 +14,7 @@ #include "MainWindow.h" #include "UserSettingsPage.h" #include "Utils.h" -#include "WebRTCSession.h" +#include "voip/WebRTCSession.h" Nheko::Nheko() { diff --git a/src/ui/UserProfile.cpp b/src/ui/UserProfile.cpp index 591110af..d62e3248 100644 --- a/src/ui/UserProfile.cpp +++ b/src/ui/UserProfile.cpp @@ -9,10 +9,10 @@ #include "Cache_p.h" #include "ChatPage.h" -#include "DeviceVerificationFlow.h" #include "Logging.h" #include "UserProfile.h" #include "Utils.h" +#include "encryption/DeviceVerificationFlow.h" #include "mtx/responses/crypto.hpp" #include "timeline/TimelineModel.h" #include "timeline/TimelineViewManager.h" diff --git a/src/CallDevices.cpp b/src/voip/CallDevices.cpp similarity index 100% rename from src/CallDevices.cpp rename to src/voip/CallDevices.cpp diff --git a/src/CallDevices.h b/src/voip/CallDevices.h similarity index 100% rename from src/CallDevices.h rename to src/voip/CallDevices.h diff --git a/src/CallManager.cpp b/src/voip/CallManager.cpp similarity index 100% rename from src/CallManager.cpp rename to src/voip/CallManager.cpp diff --git a/src/CallManager.h b/src/voip/CallManager.h similarity index 100% rename from src/CallManager.h rename to src/voip/CallManager.h diff --git a/src/WebRTCSession.cpp b/src/voip/WebRTCSession.cpp similarity index 100% rename from src/WebRTCSession.cpp rename to src/voip/WebRTCSession.cpp diff --git a/src/WebRTCSession.h b/src/voip/WebRTCSession.h similarity index 100% rename from src/WebRTCSession.h rename to src/voip/WebRTCSession.h From a5030bdd4da4ca054343f2af758103aaf1f6f73f Mon Sep 17 00:00:00 2001 From: Nicolas Werner Date: Fri, 15 Oct 2021 02:44:48 +0200 Subject: [PATCH 201/232] move device verification management to its own file --- CMakeLists.txt | 4 +- resources/qml/Root.qml | 4 + .../DeviceVerification.qml | 2 +- resources/res.qrc | 51 +++---- src/encryption/VerificationManager.cpp | 126 ++++++++++++++++++ src/encryption/VerificationManager.h | 48 +++++++ src/timeline/TimelineViewManager.cpp | 115 ++-------------- src/timeline/TimelineViewManager.h | 17 +-- src/ui/UserProfile.cpp | 4 +- 9 files changed, 221 insertions(+), 150 deletions(-) create mode 100644 src/encryption/VerificationManager.cpp create mode 100644 src/encryption/VerificationManager.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 90cd3d67..a9bdeec1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -336,6 +336,7 @@ set(SRC_FILES src/encryption/DeviceVerificationFlow.cpp src/encryption/Olm.cpp src/encryption/SelfVerificationStatus.cpp + src/encryption/VerificationManager.cpp # Generic notification stuff src/notifications/Manager.cpp @@ -548,9 +549,10 @@ qt5_wrap_cpp(MOC_HEADERS src/voip/CallManager.h src/voip/WebRTCSession.h - src/encryption/SelfVerificationStatus.h src/encryption/DeviceVerificationFlow.h src/encryption/Olm.h + src/encryption/SelfVerificationStatus.h + src/encryption/VerificationManager.h src/notifications/Manager.h diff --git a/resources/qml/Root.qml b/resources/qml/Root.qml index 18130469..361099ed 100644 --- a/resources/qml/Root.qml +++ b/resources/qml/Root.qml @@ -178,6 +178,10 @@ Page { dialog.show(); } + target: VerificationManager + } + + Connections { function onOpenProfile(profile) { var userProfile = userProfileComponent.createObject(timelineRoot, { "profile": profile diff --git a/resources/qml/device-verification/DeviceVerification.qml b/resources/qml/device-verification/DeviceVerification.qml index 01e3bad4..5bc8b9c8 100644 --- a/resources/qml/device-verification/DeviceVerification.qml +++ b/resources/qml/device-verification/DeviceVerification.qml @@ -12,7 +12,7 @@ ApplicationWindow { property var flow - onClosing: TimelineManager.removeVerificationFlow(flow) + onClosing: VerificationManager.removeVerificationFlow(flow) title: stack.currentItem.title modality: Qt.NonModal palette: Nheko.colors diff --git a/resources/res.qrc b/resources/res.qrc index ffbadd91..e544316b 100644 --- a/resources/res.qrc +++ b/resources/res.qrc @@ -141,37 +141,42 @@ qml/SelfVerificationCheck.qml qml/TypingIndicator.qml qml/NotificationWarning.qml - qml/emoji/EmojiPicker.qml - qml/emoji/StickerPicker.qml - qml/delegates/MessageDelegate.qml + qml/components/AdaptiveLayout.qml + qml/components/AdaptiveLayoutElement.qml + qml/components/AvatarListTile.qml + qml/components/FlatButton.qml qml/delegates/Encrypted.qml qml/delegates/FileMessage.qml qml/delegates/ImageMessage.qml + qml/delegates/MessageDelegate.qml qml/delegates/NoticeMessage.qml qml/delegates/Pill.qml qml/delegates/Placeholder.qml qml/delegates/PlayableMediaMessage.qml qml/delegates/Reply.qml qml/delegates/TextMessage.qml - qml/device-verification/Waiting.qml qml/device-verification/DeviceVerification.qml qml/device-verification/DigitVerification.qml qml/device-verification/EmojiVerification.qml - qml/device-verification/NewVerificationRequest.qml qml/device-verification/Failed.qml - qml/device-verification/Success.qml - qml/dialogs/ImagePackSettingsDialog.qml + qml/device-verification/NewVerificationRequest.qml + qml/device-verification/Success.qml + qml/device-verification/Waiting.qml qml/dialogs/ImagePackEditorDialog.qml - qml/dialogs/InputDialog.qml - qml/dialogs/InviteDialog.qml - qml/dialogs/JoinRoomDialog.qml - qml/dialogs/LogoutDialog.qml - qml/dialogs/RawMessageDialog.qml - qml/dialogs/ReadReceipts.qml - qml/dialogs/RoomDirectory.qml - qml/dialogs/RoomMembers.qml - qml/dialogs/RoomSettings.qml - qml/dialogs/UserProfile.qml + qml/dialogs/ImagePackSettingsDialog.qml + qml/dialogs/InputDialog.qml + qml/dialogs/InviteDialog.qml + qml/dialogs/JoinRoomDialog.qml + qml/dialogs/LeaveRoomDialog.qml + qml/dialogs/LogoutDialog.qml + qml/dialogs/RawMessageDialog.qml + qml/dialogs/ReadReceipts.qml + qml/dialogs/RoomDirectory.qml + qml/dialogs/RoomMembers.qml + qml/dialogs/RoomSettings.qml + qml/dialogs/UserProfile.qml + qml/emoji/EmojiPicker.qml + qml/emoji/StickerPicker.qml qml/ui/Ripple.qml qml/ui/Spinner.qml qml/ui/animations/BlinkAnimation.qml @@ -183,18 +188,6 @@ qml/voip/PlaceCall.qml qml/voip/ScreenShare.qml qml/voip/VideoCall.qml - qml/components/AdaptiveLayout.qml - qml/components/AdaptiveLayoutElement.qml - qml/components/AvatarListTile.qml - qml/components/FlatButton.qml - qml/dialogs/InviteDialog.qml - qml/dialogs/LeaveRoomDialog.qml - qml/dialogs/RawMessageDialog.qml - qml/dialogs/ReadReceipts.qml - qml/dialogs/RoomDirectory.qml - qml/dialogs/RoomMembers.qml - qml/dialogs/RoomSettings.qml - qml/dialogs/UserProfile.qml media/ring.ogg diff --git a/src/encryption/VerificationManager.cpp b/src/encryption/VerificationManager.cpp new file mode 100644 index 00000000..b9b51d35 --- /dev/null +++ b/src/encryption/VerificationManager.cpp @@ -0,0 +1,126 @@ +// SPDX-FileCopyrightText: 2021 Nheko Contributors +// +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "VerificationManager.h" +#include "Cache.h" +#include "ChatPage.h" +#include "DeviceVerificationFlow.h" +#include "timeline/TimelineViewManager.h" + +VerificationManager::VerificationManager(TimelineViewManager *o) + : QObject(o) + , rooms_(o->rooms()) +{} + +void +VerificationManager::receivedRoomDeviceVerificationRequest( + const mtx::events::RoomEvent &message, + TimelineModel *model) +{ + if (this->isInitialSync_) + return; + + auto event_id = QString::fromStdString(message.event_id); + if (!this->dvList.contains(event_id)) { + if (auto flow = DeviceVerificationFlow::NewInRoomVerification( + this, model, message.content, QString::fromStdString(message.sender), event_id)) { + dvList[event_id] = flow; + emit newDeviceVerificationRequest(flow.data()); + } + } +} + +void +VerificationManager::receivedDeviceVerificationRequest( + const mtx::events::msg::KeyVerificationRequest &msg, + std::string sender) +{ + if (this->isInitialSync_) + return; + + if (!msg.transaction_id) + return; + + auto txnid = QString::fromStdString(msg.transaction_id.value()); + if (!this->dvList.contains(txnid)) { + if (auto flow = DeviceVerificationFlow::NewToDeviceVerification( + this, msg, QString::fromStdString(sender), txnid)) { + dvList[txnid] = flow; + emit newDeviceVerificationRequest(flow.data()); + } + } +} + +void +VerificationManager::receivedDeviceVerificationStart( + const mtx::events::msg::KeyVerificationStart &msg, + std::string sender) +{ + if (this->isInitialSync_) + return; + + if (!msg.transaction_id) + return; + + auto txnid = QString::fromStdString(msg.transaction_id.value()); + if (!this->dvList.contains(txnid)) { + if (auto flow = DeviceVerificationFlow::NewToDeviceVerification( + this, msg, QString::fromStdString(sender), txnid)) { + dvList[txnid] = flow; + emit newDeviceVerificationRequest(flow.data()); + } + } +} + +void +VerificationManager::verifyUser(QString userid) +{ + auto joined_rooms = cache::joinedRooms(); + auto room_infos = cache::getRoomInfo(joined_rooms); + + for (std::string room_id : joined_rooms) { + if ((room_infos[QString::fromStdString(room_id)].member_count == 2) && + cache::isRoomEncrypted(room_id)) { + auto room_members = cache::roomMembers(room_id); + if (std::find(room_members.begin(), room_members.end(), (userid).toStdString()) != + room_members.end()) { + if (auto model = rooms_->getRoomById(QString::fromStdString(room_id))) { + auto flow = + DeviceVerificationFlow::InitiateUserVerification(this, model.data(), userid); + connect(model.data(), + &TimelineModel::updateFlowEventId, + this, + [this, flow](std::string eventId) { + dvList[QString::fromStdString(eventId)] = flow; + }); + emit newDeviceVerificationRequest(flow.data()); + return; + } + } + } + } + + emit ChatPage::instance()->showNotification( + tr("No encrypted private chat found with this user. Create an " + "encrypted private chat with this user and try again.")); +} + +void +VerificationManager::removeVerificationFlow(DeviceVerificationFlow *flow) +{ + for (auto it = dvList.keyValueBegin(); it != dvList.keyValueEnd(); ++it) { + if ((*it).second == flow) { + dvList.remove((*it).first); + return; + } + } +} + +void +VerificationManager::verifyDevice(QString userid, QString deviceid) +{ + auto flow = DeviceVerificationFlow::InitiateDeviceVerification(this, userid, deviceid); + this->dvList[flow->transactionId()] = flow; + emit newDeviceVerificationRequest(flow.data()); +} diff --git a/src/encryption/VerificationManager.h b/src/encryption/VerificationManager.h new file mode 100644 index 00000000..e00ddc10 --- /dev/null +++ b/src/encryption/VerificationManager.h @@ -0,0 +1,48 @@ +// SPDX-FileCopyrightText: 2021 Nheko Contributors +// +// SPDX-License-Identifier: GPL-3.0-or-later + +#pragma once + +#include +#include +#include + +#include +#include + +class DeviceVerificationFlow; +class TimelineModel; +class TimelineModel; +class TimelineViewManager; +class RoomlistModel; + +class VerificationManager : public QObject +{ + Q_OBJECT + +public: + VerificationManager(TimelineViewManager *o = nullptr); + + Q_INVOKABLE void removeVerificationFlow(DeviceVerificationFlow *flow); + void verifyUser(QString userid); + void verifyDevice(QString userid, QString deviceid); + +signals: + void newDeviceVerificationRequest(DeviceVerificationFlow *flow); + +public slots: + void receivedRoomDeviceVerificationRequest( + const mtx::events::RoomEvent &message, + TimelineModel *model); + void receivedDeviceVerificationRequest(const mtx::events::msg::KeyVerificationRequest &msg, + std::string sender); + void receivedDeviceVerificationStart(const mtx::events::msg::KeyVerificationStart &msg, + std::string sender); + +private: + QHash> dvList; + bool isInitialSync_ = false; + RoomlistModel *rooms_; +}; + diff --git a/src/timeline/TimelineViewManager.cpp b/src/timeline/TimelineViewManager.cpp index 86f59c52..94e6a0d7 100644 --- a/src/timeline/TimelineViewManager.cpp +++ b/src/timeline/TimelineViewManager.cpp @@ -143,9 +143,10 @@ TimelineViewManager::TimelineViewManager(CallManager *callManager, ChatPage *par , colorImgProvider(new ColorImageProvider()) , blurhashProvider(new BlurhashProvider()) , jdenticonProvider(new JdenticonProvider()) - , callManager_(callManager) , rooms_(new RoomlistModel(this)) , communities_(new CommunitiesModel(this)) + , callManager_(callManager) + , verificationManager_(new VerificationManager(this)) { qRegisterMetaType(); qRegisterMetaType(); @@ -244,6 +245,7 @@ TimelineViewManager::TimelineViewManager(CallManager *callManager, ChatPage *par "im.nheko", 1, 0, "Nheko", [](QQmlEngine *, QJSEngine *) -> QObject * { return new Nheko(); }); + qmlRegisterSingletonInstance("im.nheko", 1, 0, "VerificationManager", verificationManager_); qmlRegisterSingletonType( "im.nheko", 1, 0, "SelfVerificationStatus", [](QQmlEngine *, QJSEngine *) -> QObject * { return new SelfVerificationStatus(); @@ -285,63 +287,16 @@ TimelineViewManager::TimelineViewManager(CallManager *callManager, ChatPage *par connect(parent, &ChatPage::themeChanged, this, &TimelineViewManager::updateColorPalette); connect(dynamic_cast(parent), &ChatPage::receivedRoomDeviceVerificationRequest, - this, - [this](const mtx::events::RoomEvent &message, - TimelineModel *model) { - if (this->isInitialSync_) - return; - - auto event_id = QString::fromStdString(message.event_id); - if (!this->dvList.contains(event_id)) { - if (auto flow = DeviceVerificationFlow::NewInRoomVerification( - this, - model, - message.content, - QString::fromStdString(message.sender), - event_id)) { - dvList[event_id] = flow; - emit newDeviceVerificationRequest(flow.data()); - } - } - }); + verificationManager_, + &VerificationManager::receivedRoomDeviceVerificationRequest); connect(dynamic_cast(parent), &ChatPage::receivedDeviceVerificationRequest, - this, - [this](const mtx::events::msg::KeyVerificationRequest &msg, std::string sender) { - if (this->isInitialSync_) - return; - - if (!msg.transaction_id) - return; - - auto txnid = QString::fromStdString(msg.transaction_id.value()); - if (!this->dvList.contains(txnid)) { - if (auto flow = DeviceVerificationFlow::NewToDeviceVerification( - this, msg, QString::fromStdString(sender), txnid)) { - dvList[txnid] = flow; - emit newDeviceVerificationRequest(flow.data()); - } - } - }); + verificationManager_, + &VerificationManager::receivedDeviceVerificationRequest); connect(dynamic_cast(parent), &ChatPage::receivedDeviceVerificationStart, - this, - [this](const mtx::events::msg::KeyVerificationStart &msg, std::string sender) { - if (this->isInitialSync_) - return; - - if (!msg.transaction_id) - return; - - auto txnid = QString::fromStdString(msg.transaction_id.value()); - if (!this->dvList.contains(txnid)) { - if (auto flow = DeviceVerificationFlow::NewToDeviceVerification( - this, msg, QString::fromStdString(sender), txnid)) { - dvList[txnid] = flow; - emit newDeviceVerificationRequest(flow.data()); - } - } - }); + verificationManager_, + &VerificationManager::receivedDeviceVerificationStart); connect(parent, &ChatPage::loggedOut, this, [this]() { isInitialSync_ = true; emit initialSyncChanged(true); @@ -475,58 +430,6 @@ TimelineViewManager::openImageOverlayInternal(QString eventId, QImage img) }); } -void -TimelineViewManager::verifyUser(QString userid) -{ - auto joined_rooms = cache::joinedRooms(); - auto room_infos = cache::getRoomInfo(joined_rooms); - - for (std::string room_id : joined_rooms) { - if ((room_infos[QString::fromStdString(room_id)].member_count == 2) && - cache::isRoomEncrypted(room_id)) { - auto room_members = cache::roomMembers(room_id); - if (std::find(room_members.begin(), room_members.end(), (userid).toStdString()) != - room_members.end()) { - if (auto model = rooms_->getRoomById(QString::fromStdString(room_id))) { - auto flow = - DeviceVerificationFlow::InitiateUserVerification(this, model.data(), userid); - connect(model.data(), - &TimelineModel::updateFlowEventId, - this, - [this, flow](std::string eventId) { - dvList[QString::fromStdString(eventId)] = flow; - }); - emit newDeviceVerificationRequest(flow.data()); - return; - } - } - } - } - - emit ChatPage::instance()->showNotification( - tr("No encrypted private chat found with this user. Create an " - "encrypted private chat with this user and try again.")); -} - -void -TimelineViewManager::removeVerificationFlow(DeviceVerificationFlow *flow) -{ - for (auto it = dvList.keyValueBegin(); it != dvList.keyValueEnd(); ++it) { - if ((*it).second == flow) { - dvList.remove((*it).first); - return; - } - } -} - -void -TimelineViewManager::verifyDevice(QString userid, QString deviceid) -{ - auto flow = DeviceVerificationFlow::InitiateDeviceVerification(this, userid, deviceid); - this->dvList[flow->transactionId()] = flow; - emit newDeviceVerificationRequest(flow.data()); -} - void TimelineViewManager::updateReadReceipts(const QString &room_id, const std::vector &event_ids) diff --git a/src/timeline/TimelineViewManager.h b/src/timeline/TimelineViewManager.h index 723282d6..6696b1c4 100644 --- a/src/timeline/TimelineViewManager.h +++ b/src/timeline/TimelineViewManager.h @@ -9,7 +9,6 @@ #include #include #include -#include #include #include @@ -23,6 +22,7 @@ #include "Utils.h" #include "emoji/EmojiModel.h" #include "emoji/Provider.h" +#include "encryption/VerificationManager.h" #include "timeline/CommunitiesModel.h" #include "timeline/RoomlistModel.h" #include "voip/CallManager.h" @@ -33,7 +33,6 @@ class BlurhashProvider; class ColorImageProvider; class UserSettings; class ChatPage; -class DeviceVerificationFlow; class ImagePackListModel; class TimelineViewManager : public QObject @@ -53,6 +52,7 @@ public: MxcImageProvider *imageProvider() { return imgProvider; } CallManager *callManager() { return callManager_; } + VerificationManager *verificationManager() { return verificationManager_; } void clearAll() { rooms_->clear(); } @@ -73,19 +73,14 @@ public: Q_INVOKABLE void openGlobalUserProfile(QString userId); Q_INVOKABLE void focusMessageInput(); - Q_INVOKABLE void removeVerificationFlow(DeviceVerificationFlow *flow); Q_INVOKABLE void fixImageRendering(QQuickTextDocument *t, QQuickItem *i); - void verifyUser(QString userid); - void verifyDevice(QString userid, QString deviceid); - signals: void activeTimelineChanged(TimelineModel *timeline); void initialSyncChanged(bool isInitialSync); void replyingEventChanged(QString replyingEvent); void replyClosed(); - void newDeviceVerificationRequest(DeviceVerificationFlow *flow); void inviteUsers(QString roomId, QStringList users); void showRoomList(); void narrowViewChanged(); @@ -142,17 +137,17 @@ private: BlurhashProvider *blurhashProvider; JdenticonProvider *jdenticonProvider; - CallManager *callManager_ = nullptr; - bool isInitialSync_ = true; bool isWindowFocused_ = false; RoomlistModel *rooms_ = nullptr; CommunitiesModel *communities_ = nullptr; - QHash userColors; + // don't move this above the rooms_ + CallManager *callManager_ = nullptr; + VerificationManager *verificationManager_ = nullptr; - QHash> dvList; + QHash userColors; }; Q_DECLARE_METATYPE(mtx::events::msg::KeyVerificationAccept) Q_DECLARE_METATYPE(mtx::events::msg::KeyVerificationCancel) diff --git a/src/ui/UserProfile.cpp b/src/ui/UserProfile.cpp index d62e3248..0e3fd39f 100644 --- a/src/ui/UserProfile.cpp +++ b/src/ui/UserProfile.cpp @@ -338,9 +338,9 @@ void UserProfile::verify(QString device) { if (!device.isEmpty()) - manager->verifyDevice(userid_, device); + manager->verificationManager()->verifyDevice(userid_, device); else { - manager->verifyUser(userid_); + manager->verificationManager()->verifyUser(userid_); } } From b030eb923b7aac7e67bce30e34e18f6ef222f8fc Mon Sep 17 00:00:00 2001 From: Nicolas Werner Date: Sun, 17 Oct 2021 17:17:29 +0200 Subject: [PATCH 202/232] WIP --- resources/qml/SelfVerificationCheck.qml | 39 ++++++++--------- resources/qml/components/MainWindowDialog.qml | 42 +++++++++++++++++++ resources/res.qrc | 1 + 3 files changed, 63 insertions(+), 19 deletions(-) create mode 100644 resources/qml/components/MainWindowDialog.qml diff --git a/resources/qml/SelfVerificationCheck.qml b/resources/qml/SelfVerificationCheck.qml index 98a5d2dc..c5eee669 100644 --- a/resources/qml/SelfVerificationCheck.qml +++ b/resources/qml/SelfVerificationCheck.qml @@ -7,6 +7,7 @@ import QtQuick 2.15 import QtQuick.Controls 2.15 import QtQuick.Layouts 1.3 import im.nheko 1.0 +import "./components/" Item { visible: false @@ -80,31 +81,15 @@ Item { text: qsTr("Failed to setup encryption: %1").arg(errorMessage) } - Dialog { + MainWindowDialog { id: bootstrapCrosssigning - parent: Overlay.overlay - anchors.centerIn: parent - height: (Math.floor(parent.height / 2) - Nheko.paddingLarge) * 2 - width: (Math.floor(parent.width / 2) - Nheko.paddingLarge) * 2 - padding: 0 - modal: true - standardButtons: Dialog.Ok | Dialog.Cancel - closePolicy: Popup.NoAutoClose onAccepted: SelfVerificationStatus.setupCrosssigning(storeSecretsOnline.checked, usePassword.checked ? passwordField.text : "", useOnlineKeyBackup.checked) - ScrollView { - id: scroll - - clip: true - anchors.fill: parent - ScrollBar.horizontal.visible: false - ScrollBar.vertical.visible: true - GridLayout { id: grid - width: scroll.width - scroll.ScrollBar.vertical.width + width: bootstrapCrosssigning.useableWidth columns: 2 rowSpacing: 0 columnSpacing: 0 @@ -222,7 +207,6 @@ Item { } - } background: Rectangle { color: Nheko.colors.window @@ -233,11 +217,28 @@ Item { } + MainWindowDialog { + id: verifyMasterKey + + onAccepted: SelfVerificationStatus.verifyMasterKey() + + GridLayout { + id: masterGrid + + width: verifyMasterKey.useableWidth + columns: 2 + rowSpacing: 0 + columnSpacing: 0 + } + } + Connections { function onStatusChanged() { console.log("STATUS CHANGED: " + SelfVerificationStatus.status); if (SelfVerificationStatus.status == SelfVerificationStatus.NoMasterKey) bootstrapCrosssigning.open(); + else if (SelfVerificationStatus.status == SelfVerificationStatus.UnverifiedMasterKey) + verifyMasterKey.open(); } diff --git a/resources/qml/components/MainWindowDialog.qml b/resources/qml/components/MainWindowDialog.qml new file mode 100644 index 00000000..19233384 --- /dev/null +++ b/resources/qml/components/MainWindowDialog.qml @@ -0,0 +1,42 @@ +// SPDX-FileCopyrightText: 2021 Nheko Contributors +// +// SPDX-License-Identifier: GPL-3.0-or-later + +import Qt.labs.platform 1.1 as P +import QtQuick 2.15 +import QtQuick.Controls 2.15 +import QtQuick.Layouts 1.3 +import im.nheko 1.0 + +Dialog { + parent: Overlay.overlay + anchors.centerIn: parent + height: (Math.floor(parent.height / 2) - Nheko.paddingLarge) * 2 + width: (Math.floor(parent.width / 2) - Nheko.paddingLarge) * 2 + padding: 0 + modal: true + standardButtons: Dialog.Ok | Dialog.Cancel + closePolicy: Popup.NoAutoClose + + default property alias inner: scroll.data + property int useableWidth: scroll.width - scroll.ScrollBar.vertical.width + + contentChildren: [ + ScrollView { + id: scroll + + clip: true + anchors.fill: parent + ScrollBar.horizontal.visible: false + ScrollBar.vertical.visible: true + } + ] + + background: Rectangle { + color: Nheko.colors.window + border.color: Nheko.theme.separator + border.width: 1 + radius: Nheko.paddingSmall + } + +} diff --git a/resources/res.qrc b/resources/res.qrc index e544316b..f11d0b4a 100644 --- a/resources/res.qrc +++ b/resources/res.qrc @@ -145,6 +145,7 @@ qml/components/AdaptiveLayoutElement.qml qml/components/AvatarListTile.qml qml/components/FlatButton.qml + qml/components/MainWindowDialog.qml qml/delegates/Encrypted.qml qml/delegates/FileMessage.qml qml/delegates/ImageMessage.qml From 147dc9d4da25707217bc22a27a4c2f19274ca03c Mon Sep 17 00:00:00 2001 From: Nicolas Werner Date: Sun, 17 Oct 2021 17:18:02 +0200 Subject: [PATCH 203/232] Use allow list for URI schemes --- src/ui/NhekoGlobalObject.cpp | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/ui/NhekoGlobalObject.cpp b/src/ui/NhekoGlobalObject.cpp index 15f2a5af..a93466d2 100644 --- a/src/ui/NhekoGlobalObject.cpp +++ b/src/ui/NhekoGlobalObject.cpp @@ -61,7 +61,17 @@ Nheko::openLink(QString link) const QUrl url(link); // Open externally if we couldn't handle it internally if (!ChatPage::instance()->handleMatrixUri(url)) { - QDesktopServices::openUrl(url); + const QStringList allowedUrlSchemes = { + "http", + "https", + "mailto", + }; + + if (allowedUrlSchemes.contains(url.scheme())) + QDesktopServices::openUrl(url); + else + nhlog::ui()->warn("Url '{}' not opened, because the scheme is not in the allow list", + url.toDisplayString().toStdString()); } } void From 6793bdf3fd30096e018bb479ae8b52854209b108 Mon Sep 17 00:00:00 2001 From: Nicolas Werner Date: Sun, 17 Oct 2021 17:20:51 +0200 Subject: [PATCH 204/232] lint --- resources/qml/SelfVerificationCheck.qml | 4 ++-- src/encryption/DeviceVerificationFlow.cpp | 4 ++-- src/encryption/VerificationManager.h | 1 - src/notifications/ManagerWin.cpp | 12 +++++++++--- src/voip/WebRTCSession.cpp | 6 +++++- 5 files changed, 18 insertions(+), 9 deletions(-) diff --git a/resources/qml/SelfVerificationCheck.qml b/resources/qml/SelfVerificationCheck.qml index c5eee669..a7a9e41a 100644 --- a/resources/qml/SelfVerificationCheck.qml +++ b/resources/qml/SelfVerificationCheck.qml @@ -237,8 +237,8 @@ Item { console.log("STATUS CHANGED: " + SelfVerificationStatus.status); if (SelfVerificationStatus.status == SelfVerificationStatus.NoMasterKey) bootstrapCrosssigning.open(); - else if (SelfVerificationStatus.status == SelfVerificationStatus.UnverifiedMasterKey) - verifyMasterKey.open(); +// else if (SelfVerificationStatus.status == SelfVerificationStatus.UnverifiedMasterKey) +// verifyMasterKey.open(); } diff --git a/src/encryption/DeviceVerificationFlow.cpp b/src/encryption/DeviceVerificationFlow.cpp index 2481d4f9..ea5a060d 100644 --- a/src/encryption/DeviceVerificationFlow.cpp +++ b/src/encryption/DeviceVerificationFlow.cpp @@ -631,8 +631,8 @@ DeviceVerificationFlow::sendVerificationRequest() req.to = this->toClient.to_string(); req.msgtype = "m.key.verification.request"; req.body = "User is requesting to verify keys with you. However, your client does " - "not support this method, so you will need to use the legacy method of " - "key verification."; + "not support this method, so you will need to use the legacy method of " + "key verification."; } send(req); diff --git a/src/encryption/VerificationManager.h b/src/encryption/VerificationManager.h index e00ddc10..d6a39ccf 100644 --- a/src/encryption/VerificationManager.h +++ b/src/encryption/VerificationManager.h @@ -45,4 +45,3 @@ private: bool isInitialSync_ = false; RoomlistModel *rooms_; }; - diff --git a/src/notifications/ManagerWin.cpp b/src/notifications/ManagerWin.cpp index 4376e4d8..556ca028 100644 --- a/src/notifications/ManagerWin.cpp +++ b/src/notifications/ManagerWin.cpp @@ -100,10 +100,16 @@ NotificationsManager::systemPostNotification(const QString &line1, WinToast::instance()->showToast(templ, new CustomHandler()); } -void NotificationsManager::actionInvoked(uint, QString) {} -void NotificationsManager::notificationReplied(uint, QString) {} +void +NotificationsManager::actionInvoked(uint, QString) +{} +void +NotificationsManager::notificationReplied(uint, QString) +{} -void NotificationsManager::notificationClosed(uint, uint) {} +void +NotificationsManager::notificationClosed(uint, uint) +{} void NotificationsManager::removeNotification(const QString &, const QString &) diff --git a/src/voip/WebRTCSession.cpp b/src/voip/WebRTCSession.cpp index 801a365c..8e0a9f79 100644 --- a/src/voip/WebRTCSession.cpp +++ b/src/voip/WebRTCSession.cpp @@ -1114,7 +1114,11 @@ WebRTCSession::haveLocalPiP() const return false; } -bool WebRTCSession::createOffer(webrtc::CallType, uint32_t) { return false; } +bool +WebRTCSession::createOffer(webrtc::CallType, uint32_t) +{ + return false; +} bool WebRTCSession::acceptOffer(const std::string &) From 2981f71d2268087b04f6dfef2fd0d42a4f786b4d Mon Sep 17 00:00:00 2001 From: Nicolas Werner Date: Sun, 17 Oct 2021 17:33:59 +0200 Subject: [PATCH 205/232] lint using clang11 --- src/encryption/DeviceVerificationFlow.cpp | 4 ++-- src/notifications/ManagerWin.cpp | 12 +++--------- src/voip/WebRTCSession.cpp | 6 +----- 3 files changed, 6 insertions(+), 16 deletions(-) diff --git a/src/encryption/DeviceVerificationFlow.cpp b/src/encryption/DeviceVerificationFlow.cpp index ea5a060d..2481d4f9 100644 --- a/src/encryption/DeviceVerificationFlow.cpp +++ b/src/encryption/DeviceVerificationFlow.cpp @@ -631,8 +631,8 @@ DeviceVerificationFlow::sendVerificationRequest() req.to = this->toClient.to_string(); req.msgtype = "m.key.verification.request"; req.body = "User is requesting to verify keys with you. However, your client does " - "not support this method, so you will need to use the legacy method of " - "key verification."; + "not support this method, so you will need to use the legacy method of " + "key verification."; } send(req); diff --git a/src/notifications/ManagerWin.cpp b/src/notifications/ManagerWin.cpp index 556ca028..4376e4d8 100644 --- a/src/notifications/ManagerWin.cpp +++ b/src/notifications/ManagerWin.cpp @@ -100,16 +100,10 @@ NotificationsManager::systemPostNotification(const QString &line1, WinToast::instance()->showToast(templ, new CustomHandler()); } -void -NotificationsManager::actionInvoked(uint, QString) -{} -void -NotificationsManager::notificationReplied(uint, QString) -{} +void NotificationsManager::actionInvoked(uint, QString) {} +void NotificationsManager::notificationReplied(uint, QString) {} -void -NotificationsManager::notificationClosed(uint, uint) -{} +void NotificationsManager::notificationClosed(uint, uint) {} void NotificationsManager::removeNotification(const QString &, const QString &) diff --git a/src/voip/WebRTCSession.cpp b/src/voip/WebRTCSession.cpp index 8e0a9f79..801a365c 100644 --- a/src/voip/WebRTCSession.cpp +++ b/src/voip/WebRTCSession.cpp @@ -1114,11 +1114,7 @@ WebRTCSession::haveLocalPiP() const return false; } -bool -WebRTCSession::createOffer(webrtc::CallType, uint32_t) -{ - return false; -} +bool WebRTCSession::createOffer(webrtc::CallType, uint32_t) { return false; } bool WebRTCSession::acceptOffer(const std::string &) From eb94e699a199f245c5fc191b9836b830208ad48c Mon Sep 17 00:00:00 2001 From: Weblate Date: Tue, 19 Oct 2021 03:26:49 -0400 Subject: [PATCH 206/232] Translated using Weblate (French) Currently translated at 98.2% (559 of 569 strings) Co-authored-by: Eldred HABERT Translate-URL: https://weblate.nheko.im/projects/nheko/nheko-master/fr/ Translation: Nheko/nheko --- resources/langs/nheko_fr.ts | 114 ++++++++++++++++++------------------ 1 file changed, 57 insertions(+), 57 deletions(-) diff --git a/resources/langs/nheko_fr.ts b/resources/langs/nheko_fr.ts index 2895fcef..cd3fd94e 100644 --- a/resources/langs/nheko_fr.ts +++ b/resources/langs/nheko_fr.ts @@ -96,12 +96,12 @@ Unknown microphone: %1 - Microphone inconnu : %1 + Microphone inconnu : %1 Unknown camera: %1 - Caméra inconnue : %1 + Caméra inconnue : %1 @@ -164,12 +164,12 @@ Do you really want to invite %1 (%2)? - Voulez-vous vraiment inviter %1 (%2) ? + Voulez-vous vraiment inviter %1 (%2) ? Failed to invite %1 to %2: %3 - Échec de l'invitation de %1 dans %2 : %3 + Échec de l'invitation de %1 dans %2 : %3 @@ -179,7 +179,7 @@ Do you really want to kick %1 (%2)? - Voulez-vous vraiment expulser %1 (%2) ? + Voulez-vous vraiment expulser %1 (%2) ? @@ -194,7 +194,7 @@ Do you really want to ban %1 (%2)? - Voulez-vous vraiment bannir %1 (%2) ? + Voulez-vous vraiment bannir %1 (%2) ? @@ -214,12 +214,12 @@ Do you really want to unban %1 (%2)? - Voulez-vous vraiment annuler le bannissement de %1 (%2) ? + Voulez-vous vraiment annuler le bannissement de %1 (%2) ? Failed to unban %1 in %2: %3 - Échec de l'annulation du bannissement de %1 dans %2 : %3 + Échec de l'annulation du bannissement de %1 dans %2 : %3 @@ -234,7 +234,7 @@ Cache migration failed! - Échec de la migration du cache ! + Échec de la migration du cache ! @@ -261,18 +261,18 @@ Failed to setup encryption keys. Server response: %1 %2. Please try again later. - Échec de la configuration des clés de chiffrement. Réponse du serveur : %1 %2. Veuillez réessayer plus tard. + Échec de la configuration des clés de chiffrement. Réponse du serveur : %1 %2. Veuillez réessayer plus tard. Please try to login again: %1 - Veuillez re-tenter vous reconnecter : %1 + Veuillez re-tenter vous reconnecter : %1 Failed to join room: %1 - Impossible de rejoindre le salon : %1 + Impossible de rejoindre le salon : %1 @@ -282,22 +282,22 @@ Failed to remove invite: %1 - Impossible de supprimer l'invitation : %1 + Impossible de supprimer l'invitation : %1 Room creation failed: %1 - Échec de la création du salon : %1 + Échec de la création du salon : %1 Failed to leave room: %1 - Impossible de quitter le salon : %1 + Impossible de quitter le salon : %1 Failed to kick %1 from %2: %3 - Échec de l'expulsion de %1 de %2  : %3 + Échec de l'expulsion de %1 de %2  : %3
@@ -361,12 +361,12 @@ Enter your recovery key or passphrase to decrypt your secrets: - Entrez votre clé de récupération ou phrase de passe pour déchiffrer vos secrets : + Entrez votre clé de récupération ou phrase de passe pour déchiffrer vos secrets : Enter your recovery key or passphrase called %1 to decrypt your secrets: - Entrez votre clé de récupération ou votre phrase de passe nommée %1 pour déchiffrer vos secrets : + Entrez votre clé de récupération ou votre phrase de passe nommée %1 pour déchiffrer vos secrets : @@ -389,17 +389,17 @@ Please verify the following digits. You should see the same numbers on both sides. If they differ, please press 'They do not match!' to abort verification! - Veuillez vérifier les chiffres suivants. Vous devriez voir les mêmes chiffres des deux côtés. Si ceux-ci diffèrent, veuillez choisir « Ils sont différents ! » pour annuler la vérification ! + Veuillez vérifier les chiffres suivants. Vous devriez voir les mêmes chiffres des deux côtés. Si ceux-ci diffèrent, veuillez choisir « Ils sont différents ! » pour annuler la vérification ! They do not match! - Ils sont différents ! + Ils sont différents ! They match! - Ils sont identiques ! + Ils sont identiques ! @@ -483,17 +483,17 @@ Please verify the following emoji. You should see the same emoji on both sides. If they differ, please press 'They do not match!' to abort verification! - Veuillez vérifier les émoji suivants. Vous devriez voir les mêmes émoji des deux côtés. S'ils diffèrent, veuillez choisir « Ils sont différents ! » pour annuler la vérification ! + Veuillez vérifier les émoji suivants. Vous devriez voir les mêmes émoji des deux côtés. S'ils diffèrent, veuillez choisir « Ils sont différents ! » pour annuler la vérification ! They do not match! - Ils sont différents ! + Ils sont différents ! They match! - Ils sont identiques ! + Ils sont identiques ! @@ -544,7 +544,7 @@ This message is not encrypted! - Ce message n'est pas chiffré ! + Ce message n'est pas chiffré ! @@ -577,7 +577,7 @@ Key mismatch detected! - Clés non correspondantes détectées ! + Clés non correspondantes détectées ! @@ -802,9 +802,9 @@ You can also put your homeserver address there, if your server doesn't support .well-known lookup. Example: @user:server.my If Nheko fails to discover your homeserver, it will show you a field to enter the server manually. - Votre nom de connexion. Un mxid doit commencer par un « @ » suivi de l'identifiant. L'identifiant doit être suivi du nom de serveur, séparé de celui-ci par « : ». + Votre nom de connexion. Un mxid doit commencer par un « @ » suivi de l'identifiant. L'identifiant doit être suivi du nom de serveur, séparé de celui-ci par « : ». Vous pouvez également spécifier l'adresse de votre serveur ici, si votre serveur ne supporte pas l'identification .well-known. -Exemple : @utilisateur :monserveur.example.com +Exemple : @utilisateur :monserveur.example.com Si Nheko n'arrive pas à trouver votre serveur, il vous proposera de l'indiquer manuellement. @@ -842,7 +842,7 @@ Si Nheko n'arrive pas à trouver votre serveur, il vous proposera de l&apos The address that can be used to contact you homeservers client API. Example: https://server.my:8787 L'adresse qui peut être utilisée pour joindre l'API client de votre serveur. -Exemple : https ://monserveur.example.com:8787 +Exemple : https ://monserveur.example.com:8787 @@ -855,7 +855,7 @@ Exemple : https ://monserveur.example.com:8787 You have entered an invalid Matrix ID e.g @joe:matrix.org - Vous avez entré un identifiant Matrix invalide exemple correct : @moi:monserveur.example.com) + Vous avez entré un identifiant Matrix invalide exemple correct : @moi:monserveur.example.com) @@ -914,7 +914,7 @@ Exemple : https ://monserveur.example.com:8787 room name changed to: %1 - nom du salon changé en : %1 + nom du salon changé en : %1 @@ -924,7 +924,7 @@ Exemple : https ://monserveur.example.com:8787 topic changed to: %1 - sujet changé en : %1 + sujet changé en : %1 @@ -939,7 +939,7 @@ Exemple : https ://monserveur.example.com:8787 %1 created and configured room: %2 - %1 a créé et configuré le salon : %2 + %1 a créé et configuré le salon : %2 @@ -1117,7 +1117,7 @@ Exemple : https ://monserveur.example.com:8787 &Go to quoted message - + Aller au messa&ge cité @@ -1135,7 +1135,7 @@ Exemple : https ://monserveur.example.com:8787 To allow other users to see, which of your devices actually belong to you, you can verify them. This also allows key backup to work automatically. Verify %1 now? - Pour permettre aux autres utilisateurs de vérifier quels appareils de votre compte sont réellement les vôtres, vous pouvez les vérifier. Cela permet également à la sauvegarde des clés de fonctionner automatiquement. Vérifier %1 maintenant ? + Pour permettre aux autres utilisateurs de vérifier quels appareils de votre compte sont réellement les vôtres, vous pouvez les vérifier. Cela permet également à la sauvegarde des clés de fonctionner automatiquement. Vérifier %1 maintenant ? @@ -1183,7 +1183,7 @@ Exemple : https ://monserveur.example.com:8787 You will be pinging the whole room - + Vous allez notifier tout le salon @@ -1211,7 +1211,7 @@ Exemple : https ://monserveur.example.com:8787 %1: %2 Format a normal message in a notification. %1 is the sender, %2 the message - %1 : %2 + %1 : %2 @@ -1235,7 +1235,7 @@ Exemple : https ://monserveur.example.com:8787 Place a call to %1? - Appeler %1 ? + Appeler %1 ? @@ -1268,7 +1268,7 @@ Exemple : https ://monserveur.example.com:8787 unimplemented event: - Évènement non implémenté : + Évènement non implémenté : @@ -1351,7 +1351,7 @@ Exemple : https ://monserveur.example.com:8787 No supported registration flows! - Aucune méthode d'inscription supportée ! + Aucune méthode d'inscription supportée ! @@ -1468,7 +1468,7 @@ Exemple : https ://monserveur.example.com:8787 Tag room as: - Étiqueter le salon comme : + Étiqueter le salon comme : @@ -1699,7 +1699,7 @@ Exemple : https ://monserveur.example.com:8787 Failed to enable encryption: %1 - Échec de l'activation du chiffrement : %1 + Échec de l'activation du chiffrement : %1 @@ -1719,13 +1719,13 @@ Exemple : https ://monserveur.example.com:8787 Error while reading file: %1 - Erreur lors de la lecture du fichier : %1 + Erreur lors de la lecture du fichier : %1 Failed to upload image: %s - Échec de l'envoi de l'image : %s + Échec de l'envoi de l'image : %s @@ -1751,17 +1751,17 @@ Exemple : https ://monserveur.example.com:8787 Share desktop with %1? - Partager le bureau avec %1  ? + Partager le bureau avec %1  ? Window: - Fenêtre : + Fenêtre : Frame rate: - Fréquence d'images : + Fréquence d'images : @@ -1878,7 +1878,7 @@ Exemple : https ://monserveur.example.com:8787 Verification successful! Both sides verified their devices! - Vérification réussie ! Les deux côtés ont vérifié leur appareil ! + Vérification réussie ! Les deux côtés ont vérifié leur appareil ! @@ -1891,13 +1891,13 @@ Exemple : https ://monserveur.example.com:8787 Message redaction failed: %1 - Échec de la suppression du message : %1 + Échec de la suppression du message : %1 Failed to encrypt event, sending aborted! - Échec du chiffrement de l'évènement, envoi abandonné ! + Échec du chiffrement de l'évènement, envoi abandonné ! @@ -2072,7 +2072,7 @@ Exemple : https ://monserveur.example.com:8787 %1 left after having already left! This is a leave event after the user already left and shouldn't happen apart from state resets - %1 a quitté le salon après l'avoir déjà quitté ! + %1 a quitté le salon après l'avoir déjà quitté ! @@ -2291,7 +2291,7 @@ Exemple : https ://monserveur.example.com:8787 Error while reading file: %1 - Erreur lors de la lecture du fichier  : %1 + Erreur lors de la lecture du fichier  : %1 @@ -2328,7 +2328,7 @@ Exemple : https ://monserveur.example.com:8787 profile: %1 - profil : %1 + profil : %1 @@ -2783,7 +2783,7 @@ Cela met l'application en évidence dans la barre des tâches. Enter the passphrase to decrypt the file: - Entrez la phrase de passe pour déchiffrer le fichier : + Entrez la phrase de passe pour déchiffrer le fichier : @@ -2794,7 +2794,7 @@ Cela met l'application en évidence dans la barre des tâches. Enter passphrase to encrypt your session keys: - Entrez une phrase de passe pour chiffrer vos clés de session : + Entrez une phrase de passe pour chiffrer vos clés de session : @@ -3076,12 +3076,12 @@ Taille du média : %2 You: %1 - Vous : %1 + Vous : %1 %1: %2 - %1 : %2 + %1 : %2 From d56a48215a73d884a4b79df54c89a947f031e341 Mon Sep 17 00:00:00 2001 From: Eldred Habert Date: Tue, 19 Oct 2021 18:25:59 +0200 Subject: [PATCH 207/232] Fix incorrect pluralization of "rooms" --- resources/qml/TopBar.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/qml/TopBar.qml b/resources/qml/TopBar.qml index 05c61d99..e9f482c9 100644 --- a/resources/qml/TopBar.qml +++ b/resources/qml/TopBar.qml @@ -114,7 +114,7 @@ Rectangle { case Crypto.Verified: return qsTr("This room contains only verified devices."); case Crypto.TOFU: - return qsTr("This rooms contain verified devices and devices which have never changed their master key."); + return qsTr("This room contains verified devices and devices which have never changed their master key."); default: return qsTr("This room contains unverified devices!"); } From fab9ecd05295744f0f38a028fcba587c5181d2b6 Mon Sep 17 00:00:00 2001 From: Weblate Date: Tue, 19 Oct 2021 13:42:15 -0400 Subject: [PATCH 208/232] Translated using Weblate (French) Currently translated at 100.0% (569 of 569 strings) Co-authored-by: Eldred HABERT Translate-URL: https://weblate.nheko.im/projects/nheko/nheko-master/fr/ Translation: Nheko/nheko --- resources/langs/nheko_fr.ts | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/resources/langs/nheko_fr.ts b/resources/langs/nheko_fr.ts index cd3fd94e..118cd259 100644 --- a/resources/langs/nheko_fr.ts +++ b/resources/langs/nheko_fr.ts @@ -2215,37 +2215,37 @@ Exemple : https ://monserveur.example.com:8787 Change avatar globally. - + Changer l'image de profil partout. Change avatar. Will only apply to this room. - + Changer l'image de profil. Ne s'appliquera qu'à ce salon. Change display name globally. - + Changer de surnom partout. Change display name. Will only apply to this room. - + Changer de surnom. Ne s'appliquera qu'à ce salon. Room: %1 - + Salon : %1 This is a room-specific profile. The user's name and avatar may be different from their global versions. - + Ceci est un profil spécifique à un salon. Le surnom et l'image de profil peuvent être différents de leurs versions globales. Open the global profile for this user. - + Ouvrir le profil global de cet utilisateur. @@ -2256,17 +2256,17 @@ Exemple : https ://monserveur.example.com:8787 Start a private chat. - + Démarrer une discussion privée. Kick the user. - + Expulser l'utilisateur. Ban the user. - + Bannir l'utilisateur. From eb3dd47bcf4f5fa3d3d698e86e69ecf092b08ee9 Mon Sep 17 00:00:00 2001 From: Weblate Date: Tue, 19 Oct 2021 13:42:15 -0400 Subject: [PATCH 209/232] Translated using Weblate (Portuguese (Portugal)) Currently translated at 73.2% (417 of 569 strings) Co-authored-by: Tnpod Translate-URL: https://weblate.nheko.im/projects/nheko/nheko-master/pt_PT/ Translation: Nheko/nheko --- resources/langs/nheko_pt_PT.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/resources/langs/nheko_pt_PT.ts b/resources/langs/nheko_pt_PT.ts index 2f2e4c2b..a2c7264c 100644 --- a/resources/langs/nheko_pt_PT.ts +++ b/resources/langs/nheko_pt_PT.ts @@ -2390,13 +2390,14 @@ Only affects messages in encrypted chats. Privacy Screen - + Ecrã de privacidade When the window loses focus, the timeline will be blurred. - + Quando a janela perde a atenção, a cronologia +será desfocada. From af8dec908d97b1c238e30b49a916b5acbd91e3a6 Mon Sep 17 00:00:00 2001 From: Weblate Date: Tue, 19 Oct 2021 21:17:38 -0400 Subject: [PATCH 210/232] Translated using Weblate (Dutch) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Currently translated at 100.0% (569 of 569 strings) Co-authored-by: Jaron Viëtor Translate-URL: https://weblate.nheko.im/projects/nheko/nheko-master/nl/ Translation: Nheko/nheko --- resources/langs/nheko_nl.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/resources/langs/nheko_nl.ts b/resources/langs/nheko_nl.ts index 8a0f9319..bc7a3e9e 100644 --- a/resources/langs/nheko_nl.ts +++ b/resources/langs/nheko_nl.ts @@ -3127,5 +3127,10 @@ Mediagrootte: %2 + + + &Go to quoted message + &Ga naar geciteerd bericht + From 582c5215fe729a5d58e43b2131f4305b6750961e Mon Sep 17 00:00:00 2001 From: Weblate Date: Wed, 20 Oct 2021 08:47:22 -0400 Subject: [PATCH 211/232] Translated using Weblate (Portuguese (Portugal)) Currently translated at 74.8% (426 of 569 strings) Co-authored-by: Tnpod Translate-URL: https://weblate.nheko.im/projects/nheko/nheko-master/pt_PT/ Translation: Nheko/nheko --- resources/langs/nheko_pt_PT.ts | 24 +++++++++++++++--------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/resources/langs/nheko_pt_PT.ts b/resources/langs/nheko_pt_PT.ts index a2c7264c..d30c267e 100644 --- a/resources/langs/nheko_pt_PT.ts +++ b/resources/langs/nheko_pt_PT.ts @@ -2402,14 +2402,16 @@ será desfocada. Privacy screen timeout (in seconds [0 - 3600]) - + Tempo de inatividade para ecrã de privacidade (em segundos [0 - 3600]) Set timeout (in seconds) for how long after window loses focus before the screen will be blurred. Set to 0 to blur immediately after focus loss. Max value of 1 hour (3600 seconds) - + Definir tempo (em segundos) depois da janela perder a +atenção até que o ecrã seja desfocado. +Defina como 0 para desfocar imediatamente após perder a atenção. Valor máximo de 1 hora (3600 segundos) @@ -2434,41 +2436,45 @@ Set to 0 to blur immediately after focus loss. Max value of 1 hour (3600 seconds Typing notifications - + Notificações de escrita Show who is typing in a room. This will also enable or disable sending typing notifications to others. - + Mostrar quem está a escrever numa sala. +Irá também ativar ou desativar o envio de notificações de escrita para outros. Sort rooms by unreads - + Ordenar salas por não lidas Display rooms with new messages first. If this is off, the list of rooms will only be sorted by the timestamp of the last message in a room. If this is on, rooms which have active notifications (the small circle with a number in it) will be sorted on top. Rooms, that you have muted, will still be sorted by timestamp, since you don't seem to consider them as important as the other rooms. - + Exibe salas com novas mensagens primeiro. +Se desativada, a lista de salas será apenas ordenada pela data da última mensagem de cada sala. +Se ativada, salas com notificações ativas (pequeno círculo com um número dentro) serão ordenadas no topo. Salas silenciadas continuarão a ser ordenadas por data, visto que não são consideradas tão importantes como as outras. Read receipts - + Recibos de leitura Show if your message was read. Status is displayed next to timestamps. - + Mostrar se a sua mensagem foi lida. +Estado exibido ao lado da data. Send messages as Markdown - + Enviar mensagens como Markdown From 2902bbb7e70d509335e68660ff9314cc7cece959 Mon Sep 17 00:00:00 2001 From: Nicolas Werner Date: Wed, 20 Oct 2021 15:04:36 +0200 Subject: [PATCH 212/232] Fix calculating hidden space children --- src/Cache.cpp | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/src/Cache.cpp b/src/Cache.cpp index ecfbe6c9..5b77a9d4 100644 --- a/src/Cache.cpp +++ b/src/Cache.cpp @@ -3420,9 +3420,15 @@ Cache::updateSpaces(lmdb::txn &txn, spacesParentsDb_.put(txn, event.state_key, space); } } + + for (const auto &r : getRoomIds(txn)) { + if (auto parent = getStateEvent(txn, r, space)) { + rooms_with_updates.insert(r); + } + } } - const auto space_event_type = to_string(mtx::events::EventType::RoomPowerLevels); + const auto space_event_type = to_string(mtx::events::EventType::SpaceChild); for (const auto &room : rooms_with_updates) { for (const auto &event : @@ -3440,6 +3446,13 @@ Cache::updateSpaces(lmdb::txn &txn, pls->content.state_level(space_event_type)) { spacesChildrenDb_.put(txn, space, room); spacesParentsDb_.put(txn, room, space); + } else { + nhlog::db()->debug("Skipping {} in {} because of missing PL. {}: {} < {}", + room, + space, + event.sender, + pls->content.user_level(event.sender), + pls->content.state_level(space_event_type)); } } } From 5bce8fd915fef97dca685551be5d6e7b2fc1bd0b Mon Sep 17 00:00:00 2001 From: Nicolas Werner Date: Wed, 20 Oct 2021 15:19:04 +0200 Subject: [PATCH 213/232] speed up blurhash decode a bit --- third_party/blurhash/blurhash.cpp | 33 +++++++++++++++---------------- third_party/blurhash/blurhash.hpp | 2 +- 2 files changed, 17 insertions(+), 18 deletions(-) diff --git a/third_party/blurhash/blurhash.cpp b/third_party/blurhash/blurhash.cpp index a4adf89f..bcfcce5c 100644 --- a/third_party/blurhash/blurhash.cpp +++ b/third_party/blurhash/blurhash.cpp @@ -6,10 +6,6 @@ #include #include -#ifndef M_PI -#define M_PI 3.14159265358979323846 -#endif - #ifdef DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN #include #endif @@ -17,6 +13,9 @@ using namespace std::literals; namespace { +template +T pi = 3.14159265358979323846; + constexpr std::array int_to_b83{ "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz#$%*+,-.:;=?@[]^_{|}~"}; @@ -62,13 +61,13 @@ struct Components }; int -packComponents(const Components &c) +packComponents(const Components &c) noexcept { return (c.x - 1) + (c.y - 1) * 9; } Components -unpackComponents(int c) +unpackComponents(int c) noexcept { return {c % 9 + 1, c / 9 + 1}; } @@ -88,7 +87,7 @@ decode83(std::string_view value) } float -decodeMaxAC(int quantizedMaxAC) +decodeMaxAC(int quantizedMaxAC) noexcept { return (quantizedMaxAC + 1) / 166.; } @@ -101,13 +100,13 @@ decodeMaxAC(std::string_view maxAC) } int -encodeMaxAC(float maxAC) +encodeMaxAC(float maxAC) noexcept { - return std::max(0, std::min(82, int(maxAC * 166 - 0.5))); + return std::max(0, std::min(82, int(maxAC * 166 - 0.5f))); } float -srgbToLinear(int value) +srgbToLinear(int value) noexcept { auto srgbToLinearF = [](float x) { if (x <= 0.0f) @@ -124,7 +123,7 @@ srgbToLinear(int value) } int -linearToSrgb(float value) +linearToSrgb(float value) noexcept { auto linearToSrgbF = [](float x) -> float { if (x <= 0.0f) @@ -137,7 +136,7 @@ linearToSrgb(float value) return std::pow(x, 1.0f / 2.4f) * 1.055f - 0.055f; }; - return int(linearToSrgbF(value) * 255.f + 0.5); + return int(linearToSrgbF(value) * 255.f + 0.5f); } struct Color @@ -235,8 +234,8 @@ multiplyBasisFunction(Components components, int width, int height, unsigned cha for (int y = 0; y < height; y++) { for (int x = 0; x < width; x++) { - float basis = std::cos(M_PI * components.x * x / float(width)) * - std::cos(M_PI * components.y * y / float(height)); + float basis = std::cos(pi * components.x * x / float(width)) * + std::cos(pi * components.y * y / float(height)); c.r += basis * srgbToLinear(pixels[3 * x + 0 + y * width * 3]); c.g += basis * srgbToLinear(pixels[3 * x + 1 + y * width * 3]); c.b += basis * srgbToLinear(pixels[3 * x + 2 + y * width * 3]); @@ -251,7 +250,7 @@ multiplyBasisFunction(Components components, int width, int height, unsigned cha namespace blurhash { Image -decode(std::string_view blurhash, size_t width, size_t height, size_t bytesPerPixel) +decode(std::string_view blurhash, size_t width, size_t height, size_t bytesPerPixel) noexcept { Image i{}; @@ -287,8 +286,8 @@ decode(std::string_view blurhash, size_t width, size_t height, size_t bytesPerPi for (size_t nx = 0; nx < size_t(components.x); nx++) { for (size_t ny = 0; ny < size_t(components.y); ny++) { float basis = - std::cos(M_PI * float(x) * float(nx) / float(width)) * - std::cos(M_PI * float(y) * float(ny) / float(height)); + std::cos(pi * float(nx * x) / float(width)) * + std::cos(pi * float(ny * y) / float(height)); c += values[nx + ny * components.x] * basis; } } diff --git a/third_party/blurhash/blurhash.hpp b/third_party/blurhash/blurhash.hpp index e01b9b3f..d4e138ef 100644 --- a/third_party/blurhash/blurhash.hpp +++ b/third_party/blurhash/blurhash.hpp @@ -13,7 +13,7 @@ struct Image // Decode a blurhash to an image with size width*height Image -decode(std::string_view blurhash, size_t width, size_t height, size_t bytesPerPixel = 3); +decode(std::string_view blurhash, size_t width, size_t height, size_t bytesPerPixel = 3) noexcept; // Encode an image of rgb pixels (without padding) with size width*height into a blurhash with x*y // components From 662cb573e1b8bf03733540c5bc1e9dd46824ff99 Mon Sep 17 00:00:00 2001 From: Thulinma Date: Sun, 17 Oct 2021 17:25:16 +0200 Subject: [PATCH 214/232] Allow changing device names, display last seen time and IP --- resources/qml/dialogs/UserProfile.qml | 100 +++++++++++++++++++++----- src/ui/UserProfile.cpp | 19 +++++ src/ui/UserProfile.h | 5 +- 3 files changed, 105 insertions(+), 19 deletions(-) diff --git a/resources/qml/dialogs/UserProfile.qml b/resources/qml/dialogs/UserProfile.qml index 30052168..4c7095b2 100644 --- a/resources/qml/dialogs/UserProfile.qml +++ b/resources/qml/dialogs/UserProfile.qml @@ -281,6 +281,8 @@ ApplicationWindow { required property int verificationStatus required property string deviceId required property string deviceName + required property string lastIp + required property var lastTs width: devicelist.width spacing: 4 @@ -288,21 +290,90 @@ ApplicationWindow { ColumnLayout { spacing: 0 - Text { - Layout.fillWidth: true - Layout.alignment: Qt.AlignLeft - elide: Text.ElideRight - font.bold: true - color: Nheko.colors.text - text: deviceId + RowLayout { + Text { + Layout.fillWidth: true + Layout.alignment: Qt.AlignLeft + elide: Text.ElideRight + font.bold: true + color: Nheko.colors.text + text: deviceId + } + + Image { + Layout.preferredHeight: 16 + Layout.preferredWidth: 16 + visible: profile.isSelf && verificationStatus != VerificationStatus.NOT_APPLICABLE + source: { + switch (verificationStatus) { + case VerificationStatus.VERIFIED: + return "image://colorimage/:/icons/icons/ui/lock.png?green"; + case VerificationStatus.UNVERIFIED: + return "image://colorimage/:/icons/icons/ui/unlock.png?yellow"; + case VerificationStatus.SELF: + return "image://colorimage/:/icons/icons/ui/checkmark.png?green"; + default: + return "image://colorimage/:/icons/icons/ui/unlock.png?red"; + } + } + } + + ImageButton { + Layout.alignment: Qt.AlignTop + image: ":/icons/icons/ui/power-button-off.png" + hoverEnabled: true + ToolTip.visible: hovered + ToolTip.text: qsTr("Sign out this device.") + onClicked: profile.signOutDevice(deviceId) + visible: profile.isSelf + } + } + + RowLayout { + id: deviceNameRow + property bool isEditingAllowed + + TextInput { + id: deviceNameField + readOnly: !deviceNameRow.isEditingAllowed + text: deviceName + color: Nheko.colors.text + Layout.alignment: Qt.AlignLeft + Layout.fillWidth: true + selectByMouse: true + onAccepted: { + profile.changeDeviceName(deviceId, deviceNameField.text); + deviceNameRow.isEditingAllowed = false; + } + } + + ImageButton { + visible: profile.isSelf + hoverEnabled: true + ToolTip.visible: hovered + ToolTip.text: qsTr("Change device name.") + image: deviceNameRow.isEditingAllowed ? ":/icons/icons/ui/checkmark.png" : ":/icons/icons/ui/edit.png" + onClicked: { + if (deviceNameRow.isEditingAllowed) { + profile.changeDeviceName(deviceId, deviceNameField.text); + deviceNameRow.isEditingAllowed = false; + } else { + deviceNameRow.isEditingAllowed = true; + deviceNameField.focus = true; + deviceNameField.selectAll(); + } + } + } + } Text { + visible: profile.isSelf Layout.fillWidth: true - Layout.alignment: Qt.AlignRight + Layout.alignment: Qt.AlignLeft elide: Text.ElideRight color: Nheko.colors.text - text: deviceName + text: qsTr("Last seen %1 from %2").arg(new Date(lastTs).toLocaleString(Locale.ShortFormat)).arg(lastIp?lastIp:"???") } } @@ -310,7 +381,7 @@ ApplicationWindow { Image { Layout.preferredHeight: 16 Layout.preferredWidth: 16 - visible: verificationStatus != VerificationStatus.NOT_APPLICABLE + visible: !profile.isSelf && verificationStatus != VerificationStatus.NOT_APPLICABLE source: { switch (verificationStatus) { case VerificationStatus.VERIFIED: @@ -338,14 +409,7 @@ ApplicationWindow { } } - ImageButton { - image: ":/icons/icons/ui/power-button-off.png" - hoverEnabled: true - ToolTip.visible: hovered - ToolTip.text: qsTr("Sign out this device.") - onClicked: profile.signOutDevice(deviceId) - visible: profile.isSelf - } + } diff --git a/src/ui/UserProfile.cpp b/src/ui/UserProfile.cpp index 0e3fd39f..b5a16f43 100644 --- a/src/ui/UserProfile.cpp +++ b/src/ui/UserProfile.cpp @@ -62,6 +62,8 @@ DeviceInfoModel::roleNames() const {DeviceId, "deviceId"}, {DeviceName, "deviceName"}, {VerificationStatus, "verificationStatus"}, + {LastIp, "lastIp"}, + {LastTs, "lastTs"}, }; } @@ -78,6 +80,10 @@ DeviceInfoModel::data(const QModelIndex &index, int role) const return deviceList_[index.row()].display_name; case VerificationStatus: return QVariant::fromValue(deviceList_[index.row()].verification_status); + case LastIp: + return deviceList_[index.row()].lastIp; + case LastTs: + return deviceList_[index.row()].lastTs; default: return {}; } @@ -334,6 +340,19 @@ UserProfile::changeUsername(QString username) } } +void +UserProfile::changeDeviceName(QString deviceID, QString deviceName) +{ + http::client()->set_device_name( + deviceID.toStdString(), deviceName.toStdString(), [this](mtx::http::RequestErr err) { + if (err) { + nhlog::net()->warn("could not change device name"); + return; + } + refreshDevices(); + }); +} + void UserProfile::verify(QString device) { diff --git a/src/ui/UserProfile.h b/src/ui/UserProfile.h index e8bff6ba..cd2f4740 100644 --- a/src/ui/UserProfile.h +++ b/src/ui/UserProfile.h @@ -62,7 +62,7 @@ public: verification::Status verification_status; QString lastIp; - size_t lastTs; + qlonglong lastTs; }; class DeviceInfoModel : public QAbstractListModel @@ -74,6 +74,8 @@ public: DeviceId, DeviceName, VerificationStatus, + LastIp, + LastTs, }; explicit DeviceInfoModel(QObject *parent = nullptr) @@ -141,6 +143,7 @@ public: Q_INVOKABLE void kickUser(); Q_INVOKABLE void startChat(); Q_INVOKABLE void changeUsername(QString username); + Q_INVOKABLE void changeDeviceName(QString deviceID, QString deviceName); Q_INVOKABLE void changeAvatar(); Q_INVOKABLE void openGlobalProfile(); From 32eb247e02ed7d7b20d50440aaaf393476f21288 Mon Sep 17 00:00:00 2001 From: Weblate Date: Tue, 26 Oct 2021 05:02:29 -0400 Subject: [PATCH 215/232] Update translation files Updated by "Cleanup translation files" hook in Weblate. Co-authored-by: Weblate Translate-URL: https://weblate.nheko.im/projects/nheko/nheko-master/ Translation: Nheko/nheko --- resources/langs/nheko_nl.ts | 5 ----- 1 file changed, 5 deletions(-) diff --git a/resources/langs/nheko_nl.ts b/resources/langs/nheko_nl.ts index bc7a3e9e..8a0f9319 100644 --- a/resources/langs/nheko_nl.ts +++ b/resources/langs/nheko_nl.ts @@ -3127,10 +3127,5 @@ Mediagrootte: %2
- - - &Go to quoted message - &Ga naar geciteerd bericht - From 923b8157134389598b3071824d8b7ce015806968 Mon Sep 17 00:00:00 2001 From: Weblate Date: Tue, 26 Oct 2021 05:02:29 -0400 Subject: [PATCH 216/232] Translated using Weblate (Indonesian) Currently translated at 100.0% (569 of 569 strings) Co-authored-by: Linerly Co-authored-by: Weblate Translate-URL: https://weblate.nheko.im/projects/nheko/nheko-master/id/ Translation: Nheko/nheko --- resources/langs/nheko_id.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/resources/langs/nheko_id.ts b/resources/langs/nheko_id.ts index c82111c7..4b78df09 100644 --- a/resources/langs/nheko_id.ts +++ b/resources/langs/nheko_id.ts @@ -841,7 +841,7 @@ Jika Nheko gagal menemukan homeserver Anda, Nheko akan menampilkan kolom untuk m The address that can be used to contact you homeservers client API. Example: https://server.my:8787 - Alamat yang dapat digunakan untuk mengkontak API client homeserver Anda. + Alamat yang dapat digunakan untuk menghubungi API klien homeserver Anda. Misalnya: https://server.my:8787 @@ -2356,12 +2356,12 @@ Misalnya: https://server.my:8787 Keep the application running in the background after closing the client window. - Membiarkan aplikasi berjalan di latar belakang setelah menutup jendela client. + Membiarkan aplikasi berjalan di latar belakang setelah menutup jendela klien. Start the application in the background without showing the client window. - Mulai aplikasinya di latar belakang tanpa menunjukkan jendela clientnya. + Mulai aplikasinya di latar belakang tanpa menunjukkan jendela kliennya. @@ -2496,7 +2496,7 @@ Ketika dinonaktifkan, semua pesan akan dikirim sebagai teks biasa. Notify about received message when the client is not currently focused. - Memberitahukan tentang pesan yang diterima ketika clientnya tidak difokuskan. + Memberitahukan tentang pesan yang diterima ketika kliennya tidak difokuskan. @@ -2831,7 +2831,7 @@ Ini biasanya menyebabkan ikon aplikasi di bilah tugas untuk beranimasi. Welcome to nheko! The desktop client for the Matrix protocol. - Selamat datang ke nheko! Sebuah client desktop untuk protokol Matrix. + Selamat datang di nheko! Sebuah klien desktop untuk protokol Matrix. From 0c2522684beb5ddabc05f83b163dcf4d6815b398 Mon Sep 17 00:00:00 2001 From: Loren Burkholder Date: Thu, 28 Oct 2021 21:44:24 -0400 Subject: [PATCH 217/232] Auto-focus search bar in room directory --- resources/qml/dialogs/RoomDirectory.qml | 1 + 1 file changed, 1 insertion(+) diff --git a/resources/qml/dialogs/RoomDirectory.qml b/resources/qml/dialogs/RoomDirectory.qml index 67842720..bb55b27c 100644 --- a/resources/qml/dialogs/RoomDirectory.qml +++ b/resources/qml/dialogs/RoomDirectory.qml @@ -184,6 +184,7 @@ ApplicationWindow { MatrixTextField { id: roomSearch + focus: true Layout.fillWidth: true selectByMouse: true font.pixelSize: fontMetrics.font.pixelSize From 5688b2647ee686559303203a394bad1a92a744b0 Mon Sep 17 00:00:00 2001 From: Nicolas Werner Date: Sat, 30 Oct 2021 00:22:47 +0200 Subject: [PATCH 218/232] Add self verification after login --- resources/qml/SelfVerificationCheck.qml | 51 +++++++++-- src/ChatPage.cpp | 62 ++++++++++++- src/encryption/DeviceVerificationFlow.cpp | 102 +++++++++++++++------- src/encryption/DeviceVerificationFlow.h | 15 ++-- src/encryption/Olm.cpp | 63 ++++++++----- src/encryption/SelfVerificationStatus.cpp | 47 +++++++++- src/encryption/SelfVerificationStatus.h | 5 ++ src/encryption/VerificationManager.cpp | 11 ++- src/encryption/VerificationManager.h | 1 + 9 files changed, 285 insertions(+), 72 deletions(-) diff --git a/resources/qml/SelfVerificationCheck.qml b/resources/qml/SelfVerificationCheck.qml index a7a9e41a..0242a149 100644 --- a/resources/qml/SelfVerificationCheck.qml +++ b/resources/qml/SelfVerificationCheck.qml @@ -93,6 +93,7 @@ Item { columns: 2 rowSpacing: 0 columnSpacing: 0 + z: 1 Label { Layout.margins: Nheko.paddingMedium @@ -220,15 +221,53 @@ Item { MainWindowDialog { id: verifyMasterKey - onAccepted: SelfVerificationStatus.verifyMasterKey() + standardButtons: Dialog.Cancel GridLayout { id: masterGrid width: verifyMasterKey.useableWidth - columns: 2 - rowSpacing: 0 - columnSpacing: 0 + columns: 1 + z: 1 + + Label { + Layout.margins: Nheko.paddingMedium + Layout.alignment: Qt.AlignHCenter + //Layout.columnSpan: 2 + font.pointSize: fontMetrics.font.pointSize * 2 + text: qsTr("Activate Encryption") + color: Nheko.colors.text + wrapMode: Text.Wrap + } + + Label { + Layout.margins: Nheko.paddingMedium + Layout.alignment: Qt.AlignLeft + //Layout.columnSpan: 2 + Layout.maximumWidth: grid.width - Nheko.paddingMedium * 2 + text: qsTr("It seems like you have encryption already configured for this account. To be able to access your encrypted messages and make this device appear as trusted, you can either verify an existing device or (if you have one) enter your recovery passphrase. Please select one of the options below.\nIf you choose verify, you need to have the other device available. If you choose \"enter passphrase\", you will need your recovery key or passphrase. If you click cancel, you can choose to verify yourself at a later point.") + color: Nheko.colors.text + wrapMode: Text.Wrap + } + + FlatButton { + Layout.alignment: Qt.AlignHCenter + text: qsTr("verify") + onClicked: { + console.log("AAAAA"); + SelfVerificationStatus.verifyMasterKey(); + verifyMasterKey.close(); + } + } + FlatButton { + visible: SelfVerificationStatus.hasSSSS + Layout.alignment: Qt.AlignHCenter + text: qsTr("enter passphrase") + onClicked: { + SelfVerificationStatus.verifyMasterKeyWithPassphrase() + verifyMasterKey.close(); + } + } } } @@ -237,8 +276,8 @@ Item { console.log("STATUS CHANGED: " + SelfVerificationStatus.status); if (SelfVerificationStatus.status == SelfVerificationStatus.NoMasterKey) bootstrapCrosssigning.open(); -// else if (SelfVerificationStatus.status == SelfVerificationStatus.UnverifiedMasterKey) -// verifyMasterKey.open(); + else if (SelfVerificationStatus.status == SelfVerificationStatus.UnverifiedMasterKey) + verifyMasterKey.open(); } diff --git a/src/ChatPage.cpp b/src/ChatPage.cpp index 9239e342..d262387c 100644 --- a/src/ChatPage.cpp +++ b/src/ChatPage.cpp @@ -1131,11 +1131,71 @@ ChatPage::decryptDownloadedSecrets(mtx::secret_storage::AesHmacSha2KeyDescriptio return; } + auto deviceKeys = cache::client()->userKeys(http::client()->user_id().to_string()); + mtx::requests::KeySignaturesUpload req; + for (const auto &[secretName, encryptedSecret] : secrets) { auto decrypted = mtx::crypto::decrypt(encryptedSecret, *decryptionKey, secretName); - if (!decrypted.empty()) + if (!decrypted.empty()) { cache::storeSecret(secretName, decrypted); + + if (deviceKeys && + secretName == mtx::secret_storage::secrets::cross_signing_self_signing) { + auto myKey = deviceKeys->device_keys.at(http::client()->device_id()); + if (myKey.user_id == http::client()->user_id().to_string() && + myKey.device_id == http::client()->device_id() && + myKey.keys["ed25519:" + http::client()->device_id()] == + olm::client()->identity_keys().ed25519 && + myKey.keys["curve25519:" + http::client()->device_id()] == + olm::client()->identity_keys().curve25519) { + json j = myKey; + j.erase("signatures"); + j.erase("unsigned"); + + auto ssk = mtx::crypto::PkSigning::from_seed(decrypted); + myKey.signatures[http::client()->user_id().to_string()] + ["ed25519:" + ssk.public_key()] = ssk.sign(j.dump()); + req.signatures[http::client()->user_id().to_string()] + [http::client()->device_id()] = myKey; + } + } else if (deviceKeys && + secretName == mtx::secret_storage::secrets::cross_signing_master) { + auto mk = mtx::crypto::PkSigning::from_seed(decrypted); + + if (deviceKeys->master_keys.user_id == http::client()->user_id().to_string() && + deviceKeys->master_keys.keys["ed25519:" + mk.public_key()] == mk.public_key()) { + json j = deviceKeys->master_keys; + j.erase("signatures"); + j.erase("unsigned"); + mtx::crypto::CrossSigningKeys master_key = j; + master_key.signatures[http::client()->user_id().to_string()] + ["ed25519:" + http::client()->device_id()] = + olm::client()->sign_message(j.dump()); + req.signatures[http::client()->user_id().to_string()][mk.public_key()] = + master_key; + } + } + } } + + if (!req.signatures.empty()) + http::client()->keys_signatures_upload( + req, [](const mtx::responses::KeySignaturesUpload &res, mtx::http::RequestErr err) { + if (err) { + nhlog::net()->error("failed to upload signatures: {},{}", + mtx::errors::to_string(err->matrix_error.errcode), + static_cast(err->status_code)); + } + + for (const auto &[user_id, tmp] : res.errors) + for (const auto &[key_id, e] : tmp) + nhlog::net()->error("signature error for user '{}' and key " + "id {}: {}, {}", + user_id, + key_id, + mtx::errors::to_string(e.errcode), + e.error); + }); } void diff --git a/src/encryption/DeviceVerificationFlow.cpp b/src/encryption/DeviceVerificationFlow.cpp index 2481d4f9..f05d5c9f 100644 --- a/src/encryption/DeviceVerificationFlow.cpp +++ b/src/encryption/DeviceVerificationFlow.cpp @@ -32,12 +32,15 @@ DeviceVerificationFlow::DeviceVerificationFlow(QObject *, DeviceVerificationFlow::Type flow_type, TimelineModel *model, QString userID, - QString deviceId_) + std::vector deviceIds_) : sender(false) , type(flow_type) - , deviceId(deviceId_) + , deviceIds(std::move(deviceIds_)) , model_(model) { + if (deviceIds.size() == 1) + deviceId = deviceIds.front(); + timeout = new QTimer(this); timeout->setSingleShot(true); this->sas = olm::client()->sas_init(); @@ -346,33 +349,62 @@ DeviceVerificationFlow::DeviceVerificationFlow(QObject *, } }); - connect(ChatPage::instance(), - &ChatPage::receivedDeviceVerificationReady, - this, - [this](const mtx::events::msg::KeyVerificationReady &msg) { - nhlog::crypto()->info("verification: received ready"); - if (!sender) { - if (msg.from_device != http::client()->device_id()) { - error_ = User; - emit errorChanged(); - setState(Failed); - } + connect( + ChatPage::instance(), + &ChatPage::receivedDeviceVerificationReady, + this, + [this](const mtx::events::msg::KeyVerificationReady &msg) { + nhlog::crypto()->info("verification: received ready"); + if (!sender) { + if (msg.from_device != http::client()->device_id()) { + error_ = User; + emit errorChanged(); + setState(Failed); + } - return; - } + return; + } - if (msg.transaction_id.has_value()) { - if (msg.transaction_id.value() != this->transaction_id) - return; - } else if (msg.relations.references()) { - if (msg.relations.references() != this->relation.event_id) - return; - else { - this->deviceId = QString::fromStdString(msg.from_device); - } - } - this->startVerificationRequest(); - }); + if (msg.transaction_id.has_value()) { + if (msg.transaction_id.value() != this->transaction_id) + return; + + if (this->deviceId.isEmpty() && this->deviceIds.size() > 1) { + auto from = QString::fromStdString(msg.from_device); + if (std::find(deviceIds.begin(), deviceIds.end(), from) != deviceIds.end()) { + mtx::events::msg::KeyVerificationCancel req{}; + req.code = "m.user"; + req.reason = "accepted by other device"; + req.transaction_id = this->transaction_id; + mtx::requests::ToDeviceMessages body; + + for (const auto &d : this->deviceIds) { + if (d != from) + body[this->toClient][d.toStdString()] = req; + } + + http::client()->send_to_device( + http::client()->generate_txn_id(), body, [](mtx::http::RequestErr err) { + if (err) + nhlog::net()->warn( + "failed to send verification to_device message: {} {}", + err->matrix_error.error, + static_cast(err->status_code)); + }); + + this->deviceId = from; + this->deviceIds = {from}; + } + } + } else if (msg.relations.references()) { + if (msg.relations.references() != this->relation.event_id) + return; + else { + this->deviceId = QString::fromStdString(msg.from_device); + } + } + this->startVerificationRequest(); + }); connect(ChatPage::instance(), &ChatPage::receivedDeviceVerificationDone, @@ -782,7 +814,7 @@ DeviceVerificationFlow::NewInRoomVerification(QObject *parent_, Type::RoomMsg, timelineModel_, other_user_, - QString::fromStdString(msg.from_device))); + {QString::fromStdString(msg.from_device)})); flow->setEventId(event_id_.toStdString()); @@ -801,7 +833,7 @@ DeviceVerificationFlow::NewToDeviceVerification(QObject *parent_, QString txn_id_) { QSharedPointer flow(new DeviceVerificationFlow( - parent_, Type::ToDevice, nullptr, other_user_, QString::fromStdString(msg.from_device))); + parent_, Type::ToDevice, nullptr, other_user_, {QString::fromStdString(msg.from_device)})); flow->transaction_id = txn_id_.toStdString(); if (std::find(msg.methods.begin(), @@ -819,7 +851,7 @@ DeviceVerificationFlow::NewToDeviceVerification(QObject *parent_, QString txn_id_) { QSharedPointer flow(new DeviceVerificationFlow( - parent_, Type::ToDevice, nullptr, other_user_, QString::fromStdString(msg.from_device))); + parent_, Type::ToDevice, nullptr, other_user_, {QString::fromStdString(msg.from_device)})); flow->transaction_id = txn_id_.toStdString(); flow->handleStartMessage(msg, ""); @@ -832,15 +864,19 @@ DeviceVerificationFlow::InitiateUserVerification(QObject *parent_, QString userid) { QSharedPointer flow( - new DeviceVerificationFlow(parent_, Type::RoomMsg, timelineModel_, userid, "")); + new DeviceVerificationFlow(parent_, Type::RoomMsg, timelineModel_, userid, {})); flow->sender = true; return flow; } QSharedPointer -DeviceVerificationFlow::InitiateDeviceVerification(QObject *parent_, QString userid, QString device) +DeviceVerificationFlow::InitiateDeviceVerification(QObject *parent_, + QString userid, + std::vector devices) { + assert(!devices.empty()); + QSharedPointer flow( - new DeviceVerificationFlow(parent_, Type::ToDevice, nullptr, userid, device)); + new DeviceVerificationFlow(parent_, Type::ToDevice, nullptr, userid, devices)); flow->sender = true; flow->transaction_id = http::client()->generate_txn_id(); diff --git a/src/encryption/DeviceVerificationFlow.h b/src/encryption/DeviceVerificationFlow.h index f71fa337..55713def 100644 --- a/src/encryption/DeviceVerificationFlow.h +++ b/src/encryption/DeviceVerificationFlow.h @@ -120,9 +120,8 @@ public: QString txn_id_); static QSharedPointer InitiateUserVerification(QObject *parent_, TimelineModel *timelineModel_, QString userid); - static QSharedPointer InitiateDeviceVerification(QObject *parent, - QString userid, - QString device); + static QSharedPointer + InitiateDeviceVerification(QObject *parent, QString userid, std::vector devices); // getters QString state(); @@ -161,7 +160,7 @@ private: DeviceVerificationFlow::Type flow_type, TimelineModel *model, QString userID, - QString deviceId_); + std::vector deviceIds_); void setState(State state) { if (state != state_) { @@ -196,6 +195,7 @@ private: Type type; mtx::identifiers::User toClient; QString deviceId; + std::vector deviceIds; // public part of our master key, when trusted or empty std::string our_trusted_master_key; @@ -222,11 +222,12 @@ private: { if (this->type == DeviceVerificationFlow::Type::ToDevice) { mtx::requests::ToDeviceMessages body; - msg.transaction_id = this->transaction_id; - body[this->toClient][deviceId.toStdString()] = msg; + msg.transaction_id = this->transaction_id; + for (const auto &d : deviceIds) + body[this->toClient][d.toStdString()] = msg; http::client()->send_to_device( - this->transaction_id, body, [](mtx::http::RequestErr err) { + http::client()->generate_txn_id(), body, [](mtx::http::RequestErr err) { if (err) nhlog::net()->warn("failed to send verification to_device message: {} {}", err->matrix_error.error, diff --git a/src/encryption/Olm.cpp b/src/encryption/Olm.cpp index 14c97984..01a16ba7 100644 --- a/src/encryption/Olm.cpp +++ b/src/encryption/Olm.cpp @@ -1540,6 +1540,7 @@ request_cross_signing_keys() }); }; + request(mtx::secret_storage::secrets::cross_signing_master); request(mtx::secret_storage::secrets::cross_signing_self_signing); request(mtx::secret_storage::secrets::cross_signing_user_signing); request(mtx::secret_storage::secrets::megolm_backup_v1); @@ -1574,36 +1575,52 @@ download_cross_signing_keys() backup_key = secret; http::client()->secret_storage_secret( - secrets::cross_signing_self_signing, - [backup_key](Secret secret, mtx::http::RequestErr err) { - std::optional self_signing_key; + secrets::cross_signing_master, [backup_key](Secret secret, mtx::http::RequestErr err) { + std::optional master_key; if (!err) - self_signing_key = secret; + master_key = secret; http::client()->secret_storage_secret( - secrets::cross_signing_user_signing, - [backup_key, self_signing_key](Secret secret, mtx::http::RequestErr err) { - std::optional user_signing_key; + secrets::cross_signing_self_signing, + [backup_key, master_key](Secret secret, mtx::http::RequestErr err) { + std::optional self_signing_key; if (!err) - user_signing_key = secret; + self_signing_key = secret; - std::map> - secrets; + http::client()->secret_storage_secret( + secrets::cross_signing_user_signing, + [backup_key, self_signing_key, master_key](Secret secret, + mtx::http::RequestErr err) { + std::optional user_signing_key; + if (!err) + user_signing_key = secret; - if (backup_key && !backup_key->encrypted.empty()) - secrets[backup_key->encrypted.begin()->first][secrets::megolm_backup_v1] = - backup_key->encrypted.begin()->second; - if (self_signing_key && !self_signing_key->encrypted.empty()) - secrets[self_signing_key->encrypted.begin()->first] - [secrets::cross_signing_self_signing] = - self_signing_key->encrypted.begin()->second; - if (user_signing_key && !user_signing_key->encrypted.empty()) - secrets[user_signing_key->encrypted.begin()->first] - [secrets::cross_signing_user_signing] = - user_signing_key->encrypted.begin()->second; + std::map> + secrets; - for (const auto &[key, secrets] : secrets) - unlock_secrets(key, secrets); + if (backup_key && !backup_key->encrypted.empty()) + secrets[backup_key->encrypted.begin()->first] + [secrets::megolm_backup_v1] = + backup_key->encrypted.begin()->second; + + if (master_key && !master_key->encrypted.empty()) + secrets[master_key->encrypted.begin()->first] + [secrets::cross_signing_master] = + master_key->encrypted.begin()->second; + + if (self_signing_key && !self_signing_key->encrypted.empty()) + secrets[self_signing_key->encrypted.begin()->first] + [secrets::cross_signing_self_signing] = + self_signing_key->encrypted.begin()->second; + + if (user_signing_key && !user_signing_key->encrypted.empty()) + secrets[user_signing_key->encrypted.begin()->first] + [secrets::cross_signing_user_signing] = + user_signing_key->encrypted.begin()->second; + + for (const auto &[key, secrets] : secrets) + unlock_secrets(key, secrets); + }); }); }); }); diff --git a/src/encryption/SelfVerificationStatus.cpp b/src/encryption/SelfVerificationStatus.cpp index d75a2109..d4be4442 100644 --- a/src/encryption/SelfVerificationStatus.cpp +++ b/src/encryption/SelfVerificationStatus.cpp @@ -5,10 +5,12 @@ #include "SelfVerificationStatus.h" #include "Cache_p.h" +#include "ChatPage.h" #include "Logging.h" #include "MainWindow.h" #include "MatrixClient.h" #include "Olm.h" +#include "timeline/TimelineViewManager.h" #include "ui/UIA.h" #include @@ -196,6 +198,35 @@ void SelfVerificationStatus::verifyMasterKey() { nhlog::db()->info("Clicked verify master key"); + + const auto this_user = http::client()->user_id().to_string(); + + auto keys = cache::client()->userKeys(this_user); + const auto &sigs = keys->master_keys.signatures[this_user]; + + std::vector devices; + for (const auto &[dev, sig] : sigs) { + (void)sig; + + auto d = QString::fromStdString(dev); + if (d.startsWith("ed25519:")) { + d.remove("ed25519:"); + + if (keys->device_keys.count(d.toStdString())) + devices.push_back(std::move(d)); + } + } + + if (!devices.empty()) + ChatPage::instance()->timelineManager()->verificationManager()->verifyOneOfDevices( + QString::fromStdString(this_user), std::move(devices)); +} + +void +SelfVerificationStatus::verifyMasterKeyWithPassphrase() +{ + nhlog::db()->info("Clicked verify master key with passphrase"); + olm::download_cross_signing_keys(); } void @@ -207,9 +238,15 @@ SelfVerificationStatus::verifyUnverifiedDevices() void SelfVerificationStatus::invalidate() { + using namespace mtx::secret_storage; + nhlog::db()->info("Invalidating self verification status"); + this->hasSSSS_ = false; + emit hasSSSSChanged(); + auto keys = cache::client()->userKeys(http::client()->user_id().to_string()); - if (!keys) { + if (!keys || keys->device_keys.find(http::client()->device_id()) == keys->device_keys.end()) { + cache::client()->markUserKeysOutOfDate({http::client()->user_id().to_string()}); cache::client()->query_keys(http::client()->user_id().to_string(), [](const UserKeyCache &, mtx::http::RequestErr) {}); return; @@ -223,6 +260,14 @@ SelfVerificationStatus::invalidate() return; } + http::client()->secret_storage_secret(secrets::cross_signing_self_signing, + [this](Secret secret, mtx::http::RequestErr err) { + if (!err && !secret.encrypted.empty()) { + this->hasSSSS_ = true; + emit hasSSSSChanged(); + } + }); + auto verifStatus = cache::client()->verificationStatus(http::client()->user_id().to_string()); if (!verifStatus.user_verified) { diff --git a/src/encryption/SelfVerificationStatus.h b/src/encryption/SelfVerificationStatus.h index 8cb54df6..b1f315f4 100644 --- a/src/encryption/SelfVerificationStatus.h +++ b/src/encryption/SelfVerificationStatus.h @@ -11,6 +11,7 @@ class SelfVerificationStatus : public QObject Q_OBJECT Q_PROPERTY(Status status READ status NOTIFY statusChanged) + Q_PROPERTY(bool hasSSSS READ hasSSSS NOTIFY hasSSSSChanged) public: SelfVerificationStatus(QObject *o = nullptr); @@ -25,12 +26,15 @@ public: Q_INVOKABLE void setupCrosssigning(bool useSSSS, QString password, bool useOnlineKeyBackup); Q_INVOKABLE void verifyMasterKey(); + Q_INVOKABLE void verifyMasterKeyWithPassphrase(); Q_INVOKABLE void verifyUnverifiedDevices(); Status status() const { return status_; } + bool hasSSSS() const { return hasSSSS_; } signals: void statusChanged(); + void hasSSSSChanged(); void setupCompleted(); void showRecoveryKey(QString key); void setupFailed(QString message); @@ -40,4 +44,5 @@ public slots: private: Status status_ = AllVerified; + bool hasSSSS_ = true; }; diff --git a/src/encryption/VerificationManager.cpp b/src/encryption/VerificationManager.cpp index b9b51d35..f4c7ddf2 100644 --- a/src/encryption/VerificationManager.cpp +++ b/src/encryption/VerificationManager.cpp @@ -120,7 +120,16 @@ VerificationManager::removeVerificationFlow(DeviceVerificationFlow *flow) void VerificationManager::verifyDevice(QString userid, QString deviceid) { - auto flow = DeviceVerificationFlow::InitiateDeviceVerification(this, userid, deviceid); + auto flow = DeviceVerificationFlow::InitiateDeviceVerification(this, userid, {deviceid}); + this->dvList[flow->transactionId()] = flow; + emit newDeviceVerificationRequest(flow.data()); +} + +void +VerificationManager::verifyOneOfDevices(QString userid, std::vector deviceids) +{ + auto flow = + DeviceVerificationFlow::InitiateDeviceVerification(this, userid, std::move(deviceids)); this->dvList[flow->transactionId()] = flow; emit newDeviceVerificationRequest(flow.data()); } diff --git a/src/encryption/VerificationManager.h b/src/encryption/VerificationManager.h index d6a39ccf..da646c2f 100644 --- a/src/encryption/VerificationManager.h +++ b/src/encryption/VerificationManager.h @@ -27,6 +27,7 @@ public: Q_INVOKABLE void removeVerificationFlow(DeviceVerificationFlow *flow); void verifyUser(QString userid); void verifyDevice(QString userid, QString deviceid); + void verifyOneOfDevices(QString userid, std::vector deviceids); signals: void newDeviceVerificationRequest(DeviceVerificationFlow *flow); From 5bd6208c435caa4c3af10d3bd1ff4e7d96c0b62f Mon Sep 17 00:00:00 2001 From: Nicolas Werner Date: Sat, 30 Oct 2021 01:01:39 +0200 Subject: [PATCH 219/232] Some people consider Nheko not an AAAAA title --- resources/qml/SelfVerificationCheck.qml | 1 - 1 file changed, 1 deletion(-) diff --git a/resources/qml/SelfVerificationCheck.qml b/resources/qml/SelfVerificationCheck.qml index 0242a149..26af82b3 100644 --- a/resources/qml/SelfVerificationCheck.qml +++ b/resources/qml/SelfVerificationCheck.qml @@ -254,7 +254,6 @@ Item { Layout.alignment: Qt.AlignHCenter text: qsTr("verify") onClicked: { - console.log("AAAAA"); SelfVerificationStatus.verifyMasterKey(); verifyMasterKey.close(); } From 2aabe9dcacbef4f753761f6df840da4292561d11 Mon Sep 17 00:00:00 2001 From: Nicolas Werner Date: Mon, 1 Nov 2021 22:20:15 +0100 Subject: [PATCH 220/232] Prompt user when there are unverified devices --- resources/qml/RoomList.qml | 80 +++++++++++++++++++ resources/qml/SelfVerificationCheck.qml | 4 + .../NewVerificationRequest.qml | 5 +- src/Cache.cpp | 10 +-- src/Cache_p.h | 2 +- src/encryption/DeviceVerificationFlow.h | 2 + src/encryption/SelfVerificationStatus.cpp | 20 ++++- src/ui/Theme.cpp | 3 + src/ui/Theme.h | 4 +- 9 files changed, 120 insertions(+), 10 deletions(-) diff --git a/resources/qml/RoomList.qml b/resources/qml/RoomList.qml index 85087bc4..72ac49e1 100644 --- a/resources/qml/RoomList.qml +++ b/resources/qml/RoomList.qml @@ -502,6 +502,86 @@ Page { Layout.fillWidth: true } + Rectangle { + id: unverifiedStuffBubble + color: Qt.lighter(Nheko.theme.orange, verifyButtonHovered.hovered ? 1.2 : 1.0) + Layout.fillWidth: true + implicitHeight: explanation.height + Nheko.paddingMedium * 2 + visible: SelfVerificationStatus.status != SelfVerificationStatus.AllVerified + + RowLayout { + id: unverifiedStuffBubbleContainer + width: parent.width + height: explanation.height + Nheko.paddingMedium * 2 + spacing: 0 + + Label { + id: explanation + Layout.margins: Nheko.paddingMedium + Layout.rightMargin: Nheko.paddingSmall + color: Nheko.colors.buttonText + Layout.fillWidth: true + text: switch(SelfVerificationStatus.status) { + case SelfVerificationStatus.NoMasterKey: + //: Cross-signing setup has not run yet. + return qsTr("Encryption not set up"); + case SelfVerificationStatus.UnverifiedMasterKey: + //: The user just signed in with this device and hasn't verified their master key. + return qsTr("Unverified login"); + case SelfVerificationStatus.UnverifiedDevices: + //: There are unverified devices signed in to this account. + return qsTr("Please verify your other devices"); + default: + return "" + } + textFormat: Text.PlainText + wrapMode: Text.Wrap + } + + ImageButton { + id: closeUnverifiedBubble + + Layout.rightMargin: Nheko.paddingMedium + Layout.topMargin: Nheko.paddingMedium + Layout.alignment: Qt.AlignRight | Qt.AlignTop + hoverEnabled: true + width: fontMetrics.font.pixelSize + height: fontMetrics.font.pixelSize + image: ":/icons/icons/ui/remove-symbol.png" + ToolTip.visible: closeUnverifiedBubble.hovered + ToolTip.text: qsTr("Close") + onClicked: unverifiedStuffBubble.visible = false + } + + } + + HoverHandler { + id: verifyButtonHovered + enabled: !closeUnverifiedBubble.hovered + + acceptedDevices: PointerDevice.Mouse | PointerDevice.Stylus | PointerDevice.TouchPad + } + + TapHandler { + enabled: !closeUnverifiedBubble.hovered + acceptedButtons: Qt.LeftButton + onSingleTapped: { + if (SelfVerificationStatus.status == SelfVerificationStatus.UnverifiedDevices) { + SelfVerificationStatus.verifyUnverifiedDevices(); + } else { + SelfVerificationStatus.statusChanged(); + } + } + } + } + + Rectangle { + color: Nheko.theme.separator + height: 1 + Layout.fillWidth: true + visible: unverifiedStuffBubble.visible + } + } footer: ColumnLayout { diff --git a/resources/qml/SelfVerificationCheck.qml b/resources/qml/SelfVerificationCheck.qml index 26af82b3..a7502d8d 100644 --- a/resources/qml/SelfVerificationCheck.qml +++ b/resources/qml/SelfVerificationCheck.qml @@ -277,6 +277,10 @@ Item { bootstrapCrosssigning.open(); else if (SelfVerificationStatus.status == SelfVerificationStatus.UnverifiedMasterKey) verifyMasterKey.open(); + else { + bootstrapCrosssigning.close(); + verifyMasterKey.close(); + } } diff --git a/resources/qml/device-verification/NewVerificationRequest.qml b/resources/qml/device-verification/NewVerificationRequest.qml index 5ae2d25b..7e521605 100644 --- a/resources/qml/device-verification/NewVerificationRequest.qml +++ b/resources/qml/device-verification/NewVerificationRequest.qml @@ -23,7 +23,10 @@ Pane { text: { if (flow.sender) { if (flow.isSelfVerification) - return qsTr("To allow other users to see, which of your devices actually belong to you, you can verify them. This also allows key backup to work automatically. Verify %1 now?").arg(flow.deviceId); + if (flow.isMultiDeviceVerification) + return qsTr("To allow other users to see, which of your devices actually belong to you, you can verify them. This also allows key backup to work automatically. Verify an unverified device now? (Please make sure you have one of those devices available.)"); + else + return qsTr("To allow other users to see, which of your devices actually belong to you, you can verify them. This also allows key backup to work automatically. Verify %1 now?").arg(flow.deviceId); else return qsTr("To ensure that no malicious user can eavesdrop on your encrypted communications you can verify the other party."); } else { diff --git a/src/Cache.cpp b/src/Cache.cpp index 5b77a9d4..45cc642d 100644 --- a/src/Cache.cpp +++ b/src/Cache.cpp @@ -208,8 +208,7 @@ Cache::Cache(const QString &userId, QObject *parent) [this](const std::string &u) { if (u == localUserId_.toStdString()) { auto status = verificationStatus(u); - if (status.unverified_device_count || !status.user_verified) - emit selfUnverified(); + emit selfVerificationStatusChanged(); } }, Qt::QueuedConnection); @@ -4265,6 +4264,7 @@ Cache::markDeviceVerified(const std::string &user_id, const std::string &key) std::unique_lock lock(verification_storage.verification_storage_mtx); if (user_id == local_user) { std::swap(tmp, verification_storage.status); + verification_storage.status.clear(); } else { verification_storage.status.erase(user_id); } @@ -4274,9 +4274,8 @@ Cache::markDeviceVerified(const std::string &user_id, const std::string &key) (void)status; emit verificationStatusChanged(user); } - } else { - emit verificationStatusChanged(user_id); } + emit verificationStatusChanged(user_id); } void @@ -4316,9 +4315,8 @@ Cache::markDeviceUnverified(const std::string &user_id, const std::string &key) (void)status; emit verificationStatusChanged(user); } - } else { - emit verificationStatusChanged(user_id); } + emit verificationStatusChanged(user_id); } VerificationStatus diff --git a/src/Cache_p.h b/src/Cache_p.h index f7db77d4..651d73d7 100644 --- a/src/Cache_p.h +++ b/src/Cache_p.h @@ -310,7 +310,7 @@ signals: void removeNotification(const QString &room_id, const QString &event_id); void userKeysUpdate(const std::string &sync_token, const mtx::responses::QueryKeys &keyQuery); void verificationStatusChanged(const std::string &userid); - void selfUnverified(); + void selfVerificationStatusChanged(); void secretChanged(const std::string name); private: diff --git a/src/encryption/DeviceVerificationFlow.h b/src/encryption/DeviceVerificationFlow.h index 55713def..537adf31 100644 --- a/src/encryption/DeviceVerificationFlow.h +++ b/src/encryption/DeviceVerificationFlow.h @@ -69,6 +69,7 @@ class DeviceVerificationFlow : public QObject Q_PROPERTY(std::vector sasList READ getSasList CONSTANT) Q_PROPERTY(bool isDeviceVerification READ isDeviceVerification CONSTANT) Q_PROPERTY(bool isSelfVerification READ isSelfVerification CONSTANT) + Q_PROPERTY(bool isMultiDeviceVerification READ isMultiDeviceVerification CONSTANT) public: enum State @@ -139,6 +140,7 @@ public: return this->type == DeviceVerificationFlow::Type::ToDevice; } bool isSelfVerification() const; + bool isMultiDeviceVerification() const { return deviceIds.size() > 1; } void callback_fn(const UserKeyCache &res, mtx::http::RequestErr err, std::string user_id); diff --git a/src/encryption/SelfVerificationStatus.cpp b/src/encryption/SelfVerificationStatus.cpp index d4be4442..ebb6b548 100644 --- a/src/encryption/SelfVerificationStatus.cpp +++ b/src/encryption/SelfVerificationStatus.cpp @@ -20,7 +20,7 @@ SelfVerificationStatus::SelfVerificationStatus(QObject *o) { connect(MainWindow::instance(), &MainWindow::reload, this, [this] { connect(cache::client(), - &Cache::selfUnverified, + &Cache::selfVerificationStatusChanged, this, &SelfVerificationStatus::invalidate, Qt::UniqueConnection); @@ -233,6 +233,24 @@ void SelfVerificationStatus::verifyUnverifiedDevices() { nhlog::db()->info("Clicked verify unverified devices"); + const auto this_user = http::client()->user_id().to_string(); + + auto keys = cache::client()->userKeys(this_user); + auto verif = cache::client()->verificationStatus(this_user); + + if (!keys) + return; + + std::vector devices; + for (const auto &[device, keys] : keys->device_keys) { + (void)keys; + if (!verif.verified_devices.count(device)) + devices.push_back(QString::fromStdString(device)); + } + + if (!devices.empty()) + ChatPage::instance()->timelineManager()->verificationManager()->verifyOneOfDevices( + QString::fromStdString(this_user), std::move(devices)); } void diff --git a/src/ui/Theme.cpp b/src/ui/Theme.cpp index d6f0b72f..d7c92fb8 100644 --- a/src/ui/Theme.cpp +++ b/src/ui/Theme.cpp @@ -62,13 +62,16 @@ Theme::Theme(std::string_view theme) sidebarBackground_ = QColor("#233649"); alternateButton_ = QColor("#ccc"); red_ = QColor("#a82353"); + orange_ = QColor("#fcbe05"); } else if (theme == "dark") { sidebarBackground_ = QColor("#2d3139"); alternateButton_ = QColor("#414A59"); red_ = QColor("#a82353"); + orange_ = QColor("#fcc53a"); } else { sidebarBackground_ = p.window().color(); alternateButton_ = p.dark().color(); red_ = QColor("red"); + orange_ = QColor("orange"); } } diff --git a/src/ui/Theme.h b/src/ui/Theme.h index f3e7c287..4fef897d 100644 --- a/src/ui/Theme.h +++ b/src/ui/Theme.h @@ -62,6 +62,7 @@ class Theme : public QPalette Q_PROPERTY(QColor alternateButton READ alternateButton CONSTANT) Q_PROPERTY(QColor separator READ separator CONSTANT) Q_PROPERTY(QColor red READ red CONSTANT) + Q_PROPERTY(QColor orange READ orange CONSTANT) public: Theme() {} explicit Theme(std::string_view theme); @@ -71,7 +72,8 @@ public: QColor alternateButton() const { return alternateButton_; } QColor separator() const { return separator_; } QColor red() const { return red_; } + QColor orange() const { return orange_; } private: - QColor sidebarBackground_, separator_, red_, alternateButton_; + QColor sidebarBackground_, separator_, red_, orange_, alternateButton_; }; From 417cc07172141763ac8b79143457116d9a423a0a Mon Sep 17 00:00:00 2001 From: Nicolas Werner Date: Mon, 1 Nov 2021 22:35:32 +0100 Subject: [PATCH 221/232] Fix crash on logout --- src/Cache.cpp | 48 +++++++++++++++++++++++++----------------------- 1 file changed, 25 insertions(+), 23 deletions(-) diff --git a/src/Cache.cpp b/src/Cache.cpp index 45cc642d..5e28a0b8 100644 --- a/src/Cache.cpp +++ b/src/Cache.cpp @@ -969,35 +969,37 @@ Cache::nextBatchToken() void Cache::deleteData() { - this->databaseReady_ = false; - // TODO: We need to remove the env_ while not accepting new requests. - lmdb::dbi_close(env_, syncStateDb_); - lmdb::dbi_close(env_, roomsDb_); - lmdb::dbi_close(env_, invitesDb_); - lmdb::dbi_close(env_, readReceiptsDb_); - lmdb::dbi_close(env_, notificationsDb_); + if (this->databaseReady_) { + this->databaseReady_ = false; + // TODO: We need to remove the env_ while not accepting new requests. + lmdb::dbi_close(env_, syncStateDb_); + lmdb::dbi_close(env_, roomsDb_); + lmdb::dbi_close(env_, invitesDb_); + lmdb::dbi_close(env_, readReceiptsDb_); + lmdb::dbi_close(env_, notificationsDb_); - lmdb::dbi_close(env_, devicesDb_); - lmdb::dbi_close(env_, deviceKeysDb_); + lmdb::dbi_close(env_, devicesDb_); + lmdb::dbi_close(env_, deviceKeysDb_); - lmdb::dbi_close(env_, inboundMegolmSessionDb_); - lmdb::dbi_close(env_, outboundMegolmSessionDb_); - lmdb::dbi_close(env_, megolmSessionDataDb_); + lmdb::dbi_close(env_, inboundMegolmSessionDb_); + lmdb::dbi_close(env_, outboundMegolmSessionDb_); + lmdb::dbi_close(env_, megolmSessionDataDb_); - env_.close(); + env_.close(); - verification_storage.status.clear(); + verification_storage.status.clear(); - if (!cacheDirectory_.isEmpty()) { - QDir(cacheDirectory_).removeRecursively(); - nhlog::db()->info("deleted cache files from disk"); + if (!cacheDirectory_.isEmpty()) { + QDir(cacheDirectory_).removeRecursively(); + nhlog::db()->info("deleted cache files from disk"); + } + + deleteSecret(mtx::secret_storage::secrets::megolm_backup_v1); + deleteSecret(mtx::secret_storage::secrets::cross_signing_master); + deleteSecret(mtx::secret_storage::secrets::cross_signing_user_signing); + deleteSecret(mtx::secret_storage::secrets::cross_signing_self_signing); + deleteSecret("pickle_secret", true); } - - deleteSecret(mtx::secret_storage::secrets::megolm_backup_v1); - deleteSecret(mtx::secret_storage::secrets::cross_signing_master); - deleteSecret(mtx::secret_storage::secrets::cross_signing_user_signing); - deleteSecret(mtx::secret_storage::secrets::cross_signing_self_signing); - deleteSecret("pickle_secret", true); } //! migrates db to the current format From 8beeba8e48580f553b86cbaf70c274305f4a9062 Mon Sep 17 00:00:00 2001 From: Nicolas Werner Date: Tue, 2 Nov 2021 00:15:58 +0100 Subject: [PATCH 222/232] Fix hidden spaces hiding themselves fixes #745 --- src/timeline/RoomlistModel.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/timeline/RoomlistModel.cpp b/src/timeline/RoomlistModel.cpp index 2d60dcb3..c5bbd83c 100644 --- a/src/timeline/RoomlistModel.cpp +++ b/src/timeline/RoomlistModel.cpp @@ -851,7 +851,7 @@ FilteredRoomlistModel::filterAcceptsRow(int sourceRow, const QModelIndex &) cons if (!hiddenSpaces.empty()) { for (const auto &t : parents) - if (hiddenSpaces.contains(t)) + if (t != filterStr && hiddenSpaces.contains(t)) return false; } From 12832b3c6439057c03c68aed8558022031da5bed Mon Sep 17 00:00:00 2001 From: Nicolas Werner Date: Tue, 2 Nov 2021 00:28:39 +0100 Subject: [PATCH 223/232] Fix loading spinner when switching to a fully loaded room fixes #754 --- src/timeline/EventStore.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/timeline/EventStore.cpp b/src/timeline/EventStore.cpp index d7296a7c..a1f4c67f 100644 --- a/src/timeline/EventStore.cpp +++ b/src/timeline/EventStore.cpp @@ -819,8 +819,10 @@ EventStore::decryptionError(std::string id) void EventStore::fetchMore() { - if (noMoreMessages) + if (noMoreMessages) { + emit fetchedMore(); return; + } mtx::http::MessagesOpts opts; opts.room_id = room_id_; From ae121f6021b051548220c7a71a6dd68cf1fac7e9 Mon Sep 17 00:00:00 2001 From: Nicolas Werner Date: Tue, 2 Nov 2021 00:41:07 +0100 Subject: [PATCH 224/232] Clear @room warning after sending message fixes #751 --- src/timeline/InputBar.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/timeline/InputBar.cpp b/src/timeline/InputBar.cpp index ed97a2ca..44df3411 100644 --- a/src/timeline/InputBar.cpp +++ b/src/timeline/InputBar.cpp @@ -170,6 +170,7 @@ InputBar::setText(QString newText) if (history_.size() == INPUT_HISTORY_SIZE) history_.pop_back(); + updateAtRoom(""); emit textChanged(newText); } void From 912df2920e17b5506028cb007923ab8a20f8f1eb Mon Sep 17 00:00:00 2001 From: Joseph Donofry Date: Mon, 1 Nov 2021 20:48:51 -0400 Subject: [PATCH 225/232] Update macOS notifications to use UserNotifications framework --- CMakeLists.txt | 2 +- src/notifications/Manager.h | 6 ++-- src/notifications/ManagerMac.cpp | 18 +++++++--- src/notifications/ManagerMac.mm | 57 +++++++++++++++++++++++--------- 4 files changed, 60 insertions(+), 23 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index a9bdeec1..520a6f95 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -591,7 +591,7 @@ include(Translations) set(TRANSLATION_DEPS ${LANG_QRC} ${QRC} ${QM_SRC}) if (APPLE) - set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -framework Foundation -framework Cocoa") + set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -framework Foundation -framework Cocoa -framework UserNotifications") set(SRC_FILES ${SRC_FILES} src/notifications/ManagerMac.mm src/notifications/ManagerMac.cpp src/emoji/MacHelper.mm) if(${CMAKE_VERSION} VERSION_GREATER_EQUAL "3.16.0") set_source_files_properties( src/notifications/ManagerMac.mm src/emoji/MacHelper.mm PROPERTIES SKIP_PRECOMPILE_HEADERS ON) diff --git a/src/notifications/Manager.h b/src/notifications/Manager.h index 4e24dd1b..da930296 100644 --- a/src/notifications/Manager.h +++ b/src/notifications/Manager.h @@ -77,10 +77,12 @@ private: private: // Objective-C(++) doesn't like to do lots of regular C++, so the actual notification // posting is split out - void objCxxPostNotification(const QString &title, + void objCxxPostNotification(const QString &room_name, + const QString &room_id, + const QString &event_id, const QString &subtitle, const QString &informativeText, - const QImage &bodyImage); + const QString &bodyImagePath); #endif #if defined(Q_OS_WINDOWS) diff --git a/src/notifications/ManagerMac.cpp b/src/notifications/ManagerMac.cpp index f69cec2c..30948dae 100644 --- a/src/notifications/ManagerMac.cpp +++ b/src/notifications/ManagerMac.cpp @@ -33,6 +33,9 @@ NotificationsManager::postNotification(const mtx::responses::Notification ¬if cache::displayName(QString::fromStdString(notification.room_id), QString::fromStdString(mtx::accessors::sender(notification.event))); + const auto room_id = QString::fromStdString(notification.room_id); + const auto event_id = QString::fromStdString(mtx::accessors::event_id(notification.event)); + const auto isEncrypted = std::get_if>( ¬ification.event) != nullptr; const auto isReply = utils::isReply(notification.event); @@ -41,7 +44,7 @@ NotificationsManager::postNotification(const mtx::responses::Notification ¬if const QString messageInfo = (isReply ? tr("%1 replied with an encrypted message") : tr("%1 sent an encrypted message")) .arg(sender); - objCxxPostNotification(room_name, messageInfo, "", QImage()); + objCxxPostNotification(room_name, room_id, event_id, messageInfo, "", ""); } else { const QString messageInfo = (isReply ? tr("%1 replied to a message") : tr("%1 sent a message")).arg(sender); @@ -49,12 +52,17 @@ NotificationsManager::postNotification(const mtx::responses::Notification ¬if MxcImageProvider::download( QString::fromStdString(mtx::accessors::url(notification.event)).remove("mxc://"), QSize(200, 80), - [this, notification, room_name, messageInfo](QString, QSize, QImage image, QString) { - objCxxPostNotification( - room_name, messageInfo, formatNotification(notification), image); + [this, notification, room_name, room_id, event_id, messageInfo]( + QString, QSize, QImage, QString imgPath) { + objCxxPostNotification(room_name, + room_id, + event_id, + messageInfo, + formatNotification(notification), + imgPath); }); else objCxxPostNotification( - room_name, messageInfo, formatNotification(notification), QImage()); + room_name, room_id, event_id, messageInfo, formatNotification(notification), ""); } } diff --git a/src/notifications/ManagerMac.mm b/src/notifications/ManagerMac.mm index 33b7b6af..b5ef3bfe 100644 --- a/src/notifications/ManagerMac.mm +++ b/src/notifications/ManagerMac.mm @@ -2,38 +2,65 @@ #import #import +#import #include #include -@interface NSUserNotification (CFIPrivate) -- (void)set_identityImage:(NSImage *)image; -@end - NotificationsManager::NotificationsManager(QObject *parent): QObject(parent) { } void -NotificationsManager::objCxxPostNotification(const QString &title, +NotificationsManager::objCxxPostNotification(const QString &room_name, + const QString &room_id, + const QString &event_id, const QString &subtitle, const QString &informativeText, - const QImage &bodyImage) + const QString &bodyImagePath) { + UNAuthorizationOptions options = UNAuthorizationOptionAlert + UNAuthorizationOptionSound; + UNUserNotificationCenter *center = [UNUserNotificationCenter currentNotificationCenter]; - NSUserNotification *notif = [[NSUserNotification alloc] init]; + [center requestAuthorizationWithOptions:options + completionHandler:^(BOOL granted, NSError * _Nullable error) { + if (!granted) { + NSLog(@"No notification access"); + if (error) { + NSLog(@"%@",[error localizedDescription]); + } + } + }]; - notif.title = title.toNSString(); - notif.subtitle = subtitle.toNSString(); - notif.informativeText = informativeText.toNSString(); - notif.soundName = NSUserNotificationDefaultSoundName; + UNMutableNotificationContent *content = [[UNMutableNotificationContent alloc] init]; - if (!bodyImage.isNull()) - notif.contentImage = [[NSImage alloc] initWithCGImage: bodyImage.toCGImage() size: NSZeroSize]; + content.title = room_name.toNSString(); + content.subtitle = subtitle.toNSString(); + content.body = informativeText.toNSString(); + content.sound = [UNNotificationSound defaultSound]; + content.threadIdentifier = room_id.toNSString(); - [[NSUserNotificationCenter defaultUserNotificationCenter] deliverNotification: notif]; - [notif autorelease]; + if (!bodyImagePath.isEmpty()) { + NSError * _Nullable error; + NSURL *imageURL = [NSURL URLWithString:bodyImagePath.toNSString()]; + NSArray* attachments = [NSMutableArray array]; + UNNotificationAttachment *attachment = [UNNotificationAttachment attachmentWithIdentifier:@"" URL:imageURL options:nil error:&error]; + if (error) { + NSLog(@"%@",[error localizedDescription]); + } + content.attachments = [attachments arrayByAddingObject:attachment]; + } + + UNNotificationRequest *notificationRequest = [UNNotificationRequest requestWithIdentifier:event_id.toNSString() content:content trigger:nil]; + + [center addNotificationRequest:notificationRequest withCompletionHandler:^(NSError * _Nullable error) { + if (error != nil) { + NSLog(@"Unable to Add Notification Request"); + } + }]; + + [content autorelease]; } //unused From e3002f79930276505152b9e989f69c9227211cb0 Mon Sep 17 00:00:00 2001 From: Joe Donofry Date: Wed, 3 Nov 2021 02:42:19 +0000 Subject: [PATCH 226/232] Fix macOS m.image notif crash --- src/notifications/ManagerMac.mm | 41 +++++++++++++++++++++++++++------ 1 file changed, 34 insertions(+), 7 deletions(-) diff --git a/src/notifications/ManagerMac.mm b/src/notifications/ManagerMac.mm index b5ef3bfe..8669432b 100644 --- a/src/notifications/ManagerMac.mm +++ b/src/notifications/ManagerMac.mm @@ -7,6 +7,33 @@ #include #include +@interface UNNotificationAttachment (UNNotificationAttachmentAdditions) + + (UNNotificationAttachment *) createFromImageData:(NSData*)imgData identifier:(NSString *)imageFileIdentifier options:(NSDictionary*)attachmentOptions; +@end + +@implementation UNNotificationAttachment (UNNotificationAttachmentAdditions) + + (UNNotificationAttachment *) createFromImageData:(NSData*)imgData identifier:(NSString *)imageFileIdentifier options:(NSDictionary*)attachmentOptions { + NSFileManager *fileManager = [NSFileManager defaultManager]; + NSString *tmpSubFolderName = [[NSProcessInfo processInfo] globallyUniqueString]; + NSURL *tmpSubFolderURL = [NSURL fileURLWithPath:[NSTemporaryDirectory() stringByAppendingPathComponent:tmpSubFolderName] isDirectory:true]; + NSError *error = nil; + [fileManager createDirectoryAtURL:tmpSubFolderURL withIntermediateDirectories:true attributes:nil error:&error]; + if(error) { + NSLog(@"%@",[error localizedDescription]); + return nil; + } + NSURL *fileURL = [tmpSubFolderURL URLByAppendingPathComponent:imageFileIdentifier]; + [imgData writeToURL:fileURL atomically:true]; + UNNotificationAttachment *imageAttachment = [UNNotificationAttachment attachmentWithIdentifier:@"" URL:fileURL options:attachmentOptions error:&error]; + if(error) { + NSLog(@"%@",[error localizedDescription]); + return nil; + } + return imageAttachment; + + } +@end + NotificationsManager::NotificationsManager(QObject *parent): QObject(parent) { @@ -42,14 +69,14 @@ NotificationsManager::objCxxPostNotification(const QString &room_name, content.threadIdentifier = room_id.toNSString(); if (!bodyImagePath.isEmpty()) { - NSError * _Nullable error; - NSURL *imageURL = [NSURL URLWithString:bodyImagePath.toNSString()]; - NSArray* attachments = [NSMutableArray array]; - UNNotificationAttachment *attachment = [UNNotificationAttachment attachmentWithIdentifier:@"" URL:imageURL options:nil error:&error]; - if (error) { - NSLog(@"%@",[error localizedDescription]); + NSURL *imageURL = [NSURL fileURLWithPath:bodyImagePath.toNSString()]; + NSData *img = [NSData dataWithContentsOfURL:imageURL]; + NSArray *attachments = [NSMutableArray array]; + UNNotificationAttachment *attachment = [UNNotificationAttachment createFromImageData:img identifier:@"attachment_image.jpeg" options:nil]; + if (attachment) { + attachments = [NSMutableArray arrayWithObjects: attachment, nil]; + content.attachments = attachments; } - content.attachments = [attachments arrayByAddingObject:attachment]; } UNNotificationRequest *notificationRequest = [UNNotificationRequest requestWithIdentifier:event_id.toNSString() content:content trigger:nil]; From 211fd9d76cfd691eceb8c77297ee20fd0fde4bbb Mon Sep 17 00:00:00 2001 From: Nicolas Werner Date: Wed, 3 Nov 2021 18:36:12 +0100 Subject: [PATCH 227/232] Fix registration on matrix.org This was a bit of a journey: https://github.com/matrix-org/matrix-doc/pull/3471 But it should work now and we now use the UIAHandler everywhere. fixes #670 --- CMakeLists.txt | 2 +- io.github.NhekoReborn.Nheko.yaml | 2 +- resources/qml/Root.qml | 62 +++ .../qml/dialogs/PhoneNumberInputDialog.qml | 354 ++++++++++++++++++ resources/res.qrc | 1 + src/RegisterPage.cpp | 123 +----- src/RegisterPage.h | 4 - src/ui/UIA.cpp | 146 +++++++- src/ui/UIA.h | 17 + 9 files changed, 587 insertions(+), 124 deletions(-) create mode 100644 resources/qml/dialogs/PhoneNumberInputDialog.qml diff --git a/CMakeLists.txt b/CMakeLists.txt index 520a6f95..fdbdaaa2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -388,7 +388,7 @@ if(USE_BUNDLED_MTXCLIENT) FetchContent_Declare( MatrixClient GIT_REPOSITORY https://github.com/Nheko-Reborn/mtxclient.git - GIT_TAG e5284ccc9d902117bbe782b0be76fa272b7f0a90 + GIT_TAG 7fe7a70fcf7540beb6d7b4847e53a425de66c6bf ) set(BUILD_LIB_EXAMPLES OFF CACHE INTERNAL "") set(BUILD_LIB_TESTS OFF CACHE INTERNAL "") diff --git a/io.github.NhekoReborn.Nheko.yaml b/io.github.NhekoReborn.Nheko.yaml index 1ad5a0db..19a2ad4f 100644 --- a/io.github.NhekoReborn.Nheko.yaml +++ b/io.github.NhekoReborn.Nheko.yaml @@ -163,7 +163,7 @@ modules: buildsystem: cmake-ninja name: mtxclient sources: - - commit: e5284ccc9d902117bbe782b0be76fa272b7f0a90 + - commit: 7fe7a70fcf7540beb6d7b4847e53a425de66c6bf type: git url: https://github.com/Nheko-Reborn/mtxclient.git - config-opts: diff --git a/resources/qml/Root.qml b/resources/qml/Root.qml index 361099ed..7bacb98e 100644 --- a/resources/qml/Root.qml +++ b/resources/qml/Root.qml @@ -255,11 +255,73 @@ Page { } } + InputDialog { + id: uiaEmailPrompt + + title: UIA.title + prompt: qsTr("Please enter a valid email address to continue:") + onAccepted: (t) => { + return UIA.continueEmail(t); + } + } + + PhoneNumberInputDialog { + id: uiaPhoneNumberPrompt + + title: UIA.title + prompt: qsTr("Please enter a valid phone number to continue:") + onAccepted: (p, t) => { + return UIA.continuePhoneNumber(p, t); + } + } + + InputDialog { + id: uiaTokenPrompt + + title: UIA.title + prompt: qsTr("Please enter the token, which has been sent to you:") + onAccepted: (t) => { + return UIA.submit3pidToken(t); + } + } + + Platform.MessageDialog { + id: uiaErrorDialog + + buttons: Platform.MessageDialog.Ok + } + + Platform.MessageDialog { + id: uiaConfirmationLinkDialog + + buttons: Platform.MessageDialog.Ok + text: qsTr("Wait for the confirmation link to arrive, then continue.") + + onAccepted: UIA.continue3pidReceived() + } + + Connections { function onPassword() { console.log("UIA: password needed"); uiaPassPrompt.show(); } + function onEmail() { + uiaEmailPrompt.show(); + } + function onPhoneNumber() { + uiaPhoneNumberPrompt.show(); + } + function onPrompt3pidToken() { + uiaTokenPrompt.show(); + } + function onConfirm3pidToken() { + uiaConfirmationLinkDialog.open(); + } + function onError(msg) { + uiaErrorDialog.text = msg; + uiaErrorDialog.open(); + } target: UIA } diff --git a/resources/qml/dialogs/PhoneNumberInputDialog.qml b/resources/qml/dialogs/PhoneNumberInputDialog.qml new file mode 100644 index 00000000..0445b7a6 --- /dev/null +++ b/resources/qml/dialogs/PhoneNumberInputDialog.qml @@ -0,0 +1,354 @@ +// SPDX-FileCopyrightText: 2021 Nheko Contributors +// SPDX-FileCopyrightText: 2021 Mirian Margiani +// +// SPDX-License-Identifier: GPL-3.0-or-later + +import ".." +import QtQuick 2.12 +import QtQuick.Controls 2.5 +import QtQuick.Layouts 1.3 +import im.nheko 1.0 + +ApplicationWindow { + id: inputDialog + + property alias prompt: promptLabel.text + property alias echoMode: statusInput.echoMode + property var onAccepted: undefined + + modality: Qt.NonModal + flags: Qt.Dialog + Component.onCompleted: Nheko.reparent(inputDialog) + width: 350 + height: fontMetrics.lineSpacing * 7 + + GridLayout { + rowSpacing: Nheko.paddingMedium + columnSpacing: Nheko.paddingMedium + anchors.margins: Nheko.paddingMedium + anchors.fill: parent + columns: 2 + + Label { + Layout.columnSpan: 2 + id: promptLabel + + color: Nheko.colors.text + } + + ComboBox { + id: numberPrefix + editable: false + delegate: ItemDelegate { + text: n + " ("+ p +")" + } + // taken from https://gitlab.com/whisperfish/whisperfish/-/blob/master/qml/js/countries.js + model: ListModel{//n=name,i=ISO,p=prefix -- see countries.js.md for source + ListElement{n:"Afghanistan";i:"AF";p:"+93"} + ListElement{n:"Åland Islands";i:"AX";p:"+358 18"} + ListElement{n:"Albania";i:"AL";p:"+355"} + ListElement{n:"Algeria";i:"DZ";p:"+213"} + ListElement{n:"American Samoa";i:"AS";p:"+1 684"} + ListElement{n:"Andorra";i:"AD";p:"+376"} + ListElement{n:"Angola";i:"AO";p:"+244"} + ListElement{n:"Anguilla";i:"AI";p:"+1 264"} + ListElement{n:"Antigua and Barbuda";i:"AG";p:"+1 268"} + ListElement{n:"Argentina";i:"AR";p:"+54"} + ListElement{n:"Armenia";i:"AM";p:"+374"} + ListElement{n:"Aruba";i:"AW";p:"+297"} + ListElement{n:"Ascension";i:"SH";p:"+247"} + ListElement{n:"Australia";i:"AU";p:"+61"} + ListElement{n:"Australian Antarctic Territory";i:"AQ";p:"+672 1"} + //ListElement{n:"Australian External Territories";i:"";p:"+672"} // NO ISO + ListElement{n:"Austria";i:"AT";p:"+43"} + ListElement{n:"Azerbaijan";i:"AZ";p:"+994"} + ListElement{n:"Bahamas";i:"BS";p:"+1 242"} + ListElement{n:"Bahrain";i:"BH";p:"+973"} + ListElement{n:"Bangladesh";i:"BD";p:"+880"} + ListElement{n:"Barbados";i:"BB";p:"+1 246"} + ListElement{n:"Barbuda";i:"AG";p:"+1 268"} + ListElement{n:"Belarus";i:"BY";p:"+375"} + ListElement{n:"Belgium";i:"BE";p:"+32"} + ListElement{n:"Belize";i:"BZ";p:"+501"} + ListElement{n:"Benin";i:"BJ";p:"+229"} + ListElement{n:"Bermuda";i:"BM";p:"+1 441"} + ListElement{n:"Bhutan";i:"BT";p:"+975"} + ListElement{n:"Bolivia";i:"BO";p:"+591"} + ListElement{n:"Bonaire";i:"BQ";p:"+599 7"} + ListElement{n:"Bosnia and Herzegovina";i:"BA";p:"+387"} + ListElement{n:"Botswana";i:"BW";p:"+267"} + ListElement{n:"Brazil";i:"BR";p:"+55"} + ListElement{n:"British Indian Ocean Territory";i:"IO";p:"+246"} + ListElement{n:"Brunei Darussalam";i:"BN";p:"+673"} + ListElement{n:"Bulgaria";i:"BG";p:"+359"} + ListElement{n:"Burkina Faso";i:"BF";p:"+226"} + ListElement{n:"Burundi";i:"BI";p:"+257"} + ListElement{n:"Cambodia";i:"KH";p:"+855"} + ListElement{n:"Cameroon";i:"CM";p:"+237"} + ListElement{n:"Canada";i:"CA";p:"+1"} + ListElement{n:"Cape Verde";i:"CV";p:"+238"} + //ListElement{n:"Caribbean Netherlands";i:"";p:"+599 3"} // NO ISO + //ListElement{n:"Caribbean Netherlands";i:"";p:"+599 4"} // NO ISO + //ListElement{n:"Caribbean Netherlands";i:"";p:"+599 7"} // NO ISO + ListElement{n:"Cayman Islands";i:"KY";p:"+1 345"} + ListElement{n:"Central African Republic";i:"CF";p:"+236"} + ListElement{n:"Chad";i:"TD";p:"+235"} + ListElement{n:"Chatham Island (New Zealand)";i:"NZ";p:"+64"} + ListElement{n:"Chile";i:"CL";p:"+56"} + ListElement{n:"China";i:"CN";p:"+86"} + ListElement{n:"Christmas Island";i:"CX";p:"+61 89164"} + ListElement{n:"Cocos (Keeling) Islands";i:"CC";p:"+61 89162"} + ListElement{n:"Colombia";i:"CO";p:"+57"} + ListElement{n:"Comoros";i:"KM";p:"+269"} + ListElement{n:"Congo (Democratic Republic of the)";i:"CD";p:"+243"} + ListElement{n:"Congo";i:"CG";p:"+242"} + ListElement{n:"Cook Islands";i:"CK";p:"+682"} + ListElement{n:"Costa Rica";i:"CR";p:"+506"} + ListElement{n:"Côte d'Ivoire";i:"CI";p:"+225"} + ListElement{n:"Croatia";i:"HR";p:"+385"} + ListElement{n:"Cuba";i:"CU";p:"+53"} + ListElement{n:"Curaçao";i:"CW";p:"+599 9"} + ListElement{n:"Cyprus";i:"CY";p:"+357"} + ListElement{n:"Czech Republic";i:"CZ";p:"+420"} + ListElement{n:"Denmark";i:"DK";p:"+45"} + //ListElement{n:"Diego Garcia";i:"";p:"+246"} // NO ISO, OCC. BY GB + ListElement{n:"Djibouti";i:"DJ";p:"+253"} + ListElement{n:"Dominica";i:"DM";p:"+1 767"} + ListElement{n:"Dominican Republic";i:"DO";p:"+1 809"} + ListElement{n:"Dominican Republic";i:"DO";p:"+1 829"} + ListElement{n:"Dominican Republic";i:"DO";p:"+1 849"} + ListElement{n:"Easter Island";i:"CL";p:"+56"} + ListElement{n:"Ecuador";i:"EC";p:"+593"} + ListElement{n:"Egypt";i:"EG";p:"+20"} + ListElement{n:"El Salvador";i:"SV";p:"+503"} + ListElement{n:"Equatorial Guinea";i:"GQ";p:"+240"} + ListElement{n:"Eritrea";i:"ER";p:"+291"} + ListElement{n:"Estonia";i:"EE";p:"+372"} + ListElement{n:"eSwatini";i:"SZ";p:"+268"} + ListElement{n:"Ethiopia";i:"ET";p:"+251"} + ListElement{n:"Falkland Islands (Malvinas)";i:"FK";p:"+500"} + ListElement{n:"Faroe Islands";i:"FO";p:"+298"} + ListElement{n:"Fiji";i:"FJ";p:"+679"} + ListElement{n:"Finland";i:"FI";p:"+358"} + ListElement{n:"France";i:"FR";p:"+33"} + //ListElement{n:"French Antilles";i:"";p:"+596"} // NO ISO + ListElement{n:"French Guiana";i:"GF";p:"+594"} + ListElement{n:"French Polynesia";i:"PF";p:"+689"} + ListElement{n:"Gabon";i:"GA";p:"+241"} + ListElement{n:"Gambia";i:"GM";p:"+220"} + ListElement{n:"Georgia";i:"GE";p:"+995"} + ListElement{n:"Germany";i:"DE";p:"+49"} + ListElement{n:"Ghana";i:"GH";p:"+233"} + ListElement{n:"Gibraltar";i:"GI";p:"+350"} + ListElement{n:"Greece";i:"GR";p:"+30"} + ListElement{n:"Greenland";i:"GL";p:"+299"} + ListElement{n:"Grenada";i:"GD";p:"+1 473"} + ListElement{n:"Guadeloupe";i:"GP";p:"+590"} + ListElement{n:"Guam";i:"GU";p:"+1 671"} + ListElement{n:"Guatemala";i:"GT";p:"+502"} + ListElement{n:"Guernsey";i:"GG";p:"+44 1481"} + ListElement{n:"Guernsey";i:"GG";p:"+44 7781"} + ListElement{n:"Guernsey";i:"GG";p:"+44 7839"} + ListElement{n:"Guernsey";i:"GG";p:"+44 7911"} + ListElement{n:"Guinea-Bissau";i:"GW";p:"+245"} + ListElement{n:"Guinea";i:"GN";p:"+224"} + ListElement{n:"Guyana";i:"GY";p:"+592"} + ListElement{n:"Haiti";i:"HT";p:"+509"} + ListElement{n:"Honduras";i:"HN";p:"+504"} + ListElement{n:"Hong Kong";i:"HK";p:"+852"} + ListElement{n:"Hungary";i:"HU";p:"+36"} + ListElement{n:"Iceland";i:"IS";p:"+354"} + ListElement{n:"India";i:"IN";p:"+91"} + ListElement{n:"Indonesia";i:"ID";p:"+62"} + ListElement{n:"Iran";i:"IR";p:"+98"} + ListElement{n:"Iraq";i:"IQ";p:"+964"} + ListElement{n:"Ireland";i:"IE";p:"+353"} + ListElement{n:"Isle of Man";i:"IM";p:"+44 1624"} + ListElement{n:"Isle of Man";i:"IM";p:"+44 7524"} + ListElement{n:"Isle of Man";i:"IM";p:"+44 7624"} + ListElement{n:"Isle of Man";i:"IM";p:"+44 7924"} + ListElement{n:"Israel";i:"IL";p:"+972"} + ListElement{n:"Italy";i:"IT";p:"+39"} + ListElement{n:"Jamaica";i:"JM";p:"+1 876"} + ListElement{n:"Jan Mayen";i:"SJ";p:"+47 79"} + ListElement{n:"Japan";i:"JP";p:"+81"} + ListElement{n:"Jersey";i:"JE";p:"+44 1534"} + ListElement{n:"Jordan";i:"JO";p:"+962"} + ListElement{n:"Kazakhstan";i:"KZ";p:"+7 6"} + ListElement{n:"Kazakhstan";i:"KZ";p:"+7 7"} + ListElement{n:"Kenya";i:"KE";p:"+254"} + ListElement{n:"Kiribati";i:"KI";p:"+686"} + ListElement{n:"Korea (North)";i:"KP";p:"+850"} + ListElement{n:"Korea (South)";i:"KR";p:"+82"} + ListElement{n:"Kosovo";i:"XK";p:"+383"} // TEMP. CODE + ListElement{n:"Kuwait";i:"KW";p:"+965"} + ListElement{n:"Kyrgyzstan";i:"KG";p:"+996"} + ListElement{n:"Laos";i:"LA";p:"+856"} + ListElement{n:"Latvia";i:"LV";p:"+371"} + ListElement{n:"Lebanon";i:"LB";p:"+961"} + ListElement{n:"Lesotho";i:"LS";p:"+266"} + ListElement{n:"Liberia";i:"LR";p:"+231"} + ListElement{n:"Libya";i:"LY";p:"+218"} + ListElement{n:"Liechtenstein";i:"LI";p:"+423"} + ListElement{n:"Lithuania";i:"LT";p:"+370"} + ListElement{n:"Luxembourg";i:"LU";p:"+352"} + ListElement{n:"Macau (Macao)";i:"MO";p:"+853"} + ListElement{n:"Madagascar";i:"MG";p:"+261"} + ListElement{n:"Malawi";i:"MW";p:"+265"} + ListElement{n:"Malaysia";i:"MY";p:"+60"} + ListElement{n:"Maldives";i:"MV";p:"+960"} + ListElement{n:"Mali";i:"ML";p:"+223"} + ListElement{n:"Malta";i:"MT";p:"+356"} + ListElement{n:"Marshall Islands";i:"MH";p:"+692"} + ListElement{n:"Martinique";i:"MQ";p:"+596"} + ListElement{n:"Mauritania";i:"MR";p:"+222"} + ListElement{n:"Mauritius";i:"MU";p:"+230"} + ListElement{n:"Mayotte";i:"YT";p:"+262 269"} + ListElement{n:"Mayotte";i:"YT";p:"+262 639"} + ListElement{n:"Mexico";i:"MX";p:"+52"} + ListElement{n:"Micronesia (Federated States of)";i:"FM";p:"+691"} + ListElement{n:"Midway Island (USA)";i:"US";p:"+1 808"} + ListElement{n:"Moldova";i:"MD";p:"+373"} + ListElement{n:"Monaco";i:"MC";p:"+377"} + ListElement{n:"Mongolia";i:"MN";p:"+976"} + ListElement{n:"Montenegro";i:"ME";p:"+382"} + ListElement{n:"Montserrat";i:"MS";p:"+1 664"} + ListElement{n:"Morocco";i:"MA";p:"+212"} + ListElement{n:"Mozambique";i:"MZ";p:"+258"} + ListElement{n:"Myanmar";i:"MM";p:"+95"} + ListElement{n:"Nagorno-Karabakh";i:"AZ";p:"+374 47"} // NO OWN ISO, DISPUTED + ListElement{n:"Nagorno-Karabakh";i:"AZ";p:"+374 97"} // NO OWN ISO, DISPUTED + ListElement{n:"Namibia";i:"NA";p:"+264"} + ListElement{n:"Nauru";i:"NR";p:"+674"} + ListElement{n:"Nepal";i:"NP";p:"+977"} + ListElement{n:"Netherlands";i:"NL";p:"+31"} + ListElement{n:"Nevis";i:"KN";p:"+1 869"} + ListElement{n:"New Caledonia";i:"NC";p:"+687"} + ListElement{n:"New Zealand";i:"NZ";p:"+64"} + ListElement{n:"Nicaragua";i:"NI";p:"+505"} + ListElement{n:"Nigeria";i:"NG";p:"+234"} + ListElement{n:"Niger";i:"NE";p:"+227"} + ListElement{n:"Niue";i:"NU";p:"+683"} + ListElement{n:"Norfolk Island";i:"NF";p:"+672 3"} + ListElement{n:"Northern Cyprus";i:"CY";p:"+90 392"} // OCC. BY TR + ListElement{n:"Northern Ireland";i:"GB";p:"+44 28"} + ListElement{n:"Northern Mariana Islands";i:"MP";p:"+1 670"} + ListElement{n:"North Macedonia";i:"MK";p:"+389"} + ListElement{n:"Norway";i:"NO";p:"+47"} + ListElement{n:"Oman";i:"OM";p:"+968"} + ListElement{n:"Pakistan";i:"PK";p:"+92"} + ListElement{n:"Palau";i:"PW";p:"+680"} + ListElement{n:"Palestine (State of)";i:"PS";p:"+970"} + ListElement{n:"Panama";i:"PA";p:"+507"} + ListElement{n:"Papua New Guinea";i:"PG";p:"+675"} + ListElement{n:"Paraguay";i:"PY";p:"+595"} + ListElement{n:"Peru";i:"PE";p:"+51"} + ListElement{n:"Philippines";i:"PH";p:"+63"} + ListElement{n:"Pitcairn Islands";i:"PN";p:"+64"} + ListElement{n:"Poland";i:"PL";p:"+48"} + ListElement{n:"Portugal";i:"PT";p:"+351"} + ListElement{n:"Puerto Rico";i:"PR";p:"+1 787"} + ListElement{n:"Puerto Rico";i:"PR";p:"+1 939"} + ListElement{n:"Qatar";i:"QA";p:"+974"} + ListElement{n:"Réunion";i:"RE";p:"+262"} + ListElement{n:"Romania";i:"RO";p:"+40"} + ListElement{n:"Russia";i:"RU";p:"+7"} + ListElement{n:"Rwanda";i:"RW";p:"+250"} + ListElement{n:"Saba";i:"BQ";p:"+599 4"} + ListElement{n:"Saint Barthélemy";i:"BL";p:"+590"} + ListElement{n:"Saint Helena";i:"SH";p:"+290"} + ListElement{n:"Saint Kitts and Nevis";i:"KN";p:"+1 869"} + ListElement{n:"Saint Lucia";i:"LC";p:"+1 758"} + ListElement{n:"Saint Martin (France)";i:"MF";p:"+590"} + ListElement{n:"Saint Pierre and Miquelon";i:"PM";p:"+508"} + ListElement{n:"Saint Vincent and the Grenadines";i:"VC";p:"+1 784"} + ListElement{n:"Samoa";i:"WS";p:"+685"} + ListElement{n:"San Marino";i:"SM";p:"+378"} + ListElement{n:"São Tomé and Príncipe";i:"ST";p:"+239"} + ListElement{n:"Saudi Arabia";i:"SA";p:"+966"} + ListElement{n:"Senegal";i:"SN";p:"+221"} + ListElement{n:"Serbia";i:"RS";p:"+381"} + ListElement{n:"Seychelles";i:"SC";p:"+248"} + ListElement{n:"Sierra Leone";i:"SL";p:"+232"} + ListElement{n:"Singapore";i:"SG";p:"+65"} + ListElement{n:"Sint Eustatius";i:"BQ";p:"+599 3"} + ListElement{n:"Sint Maarten (Netherlands)";i:"SX";p:"+1 721"} + ListElement{n:"Slovakia";i:"SK";p:"+421"} + ListElement{n:"Slovenia";i:"SI";p:"+386"} + ListElement{n:"Solomon Islands";i:"SB";p:"+677"} + ListElement{n:"Somalia";i:"SO";p:"+252"} + ListElement{n:"South Africa";i:"ZA";p:"+27"} + ListElement{n:"South Georgia and the South Sandwich Islands";i:"GS";p:"+500"} + ListElement{n:"South Ossetia";i:"GE";p:"+995 34"} // NO OWN ISO, DISPUTED + ListElement{n:"South Sudan";i:"SS";p:"+211"} + ListElement{n:"Spain";i:"ES";p:"+34"} + ListElement{n:"Sri Lanka";i:"LK";p:"+94"} + ListElement{n:"Sudan";i:"SD";p:"+249"} + ListElement{n:"Suriname";i:"SR";p:"+597"} + ListElement{n:"Svalbard";i:"SJ";p:"+47 79"} + ListElement{n:"Sweden";i:"SE";p:"+46"} + ListElement{n:"Switzerland";i:"CH";p:"+41"} + ListElement{n:"Syria";i:"SY";p:"+963"} + ListElement{n:"Taiwan";i:"SJ";p:"+886"} + ListElement{n:"Tajikistan";i:"TJ";p:"+992"} + ListElement{n:"Tanzania";i:"TZ";p:"+255"} + ListElement{n:"Thailand";i:"TH";p:"+66"} + ListElement{n:"Timor-Leste";i:"TL";p:"+670"} + ListElement{n:"Togo";i:"TG";p:"+228"} + ListElement{n:"Tokelau";i:"TK";p:"+690"} + ListElement{n:"Tonga";i:"TO";p:"+676"} + ListElement{n:"Transnistria";i:"MD";p:"+373 2"} + ListElement{n:"Transnistria";i:"MD";p:"+373 5"} + ListElement{n:"Trinidad and Tobago";i:"TT";p:"+1 868"} + ListElement{n:"Tristan da Cunha";i:"SH";p:"+290 8"} + ListElement{n:"Tunisia";i:"TN";p:"+216"} + ListElement{n:"Turkey";i:"TR";p:"+90"} + ListElement{n:"Turkmenistan";i:"TM";p:"+993"} + ListElement{n:"Turks and Caicos Islands";i:"TC";p:"+1 649"} + ListElement{n:"Tuvalu";i:"TV";p:"+688"} + ListElement{n:"Uganda";i:"UG";p:"+256"} + ListElement{n:"Ukraine";i:"UA";p:"+380"} + ListElement{n:"United Arab Emirates";i:"AE";p:"+971"} + ListElement{n:"United Kingdom";i:"GB";p:"+44"} + ListElement{n:"United States";i:"US";p:"+1"} + ListElement{n:"Uruguay";i:"UY";p:"+598"} + ListElement{n:"Uzbekistan";i:"UZ";p:"+998"} + ListElement{n:"Vanuatu";i:"VU";p:"+678"} + ListElement{n:"Vatican City State (Holy See)";i:"VA";p:"+379"} + ListElement{n:"Vatican City State (Holy See)";i:"VA";p:"+39 06 698"} + ListElement{n:"Venezuela";i:"VE";p:"+58"} + ListElement{n:"Vietnam";i:"VN";p:"+84"} + ListElement{n:"Virgin Islands (British)";i:"VG";p:"+1 284"} + ListElement{n:"Virgin Islands (US)";i:"VI";p:"+1 340"} + ListElement{n:"Wake Island (USA)";i:"US";p:"+1 808"} + ListElement{n:"Wallis and Futuna";i:"WF";p:"+681"} + ListElement{n:"Yemen";i:"YE";p:"+967"} + ListElement{n:"Zambia";i:"ZM";p:"+260"} + ListElement{n:"Zanzibar";i:"TZ";p:"+255 24"} // NO OWN ISO, DISPUTED? + ListElement{n:"Zimbabwe";i:"ZW";p:"+263"} + } + + } + + MatrixTextField { + id: statusInput + + Layout.fillWidth: true + } + + } + + footer: DialogButtonBox { + standardButtons: DialogButtonBox.Ok | DialogButtonBox.Cancel + onAccepted: { + if (inputDialog.onAccepted) + inputDialog.onAccepted(numberPrefix.model.get(numberPrefix.currentIndex).i, statusInput.text); + + inputDialog.close(); + } + onRejected: { + inputDialog.close(); + } + } + +} diff --git a/resources/res.qrc b/resources/res.qrc index f11d0b4a..66b77205 100644 --- a/resources/res.qrc +++ b/resources/res.qrc @@ -165,6 +165,7 @@ qml/device-verification/Waiting.qml qml/dialogs/ImagePackEditorDialog.qml qml/dialogs/ImagePackSettingsDialog.qml + qml/dialogs/PhoneNumberInputDialog.qml qml/dialogs/InputDialog.qml qml/dialogs/InviteDialog.qml qml/dialogs/JoinRoomDialog.qml diff --git a/src/RegisterPage.cpp b/src/RegisterPage.cpp index 0204a307..271a7fc2 100644 --- a/src/RegisterPage.cpp +++ b/src/RegisterPage.cpp @@ -23,6 +23,7 @@ #include "ui/FlatButton.h" #include "ui/RaisedButton.h" #include "ui/TextField.h" +#include "ui/UIA.h" #include "dialogs/FallbackAuth.h" #include "dialogs/ReCaptcha.h" @@ -178,8 +179,6 @@ RegisterPage::RegisterPage(QWidget *parent) connect(this, &RegisterPage::wellKnownLookup, this, &RegisterPage::doWellKnownLookup); connect(this, &RegisterPage::versionsCheck, this, &RegisterPage::doVersionsCheck); connect(this, &RegisterPage::registration, this, &RegisterPage::doRegistration); - connect(this, &RegisterPage::UIA, this, &RegisterPage::doUIA); - connect(this, &RegisterPage::registrationWithAuth, this, &RegisterPage::doRegistrationWithAuth); } void @@ -277,18 +276,11 @@ RegisterPage::onRegisterButtonClicked() // // The sequence of events looks something like this: // - // dowellKnownLookup + // doKnownLookup // v // doVersionsCheck // v - // doRegistration - // v - // doUIA <-----------------+ - // v | More auth required - // doRegistrationWithAuth -+ - // | Success - // v - // registering + // doRegistration -> loops the UIAHandler until complete emit wellKnownLookup(); @@ -367,18 +359,12 @@ RegisterPage::doRegistration() if (checkUsername() && checkPassword() && checkPasswordConfirmation()) { auto username = username_input_->text().toStdString(); auto password = password_input_->text().toStdString(); - http::client()->registration(username, password, registrationCb()); - } -} - -void -RegisterPage::doRegistrationWithAuth(const mtx::user_interactive::Auth &auth) -{ - // These inputs should still be alright, but check just in case - if (checkUsername() && checkPassword() && checkPasswordConfirmation()) { - auto username = username_input_->text().toStdString(); - auto password = password_input_->text().toStdString(); - http::client()->registration(username, password, auth, registrationCb()); + connect(UIA::instance(), &UIA::error, this, [this](QString msg) { + showError(msg); + disconnect(UIA::instance(), &UIA::error, this, nullptr); + }); + http::client()->registration( + username, password, ::UIA::instance()->genericHandler("Registration"), registrationCb()); } } @@ -392,6 +378,7 @@ RegisterPage::registrationCb() http::client()->set_user(res.user_id); http::client()->set_access_token(res.access_token); emit registerOk(); + disconnect(UIA::instance(), &UIA::error, this, nullptr); return; } @@ -403,11 +390,7 @@ RegisterPage::registrationCb() static_cast(err->status_code), err->matrix_error.error); showError(QString::fromStdString(err->matrix_error.error)); - return; } - - // Attempt to complete a UIA stage - emit UIA(err->matrix_error.unauthorized); return; } @@ -419,92 +402,6 @@ RegisterPage::registrationCb() }; } -void -RegisterPage::doUIA(const mtx::user_interactive::Unauthorized &unauthorized) -{ - auto completed_stages = unauthorized.completed; - auto flows = unauthorized.flows; - auto session = - unauthorized.session.empty() ? http::client()->generate_txn_id() : unauthorized.session; - - nhlog::ui()->info("Completed stages: {}", completed_stages.size()); - - if (!completed_stages.empty()) { - // Get rid of all flows which don't start with the sequence of - // stages that have already been completed. - flows.erase(std::remove_if(flows.begin(), - flows.end(), - [completed_stages](auto flow) { - if (completed_stages.size() > flow.stages.size()) - return true; - for (size_t f = 0; f < completed_stages.size(); f++) - if (completed_stages[f] != flow.stages[f]) - return true; - return false; - }), - flows.end()); - } - - if (flows.empty()) { - nhlog::ui()->error("No available registration flows!"); - showError(tr("No supported registration flows!")); - return; - } - - auto current_stage = flows.front().stages.at(completed_stages.size()); - - if (current_stage == mtx::user_interactive::auth_types::recaptcha) { - auto captchaDialog = new dialogs::ReCaptcha(QString::fromStdString(session), this); - - connect( - captchaDialog, &dialogs::ReCaptcha::confirmation, this, [this, session, captchaDialog]() { - captchaDialog->close(); - captchaDialog->deleteLater(); - doRegistrationWithAuth( - mtx::user_interactive::Auth{session, mtx::user_interactive::auth::Fallback{}}); - }); - - connect(captchaDialog, &dialogs::ReCaptcha::cancel, this, &RegisterPage::errorOccurred); - - QTimer::singleShot(1000, this, [captchaDialog]() { captchaDialog->show(); }); - - } else if (current_stage == mtx::user_interactive::auth_types::dummy) { - doRegistrationWithAuth( - mtx::user_interactive::Auth{session, mtx::user_interactive::auth::Dummy{}}); - - } else if (current_stage == mtx::user_interactive::auth_types::registration_token) { - bool ok; - QString token = QInputDialog::getText(this, - tr("Registration token"), - tr("Please enter a valid registration token."), - QLineEdit::Normal, - QString(), - &ok); - - if (ok) { - emit registrationWithAuth(mtx::user_interactive::Auth{ - session, mtx::user_interactive::auth::RegistrationToken{token.toStdString()}}); - } else { - emit errorOccurred(); - } - } else { - // use fallback - auto dialog = new dialogs::FallbackAuth( - QString::fromStdString(current_stage), QString::fromStdString(session), this); - - connect(dialog, &dialogs::FallbackAuth::confirmation, this, [this, session, dialog]() { - dialog->close(); - dialog->deleteLater(); - emit registrationWithAuth( - mtx::user_interactive::Auth{session, mtx::user_interactive::auth::Fallback{}}); - }); - - connect(dialog, &dialogs::FallbackAuth::cancel, this, &RegisterPage::errorOccurred); - - dialog->show(); - } -} - void RegisterPage::paintEvent(QPaintEvent *) { diff --git a/src/RegisterPage.h b/src/RegisterPage.h index b88808f9..0d7da9ad 100644 --- a/src/RegisterPage.h +++ b/src/RegisterPage.h @@ -39,8 +39,6 @@ signals: void wellKnownLookup(); void versionsCheck(); void registration(); - void UIA(const mtx::user_interactive::Unauthorized &unauthorized); - void registrationWithAuth(const mtx::user_interactive::Auth &auth); void registering(); void registerOk(); @@ -62,8 +60,6 @@ private slots: void doWellKnownLookup(); void doVersionsCheck(); void doRegistration(); - void doUIA(const mtx::user_interactive::Unauthorized &unauthorized); - void doRegistrationWithAuth(const mtx::user_interactive::Auth &auth); mtx::http::Callback registrationCb(); private: diff --git a/src/ui/UIA.cpp b/src/ui/UIA.cpp index 29161382..c157ea0f 100644 --- a/src/ui/UIA.cpp +++ b/src/ui/UIA.cpp @@ -9,6 +9,8 @@ #include #include +#include + #include "Logging.h" #include "MainWindow.h" #include "dialogs/FallbackAuth.h" @@ -54,6 +56,7 @@ UIA::genericHandler(QString context) if (flows.empty()) { nhlog::ui()->error("No available registration flows!"); + emit error(tr("No available registration flows!")); return; } @@ -61,6 +64,10 @@ UIA::genericHandler(QString context) if (current_stage == mtx::user_interactive::auth_types::password) { emit password(); + } else if (current_stage == mtx::user_interactive::auth_types::email_identity) { + emit email(); + } else if (current_stage == mtx::user_interactive::auth_types::msisdn) { + emit phoneNumber(); } else if (current_stage == mtx::user_interactive::auth_types::recaptcha) { auto captchaDialog = new dialogs::ReCaptcha(QString::fromStdString(u.session), MainWindow::instance()); @@ -74,8 +81,9 @@ UIA::genericHandler(QString context) mtx::user_interactive::auth::Fallback{}}); }); - // connect( - // captchaDialog, &dialogs::ReCaptcha::cancel, this, &RegisterPage::errorOccurred); + connect(captchaDialog, &dialogs::ReCaptcha::cancel, this, [this]() { + emit error(tr("Registration aborted")); + }); QTimer::singleShot(0, this, [captchaDialog]() { captchaDialog->show(); }); @@ -98,7 +106,7 @@ UIA::genericHandler(QString context) u.session, mtx::user_interactive::auth::RegistrationToken{token.toStdString()}}); } else { - // emit errorOccurred(); + emit error(tr("Registration aborted")); } } else { // use fallback @@ -114,8 +122,9 @@ UIA::genericHandler(QString context) mtx::user_interactive::auth::Fallback{}}); }); - // connect(dialog, &dialogs::FallbackAuth::cancel, this, - // &RegisterPage::errorOccurred); + connect(dialog, &dialogs::FallbackAuth::cancel, this, [this]() { + emit error(tr("Registration aborted")); + }); dialog->show(); } @@ -134,3 +143,130 @@ UIA::continuePassword(QString password) if (currentHandler) currentHandler->next(mtx::user_interactive::Auth{currentStatus.session, p}); } + +void +UIA::continueEmail(QString email) +{ + mtx::requests::RequestEmailToken r{}; + r.client_secret = this->client_secret = mtx::client::utils::random_token(128, false); + r.email = email.toStdString(); + r.send_attempt = 0; + http::client()->register_email_request_token( + r, [this](const mtx::responses::RequestToken &token, mtx::http::RequestErr e) { + if (!e) { + this->sid = token.sid; + this->submit_url = token.submit_url; + this->email_ = true; + + if (submit_url.empty()) { + nhlog::ui()->debug("Got no submit url."); + emit confirm3pidToken(); + } else { + nhlog::ui()->debug("Got submit url: {}", token.submit_url); + emit prompt3pidToken(); + } + } else { + nhlog::ui()->debug("Registering email failed! ({},{},{},{})", + e->status_code, + e->status_code, + e->parse_error, + e->matrix_error.error); + emit error(QString::fromStdString(e->matrix_error.error)); + } + }); +} +void +UIA::continuePhoneNumber(QString countryCode, QString phoneNumber) +{ + mtx::requests::RequestMSISDNToken r{}; + r.client_secret = this->client_secret = mtx::client::utils::random_token(128, false); + r.country = countryCode.toStdString(); + r.phone_number = phoneNumber.toStdString(); + r.send_attempt = 0; + http::client()->register_phone_request_token( + r, [this](const mtx::responses::RequestToken &token, mtx::http::RequestErr e) { + if (!e) { + this->sid = token.sid; + this->submit_url = token.submit_url; + this->email_ = false; + if (submit_url.empty()) { + nhlog::ui()->debug("Got no submit url."); + emit confirm3pidToken(); + } else { + nhlog::ui()->debug("Got submit url: {}", token.submit_url); + emit prompt3pidToken(); + } + } else { + nhlog::ui()->debug("Registering phone number failed! ({},{},{},{})", + e->status_code, + e->status_code, + e->parse_error, + e->matrix_error.error); + emit error(QString::fromStdString(e->matrix_error.error)); + } + }); +} + +void +UIA::continue3pidReceived() +{ + mtx::user_interactive::auth::ThreePIDCred c{}; + c.client_secret = this->client_secret; + c.sid = this->sid; + + if (this->email_) { + mtx::user_interactive::auth::EmailIdentity i{}; + i.threepidCred = c; + this->currentHandler->next(mtx::user_interactive::Auth{currentStatus.session, i}); + } else { + mtx::user_interactive::auth::MSISDN i{}; + i.threepidCred = c; + this->currentHandler->next(mtx::user_interactive::Auth{currentStatus.session, i}); + } +} + +void +UIA::submit3pidToken(QString token) +{ + mtx::requests::IdentitySubmitToken t{}; + t.client_secret = this->client_secret; + t.sid = this->sid; + t.token = token.toStdString(); + + http::client()->validate_submit_token( + submit_url, t, [this](const mtx::responses::Success &success, mtx::http::RequestErr e) { + if (!e && success.success) { + mtx::user_interactive::auth::ThreePIDCred c{}; + c.client_secret = this->client_secret; + c.sid = this->sid; + + nhlog::ui()->debug("Submit token success"); + + if (this->email_) { + mtx::user_interactive::auth::EmailIdentity i{}; + i.threepidCred = c; + this->currentHandler->next(mtx::user_interactive::Auth{currentStatus.session, i}); + } else { + mtx::user_interactive::auth::MSISDN i{}; + i.threepidCred = c; + this->currentHandler->next(mtx::user_interactive::Auth{currentStatus.session, i}); + } + } else { + if (e) { + nhlog::ui()->debug("Submit token invalid! ({},{},{},{})", + e->status_code, + e->status_code, + e->parse_error, + e->matrix_error.error); + emit error(QString::fromStdString(e->matrix_error.error)); + } else { + nhlog::ui()->debug("Submit token invalid!"); + emit error(tr("Invalid token")); + } + } + + this->client_secret.clear(); + this->sid.clear(); + this->submit_url.clear(); + }); +} diff --git a/src/ui/UIA.h b/src/ui/UIA.h index fb047451..0db23897 100644 --- a/src/ui/UIA.h +++ b/src/ui/UIA.h @@ -27,14 +27,31 @@ public: public slots: void continuePassword(QString password); + void continueEmail(QString email); + void continuePhoneNumber(QString countryCode, QString phoneNumber); + void submit3pidToken(QString token); + void continue3pidReceived(); signals: void password(); + void email(); + void phoneNumber(); + + void confirm3pidToken(); + void prompt3pidToken(); + void tokenAccepted(); void titleChanged(); + void error(QString msg); private: std::optional currentHandler; mtx::user_interactive::Unauthorized currentStatus; QString title_; + + // for 3pids like email and phone number + std::string client_secret; + std::string sid; + std::string submit_url; + bool email_ = true; }; From 912a8c43b26465b3bacca7491862eedb65859054 Mon Sep 17 00:00:00 2001 From: Nicolas Werner Date: Wed, 3 Nov 2021 18:52:28 +0100 Subject: [PATCH 228/232] Fix copyright order --- resources/qml/dialogs/PhoneNumberInputDialog.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/qml/dialogs/PhoneNumberInputDialog.qml b/resources/qml/dialogs/PhoneNumberInputDialog.qml index 0445b7a6..af6315ac 100644 --- a/resources/qml/dialogs/PhoneNumberInputDialog.qml +++ b/resources/qml/dialogs/PhoneNumberInputDialog.qml @@ -1,5 +1,5 @@ -// SPDX-FileCopyrightText: 2021 Nheko Contributors // SPDX-FileCopyrightText: 2021 Mirian Margiani +// SPDX-FileCopyrightText: 2021 Nheko Contributors // // SPDX-License-Identifier: GPL-3.0-or-later From 1a163f49e21ca64704460d292f1bf665be7e6c76 Mon Sep 17 00:00:00 2001 From: Nicolas Werner Date: Wed, 3 Nov 2021 22:35:54 +0100 Subject: [PATCH 229/232] Fix unjoinable invites on mobile as well as unclickable previews --- resources/qml/ChatPage.qml | 2 +- resources/qml/RoomList.qml | 1 + resources/qml/TimelineView.qml | 8 ++++---- src/timeline/RoomlistModel.cpp | 22 +++++++++++++++++++++- 4 files changed, 27 insertions(+), 6 deletions(-) diff --git a/resources/qml/ChatPage.qml b/resources/qml/ChatPage.qml index 082fa8d6..22a04b74 100644 --- a/resources/qml/ChatPage.qml +++ b/resources/qml/ChatPage.qml @@ -21,7 +21,7 @@ Rectangle { anchors.fill: parent singlePageMode: communityListC.preferredWidth + roomListC.preferredWidth + timlineViewC.minimumWidth > width - pageIndex: Rooms.currentRoom ? 2 : 1 + pageIndex: (Rooms.currentRoom || Rooms.currentRoomPreview.roomid) ? 2 : 1 AdaptiveLayoutElement { id: communityListC diff --git a/resources/qml/RoomList.qml b/resources/qml/RoomList.qml index 72ac49e1..12ecc6e8 100644 --- a/resources/qml/RoomList.qml +++ b/resources/qml/RoomList.qml @@ -192,6 +192,7 @@ Page { TapHandler { margin: -Nheko.paddingSmall onSingleTapped: { + console.log("tapped "+roomId); if (!Rooms.currentRoom || Rooms.currentRoom.roomId !== roomId) Rooms.setCurrentRoom(roomId); else diff --git a/resources/qml/TimelineView.qml b/resources/qml/TimelineView.qml index 91bbca5b..8214d9de 100644 --- a/resources/qml/TimelineView.qml +++ b/resources/qml/TimelineView.qml @@ -24,7 +24,7 @@ Item { property bool showBackButton: false Label { - visible: !room && !TimelineManager.isInitialSync && !roomPreview + visible: !room && !TimelineManager.isInitialSync && (!roomPreview || !roomPreview.roomid) anchors.centerIn: parent text: qsTr("No room open") font.pointSize: 24 @@ -137,7 +137,7 @@ Item { ColumnLayout { id: preview - property string roomId: room ? room.roomId : (roomPreview ? roomPreview.roomId : "") + property string roomId: room ? room.roomId : (roomPreview ? roomPreview.roomid : "") property string roomName: room ? room.roomName : (roomPreview ? roomPreview.roomName : "") property string roomTopic: room ? room.roomTopic : (roomPreview ? roomPreview.roomTopic : "") property string avatarUrl: room ? room.roomAvatarUrl : (roomPreview ? roomPreview.roomAvatarUrl : "") @@ -163,7 +163,7 @@ Item { } MatrixText { - text: parent.roomName + text: parent.roomName == "" ? qsTr("No preview available") : parent.roomName font.pixelSize: 24 Layout.alignment: Qt.AlignHCenter } @@ -240,7 +240,7 @@ Item { anchors.margins: Nheko.paddingMedium width: Nheko.avatarSize height: Nheko.avatarSize - visible: room != null && room.isSpace && showBackButton + visible: (room == null || room.isSpace) && showBackButton enabled: visible image: ":/icons/icons/ui/angle-pointing-to-left.png" ToolTip.visible: hovered diff --git a/src/timeline/RoomlistModel.cpp b/src/timeline/RoomlistModel.cpp index c5bbd83c..179c63af 100644 --- a/src/timeline/RoomlistModel.cpp +++ b/src/timeline/RoomlistModel.cpp @@ -609,6 +609,12 @@ RoomlistModel::setCurrentRoom(QString roomid) (currentRoomPreview_ && currentRoomPreview_->roomid() == roomid)) return; + if (roomid.isEmpty()) { + currentRoom_ = nullptr; + currentRoomPreview_ = {}; + emit currentRoomChanged(); + } + nhlog::ui()->debug("Trying to switch to: {}", roomid.toStdString()); if (models.contains(roomid)) { currentRoom_ = models.value(roomid); @@ -635,10 +641,24 @@ RoomlistModel::setCurrentRoom(QString roomid) p.roomTopic_ = QString::fromStdString(i->topic); p.roomAvatarUrl_ = QString::fromStdString(i->avatar_url); currentRoomPreview_ = std::move(p); + nhlog::ui()->debug("Switched to (preview): {}", + currentRoomPreview_->roomid_.toStdString()); + } else { + p.roomid_ = roomid; + currentRoomPreview_ = p; + nhlog::ui()->debug("Switched to (empty): {}", + currentRoomPreview_->roomid_.toStdString()); } emit currentRoomChanged(); - nhlog::ui()->debug("Switched to: {}", roomid.toStdString()); + } else { + currentRoom_ = nullptr; + + RoomPreview p; + p.roomid_ = roomid; + currentRoomPreview_ = std::move(p); + emit currentRoomChanged(); + nhlog::ui()->debug("Switched to (empty): {}", roomid.toStdString()); } } From 1e22274d8cb4da5f4f02dfa3002b9ac2838f455c Mon Sep 17 00:00:00 2001 From: Nicolas Werner Date: Wed, 3 Nov 2021 23:01:36 +0100 Subject: [PATCH 230/232] Use ItemDelegate in RoomList instead of a Rectangle with handlers fixes #683 relates to #571 --- resources/qml/RoomList.qml | 82 +- resources/qml/Root.qml | 14 +- resources/qml/SelfVerificationCheck.qml | 301 +-- resources/qml/components/MainWindowDialog.qml | 7 +- resources/qml/dialogs/JoinRoomDialog.qml | 2 + resources/qml/dialogs/LeaveRoomDialog.qml | 2 +- resources/qml/dialogs/LogoutDialog.qml | 2 +- .../qml/dialogs/PhoneNumberInputDialog.qml | 1962 ++++++++++++++--- resources/qml/dialogs/UserProfile.qml | 21 +- 9 files changed, 1895 insertions(+), 498 deletions(-) diff --git a/resources/qml/RoomList.qml b/resources/qml/RoomList.qml index 12ecc6e8..db255bd3 100644 --- a/resources/qml/RoomList.qml +++ b/resources/qml/RoomList.qml @@ -114,10 +114,10 @@ Page { } - delegate: Rectangle { + delegate: ItemDelegate { id: roomItem - property color background: Nheko.colors.window + property color backgroundColor: Nheko.colors.window property color importantText: Nheko.colors.text property color unimportantText: Nheko.colors.buttonText property color bubbleBackground: Nheko.colors.highlight @@ -136,20 +136,31 @@ Page { required property bool isDirect required property string directChatOtherUserId - color: background height: avatarSize + 2 * Nheko.paddingMedium width: ListView.view.width state: "normal" - ToolTip.visible: hovered.hovered && collapsed + ToolTip.visible: hovered && collapsed ToolTip.text: roomName + onClicked: { + console.log("tapped " + roomId); + if (!Rooms.currentRoom || Rooms.currentRoom.roomId !== roomId) + Rooms.setCurrentRoom(roomId); + else + Rooms.resetCurrentRoom(); + } + onPressAndHold: { + if (!isInvite) + roomContextMenu.show(roomId, tags); + + } states: [ State { name: "highlight" - when: hovered.hovered && !((Rooms.currentRoom && roomId == Rooms.currentRoom.roomId) || Rooms.currentRoomPreview.roomid == roomId) + when: roomItem.hovered && !((Rooms.currentRoom && roomId == Rooms.currentRoom.roomId) || Rooms.currentRoomPreview.roomid == roomId) PropertyChanges { target: roomItem - background: Nheko.colors.dark + backgroundColor: Nheko.colors.dark importantText: Nheko.colors.brightText unimportantText: Nheko.colors.brightText bubbleBackground: Nheko.colors.highlight @@ -163,7 +174,7 @@ Page { PropertyChanges { target: roomItem - background: Nheko.colors.highlight + backgroundColor: Nheko.colors.highlight importantText: Nheko.colors.highlightedText unimportantText: Nheko.colors.highlightedText bubbleBackground: Nheko.colors.highlightedText @@ -189,28 +200,6 @@ Page { acceptedDevices: PointerDevice.Mouse | PointerDevice.Stylus | PointerDevice.TouchPad } - TapHandler { - margin: -Nheko.paddingSmall - onSingleTapped: { - console.log("tapped "+roomId); - if (!Rooms.currentRoom || Rooms.currentRoom.roomId !== roomId) - Rooms.setCurrentRoom(roomId); - else - Rooms.resetCurrentRoom(); - } - onLongPressed: { - if (!isInvite) - roomContextMenu.show(roomId, tags); - - } - } - - HoverHandler { - id: hovered - - acceptedDevices: PointerDevice.Mouse | PointerDevice.Stylus | PointerDevice.TouchPad - } - } RowLayout { @@ -362,6 +351,10 @@ Page { visible: hasUnreadMessages } + background: Rectangle { + color: backgroundColor + } + } } @@ -505,35 +498,40 @@ Page { Rectangle { id: unverifiedStuffBubble - color: Qt.lighter(Nheko.theme.orange, verifyButtonHovered.hovered ? 1.2 : 1.0) + + color: Qt.lighter(Nheko.theme.orange, verifyButtonHovered.hovered ? 1.2 : 1) Layout.fillWidth: true implicitHeight: explanation.height + Nheko.paddingMedium * 2 visible: SelfVerificationStatus.status != SelfVerificationStatus.AllVerified RowLayout { id: unverifiedStuffBubbleContainer + width: parent.width height: explanation.height + Nheko.paddingMedium * 2 spacing: 0 Label { id: explanation + Layout.margins: Nheko.paddingMedium Layout.rightMargin: Nheko.paddingSmall color: Nheko.colors.buttonText Layout.fillWidth: true - text: switch(SelfVerificationStatus.status) { + text: { + switch (SelfVerificationStatus.status) { case SelfVerificationStatus.NoMasterKey: - //: Cross-signing setup has not run yet. - return qsTr("Encryption not set up"); + //: Cross-signing setup has not run yet. + return qsTr("Encryption not set up"); case SelfVerificationStatus.UnverifiedMasterKey: - //: The user just signed in with this device and hasn't verified their master key. - return qsTr("Unverified login"); + //: The user just signed in with this device and hasn't verified their master key. + return qsTr("Unverified login"); case SelfVerificationStatus.UnverifiedDevices: - //: There are unverified devices signed in to this account. - return qsTr("Please verify your other devices"); + //: There are unverified devices signed in to this account. + return qsTr("Please verify your other devices"); default: - return "" + return ""; + } } textFormat: Text.PlainText wrapMode: Text.Wrap @@ -558,8 +556,8 @@ Page { HoverHandler { id: verifyButtonHovered - enabled: !closeUnverifiedBubble.hovered + enabled: !closeUnverifiedBubble.hovered acceptedDevices: PointerDevice.Mouse | PointerDevice.Stylus | PointerDevice.TouchPad } @@ -567,13 +565,13 @@ Page { enabled: !closeUnverifiedBubble.hovered acceptedButtons: Qt.LeftButton onSingleTapped: { - if (SelfVerificationStatus.status == SelfVerificationStatus.UnverifiedDevices) { + if (SelfVerificationStatus.status == SelfVerificationStatus.UnverifiedDevices) SelfVerificationStatus.verifyUnverifiedDevices(); - } else { + else SelfVerificationStatus.statusChanged(); - } } } + } Rectangle { diff --git a/resources/qml/Root.qml b/resources/qml/Root.qml index 7bacb98e..f6b26041 100644 --- a/resources/qml/Root.qml +++ b/resources/qml/Root.qml @@ -116,6 +116,7 @@ Page { LogoutDialog { } + } Component { @@ -123,6 +124,7 @@ Page { JoinRoomDialog { } + } Component { @@ -130,6 +132,7 @@ Page { LeaveRoomDialog { } + } Shortcut { @@ -222,8 +225,8 @@ Page { function onOpenLeaveRoomDialog(roomid) { var dialog = leaveRoomComponent.createObject(timelineRoot, { - "roomId": roomid - }); + "roomId": roomid + }); dialog.open(); } @@ -296,28 +299,31 @@ Page { buttons: Platform.MessageDialog.Ok text: qsTr("Wait for the confirmation link to arrive, then continue.") - onAccepted: UIA.continue3pidReceived() } - Connections { function onPassword() { console.log("UIA: password needed"); uiaPassPrompt.show(); } + function onEmail() { uiaEmailPrompt.show(); } + function onPhoneNumber() { uiaPhoneNumberPrompt.show(); } + function onPrompt3pidToken() { uiaTokenPrompt.show(); } + function onConfirm3pidToken() { uiaConfirmationLinkDialog.open(); } + function onError(msg) { uiaErrorDialog.text = msg; uiaErrorDialog.open(); diff --git a/resources/qml/SelfVerificationCheck.qml b/resources/qml/SelfVerificationCheck.qml index a7502d8d..23997e58 100644 --- a/resources/qml/SelfVerificationCheck.qml +++ b/resources/qml/SelfVerificationCheck.qml @@ -2,12 +2,12 @@ // // SPDX-License-Identifier: GPL-3.0-or-later +import "./components/" import Qt.labs.platform 1.1 as P import QtQuick 2.15 import QtQuick.Controls 2.15 import QtQuick.Layouts 1.3 import im.nheko 1.0 -import "./components/" Item { visible: false @@ -86,128 +86,127 @@ Item { onAccepted: SelfVerificationStatus.setupCrosssigning(storeSecretsOnline.checked, usePassword.checked ? passwordField.text : "", useOnlineKeyBackup.checked) - GridLayout { - id: grid + GridLayout { + id: grid - width: bootstrapCrosssigning.useableWidth - columns: 2 - rowSpacing: 0 - columnSpacing: 0 + width: bootstrapCrosssigning.useableWidth + columns: 2 + rowSpacing: 0 + columnSpacing: 0 z: 1 - Label { - Layout.margins: Nheko.paddingMedium - Layout.alignment: Qt.AlignHCenter - Layout.columnSpan: 2 - font.pointSize: fontMetrics.font.pointSize * 2 - text: qsTr("Setup Encryption") - color: Nheko.colors.text - wrapMode: Text.Wrap - } + Label { + Layout.margins: Nheko.paddingMedium + Layout.alignment: Qt.AlignHCenter + Layout.columnSpan: 2 + font.pointSize: fontMetrics.font.pointSize * 2 + text: qsTr("Setup Encryption") + color: Nheko.colors.text + wrapMode: Text.Wrap + } - Label { - Layout.margins: Nheko.paddingMedium - Layout.alignment: Qt.AlignLeft - Layout.columnSpan: 2 - Layout.maximumWidth: grid.width - Nheko.paddingMedium * 2 - text: qsTr("Hello and welcome to Matrix!\nIt seems like you are new. Before you can securely encrypt your messages, we need to setup a few small things. You can either press accept immediately or adjust a few basic options. We also try to explain a few of the basics. You can skip those parts, but they might prove to be helpful!") - color: Nheko.colors.text - wrapMode: Text.Wrap - } + Label { + Layout.margins: Nheko.paddingMedium + Layout.alignment: Qt.AlignLeft + Layout.columnSpan: 2 + Layout.maximumWidth: grid.width - Nheko.paddingMedium * 2 + text: qsTr("Hello and welcome to Matrix!\nIt seems like you are new. Before you can securely encrypt your messages, we need to setup a few small things. You can either press accept immediately or adjust a few basic options. We also try to explain a few of the basics. You can skip those parts, but they might prove to be helpful!") + color: Nheko.colors.text + wrapMode: Text.Wrap + } - Label { - Layout.margins: Nheko.paddingMedium - Layout.alignment: Qt.AlignLeft - Layout.columnSpan: 1 - Layout.maximumWidth: Math.floor(grid.width / 2) - Nheko.paddingMedium * 2 - text: "Store secrets online.\nYou have a few secrets to make all the encryption magic work. While you can keep them stored only locally, we recommend storing them encrypted on the server. Otherwise it will be painful to recover them. Only disable this if you are paranoid and like losing your data!" - color: Nheko.colors.text - wrapMode: Text.Wrap - } + Label { + Layout.margins: Nheko.paddingMedium + Layout.alignment: Qt.AlignLeft + Layout.columnSpan: 1 + Layout.maximumWidth: Math.floor(grid.width / 2) - Nheko.paddingMedium * 2 + text: "Store secrets online.\nYou have a few secrets to make all the encryption magic work. While you can keep them stored only locally, we recommend storing them encrypted on the server. Otherwise it will be painful to recover them. Only disable this if you are paranoid and like losing your data!" + color: Nheko.colors.text + wrapMode: Text.Wrap + } - Item { - Layout.margins: Nheko.paddingMedium - Layout.preferredHeight: storeSecretsOnline.height - Layout.alignment: Qt.AlignLeft | Qt.AlignVCenter - Layout.fillWidth: true + Item { + Layout.margins: Nheko.paddingMedium + Layout.preferredHeight: storeSecretsOnline.height + Layout.alignment: Qt.AlignLeft | Qt.AlignVCenter + Layout.fillWidth: true - ToggleButton { - id: storeSecretsOnline - - checked: true - onClicked: console.log("Store secrets toggled: " + checked) - } - - } - - Label { - Layout.margins: Nheko.paddingMedium - Layout.alignment: Qt.AlignLeft - Layout.columnSpan: 1 - Layout.rowSpan: 2 - Layout.maximumWidth: Math.floor(grid.width / 2) - Nheko.paddingMedium * 2 - visible: storeSecretsOnline.checked - text: "Set an online backup password.\nWe recommend you DON'T set a password and instead only rely on the recovery key. You will get a recovery key in any case when storing the cross-signing secrets online, but passwords are usually not very random, so they are easier to attack than a completely random recovery key. If you choose to use a password, DON'T make it the same as your login password, otherwise your server can read all your encrypted messages. (You don't want that.)" - color: Nheko.colors.text - wrapMode: Text.Wrap - } - - Item { - Layout.margins: Nheko.paddingMedium - Layout.topMargin: Nheko.paddingLarge - Layout.preferredHeight: storeSecretsOnline.height - Layout.alignment: Qt.AlignLeft | Qt.AlignTop - Layout.rowSpan: usePassword.checked ? 1 : 2 - Layout.fillWidth: true - visible: storeSecretsOnline.checked - - ToggleButton { - id: usePassword - - checked: false - } - - } - - MatrixTextField { - id: passwordField - - Layout.margins: Nheko.paddingMedium - Layout.maximumWidth: Math.floor(grid.width / 2) - Nheko.paddingMedium * 2 - Layout.alignment: Qt.AlignLeft | Qt.AlignTop - Layout.columnSpan: 1 - Layout.fillWidth: true - visible: storeSecretsOnline.checked && usePassword.checked - echoMode: TextInput.Password - } - - Label { - Layout.margins: Nheko.paddingMedium - Layout.alignment: Qt.AlignLeft - Layout.columnSpan: 1 - Layout.maximumWidth: Math.floor(grid.width / 2) - Nheko.paddingMedium * 2 - text: "Use online key backup.\nStore the keys for your messages securely encrypted online. In general you do want this, because it protects your messages from becoming unreadable, if you log out by accident. It does however carry a small security risk, if you ever share your recovery key by accident. Currently this also has some other weaknesses, that might allow the server to insert new keys into your backup. The server will however never be able to read your messages." - color: Nheko.colors.text - wrapMode: Text.Wrap - } - - Item { - Layout.margins: Nheko.paddingMedium - Layout.preferredHeight: storeSecretsOnline.height - Layout.alignment: Qt.AlignLeft | Qt.AlignVCenter - Layout.fillWidth: true - - ToggleButton { - id: useOnlineKeyBackup - - checked: true - onClicked: console.log("Online key backup toggled: " + checked) - } + ToggleButton { + id: storeSecretsOnline + checked: true + onClicked: console.log("Store secrets toggled: " + checked) } } + Label { + Layout.margins: Nheko.paddingMedium + Layout.alignment: Qt.AlignLeft + Layout.columnSpan: 1 + Layout.rowSpan: 2 + Layout.maximumWidth: Math.floor(grid.width / 2) - Nheko.paddingMedium * 2 + visible: storeSecretsOnline.checked + text: "Set an online backup password.\nWe recommend you DON'T set a password and instead only rely on the recovery key. You will get a recovery key in any case when storing the cross-signing secrets online, but passwords are usually not very random, so they are easier to attack than a completely random recovery key. If you choose to use a password, DON'T make it the same as your login password, otherwise your server can read all your encrypted messages. (You don't want that.)" + color: Nheko.colors.text + wrapMode: Text.Wrap + } + + Item { + Layout.margins: Nheko.paddingMedium + Layout.topMargin: Nheko.paddingLarge + Layout.preferredHeight: storeSecretsOnline.height + Layout.alignment: Qt.AlignLeft | Qt.AlignTop + Layout.rowSpan: usePassword.checked ? 1 : 2 + Layout.fillWidth: true + visible: storeSecretsOnline.checked + + ToggleButton { + id: usePassword + + checked: false + } + + } + + MatrixTextField { + id: passwordField + + Layout.margins: Nheko.paddingMedium + Layout.maximumWidth: Math.floor(grid.width / 2) - Nheko.paddingMedium * 2 + Layout.alignment: Qt.AlignLeft | Qt.AlignTop + Layout.columnSpan: 1 + Layout.fillWidth: true + visible: storeSecretsOnline.checked && usePassword.checked + echoMode: TextInput.Password + } + + Label { + Layout.margins: Nheko.paddingMedium + Layout.alignment: Qt.AlignLeft + Layout.columnSpan: 1 + Layout.maximumWidth: Math.floor(grid.width / 2) - Nheko.paddingMedium * 2 + text: "Use online key backup.\nStore the keys for your messages securely encrypted online. In general you do want this, because it protects your messages from becoming unreadable, if you log out by accident. It does however carry a small security risk, if you ever share your recovery key by accident. Currently this also has some other weaknesses, that might allow the server to insert new keys into your backup. The server will however never be able to read your messages." + color: Nheko.colors.text + wrapMode: Text.Wrap + } + + Item { + Layout.margins: Nheko.paddingMedium + Layout.preferredHeight: storeSecretsOnline.height + Layout.alignment: Qt.AlignLeft | Qt.AlignVCenter + Layout.fillWidth: true + + ToggleButton { + id: useOnlineKeyBackup + + checked: true + onClicked: console.log("Online key backup toggled: " + checked) + } + + } + + } background: Rectangle { color: Nheko.colors.window @@ -230,58 +229,60 @@ Item { columns: 1 z: 1 - Label { - Layout.margins: Nheko.paddingMedium - Layout.alignment: Qt.AlignHCenter - //Layout.columnSpan: 2 - font.pointSize: fontMetrics.font.pointSize * 2 - text: qsTr("Activate Encryption") - color: Nheko.colors.text - wrapMode: Text.Wrap - } - - Label { - Layout.margins: Nheko.paddingMedium - Layout.alignment: Qt.AlignLeft - //Layout.columnSpan: 2 - Layout.maximumWidth: grid.width - Nheko.paddingMedium * 2 - text: qsTr("It seems like you have encryption already configured for this account. To be able to access your encrypted messages and make this device appear as trusted, you can either verify an existing device or (if you have one) enter your recovery passphrase. Please select one of the options below.\nIf you choose verify, you need to have the other device available. If you choose \"enter passphrase\", you will need your recovery key or passphrase. If you click cancel, you can choose to verify yourself at a later point.") - color: Nheko.colors.text - wrapMode: Text.Wrap - } - - FlatButton { - Layout.alignment: Qt.AlignHCenter - text: qsTr("verify") - onClicked: { - SelfVerificationStatus.verifyMasterKey(); - verifyMasterKey.close(); + Label { + Layout.margins: Nheko.paddingMedium + Layout.alignment: Qt.AlignHCenter + //Layout.columnSpan: 2 + font.pointSize: fontMetrics.font.pointSize * 2 + text: qsTr("Activate Encryption") + color: Nheko.colors.text + wrapMode: Text.Wrap } - } - FlatButton { - visible: SelfVerificationStatus.hasSSSS - Layout.alignment: Qt.AlignHCenter - text: qsTr("enter passphrase") - onClicked: { - SelfVerificationStatus.verifyMasterKeyWithPassphrase() - verifyMasterKey.close(); + + Label { + Layout.margins: Nheko.paddingMedium + Layout.alignment: Qt.AlignLeft + //Layout.columnSpan: 2 + Layout.maximumWidth: grid.width - Nheko.paddingMedium * 2 + text: qsTr("It seems like you have encryption already configured for this account. To be able to access your encrypted messages and make this device appear as trusted, you can either verify an existing device or (if you have one) enter your recovery passphrase. Please select one of the options below.\nIf you choose verify, you need to have the other device available. If you choose \"enter passphrase\", you will need your recovery key or passphrase. If you click cancel, you can choose to verify yourself at a later point.") + color: Nheko.colors.text + wrapMode: Text.Wrap } + + FlatButton { + Layout.alignment: Qt.AlignHCenter + text: qsTr("verify") + onClicked: { + SelfVerificationStatus.verifyMasterKey(); + verifyMasterKey.close(); + } + } + + FlatButton { + visible: SelfVerificationStatus.hasSSSS + Layout.alignment: Qt.AlignHCenter + text: qsTr("enter passphrase") + onClicked: { + SelfVerificationStatus.verifyMasterKeyWithPassphrase(); + verifyMasterKey.close(); + } + } + } - } + } Connections { function onStatusChanged() { console.log("STATUS CHANGED: " + SelfVerificationStatus.status); - if (SelfVerificationStatus.status == SelfVerificationStatus.NoMasterKey) + if (SelfVerificationStatus.status == SelfVerificationStatus.NoMasterKey) { bootstrapCrosssigning.open(); - else if (SelfVerificationStatus.status == SelfVerificationStatus.UnverifiedMasterKey) + } else if (SelfVerificationStatus.status == SelfVerificationStatus.UnverifiedMasterKey) { verifyMasterKey.open(); - else { + } else { bootstrapCrosssigning.close(); verifyMasterKey.close(); } - } function onShowRecoveryKey(key) { diff --git a/resources/qml/components/MainWindowDialog.qml b/resources/qml/components/MainWindowDialog.qml index 19233384..901260b5 100644 --- a/resources/qml/components/MainWindowDialog.qml +++ b/resources/qml/components/MainWindowDialog.qml @@ -9,6 +9,9 @@ import QtQuick.Layouts 1.3 import im.nheko 1.0 Dialog { + default property alias inner: scroll.data + property int useableWidth: scroll.width - scroll.ScrollBar.vertical.width + parent: Overlay.overlay anchors.centerIn: parent height: (Math.floor(parent.height / 2) - Nheko.paddingLarge) * 2 @@ -17,10 +20,6 @@ Dialog { modal: true standardButtons: Dialog.Ok | Dialog.Cancel closePolicy: Popup.NoAutoClose - - default property alias inner: scroll.data - property int useableWidth: scroll.width - scroll.ScrollBar.vertical.width - contentChildren: [ ScrollView { id: scroll diff --git a/resources/qml/dialogs/JoinRoomDialog.qml b/resources/qml/dialogs/JoinRoomDialog.qml index d3defa82..df31d994 100644 --- a/resources/qml/dialogs/JoinRoomDialog.qml +++ b/resources/qml/dialogs/JoinRoomDialog.qml @@ -45,6 +45,7 @@ ApplicationWindow { onAccepted: { if (input.text.match("#.+?:.{3,}")) dbb.accepted(); + } } @@ -71,6 +72,7 @@ ApplicationWindow { text: "Cancel" DialogButtonBox.buttonRole: DialogButtonBox.RejectRole } + } } diff --git a/resources/qml/dialogs/LeaveRoomDialog.qml b/resources/qml/dialogs/LeaveRoomDialog.qml index 1341ad72..e9c12e8f 100644 --- a/resources/qml/dialogs/LeaveRoomDialog.qml +++ b/resources/qml/dialogs/LeaveRoomDialog.qml @@ -2,9 +2,9 @@ // // SPDX-License-Identifier: GPL-3.0-or-later +import Qt.labs.platform 1.1 import QtQuick 2.15 import QtQuick.Controls 2.15 -import Qt.labs.platform 1.1 import im.nheko 1.0 MessageDialog { diff --git a/resources/qml/dialogs/LogoutDialog.qml b/resources/qml/dialogs/LogoutDialog.qml index 9e107097..eb82dd15 100644 --- a/resources/qml/dialogs/LogoutDialog.qml +++ b/resources/qml/dialogs/LogoutDialog.qml @@ -2,9 +2,9 @@ // // SPDX-License-Identifier: GPL-3.0-or-later +import Qt.labs.platform 1.1 import QtQuick 2.15 import QtQuick.Controls 2.15 -import Qt.labs.platform 1.1 import im.nheko 1.0 MessageDialog { diff --git a/resources/qml/dialogs/PhoneNumberInputDialog.qml b/resources/qml/dialogs/PhoneNumberInputDialog.qml index af6315ac..b4f2a9f0 100644 --- a/resources/qml/dialogs/PhoneNumberInputDialog.qml +++ b/resources/qml/dialogs/PhoneNumberInputDialog.qml @@ -30,303 +30,1693 @@ ApplicationWindow { columns: 2 Label { - Layout.columnSpan: 2 id: promptLabel + Layout.columnSpan: 2 color: Nheko.colors.text } ComboBox { id: numberPrefix + editable: false + delegate: ItemDelegate { - text: n + " ("+ p +")" + text: n + " (" + p + ")" } // taken from https://gitlab.com/whisperfish/whisperfish/-/blob/master/qml/js/countries.js - model: ListModel{//n=name,i=ISO,p=prefix -- see countries.js.md for source - ListElement{n:"Afghanistan";i:"AF";p:"+93"} - ListElement{n:"Åland Islands";i:"AX";p:"+358 18"} - ListElement{n:"Albania";i:"AL";p:"+355"} - ListElement{n:"Algeria";i:"DZ";p:"+213"} - ListElement{n:"American Samoa";i:"AS";p:"+1 684"} - ListElement{n:"Andorra";i:"AD";p:"+376"} - ListElement{n:"Angola";i:"AO";p:"+244"} - ListElement{n:"Anguilla";i:"AI";p:"+1 264"} - ListElement{n:"Antigua and Barbuda";i:"AG";p:"+1 268"} - ListElement{n:"Argentina";i:"AR";p:"+54"} - ListElement{n:"Armenia";i:"AM";p:"+374"} - ListElement{n:"Aruba";i:"AW";p:"+297"} - ListElement{n:"Ascension";i:"SH";p:"+247"} - ListElement{n:"Australia";i:"AU";p:"+61"} - ListElement{n:"Australian Antarctic Territory";i:"AQ";p:"+672 1"} - //ListElement{n:"Australian External Territories";i:"";p:"+672"} // NO ISO - ListElement{n:"Austria";i:"AT";p:"+43"} - ListElement{n:"Azerbaijan";i:"AZ";p:"+994"} - ListElement{n:"Bahamas";i:"BS";p:"+1 242"} - ListElement{n:"Bahrain";i:"BH";p:"+973"} - ListElement{n:"Bangladesh";i:"BD";p:"+880"} - ListElement{n:"Barbados";i:"BB";p:"+1 246"} - ListElement{n:"Barbuda";i:"AG";p:"+1 268"} - ListElement{n:"Belarus";i:"BY";p:"+375"} - ListElement{n:"Belgium";i:"BE";p:"+32"} - ListElement{n:"Belize";i:"BZ";p:"+501"} - ListElement{n:"Benin";i:"BJ";p:"+229"} - ListElement{n:"Bermuda";i:"BM";p:"+1 441"} - ListElement{n:"Bhutan";i:"BT";p:"+975"} - ListElement{n:"Bolivia";i:"BO";p:"+591"} - ListElement{n:"Bonaire";i:"BQ";p:"+599 7"} - ListElement{n:"Bosnia and Herzegovina";i:"BA";p:"+387"} - ListElement{n:"Botswana";i:"BW";p:"+267"} - ListElement{n:"Brazil";i:"BR";p:"+55"} - ListElement{n:"British Indian Ocean Territory";i:"IO";p:"+246"} - ListElement{n:"Brunei Darussalam";i:"BN";p:"+673"} - ListElement{n:"Bulgaria";i:"BG";p:"+359"} - ListElement{n:"Burkina Faso";i:"BF";p:"+226"} - ListElement{n:"Burundi";i:"BI";p:"+257"} - ListElement{n:"Cambodia";i:"KH";p:"+855"} - ListElement{n:"Cameroon";i:"CM";p:"+237"} - ListElement{n:"Canada";i:"CA";p:"+1"} - ListElement{n:"Cape Verde";i:"CV";p:"+238"} - //ListElement{n:"Caribbean Netherlands";i:"";p:"+599 3"} // NO ISO - //ListElement{n:"Caribbean Netherlands";i:"";p:"+599 4"} // NO ISO - //ListElement{n:"Caribbean Netherlands";i:"";p:"+599 7"} // NO ISO - ListElement{n:"Cayman Islands";i:"KY";p:"+1 345"} - ListElement{n:"Central African Republic";i:"CF";p:"+236"} - ListElement{n:"Chad";i:"TD";p:"+235"} - ListElement{n:"Chatham Island (New Zealand)";i:"NZ";p:"+64"} - ListElement{n:"Chile";i:"CL";p:"+56"} - ListElement{n:"China";i:"CN";p:"+86"} - ListElement{n:"Christmas Island";i:"CX";p:"+61 89164"} - ListElement{n:"Cocos (Keeling) Islands";i:"CC";p:"+61 89162"} - ListElement{n:"Colombia";i:"CO";p:"+57"} - ListElement{n:"Comoros";i:"KM";p:"+269"} - ListElement{n:"Congo (Democratic Republic of the)";i:"CD";p:"+243"} - ListElement{n:"Congo";i:"CG";p:"+242"} - ListElement{n:"Cook Islands";i:"CK";p:"+682"} - ListElement{n:"Costa Rica";i:"CR";p:"+506"} - ListElement{n:"Côte d'Ivoire";i:"CI";p:"+225"} - ListElement{n:"Croatia";i:"HR";p:"+385"} - ListElement{n:"Cuba";i:"CU";p:"+53"} - ListElement{n:"Curaçao";i:"CW";p:"+599 9"} - ListElement{n:"Cyprus";i:"CY";p:"+357"} - ListElement{n:"Czech Republic";i:"CZ";p:"+420"} - ListElement{n:"Denmark";i:"DK";p:"+45"} - //ListElement{n:"Diego Garcia";i:"";p:"+246"} // NO ISO, OCC. BY GB - ListElement{n:"Djibouti";i:"DJ";p:"+253"} - ListElement{n:"Dominica";i:"DM";p:"+1 767"} - ListElement{n:"Dominican Republic";i:"DO";p:"+1 809"} - ListElement{n:"Dominican Republic";i:"DO";p:"+1 829"} - ListElement{n:"Dominican Republic";i:"DO";p:"+1 849"} - ListElement{n:"Easter Island";i:"CL";p:"+56"} - ListElement{n:"Ecuador";i:"EC";p:"+593"} - ListElement{n:"Egypt";i:"EG";p:"+20"} - ListElement{n:"El Salvador";i:"SV";p:"+503"} - ListElement{n:"Equatorial Guinea";i:"GQ";p:"+240"} - ListElement{n:"Eritrea";i:"ER";p:"+291"} - ListElement{n:"Estonia";i:"EE";p:"+372"} - ListElement{n:"eSwatini";i:"SZ";p:"+268"} - ListElement{n:"Ethiopia";i:"ET";p:"+251"} - ListElement{n:"Falkland Islands (Malvinas)";i:"FK";p:"+500"} - ListElement{n:"Faroe Islands";i:"FO";p:"+298"} - ListElement{n:"Fiji";i:"FJ";p:"+679"} - ListElement{n:"Finland";i:"FI";p:"+358"} - ListElement{n:"France";i:"FR";p:"+33"} - //ListElement{n:"French Antilles";i:"";p:"+596"} // NO ISO - ListElement{n:"French Guiana";i:"GF";p:"+594"} - ListElement{n:"French Polynesia";i:"PF";p:"+689"} - ListElement{n:"Gabon";i:"GA";p:"+241"} - ListElement{n:"Gambia";i:"GM";p:"+220"} - ListElement{n:"Georgia";i:"GE";p:"+995"} - ListElement{n:"Germany";i:"DE";p:"+49"} - ListElement{n:"Ghana";i:"GH";p:"+233"} - ListElement{n:"Gibraltar";i:"GI";p:"+350"} - ListElement{n:"Greece";i:"GR";p:"+30"} - ListElement{n:"Greenland";i:"GL";p:"+299"} - ListElement{n:"Grenada";i:"GD";p:"+1 473"} - ListElement{n:"Guadeloupe";i:"GP";p:"+590"} - ListElement{n:"Guam";i:"GU";p:"+1 671"} - ListElement{n:"Guatemala";i:"GT";p:"+502"} - ListElement{n:"Guernsey";i:"GG";p:"+44 1481"} - ListElement{n:"Guernsey";i:"GG";p:"+44 7781"} - ListElement{n:"Guernsey";i:"GG";p:"+44 7839"} - ListElement{n:"Guernsey";i:"GG";p:"+44 7911"} - ListElement{n:"Guinea-Bissau";i:"GW";p:"+245"} - ListElement{n:"Guinea";i:"GN";p:"+224"} - ListElement{n:"Guyana";i:"GY";p:"+592"} - ListElement{n:"Haiti";i:"HT";p:"+509"} - ListElement{n:"Honduras";i:"HN";p:"+504"} - ListElement{n:"Hong Kong";i:"HK";p:"+852"} - ListElement{n:"Hungary";i:"HU";p:"+36"} - ListElement{n:"Iceland";i:"IS";p:"+354"} - ListElement{n:"India";i:"IN";p:"+91"} - ListElement{n:"Indonesia";i:"ID";p:"+62"} - ListElement{n:"Iran";i:"IR";p:"+98"} - ListElement{n:"Iraq";i:"IQ";p:"+964"} - ListElement{n:"Ireland";i:"IE";p:"+353"} - ListElement{n:"Isle of Man";i:"IM";p:"+44 1624"} - ListElement{n:"Isle of Man";i:"IM";p:"+44 7524"} - ListElement{n:"Isle of Man";i:"IM";p:"+44 7624"} - ListElement{n:"Isle of Man";i:"IM";p:"+44 7924"} - ListElement{n:"Israel";i:"IL";p:"+972"} - ListElement{n:"Italy";i:"IT";p:"+39"} - ListElement{n:"Jamaica";i:"JM";p:"+1 876"} - ListElement{n:"Jan Mayen";i:"SJ";p:"+47 79"} - ListElement{n:"Japan";i:"JP";p:"+81"} - ListElement{n:"Jersey";i:"JE";p:"+44 1534"} - ListElement{n:"Jordan";i:"JO";p:"+962"} - ListElement{n:"Kazakhstan";i:"KZ";p:"+7 6"} - ListElement{n:"Kazakhstan";i:"KZ";p:"+7 7"} - ListElement{n:"Kenya";i:"KE";p:"+254"} - ListElement{n:"Kiribati";i:"KI";p:"+686"} - ListElement{n:"Korea (North)";i:"KP";p:"+850"} - ListElement{n:"Korea (South)";i:"KR";p:"+82"} - ListElement{n:"Kosovo";i:"XK";p:"+383"} // TEMP. CODE - ListElement{n:"Kuwait";i:"KW";p:"+965"} - ListElement{n:"Kyrgyzstan";i:"KG";p:"+996"} - ListElement{n:"Laos";i:"LA";p:"+856"} - ListElement{n:"Latvia";i:"LV";p:"+371"} - ListElement{n:"Lebanon";i:"LB";p:"+961"} - ListElement{n:"Lesotho";i:"LS";p:"+266"} - ListElement{n:"Liberia";i:"LR";p:"+231"} - ListElement{n:"Libya";i:"LY";p:"+218"} - ListElement{n:"Liechtenstein";i:"LI";p:"+423"} - ListElement{n:"Lithuania";i:"LT";p:"+370"} - ListElement{n:"Luxembourg";i:"LU";p:"+352"} - ListElement{n:"Macau (Macao)";i:"MO";p:"+853"} - ListElement{n:"Madagascar";i:"MG";p:"+261"} - ListElement{n:"Malawi";i:"MW";p:"+265"} - ListElement{n:"Malaysia";i:"MY";p:"+60"} - ListElement{n:"Maldives";i:"MV";p:"+960"} - ListElement{n:"Mali";i:"ML";p:"+223"} - ListElement{n:"Malta";i:"MT";p:"+356"} - ListElement{n:"Marshall Islands";i:"MH";p:"+692"} - ListElement{n:"Martinique";i:"MQ";p:"+596"} - ListElement{n:"Mauritania";i:"MR";p:"+222"} - ListElement{n:"Mauritius";i:"MU";p:"+230"} - ListElement{n:"Mayotte";i:"YT";p:"+262 269"} - ListElement{n:"Mayotte";i:"YT";p:"+262 639"} - ListElement{n:"Mexico";i:"MX";p:"+52"} - ListElement{n:"Micronesia (Federated States of)";i:"FM";p:"+691"} - ListElement{n:"Midway Island (USA)";i:"US";p:"+1 808"} - ListElement{n:"Moldova";i:"MD";p:"+373"} - ListElement{n:"Monaco";i:"MC";p:"+377"} - ListElement{n:"Mongolia";i:"MN";p:"+976"} - ListElement{n:"Montenegro";i:"ME";p:"+382"} - ListElement{n:"Montserrat";i:"MS";p:"+1 664"} - ListElement{n:"Morocco";i:"MA";p:"+212"} - ListElement{n:"Mozambique";i:"MZ";p:"+258"} - ListElement{n:"Myanmar";i:"MM";p:"+95"} - ListElement{n:"Nagorno-Karabakh";i:"AZ";p:"+374 47"} // NO OWN ISO, DISPUTED - ListElement{n:"Nagorno-Karabakh";i:"AZ";p:"+374 97"} // NO OWN ISO, DISPUTED - ListElement{n:"Namibia";i:"NA";p:"+264"} - ListElement{n:"Nauru";i:"NR";p:"+674"} - ListElement{n:"Nepal";i:"NP";p:"+977"} - ListElement{n:"Netherlands";i:"NL";p:"+31"} - ListElement{n:"Nevis";i:"KN";p:"+1 869"} - ListElement{n:"New Caledonia";i:"NC";p:"+687"} - ListElement{n:"New Zealand";i:"NZ";p:"+64"} - ListElement{n:"Nicaragua";i:"NI";p:"+505"} - ListElement{n:"Nigeria";i:"NG";p:"+234"} - ListElement{n:"Niger";i:"NE";p:"+227"} - ListElement{n:"Niue";i:"NU";p:"+683"} - ListElement{n:"Norfolk Island";i:"NF";p:"+672 3"} - ListElement{n:"Northern Cyprus";i:"CY";p:"+90 392"} // OCC. BY TR - ListElement{n:"Northern Ireland";i:"GB";p:"+44 28"} - ListElement{n:"Northern Mariana Islands";i:"MP";p:"+1 670"} - ListElement{n:"North Macedonia";i:"MK";p:"+389"} - ListElement{n:"Norway";i:"NO";p:"+47"} - ListElement{n:"Oman";i:"OM";p:"+968"} - ListElement{n:"Pakistan";i:"PK";p:"+92"} - ListElement{n:"Palau";i:"PW";p:"+680"} - ListElement{n:"Palestine (State of)";i:"PS";p:"+970"} - ListElement{n:"Panama";i:"PA";p:"+507"} - ListElement{n:"Papua New Guinea";i:"PG";p:"+675"} - ListElement{n:"Paraguay";i:"PY";p:"+595"} - ListElement{n:"Peru";i:"PE";p:"+51"} - ListElement{n:"Philippines";i:"PH";p:"+63"} - ListElement{n:"Pitcairn Islands";i:"PN";p:"+64"} - ListElement{n:"Poland";i:"PL";p:"+48"} - ListElement{n:"Portugal";i:"PT";p:"+351"} - ListElement{n:"Puerto Rico";i:"PR";p:"+1 787"} - ListElement{n:"Puerto Rico";i:"PR";p:"+1 939"} - ListElement{n:"Qatar";i:"QA";p:"+974"} - ListElement{n:"Réunion";i:"RE";p:"+262"} - ListElement{n:"Romania";i:"RO";p:"+40"} - ListElement{n:"Russia";i:"RU";p:"+7"} - ListElement{n:"Rwanda";i:"RW";p:"+250"} - ListElement{n:"Saba";i:"BQ";p:"+599 4"} - ListElement{n:"Saint Barthélemy";i:"BL";p:"+590"} - ListElement{n:"Saint Helena";i:"SH";p:"+290"} - ListElement{n:"Saint Kitts and Nevis";i:"KN";p:"+1 869"} - ListElement{n:"Saint Lucia";i:"LC";p:"+1 758"} - ListElement{n:"Saint Martin (France)";i:"MF";p:"+590"} - ListElement{n:"Saint Pierre and Miquelon";i:"PM";p:"+508"} - ListElement{n:"Saint Vincent and the Grenadines";i:"VC";p:"+1 784"} - ListElement{n:"Samoa";i:"WS";p:"+685"} - ListElement{n:"San Marino";i:"SM";p:"+378"} - ListElement{n:"São Tomé and Príncipe";i:"ST";p:"+239"} - ListElement{n:"Saudi Arabia";i:"SA";p:"+966"} - ListElement{n:"Senegal";i:"SN";p:"+221"} - ListElement{n:"Serbia";i:"RS";p:"+381"} - ListElement{n:"Seychelles";i:"SC";p:"+248"} - ListElement{n:"Sierra Leone";i:"SL";p:"+232"} - ListElement{n:"Singapore";i:"SG";p:"+65"} - ListElement{n:"Sint Eustatius";i:"BQ";p:"+599 3"} - ListElement{n:"Sint Maarten (Netherlands)";i:"SX";p:"+1 721"} - ListElement{n:"Slovakia";i:"SK";p:"+421"} - ListElement{n:"Slovenia";i:"SI";p:"+386"} - ListElement{n:"Solomon Islands";i:"SB";p:"+677"} - ListElement{n:"Somalia";i:"SO";p:"+252"} - ListElement{n:"South Africa";i:"ZA";p:"+27"} - ListElement{n:"South Georgia and the South Sandwich Islands";i:"GS";p:"+500"} - ListElement{n:"South Ossetia";i:"GE";p:"+995 34"} // NO OWN ISO, DISPUTED - ListElement{n:"South Sudan";i:"SS";p:"+211"} - ListElement{n:"Spain";i:"ES";p:"+34"} - ListElement{n:"Sri Lanka";i:"LK";p:"+94"} - ListElement{n:"Sudan";i:"SD";p:"+249"} - ListElement{n:"Suriname";i:"SR";p:"+597"} - ListElement{n:"Svalbard";i:"SJ";p:"+47 79"} - ListElement{n:"Sweden";i:"SE";p:"+46"} - ListElement{n:"Switzerland";i:"CH";p:"+41"} - ListElement{n:"Syria";i:"SY";p:"+963"} - ListElement{n:"Taiwan";i:"SJ";p:"+886"} - ListElement{n:"Tajikistan";i:"TJ";p:"+992"} - ListElement{n:"Tanzania";i:"TZ";p:"+255"} - ListElement{n:"Thailand";i:"TH";p:"+66"} - ListElement{n:"Timor-Leste";i:"TL";p:"+670"} - ListElement{n:"Togo";i:"TG";p:"+228"} - ListElement{n:"Tokelau";i:"TK";p:"+690"} - ListElement{n:"Tonga";i:"TO";p:"+676"} - ListElement{n:"Transnistria";i:"MD";p:"+373 2"} - ListElement{n:"Transnistria";i:"MD";p:"+373 5"} - ListElement{n:"Trinidad and Tobago";i:"TT";p:"+1 868"} - ListElement{n:"Tristan da Cunha";i:"SH";p:"+290 8"} - ListElement{n:"Tunisia";i:"TN";p:"+216"} - ListElement{n:"Turkey";i:"TR";p:"+90"} - ListElement{n:"Turkmenistan";i:"TM";p:"+993"} - ListElement{n:"Turks and Caicos Islands";i:"TC";p:"+1 649"} - ListElement{n:"Tuvalu";i:"TV";p:"+688"} - ListElement{n:"Uganda";i:"UG";p:"+256"} - ListElement{n:"Ukraine";i:"UA";p:"+380"} - ListElement{n:"United Arab Emirates";i:"AE";p:"+971"} - ListElement{n:"United Kingdom";i:"GB";p:"+44"} - ListElement{n:"United States";i:"US";p:"+1"} - ListElement{n:"Uruguay";i:"UY";p:"+598"} - ListElement{n:"Uzbekistan";i:"UZ";p:"+998"} - ListElement{n:"Vanuatu";i:"VU";p:"+678"} - ListElement{n:"Vatican City State (Holy See)";i:"VA";p:"+379"} - ListElement{n:"Vatican City State (Holy See)";i:"VA";p:"+39 06 698"} - ListElement{n:"Venezuela";i:"VE";p:"+58"} - ListElement{n:"Vietnam";i:"VN";p:"+84"} - ListElement{n:"Virgin Islands (British)";i:"VG";p:"+1 284"} - ListElement{n:"Virgin Islands (US)";i:"VI";p:"+1 340"} - ListElement{n:"Wake Island (USA)";i:"US";p:"+1 808"} - ListElement{n:"Wallis and Futuna";i:"WF";p:"+681"} - ListElement{n:"Yemen";i:"YE";p:"+967"} - ListElement{n:"Zambia";i:"ZM";p:"+260"} - ListElement{n:"Zanzibar";i:"TZ";p:"+255 24"} // NO OWN ISO, DISPUTED? - ListElement{n:"Zimbabwe";i:"ZW";p:"+263"} - } + + //n=name,i=ISO,p=prefix -- see countries.js.md for source + model: ListModel { + ListElement { + n: "Afghanistan" + i: "AF" + p: "+93" + } + + ListElement { + n: "Åland Islands" + i: "AX" + p: "+358 18" + } + + ListElement { + n: "Albania" + i: "AL" + p: "+355" + } + + ListElement { + n: "Algeria" + i: "DZ" + p: "+213" + } + + ListElement { + n: "American Samoa" + i: "AS" + p: "+1 684" + } + + ListElement { + n: "Andorra" + i: "AD" + p: "+376" + } + + ListElement { + n: "Angola" + i: "AO" + p: "+244" + } + + ListElement { + n: "Anguilla" + i: "AI" + p: "+1 264" + } + + ListElement { + n: "Antigua and Barbuda" + i: "AG" + p: "+1 268" + } + + ListElement { + n: "Argentina" + i: "AR" + p: "+54" + } + + ListElement { + n: "Armenia" + i: "AM" + p: "+374" + } + + ListElement { + n: "Aruba" + i: "AW" + p: "+297" + } + + ListElement { + n: "Ascension" + i: "SH" + p: "+247" + } + + ListElement { + n: "Australia" + i: "AU" + p: "+61" + } + + ListElement { + n: "Australian Antarctic Territory" + i: "AQ" + p: "+672 1" + } + //ListElement{n:"Australian External Territories";i:"";p:"+672"} // NO ISO + + ListElement { + n: "Austria" + i: "AT" + p: "+43" + } + + ListElement { + n: "Azerbaijan" + i: "AZ" + p: "+994" + } + + ListElement { + n: "Bahamas" + i: "BS" + p: "+1 242" + } + + ListElement { + n: "Bahrain" + i: "BH" + p: "+973" + } + + ListElement { + n: "Bangladesh" + i: "BD" + p: "+880" + } + + ListElement { + n: "Barbados" + i: "BB" + p: "+1 246" + } + + ListElement { + n: "Barbuda" + i: "AG" + p: "+1 268" + } + + ListElement { + n: "Belarus" + i: "BY" + p: "+375" + } + + ListElement { + n: "Belgium" + i: "BE" + p: "+32" + } + + ListElement { + n: "Belize" + i: "BZ" + p: "+501" + } + + ListElement { + n: "Benin" + i: "BJ" + p: "+229" + } + + ListElement { + n: "Bermuda" + i: "BM" + p: "+1 441" + } + + ListElement { + n: "Bhutan" + i: "BT" + p: "+975" + } + + ListElement { + n: "Bolivia" + i: "BO" + p: "+591" + } + + ListElement { + n: "Bonaire" + i: "BQ" + p: "+599 7" + } + + ListElement { + n: "Bosnia and Herzegovina" + i: "BA" + p: "+387" + } + + ListElement { + n: "Botswana" + i: "BW" + p: "+267" + } + + ListElement { + n: "Brazil" + i: "BR" + p: "+55" + } + + ListElement { + n: "British Indian Ocean Territory" + i: "IO" + p: "+246" + } + + ListElement { + n: "Brunei Darussalam" + i: "BN" + p: "+673" + } + + ListElement { + n: "Bulgaria" + i: "BG" + p: "+359" + } + + ListElement { + n: "Burkina Faso" + i: "BF" + p: "+226" + } + + ListElement { + n: "Burundi" + i: "BI" + p: "+257" + } + + ListElement { + n: "Cambodia" + i: "KH" + p: "+855" + } + + ListElement { + n: "Cameroon" + i: "CM" + p: "+237" + } + + ListElement { + n: "Canada" + i: "CA" + p: "+1" + } + + ListElement { + n: "Cape Verde" + i: "CV" + p: "+238" + } + //ListElement{n:"Caribbean Netherlands";i:"";p:"+599 3"} // NO ISO + + //ListElement{n:"Caribbean Netherlands";i:"";p:"+599 4"} // NO ISO + //ListElement{n:"Caribbean Netherlands";i:"";p:"+599 7"} // NO ISO + ListElement { + n: "Cayman Islands" + i: "KY" + p: "+1 345" + } + + ListElement { + n: "Central African Republic" + i: "CF" + p: "+236" + } + + ListElement { + n: "Chad" + i: "TD" + p: "+235" + } + + ListElement { + n: "Chatham Island (New Zealand)" + i: "NZ" + p: "+64" + } + + ListElement { + n: "Chile" + i: "CL" + p: "+56" + } + + ListElement { + n: "China" + i: "CN" + p: "+86" + } + + ListElement { + n: "Christmas Island" + i: "CX" + p: "+61 89164" + } + + ListElement { + n: "Cocos (Keeling) Islands" + i: "CC" + p: "+61 89162" + } + + ListElement { + n: "Colombia" + i: "CO" + p: "+57" + } + + ListElement { + n: "Comoros" + i: "KM" + p: "+269" + } + + ListElement { + n: "Congo (Democratic Republic of the)" + i: "CD" + p: "+243" + } + + ListElement { + n: "Congo" + i: "CG" + p: "+242" + } + + ListElement { + n: "Cook Islands" + i: "CK" + p: "+682" + } + + ListElement { + n: "Costa Rica" + i: "CR" + p: "+506" + } + + ListElement { + n: "Côte d'Ivoire" + i: "CI" + p: "+225" + } + + ListElement { + n: "Croatia" + i: "HR" + p: "+385" + } + + ListElement { + n: "Cuba" + i: "CU" + p: "+53" + } + + ListElement { + n: "Curaçao" + i: "CW" + p: "+599 9" + } + + ListElement { + n: "Cyprus" + i: "CY" + p: "+357" + } + + ListElement { + n: "Czech Republic" + i: "CZ" + p: "+420" + } + + ListElement { + n: "Denmark" + i: "DK" + p: "+45" + } + //ListElement{n:"Diego Garcia";i:"";p:"+246"} // NO ISO, OCC. BY GB + + ListElement { + n: "Djibouti" + i: "DJ" + p: "+253" + } + + ListElement { + n: "Dominica" + i: "DM" + p: "+1 767" + } + + ListElement { + n: "Dominican Republic" + i: "DO" + p: "+1 809" + } + + ListElement { + n: "Dominican Republic" + i: "DO" + p: "+1 829" + } + + ListElement { + n: "Dominican Republic" + i: "DO" + p: "+1 849" + } + + ListElement { + n: "Easter Island" + i: "CL" + p: "+56" + } + + ListElement { + n: "Ecuador" + i: "EC" + p: "+593" + } + + ListElement { + n: "Egypt" + i: "EG" + p: "+20" + } + + ListElement { + n: "El Salvador" + i: "SV" + p: "+503" + } + + ListElement { + n: "Equatorial Guinea" + i: "GQ" + p: "+240" + } + + ListElement { + n: "Eritrea" + i: "ER" + p: "+291" + } + + ListElement { + n: "Estonia" + i: "EE" + p: "+372" + } + + ListElement { + n: "eSwatini" + i: "SZ" + p: "+268" + } + + ListElement { + n: "Ethiopia" + i: "ET" + p: "+251" + } + + ListElement { + n: "Falkland Islands (Malvinas)" + i: "FK" + p: "+500" + } + + ListElement { + n: "Faroe Islands" + i: "FO" + p: "+298" + } + + ListElement { + n: "Fiji" + i: "FJ" + p: "+679" + } + + ListElement { + n: "Finland" + i: "FI" + p: "+358" + } + + ListElement { + n: "France" + i: "FR" + p: "+33" + } + //ListElement{n:"French Antilles";i:"";p:"+596"} // NO ISO + + ListElement { + n: "French Guiana" + i: "GF" + p: "+594" + } + + ListElement { + n: "French Polynesia" + i: "PF" + p: "+689" + } + + ListElement { + n: "Gabon" + i: "GA" + p: "+241" + } + + ListElement { + n: "Gambia" + i: "GM" + p: "+220" + } + + ListElement { + n: "Georgia" + i: "GE" + p: "+995" + } + + ListElement { + n: "Germany" + i: "DE" + p: "+49" + } + + ListElement { + n: "Ghana" + i: "GH" + p: "+233" + } + + ListElement { + n: "Gibraltar" + i: "GI" + p: "+350" + } + + ListElement { + n: "Greece" + i: "GR" + p: "+30" + } + + ListElement { + n: "Greenland" + i: "GL" + p: "+299" + } + + ListElement { + n: "Grenada" + i: "GD" + p: "+1 473" + } + + ListElement { + n: "Guadeloupe" + i: "GP" + p: "+590" + } + + ListElement { + n: "Guam" + i: "GU" + p: "+1 671" + } + + ListElement { + n: "Guatemala" + i: "GT" + p: "+502" + } + + ListElement { + n: "Guernsey" + i: "GG" + p: "+44 1481" + } + + ListElement { + n: "Guernsey" + i: "GG" + p: "+44 7781" + } + + ListElement { + n: "Guernsey" + i: "GG" + p: "+44 7839" + } + + ListElement { + n: "Guernsey" + i: "GG" + p: "+44 7911" + } + + ListElement { + n: "Guinea-Bissau" + i: "GW" + p: "+245" + } + + ListElement { + n: "Guinea" + i: "GN" + p: "+224" + } + + ListElement { + n: "Guyana" + i: "GY" + p: "+592" + } + + ListElement { + n: "Haiti" + i: "HT" + p: "+509" + } + + ListElement { + n: "Honduras" + i: "HN" + p: "+504" + } + + ListElement { + n: "Hong Kong" + i: "HK" + p: "+852" + } + + ListElement { + n: "Hungary" + i: "HU" + p: "+36" + } + + ListElement { + n: "Iceland" + i: "IS" + p: "+354" + } + + ListElement { + n: "India" + i: "IN" + p: "+91" + } + + ListElement { + n: "Indonesia" + i: "ID" + p: "+62" + } + + ListElement { + n: "Iran" + i: "IR" + p: "+98" + } + + ListElement { + n: "Iraq" + i: "IQ" + p: "+964" + } + + ListElement { + n: "Ireland" + i: "IE" + p: "+353" + } + + ListElement { + n: "Isle of Man" + i: "IM" + p: "+44 1624" + } + + ListElement { + n: "Isle of Man" + i: "IM" + p: "+44 7524" + } + + ListElement { + n: "Isle of Man" + i: "IM" + p: "+44 7624" + } + + ListElement { + n: "Isle of Man" + i: "IM" + p: "+44 7924" + } + + ListElement { + n: "Israel" + i: "IL" + p: "+972" + } + + ListElement { + n: "Italy" + i: "IT" + p: "+39" + } + + ListElement { + n: "Jamaica" + i: "JM" + p: "+1 876" + } + + ListElement { + n: "Jan Mayen" + i: "SJ" + p: "+47 79" + } + + ListElement { + n: "Japan" + i: "JP" + p: "+81" + } + + ListElement { + n: "Jersey" + i: "JE" + p: "+44 1534" + } + + ListElement { + n: "Jordan" + i: "JO" + p: "+962" + } + + ListElement { + n: "Kazakhstan" + i: "KZ" + p: "+7 6" + } + + ListElement { + n: "Kazakhstan" + i: "KZ" + p: "+7 7" + } + + ListElement { + n: "Kenya" + i: "KE" + p: "+254" + } + + ListElement { + n: "Kiribati" + i: "KI" + p: "+686" + } + + ListElement { + n: "Korea (North)" + i: "KP" + p: "+850" + } + + ListElement { + n: "Korea (South)" + i: "KR" + p: "+82" + } + // TEMP. CODE + + ListElement { + n: "Kosovo" + i: "XK" + p: "+383" + } + + ListElement { + n: "Kuwait" + i: "KW" + p: "+965" + } + + ListElement { + n: "Kyrgyzstan" + i: "KG" + p: "+996" + } + + ListElement { + n: "Laos" + i: "LA" + p: "+856" + } + + ListElement { + n: "Latvia" + i: "LV" + p: "+371" + } + + ListElement { + n: "Lebanon" + i: "LB" + p: "+961" + } + + ListElement { + n: "Lesotho" + i: "LS" + p: "+266" + } + + ListElement { + n: "Liberia" + i: "LR" + p: "+231" + } + + ListElement { + n: "Libya" + i: "LY" + p: "+218" + } + + ListElement { + n: "Liechtenstein" + i: "LI" + p: "+423" + } + + ListElement { + n: "Lithuania" + i: "LT" + p: "+370" + } + + ListElement { + n: "Luxembourg" + i: "LU" + p: "+352" + } + + ListElement { + n: "Macau (Macao)" + i: "MO" + p: "+853" + } + + ListElement { + n: "Madagascar" + i: "MG" + p: "+261" + } + + ListElement { + n: "Malawi" + i: "MW" + p: "+265" + } + + ListElement { + n: "Malaysia" + i: "MY" + p: "+60" + } + + ListElement { + n: "Maldives" + i: "MV" + p: "+960" + } + + ListElement { + n: "Mali" + i: "ML" + p: "+223" + } + + ListElement { + n: "Malta" + i: "MT" + p: "+356" + } + + ListElement { + n: "Marshall Islands" + i: "MH" + p: "+692" + } + + ListElement { + n: "Martinique" + i: "MQ" + p: "+596" + } + + ListElement { + n: "Mauritania" + i: "MR" + p: "+222" + } + + ListElement { + n: "Mauritius" + i: "MU" + p: "+230" + } + + ListElement { + n: "Mayotte" + i: "YT" + p: "+262 269" + } + + ListElement { + n: "Mayotte" + i: "YT" + p: "+262 639" + } + + ListElement { + n: "Mexico" + i: "MX" + p: "+52" + } + + ListElement { + n: "Micronesia (Federated States of)" + i: "FM" + p: "+691" + } + + ListElement { + n: "Midway Island (USA)" + i: "US" + p: "+1 808" + } + + ListElement { + n: "Moldova" + i: "MD" + p: "+373" + } + + ListElement { + n: "Monaco" + i: "MC" + p: "+377" + } + + ListElement { + n: "Mongolia" + i: "MN" + p: "+976" + } + + ListElement { + n: "Montenegro" + i: "ME" + p: "+382" + } + + ListElement { + n: "Montserrat" + i: "MS" + p: "+1 664" + } + + ListElement { + n: "Morocco" + i: "MA" + p: "+212" + } + + ListElement { + n: "Mozambique" + i: "MZ" + p: "+258" + } + + ListElement { + n: "Myanmar" + i: "MM" + p: "+95" + } + // NO OWN ISO, DISPUTED + + ListElement { + n: "Nagorno-Karabakh" + i: "AZ" + p: "+374 47" + } + // NO OWN ISO, DISPUTED + + ListElement { + n: "Nagorno-Karabakh" + i: "AZ" + p: "+374 97" + } + + ListElement { + n: "Namibia" + i: "NA" + p: "+264" + } + + ListElement { + n: "Nauru" + i: "NR" + p: "+674" + } + + ListElement { + n: "Nepal" + i: "NP" + p: "+977" + } + + ListElement { + n: "Netherlands" + i: "NL" + p: "+31" + } + + ListElement { + n: "Nevis" + i: "KN" + p: "+1 869" + } + + ListElement { + n: "New Caledonia" + i: "NC" + p: "+687" + } + + ListElement { + n: "New Zealand" + i: "NZ" + p: "+64" + } + + ListElement { + n: "Nicaragua" + i: "NI" + p: "+505" + } + + ListElement { + n: "Nigeria" + i: "NG" + p: "+234" + } + + ListElement { + n: "Niger" + i: "NE" + p: "+227" + } + + ListElement { + n: "Niue" + i: "NU" + p: "+683" + } + + ListElement { + n: "Norfolk Island" + i: "NF" + p: "+672 3" + } + // OCC. BY TR + + ListElement { + n: "Northern Cyprus" + i: "CY" + p: "+90 392" + } + + ListElement { + n: "Northern Ireland" + i: "GB" + p: "+44 28" + } + + ListElement { + n: "Northern Mariana Islands" + i: "MP" + p: "+1 670" + } + + ListElement { + n: "North Macedonia" + i: "MK" + p: "+389" + } + + ListElement { + n: "Norway" + i: "NO" + p: "+47" + } + + ListElement { + n: "Oman" + i: "OM" + p: "+968" + } + + ListElement { + n: "Pakistan" + i: "PK" + p: "+92" + } + + ListElement { + n: "Palau" + i: "PW" + p: "+680" + } + + ListElement { + n: "Palestine (State of)" + i: "PS" + p: "+970" + } + + ListElement { + n: "Panama" + i: "PA" + p: "+507" + } + + ListElement { + n: "Papua New Guinea" + i: "PG" + p: "+675" + } + + ListElement { + n: "Paraguay" + i: "PY" + p: "+595" + } + + ListElement { + n: "Peru" + i: "PE" + p: "+51" + } + + ListElement { + n: "Philippines" + i: "PH" + p: "+63" + } + + ListElement { + n: "Pitcairn Islands" + i: "PN" + p: "+64" + } + + ListElement { + n: "Poland" + i: "PL" + p: "+48" + } + + ListElement { + n: "Portugal" + i: "PT" + p: "+351" + } + + ListElement { + n: "Puerto Rico" + i: "PR" + p: "+1 787" + } + + ListElement { + n: "Puerto Rico" + i: "PR" + p: "+1 939" + } + + ListElement { + n: "Qatar" + i: "QA" + p: "+974" + } + + ListElement { + n: "Réunion" + i: "RE" + p: "+262" + } + + ListElement { + n: "Romania" + i: "RO" + p: "+40" + } + + ListElement { + n: "Russia" + i: "RU" + p: "+7" + } + + ListElement { + n: "Rwanda" + i: "RW" + p: "+250" + } + + ListElement { + n: "Saba" + i: "BQ" + p: "+599 4" + } + + ListElement { + n: "Saint Barthélemy" + i: "BL" + p: "+590" + } + + ListElement { + n: "Saint Helena" + i: "SH" + p: "+290" + } + + ListElement { + n: "Saint Kitts and Nevis" + i: "KN" + p: "+1 869" + } + + ListElement { + n: "Saint Lucia" + i: "LC" + p: "+1 758" + } + + ListElement { + n: "Saint Martin (France)" + i: "MF" + p: "+590" + } + + ListElement { + n: "Saint Pierre and Miquelon" + i: "PM" + p: "+508" + } + + ListElement { + n: "Saint Vincent and the Grenadines" + i: "VC" + p: "+1 784" + } + + ListElement { + n: "Samoa" + i: "WS" + p: "+685" + } + + ListElement { + n: "San Marino" + i: "SM" + p: "+378" + } + + ListElement { + n: "São Tomé and Príncipe" + i: "ST" + p: "+239" + } + + ListElement { + n: "Saudi Arabia" + i: "SA" + p: "+966" + } + + ListElement { + n: "Senegal" + i: "SN" + p: "+221" + } + + ListElement { + n: "Serbia" + i: "RS" + p: "+381" + } + + ListElement { + n: "Seychelles" + i: "SC" + p: "+248" + } + + ListElement { + n: "Sierra Leone" + i: "SL" + p: "+232" + } + + ListElement { + n: "Singapore" + i: "SG" + p: "+65" + } + + ListElement { + n: "Sint Eustatius" + i: "BQ" + p: "+599 3" + } + + ListElement { + n: "Sint Maarten (Netherlands)" + i: "SX" + p: "+1 721" + } + + ListElement { + n: "Slovakia" + i: "SK" + p: "+421" + } + + ListElement { + n: "Slovenia" + i: "SI" + p: "+386" + } + + ListElement { + n: "Solomon Islands" + i: "SB" + p: "+677" + } + + ListElement { + n: "Somalia" + i: "SO" + p: "+252" + } + + ListElement { + n: "South Africa" + i: "ZA" + p: "+27" + } + + ListElement { + n: "South Georgia and the South Sandwich Islands" + i: "GS" + p: "+500" + } + // NO OWN ISO, DISPUTED + + ListElement { + n: "South Ossetia" + i: "GE" + p: "+995 34" + } + + ListElement { + n: "South Sudan" + i: "SS" + p: "+211" + } + + ListElement { + n: "Spain" + i: "ES" + p: "+34" + } + + ListElement { + n: "Sri Lanka" + i: "LK" + p: "+94" + } + + ListElement { + n: "Sudan" + i: "SD" + p: "+249" + } + + ListElement { + n: "Suriname" + i: "SR" + p: "+597" + } + + ListElement { + n: "Svalbard" + i: "SJ" + p: "+47 79" + } + + ListElement { + n: "Sweden" + i: "SE" + p: "+46" + } + + ListElement { + n: "Switzerland" + i: "CH" + p: "+41" + } + + ListElement { + n: "Syria" + i: "SY" + p: "+963" + } + + ListElement { + n: "Taiwan" + i: "SJ" + p: "+886" + } + + ListElement { + n: "Tajikistan" + i: "TJ" + p: "+992" + } + + ListElement { + n: "Tanzania" + i: "TZ" + p: "+255" + } + + ListElement { + n: "Thailand" + i: "TH" + p: "+66" + } + + ListElement { + n: "Timor-Leste" + i: "TL" + p: "+670" + } + + ListElement { + n: "Togo" + i: "TG" + p: "+228" + } + + ListElement { + n: "Tokelau" + i: "TK" + p: "+690" + } + + ListElement { + n: "Tonga" + i: "TO" + p: "+676" + } + + ListElement { + n: "Transnistria" + i: "MD" + p: "+373 2" + } + + ListElement { + n: "Transnistria" + i: "MD" + p: "+373 5" + } + + ListElement { + n: "Trinidad and Tobago" + i: "TT" + p: "+1 868" + } + + ListElement { + n: "Tristan da Cunha" + i: "SH" + p: "+290 8" + } + + ListElement { + n: "Tunisia" + i: "TN" + p: "+216" + } + + ListElement { + n: "Turkey" + i: "TR" + p: "+90" + } + + ListElement { + n: "Turkmenistan" + i: "TM" + p: "+993" + } + + ListElement { + n: "Turks and Caicos Islands" + i: "TC" + p: "+1 649" + } + + ListElement { + n: "Tuvalu" + i: "TV" + p: "+688" + } + + ListElement { + n: "Uganda" + i: "UG" + p: "+256" + } + + ListElement { + n: "Ukraine" + i: "UA" + p: "+380" + } + + ListElement { + n: "United Arab Emirates" + i: "AE" + p: "+971" + } + + ListElement { + n: "United Kingdom" + i: "GB" + p: "+44" + } + + ListElement { + n: "United States" + i: "US" + p: "+1" + } + + ListElement { + n: "Uruguay" + i: "UY" + p: "+598" + } + + ListElement { + n: "Uzbekistan" + i: "UZ" + p: "+998" + } + + ListElement { + n: "Vanuatu" + i: "VU" + p: "+678" + } + + ListElement { + n: "Vatican City State (Holy See)" + i: "VA" + p: "+379" + } + + ListElement { + n: "Vatican City State (Holy See)" + i: "VA" + p: "+39 06 698" + } + + ListElement { + n: "Venezuela" + i: "VE" + p: "+58" + } + + ListElement { + n: "Vietnam" + i: "VN" + p: "+84" + } + + ListElement { + n: "Virgin Islands (British)" + i: "VG" + p: "+1 284" + } + + ListElement { + n: "Virgin Islands (US)" + i: "VI" + p: "+1 340" + } + + ListElement { + n: "Wake Island (USA)" + i: "US" + p: "+1 808" + } + + ListElement { + n: "Wallis and Futuna" + i: "WF" + p: "+681" + } + + ListElement { + n: "Yemen" + i: "YE" + p: "+967" + } + + ListElement { + n: "Zambia" + i: "ZM" + p: "+260" + } + // NO OWN ISO, DISPUTED? + + ListElement { + n: "Zanzibar" + i: "TZ" + p: "+255 24" + } + + ListElement { + n: "Zimbabwe" + i: "ZW" + p: "+263" + } + + } } diff --git a/resources/qml/dialogs/UserProfile.qml b/resources/qml/dialogs/UserProfile.qml index 4c7095b2..da573ec1 100644 --- a/resources/qml/dialogs/UserProfile.qml +++ b/resources/qml/dialogs/UserProfile.qml @@ -319,22 +319,25 @@ ApplicationWindow { } ImageButton { - Layout.alignment: Qt.AlignTop - image: ":/icons/icons/ui/power-button-off.png" - hoverEnabled: true - ToolTip.visible: hovered - ToolTip.text: qsTr("Sign out this device.") - onClicked: profile.signOutDevice(deviceId) - visible: profile.isSelf + Layout.alignment: Qt.AlignTop + image: ":/icons/icons/ui/power-button-off.png" + hoverEnabled: true + ToolTip.visible: hovered + ToolTip.text: qsTr("Sign out this device.") + onClicked: profile.signOutDevice(deviceId) + visible: profile.isSelf } + } RowLayout { id: deviceNameRow + property bool isEditingAllowed TextInput { id: deviceNameField + readOnly: !deviceNameRow.isEditingAllowed text: deviceName color: Nheko.colors.text @@ -373,7 +376,7 @@ ApplicationWindow { Layout.alignment: Qt.AlignLeft elide: Text.ElideRight color: Nheko.colors.text - text: qsTr("Last seen %1 from %2").arg(new Date(lastTs).toLocaleString(Locale.ShortFormat)).arg(lastIp?lastIp:"???") + text: qsTr("Last seen %1 from %2").arg(new Date(lastTs).toLocaleString(Locale.ShortFormat)).arg(lastIp ? lastIp : "???") } } @@ -409,8 +412,6 @@ ApplicationWindow { } } - - } footer: DialogButtonBox { From 8563ec002dc62111217046ce8ec71d49234874dc Mon Sep 17 00:00:00 2001 From: Nicolas Werner Date: Wed, 3 Nov 2021 23:20:28 +0100 Subject: [PATCH 231/232] Use ItemDelegate for CommunitiesList --- resources/qml/CommunitiesList.qml | 47 +++++++++++++++---------------- 1 file changed, 23 insertions(+), 24 deletions(-) diff --git a/resources/qml/CommunitiesList.qml b/resources/qml/CommunitiesList.qml index ff9b7da7..6a2c642c 100644 --- a/resources/qml/CommunitiesList.qml +++ b/resources/qml/CommunitiesList.qml @@ -47,29 +47,32 @@ Page { } - delegate: Rectangle { + delegate: ItemDelegate { id: communityItem - property color background: Nheko.colors.window + property color backgroundColor: Nheko.colors.window property color importantText: Nheko.colors.text property color unimportantText: Nheko.colors.buttonText property color bubbleBackground: Nheko.colors.highlight property color bubbleText: Nheko.colors.highlightedText - color: background + background: Rectangle { + color: backgroundColor + } + height: avatarSize + 2 * Nheko.paddingMedium width: ListView.view.width state: "normal" - ToolTip.visible: hovered.hovered && collapsed + ToolTip.visible: hovered && collapsed ToolTip.text: model.tooltip states: [ State { name: "highlight" - when: (hovered.hovered || model.hidden) && !(Communities.currentTagId == model.id) + when: (communityItem.hovered || model.hidden) && !(Communities.currentTagId == model.id) PropertyChanges { target: communityItem - background: Nheko.colors.dark + backgroundColor: Nheko.colors.dark importantText: Nheko.colors.brightText unimportantText: Nheko.colors.brightText bubbleBackground: Nheko.colors.highlight @@ -83,7 +86,7 @@ Page { PropertyChanges { target: communityItem - background: Nheko.colors.highlight + backgroundColor: Nheko.colors.highlight importantText: Nheko.colors.highlightedText unimportantText: Nheko.colors.highlightedText bubbleBackground: Nheko.colors.highlightedText @@ -93,24 +96,20 @@ Page { } ] - TapHandler { - margin: -Nheko.paddingSmall - acceptedButtons: Qt.RightButton - onSingleTapped: communityContextMenu.show(model.id) - gesturePolicy: TapHandler.ReleaseWithinBounds + Item { + anchors.fill: parent + + TapHandler { + acceptedButtons: Qt.RightButton + onSingleTapped: communityContextMenu.show(model.id) + gesturePolicy: TapHandler.ReleaseWithinBounds + acceptedDevices: PointerDevice.Mouse | PointerDevice.Stylus | PointerDevice.TouchPad + } + } - TapHandler { - margin: -Nheko.paddingSmall - onSingleTapped: Communities.setCurrentTagId(model.id) - onLongPressed: communityContextMenu.show(model.id) - } - - HoverHandler { - id: hovered - - margin: -Nheko.paddingSmall - } + onClicked: Communities.setCurrentTagId(model.id) + onPressAndHold: communityContextMenu.show(model.id) RowLayout { spacing: Nheko.paddingMedium @@ -132,7 +131,7 @@ Page { } roomid: model.id displayName: model.displayName - color: communityItem.background + color: communityItem.backgroundColor } ElidedLabel { From e1b9a0c61949b85fa1396681e3ed8b107f69e24b Mon Sep 17 00:00:00 2001 From: Nicolas Werner Date: Wed, 3 Nov 2021 23:44:55 +0100 Subject: [PATCH 232/232] Update translations --- resources/langs/nheko_cs.ts | 672 ++++++++++++++++++---------- resources/langs/nheko_de.ts | 678 ++++++++++++++++++---------- resources/langs/nheko_el.ts | 672 ++++++++++++++++++---------- resources/langs/nheko_en.ts | 680 ++++++++++++++++++---------- resources/langs/nheko_eo.ts | 676 ++++++++++++++++++---------- resources/langs/nheko_es.ts | 672 ++++++++++++++++++---------- resources/langs/nheko_et.ts | 678 ++++++++++++++++++---------- resources/langs/nheko_fi.ts | 678 ++++++++++++++++++---------- resources/langs/nheko_fr.ts | 784 ++++++++++++++++++++------------- resources/langs/nheko_hu.ts | 672 ++++++++++++++++++---------- resources/langs/nheko_id.ts | 678 ++++++++++++++++++---------- resources/langs/nheko_it.ts | 672 ++++++++++++++++++---------- resources/langs/nheko_ja.ts | 672 ++++++++++++++++++---------- resources/langs/nheko_ml.ts | 672 ++++++++++++++++++---------- resources/langs/nheko_nl.ts | 686 ++++++++++++++++++----------- resources/langs/nheko_pl.ts | 680 ++++++++++++++++++---------- resources/langs/nheko_pt_BR.ts | 672 ++++++++++++++++++---------- resources/langs/nheko_pt_PT.ts | 690 ++++++++++++++++++----------- resources/langs/nheko_ro.ts | 672 ++++++++++++++++++---------- resources/langs/nheko_ru.ts | 672 ++++++++++++++++++---------- resources/langs/nheko_si.ts | 672 ++++++++++++++++++---------- resources/langs/nheko_sv.ts | 672 ++++++++++++++++++---------- resources/langs/nheko_zh_CN.ts | 672 ++++++++++++++++++---------- src/Cache.cpp | 10 +- 24 files changed, 10059 insertions(+), 5595 deletions(-) diff --git a/resources/langs/nheko_cs.ts b/resources/langs/nheko_cs.ts index b0307278..9d5a913c 100644 --- a/resources/langs/nheko_cs.ts +++ b/resources/langs/nheko_cs.ts @@ -4,7 +4,7 @@ ActiveCallBar - + Calling... @@ -56,7 +56,7 @@ CallInvite - + Video Call @@ -74,7 +74,7 @@ CallInviteBar - + Video Call @@ -117,7 +117,7 @@ CallManager - + Entire screen @@ -125,23 +125,23 @@ ChatPage - + Failed to invite user: %1 - + Invited user: %1 - + Migrating the cache to the current version failed. This can have different reasons. Please open an issue and try to use an older version in the mean time. Alternatively you can try deleting the cache manually. - + Confirm join @@ -151,23 +151,23 @@ - + Room %1 created. - - + + Confirm invite - + Do you really want to invite %1 (%2)? - + Failed to invite %1 to %2: %3 @@ -182,7 +182,7 @@ - + Kicked user: %1 @@ -197,7 +197,7 @@ - + Failed to ban %1 in %2: %3 @@ -217,7 +217,7 @@ - + Failed to unban %1 in %2: %3 @@ -227,12 +227,12 @@ - + Do you really want to start a private chat with %1? - + Cache migration failed! @@ -259,23 +259,23 @@ - + Failed to setup encryption keys. Server response: %1 %2. Please try again later. - - + + Please try to login again: %1 - + Failed to join room: %1 - + You joined the room @@ -285,17 +285,17 @@ - + Room creation failed: %1 - + Failed to leave room: %1 - + Failed to kick %1 from %2: %3 @@ -354,7 +354,7 @@ CrossSigningSecrets - + Decrypt secrets @@ -364,12 +364,12 @@ - + Enter your recovery key or passphrase called %1 to decrypt your secrets: - + Decryption failed @@ -581,17 +581,26 @@ - Device verification timed out. - + Other party canceled the verification. - + + Verification messages received out of order! + + + + + Unknown verification error. + + + + Close @@ -612,7 +621,7 @@ - + Add images @@ -622,7 +631,7 @@ - + State key @@ -638,13 +647,13 @@ - + Use as Emoji - - + + Use as Sticker @@ -697,7 +706,7 @@ - + Private pack @@ -712,7 +721,7 @@ - + Enable globally @@ -735,7 +744,7 @@ InputBar - + Select a file @@ -745,7 +754,7 @@ - + Failed to upload media. Please try again. @@ -753,7 +762,7 @@ InviteDialog - + Invite users to %1 @@ -784,6 +793,32 @@ + + JoinRoomDialog + + + Join room + + + + + Room ID or alias + + + + + LeaveRoomDialog + + + Leave room + + + + + Are you sure you want to leave? + + + LoginPage @@ -846,25 +881,25 @@ Example: https://server.my:8787 - + - + You have entered an invalid Matrix ID e.g @joe:matrix.org - + Autodiscovery failed. Received malformed response. - + Autodiscovery failed. Unknown error when requesting .well-known. - + The required endpoints were not found. Possibly not a Matrix server. @@ -874,26 +909,44 @@ Example: https://server.my:8787 - + An unknown error occured. Make sure the homeserver domain is valid. - + SSO LOGIN - + Empty password - + SSO login failed + + LogoutDialog + + + Log out + + + + + A call is in progress. Log out? + + + + + Are you sure you want to log out? + + + MessageDelegate @@ -1019,7 +1072,7 @@ Example: https://server.my:8787 MessageView - + Edit @@ -1039,7 +1092,7 @@ Example: https://server.my:8787 - + &Copy @@ -1129,7 +1182,12 @@ Example: https://server.my:8787 - + + To allow other users to see, which of your devices actually belong to you, you can verify them. This also allows key backup to work automatically. Verify an unverified device now? (Please make sure you have one of those devices available.) + + + + To allow other users to see, which of your devices actually belong to you, you can verify them. This also allows key backup to work automatically. Verify %1 now? @@ -1178,7 +1236,7 @@ Example: https://server.my:8787 NotificationWarning - You will be pinging the whole room + You are about to notify the whole room @@ -1186,29 +1244,17 @@ Example: https://server.my:8787 NotificationsManager - - + + %1 sent an encrypted message - - * %1 %2 - Format an emote message in a notification, %1 is the sender, %2 the message - - - - + %1 replied: %2 Format a reply in a notification. %1 is the sender, %2 the message - - - %1: %2 - Format a normal message in a notification. %1 is the sender, %2 the message - - @@ -1239,7 +1285,7 @@ Example: https://server.my:8787 - + Voice @@ -1270,7 +1316,7 @@ Example: https://server.my:8787 QCoreApplication - + Create a unique profile, which allows you to log into several accounts at the same time and start multiple instances of nheko. @@ -1288,7 +1334,7 @@ Example: https://server.my:8787 ReadReceipts - + Read receipts @@ -1296,7 +1342,7 @@ Example: https://server.my:8787 ReadReceiptsModel - + Yesterday, %1 @@ -1304,18 +1350,18 @@ Example: https://server.my:8787 RegisterPage - + Username - + The username must not be empty, and must contain only the characters a-z, 0-9, ., _, =, -, and /. - + Password @@ -1345,37 +1391,22 @@ Example: https://server.my:8787 - - No supported registration flows! - - - - - Registration token - - - - - Please enter a valid registration token. - - - - + Autodiscovery failed. Received malformed response. - + Autodiscovery failed. Unknown error when requesting .well-known. - + The required endpoints were not found. Possibly not a Matrix server. - + Received malformed response. Make sure the homeserver domain is valid. @@ -1385,7 +1416,7 @@ Example: https://server.my:8787 - + Password is not long enough (min 8 chars) @@ -1416,20 +1447,25 @@ Example: https://server.my:8787 RoomDirectory - + Explore Public Rooms - + Search for public rooms + + + Choose custom homeserver + + RoomInfo - + no version stored @@ -1446,16 +1482,6 @@ Example: https://server.my:8787 Enter the tag you want to use: - - - Leave Room - - - - - Are you sure you want to leave this room? - - Leave room @@ -1487,7 +1513,7 @@ Example: https://server.my:8787 - + Status Message @@ -1512,7 +1538,30 @@ Example: https://server.my:8787 - + + Encryption not set up + Cross-signing setup has not run yet. + + + + + Unverified login + The user just signed in with this device and hasn't verified their master key. + + + + + Please verify your other devices + There are unverified devices signed in to this account. + + + + + Close + + + + Start a new chat @@ -1540,12 +1589,12 @@ Example: https://server.my:8787 RoomMembers - + Members of %1 - + %n people in %1 Summary above list of members @@ -1583,12 +1632,12 @@ Example: https://server.my:8787 RoomSettings - + Room Settings - + %1 member(s) @@ -1694,12 +1743,12 @@ Example: https://server.my:8787 - + Failed to enable encryption: %1 - + Select an avatar @@ -1719,8 +1768,8 @@ Example: https://server.my:8787 - - + + Failed to upload image: %s @@ -1728,21 +1777,49 @@ Example: https://server.my:8787 RoomlistModel - + Pending invite. - + Previewing this room - + No preview available + + Root + + + Please enter your login password to continue: + + + + + Please enter a valid email address to continue: + + + + + Please enter a valid phone number to continue: + + + + + Please enter the token, which has been sent to you: + + + + + Wait for the confirmation link to arrive, then continue. + + + ScreenShare @@ -1800,20 +1877,98 @@ Example: https://server.my:8787 SecretStorage - + Failed to connect to secret storage - Nheko could not connect to the secure storage to save encryption secrets to. This can have multiple reasons. Check if your D-Bus service is running and you have configured a service like KWallet, Gnome Secrets or the equivalent for your platform. If you are having trouble, feel free to open an issue here: https://github.com/Nheko-Reborn/nheko/issues + Nheko could not connect to the secure storage to save encryption secrets to. This can have multiple reasons. Check if your D-Bus service is running and you have configured a service like KWallet, Gnome Keyring, KeePassXC or the equivalent for your platform. If you are having trouble, feel free to open an issue here: https://github.com/Nheko-Reborn/nheko/issues + + + + + SelfVerificationCheck + + + This is your recovery key. You will need it to restore access to your encrypted messages and verification keys. Keep this safe. Don't share it with anyone and don't lose it! Do not pass go! Do not collect $200! + + + + + Encryption setup successfully + + + + + Failed to setup encryption: %1 + + + + + Setup Encryption + + + + + Hello and welcome to Matrix! +It seems like you are new. Before you can securely encrypt your messages, we need to setup a few small things. You can either press accept immediately or adjust a few basic options. We also try to explain a few of the basics. You can skip those parts, but they might prove to be helpful! + + + + + Activate Encryption + + + + + It seems like you have encryption already configured for this account. To be able to access your encrypted messages and make this device appear as trusted, you can either verify an existing device or (if you have one) enter your recovery passphrase. Please select one of the options below. +If you choose verify, you need to have the other device available. If you choose "enter passphrase", you will need your recovery key or passphrase. If you click cancel, you can choose to verify yourself at a later point. + + + + + verify + + + + + enter passphrase + + + + + SelfVerificationStatus + + + Failed to create keys for cross-signing! + + + + + Failed to create keys for online key backup! + + + + + Failed to create keys secure server side secret storage! + + + + + Encryption Setup + + + + + Encryption setup failed: %1 SingleImagePackModel - + Failed to update image pack: %1 @@ -1829,7 +1984,7 @@ Example: https://server.my:8787 - + Failed to upload image: %1 @@ -1886,18 +2041,18 @@ Example: https://server.my:8787 TimelineModel - + Message redaction failed: %1 - + Failed to encrypt event, sending aborted! - + Save image @@ -1917,7 +2072,7 @@ Example: https://server.my:8787 - + %1 and %2 are typing. Multiple users are typing. First argument is a comma separated list of potentially multiple users. Second argument is the last user of that list. (If only one user is typing, %1 is empty. You should still use it in your string though to silence Qt warnings.) @@ -1927,7 +2082,7 @@ Example: https://server.my:8787 - + %1 opened the room to the public. @@ -1967,12 +2122,12 @@ Example: https://server.my:8787 - + %1 set the room history visible to members since they were invited. - + %1 set the room history visible to members since they joined the room. @@ -1982,12 +2137,12 @@ Example: https://server.my:8787 - + %1 was invited. - + %1 changed their avatar. @@ -2002,12 +2157,12 @@ Example: https://server.my:8787 - + %1 joined via authorisation from %2's server. - + %1 rejected their invite. @@ -2037,27 +2192,27 @@ Example: https://server.my:8787 - + Reason: %1 - + %1 redacted their knock. - + You joined this room. - + %1 has changed their avatar and changed their display name to %2. - + %1 has changed their display name to %2. @@ -2094,7 +2249,12 @@ Example: https://server.my:8787 - + + No preview available + + + + %1 member(s) @@ -2119,28 +2279,20 @@ Example: https://server.my:8787 - - TimelineViewManager - - - No encrypted private chat found with this user. Create an encrypted private chat with this user and try again. - - - TopBar - + Back to room list - + No room selected - + This room is not encrypted! @@ -2151,7 +2303,7 @@ Example: https://server.my:8787 - This rooms contain verified devices and devices which have never changed their master key. + This room contains verified devices and devices which have never changed their master key. @@ -2198,10 +2350,35 @@ Example: https://server.my:8787 + + UIA + + + No available registration flows! + + + + + + + Registration aborted + + + + + Please enter a valid registration token. + + + + + Invalid token + + + UserProfile - + Global User Profile @@ -2211,7 +2388,7 @@ Example: https://server.my:8787 - + Change avatar globally. @@ -2221,7 +2398,7 @@ Example: https://server.my:8787 - + Change display name globally. @@ -2231,7 +2408,7 @@ Example: https://server.my:8787 - + Room: %1 @@ -2241,18 +2418,18 @@ Example: https://server.my:8787 - + Open the global profile for this user. - - + + Verify - + Start a private chat. @@ -2267,12 +2444,42 @@ Example: https://server.my:8787 - + + Refresh device list. + + + + + Sign out this device. + + + + + Change device name. + + + + + Last seen %1 from %2 + + + + Unverify - + + Sign out device %1 + + + + + You signed out this device. + + + + Select an avatar @@ -2295,8 +2502,8 @@ Example: https://server.my:8787 UserSettings - - + + Default @@ -2304,7 +2511,7 @@ Example: https://server.my:8787 UserSettingsPage - + Minimize to tray @@ -2314,22 +2521,22 @@ Example: https://server.my:8787 - + Group's sidebar - + Circular Avatars - + profile: %1 - + Default @@ -2354,7 +2561,7 @@ Example: https://server.my:8787 - + Keep the application running in the background after closing the client window. @@ -2369,6 +2576,16 @@ Example: https://server.my:8787 OFF - square, ON - Circle. + + + Use identicons + + + + + Display an identicon instead of a letter when a user has not set an avatar. + + Show a column containing groups and tags next to the room list. @@ -2397,7 +2614,7 @@ be blurred. - + Privacy screen timeout (in seconds [0 - 3600]) @@ -2452,7 +2669,7 @@ If this is on, rooms which have active notifications (the small circle with a nu - + Read receipts @@ -2463,7 +2680,7 @@ Status is displayed next to timestamps. - + Send messages as Markdown @@ -2550,7 +2767,7 @@ This usually causes the application icon in the task bar to animate in some fash - + Enable online key backup @@ -2560,7 +2777,7 @@ This usually causes the application icon in the task bar to animate in some fash - + CACHED @@ -2570,7 +2787,7 @@ This usually causes the application icon in the task bar to animate in some fash - + Scale factor @@ -2645,7 +2862,7 @@ This usually causes the application icon in the task bar to animate in some fash - + Session Keys @@ -2665,17 +2882,17 @@ This usually causes the application icon in the task bar to animate in some fash - + GENERAL - + INTERFACE - + Plays media like GIFs or WEBPs only when explicitly hovering over them. @@ -2715,7 +2932,7 @@ This usually causes the application icon in the task bar to animate in some fash - + Self signing key @@ -2745,14 +2962,14 @@ This usually causes the application icon in the task bar to animate in some fash - + Open Sessions File - + @@ -2760,19 +2977,19 @@ This usually causes the application icon in the task bar to animate in some fash - - + + File Password - + Enter the passphrase to decrypt the file: - + The password cannot be empty @@ -2787,6 +3004,14 @@ This usually causes the application icon in the task bar to animate in some fash + + VerificationManager + + + No encrypted private chat found with this user. Create an encrypted private chat with this user and try again. + + + Waiting @@ -2841,7 +3066,7 @@ This usually causes the application icon in the task bar to animate in some fash descriptiveTime - + Yesterday @@ -2912,37 +3137,6 @@ This usually causes the application icon in the task bar to animate in some fash - - dialogs::JoinRoom - - - Join - - - - - Cancel - - - - - Room ID or alias - - - - - dialogs::LeaveRoom - - - Cancel - - - - - Are you sure you want to leave? - - - dialogs::Logout @@ -3007,47 +3201,47 @@ Media size: %2 - + You sent an image - + %1 sent an image - + You sent a file - + %1 sent a file - + You sent a video - + %1 sent a video - + You sent a sticker - + %1 sent a sticker - + You sent a notification @@ -3062,7 +3256,7 @@ Media size: %2 - + %1: %2 @@ -3082,27 +3276,27 @@ Media size: %2 - + %1 placed a call - + You answered a call - + %1 answered a call - + You ended a call - + %1 ended a call @@ -3110,7 +3304,7 @@ Media size: %2 utils - + Unknown Message Type diff --git a/resources/langs/nheko_de.ts b/resources/langs/nheko_de.ts index bf9068d2..538f67e2 100644 --- a/resources/langs/nheko_de.ts +++ b/resources/langs/nheko_de.ts @@ -4,7 +4,7 @@ ActiveCallBar - + Calling... Wählt... @@ -56,7 +56,7 @@ CallInvite - + Video Call Videoanruf @@ -74,7 +74,7 @@ CallInviteBar - + Video Call Videoanruf @@ -117,7 +117,7 @@ CallManager - + Entire screen Ganzer Bildschirm @@ -125,23 +125,23 @@ ChatPage - + Failed to invite user: %1 Nutzer konnte nicht eingeladen werden: %1 - + Invited user: %1 Eingeladener Benutzer: %1 - + Migrating the cache to the current version failed. This can have different reasons. Please open an issue and try to use an older version in the mean time. Alternatively you can try deleting the cache manually. Das Migrieren des Caches auf die aktuelle Version ist fehlgeschlagen. Das kann verschiedene Gründe als Ursache haben. Bitte melde den Fehler und verwende in der Zwischenzeit eine ältere Version. Alternativ kannst du den Cache manuell löschen. - + Confirm join Beitritt bestätigen @@ -151,23 +151,23 @@ Möchtest du wirklich %1 beitreten? - + Room %1 created. Raum %1 erzeugt. - - + + Confirm invite Einladung bestätigen - + Do you really want to invite %1 (%2)? Nutzer %1 (%2) wirklich einladen? - + Failed to invite %1 to %2: %3 Einladung von %1 in Raum %2 fehlgeschlagen: %3 @@ -182,7 +182,7 @@ Nutzer %1 (%2) wirklich kicken? - + Kicked user: %1 Gekickter Benutzer: %1 @@ -197,7 +197,7 @@ Nutzer %1 (%2) wirklich bannen? - + Failed to ban %1 in %2: %3 %1 konnte nicht aus %2 verbannt werden: %3 @@ -217,7 +217,7 @@ Bann des Nutzers %1 (%2) wirklich aufheben? - + Failed to unban %1 in %2: %3 Verbannung von %1 aus %2 konnte nicht aufgehoben werden: %3 @@ -227,12 +227,12 @@ Verbannung aufgehoben: %1 - + Do you really want to start a private chat with %1? Möchtest du wirklich eine private Konversation mit %1 beginnen? - + Cache migration failed! Migration des Caches fehlgeschlagen! @@ -259,23 +259,23 @@ Gespeicherte Nachrichten konnten nicht wiederhergestellt werden. Bitte melde Dich erneut an. - + Failed to setup encryption keys. Server response: %1 %2. Please try again later. Fehler beim Setup der Verschlüsselungsschlüssel. Servermeldung: %1 %2. Bitte versuche es später erneut. - - + + Please try to login again: %1 Bitte melde dich erneut an: %1 - + Failed to join room: %1 Konnte Raum nicht betreten: %1 - + You joined the room Du hast den Raum betreten @@ -285,17 +285,17 @@ Einladung konnte nicht zurückgezogen werden: %1 - + Room creation failed: %1 Raum konnte nicht erstellt werden: %1 - + Failed to leave room: %1 Konnte den Raum nicht verlassen: %1 - + Failed to kick %1 from %2: %3 Kontte %1 nicht aus %2 entfernen: %3 @@ -354,7 +354,7 @@ CrossSigningSecrets - + Decrypt secrets Geheimnisse entschlüsseln @@ -364,12 +364,12 @@ Gib deinen Wiederherstellungsschlüssel oder dein Wiederherstellungspasswort ein um deine Geheimnisse zu entschlüsseln: - + Enter your recovery key or passphrase called %1 to decrypt your secrets: Gib deinen Wiederherstellungsschlüssel oder dein Wiederherstellungspasswort mit dem Namen %1 ein um deine Geheimnisse zu entschlüsseln: - + Decryption failed Entschlüsseln fehlgeschlagen @@ -581,17 +581,26 @@ - Device verification timed out. Verifizierung abgelaufen, die andere Seite antwortet nicht. - + Other party canceled the verification. Die andere Seite hat die Verifizierung abgebrochen. - + + Verification messages received out of order! + + + + + Unknown verification error. + + + + Close Schließen @@ -612,7 +621,7 @@ Bilderpackung bearbeiten - + Add images Bilder hinzufügen @@ -622,7 +631,7 @@ Sticker (*.png *.webp *.gif *.jpg *.jpeg) - + State key Eindeutiger Name @@ -638,13 +647,13 @@ - + Use as Emoji Als Emoji verwenden - - + + Use as Sticker Als Sticker verwenden @@ -697,7 +706,7 @@ Neue Packung - + Private pack Private Packung @@ -712,7 +721,7 @@ Global aktivierte Packung - + Enable globally Global aktivieren @@ -735,7 +744,7 @@ InputBar - + Select a file Datei auswählen @@ -745,7 +754,7 @@ Alle Dateien (*) - + Failed to upload media. Please try again. Medienupload fehlgeschlagen. Bitte versuche es erneut. @@ -753,7 +762,7 @@ InviteDialog - + Invite users to %1 Lade Benutzer in %1 ein @@ -784,6 +793,32 @@ Abbrechen + + JoinRoomDialog + + + Join room + + + + + Room ID or alias + Raum-ID oder -Alias + + + + LeaveRoomDialog + + + Leave room + Raum verlassen + + + + Are you sure you want to leave? + Willst du wirklich den Raum verlassen? + + LoginPage @@ -850,25 +885,25 @@ Beispiel: https://mein.server:8787 ANMELDEN - + - + You have entered an invalid Matrix ID e.g @joe:matrix.org Du hast eine invalide Matrix ID eingegeben. Normalerwise sehen die so aus: @joe:matrix.org - + Autodiscovery failed. Received malformed response. Automatische Erkennung fehlgeschlagen. Antwort war fehlerhaft. - + Autodiscovery failed. Unknown error when requesting .well-known. Automatische Erkennung fehlgeschlagen. Unbekannter Fehler bei Anfrage .well-known. - + The required endpoints were not found. Possibly not a Matrix server. Benötigte Ansprechpunkte nicht auffindbar. Möglicherweise kein Matrixserver. @@ -878,26 +913,44 @@ Beispiel: https://mein.server:8787 Erhaltene Antwort war fehlerhaft. Bitte Homeserverdomain prüfen. - + An unknown error occured. Make sure the homeserver domain is valid. Ein unbekannter Fehler ist aufgetreten. Bitte Homeserverdomain prüfen. - + SSO LOGIN SSO ANMELDUNG - + Empty password Leeres Passwort - + SSO login failed SSO Anmeldung fehlgeschlagen + + LogoutDialog + + + Log out + + + + + A call is in progress. Log out? + + + + + Are you sure you want to log out? + + + MessageDelegate @@ -1023,7 +1076,7 @@ Beispiel: https://mein.server:8787 MessageView - + Edit Bearbeiten @@ -1043,7 +1096,7 @@ Beispiel: https://mein.server:8787 Optionen - + &Copy &Kopieren @@ -1133,7 +1186,12 @@ Beispiel: https://mein.server:8787 Verifizierungsanfrage erhalten - + + To allow other users to see, which of your devices actually belong to you, you can verify them. This also allows key backup to work automatically. Verify an unverified device now? (Please make sure you have one of those devices available.) + + + + To allow other users to see, which of your devices actually belong to you, you can verify them. This also allows key backup to work automatically. Verify %1 now? Damit andere Nutzer sehen, welche Geräte tatsächlich dir gehören, kannst du sie verifizieren. Das erlaubt auch Schlüsselbackup zu nutzen ohne ein Passwort einzugeben. %1 jetzt verifizieren? @@ -1182,37 +1240,25 @@ Beispiel: https://mein.server:8787 NotificationWarning - You will be pinging the whole room - Du wirst den ganzen Raum benachrichtigen + You are about to notify the whole room + NotificationsManager - - + + %1 sent an encrypted message %1 hat eine verschlüsselte Nachricht gesendet - - * %1 %2 - Format an emote message in a notification, %1 is the sender, %2 the message - * %1 %2 - - - + %1 replied: %2 Format a reply in a notification. %1 is the sender, %2 the message %1 hat geantwortet: %2 - - - %1: %2 - Format a normal message in a notification. %1 is the sender, %2 the message - %1: %2 - @@ -1243,7 +1289,7 @@ Beispiel: https://mein.server:8787 Kein Mikrofon gefunden. - + Voice Sprache @@ -1274,7 +1320,7 @@ Beispiel: https://mein.server:8787 QCoreApplication - + Create a unique profile, which allows you to log into several accounts at the same time and start multiple instances of nheko. Benutze ein separates profil, wodurch mehrere Accounts und Nhekoinstanzen zur gleichen Zeit verwendet werden können. @@ -1292,7 +1338,7 @@ Beispiel: https://mein.server:8787 ReadReceipts - + Read receipts Lesebestätigungen @@ -1300,7 +1346,7 @@ Beispiel: https://mein.server:8787 ReadReceiptsModel - + Yesterday, %1 Gestern, %1 @@ -1308,18 +1354,18 @@ Beispiel: https://mein.server:8787 RegisterPage - + Username Benutzername - + The username must not be empty, and must contain only the characters a-z, 0-9, ., _, =, -, and /. Der Benutzername sollte nicht leer sein und nur aus a-z, 0-9, ., _, =, - und / bestehen. - + Password Passwort @@ -1349,37 +1395,22 @@ Beispiel: https://mein.server:8787 REGISTRIEREN - - No supported registration flows! - Keine unterstützten Registrierungsmethoden! - - - - Registration token - Registrierungstoken - - - - Please enter a valid registration token. - Bitte gebe ein gültiges Registrierungstoken ein. - - - + Autodiscovery failed. Received malformed response. Automatische Erkennung fehlgeschlagen. Antwort war fehlerhaft. - + Autodiscovery failed. Unknown error when requesting .well-known. Automatische Erkennung fehlgeschlagen. Unbekannter Fehler bei Anfrage .well-known. - + The required endpoints were not found. Possibly not a Matrix server. Benötigte Ansprechpunkte nicht auffindbar. Möglicherweise kein Matrixserver. - + Received malformed response. Make sure the homeserver domain is valid. Erhaltene Antwort war fehlerhaft. Bitte Homeserverdomain prüfen. @@ -1389,7 +1420,7 @@ Beispiel: https://mein.server:8787 Ein unbekannter Fehler ist aufgetreten. Bitte Homeserverdomain prüfen. - + Password is not long enough (min 8 chars) Passwort nicht lang genug (mind. 8 Zeichen) @@ -1420,20 +1451,25 @@ Beispiel: https://mein.server:8787 RoomDirectory - + Explore Public Rooms Öffentliche Räume erkunden - + Search for public rooms Suche nach öffentlichen Räumen + + + Choose custom homeserver + + RoomInfo - + no version stored keine Version gespeichert @@ -1450,16 +1486,6 @@ Beispiel: https://mein.server:8787 Enter the tag you want to use: Gib den Tag, den du verwenden willst, ein: - - - Leave Room - Raum verlassen - - - - Are you sure you want to leave this room? - Willst du wirklich diesen Raum verlassen? - Leave room @@ -1491,7 +1517,7 @@ Beispiel: https://mein.server:8787 Neuen Tag erstellen... - + Status Message Statusnachricht @@ -1516,7 +1542,30 @@ Beispiel: https://mein.server:8787 Abmelden - + + Encryption not set up + Cross-signing setup has not run yet. + + + + + Unverified login + The user just signed in with this device and hasn't verified their master key. + + + + + Please verify your other devices + There are unverified devices signed in to this account. + + + + + Close + Schließen + + + Start a new chat Neues Gespräch beginnen @@ -1544,12 +1593,12 @@ Beispiel: https://mein.server:8787 RoomMembers - + Members of %1 Teilnehmer in %1 - + %n people in %1 Summary above list of members @@ -1586,12 +1635,12 @@ Beispiel: https://mein.server:8787 RoomSettings - + Room Settings Raumeinstellungen - + %1 member(s) %1 Teilnehmer @@ -1697,12 +1746,12 @@ Beispiel: https://mein.server:8787 Raumversion - + Failed to enable encryption: %1 Aktivierung der Verschlüsselung fehlgeschlagen: %1 - + Select an avatar Wähle einen Avatar @@ -1722,8 +1771,8 @@ Beispiel: https://mein.server:8787 Fehler beim Lesen der Datei: %1 - - + + Failed to upload image: %s Hochladen des Bildes fehlgeschlagen: %s @@ -1731,21 +1780,49 @@ Beispiel: https://mein.server:8787 RoomlistModel - + Pending invite. Offene Einladung. - + Previewing this room Vorschau dieses Raums - + No preview available Keine Vorschau verfügbar + + Root + + + Please enter your login password to continue: + + + + + Please enter a valid email address to continue: + + + + + Please enter a valid phone number to continue: + + + + + Please enter the token, which has been sent to you: + + + + + Wait for the confirmation link to arrive, then continue. + + + ScreenShare @@ -1803,20 +1880,98 @@ Beispiel: https://mein.server:8787 SecretStorage - + Failed to connect to secret storage Verbindung zum kryptografischen Speicher fehlgeschlagen - Nheko could not connect to the secure storage to save encryption secrets to. This can have multiple reasons. Check if your D-Bus service is running and you have configured a service like KWallet, Gnome Secrets or the equivalent for your platform. If you are having trouble, feel free to open an issue here: https://github.com/Nheko-Reborn/nheko/issues - Nheko konnte sich nicht mit dem kryptografischen Speicher verbinden um die Geheimnisse zu speichern. Dies kann verschiedene Gründe haben. Stelle sicher, dass der D-Bus-Dienst läuft und du einen Dienst wie KWallet, Gnome Secrets oder dein Platformäquivalent konfiguriert hast. Wenn du Probleme hast, kannst du hier um Hilfe fragen: https://github.com/Nheko-Reborn/nheko/issues + Nheko could not connect to the secure storage to save encryption secrets to. This can have multiple reasons. Check if your D-Bus service is running and you have configured a service like KWallet, Gnome Keyring, KeePassXC or the equivalent for your platform. If you are having trouble, feel free to open an issue here: https://github.com/Nheko-Reborn/nheko/issues + + + + + SelfVerificationCheck + + + This is your recovery key. You will need it to restore access to your encrypted messages and verification keys. Keep this safe. Don't share it with anyone and don't lose it! Do not pass go! Do not collect $200! + + + + + Encryption setup successfully + + + + + Failed to setup encryption: %1 + + + + + Setup Encryption + + + + + Hello and welcome to Matrix! +It seems like you are new. Before you can securely encrypt your messages, we need to setup a few small things. You can either press accept immediately or adjust a few basic options. We also try to explain a few of the basics. You can skip those parts, but they might prove to be helpful! + + + + + Activate Encryption + + + + + It seems like you have encryption already configured for this account. To be able to access your encrypted messages and make this device appear as trusted, you can either verify an existing device or (if you have one) enter your recovery passphrase. Please select one of the options below. +If you choose verify, you need to have the other device available. If you choose "enter passphrase", you will need your recovery key or passphrase. If you click cancel, you can choose to verify yourself at a later point. + + + + + verify + + + + + enter passphrase + + + + + SelfVerificationStatus + + + Failed to create keys for cross-signing! + + + + + Failed to create keys for online key backup! + + + + + Failed to create keys secure server side secret storage! + + + + + Encryption Setup + + + + + Encryption setup failed: %1 + SingleImagePackModel - + Failed to update image pack: %1 Konnte die Bilderpackung nicht aktualisieren: %1 @@ -1832,7 +1987,7 @@ Beispiel: https://mein.server:8787 Konnte Bild nicht öffnen: %1 - + Failed to upload image: %1 Konnte Bild nicht hochladen: %1 @@ -1889,18 +2044,18 @@ Beispiel: https://mein.server:8787 TimelineModel - + Message redaction failed: %1 Nachricht zurückziehen fehlgeschlagen: %1 - + Failed to encrypt event, sending aborted! Event konnte nicht verschlüsselt werden, senden wurde abgebrochen! - + Save image Bild speichern @@ -1920,7 +2075,7 @@ Beispiel: https://mein.server:8787 Datei speichern - + %1 and %2 are typing. Multiple users are typing. First argument is a comma separated list of potentially multiple users. Second argument is the last user of that list. (If only one user is typing, %1 is empty. You should still use it in your string though to silence Qt warnings.) @@ -1929,7 +2084,7 @@ Beispiel: https://mein.server:8787 - + %1 opened the room to the public. %1 hat diesen Raum öffentlich gemacht. @@ -1969,12 +2124,12 @@ Beispiel: https://mein.server:8787 %1 hat eingestellt, dass nur Teilnehmer Nachrichten in diesem Raum lesen können (ab diesem Punkt). - + %1 set the room history visible to members since they were invited. %1 hat eingestellt, dass Teilnehmer die Historie dieses Raums lesen können ab dem Zeitpunkt, zu dem sie eingeladen wurden. - + %1 set the room history visible to members since they joined the room. %1 hat eingestellt, dass Teilnehmer die Historie dieses Raums lesen können ab dem Zeitpunkt, zu dem sie beigetreten sind. @@ -1984,12 +2139,12 @@ Beispiel: https://mein.server:8787 %1 hat die Berechtigungen dieses Raums bearbeitet. - + %1 was invited. %1 wurde eingeladen. - + %1 changed their avatar. %1 hat den Avatar geändert. @@ -2004,12 +2159,12 @@ Beispiel: https://mein.server:8787 %1 hat den Raum betreten. - + %1 joined via authorisation from %2's server. %1 hat den Raum durch Authorisierung von %2s Server betreten. - + %1 rejected their invite. %1 hat die Einladung abgewiesen. @@ -2039,27 +2194,27 @@ Beispiel: https://mein.server:8787 %1 wurde gebannt. - + Reason: %1 Grund: %1 - + %1 redacted their knock. %1 hat das Anklopfen zurückgezogen. - + You joined this room. Du bist dem Raum beigetreten. - + %1 has changed their avatar and changed their display name to %2. %1 hat den eigenen Avatar und Namen geändert zu %2. - + %1 has changed their display name to %2. %1 hat den eigenen Namen geändert zu %2. @@ -2096,7 +2251,12 @@ Beispiel: https://mein.server:8787 Kein Raum geöffnet - + + No preview available + Keine Vorschau verfügbar + + + %1 member(s) %1 Teilnehmer @@ -2121,28 +2281,20 @@ Beispiel: https://mein.server:8787 Zurück zur Raumliste - - TimelineViewManager - - - No encrypted private chat found with this user. Create an encrypted private chat with this user and try again. - Keinen verschlüsselten Chat mit diesem User gefunden. Erstelle einen verschlüsselten 1:1 Chat mit diesem Nutzer und versuche es erneut. - - TopBar - + Back to room list Zurück zur Raumliste - + No room selected Kein Raum ausgewählt - + This room is not encrypted! Dieser Raum ist nicht verschlüsselt! @@ -2153,8 +2305,8 @@ Beispiel: https://mein.server:8787 - This rooms contain verified devices and devices which have never changed their master key. - Dieser Raum beinhaltet verifizierte Geräte und Geräte, die nie ihre Identität gewechselt haben. + This room contains verified devices and devices which have never changed their master key. + @@ -2200,10 +2352,35 @@ Beispiel: https://mein.server:8787 Schließen + + UIA + + + No available registration flows! + + + + + + + Registration aborted + + + + + Please enter a valid registration token. + Bitte gebe ein gültiges Registrierungstoken ein. + + + + Invalid token + + + UserProfile - + Global User Profile Globales Nutzerprofil @@ -2213,7 +2390,7 @@ Beispiel: https://mein.server:8787 Raumspezifisches Nutzerprofil - + Change avatar globally. Ändere das Profilbild in allen Räumen. @@ -2223,7 +2400,7 @@ Beispiel: https://mein.server:8787 Ändere das Profilbild nur in diesem Raum. - + Change display name globally. Ändere den Anzeigenamen in allen Räumen. @@ -2233,7 +2410,7 @@ Beispiel: https://mein.server:8787 Ändere den Anzeigenamen nur in diesem Raum. - + Room: %1 Raum: %1 @@ -2243,18 +2420,18 @@ Beispiel: https://mein.server:8787 Dies ist das raumspezifische Nutzerprofil. Der Anzeigename und das Profilbild kann sich von dem globalen Profil unterscheiden. - + Open the global profile for this user. Öffne das globale Profil des Nutzers. - - + + Verify Verifizieren - + Start a private chat. Starte eine private Unterhaltung. @@ -2269,12 +2446,42 @@ Beispiel: https://mein.server:8787 Benutzer aus dem Raum verbannen. - + + Refresh device list. + + + + + Sign out this device. + + + + + Change device name. + + + + + Last seen %1 from %2 + + + + Unverify Verifizierung zurückziehen - + + Sign out device %1 + + + + + You signed out this device. + + + + Select an avatar Avatar wählen @@ -2297,8 +2504,8 @@ Beispiel: https://mein.server:8787 UserSettings - - + + Default Standard @@ -2306,7 +2513,7 @@ Beispiel: https://mein.server:8787 UserSettingsPage - + Minimize to tray Ins Benachrichtigungsfeld minimieren @@ -2316,22 +2523,22 @@ Beispiel: https://mein.server:8787 Im Benachrichtigungsfeld starten - + Group's sidebar Gruppen-Seitenleiste - + Circular Avatars Runde Profilbilder - + profile: %1 Profil: %1 - + Default Standard @@ -2356,7 +2563,7 @@ Beispiel: https://mein.server:8787 DOWNLOADEN - + Keep the application running in the background after closing the client window. Applikation im Hintergrund weiterlaufen lassen. @@ -2372,6 +2579,16 @@ OFF - square, ON - Circle. Ändert das Aussehen von Benutzeravataren. AUS - Quadratisch, AN - Kreis. + + + Use identicons + + + + + Display an identicon instead of a letter when a user has not set an avatar. + + Show a column containing groups and tags next to the room list. @@ -2401,7 +2618,7 @@ be blurred. Die Zeitliste wird unscharf, wenn das Fenster den Fokus verliert. - + Privacy screen timeout (in seconds [0 - 3600]) Sichtschutz-Zeitbegrenzung (in Sekunden [0 - 3600]) @@ -2461,7 +2678,7 @@ Wenn das aus ist, werden die Räume in der Raumliste rein nach dem Sendezeitpunk Wenn das eingeschaltet ist, werden Nachrichten mit aktiven Erwähnung zuerst sortiert (der rote Kreis). Danach kommen andere Benachrichtigungen (weißer Kreis) und zuletzt stummgeschaltete Räume sortiert nach deren Zeitstempel. - + Read receipts Lesebestätigungen @@ -2473,7 +2690,7 @@ Status is displayed next to timestamps. Der Status wird neben der Nachricht angezeigt. - + Send messages as Markdown Sende Nachrichten als Markdown formatiert @@ -2562,7 +2779,7 @@ Normalerweise animiert das den Taskbaricon oder färbt das Fenster orange ein.Speichere eine Kopie der Nachrichtenschlüssel verschlüsselt auf dem Server. - + Enable online key backup Onlinenachrichtenschlüsselspeicher aktivieren @@ -2572,7 +2789,7 @@ Normalerweise animiert das den Taskbaricon oder färbt das Fenster orange ein.Die Nhekoentwickler empfehlen aktuell nicht die Onlinesicherung zu ativieren bevor die symmetrische Methode zu verfügung steht. Trotzdem aktivieren? - + CACHED IM CACHE @@ -2582,7 +2799,7 @@ Normalerweise animiert das den Taskbaricon oder färbt das Fenster orange ein.NICHT IM CACHE - + Scale factor Skalierungsfaktor @@ -2657,7 +2874,7 @@ Normalerweise animiert das den Taskbaricon oder färbt das Fenster orange ein.Gerätefingerabdruck - + Session Keys Sitzungsschlüssel @@ -2677,17 +2894,17 @@ Normalerweise animiert das den Taskbaricon oder färbt das Fenster orange ein.VERSCHLÜSSELUNG - + GENERAL ALLGEMEINES - + INTERFACE OBERFLÄCHE - + Plays media like GIFs or WEBPs only when explicitly hovering over them. Spiele Medien wie GIF oder WEBP nur ab, wenn du die Maus darüber bewegst. @@ -2727,7 +2944,7 @@ Normalerweise animiert das den Taskbaricon oder färbt das Fenster orange ein.Der Schlüssel um andere Nutzer zu verifizieren. Wenn der lokal zwischengespeichert ist, dann werden durch eine Nutzerverifizierung alle Geräte verifiziert. - + Self signing key Selbstverifizierungsschlüssel @@ -2757,14 +2974,14 @@ Normalerweise animiert das den Taskbaricon oder färbt das Fenster orange ein.Alle Dateien (*) - + Open Sessions File Öffne Sessions Datei - + @@ -2772,19 +2989,19 @@ Normalerweise animiert das den Taskbaricon oder färbt das Fenster orange ein.Feher - - + + File Password Password für Datei - + Enter the passphrase to decrypt the file: Bitte gib das Passwort zum Enschlüsseln der Datei ein: - + The password cannot be empty Das Passwort darf nicht leer sein @@ -2799,6 +3016,14 @@ Normalerweise animiert das den Taskbaricon oder färbt das Fenster orange ein.Datei zum Speichern der zu exportierenden Sitzungsschlüssel + + VerificationManager + + + No encrypted private chat found with this user. Create an encrypted private chat with this user and try again. + Keinen verschlüsselten Chat mit diesem User gefunden. Erstelle einen verschlüsselten 1:1 Chat mit diesem Nutzer und versuche es erneut. + + Waiting @@ -2853,7 +3078,7 @@ Normalerweise animiert das den Taskbaricon oder färbt das Fenster orange ein. descriptiveTime - + Yesterday Gestern @@ -2924,37 +3149,6 @@ Normalerweise animiert das den Taskbaricon oder färbt das Fenster orange ein.Öffne das Fallback, folge den Anweisungen und bestätige nach Abschluss via "Bestätigen". - - dialogs::JoinRoom - - - Join - Betreten - - - - Cancel - Abbrechen - - - - Room ID or alias - Raum-ID oder -Alias - - - - dialogs::LeaveRoom - - - Cancel - Abbrechen - - - - Are you sure you want to leave? - Willst du wirklich den Raum verlassen? - - dialogs::Logout @@ -3021,47 +3215,47 @@ Medien-Größe: %2 %1 hat eine Audiodatei gesendet - + You sent an image Du hast ein Bild gesendet - + %1 sent an image %1 hat ein Bild gesendet - + You sent a file Du hast eine Datei gesendet - + %1 sent a file %1 hat eine Datei gesendet - + You sent a video Du hast ein Video gesendet - + %1 sent a video %1 hat ein Video gesendet - + You sent a sticker Du hast einen Sticker gesendet - + %1 sent a sticker %1 hat einen Sticker gesendet - + You sent a notification Du hast eine Benachrichtigung gesendet @@ -3076,7 +3270,7 @@ Medien-Größe: %2 Du: %1 - + %1: %2 %1: %2 @@ -3096,27 +3290,27 @@ Medien-Größe: %2 Du hast angerufen - + %1 placed a call %1 hat angerufen - + You answered a call Du hast einen Anruf angenommen - + %1 answered a call %1 hat einen Anruf angenommen - + You ended a call Du hast einen Anruf beendet - + %1 ended a call %1 hat einen Anruf beendet @@ -3124,7 +3318,7 @@ Medien-Größe: %2 utils - + Unknown Message Type Unbekannter Nachrichtentyp diff --git a/resources/langs/nheko_el.ts b/resources/langs/nheko_el.ts index e74a53c0..88503e86 100644 --- a/resources/langs/nheko_el.ts +++ b/resources/langs/nheko_el.ts @@ -4,7 +4,7 @@ ActiveCallBar - + Calling... @@ -56,7 +56,7 @@ CallInvite - + Video Call @@ -74,7 +74,7 @@ CallInviteBar - + Video Call @@ -117,7 +117,7 @@ CallManager - + Entire screen @@ -125,23 +125,23 @@ ChatPage - + Failed to invite user: %1 - + Invited user: %1 - + Migrating the cache to the current version failed. This can have different reasons. Please open an issue and try to use an older version in the mean time. Alternatively you can try deleting the cache manually. - + Confirm join @@ -151,23 +151,23 @@ - + Room %1 created. - - + + Confirm invite - + Do you really want to invite %1 (%2)? - + Failed to invite %1 to %2: %3 @@ -182,7 +182,7 @@ - + Kicked user: %1 @@ -197,7 +197,7 @@ - + Failed to ban %1 in %2: %3 @@ -217,7 +217,7 @@ - + Failed to unban %1 in %2: %3 @@ -227,12 +227,12 @@ - + Do you really want to start a private chat with %1? - + Cache migration failed! @@ -259,23 +259,23 @@ - + Failed to setup encryption keys. Server response: %1 %2. Please try again later. - - + + Please try to login again: %1 - + Failed to join room: %1 - + You joined the room @@ -285,17 +285,17 @@ - + Room creation failed: %1 - + Failed to leave room: %1 - + Failed to kick %1 from %2: %3 @@ -354,7 +354,7 @@ CrossSigningSecrets - + Decrypt secrets @@ -364,12 +364,12 @@ - + Enter your recovery key or passphrase called %1 to decrypt your secrets: - + Decryption failed @@ -581,17 +581,26 @@ - Device verification timed out. - + Other party canceled the verification. - + + Verification messages received out of order! + + + + + Unknown verification error. + + + + Close @@ -612,7 +621,7 @@ - + Add images @@ -622,7 +631,7 @@ - + State key @@ -638,13 +647,13 @@ - + Use as Emoji - - + + Use as Sticker @@ -697,7 +706,7 @@ - + Private pack @@ -712,7 +721,7 @@ - + Enable globally @@ -735,7 +744,7 @@ InputBar - + Select a file Διάλεξε ένα αρχείο @@ -745,7 +754,7 @@ Όλα τα αρχεία (*) - + Failed to upload media. Please try again. @@ -753,7 +762,7 @@ InviteDialog - + Invite users to %1 @@ -784,6 +793,32 @@ Άκυρο + + JoinRoomDialog + + + Join room + + + + + Room ID or alias + ID ή όνομα συνομιλίας + + + + LeaveRoomDialog + + + Leave room + Βγές + + + + Are you sure you want to leave? + Είστε σίγουροι οτι θέλετε να κλείσετε τη συνομιλία; + + LoginPage @@ -846,25 +881,25 @@ Example: https://server.my:8787 ΕΙΣΟΔΟΣ - + - + You have entered an invalid Matrix ID e.g @joe:matrix.org - + Autodiscovery failed. Received malformed response. - + Autodiscovery failed. Unknown error when requesting .well-known. - + The required endpoints were not found. Possibly not a Matrix server. @@ -874,26 +909,44 @@ Example: https://server.my:8787 - + An unknown error occured. Make sure the homeserver domain is valid. - + SSO LOGIN - + Empty password Κενός κωδικός - + SSO login failed + + LogoutDialog + + + Log out + + + + + A call is in progress. Log out? + + + + + Are you sure you want to log out? + + + MessageDelegate @@ -1019,7 +1072,7 @@ Example: https://server.my:8787 MessageView - + Edit @@ -1039,7 +1092,7 @@ Example: https://server.my:8787 - + &Copy @@ -1129,7 +1182,12 @@ Example: https://server.my:8787 - + + To allow other users to see, which of your devices actually belong to you, you can verify them. This also allows key backup to work automatically. Verify an unverified device now? (Please make sure you have one of those devices available.) + + + + To allow other users to see, which of your devices actually belong to you, you can verify them. This also allows key backup to work automatically. Verify %1 now? @@ -1178,7 +1236,7 @@ Example: https://server.my:8787 NotificationWarning - You will be pinging the whole room + You are about to notify the whole room @@ -1186,29 +1244,17 @@ Example: https://server.my:8787 NotificationsManager - - + + %1 sent an encrypted message - - * %1 %2 - Format an emote message in a notification, %1 is the sender, %2 the message - - - - + %1 replied: %2 Format a reply in a notification. %1 is the sender, %2 the message - - - %1: %2 - Format a normal message in a notification. %1 is the sender, %2 the message - - @@ -1239,7 +1285,7 @@ Example: https://server.my:8787 - + Voice @@ -1270,7 +1316,7 @@ Example: https://server.my:8787 QCoreApplication - + Create a unique profile, which allows you to log into several accounts at the same time and start multiple instances of nheko. @@ -1288,7 +1334,7 @@ Example: https://server.my:8787 ReadReceipts - + Read receipts @@ -1296,7 +1342,7 @@ Example: https://server.my:8787 ReadReceiptsModel - + Yesterday, %1 @@ -1304,18 +1350,18 @@ Example: https://server.my:8787 RegisterPage - + Username Όνομα χρήστη - + The username must not be empty, and must contain only the characters a-z, 0-9, ., _, =, -, and /. - + Password Κωδικός @@ -1345,37 +1391,22 @@ Example: https://server.my:8787 ΕΓΓΡΑΦΗ - - No supported registration flows! - - - - - Registration token - - - - - Please enter a valid registration token. - - - - + Autodiscovery failed. Received malformed response. - + Autodiscovery failed. Unknown error when requesting .well-known. - + The required endpoints were not found. Possibly not a Matrix server. - + Received malformed response. Make sure the homeserver domain is valid. @@ -1385,7 +1416,7 @@ Example: https://server.my:8787 - + Password is not long enough (min 8 chars) Ο κωδικός δεν αποτελείται από αρκετους χαρακτήρες @@ -1416,20 +1447,25 @@ Example: https://server.my:8787 RoomDirectory - + Explore Public Rooms - + Search for public rooms + + + Choose custom homeserver + + RoomInfo - + no version stored @@ -1446,16 +1482,6 @@ Example: https://server.my:8787 Enter the tag you want to use: - - - Leave Room - - - - - Are you sure you want to leave this room? - - Leave room @@ -1487,7 +1513,7 @@ Example: https://server.my:8787 - + Status Message @@ -1512,7 +1538,30 @@ Example: https://server.my:8787 - + + Encryption not set up + Cross-signing setup has not run yet. + + + + + Unverified login + The user just signed in with this device and hasn't verified their master key. + + + + + Please verify your other devices + There are unverified devices signed in to this account. + + + + + Close + + + + Start a new chat @@ -1540,12 +1589,12 @@ Example: https://server.my:8787 RoomMembers - + Members of %1 - + %n people in %1 Summary above list of members @@ -1582,12 +1631,12 @@ Example: https://server.my:8787 RoomSettings - + Room Settings - + %1 member(s) @@ -1693,12 +1742,12 @@ Example: https://server.my:8787 - + Failed to enable encryption: %1 - + Select an avatar @@ -1718,8 +1767,8 @@ Example: https://server.my:8787 - - + + Failed to upload image: %s @@ -1727,21 +1776,49 @@ Example: https://server.my:8787 RoomlistModel - + Pending invite. - + Previewing this room - + No preview available + + Root + + + Please enter your login password to continue: + + + + + Please enter a valid email address to continue: + + + + + Please enter a valid phone number to continue: + + + + + Please enter the token, which has been sent to you: + + + + + Wait for the confirmation link to arrive, then continue. + + + ScreenShare @@ -1799,20 +1876,98 @@ Example: https://server.my:8787 SecretStorage - + Failed to connect to secret storage - Nheko could not connect to the secure storage to save encryption secrets to. This can have multiple reasons. Check if your D-Bus service is running and you have configured a service like KWallet, Gnome Secrets or the equivalent for your platform. If you are having trouble, feel free to open an issue here: https://github.com/Nheko-Reborn/nheko/issues + Nheko could not connect to the secure storage to save encryption secrets to. This can have multiple reasons. Check if your D-Bus service is running and you have configured a service like KWallet, Gnome Keyring, KeePassXC or the equivalent for your platform. If you are having trouble, feel free to open an issue here: https://github.com/Nheko-Reborn/nheko/issues + + + + + SelfVerificationCheck + + + This is your recovery key. You will need it to restore access to your encrypted messages and verification keys. Keep this safe. Don't share it with anyone and don't lose it! Do not pass go! Do not collect $200! + + + + + Encryption setup successfully + + + + + Failed to setup encryption: %1 + + + + + Setup Encryption + + + + + Hello and welcome to Matrix! +It seems like you are new. Before you can securely encrypt your messages, we need to setup a few small things. You can either press accept immediately or adjust a few basic options. We also try to explain a few of the basics. You can skip those parts, but they might prove to be helpful! + + + + + Activate Encryption + + + + + It seems like you have encryption already configured for this account. To be able to access your encrypted messages and make this device appear as trusted, you can either verify an existing device or (if you have one) enter your recovery passphrase. Please select one of the options below. +If you choose verify, you need to have the other device available. If you choose "enter passphrase", you will need your recovery key or passphrase. If you click cancel, you can choose to verify yourself at a later point. + + + + + verify + + + + + enter passphrase + + + + + SelfVerificationStatus + + + Failed to create keys for cross-signing! + + + + + Failed to create keys for online key backup! + + + + + Failed to create keys secure server side secret storage! + + + + + Encryption Setup + + + + + Encryption setup failed: %1 SingleImagePackModel - + Failed to update image pack: %1 @@ -1828,7 +1983,7 @@ Example: https://server.my:8787 - + Failed to upload image: %1 @@ -1885,18 +2040,18 @@ Example: https://server.my:8787 TimelineModel - + Message redaction failed: %1 - + Failed to encrypt event, sending aborted! - + Save image Αποθήκευση Εικόνας @@ -1916,7 +2071,7 @@ Example: https://server.my:8787 - + %1 and %2 are typing. Multiple users are typing. First argument is a comma separated list of potentially multiple users. Second argument is the last user of that list. (If only one user is typing, %1 is empty. You should still use it in your string though to silence Qt warnings.) @@ -1925,7 +2080,7 @@ Example: https://server.my:8787 - + %1 opened the room to the public. @@ -1965,12 +2120,12 @@ Example: https://server.my:8787 - + %1 set the room history visible to members since they were invited. - + %1 set the room history visible to members since they joined the room. @@ -1980,12 +2135,12 @@ Example: https://server.my:8787 - + %1 was invited. - + %1 changed their avatar. @@ -2000,12 +2155,12 @@ Example: https://server.my:8787 - + %1 joined via authorisation from %2's server. - + %1 rejected their invite. @@ -2035,27 +2190,27 @@ Example: https://server.my:8787 - + Reason: %1 - + %1 redacted their knock. - + You joined this room. - + %1 has changed their avatar and changed their display name to %2. - + %1 has changed their display name to %2. @@ -2092,7 +2247,12 @@ Example: https://server.my:8787 - + + No preview available + + + + %1 member(s) @@ -2117,28 +2277,20 @@ Example: https://server.my:8787 - - TimelineViewManager - - - No encrypted private chat found with this user. Create an encrypted private chat with this user and try again. - - - TopBar - + Back to room list - + No room selected - + This room is not encrypted! @@ -2149,7 +2301,7 @@ Example: https://server.my:8787 - This rooms contain verified devices and devices which have never changed their master key. + This room contains verified devices and devices which have never changed their master key. @@ -2196,10 +2348,35 @@ Example: https://server.my:8787 Έξοδος + + UIA + + + No available registration flows! + + + + + + + Registration aborted + + + + + Please enter a valid registration token. + + + + + Invalid token + + + UserProfile - + Global User Profile @@ -2209,7 +2386,7 @@ Example: https://server.my:8787 - + Change avatar globally. @@ -2219,7 +2396,7 @@ Example: https://server.my:8787 - + Change display name globally. @@ -2229,7 +2406,7 @@ Example: https://server.my:8787 - + Room: %1 @@ -2239,18 +2416,18 @@ Example: https://server.my:8787 - + Open the global profile for this user. - - + + Verify - + Start a private chat. @@ -2265,12 +2442,42 @@ Example: https://server.my:8787 - + + Refresh device list. + + + + + Sign out this device. + + + + + Change device name. + + + + + Last seen %1 from %2 + + + + Unverify - + + Sign out device %1 + + + + + You signed out this device. + + + + Select an avatar @@ -2293,8 +2500,8 @@ Example: https://server.my:8787 UserSettings - - + + Default @@ -2302,7 +2509,7 @@ Example: https://server.my:8787 UserSettingsPage - + Minimize to tray Ελαχιστοποίηση @@ -2312,22 +2519,22 @@ Example: https://server.my:8787 - + Group's sidebar - + Circular Avatars - + profile: %1 - + Default @@ -2352,7 +2559,7 @@ Example: https://server.my:8787 - + Keep the application running in the background after closing the client window. @@ -2367,6 +2574,16 @@ Example: https://server.my:8787 OFF - square, ON - Circle. + + + Use identicons + + + + + Display an identicon instead of a letter when a user has not set an avatar. + + Show a column containing groups and tags next to the room list. @@ -2395,7 +2612,7 @@ be blurred. - + Privacy screen timeout (in seconds [0 - 3600]) @@ -2450,7 +2667,7 @@ If this is on, rooms which have active notifications (the small circle with a nu - + Read receipts @@ -2461,7 +2678,7 @@ Status is displayed next to timestamps. - + Send messages as Markdown @@ -2548,7 +2765,7 @@ This usually causes the application icon in the task bar to animate in some fash - + Enable online key backup @@ -2558,7 +2775,7 @@ This usually causes the application icon in the task bar to animate in some fash - + CACHED @@ -2568,7 +2785,7 @@ This usually causes the application icon in the task bar to animate in some fash - + Scale factor @@ -2643,7 +2860,7 @@ This usually causes the application icon in the task bar to animate in some fash - + Session Keys @@ -2663,17 +2880,17 @@ This usually causes the application icon in the task bar to animate in some fash - + GENERAL ΓΕΝΙΚΑ - + INTERFACE - + Plays media like GIFs or WEBPs only when explicitly hovering over them. @@ -2713,7 +2930,7 @@ This usually causes the application icon in the task bar to animate in some fash - + Self signing key @@ -2743,14 +2960,14 @@ This usually causes the application icon in the task bar to animate in some fash Όλα τα αρχεία (*) - + Open Sessions File - + @@ -2758,19 +2975,19 @@ This usually causes the application icon in the task bar to animate in some fash - - + + File Password - + Enter the passphrase to decrypt the file: - + The password cannot be empty @@ -2785,6 +3002,14 @@ This usually causes the application icon in the task bar to animate in some fash + + VerificationManager + + + No encrypted private chat found with this user. Create an encrypted private chat with this user and try again. + + + Waiting @@ -2839,7 +3064,7 @@ This usually causes the application icon in the task bar to animate in some fash descriptiveTime - + Yesterday @@ -2910,37 +3135,6 @@ This usually causes the application icon in the task bar to animate in some fash - - dialogs::JoinRoom - - - Join - - - - - Cancel - Άκυρο - - - - Room ID or alias - ID ή όνομα συνομιλίας - - - - dialogs::LeaveRoom - - - Cancel - Άκυρο - - - - Are you sure you want to leave? - Είστε σίγουροι οτι θέλετε να κλείσετε τη συνομιλία; - - dialogs::Logout @@ -3005,47 +3199,47 @@ Media size: %2 - + You sent an image - + %1 sent an image - + You sent a file - + %1 sent a file - + You sent a video - + %1 sent a video - + You sent a sticker - + %1 sent a sticker - + You sent a notification @@ -3060,7 +3254,7 @@ Media size: %2 - + %1: %2 @@ -3080,27 +3274,27 @@ Media size: %2 - + %1 placed a call - + You answered a call - + %1 answered a call - + You ended a call - + %1 ended a call @@ -3108,7 +3302,7 @@ Media size: %2 utils - + Unknown Message Type diff --git a/resources/langs/nheko_en.ts b/resources/langs/nheko_en.ts index 61264bb7..5768e051 100644 --- a/resources/langs/nheko_en.ts +++ b/resources/langs/nheko_en.ts @@ -4,7 +4,7 @@ ActiveCallBar - + Calling... Calling... @@ -56,7 +56,7 @@ CallInvite - + Video Call Video Call @@ -74,7 +74,7 @@ CallInviteBar - + Video Call Video Call @@ -117,7 +117,7 @@ CallManager - + Entire screen Entire screen @@ -125,23 +125,23 @@ ChatPage - + Failed to invite user: %1 Failed to invite user: %1 - + Invited user: %1 Invited user: %1 - + Migrating the cache to the current version failed. This can have different reasons. Please open an issue and try to use an older version in the mean time. Alternatively you can try deleting the cache manually. Migrating the cache to the current version failed. This can have different reasons. Please open an issue and try to use an older version in the mean time. Alternatively you can try deleting the cache manually. - + Confirm join Confirm join @@ -151,23 +151,23 @@ Do you really want to join %1? - + Room %1 created. Room %1 created. - - + + Confirm invite Confirm invite - + Do you really want to invite %1 (%2)? Do you really want to invite %1 (%2)? - + Failed to invite %1 to %2: %3 Failed to invite %1 to %2: %3 @@ -182,7 +182,7 @@ Do you really want to kick %1 (%2)? - + Kicked user: %1 Kicked user: %1 @@ -197,7 +197,7 @@ Do you really want to ban %1 (%2)? - + Failed to ban %1 in %2: %3 Failed to ban %1 in %2: %3 @@ -217,7 +217,7 @@ Do you really want to unban %1 (%2)? - + Failed to unban %1 in %2: %3 Failed to unban %1 in %2: %3 @@ -227,12 +227,12 @@ Unbanned user: %1 - + Do you really want to start a private chat with %1? Do you really want to start a private chat with %1? - + Cache migration failed! Cache migration failed! @@ -259,23 +259,23 @@ Failed to restore save data. Please login again. - + Failed to setup encryption keys. Server response: %1 %2. Please try again later. Failed to setup encryption keys. Server response: %1 %2. Please try again later. - - + + Please try to login again: %1 Please try to login again: %1 - + Failed to join room: %1 Failed to join room: %1 - + You joined the room You joined the room @@ -285,17 +285,17 @@ Failed to remove invite: %1 - + Room creation failed: %1 Room creation failed: %1 - + Failed to leave room: %1 Failed to leave room: %1 - + Failed to kick %1 from %2: %3 Failed to kick %1 from %2: %3 @@ -354,7 +354,7 @@ CrossSigningSecrets - + Decrypt secrets Decrypt secrets @@ -364,12 +364,12 @@ Enter your recovery key or passphrase to decrypt your secrets: - + Enter your recovery key or passphrase called %1 to decrypt your secrets: Enter your recovery key or passphrase called %1 to decrypt your secrets: - + Decryption failed Decryption failed @@ -581,17 +581,26 @@ - Device verification timed out. Device verification timed out. - + Other party canceled the verification. Other party canceled the verification. - + + Verification messages received out of order! + Verification messages received out of order! + + + + Unknown verification error. + Unknown verification error. + + + Close Close @@ -612,7 +621,7 @@ Editing image pack - + Add images Add images @@ -622,7 +631,7 @@ Stickers (*.png *.webp *.gif *.jpg *.jpeg) - + State key State key @@ -638,13 +647,13 @@ - + Use as Emoji Use as Emoji - - + + Use as Sticker Use as Sticker @@ -697,7 +706,7 @@ New room pack - + Private pack Private pack @@ -712,7 +721,7 @@ Globally enabled pack - + Enable globally Enable globally @@ -735,7 +744,7 @@ InputBar - + Select a file Select a file @@ -745,7 +754,7 @@ All Files (*) - + Failed to upload media. Please try again. Failed to upload media. Please try again. @@ -753,7 +762,7 @@ InviteDialog - + Invite users to %1 Invite users to %1 @@ -784,6 +793,32 @@ Cancel + + JoinRoomDialog + + + Join room + Join room + + + + Room ID or alias + Room ID or alias + + + + LeaveRoomDialog + + + Leave room + Leave room + + + + Are you sure you want to leave? + Are you sure you want to leave? + + LoginPage @@ -850,25 +885,25 @@ Example: https://server.my:8787 LOGIN - + - + You have entered an invalid Matrix ID e.g @joe:matrix.org You have entered an invalid Matrix ID e.g @joe:matrix.org - + Autodiscovery failed. Received malformed response. Autodiscovery failed. Received malformed response. - + Autodiscovery failed. Unknown error when requesting .well-known. Autodiscovery failed. Unknown error while requesting .well-known. - + The required endpoints were not found. Possibly not a Matrix server. The required endpoints were not found. Possibly not a Matrix server. @@ -878,26 +913,44 @@ Example: https://server.my:8787 Received malformed response. Make sure the homeserver domain is valid. - + An unknown error occured. Make sure the homeserver domain is valid. An unknown error occured. Make sure the homeserver domain is valid. - + SSO LOGIN SSO LOGIN - + Empty password Empty password - + SSO login failed SSO login failed + + LogoutDialog + + + Log out + Log out + + + + A call is in progress. Log out? + A call is in progress. Log out? + + + + Are you sure you want to log out? + Are you sure you want to log out? + + MessageDelegate @@ -1023,7 +1076,7 @@ Example: https://server.my:8787 MessageView - + Edit Edit @@ -1043,7 +1096,7 @@ Example: https://server.my:8787 Options - + &Copy &Copy @@ -1133,7 +1186,12 @@ Example: https://server.my:8787 Received Verification Request - + + To allow other users to see, which of your devices actually belong to you, you can verify them. This also allows key backup to work automatically. Verify an unverified device now? (Please make sure you have one of those devices available.) + To allow other users to see, which of your devices actually belong to you, you can verify them. This also allows key backup to work automatically. Verify an unverified device now? (Please make sure you have one of those devices available.) + + + To allow other users to see, which of your devices actually belong to you, you can verify them. This also allows key backup to work automatically. Verify %1 now? To allow other users to see, which of your devices actually belong to you, you can verify them. This also allows key backup to work automatically. Verify %1 now? @@ -1182,37 +1240,25 @@ Example: https://server.my:8787 NotificationWarning - You will be pinging the whole room - You will be pinging the whole room + You are about to notify the whole room + You are about to notify the whole room NotificationsManager - - + + %1 sent an encrypted message %1 sent an encrypted message - - * %1 %2 - Format an emote message in a notification, %1 is the sender, %2 the message - * %1 %2 - - - + %1 replied: %2 Format a reply in a notification. %1 is the sender, %2 the message %1 replied: %2 - - - %1: %2 - Format a normal message in a notification. %1 is the sender, %2 the message - %1: %2 - @@ -1243,7 +1289,7 @@ Example: https://server.my:8787 No microphone found. - + Voice Voice @@ -1274,7 +1320,7 @@ Example: https://server.my:8787 QCoreApplication - + Create a unique profile, which allows you to log into several accounts at the same time and start multiple instances of nheko. Create a unique profile, which allows you to log into several accounts at the same time and start multiple instances of nheko. @@ -1292,7 +1338,7 @@ Example: https://server.my:8787 ReadReceipts - + Read receipts Read receipts @@ -1300,7 +1346,7 @@ Example: https://server.my:8787 ReadReceiptsModel - + Yesterday, %1 Yesterday, %1 @@ -1308,18 +1354,18 @@ Example: https://server.my:8787 RegisterPage - + Username Username - + The username must not be empty, and must contain only the characters a-z, 0-9, ., _, =, -, and /. The username must not be empty, and must contain only the characters a-z, 0-9, ., _, =, -, and /. - + Password Password @@ -1349,37 +1395,22 @@ Example: https://server.my:8787 REGISTER - - No supported registration flows! - No supported registration flows! - - - - Registration token - Registration token - - - - Please enter a valid registration token. - Please enter a valid registration token. - - - + Autodiscovery failed. Received malformed response. Autodiscovery failed. Received malformed response. - + Autodiscovery failed. Unknown error when requesting .well-known. Autodiscovery failed. Unknown error while requesting .well-known. - + The required endpoints were not found. Possibly not a Matrix server. The required endpoints were not found. Possibly not a Matrix server. - + Received malformed response. Make sure the homeserver domain is valid. Received malformed response. Make sure the homeserver domain is valid. @@ -1389,7 +1420,7 @@ Example: https://server.my:8787 An unknown error occured. Make sure the homeserver domain is valid. - + Password is not long enough (min 8 chars) Password is not long enough (min 8 chars) @@ -1420,20 +1451,25 @@ Example: https://server.my:8787 RoomDirectory - + Explore Public Rooms Explore Public Rooms - + Search for public rooms Search for public rooms + + + Choose custom homeserver + Choose custom homeserver + RoomInfo - + no version stored no version stored @@ -1450,16 +1486,6 @@ Example: https://server.my:8787 Enter the tag you want to use: Enter the tag you want to use: - - - Leave Room - Leave room - - - - Are you sure you want to leave this room? - Are you sure you want to leave this room? - Leave room @@ -1491,7 +1517,7 @@ Example: https://server.my:8787 Create new tag... - + Status Message Status Message @@ -1516,7 +1542,30 @@ Example: https://server.my:8787 Logout - + + Encryption not set up + Cross-signing setup has not run yet. + Encryption not set up + + + + Unverified login + The user just signed in with this device and hasn't verified their master key. + Unverified login + + + + Please verify your other devices + There are unverified devices signed in to this account. + Please verify your other devices + + + + Close + Close + + + Start a new chat Start a new chat @@ -1544,12 +1593,12 @@ Example: https://server.my:8787 RoomMembers - + Members of %1 Members of %1 - + %n people in %1 Summary above list of members @@ -1586,12 +1635,12 @@ Example: https://server.my:8787 RoomSettings - + Room Settings Room Settings - + %1 member(s) %1 member(s) @@ -1697,12 +1746,12 @@ Example: https://server.my:8787 Room Version - + Failed to enable encryption: %1 Failed to enable encryption: %1 - + Select an avatar Select an avatar @@ -1722,8 +1771,8 @@ Example: https://server.my:8787 Error while reading file: %1 - - + + Failed to upload image: %s Failed to upload image: %s @@ -1731,21 +1780,49 @@ Example: https://server.my:8787 RoomlistModel - + Pending invite. Pending invite. - + Previewing this room Previewing this room - + No preview available No preview available + + Root + + + Please enter your login password to continue: + Please enter your login password to continue: + + + + Please enter a valid email address to continue: + Please enter a valid email address to continue: + + + + Please enter a valid phone number to continue: + Please enter a valid phone number to continue: + + + + Please enter the token, which has been sent to you: + Please enter the token, which has been sent to you: + + + + Wait for the confirmation link to arrive, then continue. + Wait for the confirmation link to arrive, then continue. + + ScreenShare @@ -1803,20 +1880,100 @@ Example: https://server.my:8787 SecretStorage - + Failed to connect to secret storage Failed to connect to secret storage - Nheko could not connect to the secure storage to save encryption secrets to. This can have multiple reasons. Check if your D-Bus service is running and you have configured a service like KWallet, Gnome Secrets or the equivalent for your platform. If you are having trouble, feel free to open an issue here: https://github.com/Nheko-Reborn/nheko/issues - Nheko could not connect to the secure storage to save encryption secrets to. This can have multiple reasons. Check if your D-Bus service is running and you have configured a service like KWallet, Gnome Secrets or the equivalent for your platform. If you are having trouble, feel free to open an issue here: https://github.com/Nheko-Reborn/nheko/issues + Nheko could not connect to the secure storage to save encryption secrets to. This can have multiple reasons. Check if your D-Bus service is running and you have configured a service like KWallet, Gnome Keyring, KeePassXC or the equivalent for your platform. If you are having trouble, feel free to open an issue here: https://github.com/Nheko-Reborn/nheko/issues + Nheko could not connect to the secure storage to save encryption secrets to. This can have multiple reasons. Check if your D-Bus service is running and you have configured a service like KWallet, Gnome Keyring, KeePassXC or the equivalent for your platform. If you are having trouble, feel free to open an issue here: https://github.com/Nheko-Reborn/nheko/issues + + + + SelfVerificationCheck + + + This is your recovery key. You will need it to restore access to your encrypted messages and verification keys. Keep this safe. Don't share it with anyone and don't lose it! Do not pass go! Do not collect $200! + This is your recovery key. You will need it to restore access to your encrypted messages and verification keys. Keep this safe. Don't share it with anyone and don't lose it! Do not pass go! Do not collect $200! + + + + Encryption setup successfully + Encryption setup successfully + + + + Failed to setup encryption: %1 + Failed to setup encryption: %1 + + + + Setup Encryption + Setup Encryption + + + + Hello and welcome to Matrix! +It seems like you are new. Before you can securely encrypt your messages, we need to setup a few small things. You can either press accept immediately or adjust a few basic options. We also try to explain a few of the basics. You can skip those parts, but they might prove to be helpful! + Hello and welcome to Matrix! +It seems like you are new. Before you can securely encrypt your messages, we need to setup a few small things. You can either press accept immediately or adjust a few basic options. We also try to explain a few of the basics. You can skip those parts, but they might prove to be helpful! + + + + Activate Encryption + Activate Encryption + + + + It seems like you have encryption already configured for this account. To be able to access your encrypted messages and make this device appear as trusted, you can either verify an existing device or (if you have one) enter your recovery passphrase. Please select one of the options below. +If you choose verify, you need to have the other device available. If you choose "enter passphrase", you will need your recovery key or passphrase. If you click cancel, you can choose to verify yourself at a later point. + It seems like you have encryption already configured for this account. To be able to access your encrypted messages and make this device appear as trusted, you can either verify an existing device or (if you have one) enter your recovery passphrase. Please select one of the options below. +If you choose verify, you need to have the other device available. If you choose "enter passphrase", you will need your recovery key or passphrase. If you click cancel, you can choose to verify yourself at a later point. + + + + verify + verify + + + + enter passphrase + enter passphrase + + + + SelfVerificationStatus + + + Failed to create keys for cross-signing! + Failed to create keys for cross-signing! + + + + Failed to create keys for online key backup! + Failed to create keys for online key backup! + + + + Failed to create keys secure server side secret storage! + Failed to create keys secure server side secret storage! + + + + Encryption Setup + Encryption Setup + + + + Encryption setup failed: %1 + Encryption setup failed: %1 SingleImagePackModel - + Failed to update image pack: %1 Failed to update image pack: %1 @@ -1832,7 +1989,7 @@ Example: https://server.my:8787 Failed to open image: %1 - + Failed to upload image: %1 Failed to upload image: %1 @@ -1889,18 +2046,18 @@ Example: https://server.my:8787 TimelineModel - + Message redaction failed: %1 Message redaction failed: %1 - + Failed to encrypt event, sending aborted! Failed to encrypt event, sending aborted! - + Save image Save image @@ -1920,7 +2077,7 @@ Example: https://server.my:8787 Save file - + %1 and %2 are typing. Multiple users are typing. First argument is a comma separated list of potentially multiple users. Second argument is the last user of that list. (If only one user is typing, %1 is empty. You should still use it in your string though to silence Qt warnings.) @@ -1929,7 +2086,7 @@ Example: https://server.my:8787 - + %1 opened the room to the public. %1 opened the room to the public. @@ -1969,12 +2126,12 @@ Example: https://server.my:8787 %1 set the room history visible to members from this point on. - + %1 set the room history visible to members since they were invited. %1 set the room history visible to members since they were invited. - + %1 set the room history visible to members since they joined the room. %1 set the room history visible to members since they joined the room. @@ -1984,12 +2141,12 @@ Example: https://server.my:8787 %1 has changed the room's permissions. - + %1 was invited. %1 was invited. - + %1 changed their avatar. %1 changed their avatar. @@ -2004,12 +2161,12 @@ Example: https://server.my:8787 %1 joined. - + %1 joined via authorisation from %2's server. %1 joined via authorisation from %2's server. - + %1 rejected their invite. %1 rejected their invite. @@ -2039,27 +2196,27 @@ Example: https://server.my:8787 %1 was banned. - + Reason: %1 Reason: %1 - + %1 redacted their knock. %1 redacted their knock. - + You joined this room. You joined this room. - + %1 has changed their avatar and changed their display name to %2. %1 has changed their avatar and changed their display name to %2. - + %1 has changed their display name to %2. %1 has changed their display name to %2. @@ -2096,7 +2253,12 @@ Example: https://server.my:8787 No room open - + + No preview available + No preview available + + + %1 member(s) %1 member(s) @@ -2121,28 +2283,20 @@ Example: https://server.my:8787 Back to room list - - TimelineViewManager - - - No encrypted private chat found with this user. Create an encrypted private chat with this user and try again. - No encrypted private chat found with this user. Create an encrypted private chat with this user and try again. - - TopBar - + Back to room list Back to room list - + No room selected No room selected - + This room is not encrypted! This room is not encrypted! @@ -2153,8 +2307,8 @@ Example: https://server.my:8787 - This rooms contain verified devices and devices which have never changed their master key. - This rooms contain verified devices and devices which have never changed their master key. + This room contains verified devices and devices which have never changed their master key. + This room contains verified devices and devices which have never changed their master key. @@ -2200,10 +2354,35 @@ Example: https://server.my:8787 Quit + + UIA + + + No available registration flows! + No available registration flows! + + + + + + Registration aborted + Registration aborted + + + + Please enter a valid registration token. + Please enter a valid registration token. + + + + Invalid token + Invalid token + + UserProfile - + Global User Profile Global User Profile @@ -2213,7 +2392,7 @@ Example: https://server.my:8787 Room User Profile - + Change avatar globally. Change avatar globally. @@ -2223,7 +2402,7 @@ Example: https://server.my:8787 Change avatar. Will only apply to this room. - + Change display name globally. Change display name globally. @@ -2233,7 +2412,7 @@ Example: https://server.my:8787 Change display name. Will only apply to this room. - + Room: %1 Room: %1 @@ -2243,18 +2422,18 @@ Example: https://server.my:8787 This is a room-specific profile. The user's name and avatar may be different from their global versions. - + Open the global profile for this user. Open the global profile for this user. - - + + Verify Verify - + Start a private chat. Start a private chat. @@ -2269,12 +2448,42 @@ Example: https://server.my:8787 Ban the user. - + + Refresh device list. + Refresh device list. + + + + Sign out this device. + Sign out this device. + + + + Change device name. + Change device name. + + + + Last seen %1 from %2 + Last seen %1 from %2 + + + Unverify Unverify - + + Sign out device %1 + Sign out device %1 + + + + You signed out this device. + You signed out this device. + + + Select an avatar Select an avatar @@ -2297,8 +2506,8 @@ Example: https://server.my:8787 UserSettings - - + + Default Default @@ -2306,7 +2515,7 @@ Example: https://server.my:8787 UserSettingsPage - + Minimize to tray Minimize to tray @@ -2316,22 +2525,22 @@ Example: https://server.my:8787 Start in tray - + Group's sidebar Group's sidebar - + Circular Avatars Circular Avatars - + profile: %1 profile: %1 - + Default Default @@ -2356,7 +2565,7 @@ Example: https://server.my:8787 DOWNLOAD - + Keep the application running in the background after closing the client window. Keep the application running in the background after closing the client window. @@ -2372,6 +2581,16 @@ OFF - square, ON - Circle. Change the appearance of user avatars in chats. OFF - square, ON - Circle. + + + Use identicons + Use identicons + + + + Display an identicon instead of a letter when a user has not set an avatar. + Display an identicon instead of a letter when a user has not set an avatar. + Show a column containing groups and tags next to the room list. @@ -2402,7 +2621,7 @@ be blurred. be blurred. - + Privacy screen timeout (in seconds [0 - 3600]) Privacy screen timeout (in seconds [0 - 3600]) @@ -2462,7 +2681,7 @@ If this is off, the list of rooms will only be sorted by the timestamp of the la If this is on, rooms which have active notifications (the small circle with a number in it) will be sorted on top. Rooms, that you have muted, will still be sorted by timestamp, since you don't seem to consider them as important as the other rooms. - + Read receipts Read receipts @@ -2474,7 +2693,7 @@ Status is displayed next to timestamps. Status is displayed next to timestamps. - + Send messages as Markdown Send messages as Markdown @@ -2563,7 +2782,7 @@ This usually causes the application icon in the task bar to animate in some fash Download message encryption keys from and upload to the encrypted online key backup. - + Enable online key backup Enable online key backup @@ -2573,7 +2792,7 @@ This usually causes the application icon in the task bar to animate in some fash The Nheko authors recommend not enabling online key backup until symmetric online key backup is available. Enable anyway? - + CACHED CACHED @@ -2583,7 +2802,7 @@ This usually causes the application icon in the task bar to animate in some fash NOT CACHED - + Scale factor Scale factor @@ -2658,7 +2877,7 @@ This usually causes the application icon in the task bar to animate in some fash Device Fingerprint - + Session Keys Session Keys @@ -2678,17 +2897,17 @@ This usually causes the application icon in the task bar to animate in some fash ENCRYPTION - + GENERAL GENERAL - + INTERFACE INTERFACE - + Plays media like GIFs or WEBPs only when explicitly hovering over them. Plays media like GIFs or WEBPs only when explicitly hovering over them. @@ -2728,7 +2947,7 @@ This usually causes the application icon in the task bar to animate in some fash The key to verify other users. If it is cached, verifying a user will verify all their devices. - + Self signing key Self signing key @@ -2758,14 +2977,14 @@ This usually causes the application icon in the task bar to animate in some fash All Files (*) - + Open Sessions File Open Sessions File - + @@ -2773,19 +2992,19 @@ This usually causes the application icon in the task bar to animate in some fash Error - - + + File Password File Password - + Enter the passphrase to decrypt the file: Enter the passphrase to decrypt the file: - + The password cannot be empty The password cannot be empty @@ -2800,6 +3019,14 @@ This usually causes the application icon in the task bar to animate in some fash File to save the exported session keys + + VerificationManager + + + No encrypted private chat found with this user. Create an encrypted private chat with this user and try again. + No encrypted private chat found with this user. Create an encrypted private chat with this user and try again. + + Waiting @@ -2854,7 +3081,7 @@ This usually causes the application icon in the task bar to animate in some fash descriptiveTime - + Yesterday Yesterday @@ -2925,37 +3152,6 @@ This usually causes the application icon in the task bar to animate in some fash Open the fallback, follow the steps and confirm after completing them. - - dialogs::JoinRoom - - - Join - Join - - - - Cancel - Cancel - - - - Room ID or alias - Room ID or alias - - - - dialogs::LeaveRoom - - - Cancel - Cancel - - - - Are you sure you want to leave? - Are you sure you want to leave? - - dialogs::Logout @@ -3022,47 +3218,47 @@ Media size: %2 %1 sent an audio clip - + You sent an image You sent an image - + %1 sent an image %1 sent an image - + You sent a file You sent a file - + %1 sent a file %1 sent a file - + You sent a video You sent a video - + %1 sent a video %1 sent a video - + You sent a sticker You sent a sticker - + %1 sent a sticker %1 sent a sticker - + You sent a notification You sent a notification @@ -3077,7 +3273,7 @@ Media size: %2 You: %1 - + %1: %2 %1: %2 @@ -3097,27 +3293,27 @@ Media size: %2 You placed a call - + %1 placed a call %1 placed a call - + You answered a call You answered a call - + %1 answered a call %1 answered a call - + You ended a call You ended a call - + %1 ended a call %1 ended a call @@ -3125,7 +3321,7 @@ Media size: %2 utils - + Unknown Message Type Unknown Message Type diff --git a/resources/langs/nheko_eo.ts b/resources/langs/nheko_eo.ts index 021751ff..0d80dbcf 100644 --- a/resources/langs/nheko_eo.ts +++ b/resources/langs/nheko_eo.ts @@ -4,7 +4,7 @@ ActiveCallBar - + Calling... Vokante… @@ -56,7 +56,7 @@ CallInvite - + Video Call Vidvoko @@ -74,7 +74,7 @@ CallInviteBar - + Video Call Vidvoko @@ -117,7 +117,7 @@ CallManager - + Entire screen Tuta ekrano @@ -125,23 +125,23 @@ ChatPage - + Failed to invite user: %1 Malsukcesis inviti uzanton: %1 - + Invited user: %1 Invitita uzanto: %1 - + Migrating the cache to the current version failed. This can have different reasons. Please open an issue and try to use an older version in the mean time. Alternatively you can try deleting the cache manually. Malsukcesis migrado de kaŝmemoro al nuna versio. Tio povas havi diversajn kialojn. Bonvolu raporti eraron kaj dume provi malpli novan version. Alternative, vi povas provi forigi la kaŝmemoron permane. - + Confirm join Konfirmu aliĝon @@ -151,24 +151,24 @@ Ĉu vi certe volas aliĝi al %1? - + Room %1 created. I believe that the -at ending is correct here. Ĉambro %1 farit. - - + + Confirm invite Konfirmu inviton - + Do you really want to invite %1 (%2)? Ĉu vi certe volas inviti uzanton %1 (%2)? - + Failed to invite %1 to %2: %3 Malsukcesis inviti uzanton %1 al %2: %3 @@ -183,7 +183,7 @@ Ĉu vi certe volas forpeli uzanton %1 (%2)? - + Kicked user: %1 Forpelis uzanton: %1 @@ -198,7 +198,7 @@ Ĉu vi certe volas forbari uzanton %1 (%2)? - + Failed to ban %1 in %2: %3 Malsukcesis forbari uzanton %1 en %2: %3 @@ -218,7 +218,7 @@ Ĉu vi certe volas malforbari uzanton %1 (%2)? - + Failed to unban %1 in %2: %3 Malsukcesis malforbari uzanton %1 en %2: %3 @@ -228,12 +228,12 @@ Malforbaris uzanton: %1 - + Do you really want to start a private chat with %1? Ĉu vi certe volas komenci privatan babilon kun %1? - + Cache migration failed! Malsukcesis migrado de kaŝmemoro! @@ -260,23 +260,23 @@ Malsukcesis rehavi konservitajn datumojn. Bonvolu resaluti. - + Failed to setup encryption keys. Server response: %1 %2. Please try again later. Malsukcesis agordi ĉifrajn ŝlosilojn. Respondo de servilo: %1 %2. Bonvolu reprovi poste. - - + + Please try to login again: %1 Bonvolu provi resaluti: %1 - + Failed to join room: %1 Malsukcesis aliĝi al ĉambro: %1 - + You joined the room Vi aliĝis la ĉambron @@ -286,17 +286,17 @@ Malsukcesis forigi inviton: %1 - + Room creation failed: %1 Malsukcesis krei ĉambron: %1 - + Failed to leave room: %1 Malsukcesis eliri el ĉambro: %1 - + Failed to kick %1 from %2: %3 Malsukcesis forpeli uzanton %1 de %2: %3 @@ -355,7 +355,7 @@ CrossSigningSecrets - + Decrypt secrets Malĉifri sekretojn @@ -365,12 +365,12 @@ Enigu vian rehavan ŝlosilon aŭ pasfrazon por malĉifri viajn sekretojn: - + Enter your recovery key or passphrase called %1 to decrypt your secrets: Enigu vian rehavan ŝlosilon aŭ pasfrazon kun nomo %1 por malĉifri viajn sekretojn: - + Decryption failed Malsukcesis malĉifrado @@ -582,17 +582,26 @@ - Device verification timed out. Trafiĝis tempolimo de aparata kontrolo. - + Other party canceled the verification. Aliulo nuligis la kontrolon. - + + Verification messages received out of order! + + + + + Unknown verification error. + + + + Close Fermi @@ -613,7 +622,7 @@ Redaktado de bildopako - + Add images Aldoni bildojn @@ -623,7 +632,7 @@ Glumarkoj (*.png *.webp *.gif *.jpg *.jpeg) - + State key Identigilo (stata ŝlosilo) @@ -639,13 +648,13 @@ - + Use as Emoji Uzi kiel bildosignon - - + + Use as Sticker Uzi kiel glumarkon @@ -698,7 +707,7 @@ Nova ĉambra pako - + Private pack Privata pako @@ -713,7 +722,7 @@ Ĉie ŝaltita pako - + Enable globally Ŝalti ĉie @@ -736,7 +745,7 @@ InputBar - + Select a file Elektu dosieron @@ -746,7 +755,7 @@ Ĉiuj dosieroj (*) - + Failed to upload media. Please try again. Malsukcesis alŝuti vidaŭdaĵojn. Bonvolu reprovi. @@ -754,7 +763,7 @@ InviteDialog - + Invite users to %1 Invitu uzantojn al %1 @@ -785,6 +794,32 @@ Nuligi + + JoinRoomDialog + + + Join room + + + + + Room ID or alias + Identigilo aŭ kromnomo de ĉambro + + + + LeaveRoomDialog + + + Leave room + Eliri el ĉambro + + + + Are you sure you want to leave? + Ĉu vi certas, ke vi volas foriri? + + LoginPage @@ -854,25 +889,25 @@ Ekzemplo: https://servilo.mia:8787 SALUTI - + - + You have entered an invalid Matrix ID e.g @joe:matrix.org Vi enigis nevalidan identigilon de Matrikso ekz. @tacuo:matrix.org - + Autodiscovery failed. Received malformed response. Malsukcesis memaga trovado. Ricevis misformitan respondon. - + Autodiscovery failed. Unknown error when requesting .well-known. Malsukcesis memaga trovado. Okazis nekonata eraro dum petado. .well-known. - + The required endpoints were not found. Possibly not a Matrix server. La bezonataj konektaj lokoj ne troviĝis. Eble tio ne estas Matriksa servilo. @@ -882,26 +917,44 @@ Ekzemplo: https://servilo.mia:8787 Ricevis misformitan respondon. Certiĝu, ke retnomo de la hejmservilo estas valida. - + An unknown error occured. Make sure the homeserver domain is valid. Okazis nekonata eraro. Certiĝu, ke retnomo de la hejmservilo estas valida. - + SSO LOGIN UNUNURA SALUTO - + Empty password Malplena pasvorto - + SSO login failed Malsukcesis ununura saluto + + LogoutDialog + + + Log out + + + + + A call is in progress. Log out? + + + + + Are you sure you want to log out? + + + MessageDelegate @@ -1027,7 +1080,7 @@ Ekzemplo: https://servilo.mia:8787 MessageView - + Edit Redakti @@ -1047,7 +1100,7 @@ Ekzemplo: https://servilo.mia:8787 Elektebloj - + &Copy &Kopii @@ -1137,7 +1190,12 @@ Ekzemplo: https://servilo.mia:8787 Ricevita kontrolpeto - + + To allow other users to see, which of your devices actually belong to you, you can verify them. This also allows key backup to work automatically. Verify an unverified device now? (Please make sure you have one of those devices available.) + + + + To allow other users to see, which of your devices actually belong to you, you can verify them. This also allows key backup to work automatically. Verify %1 now? Por vidigi al aliuloj, kiuj viaj aparatoj vere apartenas al vi, vi povas ilin kontroli. Tio ankaŭ funkciigus memagan savkopiadon de ŝlosiloj. Ĉu vi volas kontroli aparaton %1 nun? @@ -1186,7 +1244,7 @@ Ekzemplo: https://servilo.mia:8787 NotificationWarning - You will be pinging the whole room + You are about to notify the whole room @@ -1194,29 +1252,17 @@ Ekzemplo: https://servilo.mia:8787 NotificationsManager - - + + %1 sent an encrypted message %1 sendis ĉifritan mesaĝon - - * %1 %2 - Format an emote message in a notification, %1 is the sender, %2 the message - * %1 %2 - - - + %1 replied: %2 Format a reply in a notification. %1 is the sender, %2 the message %1 respondis: %2 - - - %1: %2 - Format a normal message in a notification. %1 is the sender, %2 the message - %1: %2 - @@ -1247,7 +1293,7 @@ Ekzemplo: https://servilo.mia:8787 Neniu mikrofono troviĝis. - + Voice Voĉe @@ -1278,7 +1324,7 @@ Ekzemplo: https://servilo.mia:8787 QCoreApplication - + Create a unique profile, which allows you to log into several accounts at the same time and start multiple instances of nheko. Krei unikan profilon, kiu permesos al vi saluti kelkajn kontojn samtempe, kaj startigi plurajn nhekojn. @@ -1296,7 +1342,7 @@ Ekzemplo: https://servilo.mia:8787 ReadReceipts - + Read receipts Kvitancoj @@ -1304,7 +1350,7 @@ Ekzemplo: https://servilo.mia:8787 ReadReceiptsModel - + Yesterday, %1 Hieraŭ, %1 @@ -1312,18 +1358,18 @@ Ekzemplo: https://servilo.mia:8787 RegisterPage - + Username Uzantonomo - + The username must not be empty, and must contain only the characters a-z, 0-9, ., _, =, -, and /. La uzantonomo devas ne esti malplena, kaj devas enhavi nur la signojn a–z, 0–9, ., _, =, -, kaj /. - + Password Pasvorto @@ -1353,37 +1399,22 @@ Ekzemplo: https://servilo.mia:8787 REGISTRIĜI - - No supported registration flows! - Neniuj subtenataj manieroj de registriĝo! - - - - Registration token - Registra peco - - - - Please enter a valid registration token. - Bonvolu enigi validan registran pecon. - - - + Autodiscovery failed. Received malformed response. Malsukcesis memaga trovado. Ricevis misformitan respondon. - + Autodiscovery failed. Unknown error when requesting .well-known. Malsukcesis memaga trovado. Okazis nekonata eraro dum petado. .well-known. - + The required endpoints were not found. Possibly not a Matrix server. La bezonataj konektaj lokoj ne troviĝis. Eble tio ne estas Matriksa servilo. - + Received malformed response. Make sure the homeserver domain is valid. Ricevis misformitan respondon. Certiĝu, ke retnomo de la hejmservilo estas valida. @@ -1393,7 +1424,7 @@ Ekzemplo: https://servilo.mia:8787 Okazis nekonata eraro. Certiĝu, ke retnomo de la hejmservilo estas valida. - + Password is not long enough (min 8 chars) Pasvorto nesufiĉe longas (almenaŭ 8 signoj) @@ -1424,20 +1455,25 @@ Ekzemplo: https://servilo.mia:8787 RoomDirectory - + Explore Public Rooms Esplori publikajn ĉambrojn - + Search for public rooms Serĉi publikajn ĉambrojn + + + Choose custom homeserver + + RoomInfo - + no version stored neniu versio konservita @@ -1454,16 +1490,6 @@ Ekzemplo: https://servilo.mia:8787 Enter the tag you want to use: Enigu la etikedon, kiun vi volas uzi: - - - Leave Room - Eliri el ĉambro - - - - Are you sure you want to leave this room? - Ĉu vi certas, ke vi volas eliri el ĉi tiu ĉambro? - Leave room @@ -1495,7 +1521,7 @@ Ekzemplo: https://servilo.mia:8787 Krei novan etikedon… - + Status Message Statmesaĝo @@ -1520,7 +1546,30 @@ Ekzemplo: https://servilo.mia:8787 Adiaŭi - + + Encryption not set up + Cross-signing setup has not run yet. + + + + + Unverified login + The user just signed in with this device and hasn't verified their master key. + + + + + Please verify your other devices + There are unverified devices signed in to this account. + + + + + Close + Fermi + + + Start a new chat Komenci novan babilon @@ -1548,12 +1597,12 @@ Ekzemplo: https://servilo.mia:8787 RoomMembers - + Members of %1 Anoj de %1 - + %n people in %1 Summary above list of members @@ -1590,12 +1639,12 @@ Ekzemplo: https://servilo.mia:8787 RoomSettings - + Room Settings Agordoj de ĉambro - + %1 member(s) %1 ĉambrano(j) @@ -1701,12 +1750,12 @@ Ekzemplo: https://servilo.mia:8787 Versio de ĉambro - + Failed to enable encryption: %1 Malsukcesis ŝalti ĉifradon: %1 - + Select an avatar Elektu bildon de ĉambro @@ -1726,8 +1775,8 @@ Ekzemplo: https://servilo.mia:8787 Eraris legado de dosiero: %1 - - + + Failed to upload image: %s Malsukcesis alŝuti bildon: %s @@ -1735,21 +1784,49 @@ Ekzemplo: https://servilo.mia:8787 RoomlistModel - + Pending invite. Atendanta invito. - + Previewing this room Antaŭrigardante ĉi tiun ĉambron - + No preview available Neniu antaŭrigardo disponeblas + + Root + + + Please enter your login password to continue: + + + + + Please enter a valid email address to continue: + + + + + Please enter a valid phone number to continue: + + + + + Please enter the token, which has been sent to you: + + + + + Wait for the confirmation link to arrive, then continue. + + + ScreenShare @@ -1807,20 +1884,98 @@ Ekzemplo: https://servilo.mia:8787 SecretStorage - + Failed to connect to secret storage Malsukcesis konektiĝi al sekreta deponejo - Nheko could not connect to the secure storage to save encryption secrets to. This can have multiple reasons. Check if your D-Bus service is running and you have configured a service like KWallet, Gnome Secrets or the equivalent for your platform. If you are having trouble, feel free to open an issue here: https://github.com/Nheko-Reborn/nheko/issues - Nheko ne povis konektiĝi al la sekreta deponejo por konservi ĉifrajn sekretojn. Tio povas havi diversajn kialojn. Kontrolu, ĉu la servo «D-Bus» funkcias, kaj ĉu vi agordis kroman servon kiel «KWallet», «Gnome Secrets», aŭ similan servon por via sistemo. Se daŭras problemoj, laŭplaĉe raportu ilin tie ĉi: https://github.com/Nheko-Reborn/nheko/issues + Nheko could not connect to the secure storage to save encryption secrets to. This can have multiple reasons. Check if your D-Bus service is running and you have configured a service like KWallet, Gnome Keyring, KeePassXC or the equivalent for your platform. If you are having trouble, feel free to open an issue here: https://github.com/Nheko-Reborn/nheko/issues + + + + + SelfVerificationCheck + + + This is your recovery key. You will need it to restore access to your encrypted messages and verification keys. Keep this safe. Don't share it with anyone and don't lose it! Do not pass go! Do not collect $200! + + + + + Encryption setup successfully + + + + + Failed to setup encryption: %1 + + + + + Setup Encryption + + + + + Hello and welcome to Matrix! +It seems like you are new. Before you can securely encrypt your messages, we need to setup a few small things. You can either press accept immediately or adjust a few basic options. We also try to explain a few of the basics. You can skip those parts, but they might prove to be helpful! + + + + + Activate Encryption + + + + + It seems like you have encryption already configured for this account. To be able to access your encrypted messages and make this device appear as trusted, you can either verify an existing device or (if you have one) enter your recovery passphrase. Please select one of the options below. +If you choose verify, you need to have the other device available. If you choose "enter passphrase", you will need your recovery key or passphrase. If you click cancel, you can choose to verify yourself at a later point. + + + + + verify + + + + + enter passphrase + + + + + SelfVerificationStatus + + + Failed to create keys for cross-signing! + + + + + Failed to create keys for online key backup! + + + + + Failed to create keys secure server side secret storage! + + + + + Encryption Setup + + + + + Encryption setup failed: %1 + SingleImagePackModel - + Failed to update image pack: %1 Malsukcesis ĝisdatigi bildopakon: %1 @@ -1836,7 +1991,7 @@ Ekzemplo: https://servilo.mia:8787 Malsukcesis malfermi bildon: %1 - + Failed to upload image: %1 Malsukcesis alŝuti bildon: %1 @@ -1894,18 +2049,18 @@ Ekzemplo: https://servilo.mia:8787 TimelineModel - + Message redaction failed: %1 Malsukcesis redaktado de mesaĝo: %1 - + Failed to encrypt event, sending aborted! Malsukcesis ĉifri okazon; sendado nuliĝis! - + Save image Konservi bildon @@ -1925,7 +2080,7 @@ Ekzemplo: https://servilo.mia:8787 Konservi dosieron - + %1 and %2 are typing. Multiple users are typing. First argument is a comma separated list of potentially multiple users. Second argument is the last user of that list. (If only one user is typing, %1 is empty. You should still use it in your string though to silence Qt warnings.) @@ -1934,7 +2089,7 @@ Ekzemplo: https://servilo.mia:8787 - + %1 opened the room to the public. %1 malfermis la ĉambron al publiko. @@ -1974,12 +2129,12 @@ Ekzemplo: https://servilo.mia:8787 %1 videbligis historion de la ĉambro al ĉiuj ĉambranoj ekde nun. - + %1 set the room history visible to members since they were invited. %1 videbligis historion de la ĉambro al ĉambranoj ekde ties invito. - + %1 set the room history visible to members since they joined the room. %1 videbligis historion de la ĉambro al ĉambranoj ekde ties aliĝo. @@ -1989,13 +2144,13 @@ Ekzemplo: https://servilo.mia:8787 %1 ŝanĝis permesojn de la ĉambro. - + %1 was invited. %1 estis invitata. %1 estis invitita. - + %1 changed their avatar. %1 ŝanĝis sian avataron. %1 ŝanĝis sian profilbildon. @@ -2011,12 +2166,12 @@ Ekzemplo: https://servilo.mia:8787 %1 aliĝis. - + %1 joined via authorisation from %2's server. %1 aliĝis per rajtigo de servilo de %2. - + %1 rejected their invite. %1 rifuzis sian inviton. @@ -2046,27 +2201,27 @@ Ekzemplo: https://servilo.mia:8787 %1 estas forbarita. - + Reason: %1 Kialo: %1 - + %1 redacted their knock. %1 forigis sian frapon. - + You joined this room. Vi aliĝis ĉi tiun ĉambron. - + %1 has changed their avatar and changed their display name to %2. %1 ŝanĝis sian profilbildon kaj sian prezentan nomon al %2. - + %1 has changed their display name to %2. %1 ŝanĝis sian prezentan nomon al %2. @@ -2103,7 +2258,12 @@ Ekzemplo: https://servilo.mia:8787 Neniu ĉambro estas malfermita - + + No preview available + Neniu antaŭrigardo disponeblas + + + %1 member(s) %1 ĉambrano(j) @@ -2128,28 +2288,20 @@ Ekzemplo: https://servilo.mia:8787 Reen al listo de ĉambroj - - TimelineViewManager - - - No encrypted private chat found with this user. Create an encrypted private chat with this user and try again. - Neniu ĉifrita privata babilo kun ĉi tiu uzanto troviĝis. Kreu ĉifritan privatan babilon kun ĉi tiu uzanto kaj reprovu. - - TopBar - + Back to room list Reen al listo de ĉambroj - + No room selected Neniu ĉambro estas elektita - + This room is not encrypted! Ĉi tiu ĉambro ne estas ĉifrata! @@ -2160,8 +2312,8 @@ Ekzemplo: https://servilo.mia:8787 - This rooms contain verified devices and devices which have never changed their master key. - Ĉi tiu ĉambro enhavas kontrolitajn aparatojn kaj aparatojn, kiuj neniam ŝanĝis sian ĉefan ŝlosilon. + This room contains verified devices and devices which have never changed their master key. + @@ -2207,10 +2359,35 @@ Ekzemplo: https://servilo.mia:8787 Ĉesigi + + UIA + + + No available registration flows! + + + + + + + Registration aborted + + + + + Please enter a valid registration token. + Bonvolu enigi validan registran pecon. + + + + Invalid token + + + UserProfile - + Global User Profile Ĉiea profilo de uzanto @@ -2220,7 +2397,7 @@ Ekzemplo: https://servilo.mia:8787 Ĉambra profilo de uzanto - + Change avatar globally. Ŝanĝi bildon ĉie. @@ -2230,7 +2407,7 @@ Ekzemplo: https://servilo.mia:8787 Ŝanĝi bildon. Efektiviĝos nur en ĉi tiu ĉambro. - + Change display name globally. Ŝanĝi prezentan nomon ĉie. @@ -2240,7 +2417,7 @@ Ekzemplo: https://servilo.mia:8787 Ŝanĝi prezentan nomon. Efektiviĝos nur en ĉi tiu ĉambro. - + Room: %1 Ĉambro: %1 @@ -2250,18 +2427,18 @@ Ekzemplo: https://servilo.mia:8787 Ĉi tio estas profilo speciala por ĉambro. La nomo kaj profilbildo de la uzanto povas esti malsamaj de siaj ĉieaj versioj. - + Open the global profile for this user. Malfermi la ĉiean profilon de ĉi tiu uzanto. - - + + Verify Kontroli - + Start a private chat. Komenci privatan babilon. @@ -2276,12 +2453,42 @@ Ekzemplo: https://servilo.mia:8787 Forbari la uzanton. - + + Refresh device list. + + + + + Sign out this device. + + + + + Change device name. + + + + + Last seen %1 from %2 + + + + Unverify Malkontroli - + + Sign out device %1 + + + + + You signed out this device. + + + + Select an avatar Elektu profilbildon @@ -2304,8 +2511,8 @@ Ekzemplo: https://servilo.mia:8787 UserSettings - - + + Default Implicita @@ -2313,7 +2520,7 @@ Ekzemplo: https://servilo.mia:8787 UserSettingsPage - + Minimize to tray Etigi al plato @@ -2323,22 +2530,22 @@ Ekzemplo: https://servilo.mia:8787 Komenci ete sur pleto - + Group's sidebar Flanka breto de grupoj - + Circular Avatars Rondaj profilbildoj - + profile: %1 profilo: %1 - + Default Implicita @@ -2363,7 +2570,7 @@ Ekzemplo: https://servilo.mia:8787 ELŜUTI - + Keep the application running in the background after closing the client window. Daŭrigi la aplikaĵon fone post fermo de la klienta fenestro. @@ -2379,6 +2586,16 @@ OFF - square, ON - Circle. Ŝanĝas la aspekton de profilbildoj de uzantoj en babilujo. NE – kvadrataj, JES – rondaj. + + + Use identicons + + + + + Display an identicon instead of a letter when a user has not set an avatar. + + Show a column containing groups and tags next to the room list. @@ -2409,7 +2626,7 @@ be blurred. malklariĝos. - + Privacy screen timeout (in seconds [0 - 3600]) Atendo ĝis privateca ŝirmilo (0–3600 sekundoj) @@ -2476,7 +2693,7 @@ kiujn vi silentigis, ankoraŭ estos ordigitaj laŭ tempo, ĉar vi probable ne pensas ilin same gravaj kiel la aliaj ĉambroj. - + Read receipts Kvitancoj @@ -2488,7 +2705,7 @@ Status is displayed next to timestamps. Stato estas montrita apud tempindikoj. - + Send messages as Markdown Sendi mesaĝojn Markdaŭne @@ -2577,7 +2794,7 @@ This usually causes the application icon in the task bar to animate in some fash Elŝutu ĉifrajn ŝlosilojn por mesaĝoj de la ĉifrita enreta deponejo de ŝlosiloj, aŭ alŝutu ilin tien. - + Enable online key backup Ŝalti enretan savkopiadon de ŝlosiloj @@ -2587,7 +2804,7 @@ This usually causes the application icon in the task bar to animate in some fash Aŭtoroj de Nheko rekomendas ne ŝalti enretan savkopiadon de ŝlosiloj, almenaŭ ĝis simetria enreta savkopiado estos disponebla. Ĉu vi tamen volas ĝin ŝalti? - + CACHED KAŜMEMORITA @@ -2597,7 +2814,7 @@ This usually causes the application icon in the task bar to animate in some fash NE KAŜMEMORITA - + Scale factor Skala obligo @@ -2672,7 +2889,7 @@ This usually causes the application icon in the task bar to animate in some fash Fingrospuro de aparato - + Session Keys Ŝlosiloj de salutaĵo @@ -2692,17 +2909,17 @@ This usually causes the application icon in the task bar to animate in some fash ĈIFRADO - + GENERAL ĜENERALAJ - + INTERFACE FASADO - + Plays media like GIFs or WEBPs only when explicitly hovering over them. Ludas vidaŭdaĵojn kiel GIF-ojn aŭ WEBP-ojn nur sub musmontrilo. @@ -2742,7 +2959,7 @@ This usually causes the application icon in the task bar to animate in some fash Ŝlosilo por kontrolado de aliaj uzantoj. Se ĝi estas kaŝmemorata, kontrolo de uzanto kontrolos ankaŭ ĉiujn ĝiajn aparatojn. - + Self signing key Mem-subskriba ŝlosilo @@ -2772,14 +2989,14 @@ This usually causes the application icon in the task bar to animate in some fash Ĉiuj dosieroj (*) - + Open Sessions File Malfermi dosieron kun salutaĵoj - + @@ -2787,19 +3004,19 @@ This usually causes the application icon in the task bar to animate in some fash Eraro - - + + File Password Pasvorto de dosiero - + Enter the passphrase to decrypt the file: Enigu pasfrazon por malĉifri la dosieron: - + The password cannot be empty La pasvorto ne povas esti malplena @@ -2814,6 +3031,14 @@ This usually causes the application icon in the task bar to animate in some fash Dosiero, kien konserviĝos la elportitaj ŝloslioj de salutaĵo + + VerificationManager + + + No encrypted private chat found with this user. Create an encrypted private chat with this user and try again. + Neniu ĉifrita privata babilo kun ĉi tiu uzanto troviĝis. Kreu ĉifritan privatan babilon kun ĉi tiu uzanto kaj reprovu. + + Waiting @@ -2869,7 +3094,7 @@ This usually causes the application icon in the task bar to animate in some fash descriptiveTime - + Yesterday Hieraŭ @@ -2940,37 +3165,6 @@ This usually causes the application icon in the task bar to animate in some fash Iru al la alternativa metodo, sekvu la paŝojn, kaj fininte ilin, konfirmu. - - dialogs::JoinRoom - - - Join - Aliĝi - - - - Cancel - Nuligi - - - - Room ID or alias - Identigilo aŭ kromnomo de ĉambro - - - - dialogs::LeaveRoom - - - Cancel - Nuligi - - - - Are you sure you want to leave? - Ĉu vi certas, ke vi volas foriri? - - dialogs::Logout @@ -3037,47 +3231,47 @@ Grandeco de vidaŭdaĵo: %2 %1 sendis sonmesaĝon - + You sent an image Vi sendis bildon - + %1 sent an image %1 sendis bildon - + You sent a file Vi sendis dosieron - + %1 sent a file %1 sendis dosieron - + You sent a video Vi sendis filmon - + %1 sent a video %1 sendis filmon - + You sent a sticker Vi sendis glumarkon - + %1 sent a sticker %1 sendis glumarkon - + You sent a notification Vi sendis sciigon @@ -3092,7 +3286,7 @@ Grandeco de vidaŭdaĵo: %2 Vi: %1 - + %1: %2 %1: %2 @@ -3112,27 +3306,27 @@ Grandeco de vidaŭdaĵo: %2 Vi vokis - + %1 placed a call %1 vokis - + You answered a call Vi respondis vokon - + %1 answered a call %1 respondis vokon - + You ended a call Vi finis vokon - + %1 ended a call %1 finis vokon @@ -3140,7 +3334,7 @@ Grandeco de vidaŭdaĵo: %2 utils - + Unknown Message Type Nekonata tipo de mesaĝo diff --git a/resources/langs/nheko_es.ts b/resources/langs/nheko_es.ts index 80ccb676..63916a0a 100644 --- a/resources/langs/nheko_es.ts +++ b/resources/langs/nheko_es.ts @@ -4,7 +4,7 @@ ActiveCallBar - + Calling... Llamando... @@ -56,7 +56,7 @@ CallInvite - + Video Call Videollamada @@ -74,7 +74,7 @@ CallInviteBar - + Video Call Videollamada @@ -117,7 +117,7 @@ CallManager - + Entire screen Pantalla completa @@ -125,23 +125,23 @@ ChatPage - + Failed to invite user: %1 No se pudo invitar al usuario: %1 - + Invited user: %1 Usuario invitado: %1 - + Migrating the cache to the current version failed. This can have different reasons. Please open an issue and try to use an older version in the mean time. Alternatively you can try deleting the cache manually. La migración de la caché a la versión actual ha fallado. Esto puede deberse a distintos motivos. Por favor, reporte el incidente y mientras tanto intente usar una versión anterior. También puede probar a borrar la caché manualmente. - + Confirm join @@ -151,23 +151,23 @@ - + Room %1 created. Sala %1 creada. - - + + Confirm invite Confirmar invitación - + Do you really want to invite %1 (%2)? - + Failed to invite %1 to %2: %3 @@ -182,7 +182,7 @@ - + Kicked user: %1 Se ha expulsado a %1 @@ -197,7 +197,7 @@ - + Failed to ban %1 in %2: %3 @@ -217,7 +217,7 @@ - + Failed to unban %1 in %2: %3 @@ -227,12 +227,12 @@ - + Do you really want to start a private chat with %1? - + Cache migration failed! @@ -259,23 +259,23 @@ - + Failed to setup encryption keys. Server response: %1 %2. Please try again later. - - + + Please try to login again: %1 - + Failed to join room: %1 - + You joined the room @@ -285,17 +285,17 @@ - + Room creation failed: %1 - + Failed to leave room: %1 - + Failed to kick %1 from %2: %3 @@ -354,7 +354,7 @@ CrossSigningSecrets - + Decrypt secrets @@ -364,12 +364,12 @@ - + Enter your recovery key or passphrase called %1 to decrypt your secrets: - + Decryption failed @@ -581,17 +581,26 @@ - Device verification timed out. - + Other party canceled the verification. - + + Verification messages received out of order! + + + + + Unknown verification error. + + + + Close @@ -612,7 +621,7 @@ - + Add images @@ -622,7 +631,7 @@ - + State key @@ -638,13 +647,13 @@ - + Use as Emoji - - + + Use as Sticker @@ -697,7 +706,7 @@ - + Private pack @@ -712,7 +721,7 @@ - + Enable globally @@ -735,7 +744,7 @@ InputBar - + Select a file @@ -745,7 +754,7 @@ - + Failed to upload media. Please try again. @@ -753,7 +762,7 @@ InviteDialog - + Invite users to %1 @@ -784,6 +793,32 @@ Cancelar + + JoinRoomDialog + + + Join room + + + + + Room ID or alias + + + + + LeaveRoomDialog + + + Leave room + + + + + Are you sure you want to leave? + + + LoginPage @@ -846,25 +881,25 @@ Example: https://server.my:8787 - + - + You have entered an invalid Matrix ID e.g @joe:matrix.org - + Autodiscovery failed. Received malformed response. - + Autodiscovery failed. Unknown error when requesting .well-known. - + The required endpoints were not found. Possibly not a Matrix server. @@ -874,26 +909,44 @@ Example: https://server.my:8787 - + An unknown error occured. Make sure the homeserver domain is valid. - + SSO LOGIN - + Empty password - + SSO login failed + + LogoutDialog + + + Log out + + + + + A call is in progress. Log out? + + + + + Are you sure you want to log out? + + + MessageDelegate @@ -1019,7 +1072,7 @@ Example: https://server.my:8787 MessageView - + Edit @@ -1039,7 +1092,7 @@ Example: https://server.my:8787 - + &Copy @@ -1129,7 +1182,12 @@ Example: https://server.my:8787 - + + To allow other users to see, which of your devices actually belong to you, you can verify them. This also allows key backup to work automatically. Verify an unverified device now? (Please make sure you have one of those devices available.) + + + + To allow other users to see, which of your devices actually belong to you, you can verify them. This also allows key backup to work automatically. Verify %1 now? @@ -1178,7 +1236,7 @@ Example: https://server.my:8787 NotificationWarning - You will be pinging the whole room + You are about to notify the whole room @@ -1186,29 +1244,17 @@ Example: https://server.my:8787 NotificationsManager - - + + %1 sent an encrypted message - - * %1 %2 - Format an emote message in a notification, %1 is the sender, %2 the message - - - - + %1 replied: %2 Format a reply in a notification. %1 is the sender, %2 the message - - - %1: %2 - Format a normal message in a notification. %1 is the sender, %2 the message - - @@ -1239,7 +1285,7 @@ Example: https://server.my:8787 No se encontró micrófono. - + Voice @@ -1270,7 +1316,7 @@ Example: https://server.my:8787 QCoreApplication - + Create a unique profile, which allows you to log into several accounts at the same time and start multiple instances of nheko. @@ -1288,7 +1334,7 @@ Example: https://server.my:8787 ReadReceipts - + Read receipts @@ -1296,7 +1342,7 @@ Example: https://server.my:8787 ReadReceiptsModel - + Yesterday, %1 @@ -1304,18 +1350,18 @@ Example: https://server.my:8787 RegisterPage - + Username - + The username must not be empty, and must contain only the characters a-z, 0-9, ., _, =, -, and /. - + Password @@ -1345,37 +1391,22 @@ Example: https://server.my:8787 - - No supported registration flows! - - - - - Registration token - - - - - Please enter a valid registration token. - - - - + Autodiscovery failed. Received malformed response. - + Autodiscovery failed. Unknown error when requesting .well-known. - + The required endpoints were not found. Possibly not a Matrix server. - + Received malformed response. Make sure the homeserver domain is valid. @@ -1385,7 +1416,7 @@ Example: https://server.my:8787 - + Password is not long enough (min 8 chars) @@ -1416,20 +1447,25 @@ Example: https://server.my:8787 RoomDirectory - + Explore Public Rooms - + Search for public rooms + + + Choose custom homeserver + + RoomInfo - + no version stored @@ -1446,16 +1482,6 @@ Example: https://server.my:8787 Enter the tag you want to use: - - - Leave Room - - - - - Are you sure you want to leave this room? - - Leave room @@ -1487,7 +1513,7 @@ Example: https://server.my:8787 - + Status Message @@ -1512,7 +1538,30 @@ Example: https://server.my:8787 - + + Encryption not set up + Cross-signing setup has not run yet. + + + + + Unverified login + The user just signed in with this device and hasn't verified their master key. + + + + + Please verify your other devices + There are unverified devices signed in to this account. + + + + + Close + + + + Start a new chat @@ -1540,12 +1589,12 @@ Example: https://server.my:8787 RoomMembers - + Members of %1 - + %n people in %1 Summary above list of members @@ -1582,12 +1631,12 @@ Example: https://server.my:8787 RoomSettings - + Room Settings - + %1 member(s) @@ -1693,12 +1742,12 @@ Example: https://server.my:8787 - + Failed to enable encryption: %1 - + Select an avatar @@ -1718,8 +1767,8 @@ Example: https://server.my:8787 - - + + Failed to upload image: %s @@ -1727,21 +1776,49 @@ Example: https://server.my:8787 RoomlistModel - + Pending invite. - + Previewing this room - + No preview available + + Root + + + Please enter your login password to continue: + + + + + Please enter a valid email address to continue: + + + + + Please enter a valid phone number to continue: + + + + + Please enter the token, which has been sent to you: + + + + + Wait for the confirmation link to arrive, then continue. + + + ScreenShare @@ -1799,20 +1876,98 @@ Example: https://server.my:8787 SecretStorage - + Failed to connect to secret storage - Nheko could not connect to the secure storage to save encryption secrets to. This can have multiple reasons. Check if your D-Bus service is running and you have configured a service like KWallet, Gnome Secrets or the equivalent for your platform. If you are having trouble, feel free to open an issue here: https://github.com/Nheko-Reborn/nheko/issues + Nheko could not connect to the secure storage to save encryption secrets to. This can have multiple reasons. Check if your D-Bus service is running and you have configured a service like KWallet, Gnome Keyring, KeePassXC or the equivalent for your platform. If you are having trouble, feel free to open an issue here: https://github.com/Nheko-Reborn/nheko/issues + + + + + SelfVerificationCheck + + + This is your recovery key. You will need it to restore access to your encrypted messages and verification keys. Keep this safe. Don't share it with anyone and don't lose it! Do not pass go! Do not collect $200! + + + + + Encryption setup successfully + + + + + Failed to setup encryption: %1 + + + + + Setup Encryption + + + + + Hello and welcome to Matrix! +It seems like you are new. Before you can securely encrypt your messages, we need to setup a few small things. You can either press accept immediately or adjust a few basic options. We also try to explain a few of the basics. You can skip those parts, but they might prove to be helpful! + + + + + Activate Encryption + + + + + It seems like you have encryption already configured for this account. To be able to access your encrypted messages and make this device appear as trusted, you can either verify an existing device or (if you have one) enter your recovery passphrase. Please select one of the options below. +If you choose verify, you need to have the other device available. If you choose "enter passphrase", you will need your recovery key or passphrase. If you click cancel, you can choose to verify yourself at a later point. + + + + + verify + + + + + enter passphrase + + + + + SelfVerificationStatus + + + Failed to create keys for cross-signing! + + + + + Failed to create keys for online key backup! + + + + + Failed to create keys secure server side secret storage! + + + + + Encryption Setup + + + + + Encryption setup failed: %1 SingleImagePackModel - + Failed to update image pack: %1 @@ -1828,7 +1983,7 @@ Example: https://server.my:8787 - + Failed to upload image: %1 @@ -1885,18 +2040,18 @@ Example: https://server.my:8787 TimelineModel - + Message redaction failed: %1 - + Failed to encrypt event, sending aborted! - + Save image @@ -1916,7 +2071,7 @@ Example: https://server.my:8787 - + %1 and %2 are typing. Multiple users are typing. First argument is a comma separated list of potentially multiple users. Second argument is the last user of that list. (If only one user is typing, %1 is empty. You should still use it in your string though to silence Qt warnings.) @@ -1925,7 +2080,7 @@ Example: https://server.my:8787 - + %1 opened the room to the public. @@ -1965,12 +2120,12 @@ Example: https://server.my:8787 - + %1 set the room history visible to members since they were invited. - + %1 set the room history visible to members since they joined the room. @@ -1980,17 +2135,17 @@ Example: https://server.my:8787 - + %1 was invited. - + %1 has changed their avatar and changed their display name to %2. - + %1 has changed their display name to %2. @@ -2010,12 +2165,12 @@ Example: https://server.my:8787 - + %1 joined via authorisation from %2's server. - + %1 rejected their invite. @@ -2045,22 +2200,22 @@ Example: https://server.my:8787 - + Reason: %1 - + %1 redacted their knock. - + You joined this room. Te has unido a esta sala. - + Rejected the knock from %1. @@ -2092,7 +2247,12 @@ Example: https://server.my:8787 - + + No preview available + + + + %1 member(s) @@ -2117,28 +2277,20 @@ Example: https://server.my:8787 - - TimelineViewManager - - - No encrypted private chat found with this user. Create an encrypted private chat with this user and try again. - - - TopBar - + Back to room list - + No room selected - + This room is not encrypted! @@ -2149,7 +2301,7 @@ Example: https://server.my:8787 - This rooms contain verified devices and devices which have never changed their master key. + This room contains verified devices and devices which have never changed their master key. @@ -2196,10 +2348,35 @@ Example: https://server.my:8787 + + UIA + + + No available registration flows! + + + + + + + Registration aborted + + + + + Please enter a valid registration token. + + + + + Invalid token + + + UserProfile - + Global User Profile @@ -2209,7 +2386,7 @@ Example: https://server.my:8787 - + Change avatar globally. @@ -2219,7 +2396,7 @@ Example: https://server.my:8787 - + Change display name globally. @@ -2229,7 +2406,7 @@ Example: https://server.my:8787 - + Room: %1 @@ -2239,18 +2416,18 @@ Example: https://server.my:8787 - + Open the global profile for this user. - - + + Verify - + Start a private chat. @@ -2265,12 +2442,42 @@ Example: https://server.my:8787 - + + Refresh device list. + + + + + Sign out this device. + + + + + Change device name. + + + + + Last seen %1 from %2 + + + + Unverify - + + Sign out device %1 + + + + + You signed out this device. + + + + Select an avatar @@ -2293,8 +2500,8 @@ Example: https://server.my:8787 UserSettings - - + + Default @@ -2302,7 +2509,7 @@ Example: https://server.my:8787 UserSettingsPage - + Minimize to tray @@ -2312,22 +2519,22 @@ Example: https://server.my:8787 - + Group's sidebar - + Circular Avatars - + profile: %1 - + Default @@ -2352,7 +2559,7 @@ Example: https://server.my:8787 - + Keep the application running in the background after closing the client window. @@ -2367,6 +2574,16 @@ Example: https://server.my:8787 OFF - square, ON - Circle. + + + Use identicons + + + + + Display an identicon instead of a letter when a user has not set an avatar. + + Show a column containing groups and tags next to the room list. @@ -2395,7 +2612,7 @@ be blurred. - + Privacy screen timeout (in seconds [0 - 3600]) @@ -2450,7 +2667,7 @@ If this is on, rooms which have active notifications (the small circle with a nu - + Read receipts @@ -2461,7 +2678,7 @@ Status is displayed next to timestamps. - + Send messages as Markdown @@ -2548,7 +2765,7 @@ This usually causes the application icon in the task bar to animate in some fash - + Enable online key backup @@ -2558,7 +2775,7 @@ This usually causes the application icon in the task bar to animate in some fash - + CACHED @@ -2568,7 +2785,7 @@ This usually causes the application icon in the task bar to animate in some fash - + Scale factor @@ -2643,7 +2860,7 @@ This usually causes the application icon in the task bar to animate in some fash - + Session Keys @@ -2663,17 +2880,17 @@ This usually causes the application icon in the task bar to animate in some fash - + GENERAL - + INTERFACE - + Plays media like GIFs or WEBPs only when explicitly hovering over them. @@ -2713,7 +2930,7 @@ This usually causes the application icon in the task bar to animate in some fash - + Self signing key @@ -2743,14 +2960,14 @@ This usually causes the application icon in the task bar to animate in some fash - + Open Sessions File - + @@ -2758,19 +2975,19 @@ This usually causes the application icon in the task bar to animate in some fash - - + + File Password - + Enter the passphrase to decrypt the file: - + The password cannot be empty @@ -2785,6 +3002,14 @@ This usually causes the application icon in the task bar to animate in some fash + + VerificationManager + + + No encrypted private chat found with this user. Create an encrypted private chat with this user and try again. + + + Waiting @@ -2839,7 +3064,7 @@ This usually causes the application icon in the task bar to animate in some fash descriptiveTime - + Yesterday @@ -2910,37 +3135,6 @@ This usually causes the application icon in the task bar to animate in some fash - - dialogs::JoinRoom - - - Join - - - - - Cancel - Cancelar - - - - Room ID or alias - - - - - dialogs::LeaveRoom - - - Cancel - Cancelar - - - - Are you sure you want to leave? - - - dialogs::Logout @@ -3005,47 +3199,47 @@ Media size: %2 - + You sent an image - + %1 sent an image - + You sent a file - + %1 sent a file - + You sent a video - + %1 sent a video - + You sent a sticker - + %1 sent a sticker - + You sent a notification @@ -3060,7 +3254,7 @@ Media size: %2 - + %1: %2 @@ -3080,27 +3274,27 @@ Media size: %2 - + %1 placed a call - + You answered a call - + %1 answered a call - + You ended a call - + %1 ended a call @@ -3108,7 +3302,7 @@ Media size: %2 utils - + Unknown Message Type diff --git a/resources/langs/nheko_et.ts b/resources/langs/nheko_et.ts index 7486656e..0d72ad6b 100644 --- a/resources/langs/nheko_et.ts +++ b/resources/langs/nheko_et.ts @@ -4,7 +4,7 @@ ActiveCallBar - + Calling... Helistan... @@ -56,7 +56,7 @@ CallInvite - + Video Call Videokõne @@ -74,7 +74,7 @@ CallInviteBar - + Video Call Videokõne @@ -117,7 +117,7 @@ CallManager - + Entire screen Terve ekraan @@ -125,23 +125,23 @@ ChatPage - + Failed to invite user: %1 Kutse saatmine kasutajale ei õnnestunud: %1 - + Invited user: %1 Kutsutud kasutaja: %1 - + Migrating the cache to the current version failed. This can have different reasons. Please open an issue and try to use an older version in the mean time. Alternatively you can try deleting the cache manually. Puhverdatud andmete muutmine sobivaks rakenduse praeguse versiooniga ei õnnestunud. Sellel võib olla erinevaid põhjuseid. Palun saada meile veateade ja seni kasuta vanemat rakenduse versiooni. Aga kui sa soovid proovida, siis kustuta puhverdatud andmed käsitsi. - + Confirm join Kinnita liitumine @@ -151,23 +151,23 @@ Kas sa kindlasti soovid liituda %1 jututoaga? - + Room %1 created. %1 jututuba on loodud. - - + + Confirm invite Kinnita kutse - + Do you really want to invite %1 (%2)? Kas sa tõesti soovid saata kutset kasutajale %1 (%2)? - + Failed to invite %1 to %2: %3 Kasutaja %1 kutsumine %2 jututuppa ei õnnestunud: %3 @@ -182,7 +182,7 @@ Kas sa tõesti soovid müksata kasutaja %1 (%2) jututoast välja? - + Kicked user: %1 Väljamüksatud kasutaja: %1 @@ -197,7 +197,7 @@ Kas sa tõesti soovid kasutajale %1 (%2) seada suhtluskeeldu? - + Failed to ban %1 in %2: %3 Kasutajale %1 suhtluskeelu seadmine %2 jututoas ei õnnestunud: %3 @@ -217,7 +217,7 @@ Kas sa tõesti soovid kasutajalt %1 (%2) eemaldada suhtluskeelu? - + Failed to unban %1 in %2: %3 Kasutajalt %1 suhtluskeelu eemaldamine %2 jututoas ei õnnestunud: %3 @@ -227,12 +227,12 @@ Suhtluskeeld eemaldatud: %1 - + Do you really want to start a private chat with %1? Kas sa kindlasti soovid alustada otsevestlust kasutajaga %1? - + Cache migration failed! Puhvri versiooniuuendus ebaõnnestus! @@ -259,23 +259,23 @@ Salvestatud andmete taastamine ei õnnestunud. Palun logi uuesti sisse. - + Failed to setup encryption keys. Server response: %1 %2. Please try again later. Krüptovõtmete kasutusele võtmine ei õnnestunud. Koduserveri vastus päringule: %1 %2. Palun proovi hiljem uuesti. - - + + Please try to login again: %1 Palun proovi uuesti sisse logida: %1 - + Failed to join room: %1 Jututoaga liitumine ei õnnestunud: %1 - + You joined the room Sa liitusid selle jututoaga @@ -285,17 +285,17 @@ Kutse tagasivõtmine ei õnnestunud: %1 - + Room creation failed: %1 Jututoa loomine ei õnnestunud: %1 - + Failed to leave room: %1 Jututoast lahkumine ei õnnestunud: %1 - + Failed to kick %1 from %2: %3 Kasutaja %1 väljamüksamine %2 jututoast ei õnnestunud: %3 @@ -354,7 +354,7 @@ CrossSigningSecrets - + Decrypt secrets Dekrüpti andmed @@ -364,12 +364,12 @@ Andmete dekrüptimiseks sisesta oma taastevõti või salafraas: - + Enter your recovery key or passphrase called %1 to decrypt your secrets: Andmete dekrüptimiseks sisesta oma taastevõti või salafraas nimega %1: - + Decryption failed Dekrüptimine ei õnnestunud @@ -581,17 +581,26 @@ - Device verification timed out. Seadme verifitseerimine aegus. - + Other party canceled the verification. Teine osapool katkestas verifitseerimise. - + + Verification messages received out of order! + + + + + Unknown verification error. + + + + Close Sulge @@ -612,7 +621,7 @@ Muudan pildipakki - + Add images Lisa pilte @@ -622,7 +631,7 @@ Kleepsud (*.png *.webp *.gif *.jpg *.jpeg) - + State key Olekuvõti @@ -638,13 +647,13 @@ - + Use as Emoji Kasuta emojina - - + + Use as Sticker Kasuta kleepsuna @@ -697,7 +706,7 @@ Uus jututoa pildipakk - + Private pack Isiklik pildipakk @@ -712,7 +721,7 @@ Üldkasutatav pildipakk - + Enable globally Luba kasutada üldiselt @@ -735,7 +744,7 @@ InputBar - + Select a file Vali fail @@ -745,7 +754,7 @@ Kõik failid (*) - + Failed to upload media. Please try again. Meediafailide üleslaadimine ei õnnestunud. Palun proovi uuesti. @@ -753,7 +762,7 @@ InviteDialog - + Invite users to %1 Kutsu kasutajaid %1 jututuppa @@ -784,6 +793,32 @@ Loobu + + JoinRoomDialog + + + Join room + + + + + Room ID or alias + Jututoa tunnus või alias + + + + LeaveRoomDialog + + + Leave room + Lahku jututoast + + + + Are you sure you want to leave? + Kas sa oled kindel, et soovid lahkuda? + + LoginPage @@ -850,25 +885,25 @@ Näiteks: https://server.minu:8787 LOGI SISSE - + - + You have entered an invalid Matrix ID e.g @joe:matrix.org Sisestatud Matrix'i kasutajatunnus on vigane - peaks olema @kasutaja:server.tld - + Autodiscovery failed. Received malformed response. Koduserveri automaatne tuvastamine ei õnnestunud: päringuvastus oli vigane. - + Autodiscovery failed. Unknown error when requesting .well-known. Koduserveri automaatne tuvastamine ei õnnestunud: tundmatu viga .well-known päringu tegemisel. - + The required endpoints were not found. Possibly not a Matrix server. Protokolli järgi nõutavaid lõpppunkte ei leidunud. Ilmselt pole tegemist Matrix'i serveriga. @@ -878,26 +913,44 @@ Näiteks: https://server.minu:8787 Päringule sain tagasi vigase vastuse. Palun kontrolli, et koduserveri domeen oleks õige. - + An unknown error occured. Make sure the homeserver domain is valid. Tekkis teadmata viga. Palun kontrolli, et koduserveri domeen on õige. - + SSO LOGIN ÜHEKORDNE SISSELOGIMINE - + Empty password Tühi salasõna - + SSO login failed Ühekordne sisselogimine ei õnnestunud + + LogoutDialog + + + Log out + + + + + A call is in progress. Log out? + + + + + Are you sure you want to log out? + + + MessageDelegate @@ -1023,7 +1076,7 @@ Näiteks: https://server.minu:8787 MessageView - + Edit Muuda @@ -1043,7 +1096,7 @@ Näiteks: https://server.minu:8787 Valikud - + &Copy &Kopeeri @@ -1133,7 +1186,12 @@ Näiteks: https://server.minu:8787 Saabus verifitseerimispäring - + + To allow other users to see, which of your devices actually belong to you, you can verify them. This also allows key backup to work automatically. Verify an unverified device now? (Please make sure you have one of those devices available.) + + + + To allow other users to see, which of your devices actually belong to you, you can verify them. This also allows key backup to work automatically. Verify %1 now? Selleks, et muud kasutajad automaatselt usaldaks sinu seadmeid, peaksid nad verifitseerima. Samaga muutub ka krüptovõtmete varundus automaatseks. Kas verifitseerime seadme %1? @@ -1182,37 +1240,25 @@ Näiteks: https://server.minu:8787 NotificationWarning - You will be pinging the whole room - Sa saadad viitesõnumi tervele jututoale + You are about to notify the whole room + NotificationsManager - - + + %1 sent an encrypted message %1 saatis krüptitud sõnumi - - * %1 %2 - Format an emote message in a notification, %1 is the sender, %2 the message - * %1 %2 - - - + %1 replied: %2 Format a reply in a notification. %1 is the sender, %2 the message %1 vastas: %2 - - - %1: %2 - Format a normal message in a notification. %1 is the sender, %2 the message - %1: %2 - @@ -1243,7 +1289,7 @@ Näiteks: https://server.minu:8787 Ei suuda tuvastada mikrofoni. - + Voice Häälkõne @@ -1274,7 +1320,7 @@ Näiteks: https://server.minu:8787 QCoreApplication - + Create a unique profile, which allows you to log into several accounts at the same time and start multiple instances of nheko. Loo unikaalne profiil, mis võimaldab sul logida samaaegselt sisse erinevatele kasutajakontodele ning käivitada mitu Nheko programmiakent. @@ -1292,7 +1338,7 @@ Näiteks: https://server.minu:8787 ReadReceipts - + Read receipts Lugemisteatised @@ -1300,7 +1346,7 @@ Näiteks: https://server.minu:8787 ReadReceiptsModel - + Yesterday, %1 Eile, %1 @@ -1308,18 +1354,18 @@ Näiteks: https://server.minu:8787 RegisterPage - + Username Kasutajanimi - + The username must not be empty, and must contain only the characters a-z, 0-9, ., _, =, -, and /. Kasutajanimi ei tohi olla tühi ning võib sisaldada vaid a-z, 0-9, ., _, =, -, / tähemärke. - + Password Salasõna @@ -1349,37 +1395,22 @@ Näiteks: https://server.minu:8787 REGISTREERI - - No supported registration flows! - Selline registreerimise töövoog pole toetatud! - - - - Registration token - Registreerimise tunnuslubatunnusluba - - - - Please enter a valid registration token. - Registreerimiseks palun sisesta kehtiv tunnusluba. - - - + Autodiscovery failed. Received malformed response. Koduserveri automaatne tuvastamine ei õnnestunud: päringuvastus oli vigane. - + Autodiscovery failed. Unknown error when requesting .well-known. Koduserveri automaatne tuvastamine ei õnnestunud: tundmatu viga .well-known päringu tegemisel. - + The required endpoints were not found. Possibly not a Matrix server. Protokolli järgi nõutavaid lõpppunkte ei leidunud. Ilmselt pole tegemist Matrix'i serveriga. - + Received malformed response. Make sure the homeserver domain is valid. Päringule sain tagasi vigase vastuse. Palun kontrolli, et koduserveri domeen oleks õige. @@ -1389,7 +1420,7 @@ Näiteks: https://server.minu:8787 Tekkis teadmata viga. Palun kontrolli, et koduserveri domeen on õige. - + Password is not long enough (min 8 chars) Salasõna pole piisavalt pikk (vähemalt 8 tähemärki) @@ -1420,20 +1451,25 @@ Näiteks: https://server.minu:8787 RoomDirectory - + Explore Public Rooms Tutvu avalike jututubadega - + Search for public rooms Otsi avalikke jututube + + + Choose custom homeserver + + RoomInfo - + no version stored salvestatud versiooni ei leidu @@ -1450,16 +1486,6 @@ Näiteks: https://server.minu:8787 Enter the tag you want to use: Kirjuta silt, mida soovid kasutada: - - - Leave Room - Lahku jututoast - - - - Are you sure you want to leave this room? - Kas sa oled kindel, et soovid lahkuda sellest jututoast? - Leave room @@ -1491,7 +1517,7 @@ Näiteks: https://server.minu:8787 Loo uus silt... - + Status Message Olekuteade @@ -1516,7 +1542,30 @@ Näiteks: https://server.minu:8787 Logi välja - + + Encryption not set up + Cross-signing setup has not run yet. + + + + + Unverified login + The user just signed in with this device and hasn't verified their master key. + + + + + Please verify your other devices + There are unverified devices signed in to this account. + + + + + Close + Sulge + + + Start a new chat Alusta uut vestlust @@ -1544,12 +1593,12 @@ Näiteks: https://server.minu:8787 RoomMembers - + Members of %1 %1 jututoa liikmed - + %n people in %1 Summary above list of members @@ -1586,12 +1635,12 @@ Näiteks: https://server.minu:8787 RoomSettings - + Room Settings Jututoa seadistused - + %1 member(s) %1 liige(t) @@ -1697,12 +1746,12 @@ Näiteks: https://server.minu:8787 Jututoa versioon - + Failed to enable encryption: %1 Krüptimise kasutuselevõtmine ei õnnestunud: %1 - + Select an avatar Vali tunnuspilt @@ -1722,8 +1771,8 @@ Näiteks: https://server.minu:8787 Viga faili lugemisel: %1 - - + + Failed to upload image: %s Viga faili üleslaadimisel: %1 @@ -1731,21 +1780,49 @@ Näiteks: https://server.minu:8787 RoomlistModel - + Pending invite. Ootel kutse. - + Previewing this room Jututoa eelvaade - + No preview available Eelvaade pole saadaval + + Root + + + Please enter your login password to continue: + + + + + Please enter a valid email address to continue: + + + + + Please enter a valid phone number to continue: + + + + + Please enter the token, which has been sent to you: + + + + + Wait for the confirmation link to arrive, then continue. + + + ScreenShare @@ -1803,20 +1880,98 @@ Näiteks: https://server.minu:8787 SecretStorage - + Failed to connect to secret storage Ühenduse loomine võtmehoidlaga ei õnnestunud - Nheko could not connect to the secure storage to save encryption secrets to. This can have multiple reasons. Check if your D-Bus service is running and you have configured a service like KWallet, Gnome Secrets or the equivalent for your platform. If you are having trouble, feel free to open an issue here: https://github.com/Nheko-Reborn/nheko/issues - Krüptovõtmete salvestamiseks Nhekol ei õnnestunud luua ühendust võtmehoidlaga. Sellel võib olla mitu põhjust. Kontrolli, kas D-Bus'i alusteenus toimib ning sa oled seadistanud KWallet'i, Gnome Secrets'i või mõne muu sinu platvormil kasutatava turvalise andmehoidla teenuse. Probleemide korral palun ava siin https://github.com/Nheko-Reborn/nheko/issues veateade + Nheko could not connect to the secure storage to save encryption secrets to. This can have multiple reasons. Check if your D-Bus service is running and you have configured a service like KWallet, Gnome Keyring, KeePassXC or the equivalent for your platform. If you are having trouble, feel free to open an issue here: https://github.com/Nheko-Reborn/nheko/issues + + + + + SelfVerificationCheck + + + This is your recovery key. You will need it to restore access to your encrypted messages and verification keys. Keep this safe. Don't share it with anyone and don't lose it! Do not pass go! Do not collect $200! + + + + + Encryption setup successfully + + + + + Failed to setup encryption: %1 + + + + + Setup Encryption + + + + + Hello and welcome to Matrix! +It seems like you are new. Before you can securely encrypt your messages, we need to setup a few small things. You can either press accept immediately or adjust a few basic options. We also try to explain a few of the basics. You can skip those parts, but they might prove to be helpful! + + + + + Activate Encryption + + + + + It seems like you have encryption already configured for this account. To be able to access your encrypted messages and make this device appear as trusted, you can either verify an existing device or (if you have one) enter your recovery passphrase. Please select one of the options below. +If you choose verify, you need to have the other device available. If you choose "enter passphrase", you will need your recovery key or passphrase. If you click cancel, you can choose to verify yourself at a later point. + + + + + verify + + + + + enter passphrase + + + + + SelfVerificationStatus + + + Failed to create keys for cross-signing! + + + + + Failed to create keys for online key backup! + + + + + Failed to create keys secure server side secret storage! + + + + + Encryption Setup + + + + + Encryption setup failed: %1 + SingleImagePackModel - + Failed to update image pack: %1 Pildipaki uuendamine ei õnnestunud: %1 @@ -1832,7 +1987,7 @@ Näiteks: https://server.minu:8787 Pildi avamine ei õnnestunud: %1 - + Failed to upload image: %1 Faili üleslaadimine ei õnnestunud: %1 @@ -1889,18 +2044,18 @@ Näiteks: https://server.minu:8787 TimelineModel - + Message redaction failed: %1 Sõnumi ümbersõnastamine ebaõnnestus: %1 - + Failed to encrypt event, sending aborted! Sündmuse krüptimine ei õnnestunud, katkestame saatmise! - + Save image Salvesta pilt @@ -1920,7 +2075,7 @@ Näiteks: https://server.minu:8787 Salvesta fail - + %1 and %2 are typing. Multiple users are typing. First argument is a comma separated list of potentially multiple users. Second argument is the last user of that list. (If only one user is typing, %1 is empty. You should still use it in your string though to silence Qt warnings.) @@ -1929,7 +2084,7 @@ Näiteks: https://server.minu:8787 - + %1 opened the room to the public. %1 tegi jututoa avalikuks. @@ -1969,12 +2124,12 @@ Näiteks: https://server.minu:8787 %1 muutis, et selle jututoa ajalugu saavad lugeda kõik liikmed alates praegusest ajahetkest. - + %1 set the room history visible to members since they were invited. %1 muutis, et selle jututoa ajalugu saavad lugeda kõik liikmed alates oma kutse saatmisest. - + %1 set the room history visible to members since they joined the room. %1 muutis, et selle jututoa ajalugu saavad lugeda kõik liikmed alates jututoaga liitumise hetkest. @@ -1984,12 +2139,12 @@ Näiteks: https://server.minu:8787 %1 muutis selle jututoa õigusi. - + %1 was invited. %1 sai kutse. - + %1 changed their avatar. %1 muutis oma tunnuspilti. @@ -2004,12 +2159,12 @@ Näiteks: https://server.minu:8787 %1 liitus jututoaga. - + %1 joined via authorisation from %2's server. %1 liitus peale autentimist serverist %2. - + %1 rejected their invite. %1 lükkas liitumiskutse tagasi. @@ -2039,27 +2194,27 @@ Näiteks: https://server.minu:8787 Kasutaja %1 sai suhtluskeelu. - + Reason: %1 Põhjus: %1 - + %1 redacted their knock. %1 muutis oma koputust jututoa uksele. - + You joined this room. Sa liitusid jututoaga. - + %1 has changed their avatar and changed their display name to %2. %1 muutis oma tunnuspilti ja seadistas uueks kuvatavaks nimeks %2. - + %1 has changed their display name to %2. %1 seadistas uueks kuvatavaks nimeks %2. @@ -2096,7 +2251,12 @@ Näiteks: https://server.minu:8787 Ühtegi jututuba pole avatud - + + No preview available + Eelvaade pole saadaval + + + %1 member(s) %1 liige(t) @@ -2121,28 +2281,20 @@ Näiteks: https://server.minu:8787 Tagasi jututubade loendisse - - TimelineViewManager - - - No encrypted private chat found with this user. Create an encrypted private chat with this user and try again. - Ühtegi krüptitud vestlust selle kasutajaga ei leidunud. Palun loo temaga krüptitud vestlus ja proovi uuesti. - - TopBar - + Back to room list Tagasi jututubade loendisse - + No room selected Jututuba on valimata - + This room is not encrypted! See jututuba on krüptimata! @@ -2153,8 +2305,8 @@ Näiteks: https://server.minu:8787 - This rooms contain verified devices and devices which have never changed their master key. - Selles jututoas on vaid verifitseeritud seadmed ning nad ei ole kunagi muutnud oma juurvõtit. + This room contains verified devices and devices which have never changed their master key. + @@ -2200,10 +2352,35 @@ Näiteks: https://server.minu:8787 Lõpeta töö + + UIA + + + No available registration flows! + + + + + + + Registration aborted + + + + + Please enter a valid registration token. + Registreerimiseks palun sisesta kehtiv tunnusluba. + + + + Invalid token + + + UserProfile - + Global User Profile Üldine kasutajaprofiil @@ -2213,7 +2390,7 @@ Näiteks: https://server.minu:8787 Kasutajaprofiil jututoas - + Change avatar globally. Muuda oma tunnuspilti kõikjal. @@ -2223,7 +2400,7 @@ Näiteks: https://server.minu:8787 Muuda oma tunnuspilti vaid selles jututoas. - + Change display name globally. Muuda oma kuvatavat nime kõikjal. @@ -2233,7 +2410,7 @@ Näiteks: https://server.minu:8787 Muuda oma kuvatavat nime vaid selles jututoas. - + Room: %1 Jututuba: %1 @@ -2243,18 +2420,18 @@ Näiteks: https://server.minu:8787 See kasutajaprofiil on vaid selle jututoa kohane. Kasutaja kuvatav nimi ja tunnuspilt võivad muudes jutubades olla teistsugused. - + Open the global profile for this user. Vaata selle kasutaja üldist profiili. - - + + Verify Verifitseeri - + Start a private chat. Alusta privaatset vestlust. @@ -2269,12 +2446,42 @@ Näiteks: https://server.minu:8787 Sea kasutajale suhtluskeeld. - + + Refresh device list. + + + + + Sign out this device. + + + + + Change device name. + + + + + Last seen %1 from %2 + + + + Unverify Võta verifitseerimine tagasi - + + Sign out device %1 + + + + + You signed out this device. + + + + Select an avatar Vali tunnuspilt @@ -2297,8 +2504,8 @@ Näiteks: https://server.minu:8787 UserSettings - - + + Default Vaikimisi @@ -2306,7 +2513,7 @@ Näiteks: https://server.minu:8787 UserSettingsPage - + Minimize to tray Vähenda tegumiribale @@ -2316,22 +2523,22 @@ Näiteks: https://server.minu:8787 Käivita tegumiribalt - + Group's sidebar Rühmade küljepaan - + Circular Avatars Ümmargused tunnuspildid - + profile: %1 Profiil: %1 - + Default Vaikimisi @@ -2356,7 +2563,7 @@ Näiteks: https://server.minu:8787 ALLALAADIMISED - + Keep the application running in the background after closing the client window. Peale akna sulgemist jäta rakendus taustal tööle. @@ -2372,6 +2579,16 @@ OFF - square, ON - Circle. Muuda vestlustes kuvatavate tunnuspiltide kuju. Väljalülitatuna - ruut, sisselülitatuna - ümmargune. + + + Use identicons + + + + + Display an identicon instead of a letter when a user has not set an avatar. + + Show a column containing groups and tags next to the room list. @@ -2402,7 +2619,7 @@ be blurred. siis ajajoone vaade hägustub. - + Privacy screen timeout (in seconds [0 - 3600]) Viivitus privaatsussirmi sisselülitamisel (sekundites [0 - 3600]) @@ -2462,7 +2679,7 @@ Kui see valik on välja lülitatud, siis jututoad järjestatakse viimati saanunu Kui see valik on sisse lülitatud, siis teavitustega jututoad (pisike ümmargune numbrifa ikoon) järjestatakse esimesena. Sinu poolt summutatud jututoad järjestatakse ikkagi ajatempli alusel, sest sa ei pea neid teistega võrreldes piisavalt tähtsaks. - + Read receipts Lugemisteatised @@ -2474,7 +2691,7 @@ Status is displayed next to timestamps. Lugemise olekut kuvatakse ajatempli kõrval. - + Send messages as Markdown Saada sõnumid Markdown-vormindusena @@ -2563,7 +2780,7 @@ See tavaliselt tähendab, et rakenduse ikoon tegumiribal annab mingit sorti anim Luba krüptitud võtmete varunduseks laadida sõnumite krüptovõtmeid sinu serverisse või sinu serverist. - + Enable online key backup Võta kasutusele krüptovõtmete varundus võrgus @@ -2573,7 +2790,7 @@ See tavaliselt tähendab, et rakenduse ikoon tegumiribal annab mingit sorti anim Seni kuni sümmetriline krüptovõtmete varundamine pole teostatav, siis Nheko arendajad ei soovita krüptovõtmeid võrgus salvestada. Kas ikkagi jätkame? - + CACHED PUHVERDATUD @@ -2583,7 +2800,7 @@ See tavaliselt tähendab, et rakenduse ikoon tegumiribal annab mingit sorti anim PUHVERDAMATA - + Scale factor Mastaabitegur @@ -2658,7 +2875,7 @@ See tavaliselt tähendab, et rakenduse ikoon tegumiribal annab mingit sorti anim Seadme sõrmejälg - + Session Keys Sessioonivõtmed @@ -2678,17 +2895,17 @@ See tavaliselt tähendab, et rakenduse ikoon tegumiribal annab mingit sorti anim KRÜPTIMINE - + GENERAL ÜLDISED SEADISTUSED - + INTERFACE LIIDES - + Plays media like GIFs or WEBPs only when explicitly hovering over them. Esitame liikuvaid GIF ja WEBP pilte vaid siis, kui kursor on pildi kohal. @@ -2728,7 +2945,7 @@ See tavaliselt tähendab, et rakenduse ikoon tegumiribal annab mingit sorti anim Teiste kasutajate verifitseerimiseks mõeldud võti. Kui see võti on puhverdatud, siis kasutaja verifitseerimine tähendab ka kõikide tema seadmete verifitseerimist. - + Self signing key Omatunnustusvõti @@ -2758,14 +2975,14 @@ See tavaliselt tähendab, et rakenduse ikoon tegumiribal annab mingit sorti anim Kõik failid (*) - + Open Sessions File Ava sessioonide fail - + @@ -2773,19 +2990,19 @@ See tavaliselt tähendab, et rakenduse ikoon tegumiribal annab mingit sorti anim Viga - - + + File Password Faili salasõna - + Enter the passphrase to decrypt the file: Faili dekrüptimiseks sisesta salafraas: - + The password cannot be empty Salasõna ei saa olla tühi @@ -2800,6 +3017,14 @@ See tavaliselt tähendab, et rakenduse ikoon tegumiribal annab mingit sorti anim Fail, kuhu salvestad eksporditavad sessiooni krüptovõtmed + + VerificationManager + + + No encrypted private chat found with this user. Create an encrypted private chat with this user and try again. + Ühtegi krüptitud vestlust selle kasutajaga ei leidunud. Palun loo temaga krüptitud vestlus ja proovi uuesti. + + Waiting @@ -2854,7 +3079,7 @@ See tavaliselt tähendab, et rakenduse ikoon tegumiribal annab mingit sorti anim descriptiveTime - + Yesterday Eile @@ -2925,37 +3150,6 @@ See tavaliselt tähendab, et rakenduse ikoon tegumiribal annab mingit sorti anim Ava kasutaja registreerimise tagavaravariant, läbi kõik sammud ja kinnita seda, kui kõik valmis on. - - dialogs::JoinRoom - - - Join - Liitu - - - - Cancel - Tühista - - - - Room ID or alias - Jututoa tunnus või alias - - - - dialogs::LeaveRoom - - - Cancel - Tühista - - - - Are you sure you want to leave? - Kas sa oled kindel, et soovid lahkuda? - - dialogs::Logout @@ -3022,47 +3216,47 @@ Meedia suurus: %2 %1 saatis helifaili - + You sent an image Sa saatsid pildi - + %1 sent an image %1 saatis pildi - + You sent a file Sa saatsid faili - + %1 sent a file %1 saatis faili - + You sent a video Sa saatsid video - + %1 sent a video %1 saatis video - + You sent a sticker Sa saatsid kleepsu - + %1 sent a sticker %1 saatis kleepsu - + You sent a notification Sa saatsid teavituse @@ -3077,7 +3271,7 @@ Meedia suurus: %2 Sina: %1 - + %1: %2 %1: %2 @@ -3097,27 +3291,27 @@ Meedia suurus: %2 Sa helistasid - + %1 placed a call %1 helistas - + You answered a call Sa vastasid kõnele - + %1 answered a call %1 vastas kõnele - + You ended a call Sa lõpetasid kõne - + %1 ended a call %1 lõpetas kõne @@ -3125,7 +3319,7 @@ Meedia suurus: %2 utils - + Unknown Message Type Tundmatu sõnumitüüp diff --git a/resources/langs/nheko_fi.ts b/resources/langs/nheko_fi.ts index c269fa36..2fb5221d 100644 --- a/resources/langs/nheko_fi.ts +++ b/resources/langs/nheko_fi.ts @@ -4,7 +4,7 @@ ActiveCallBar - + Calling... Soitetaan... @@ -56,7 +56,7 @@ CallInvite - + Video Call Videopuhelu @@ -74,7 +74,7 @@ CallInviteBar - + Video Call Videopuhelu @@ -117,7 +117,7 @@ CallManager - + Entire screen Koko näyttö @@ -125,23 +125,23 @@ ChatPage - + Failed to invite user: %1 Käyttäjää %1 ei onnistuttu kutsumaan - + Invited user: %1 Kutsuttu käyttäjä: %1 - + Migrating the cache to the current version failed. This can have different reasons. Please open an issue and try to use an older version in the mean time. Alternatively you can try deleting the cache manually. Välimuistin tuominen nykyiseen versioon epäonnistui. Tällä voi olla eri syitä. Luo vikailmoitus ja yritä sillä aikaa käyttää vanhempaa versiota. Voit myös vaihtoehtoisesti koettaa tyhjentää välimuistin käsin. - + Confirm join Vahvista liittyminen @@ -151,23 +151,23 @@ Haluatko todella liittyä huoneeseen %1? - + Room %1 created. Huone %1 luotu. - - + + Confirm invite Vahvista kutsu - + Do you really want to invite %1 (%2)? Haluatko kutsua %1 (%2)? - + Failed to invite %1 to %2: %3 Epäonnistuttiin kutsuminen %1 huoneeseen %2:%3 @@ -182,7 +182,7 @@ Haluatko potkia %1 (%2)? - + Kicked user: %1 Potkittiin käyttäjä: %1 @@ -197,7 +197,7 @@ Haluatko antaa porttikiellon käyttäjälle %1 (%2)? - + Failed to ban %1 in %2: %3 Ei onnistuttu antamaan porttikieltoa käyttäjälle %1 huoneessa %2:%3 @@ -217,7 +217,7 @@ Haluatko purkaa porttikiellon käyttäjältä %1 (%2)? - + Failed to unban %1 in %2: %3 Ei onnistuttu purkamaan porttikieltoa käyttäjältä %1 huoneessa %2: %3 @@ -227,12 +227,12 @@ Purettiin porttikielto käyttäjältä %1 - + Do you really want to start a private chat with %1? Haluatko luoda yksityisen keskustelun käyttäjän %1 kanssa? - + Cache migration failed! Välimuistin siirto epäonnistui! @@ -259,23 +259,23 @@ Tallennettujen tietojen palauttaminen epäonnistui. Ole hyvä ja kirjaudu sisään uudelleen. - + Failed to setup encryption keys. Server response: %1 %2. Please try again later. Salausavainten lähetys epäonnistui. Palvelimen vastaus: %1 %2. Ole hyvä ja yritä uudelleen myöhemmin. - - + + Please try to login again: %1 Ole hyvä ja yritä kirjautua sisään uudelleen: %1 - + Failed to join room: %1 Huoneeseen liittyminen epäonnistui: %1 - + You joined the room Sinä liityit huoneeseen @@ -285,17 +285,17 @@ Kutsua ei onnistuttu poistamaan: %1 - + Room creation failed: %1 Huoneen luominen epäonnistui: %1 - + Failed to leave room: %1 Huoneesta poistuminen epäonnistui: %1 - + Failed to kick %1 from %2: %3 Ei onnistuttu potkimaan käyttäjää %1 huoneesta %2: %3 @@ -354,7 +354,7 @@ CrossSigningSecrets - + Decrypt secrets Salaisuuksien salauksen purku @@ -364,12 +364,12 @@ Anna palauttamisavain tai salasana purkaaksesi salaisuuksiesi salaus: - + Enter your recovery key or passphrase called %1 to decrypt your secrets: Anna palautusavaimesi tai salasanasi nimeltä %1 purkaaksesi salaisuuksien salauksen: - + Decryption failed Salauksen purku epäonnistui @@ -581,17 +581,26 @@ - Device verification timed out. Aikakatkaisu laitteen vahvistuksessa. - + Other party canceled the verification. Toinen osapuoli perui vahvistuksen. - + + Verification messages received out of order! + + + + + Unknown verification error. + + + + Close Sulje @@ -612,7 +621,7 @@ Muokataan kuvapakkausta - + Add images Lisää kuvia @@ -622,7 +631,7 @@ Tarrat (*.png *.webp *.gif *.jpg *.jpeg) - + State key TIla-avain @@ -638,13 +647,13 @@ - + Use as Emoji Käytä emojina - - + + Use as Sticker Käytä tarrana @@ -697,7 +706,7 @@ Uusi huonepakkaus - + Private pack Yksityinen pakkaus @@ -712,7 +721,7 @@ Kaikkialla käytössä oleva pakkaus - + Enable globally Salli käytettäväksi kaikkialla @@ -735,7 +744,7 @@ InputBar - + Select a file Valitse tiedosto @@ -745,7 +754,7 @@ Kaikki Tiedostot (*) - + Failed to upload media. Please try again. Mediaa ei onnistuttu lataamaan. Yritä uudelleen. @@ -753,7 +762,7 @@ InviteDialog - + Invite users to %1 Kutsu käyttäjiä %1 @@ -784,6 +793,32 @@ Peruuta + + JoinRoomDialog + + + Join room + + + + + Room ID or alias + Huoneen tunnus tai osoite + + + + LeaveRoomDialog + + + Leave room + Poistu huoneesta + + + + Are you sure you want to leave? + Oletko varma, että haluat poistua? + + LoginPage @@ -850,25 +885,25 @@ Esimerkki: https://server.my:8787 KIRJAUDU - + - + You have entered an invalid Matrix ID e.g @joe:matrix.org Väärä Matrix-tunnus. Esim. @joe:matrix.org - + Autodiscovery failed. Received malformed response. Palvelimen tietojen hakeminen epäonnistui: virheellinen vastaus. - + Autodiscovery failed. Unknown error when requesting .well-known. Palvelimen tietojen hakeminen epäonnistui: tuntematon virhe hakiessa .well-known -tiedostoa. - + The required endpoints were not found. Possibly not a Matrix server. Vaadittuja päätepisteitä ei löydetty. Mahdollisesti ei Matrix-palvelin. @@ -878,26 +913,44 @@ Esimerkki: https://server.my:8787 Vastaanotettiin virheellinen vastaus. Varmista, että kotipalvelimen osoite on pätevä. - + An unknown error occured. Make sure the homeserver domain is valid. Tapahtui tuntematon virhe. Varmista, että kotipalvelimen osoite on pätevä. - + SSO LOGIN SSO-kirjautuminen - + Empty password Tyhjä salasana - + SSO login failed SSO-kirjautuminen epäonnistui + + LogoutDialog + + + Log out + + + + + A call is in progress. Log out? + + + + + Are you sure you want to log out? + + + MessageDelegate @@ -1023,7 +1076,7 @@ Esimerkki: https://server.my:8787 MessageView - + Edit Muokkaa @@ -1043,7 +1096,7 @@ Esimerkki: https://server.my:8787 Asetukset - + &Copy &Kopioi @@ -1133,7 +1186,12 @@ Esimerkki: https://server.my:8787 Otettiin vastaan vahvistuspyyntö - + + To allow other users to see, which of your devices actually belong to you, you can verify them. This also allows key backup to work automatically. Verify an unverified device now? (Please make sure you have one of those devices available.) + + + + To allow other users to see, which of your devices actually belong to you, you can verify them. This also allows key backup to work automatically. Verify %1 now? Voit vahvistaa laitteesi, jotta sallit muiden nähdä, mitkä niistä oikeasti kuuluvat sinulle. Tämä myös mahdollistaa avaimen varmuuskopioinnin toiminnnan automaattisesti. Vahvistetaanko %1 nyt? @@ -1182,37 +1240,25 @@ Esimerkki: https://server.my:8787 NotificationWarning - You will be pinging the whole room - Hälytät koko huonetta + You are about to notify the whole room + NotificationsManager - - + + %1 sent an encrypted message %1 lähetti salatun viestin - - * %1 %2 - Format an emote message in a notification, %1 is the sender, %2 the message - * %1 %2 - - - + %1 replied: %2 Format a reply in a notification. %1 is the sender, %2 the message %1 vastasi: %2 - - - %1: %2 - Format a normal message in a notification. %1 is the sender, %2 the message - %1: %2 - @@ -1243,7 +1289,7 @@ Esimerkki: https://server.my:8787 Mikrofonia ei löydy. - + Voice Ääni @@ -1274,7 +1320,7 @@ Esimerkki: https://server.my:8787 QCoreApplication - + Create a unique profile, which allows you to log into several accounts at the same time and start multiple instances of nheko. Luo uniikki profili, joka mahdollistaa kirjautumisen usealle tilille samanaikaisesti ja useamman nheko-instanssin aloittamisen. @@ -1292,7 +1338,7 @@ Esimerkki: https://server.my:8787 ReadReceipts - + Read receipts Lukukuittaukset @@ -1300,7 +1346,7 @@ Esimerkki: https://server.my:8787 ReadReceiptsModel - + Yesterday, %1 Eilen, &1 @@ -1308,18 +1354,18 @@ Esimerkki: https://server.my:8787 RegisterPage - + Username Käyttäjänimi - + The username must not be empty, and must contain only the characters a-z, 0-9, ., _, =, -, and /. Käyttäjätunnus ei saa olla tyhjä, ja se saa sisältää vain merkkejä a-z, 0-9, ., _, =, - ja /. - + Password Salasana @@ -1349,37 +1395,22 @@ Esimerkki: https://server.my:8787 REKISTERÖIDY - - No supported registration flows! - Ei tuettuja rekisteröintejä! - - - - Registration token - Rekisteröitymispoletti - - - - Please enter a valid registration token. - Anna kelvollinen rekisteröitymispoletti. - - - + Autodiscovery failed. Received malformed response. Palvelimen tietojen hakeminen epäonnistui: virheellinen vastaus. - + Autodiscovery failed. Unknown error when requesting .well-known. Palvelimen tietojen hakeminen epäonnistui: tuntematon virhe hakiessa .well-known -tiedostoa. - + The required endpoints were not found. Possibly not a Matrix server. Vaadittuja päätepisteitä ei löydetty. Mahdollisesti ei Matrix-palvelin. - + Received malformed response. Make sure the homeserver domain is valid. Vastaanotettiin virheellinen vastaus. Varmista, että kotipalvelimen osoite on pätevä. @@ -1389,7 +1420,7 @@ Esimerkki: https://server.my:8787 Tapahtui tuntematon virhe. Varmista, että kotipalvelimen osoite on pätevä. - + Password is not long enough (min 8 chars) Salasana ei ole tarpeeksi pitkä (vähintään 8 merkkiä) @@ -1420,20 +1451,25 @@ Esimerkki: https://server.my:8787 RoomDirectory - + Explore Public Rooms Tutki julkisia huoneita - + Search for public rooms Etsi julkisia huoneita + + + Choose custom homeserver + + RoomInfo - + no version stored ei tallennettua versiota @@ -1450,16 +1486,6 @@ Esimerkki: https://server.my:8787 Enter the tag you want to use: Kirjoita tagi jota haluat käyttää: - - - Leave Room - Poistu huoneesta - - - - Are you sure you want to leave this room? - Oletko varma, että haluat poistua tästä huoneesta? - Leave room @@ -1491,7 +1517,7 @@ Esimerkki: https://server.my:8787 Luo uusi tagi... - + Status Message Tilapäivitys @@ -1516,7 +1542,30 @@ Esimerkki: https://server.my:8787 Kirjaudu ulos - + + Encryption not set up + Cross-signing setup has not run yet. + + + + + Unverified login + The user just signed in with this device and hasn't verified their master key. + + + + + Please verify your other devices + There are unverified devices signed in to this account. + + + + + Close + Sulje + + + Start a new chat Aloita uusi keskustelu @@ -1544,12 +1593,12 @@ Esimerkki: https://server.my:8787 RoomMembers - + Members of %1 &1 jäsenet - + %n people in %1 Summary above list of members @@ -1586,12 +1635,12 @@ Esimerkki: https://server.my:8787 RoomSettings - + Room Settings Huoneen asetukset - + %1 member(s) %1 jäsentä @@ -1697,12 +1746,12 @@ Esimerkki: https://server.my:8787 Huoneen versio - + Failed to enable encryption: %1 Salauksen aktivointi epäonnistui: %1 - + Select an avatar Valitse profiilikuva @@ -1722,8 +1771,8 @@ Esimerkki: https://server.my:8787 Virhe lukiessa tiedostoa: %1 - - + + Failed to upload image: %s Kuvan lähetys epäonnistui: %s @@ -1731,21 +1780,49 @@ Esimerkki: https://server.my:8787 RoomlistModel - + Pending invite. Kutsua odotetaan. - + Previewing this room Esikatsellaan tätä huonetta - + No preview available Esikatselu ei saatavilla + + Root + + + Please enter your login password to continue: + + + + + Please enter a valid email address to continue: + + + + + Please enter a valid phone number to continue: + + + + + Please enter the token, which has been sent to you: + + + + + Wait for the confirmation link to arrive, then continue. + + + ScreenShare @@ -1803,20 +1880,98 @@ Esimerkki: https://server.my:8787 SecretStorage - + Failed to connect to secret storage Salattuun tallennustilaan ei saatu yhteyttä - Nheko could not connect to the secure storage to save encryption secrets to. This can have multiple reasons. Check if your D-Bus service is running and you have configured a service like KWallet, Gnome Secrets or the equivalent for your platform. If you are having trouble, feel free to open an issue here: https://github.com/Nheko-Reborn/nheko/issues - Nheko ei pystynyt yhdistämään salattuun tallennustilaan tallentamaan salaukseen kuuluvia salaisuuksia. Tämä voi johtua useasta syystä. Tarkista, onko D-Bus-palvelu käynnissä ja oletko määrittänyt alustallesi palvelun kuten KWallet, Gnome Secrets tai vastaavan. Jos sinulla on ongelmia, voit luoda vikailmoituksen täällä: https://github.com/Nheko-Reborn/nheko/issues + Nheko could not connect to the secure storage to save encryption secrets to. This can have multiple reasons. Check if your D-Bus service is running and you have configured a service like KWallet, Gnome Keyring, KeePassXC or the equivalent for your platform. If you are having trouble, feel free to open an issue here: https://github.com/Nheko-Reborn/nheko/issues + + + + + SelfVerificationCheck + + + This is your recovery key. You will need it to restore access to your encrypted messages and verification keys. Keep this safe. Don't share it with anyone and don't lose it! Do not pass go! Do not collect $200! + + + + + Encryption setup successfully + + + + + Failed to setup encryption: %1 + + + + + Setup Encryption + + + + + Hello and welcome to Matrix! +It seems like you are new. Before you can securely encrypt your messages, we need to setup a few small things. You can either press accept immediately or adjust a few basic options. We also try to explain a few of the basics. You can skip those parts, but they might prove to be helpful! + + + + + Activate Encryption + + + + + It seems like you have encryption already configured for this account. To be able to access your encrypted messages and make this device appear as trusted, you can either verify an existing device or (if you have one) enter your recovery passphrase. Please select one of the options below. +If you choose verify, you need to have the other device available. If you choose "enter passphrase", you will need your recovery key or passphrase. If you click cancel, you can choose to verify yourself at a later point. + + + + + verify + + + + + enter passphrase + + + + + SelfVerificationStatus + + + Failed to create keys for cross-signing! + + + + + Failed to create keys for online key backup! + + + + + Failed to create keys secure server side secret storage! + + + + + Encryption Setup + + + + + Encryption setup failed: %1 + SingleImagePackModel - + Failed to update image pack: %1 Kuvapakkausta %1 ei onnistuttu päivittämään @@ -1832,7 +1987,7 @@ Esimerkki: https://server.my:8787 Kuvaa %1 ei onnistuttu avaamaan - + Failed to upload image: %1 Kuvan lähetys epäonnistui: %s @@ -1889,18 +2044,18 @@ Esimerkki: https://server.my:8787 TimelineModel - + Message redaction failed: %1 Viestin muokkaus epäonnistui: %1 - + Failed to encrypt event, sending aborted! Tapahtuman salaus epäonnistui, lähetys keskeytetään! - + Save image Tallenna kuva @@ -1920,7 +2075,7 @@ Esimerkki: https://server.my:8787 Tallenna tiedosto - + %1 and %2 are typing. Multiple users are typing. First argument is a comma separated list of potentially multiple users. Second argument is the last user of that list. (If only one user is typing, %1 is empty. You should still use it in your string though to silence Qt warnings.) @@ -1929,7 +2084,7 @@ Esimerkki: https://server.my:8787 - + %1 opened the room to the public. %1 avasi huoneen julkiseksi. @@ -1969,12 +2124,12 @@ Esimerkki: https://server.my:8787 %1 asetti huoneen historian näkyväksi jäsenille tästä lähtien. - + %1 set the room history visible to members since they were invited. %1 asetti huoneen historian näkyväksi jäsenille kutsumisesta lähtien. - + %1 set the room history visible to members since they joined the room. %1 asetti huoneen historian näkyväksi jäsenille huoneeseen liittymisen jälkeen. @@ -1984,12 +2139,12 @@ Esimerkki: https://server.my:8787 %1 on muuttanut huoneen lupia. - + %1 was invited. &1 kutsuttiin. - + %1 changed their avatar. %1 muutti avatariaan. @@ -2004,12 +2159,12 @@ Esimerkki: https://server.my:8787 %1 liittyi. - + %1 joined via authorisation from %2's server. %1 liittyi käyttäjän %2 palvelimen suomalla vahvistuksella. - + %1 rejected their invite. %1 hylkäsi kutsunsa. @@ -2039,27 +2194,27 @@ Esimerkki: https://server.my:8787 Käyttäjälle %1 annettiin porttikielto. - + Reason: %1 Syy: %1 - + %1 redacted their knock. %1 perui koputuksensa. - + You joined this room. Sinä liityit tähän huoneeseen. - + %1 has changed their avatar and changed their display name to %2. %1 vaihtoi avatariaan ja vaihtoi näyttönimekseen %2. - + %1 has changed their display name to %2. %1 vaihtoi näyttönimekseen %2. @@ -2096,7 +2251,12 @@ Esimerkki: https://server.my:8787 Ei avointa huonetta - + + No preview available + Esikatselu ei saatavilla + + + %1 member(s) %1 jäsentä @@ -2121,28 +2281,20 @@ Esimerkki: https://server.my:8787 Takaisin huonelistaan - - TimelineViewManager - - - No encrypted private chat found with this user. Create an encrypted private chat with this user and try again. - Salattua keskustelua ei löydetty tälle käyttäjälle. Luo salattu yksityiskeskustelu tämän käyttäjän kanssa ja yritä uudestaan. - - TopBar - + Back to room list Takaisin huonelistaan - + No room selected Ei valittua huonetta - + This room is not encrypted! Tämä huone ei ole salattu! @@ -2153,8 +2305,8 @@ Esimerkki: https://server.my:8787 - This rooms contain verified devices and devices which have never changed their master key. - Tämä huone sisältää vahvistettuja laitteita ja laitteita, jotka eivät ole koskaan vaihtaneet pääavainta. + This room contains verified devices and devices which have never changed their master key. + @@ -2200,10 +2352,35 @@ Esimerkki: https://server.my:8787 Lopeta + + UIA + + + No available registration flows! + + + + + + + Registration aborted + + + + + Please enter a valid registration token. + Anna kelvollinen rekisteröitymispoletti. + + + + Invalid token + + + UserProfile - + Global User Profile Yleinen käyttäjäprofiili @@ -2213,7 +2390,7 @@ Esimerkki: https://server.my:8787 Huoneen käyttäjäprofiili - + Change avatar globally. Vaihda avataria kaikkialla. @@ -2223,7 +2400,7 @@ Esimerkki: https://server.my:8787 Muuta avataria. Toimii vain tässä huoneessa. - + Change display name globally. Muuta näyttönimeä kaikkialla. @@ -2233,7 +2410,7 @@ Esimerkki: https://server.my:8787 Muuta näyttönimeä. Toimii vain tässä huoneessa. - + Room: %1 Huone: %1 @@ -2243,18 +2420,18 @@ Esimerkki: https://server.my:8787 Tämä on huoneelle erityinen profiili. Käyttäjän nimi ja avatar voivat erota niiden kaikkialla käytössä olevista versioista. - + Open the global profile for this user. Avaa tämän käyttäjän yleinen profiili. - - + + Verify Vahvista - + Start a private chat. Aloita yksityinen keskustelu. @@ -2269,12 +2446,42 @@ Esimerkki: https://server.my:8787 Anna käyttäjälle porttikielto. - + + Refresh device list. + + + + + Sign out this device. + + + + + Change device name. + + + + + Last seen %1 from %2 + + + + Unverify Peru vahvistus - + + Sign out device %1 + + + + + You signed out this device. + + + + Select an avatar Valitse profiilikuva @@ -2297,8 +2504,8 @@ Esimerkki: https://server.my:8787 UserSettings - - + + Default Oletus @@ -2306,7 +2513,7 @@ Esimerkki: https://server.my:8787 UserSettingsPage - + Minimize to tray Pienennä ilmoitusalueelle @@ -2316,22 +2523,22 @@ Esimerkki: https://server.my:8787 Aloita ilmoitusalueella - + Group's sidebar Ryhmäsivupalkki - + Circular Avatars Pyöreät avatarit - + profile: %1 profiili: %1 - + Default Oletus @@ -2356,7 +2563,7 @@ Esimerkki: https://server.my:8787 LATAA - + Keep the application running in the background after closing the client window. Anna sovelluksen pyöriä taustalla asiakasohjelman ikkunan sulkemisen jälkeen. @@ -2372,6 +2579,16 @@ OFF - square, ON - Circle. Muuta käyttäjien avatarien ulkonäköä keskusteluissa. POIS PÄÄLTÄ - neliö, PÄÄLLÄ - pyöreä. + + + Use identicons + + + + + Display an identicon instead of a letter when a user has not set an avatar. + + Show a column containing groups and tags next to the room list. @@ -2402,7 +2619,7 @@ be blurred. sumennetaan. - + Privacy screen timeout (in seconds [0 - 3600]) Yksityisyysnäkymän aikakatkaisu (sekunneissa [0-3600]) @@ -2462,7 +2679,7 @@ Jos tämä on poissa päältä, lista huoneista lajitellaan vain huoneen viimeis Jos tämä on päällä, huoneet, joissa ilmoitukset ovat päällä (pieni ympyrä, jonka sisässä on numero), lajitellaan päällimmäisiksi. Mykistämäsi huoneet lajitellaan aikaleiman mukaan, koska et nähtävästi pidä niitä yhtä tärkeinä kuin muita huoneita. - + Read receipts Lukukuittaukset @@ -2474,7 +2691,7 @@ Status is displayed next to timestamps. Tila näytetään aikaleimojen vieressä. - + Send messages as Markdown Lähetä viestit Markdownina @@ -2563,7 +2780,7 @@ Tämä yleensä saa sovelluksen kuvakkeen liikkumaan jollain tapaa tehtäväpalk - + Enable online key backup Salli avaimen varmuuskopiointi verkkoon @@ -2573,7 +2790,7 @@ Tämä yleensä saa sovelluksen kuvakkeen liikkumaan jollain tapaa tehtäväpalk Nhekon tekijät eivät suosittele avaimen varmuuskopiointia verkkoon kunnes avaimen symmetrinen varmuuskopiointi verkkoon on saatavilla. Sallitaanko se kuitenkin? - + CACHED VÄLIMUISTISSA @@ -2583,7 +2800,7 @@ Tämä yleensä saa sovelluksen kuvakkeen liikkumaan jollain tapaa tehtäväpalk EI VÄLIMUISTISSA - + Scale factor Mittakerroin @@ -2658,7 +2875,7 @@ Tämä yleensä saa sovelluksen kuvakkeen liikkumaan jollain tapaa tehtäväpalk Laitteen sormenjälki - + Session Keys Istunnon avaimet @@ -2678,17 +2895,17 @@ Tämä yleensä saa sovelluksen kuvakkeen liikkumaan jollain tapaa tehtäväpalk SALAUS - + GENERAL YLEISET ASETUKSET - + INTERFACE KÄYTTÖLIITTYMÄ - + Plays media like GIFs or WEBPs only when explicitly hovering over them. Soittaa mediaa kuten GIF- ja WEBP-tiedostoja vain kun kursori on niiden kohdalla. @@ -2728,7 +2945,7 @@ Tämä yleensä saa sovelluksen kuvakkeen liikkumaan jollain tapaa tehtäväpalk Avain vahvistamaan muita käyttäjiä. Jos se on välimuistissa, käyttäjän varmistaminen varmistaa hänen kaikki laitteensa. - + Self signing key Itsensä allekirjoittava avain @@ -2758,14 +2975,14 @@ Tämä yleensä saa sovelluksen kuvakkeen liikkumaan jollain tapaa tehtäväpalk Kaikki Tiedostot (*) - + Open Sessions File Avaa Istuntoavaintiedosto - + @@ -2773,19 +2990,19 @@ Tämä yleensä saa sovelluksen kuvakkeen liikkumaan jollain tapaa tehtäväpalk Virhe - - + + File Password Tiedoston Salasana - + Enter the passphrase to decrypt the file: Anna salasana tiedoston salauksen purkamiseksi: - + The password cannot be empty Salasana ei voi olla tyhjä @@ -2800,6 +3017,14 @@ Tämä yleensä saa sovelluksen kuvakkeen liikkumaan jollain tapaa tehtäväpalk Tiedosto, johon viedyt istuntoavaimet tallennetaan + + VerificationManager + + + No encrypted private chat found with this user. Create an encrypted private chat with this user and try again. + Salattua keskustelua ei löydetty tälle käyttäjälle. Luo salattu yksityiskeskustelu tämän käyttäjän kanssa ja yritä uudestaan. + + Waiting @@ -2854,7 +3079,7 @@ Tämä yleensä saa sovelluksen kuvakkeen liikkumaan jollain tapaa tehtäväpalk descriptiveTime - + Yesterday Eilen @@ -2925,37 +3150,6 @@ Tämä yleensä saa sovelluksen kuvakkeen liikkumaan jollain tapaa tehtäväpalk Avaa varajärjestely, seuraa ohjeita ja vahvista kun olet saanut ne valmiiksi. - - dialogs::JoinRoom - - - Join - Liity - - - - Cancel - Peruuta - - - - Room ID or alias - Huoneen tunnus tai osoite - - - - dialogs::LeaveRoom - - - Cancel - Peruuta - - - - Are you sure you want to leave? - Oletko varma, että haluat poistua? - - dialogs::Logout @@ -3022,47 +3216,47 @@ Median koko: %2 %1 lähetti äänileikkeen - + You sent an image Lähetit kuvan - + %1 sent an image %1 lähetti kuvan - + You sent a file Lähetit tiedoston - + %1 sent a file %1 lähetti tiedoston - + You sent a video Lähetit videotiedoston - + %1 sent a video %1 lähetti videotiedoston - + You sent a sticker Lähetit tarran - + %1 sent a sticker %1 lähetti tarran - + You sent a notification Lähetit ilmoituksen @@ -3077,7 +3271,7 @@ Median koko: %2 Sinä: %1 - + %1: %2 %1: %2 @@ -3097,27 +3291,27 @@ Median koko: %2 Soitit puhelun - + %1 placed a call %1 soitti puhelun - + You answered a call Vastasit puheluun - + %1 answered a call %1 vastasi puheluun - + You ended a call Lopetit puhelun - + %1 ended a call %1 lopetti puhelun @@ -3125,7 +3319,7 @@ Median koko: %2 utils - + Unknown Message Type Tuntematon viestityyppi diff --git a/resources/langs/nheko_fr.ts b/resources/langs/nheko_fr.ts index 118cd259..f57f8984 100644 --- a/resources/langs/nheko_fr.ts +++ b/resources/langs/nheko_fr.ts @@ -4,7 +4,7 @@ ActiveCallBar - + Calling... Appel en cours… @@ -56,7 +56,7 @@ CallInvite - + Video Call Appel vidéo @@ -74,7 +74,7 @@ CallInviteBar - + Video Call Appel vidéo @@ -96,12 +96,12 @@ Unknown microphone: %1 - Microphone inconnu : %1 + Microphone inconnu : %1 Unknown camera: %1 - Caméra inconnue : %1 + Caméra inconnue : %1 @@ -117,7 +117,7 @@ CallManager - + Entire screen Tout l'écran @@ -125,23 +125,23 @@ ChatPage - + Failed to invite user: %1 Échec lors de l'invitation de %1 - + Invited user: %1 Utilisateur %1 invité(e) - + Migrating the cache to the current version failed. This can have different reasons. Please open an issue and try to use an older version in the mean time. Alternatively you can try deleting the cache manually. La migration du cache vers la version actuelle a échoué. Cela peut arriver pour différentes raisons. Signalez le problème et essayez d'utiliser une ancienne version en attendant. Vous pouvez également supprimer le cache manuellement. - + Confirm join Confirmez la participation @@ -151,25 +151,25 @@ Voulez-vous vraiment rejoindre %1 ? - + Room %1 created. Salon %1 créé. - - + + Confirm invite Confirmer l'invitation - + Do you really want to invite %1 (%2)? - Voulez-vous vraiment inviter %1 (%2) ? + Voulez-vous vraiment inviter %1 (%2) ? - + Failed to invite %1 to %2: %3 - Échec de l'invitation de %1 dans %2 : %3 + Échec de l'invitation de %1 dans %2 : %3 @@ -179,10 +179,10 @@ Do you really want to kick %1 (%2)? - Voulez-vous vraiment expulser %1 (%2) ? + Voulez-vous vraiment expulser %1 (%2) ? - + Kicked user: %1 L'utilisateur %1 a été expulsé. @@ -194,10 +194,10 @@ Do you really want to ban %1 (%2)? - Voulez-vous vraiment bannir %1 (%2) ? + Voulez-vous vraiment bannir %1 (%2) ? - + Failed to ban %1 in %2: %3 Échec du bannissement de %1 de %2 : %3 @@ -214,12 +214,12 @@ Do you really want to unban %1 (%2)? - Voulez-vous vraiment annuler le bannissement de %1 (%2) ? + Voulez-vous vraiment annuler le bannissement de %1 (%2) ? - + Failed to unban %1 in %2: %3 - Échec de l'annulation du bannissement de %1 dans %2 : %3 + Échec de l'annulation du bannissement de %1 dans %2 : %3 @@ -227,14 +227,14 @@ %1 n'est plus banni(e) - + Do you really want to start a private chat with %1? Voulez-vous vraiment commencer une discussion privée avec %1 ? - + Cache migration failed! - Échec de la migration du cache ! + Échec de la migration du cache ! @@ -259,45 +259,45 @@ Échec de la restauration des données sauvegardées. Veuillez vous reconnecter. - + Failed to setup encryption keys. Server response: %1 %2. Please try again later. - Échec de la configuration des clés de chiffrement. Réponse du serveur : %1 %2. Veuillez réessayer plus tard. + Échec de la configuration des clés de chiffrement. Réponse du serveur : %1 %2. Veuillez réessayer plus tard. - - + + Please try to login again: %1 - Veuillez re-tenter vous reconnecter : %1 + Veuillez re-tenter vous reconnecter : %1 - + Failed to join room: %1 - Impossible de rejoindre le salon : %1 + Impossible de rejoindre le salon : %1 - + You joined the room Vous avez rejoint le salon Failed to remove invite: %1 - Impossible de supprimer l'invitation : %1 + Impossible de supprimer l'invitation : %1 - + Room creation failed: %1 - Échec de la création du salon : %1 + Échec de la création du salon : %1 - + Failed to leave room: %1 - Impossible de quitter le salon : %1 + Impossible de quitter le salon : %1 - + Failed to kick %1 from %2: %3 - Échec de l'expulsion de %1 de %2  : %3 + Échec de l'expulsion de %1 de %2  : %3 @@ -354,22 +354,22 @@ CrossSigningSecrets - + Decrypt secrets Déchiffrer les secrets Enter your recovery key or passphrase to decrypt your secrets: - Entrez votre clé de récupération ou phrase de passe pour déchiffrer vos secrets : + Entrez votre clé de récupération ou phrase de passe pour déchiffrer vos secrets : - + Enter your recovery key or passphrase called %1 to decrypt your secrets: - Entrez votre clé de récupération ou votre phrase de passe nommée %1 pour déchiffrer vos secrets : + Entrez votre clé de récupération ou votre phrase de passe nommée %1 pour déchiffrer vos secrets : - + Decryption failed Échec du déchiffrement @@ -389,17 +389,17 @@ Please verify the following digits. You should see the same numbers on both sides. If they differ, please press 'They do not match!' to abort verification! - Veuillez vérifier les chiffres suivants. Vous devriez voir les mêmes chiffres des deux côtés. Si ceux-ci diffèrent, veuillez choisir « Ils sont différents ! » pour annuler la vérification ! + Veuillez vérifier les chiffres suivants. Vous devriez voir les mêmes chiffres des deux côtés. Si ceux-ci diffèrent, veuillez choisir « Ils sont différents ! » pour annuler la vérification ! They do not match! - Ils sont différents ! + Ils sont différents ! They match! - Ils sont identiques ! + Ils sont identiques ! @@ -483,17 +483,17 @@ Please verify the following emoji. You should see the same emoji on both sides. If they differ, please press 'They do not match!' to abort verification! - Veuillez vérifier les émoji suivants. Vous devriez voir les mêmes émoji des deux côtés. S'ils diffèrent, veuillez choisir « Ils sont différents ! » pour annuler la vérification ! + Veuillez vérifier les émoji suivants. Vous devriez voir les mêmes émoji des deux côtés. S'ils diffèrent, veuillez choisir « Ils sont différents ! » pour annuler la vérification ! They do not match! - Ils sont différents ! + Ils sont différents ! They match! - Ils sont identiques ! + Ils sont identiques ! @@ -544,7 +544,7 @@ This message is not encrypted! - Ce message n'est pas chiffré ! + Ce message n'est pas chiffré ! @@ -577,21 +577,30 @@ Key mismatch detected! - Clés non correspondantes détectées ! + Clés non correspondantes détectées ! - Device verification timed out. Délai dépassé pour la vérification de l'appareil. - + Other party canceled the verification. Le correspondant a annulé la vérification. - + + Verification messages received out of order! + + + + + Unknown verification error. + + + + Close Fermer @@ -612,7 +621,7 @@ Modification du paquet d'images - + Add images Ajouter des images @@ -622,7 +631,7 @@ Autocollants (*.png *.webp *.gif *.jpg *.jpeg) - + State key Clef d'état @@ -638,13 +647,13 @@ - + Use as Emoji Utiliser en tant qu'émoji - - + + Use as Sticker Utiliser en tant qu'autocollant @@ -697,7 +706,7 @@ Nouveau paquet de salle - + Private pack Paquet privé @@ -712,7 +721,7 @@ Paquet activé partout - + Enable globally Activer partout @@ -735,7 +744,7 @@ InputBar - + Select a file Sélectionnez un fichier @@ -745,7 +754,7 @@ Tous les types de fichiers (*) - + Failed to upload media. Please try again. Échec de l'envoi du média. Veuillez réessayer. @@ -753,7 +762,7 @@ InviteDialog - + Invite users to %1 Inviter des utilisateurs dans %1 @@ -784,6 +793,32 @@ Annuler + + JoinRoomDialog + + + Join room + + + + + Room ID or alias + Identifiant ou alias du salon + + + + LeaveRoomDialog + + + Leave room + Quitter le salon + + + + Are you sure you want to leave? + Êtes-vous sûr·e de vouloir quitter ? + + LoginPage @@ -802,9 +837,9 @@ You can also put your homeserver address there, if your server doesn't support .well-known lookup. Example: @user:server.my If Nheko fails to discover your homeserver, it will show you a field to enter the server manually. - Votre nom de connexion. Un mxid doit commencer par un « @ » suivi de l'identifiant. L'identifiant doit être suivi du nom de serveur, séparé de celui-ci par « : ». + Votre nom de connexion. Un mxid doit commencer par un « @ » suivi de l'identifiant. L'identifiant doit être suivi du nom de serveur, séparé de celui-ci par « : ». Vous pouvez également spécifier l'adresse de votre serveur ici, si votre serveur ne supporte pas l'identification .well-known. -Exemple : @utilisateur :monserveur.example.com +Exemple : @utilisateur :monserveur.example.com Si Nheko n'arrive pas à trouver votre serveur, il vous proposera de l'indiquer manuellement. @@ -842,7 +877,7 @@ Si Nheko n'arrive pas à trouver votre serveur, il vous proposera de l&apos The address that can be used to contact you homeservers client API. Example: https://server.my:8787 L'adresse qui peut être utilisée pour joindre l'API client de votre serveur. -Exemple : https ://monserveur.example.com:8787 +Exemple : https ://monserveur.example.com:8787 @@ -850,25 +885,25 @@ Exemple : https ://monserveur.example.com:8787 CONNEXION - + - + You have entered an invalid Matrix ID e.g @joe:matrix.org - Vous avez entré un identifiant Matrix invalide exemple correct : @moi:monserveur.example.com) + Vous avez entré un identifiant Matrix invalide exemple correct : @moi:monserveur.example.com) - + Autodiscovery failed. Received malformed response. Échec de la découverte automatique. Réponse mal formée reçue. - + Autodiscovery failed. Unknown error when requesting .well-known. Échec de la découverte automatique. Erreur inconnue lors de la demande de .well-known. - + The required endpoints were not found. Possibly not a Matrix server. Les endpoints requis n'ont pas été trouvés. Ce n'est peut-être pas un serveur Matrix. @@ -878,26 +913,44 @@ Exemple : https ://monserveur.example.com:8787 Réponse mal formée reçue. Vérifiez que le nom de domaine du serveur est valide. - + An unknown error occured. Make sure the homeserver domain is valid. Une erreur inconnue est survenue. Vérifiez que le nom de domaine du serveur est valide. - + SSO LOGIN CONNEXION SSO - + Empty password Mot de passe vide - + SSO login failed Échec de la connexion SSO + + LogoutDialog + + + Log out + + + + + A call is in progress. Log out? + + + + + Are you sure you want to log out? + + + MessageDelegate @@ -914,7 +967,7 @@ Exemple : https ://monserveur.example.com:8787 room name changed to: %1 - nom du salon changé en : %1 + nom du salon changé en : %1 @@ -924,7 +977,7 @@ Exemple : https ://monserveur.example.com:8787 topic changed to: %1 - sujet changé en : %1 + sujet changé en : %1 @@ -939,7 +992,7 @@ Exemple : https ://monserveur.example.com:8787 %1 created and configured room: %2 - %1 a créé et configuré le salon : %2 + %1 a créé et configuré le salon : %2 @@ -1023,7 +1076,7 @@ Exemple : https ://monserveur.example.com:8787 MessageView - + Edit Modifier @@ -1043,7 +1096,7 @@ Exemple : https ://monserveur.example.com:8787 Options - + &Copy &Copier @@ -1133,9 +1186,14 @@ Exemple : https ://monserveur.example.com:8787 Demande de vérification reçue - + + To allow other users to see, which of your devices actually belong to you, you can verify them. This also allows key backup to work automatically. Verify an unverified device now? (Please make sure you have one of those devices available.) + + + + To allow other users to see, which of your devices actually belong to you, you can verify them. This also allows key backup to work automatically. Verify %1 now? - Pour permettre aux autres utilisateurs de vérifier quels appareils de votre compte sont réellement les vôtres, vous pouvez les vérifier. Cela permet également à la sauvegarde des clés de fonctionner automatiquement. Vérifier %1 maintenant ? + Pour permettre aux autres utilisateurs de vérifier quels appareils de votre compte sont réellement les vôtres, vous pouvez les vérifier. Cela permet également à la sauvegarde des clés de fonctionner automatiquement. Vérifier %1 maintenant ? @@ -1182,37 +1240,25 @@ Exemple : https ://monserveur.example.com:8787 NotificationWarning - You will be pinging the whole room - Vous allez notifier tout le salon + You are about to notify the whole room + NotificationsManager - - + + %1 sent an encrypted message %1 a envoyé un message chiffré - - * %1 %2 - Format an emote message in a notification, %1 is the sender, %2 the message - * %1 %2 - - - + %1 replied: %2 Format a reply in a notification. %1 is the sender, %2 the message %1 a répondu : %2 - - - %1: %2 - Format a normal message in a notification. %1 is the sender, %2 the message - %1 : %2 - @@ -1235,7 +1281,7 @@ Exemple : https ://monserveur.example.com:8787 Place a call to %1? - Appeler %1 ? + Appeler %1 ? @@ -1243,7 +1289,7 @@ Exemple : https ://monserveur.example.com:8787 Pas de microphone trouvé. - + Voice Vocal @@ -1268,13 +1314,13 @@ Exemple : https ://monserveur.example.com:8787 unimplemented event: - Évènement non implémenté : + Évènement non implémenté : QCoreApplication - + Create a unique profile, which allows you to log into several accounts at the same time and start multiple instances of nheko. Créer un profil unique, vous permettant de vous connecter simultanément à plusieurs comptes et de lancer plusieurs instances de nheko. @@ -1292,7 +1338,7 @@ Exemple : https ://monserveur.example.com:8787 ReadReceipts - + Read receipts Accusés de lecture @@ -1300,7 +1346,7 @@ Exemple : https ://monserveur.example.com:8787 ReadReceiptsModel - + Yesterday, %1 Hier, %1 @@ -1308,18 +1354,18 @@ Exemple : https ://monserveur.example.com:8787 RegisterPage - + Username Nom d'utilisateur - + The username must not be empty, and must contain only the characters a-z, 0-9, ., _, =, -, and /. Le nom d'utilisateur ne doit pas être vide, et ne peut contenir que les caractères a-z, 0-9, ., _, =, -, et /. - + Password Mot de passe @@ -1349,37 +1395,22 @@ Exemple : https ://monserveur.example.com:8787 S'ENREGISTRER - - No supported registration flows! - Aucune méthode d'inscription supportée ! - - - - Registration token - Jeton d'enregistrement - - - - Please enter a valid registration token. - Veuillez entrer un jeton d'enregistrement valide. - - - + Autodiscovery failed. Received malformed response. Échec de la découverte automatique. Réponse mal formée reçue. - + Autodiscovery failed. Unknown error when requesting .well-known. Échec de la découverte automatique. Erreur inconnue lors de la demande de .well-known. - + The required endpoints were not found. Possibly not a Matrix server. Les endpoints requis n'ont pas été trouvés. Ce n'est peut-être pas un serveur Matrix. - + Received malformed response. Make sure the homeserver domain is valid. Réponse mal formée reçue. Vérifiez que le nom de domaine du serveur est valide. @@ -1389,7 +1420,7 @@ Exemple : https ://monserveur.example.com:8787 Une erreur inconnue est survenue. Vérifiez que le nom de domaine du serveur est valide. - + Password is not long enough (min 8 chars) Le mot de passe n'est pas assez long (8 caractères minimum) @@ -1420,20 +1451,25 @@ Exemple : https ://monserveur.example.com:8787 RoomDirectory - + Explore Public Rooms Explorer les salons publics - + Search for public rooms Rechercher des salons publics + + + Choose custom homeserver + + RoomInfo - + no version stored pas de version enregistrée @@ -1450,16 +1486,6 @@ Exemple : https ://monserveur.example.com:8787 Enter the tag you want to use: Entrez l'étiquette que vous voulez utiliser : - - - Leave Room - Quitter le salon - - - - Are you sure you want to leave this room? - Êtes-vous sûr de vouloir quitter ce salon ? - Leave room @@ -1468,7 +1494,7 @@ Exemple : https ://monserveur.example.com:8787 Tag room as: - Étiqueter le salon comme : + Étiqueter le salon comme : @@ -1491,7 +1517,7 @@ Exemple : https ://monserveur.example.com:8787 Créer une nouvelle étiquette… - + Status Message Message de statut @@ -1516,7 +1542,30 @@ Exemple : https ://monserveur.example.com:8787 Déconnexion - + + Encryption not set up + Cross-signing setup has not run yet. + + + + + Unverified login + The user just signed in with this device and hasn't verified their master key. + + + + + Please verify your other devices + There are unverified devices signed in to this account. + + + + + Close + Fermer + + + Start a new chat Commencer une nouvelle discussion @@ -1544,12 +1593,12 @@ Exemple : https ://monserveur.example.com:8787 RoomMembers - + Members of %1 Membres de %1 - + %n people in %1 Summary above list of members @@ -1586,12 +1635,12 @@ Exemple : https ://monserveur.example.com:8787 RoomSettings - + Room Settings Configuration du salon - + %1 member(s) %1 membre(s) @@ -1697,12 +1746,12 @@ Exemple : https ://monserveur.example.com:8787 Version du salon - + Failed to enable encryption: %1 - Échec de l'activation du chiffrement : %1 + Échec de l'activation du chiffrement : %1 - + Select an avatar Sélectionner un avatar @@ -1719,49 +1768,77 @@ Exemple : https ://monserveur.example.com:8787 Error while reading file: %1 - Erreur lors de la lecture du fichier : %1 + Erreur lors de la lecture du fichier : %1 - - + + Failed to upload image: %s - Échec de l'envoi de l'image : %s + Échec de l'envoi de l'image : %s RoomlistModel - + Pending invite. Invitation en attente. - + Previewing this room Prévisualisation du salon - + No preview available Aucune prévisualisation disponible + + Root + + + Please enter your login password to continue: + + + + + Please enter a valid email address to continue: + + + + + Please enter a valid phone number to continue: + + + + + Please enter the token, which has been sent to you: + + + + + Wait for the confirmation link to arrive, then continue. + + + ScreenShare Share desktop with %1? - Partager le bureau avec %1  ? + Partager le bureau avec %1  ? Window: - Fenêtre : + Fenêtre : Frame rate: - Fréquence d'images : + Fréquence d'images : @@ -1803,20 +1880,98 @@ Exemple : https ://monserveur.example.com:8787 SecretStorage - + Failed to connect to secret storage Échec de la connexion au stockage des secrets - Nheko could not connect to the secure storage to save encryption secrets to. This can have multiple reasons. Check if your D-Bus service is running and you have configured a service like KWallet, Gnome Secrets or the equivalent for your platform. If you are having trouble, feel free to open an issue here: https://github.com/Nheko-Reborn/nheko/issues - Nheko n'a pas pu se connecter au stockage des secrets pour y stocker les secrets de chiffrement. Plusieurs causes sont possibles. Vérifiez si votre service D-Bus est en cours d'exécution et si vous avez configuré un service comme KWallet, Gnome Secrets, ou un équivalent pour votre plate-forme. En cas de difficulté, n'hésitez pas à ouvrir un ticket ici (en anglais) : https://github.com/Nheko-Reborn/nheko/issues + Nheko could not connect to the secure storage to save encryption secrets to. This can have multiple reasons. Check if your D-Bus service is running and you have configured a service like KWallet, Gnome Keyring, KeePassXC or the equivalent for your platform. If you are having trouble, feel free to open an issue here: https://github.com/Nheko-Reborn/nheko/issues + + + + + SelfVerificationCheck + + + This is your recovery key. You will need it to restore access to your encrypted messages and verification keys. Keep this safe. Don't share it with anyone and don't lose it! Do not pass go! Do not collect $200! + + + + + Encryption setup successfully + + + + + Failed to setup encryption: %1 + + + + + Setup Encryption + + + + + Hello and welcome to Matrix! +It seems like you are new. Before you can securely encrypt your messages, we need to setup a few small things. You can either press accept immediately or adjust a few basic options. We also try to explain a few of the basics. You can skip those parts, but they might prove to be helpful! + + + + + Activate Encryption + + + + + It seems like you have encryption already configured for this account. To be able to access your encrypted messages and make this device appear as trusted, you can either verify an existing device or (if you have one) enter your recovery passphrase. Please select one of the options below. +If you choose verify, you need to have the other device available. If you choose "enter passphrase", you will need your recovery key or passphrase. If you click cancel, you can choose to verify yourself at a later point. + + + + + verify + + + + + enter passphrase + + + + + SelfVerificationStatus + + + Failed to create keys for cross-signing! + + + + + Failed to create keys for online key backup! + + + + + Failed to create keys secure server side secret storage! + + + + + Encryption Setup + + + + + Encryption setup failed: %1 + SingleImagePackModel - + Failed to update image pack: %1 Échec de la mise à jour du paquet d'images : %1 @@ -1832,7 +1987,7 @@ Exemple : https ://monserveur.example.com:8787 Échec de l'ouverture de l'image : %1 - + Failed to upload image: %1 Échec de l'envoi de l'image : %1 @@ -1878,7 +2033,7 @@ Exemple : https ://monserveur.example.com:8787 Verification successful! Both sides verified their devices! - Vérification réussie ! Les deux côtés ont vérifié leur appareil ! + Vérification réussie ! Les deux côtés ont vérifié leur appareil ! @@ -1889,18 +2044,18 @@ Exemple : https ://monserveur.example.com:8787 TimelineModel - + Message redaction failed: %1 - Échec de la suppression du message : %1 + Échec de la suppression du message : %1 - + Failed to encrypt event, sending aborted! - Échec du chiffrement de l'évènement, envoi abandonné ! + Échec du chiffrement de l'évènement, envoi abandonné ! - + Save image Enregistrer l'image @@ -1920,7 +2075,7 @@ Exemple : https ://monserveur.example.com:8787 Enregistrer le fichier - + %1 and %2 are typing. Multiple users are typing. First argument is a comma separated list of potentially multiple users. Second argument is the last user of that list. (If only one user is typing, %1 is empty. You should still use it in your string though to silence Qt warnings.) @@ -1929,7 +2084,7 @@ Exemple : https ://monserveur.example.com:8787 - + %1 opened the room to the public. %1 a ouvert le salon au public. @@ -1969,12 +2124,12 @@ Exemple : https ://monserveur.example.com:8787 %1 a rendu l'historique du salon visible aux membre à partir de cet instant. - + %1 set the room history visible to members since they were invited. %1 a rendu l'historique visible aux membres à partir de leur invitation. - + %1 set the room history visible to members since they joined the room. %1 a rendu l'historique du salon visible aux membres à partir de l'instant où ils le rejoignent. @@ -1984,12 +2139,12 @@ Exemple : https ://monserveur.example.com:8787 %1 a changé les permissions du salon. - + %1 was invited. %1 a été invité(e). - + %1 changed their avatar. %1 a changé son avatar. @@ -2004,12 +2159,12 @@ Exemple : https ://monserveur.example.com:8787 %1 a rejoint le salon. - + %1 joined via authorisation from %2's server. %1 a rejoint via une autorisation de la part du serveur de %2. - + %1 rejected their invite. %1 a rejeté son invitation. @@ -2039,27 +2194,27 @@ Exemple : https ://monserveur.example.com:8787 %1 a été banni. - + Reason: %1 Raison : %1 - + %1 redacted their knock. %1 a arrêté de toquer. - + You joined this room. Vous avez rejoint ce salon. - + %1 has changed their avatar and changed their display name to %2. %1 a changé son avatar et changé son surnom en %2. - + %1 has changed their display name to %2. %1 a changé son surnom en %2. @@ -2072,7 +2227,7 @@ Exemple : https ://monserveur.example.com:8787 %1 left after having already left! This is a leave event after the user already left and shouldn't happen apart from state resets - %1 a quitté le salon après l'avoir déjà quitté ! + %1 a quitté le salon après l'avoir déjà quitté ! @@ -2096,7 +2251,12 @@ Exemple : https ://monserveur.example.com:8787 Aucun salon ouvert - + + No preview available + Aucune prévisualisation disponible + + + %1 member(s) %1 membre(s) @@ -2121,28 +2281,20 @@ Exemple : https ://monserveur.example.com:8787 Revenir à la liste des salons - - TimelineViewManager - - - No encrypted private chat found with this user. Create an encrypted private chat with this user and try again. - Aucune discussion privée chiffrée trouvée avec cet utilisateur. Créez-en une et réessayez. - - TopBar - + Back to room list Revenir à la liste des salons - + No room selected Pas de salon sélectionné - + This room is not encrypted! Ce salon n'est pas chiffré ! @@ -2153,8 +2305,8 @@ Exemple : https ://monserveur.example.com:8787 - This rooms contain verified devices and devices which have never changed their master key. - Ce salon contient des appareils vérifiés et des appareils qui n'ont jamais changé leur clef maîtresse. + This room contains verified devices and devices which have never changed their master key. + @@ -2200,10 +2352,35 @@ Exemple : https ://monserveur.example.com:8787 Quitter + + UIA + + + No available registration flows! + + + + + + + Registration aborted + + + + + Please enter a valid registration token. + Veuillez entrer un jeton d'enregistrement valide. + + + + Invalid token + + + UserProfile - + Global User Profile Profil général de l'utilisateur @@ -2213,7 +2390,7 @@ Exemple : https ://monserveur.example.com:8787 Profil utilisateur spécifique au salon - + Change avatar globally. Changer l'image de profil partout. @@ -2223,7 +2400,7 @@ Exemple : https ://monserveur.example.com:8787 Changer l'image de profil. Ne s'appliquera qu'à ce salon. - + Change display name globally. Changer de surnom partout. @@ -2233,7 +2410,7 @@ Exemple : https ://monserveur.example.com:8787 Changer de surnom. Ne s'appliquera qu'à ce salon. - + Room: %1 Salon : %1 @@ -2243,18 +2420,18 @@ Exemple : https ://monserveur.example.com:8787 Ceci est un profil spécifique à un salon. Le surnom et l'image de profil peuvent être différents de leurs versions globales. - + Open the global profile for this user. Ouvrir le profil global de cet utilisateur. - - + + Verify Vérifier - + Start a private chat. Démarrer une discussion privée. @@ -2269,12 +2446,42 @@ Exemple : https ://monserveur.example.com:8787 Bannir l'utilisateur. - + + Refresh device list. + + + + + Sign out this device. + + + + + Change device name. + + + + + Last seen %1 from %2 + + + + Unverify Dé-vérifier - + + Sign out device %1 + + + + + You signed out this device. + + + + Select an avatar Sélectionnez un avatar @@ -2291,14 +2498,14 @@ Exemple : https ://monserveur.example.com:8787 Error while reading file: %1 - Erreur lors de la lecture du fichier  : %1 + Erreur lors de la lecture du fichier  : %1 UserSettings - - + + Default Défaut @@ -2306,7 +2513,7 @@ Exemple : https ://monserveur.example.com:8787 UserSettingsPage - + Minimize to tray Réduire dans la barre des tâches @@ -2316,22 +2523,22 @@ Exemple : https ://monserveur.example.com:8787 Démarrer dans la barre des tâches - + Group's sidebar Barre latérale des groupes - + Circular Avatars Avatars circulaires - + profile: %1 - profil : %1 + profil : %1 - + Default Défaut @@ -2356,7 +2563,7 @@ Exemple : https ://monserveur.example.com:8787 TÉLÉCHARGER - + Keep the application running in the background after closing the client window. Conserver l'application en arrière-plan après avoir fermé la fenêtre du client. @@ -2372,6 +2579,16 @@ OFF - square, ON - Circle. Change l'apparence des avatars des utilisateurs dans les discussions. OFF – carré, ON – cercle. + + + Use identicons + + + + + Display an identicon instead of a letter when a user has not set an avatar. + + Show a column containing groups and tags next to the room list. @@ -2402,7 +2619,7 @@ be blurred. sera floutée. - + Privacy screen timeout (in seconds [0 - 3600]) Attente pour l'activation de la protection anti-indiscrétion (en secondes, 0 à 3600) @@ -2464,7 +2681,7 @@ Si activé, les salons qui ont des notifications actives (le petit cercle avec u Les salons que vous avez rendu silencieux seront toujours triés par date du dernier message, puisqu'ils sont considérés comme moins importants. - + Read receipts Accusés de lecture @@ -2476,7 +2693,7 @@ Status is displayed next to timestamps. Le statut est montré près de la date des messages. - + Send messages as Markdown Composer les messages au format Markdown @@ -2565,7 +2782,7 @@ Cela met l'application en évidence dans la barre des tâches.Télécharge les clefs de chiffrement de message depuis et envoie vers la sauvegarde chiffrée en ligne de clefs. - + Enable online key backup Activer la sauvegarde de clefs en ligne @@ -2575,7 +2792,7 @@ Cela met l'application en évidence dans la barre des tâches.Les auteurs de Nheko ne recommandent pas d'activer la sauvegarde en ligne de clefs jusqu'à ce que la sauvegarde symétrique en ligne de clefs soit disponible. Activer quand même ? - + CACHED EN CACHE @@ -2585,7 +2802,7 @@ Cela met l'application en évidence dans la barre des tâches.PAS DANS LE CACHE - + Scale factor Facteur d'échelle @@ -2660,7 +2877,7 @@ Cela met l'application en évidence dans la barre des tâches.Empreinte de l'appareil - + Session Keys Clés de session @@ -2680,17 +2897,17 @@ Cela met l'application en évidence dans la barre des tâches.CHIFFREMENT - + GENERAL GÉNÉRAL - + INTERFACE INTERFACE - + Plays media like GIFs or WEBPs only when explicitly hovering over them. Joue les images comme les GIFs ou WEBPs uniquement quand la souris est au-dessus. @@ -2730,7 +2947,7 @@ Cela met l'application en évidence dans la barre des tâches.La clé utilisée pour vérifier d'autres utilisateurs. Si celle-ci est dans le cache, vérifier un utilisateur vérifiera tous ses appareils. - + Self signing key Clé d'auto-vérification @@ -2760,14 +2977,14 @@ Cela met l'application en évidence dans la barre des tâches.Tous les types de fichiers (*) - + Open Sessions File Ouvrir le fichier de sessions - + @@ -2775,26 +2992,26 @@ Cela met l'application en évidence dans la barre des tâches.Erreur - - + + File Password Mot de passe du fichier - + Enter the passphrase to decrypt the file: - Entrez la phrase de passe pour déchiffrer le fichier : + Entrez la phrase de passe pour déchiffrer le fichier : - + The password cannot be empty Le mot de passe ne peut être vide Enter passphrase to encrypt your session keys: - Entrez une phrase de passe pour chiffrer vos clés de session : + Entrez une phrase de passe pour chiffrer vos clés de session : @@ -2802,6 +3019,14 @@ Cela met l'application en évidence dans la barre des tâches.Fichier où sauvegarder les clés de session exportées + + VerificationManager + + + No encrypted private chat found with this user. Create an encrypted private chat with this user and try again. + Aucune discussion privée chiffrée trouvée avec cet utilisateur. Créez-en une et réessayez. + + Waiting @@ -2856,7 +3081,7 @@ Cela met l'application en évidence dans la barre des tâches. descriptiveTime - + Yesterday Hier @@ -2927,37 +3152,6 @@ Cela met l'application en évidence dans la barre des tâches.Ouvrez la solution de secours, suivez les étapes et confirmez après les avoir terminées. - - dialogs::JoinRoom - - - Join - Rejoindre - - - - Cancel - Annuler - - - - Room ID or alias - Identifiant ou alias du salon - - - - dialogs::LeaveRoom - - - Cancel - Annuler - - - - Are you sure you want to leave? - Êtes-vous sûr·e de vouloir quitter ? - - dialogs::Logout @@ -3024,47 +3218,47 @@ Taille du média : %2 %1 a envoyé un message audio - + You sent an image Vous avez envoyé une image - + %1 sent an image %1 a envoyé une image - + You sent a file Vous avez envoyé un fichier - + %1 sent a file %1 a envoyé un fichier - + You sent a video Vous avez envoyé une vidéo - + %1 sent a video %1 a envoyé une vidéo - + You sent a sticker Vous avez envoyé un autocollant - + %1 sent a sticker %1 a envoyé un autocollant - + You sent a notification Vous avez envoyé une notification @@ -3076,12 +3270,12 @@ Taille du média : %2 You: %1 - Vous : %1 + Vous : %1 - + %1: %2 - %1 : %2 + %1 : %2 @@ -3099,27 +3293,27 @@ Taille du média : %2 Vous avez appelé - + %1 placed a call %1 a appelé - + You answered a call Vous avez répondu à un appel - + %1 answered a call %1 a répondu à un appel - + You ended a call Vous avez terminé un appel - + %1 ended a call %1 a terminé un appel @@ -3127,7 +3321,7 @@ Taille du média : %2 utils - + Unknown Message Type Type du message inconnu diff --git a/resources/langs/nheko_hu.ts b/resources/langs/nheko_hu.ts index a0cd1ab5..ffef514b 100644 --- a/resources/langs/nheko_hu.ts +++ b/resources/langs/nheko_hu.ts @@ -4,7 +4,7 @@ ActiveCallBar - + Calling... Hívás... @@ -56,7 +56,7 @@ CallInvite - + Video Call Videóhívás @@ -74,7 +74,7 @@ CallInviteBar - + Video Call Videóhívás @@ -117,7 +117,7 @@ CallManager - + Entire screen Az egész képernyő @@ -125,23 +125,23 @@ ChatPage - + Failed to invite user: %1 Nem sikerült meghívni a felhasználót: %1 - + Invited user: %1 A felhasználó meg lett hívva: %1 - + Migrating the cache to the current version failed. This can have different reasons. Please open an issue and try to use an older version in the mean time. Alternatively you can try deleting the cache manually. A gyorsítótár átvitele a jelenlegi verzióhoz nem sikerült. Ennek több oka is lehet. Kérlek, írj egy hibajelentést és egyelőre próbálj meg egy régebbi verziót használni! Alternatív megoldásként megprobálhatod eltávolítani a gyorsítótárat kézzel. - + Confirm join Csatlakozás megerősítése @@ -151,23 +151,23 @@ Biztosan csatlakozni akarsz a(z) %1 szobához? - + Room %1 created. A %1 nevű szoba létre lett hozva. - - + + Confirm invite Meghívás megerősítése - + Do you really want to invite %1 (%2)? Biztos, hogy meg akarod hívni a következő felhasználót: %1 (%2)? - + Failed to invite %1 to %2: %3 Nem sikerült %1 meghívása a(z) %2 szobába: %3 @@ -182,7 +182,7 @@ Biztosan ki akarod rúgni %1 (%2) felhasználót? - + Kicked user: %1 Kirúgott felhasználó: %1 @@ -197,7 +197,7 @@ Biztosan ki akarod tiltani %1 (%2) felhasználót? - + Failed to ban %1 in %2: %3 Nem sikerült kitiltani %1 felhasználót a %2 szobából: %3 @@ -217,7 +217,7 @@ Biztosan fel akarod oldani %1 (%2) felhasználó kitiltását? - + Failed to unban %1 in %2: %3 Nem sikerült feloldani %1 felhasználó kitiltását a %2 szobából: %3 @@ -227,12 +227,12 @@ Kitiltás feloldva a felhasználónak: %1 - + Do you really want to start a private chat with %1? Biztosan privát csevegést akarsz indítani %1 felhasználóval? - + Cache migration failed! Gyorsítótár migráció nem sikerült! @@ -259,23 +259,23 @@ Nem sikerült visszaállítani a mentési adatot. Kérlek, jelentkezz be ismét! - + Failed to setup encryption keys. Server response: %1 %2. Please try again later. Nem sikerült beállítani a titkosítási kulcsokat. Válasz a szervertől: %1 %2. Kérlek, próbáld újra később! - - + + Please try to login again: %1 Kérlek, próbálj meg bejelentkezni újra: %1 - + Failed to join room: %1 Nem sikerült csatlakozni a szobához: %1 - + You joined the room Csatlakoztál a szobához @@ -285,17 +285,17 @@ Nem sikerült eltávolítani a meghívót: %1 - + Room creation failed: %1 Nem sikerült létrehozni a szobát: %1 - + Failed to leave room: %1 Nem sikerült elhagyni a szobát: %1 - + Failed to kick %1 from %2: %3 Nem sikerült kirúgni %1 felhasználót %2 szobából: %3 @@ -354,7 +354,7 @@ CrossSigningSecrets - + Decrypt secrets Titkos tároló feloldása @@ -364,12 +364,12 @@ Add meg a helyreállítási kulcsodat vagy a jelmondatodat a titkos tároló feloldásához: - + Enter your recovery key or passphrase called %1 to decrypt your secrets: Add meg a %1 nevű helyreállítási kulcsodat vagy a jelmondatodat a titkos tároló feloldásához: - + Decryption failed Titkosítás feloldása nem sikerült @@ -581,17 +581,26 @@ - Device verification timed out. Időtúllépés az eszközhitelesítés alatt. - + Other party canceled the verification. A másik fél megszakította a hitelesítést. - + + Verification messages received out of order! + + + + + Unknown verification error. + + + + Close Bezárás @@ -612,7 +621,7 @@ - + Add images @@ -622,7 +631,7 @@ - + State key @@ -638,13 +647,13 @@ - + Use as Emoji - - + + Use as Sticker @@ -697,7 +706,7 @@ - + Private pack @@ -712,7 +721,7 @@ - + Enable globally @@ -735,7 +744,7 @@ InputBar - + Select a file Fájl kiválasztása @@ -745,7 +754,7 @@ Minden fájl (*) - + Failed to upload media. Please try again. Nem sikerült feltölteni a médiafájlt. Kérlek, próbáld újra! @@ -753,7 +762,7 @@ InviteDialog - + Invite users to %1 @@ -784,6 +793,32 @@ Mégse + + JoinRoomDialog + + + Join room + + + + + Room ID or alias + Szoba azonosítója vagy álneve + + + + LeaveRoomDialog + + + Leave room + Szoba elhagyása + + + + Are you sure you want to leave? + Biztosan távozni akarsz? + + LoginPage @@ -850,25 +885,25 @@ Példa: https://szerver.em:8787 BEJELENTKEZÉS - + - + You have entered an invalid Matrix ID e.g @joe:matrix.org Érvénytelen Matrixazonosítót adtál meg. Példa: @janos:matrix.org - + Autodiscovery failed. Received malformed response. Az automatikus felderítés nem sikerült. Helytelen válasz érkezett. - + Autodiscovery failed. Unknown error when requesting .well-known. Az automatikus felderítés nem sikerült. Ismeretlen hiba a .well-known lekérése közben. - + The required endpoints were not found. Possibly not a Matrix server. Nem találhatók szükséges végpontok. Lehet, hogy nem egy Matrixszerver. @@ -878,26 +913,44 @@ Példa: https://szerver.em:8787 Helytelen válasz érkezett. Ellenőrizd, hogy a homeszervered domainje helyes. - + An unknown error occured. Make sure the homeserver domain is valid. Egy ismeretlen hiba történt. Ellenőrizd, hogy a homeszervered domainje helyes. - + SSO LOGIN SSO BEJELENTKEZÉS - + Empty password Üres jelszó - + SSO login failed SSO bejelentkezés nem sikerült + + LogoutDialog + + + Log out + + + + + A call is in progress. Log out? + + + + + Are you sure you want to log out? + + + MessageDelegate @@ -1023,7 +1076,7 @@ Példa: https://szerver.em:8787 MessageView - + Edit Szerkesztés @@ -1043,7 +1096,7 @@ Példa: https://szerver.em:8787 Műveletek - + &Copy @@ -1133,7 +1186,12 @@ Példa: https://szerver.em:8787 Hitelesítési kérés érkezett - + + To allow other users to see, which of your devices actually belong to you, you can verify them. This also allows key backup to work automatically. Verify an unverified device now? (Please make sure you have one of those devices available.) + + + + To allow other users to see, which of your devices actually belong to you, you can verify them. This also allows key backup to work automatically. Verify %1 now? Hogy mások láthassák, melyik eszköz tartozik valóban hozzád, hitelesíteni tudod őket. Ez arra is lehetőséget ad, hogy automatikus biztonsági másolat készüljön a kulcsokról. Hitelesíted a %1 nevű eszközt most? @@ -1182,7 +1240,7 @@ Példa: https://szerver.em:8787 NotificationWarning - You will be pinging the whole room + You are about to notify the whole room @@ -1190,29 +1248,17 @@ Példa: https://szerver.em:8787 NotificationsManager - - + + %1 sent an encrypted message %1 küldött egy titkosított üzenetet - - * %1 %2 - Format an emote message in a notification, %1 is the sender, %2 the message - * %1 %2 - - - + %1 replied: %2 Format a reply in a notification. %1 is the sender, %2 the message %1 válasza: %2 - - - %1: %2 - Format a normal message in a notification. %1 is the sender, %2 the message - %1: %2 - @@ -1243,7 +1289,7 @@ Példa: https://szerver.em:8787 Nem található mikrofon. - + Voice Hang @@ -1274,7 +1320,7 @@ Példa: https://szerver.em:8787 QCoreApplication - + Create a unique profile, which allows you to log into several accounts at the same time and start multiple instances of nheko. Egy egyedi profil létrehozása, amellyel be tudsz jelentkezni egyszerre több fiókon keresztül és a Nheko több példányát is tudod futtatni. @@ -1292,7 +1338,7 @@ Példa: https://szerver.em:8787 ReadReceipts - + Read receipts Olvasási jegyek @@ -1300,7 +1346,7 @@ Példa: https://szerver.em:8787 ReadReceiptsModel - + Yesterday, %1 @@ -1308,18 +1354,18 @@ Példa: https://szerver.em:8787 RegisterPage - + Username Felhasználónév - + The username must not be empty, and must contain only the characters a-z, 0-9, ., _, =, -, and /. A felhasználónév nem lehet üres és csak a következő karaktereket tartalmazhatja: a-z, 0-9, ., _, =, - és /. - + Password Jelszó @@ -1349,37 +1395,22 @@ Példa: https://szerver.em:8787 REGISZTRÁCIÓ - - No supported registration flows! - Nem támogatott regisztrációs folyamat! - - - - Registration token - - - - - Please enter a valid registration token. - - - - + Autodiscovery failed. Received malformed response. Az automatikus felderítés nem sikerült. Helytelen válasz érkezett. - + Autodiscovery failed. Unknown error when requesting .well-known. Az automatikus felderítés nem sikerült. Ismeretlen hiba a .well-known lekérése közben. - + The required endpoints were not found. Possibly not a Matrix server. Nem találhatók szükséges végpontok. Lehet, hogy nem egy Matrixszerver. - + Received malformed response. Make sure the homeserver domain is valid. Helytelen válasz érkezett. Ellenőrizd, hogy a homeszervered domainje helyes. @@ -1389,7 +1420,7 @@ Példa: https://szerver.em:8787 Egy ismeretlen hiba történt. Ellenőrizd, hogy a homeszervered domainje helyes. - + Password is not long enough (min 8 chars) A jelszó nem elég hosszú (legalább 8 karakter) @@ -1420,20 +1451,25 @@ Példa: https://szerver.em:8787 RoomDirectory - + Explore Public Rooms - + Search for public rooms + + + Choose custom homeserver + + RoomInfo - + no version stored nincs tárolva verzió @@ -1450,16 +1486,6 @@ Példa: https://szerver.em:8787 Enter the tag you want to use: - - - Leave Room - - - - - Are you sure you want to leave this room? - - Leave room @@ -1491,7 +1517,7 @@ Példa: https://szerver.em:8787 - + Status Message @@ -1516,7 +1542,30 @@ Példa: https://szerver.em:8787 Kijelentkezés - + + Encryption not set up + Cross-signing setup has not run yet. + + + + + Unverified login + The user just signed in with this device and hasn't verified their master key. + + + + + Please verify your other devices + There are unverified devices signed in to this account. + + + + + Close + Bezárás + + + Start a new chat Új csevegés indítása @@ -1544,12 +1593,12 @@ Példa: https://szerver.em:8787 RoomMembers - + Members of %1 - + %n people in %1 Summary above list of members @@ -1585,12 +1634,12 @@ Példa: https://szerver.em:8787 RoomSettings - + Room Settings Szobabeállítások - + %1 member(s) %1 tag @@ -1696,12 +1745,12 @@ Példa: https://szerver.em:8787 Szoba verziója - + Failed to enable encryption: %1 Nem sikerült a titkosítás aktiválása: %1 - + Select an avatar Profilkép kiválasztása @@ -1721,8 +1770,8 @@ Példa: https://szerver.em:8787 Hiba a fájl olvasása közben: %1 - - + + Failed to upload image: %s Nem sikerült a kép feltöltése: %s @@ -1730,21 +1779,49 @@ Példa: https://szerver.em:8787 RoomlistModel - + Pending invite. - + Previewing this room - + No preview available + + Root + + + Please enter your login password to continue: + + + + + Please enter a valid email address to continue: + + + + + Please enter a valid phone number to continue: + + + + + Please enter the token, which has been sent to you: + + + + + Wait for the confirmation link to arrive, then continue. + + + ScreenShare @@ -1802,20 +1879,98 @@ Példa: https://szerver.em:8787 SecretStorage - + Failed to connect to secret storage - Nheko could not connect to the secure storage to save encryption secrets to. This can have multiple reasons. Check if your D-Bus service is running and you have configured a service like KWallet, Gnome Secrets or the equivalent for your platform. If you are having trouble, feel free to open an issue here: https://github.com/Nheko-Reborn/nheko/issues + Nheko could not connect to the secure storage to save encryption secrets to. This can have multiple reasons. Check if your D-Bus service is running and you have configured a service like KWallet, Gnome Keyring, KeePassXC or the equivalent for your platform. If you are having trouble, feel free to open an issue here: https://github.com/Nheko-Reborn/nheko/issues + + + + + SelfVerificationCheck + + + This is your recovery key. You will need it to restore access to your encrypted messages and verification keys. Keep this safe. Don't share it with anyone and don't lose it! Do not pass go! Do not collect $200! + + + + + Encryption setup successfully + + + + + Failed to setup encryption: %1 + + + + + Setup Encryption + + + + + Hello and welcome to Matrix! +It seems like you are new. Before you can securely encrypt your messages, we need to setup a few small things. You can either press accept immediately or adjust a few basic options. We also try to explain a few of the basics. You can skip those parts, but they might prove to be helpful! + + + + + Activate Encryption + + + + + It seems like you have encryption already configured for this account. To be able to access your encrypted messages and make this device appear as trusted, you can either verify an existing device or (if you have one) enter your recovery passphrase. Please select one of the options below. +If you choose verify, you need to have the other device available. If you choose "enter passphrase", you will need your recovery key or passphrase. If you click cancel, you can choose to verify yourself at a later point. + + + + + verify + + + + + enter passphrase + + + + + SelfVerificationStatus + + + Failed to create keys for cross-signing! + + + + + Failed to create keys for online key backup! + + + + + Failed to create keys secure server side secret storage! + + + + + Encryption Setup + + + + + Encryption setup failed: %1 SingleImagePackModel - + Failed to update image pack: %1 @@ -1831,7 +1986,7 @@ Példa: https://szerver.em:8787 - + Failed to upload image: %1 @@ -1888,18 +2043,18 @@ Példa: https://szerver.em:8787 TimelineModel - + Message redaction failed: %1 Az üzenet visszavonása nem sikerült: %1 - + Failed to encrypt event, sending aborted! Nem sikerült titkosítani az eseményt, küldés megszakítva! - + Save image Kép mentése @@ -1919,7 +2074,7 @@ Példa: https://szerver.em:8787 Fájl mentése - + %1 and %2 are typing. Multiple users are typing. First argument is a comma separated list of potentially multiple users. Second argument is the last user of that list. (If only one user is typing, %1 is empty. You should still use it in your string though to silence Qt warnings.) @@ -1927,7 +2082,7 @@ Példa: https://szerver.em:8787 - + %1 opened the room to the public. %1 nyilvánosan elérhetővé tette a szobát. @@ -1967,12 +2122,12 @@ Példa: https://szerver.em:8787 %1 beállította, hogy a szoba előzményei ezentúl csak a tagok számára legyenek láthatóak. - + %1 set the room history visible to members since they were invited. %1 beállította, hogy a szoba előzményei láthatóak legyenek a tagok számára a meghívásuktól kezdve. - + %1 set the room history visible to members since they joined the room. %1 beállította, hogy a szoba előzményei láthatóak legyenek a tagok számára a csatlakozásuktól kezdve. @@ -1982,12 +2137,12 @@ Példa: https://szerver.em:8787 %1 megváltoztatta a szoba engedélyeit. - + %1 was invited. %1 meg lett hívva. - + %1 changed their avatar. %1 megváltoztatta a profilképét. @@ -2002,12 +2157,12 @@ Példa: https://szerver.em:8787 %1 csatlakozott. - + %1 joined via authorisation from %2's server. - + %1 rejected their invite. %1 elutasította a meghívását. @@ -2037,27 +2192,27 @@ Példa: https://szerver.em:8787 %1 ki lett tiltva. - + Reason: %1 - + %1 redacted their knock. %1 visszavonta a kopogását. - + You joined this room. Csatlakoztál ehhez a szobához. - + %1 has changed their avatar and changed their display name to %2. - + %1 has changed their display name to %2. @@ -2094,7 +2249,12 @@ Példa: https://szerver.em:8787 Nincs nyitott szoba - + + No preview available + + + + %1 member(s) %1 tag @@ -2119,28 +2279,20 @@ Példa: https://szerver.em:8787 Vissza a szobák listájára - - TimelineViewManager - - - No encrypted private chat found with this user. Create an encrypted private chat with this user and try again. - Nem található titkosított privát csevegés ezzel a felhasználóval. Hozz létre egy titkosított privát csevegést vele, és próbáld újra! - - TopBar - + Back to room list Vissza a szobák listájára - + No room selected Nincs kiválasztva szoba - + This room is not encrypted! @@ -2151,7 +2303,7 @@ Példa: https://szerver.em:8787 - This rooms contain verified devices and devices which have never changed their master key. + This room contains verified devices and devices which have never changed their master key. @@ -2198,10 +2350,35 @@ Példa: https://szerver.em:8787 Kilépés + + UIA + + + No available registration flows! + + + + + + + Registration aborted + + + + + Please enter a valid registration token. + + + + + Invalid token + + + UserProfile - + Global User Profile Globális felhasználói profil @@ -2211,7 +2388,7 @@ Példa: https://szerver.em:8787 Szobai felhasználói profil - + Change avatar globally. @@ -2221,7 +2398,7 @@ Példa: https://szerver.em:8787 - + Change display name globally. @@ -2231,7 +2408,7 @@ Példa: https://szerver.em:8787 - + Room: %1 @@ -2241,18 +2418,18 @@ Példa: https://szerver.em:8787 - + Open the global profile for this user. - - + + Verify Hitelesítés - + Start a private chat. @@ -2267,12 +2444,42 @@ Példa: https://szerver.em:8787 - + + Refresh device list. + + + + + Sign out this device. + + + + + Change device name. + + + + + Last seen %1 from %2 + + + + Unverify Hitelesítés visszavonása - + + Sign out device %1 + + + + + You signed out this device. + + + + Select an avatar Profilkép kiválasztása @@ -2295,8 +2502,8 @@ Példa: https://szerver.em:8787 UserSettings - - + + Default Alapértelmezett @@ -2304,7 +2511,7 @@ Példa: https://szerver.em:8787 UserSettingsPage - + Minimize to tray Kicsinyítés a tálcára @@ -2314,22 +2521,22 @@ Példa: https://szerver.em:8787 Indítás a tálcán - + Group's sidebar Csoport oldalsávja - + Circular Avatars Kerekített profilképek - + profile: %1 profil: %1 - + Default Alapértelmezett @@ -2354,7 +2561,7 @@ Példa: https://szerver.em:8787 LETÖLTÉS - + Keep the application running in the background after closing the client window. Az alkalmazás azután is a háttérben fut, miután be lett zárva a főablak. @@ -2370,6 +2577,16 @@ OFF - square, ON - Circle. A profilképek megjelenése a csevegésekben. KI - szögletes, BE - kerek. + + + Use identicons + + + + + Display an identicon instead of a letter when a user has not set an avatar. + + Show a column containing groups and tags next to the room list. @@ -2400,7 +2617,7 @@ be blurred. az idővonal homályosítva lesz. - + Privacy screen timeout (in seconds [0 - 3600]) Idővonal kitakarása ennyi idő után (másodpercben, 0 és 3600 között) @@ -2461,7 +2678,7 @@ Ha ki van kapcsolva, a szobák sorrendje csak a bennük lévő utolsó üzenet d Ha be van kapcsolva, azok a szobák kerülnek felülre, amelyekhez aktív értesítés tartozik (amelyet a számot tartalmazó kis kör jelez). A némított szobák továbbra is dátum alapján lesznek rendezve, mivel nem valószínű, hogy ezeket annyira fontosnak tartod, mint a többi szobát. - + Read receipts Olvasási jegyek @@ -2473,7 +2690,7 @@ Status is displayed next to timestamps. Ez az állapot az üzenetek ideje mellett jelenik meg. - + Send messages as Markdown Üzenetek küldése Markdownként @@ -2562,7 +2779,7 @@ Ettől általában animálttá válik az alkalmazásablakok listáján szereplő - + Enable online key backup @@ -2572,7 +2789,7 @@ Ettől általában animálttá válik az alkalmazásablakok listáján szereplő - + CACHED GYORSÍTÓTÁRAZVA @@ -2582,7 +2799,7 @@ Ettől általában animálttá válik az alkalmazásablakok listáján szereplő NINCS GYORSÍTÓTÁRAZVA - + Scale factor Nagyítási tényező @@ -2657,7 +2874,7 @@ Ettől általában animálttá válik az alkalmazásablakok listáján szereplő Eszközujjlenyomat - + Session Keys Munkamenetkulcsok @@ -2677,17 +2894,17 @@ Ettől általában animálttá válik az alkalmazásablakok listáján szereplő TITKOSÍTÁS - + GENERAL ÁLTALÁNOS - + INTERFACE FELÜLET - + Plays media like GIFs or WEBPs only when explicitly hovering over them. @@ -2727,7 +2944,7 @@ Ettől általában animálttá válik az alkalmazásablakok listáján szereplő A mások hitelesítésére használt kulcs. Ha gyorsítótárazva van, egy felhasználó hitelesítésekor hitelesítve lesz az összes eszköze. - + Self signing key Önaláírókulcs @@ -2757,14 +2974,14 @@ Ettől általában animálttá válik az alkalmazásablakok listáján szereplő Minden fájl (*) - + Open Sessions File Munkameneti fájl megnyitása - + @@ -2772,19 +2989,19 @@ Ettől általában animálttá válik az alkalmazásablakok listáján szereplő Hiba - - + + File Password Fájljelszó - + Enter the passphrase to decrypt the file: Írd be a jelmondatot a fájl titkosításának feloldásához: - + The password cannot be empty A jelszó nem lehet üres @@ -2799,6 +3016,14 @@ Ettől általában animálttá válik az alkalmazásablakok listáján szereplő Exportált munkameneti kulcsok mentése fájlba + + VerificationManager + + + No encrypted private chat found with this user. Create an encrypted private chat with this user and try again. + Nem található titkosított privát csevegés ezzel a felhasználóval. Hozz létre egy titkosított privát csevegést vele, és próbáld újra! + + Waiting @@ -2853,7 +3078,7 @@ Ettől általában animálttá válik az alkalmazásablakok listáján szereplő descriptiveTime - + Yesterday Tegnap @@ -2924,37 +3149,6 @@ Ettől általában animálttá válik az alkalmazásablakok listáján szereplő Nyisd meg a fallback-ket, kövesd az utasításokat, és erősítsd meg, ha végeztél velük! - - dialogs::JoinRoom - - - Join - Csatlakozás - - - - Cancel - Mégse - - - - Room ID or alias - Szoba azonosítója vagy álneve - - - - dialogs::LeaveRoom - - - Cancel - Mégse - - - - Are you sure you want to leave? - Biztosan távozni akarsz? - - dialogs::Logout @@ -3021,47 +3215,47 @@ Média mérete: %2 %1 küldött egy hangfájlt - + You sent an image Küldtél egy képet - + %1 sent an image %1 küldött egy képet - + You sent a file Küldtél egy fájlt - + %1 sent a file %1 küldtél egy fájlt - + You sent a video Küldtél egy videót - + %1 sent a video %1 küldött egy videót - + You sent a sticker Küldtél egy matricát - + %1 sent a sticker %1 küldött egy matricát - + You sent a notification Küldtél egy értesítést @@ -3076,7 +3270,7 @@ Média mérete: %2 Te: %1 - + %1: %2 %1: %2 @@ -3096,27 +3290,27 @@ Média mérete: %2 Hívást kezdeményeztél - + %1 placed a call %1 hívást kezdeményezett - + You answered a call Fogadtál egy hívást - + %1 answered a call %1 hívást fogadott - + You ended a call Befejeztél egy hívást - + %1 ended a call %1 befejezett egy hívást @@ -3124,7 +3318,7 @@ Média mérete: %2 utils - + Unknown Message Type Ismeretlen üzenettípus diff --git a/resources/langs/nheko_id.ts b/resources/langs/nheko_id.ts index 4b78df09..accb1eab 100644 --- a/resources/langs/nheko_id.ts +++ b/resources/langs/nheko_id.ts @@ -4,7 +4,7 @@ ActiveCallBar - + Calling... Memanggil... @@ -56,7 +56,7 @@ CallInvite - + Video Call Panggilan Video @@ -74,7 +74,7 @@ CallInviteBar - + Video Call Panggilan Video @@ -117,7 +117,7 @@ CallManager - + Entire screen Semua layar @@ -125,23 +125,23 @@ ChatPage - + Failed to invite user: %1 Gagal mengundang pengguna: %1 - + Invited user: %1 Pengguna yang diundang: %1 - + Migrating the cache to the current version failed. This can have different reasons. Please open an issue and try to use an older version in the mean time. Alternatively you can try deleting the cache manually. Migrasi cache ke versi saat ini gagal. Ini dapat memiliki alasan yang berbeda. Silakan buka masalah dan coba gunakan versi yang lebih lama untuk sementara. Alternatifnya Anda dapat mencoba menghapus cache secara manual. - + Confirm join Konfirmasi untuk bergabung @@ -151,23 +151,23 @@ Apakah Anda ingin bergabung %1? - + Room %1 created. Ruangan %1 telah dibuat. - - + + Confirm invite Konfirmasi undangan - + Do you really want to invite %1 (%2)? Apakah Anda ingin menundang %1 (%2)? - + Failed to invite %1 to %2: %3 Gagal mengundang %1 ke %2: %3 @@ -182,7 +182,7 @@ Apakah Anda ingin mengeluarkan %1 (%2)? - + Kicked user: %1 Pengguna yang dikeluarkan: %1 @@ -197,7 +197,7 @@ Apakah Anda ingin mencekal %1 (%2)? - + Failed to ban %1 in %2: %3 Gagal mencekal %1 di %2: %3 @@ -217,7 +217,7 @@ Apakah Anda ingin menghilangkan cekalan %1 (%2)? - + Failed to unban %1 in %2: %3 Gagal menghilangkan pencekalan %1 di %2: %3 @@ -227,12 +227,12 @@ Menghilangkan cekalan pengguna: %1 - + Do you really want to start a private chat with %1? Apakah Anda ingin memulai chat privat dengan %1? - + Cache migration failed! Migrasi cache gagal! @@ -259,23 +259,23 @@ Gagal memulihkan data simpanan. Mohon masuk lagi. - + Failed to setup encryption keys. Server response: %1 %2. Please try again later. Gagal menyiapkan kunci enkripsi. Respons server: %1 %2. Silakan coba lagi nanti. - - + + Please try to login again: %1 Mohon mencoba masuk lagi: %1 - + Failed to join room: %1 Gagal bergabung ruangan: %1 - + You joined the room Anda bergabung ruangan ini @@ -285,17 +285,17 @@ Gagal menghapus undangan: %1 - + Room creation failed: %1 Pembuatan ruangan gagal: %1 - + Failed to leave room: %1 Gagal meninggalkan ruangan: %1 - + Failed to kick %1 from %2: %3 Gagal mengeluarkan %1 dari %2: %3 @@ -354,7 +354,7 @@ CrossSigningSecrets - + Decrypt secrets Dekripsi rahasia @@ -364,12 +364,12 @@ Masukkan kunci pemulihan Anda atau frasa sandi untuk mendekripsikan rahasia Anda: - + Enter your recovery key or passphrase called %1 to decrypt your secrets: Masukkan kunci pemulihan Anda atau frasa sandi yang bernama %1 untuk mendekripsikan rahasia Anda: - + Decryption failed Gagal mendekripsi @@ -581,17 +581,26 @@ - Device verification timed out. Waktu verifikasi perangkat habis. - + Other party canceled the verification. Pengguna yang lain membatalkan proses verifikasi ini. - + + Verification messages received out of order! + + + + + Unknown verification error. + + + + Close Tutup @@ -612,7 +621,7 @@ Mengedit paket gambar - + Add images Tambahkan gambar @@ -622,7 +631,7 @@ Stiker (*.png *.webp *.gif *.jpg *.jpeg) - + State key Kunci keadaan @@ -638,13 +647,13 @@ - + Use as Emoji Gunakan sebagai Emoji - - + + Use as Sticker Gunakan sebagai Stiker @@ -697,7 +706,7 @@ Paket ruangan baru - + Private pack Paket privat @@ -712,7 +721,7 @@ Paket yang diaktifkan secara global - + Enable globally Aktifkan secara global @@ -735,7 +744,7 @@ InputBar - + Select a file Pilih sebuah file @@ -745,7 +754,7 @@ Semua File (*) - + Failed to upload media. Please try again. Gagal untuk mengunggah media. Silakan coba lagi. @@ -753,7 +762,7 @@ InviteDialog - + Invite users to %1 Undang pengguna ke %1 @@ -784,6 +793,32 @@ Batalkan + + JoinRoomDialog + + + Join room + + + + + Room ID or alias + ID ruangan atau alias + + + + LeaveRoomDialog + + + Leave room + Tinggalkan ruangan + + + + Are you sure you want to leave? + Apakah Anda yakin untuk meninggalkan ruangan? + + LoginPage @@ -850,25 +885,25 @@ Misalnya: https://server.my:8787 MASUK - + - + You have entered an invalid Matrix ID e.g @joe:matrix.org Anda telah memasukkan ID Matrix yang tidak valid mis. @pengguna:matrix.org - + Autodiscovery failed. Received malformed response. Penemuan otomatis gagal. Menerima respons cacat. - + Autodiscovery failed. Unknown error when requesting .well-known. Penemuan otomatis gagal. Kesalahan yang tidak diketahu saat meminta .well-known. - + The required endpoints were not found. Possibly not a Matrix server. Titik akhir yang dibutuhkan tidak dapat ditemukan. Kemungkinan bukan server Matrix. @@ -878,26 +913,44 @@ Misalnya: https://server.my:8787 Menerima respons cacat. Pastikan domain homeservernya valid. - + An unknown error occured. Make sure the homeserver domain is valid. Terjadi kesalahan yang tidak diketahui. Pastikan domain homeservernya valid. - + SSO LOGIN LOGIN SSO - + Empty password Kata sandi kosong - + SSO login failed Login SSO gagal + + LogoutDialog + + + Log out + + + + + A call is in progress. Log out? + + + + + Are you sure you want to log out? + + + MessageDelegate @@ -1023,7 +1076,7 @@ Misalnya: https://server.my:8787 MessageView - + Edit Edit @@ -1043,7 +1096,7 @@ Misalnya: https://server.my:8787 Opsi - + &Copy &Salin @@ -1133,7 +1186,12 @@ Misalnya: https://server.my:8787 Menerima Permintaan Verifikasi - + + To allow other users to see, which of your devices actually belong to you, you can verify them. This also allows key backup to work automatically. Verify an unverified device now? (Please make sure you have one of those devices available.) + + + + To allow other users to see, which of your devices actually belong to you, you can verify them. This also allows key backup to work automatically. Verify %1 now? Untuk mengizinkan pengguna yang lain untuk melihat, perangkat apa saja yang sebenarnya milik Anda, verifiksi mereka. Ini juga dapat membuat kunci cadangan bekerja secara otomatis. Verifikasi %1 sekarang? @@ -1182,37 +1240,25 @@ Misalnya: https://server.my:8787 NotificationWarning - You will be pinging the whole room - Anda akan memberitahukan seluruh ruangan + You are about to notify the whole room + NotificationsManager - - + + %1 sent an encrypted message %1 mengirim pesan terenkripsi - - * %1 %2 - Format an emote message in a notification, %1 is the sender, %2 the message - * %1 %2 - - - + %1 replied: %2 Format a reply in a notification. %1 is the sender, %2 the message %1 membalas: %2 - - - %1: %2 - Format a normal message in a notification. %1 is the sender, %2 the message - %1: %2 - @@ -1243,7 +1289,7 @@ Misalnya: https://server.my:8787 Tidak ada mikrofon yang ditemukan. - + Voice Suara @@ -1274,7 +1320,7 @@ Misalnya: https://server.my:8787 QCoreApplication - + Create a unique profile, which allows you to log into several accounts at the same time and start multiple instances of nheko. Membuat profil yang unik, yang mengizinkan Anda untuk masuk ke beberapa akun pada waktu bersamaan dan mulai beberapa instansi nheko. @@ -1292,7 +1338,7 @@ Misalnya: https://server.my:8787 ReadReceipts - + Read receipts Laporan dibaca @@ -1300,7 +1346,7 @@ Misalnya: https://server.my:8787 ReadReceiptsModel - + Yesterday, %1 Kemarin, %1 @@ -1308,18 +1354,18 @@ Misalnya: https://server.my:8787 RegisterPage - + Username Nama pengguna - + The username must not be empty, and must contain only the characters a-z, 0-9, ., _, =, -, and /. Nama pengguna tidak boleh kosong, dan hanya mengandung karakter a-z, 0-9, ., _, =, -, dan /. - + Password Kata sandi @@ -1349,37 +1395,22 @@ Misalnya: https://server.my:8787 DAFTAR - - No supported registration flows! - Tidak ada aliran pendaftaran yang didukung! - - - - Registration token - Token pendaftaran - - - - Please enter a valid registration token. - Mohon masukkan token pendaftaran yang valid. - - - + Autodiscovery failed. Received malformed response. Penemuan otomatis gagal. Menerima respons cacat. - + Autodiscovery failed. Unknown error when requesting .well-known. Penemuan otomatis gagal. Terjadi kesalahan yang tidak diketahui saat meminta .well-known. - + The required endpoints were not found. Possibly not a Matrix server. Titik akhir yang dibutuhkan tidak dapat ditemukan. Kemungkinan bukan server Matrix. - + Received malformed response. Make sure the homeserver domain is valid. Menerima respons cacat. Pastikan domain homeservernya valid. @@ -1389,7 +1420,7 @@ Misalnya: https://server.my:8787 Terjadi kesalahan yang tidak diketahui. Pastikan domain homeservernya valid. - + Password is not long enough (min 8 chars) Kata sandi kurang panjang (min. 8 karakter) @@ -1420,20 +1451,25 @@ Misalnya: https://server.my:8787 RoomDirectory - + Explore Public Rooms Temukan Ruangan Publik - + Search for public rooms Cari ruangan publik + + + Choose custom homeserver + + RoomInfo - + no version stored tidak ada versi yang disimpan @@ -1450,16 +1486,6 @@ Misalnya: https://server.my:8787 Enter the tag you want to use: Masukkan tanda yang Anda ingin gunakan: - - - Leave Room - Tinggalkan ruangan - - - - Are you sure you want to leave this room? - Apakah Anda yakin untuk meninggalkan ruangan ini? - Leave room @@ -1491,7 +1517,7 @@ Misalnya: https://server.my:8787 Membuat tanda baru… - + Status Message Pesan Status @@ -1516,7 +1542,30 @@ Misalnya: https://server.my:8787 Keluar - + + Encryption not set up + Cross-signing setup has not run yet. + + + + + Unverified login + The user just signed in with this device and hasn't verified their master key. + + + + + Please verify your other devices + There are unverified devices signed in to this account. + + + + + Close + Tutup + + + Start a new chat Mulai chat baru @@ -1544,12 +1593,12 @@ Misalnya: https://server.my:8787 RoomMembers - + Members of %1 Anggota dari %1 - + %n people in %1 Summary above list of members @@ -1585,12 +1634,12 @@ Misalnya: https://server.my:8787 RoomSettings - + Room Settings Pengaturan Ruangan - + %1 member(s) %1 anggota @@ -1696,12 +1745,12 @@ Misalnya: https://server.my:8787 Versi Ruangan - + Failed to enable encryption: %1 Gagal mengaktifkan enkripsi: %1 - + Select an avatar Pilih sebuah avatar @@ -1721,8 +1770,8 @@ Misalnya: https://server.my:8787 Terjadi kesalahan saat membaca file: %1 - - + + Failed to upload image: %s Gagal mengunggah gambar: %s @@ -1730,21 +1779,49 @@ Misalnya: https://server.my:8787 RoomlistModel - + Pending invite. Undangan tertunda. - + Previewing this room Menampilkan ruangan ini - + No preview available Tidak ada tampilan yang tersedia + + Root + + + Please enter your login password to continue: + + + + + Please enter a valid email address to continue: + + + + + Please enter a valid phone number to continue: + + + + + Please enter the token, which has been sent to you: + + + + + Wait for the confirmation link to arrive, then continue. + + + ScreenShare @@ -1802,20 +1879,98 @@ Misalnya: https://server.my:8787 SecretStorage - + Failed to connect to secret storage Gagal menghubungkan ke penyimpanan rahasia - Nheko could not connect to the secure storage to save encryption secrets to. This can have multiple reasons. Check if your D-Bus service is running and you have configured a service like KWallet, Gnome Secrets or the equivalent for your platform. If you are having trouble, feel free to open an issue here: https://github.com/Nheko-Reborn/nheko/issues - Nheko tidak dapat terhubung ke penyimpanan aman untuk menyimpan rahasia enkripsi. Ini dapat memiliki beberapa alasan. Periksa apakah layanan D-Bus Anda berjalan dan Anda telah mengonfigurasi layanan seperti KWallet, Gnome Secrets atau yang setara untuk platform Anda. Jika Anda mengalami masalah, silakan membuka masalah di sini: https://github.com/Nheko-Reborn/nheko/issues + Nheko could not connect to the secure storage to save encryption secrets to. This can have multiple reasons. Check if your D-Bus service is running and you have configured a service like KWallet, Gnome Keyring, KeePassXC or the equivalent for your platform. If you are having trouble, feel free to open an issue here: https://github.com/Nheko-Reborn/nheko/issues + + + + + SelfVerificationCheck + + + This is your recovery key. You will need it to restore access to your encrypted messages and verification keys. Keep this safe. Don't share it with anyone and don't lose it! Do not pass go! Do not collect $200! + + + + + Encryption setup successfully + + + + + Failed to setup encryption: %1 + + + + + Setup Encryption + + + + + Hello and welcome to Matrix! +It seems like you are new. Before you can securely encrypt your messages, we need to setup a few small things. You can either press accept immediately or adjust a few basic options. We also try to explain a few of the basics. You can skip those parts, but they might prove to be helpful! + + + + + Activate Encryption + + + + + It seems like you have encryption already configured for this account. To be able to access your encrypted messages and make this device appear as trusted, you can either verify an existing device or (if you have one) enter your recovery passphrase. Please select one of the options below. +If you choose verify, you need to have the other device available. If you choose "enter passphrase", you will need your recovery key or passphrase. If you click cancel, you can choose to verify yourself at a later point. + + + + + verify + + + + + enter passphrase + + + + + SelfVerificationStatus + + + Failed to create keys for cross-signing! + + + + + Failed to create keys for online key backup! + + + + + Failed to create keys secure server side secret storage! + + + + + Encryption Setup + + + + + Encryption setup failed: %1 + SingleImagePackModel - + Failed to update image pack: %1 Gagal memperbarui paket gambar: %1 @@ -1831,7 +1986,7 @@ Misalnya: https://server.my:8787 Gagal membuka gambar: %1 - + Failed to upload image: %1 Gagal mengunggah gambar: %1 @@ -1888,18 +2043,18 @@ Misalnya: https://server.my:8787 TimelineModel - + Message redaction failed: %1 Reaksi pesan gagal: %1 - + Failed to encrypt event, sending aborted! Gagal mendekripsikan peristiwa, pengiriman dihentikan! - + Save image Simpan gambar @@ -1919,7 +2074,7 @@ Misalnya: https://server.my:8787 Simpan file - + %1 and %2 are typing. Multiple users are typing. First argument is a comma separated list of potentially multiple users. Second argument is the last user of that list. (If only one user is typing, %1 is empty. You should still use it in your string though to silence Qt warnings.) @@ -1927,7 +2082,7 @@ Misalnya: https://server.my:8787 - + %1 opened the room to the public. %1 membuka ruangan ke publik. @@ -1967,12 +2122,12 @@ Misalnya: https://server.my:8787 %1 membuat sejarah ruangan bisa dilihat oleh anggota dari titik sekarang. - + %1 set the room history visible to members since they were invited. %1 membuat sejarah ruangan bisa dilihat oleh anggota yang telah diundang. - + %1 set the room history visible to members since they joined the room. %1 membuat sejarah ruangan bisa dilihat oleh anggota yang telah bergabung ke ruangan ini. @@ -1982,12 +2137,12 @@ Misalnya: https://server.my:8787 %1 telah mengubah izin ruangan. - + %1 was invited. %1 diundang. - + %1 changed their avatar. %1 mengubah avatarnya. @@ -2002,12 +2157,12 @@ Misalnya: https://server.my:8787 %1 bergabung. - + %1 joined via authorisation from %2's server. %1 bergabung via otorisasi dari servernya %2. - + %1 rejected their invite. %1 menolak undangannya. @@ -2037,27 +2192,27 @@ Misalnya: https://server.my:8787 %1 telah dicekal. - + Reason: %1 Alasan: %1 - + %1 redacted their knock. %1 menolak ketukannya. - + You joined this room. Anda bergabung ruangan ini. - + %1 has changed their avatar and changed their display name to %2. %1 mengubah avatarnya dan ubah nama tampilannya ke %2. - + %1 has changed their display name to %2. %1 mengubah nama tampilannya ke %2. @@ -2094,7 +2249,12 @@ Misalnya: https://server.my:8787 Tidak ada ruangan yang dibuka - + + No preview available + Tidak ada tampilan yang tersedia + + + %1 member(s) %1 anggota @@ -2119,28 +2279,20 @@ Misalnya: https://server.my:8787 Kembali ke daftar ruangan - - TimelineViewManager - - - No encrypted private chat found with this user. Create an encrypted private chat with this user and try again. - Tidak ada chat privat terenkripsi ditemukan dengan pengguna ini. Buat chat privat terenkripsi dengan pengguna ini dan coba lagi. - - TopBar - + Back to room list Kembali ke daftar ruangan - + No room selected Tidak ada ruangan yang dipilih - + This room is not encrypted! Ruangan ini tidak dienkripsi! @@ -2151,8 +2303,8 @@ Misalnya: https://server.my:8787 - This rooms contain verified devices and devices which have never changed their master key. - Ruangan ini berisi verifikasi yang telah diverifikasi dan perangkat yang tidak pernah mengubah kunci utamanya. + This room contains verified devices and devices which have never changed their master key. + @@ -2198,10 +2350,35 @@ Misalnya: https://server.my:8787 Tutup + + UIA + + + No available registration flows! + + + + + + + Registration aborted + + + + + Please enter a valid registration token. + Mohon masukkan token pendaftaran yang valid. + + + + Invalid token + + + UserProfile - + Global User Profile Profil Pengguna Global @@ -2211,7 +2388,7 @@ Misalnya: https://server.my:8787 Profil Pengguna di Ruangan - + Change avatar globally. Ubah avatar secara global. @@ -2221,7 +2398,7 @@ Misalnya: https://server.my:8787 Ubah avatar. Hanya diterapkan di ruangan ini. - + Change display name globally. Ubah nama tampilan secara global. @@ -2231,7 +2408,7 @@ Misalnya: https://server.my:8787 Ubah nama tampilan. Hanya diterapkan di ruangan ini. - + Room: %1 Ruangan: %1 @@ -2241,18 +2418,18 @@ Misalnya: https://server.my:8787 Ini adalah profile specifik ruangan. Nama pengguna dan avatar mungkin berbeda dari versi globalnya. - + Open the global profile for this user. Buka profil global untuk pengguna ini. - - + + Verify Verifikasi - + Start a private chat. Mulai chat privat. @@ -2267,12 +2444,42 @@ Misalnya: https://server.my:8787 Cekal pengguna ini. - + + Refresh device list. + + + + + Sign out this device. + + + + + Change device name. + + + + + Last seen %1 from %2 + + + + Unverify Hilangkan verifikasi - + + Sign out device %1 + + + + + You signed out this device. + + + + Select an avatar Pilih sebuah avatar @@ -2295,8 +2502,8 @@ Misalnya: https://server.my:8787 UserSettings - - + + Default Default @@ -2304,7 +2511,7 @@ Misalnya: https://server.my:8787 UserSettingsPage - + Minimize to tray Perkecil ke baki @@ -2314,22 +2521,22 @@ Misalnya: https://server.my:8787 Mulai di baki - + Group's sidebar Bilah samping grup - + Circular Avatars Avatar Bundar - + profile: %1 profil: %1 - + Default Default @@ -2354,7 +2561,7 @@ Misalnya: https://server.my:8787 UNDUH - + Keep the application running in the background after closing the client window. Membiarkan aplikasi berjalan di latar belakang setelah menutup jendela klien. @@ -2370,6 +2577,16 @@ OFF - square, ON - Circle. Ubah penampilan avatar pengguna di chat. MATI - persegi, NYALA - Lingkaran. + + + Use identicons + + + + + Display an identicon instead of a letter when a user has not set an avatar. + + Show a column containing groups and tags next to the room list. @@ -2400,7 +2617,7 @@ be blurred. akan diburamkan. - + Privacy screen timeout (in seconds [0 - 3600]) Waktu kehabisan layar privasi (dalam detik [0 - 3600]) @@ -2460,7 +2677,7 @@ Jika ini dimatikan, daftar ruangan hanya diurutkan dari waktu pesan terakhir di Jika ini dinyalakan, ruangan yang mempunyai notifikasi aktif (lingkaran kecil dengan nomor didalam) akan diurutkan di atas. Ruangan, yang dibisukan, masih diurutkan dari waktu, karena Anda tidak pertimangkan mereka sebagai penting dengan ruangan yang lain. - + Read receipts Laporan dibaca @@ -2472,7 +2689,7 @@ Status is displayed next to timestamps. Status akan ditampilkan disebelah waktu menerima pesan. - + Send messages as Markdown Kirim pesan sebagai Markdown @@ -2561,7 +2778,7 @@ Ini biasanya menyebabkan ikon aplikasi di bilah tugas untuk beranimasi.Unduh kunci enkripsi pesan dari dan unggah ke cadangan kunci online terenkripsi. - + Enable online key backup Aktifkan cadangan kunci online @@ -2571,7 +2788,7 @@ Ini biasanya menyebabkan ikon aplikasi di bilah tugas untuk beranimasi.Pengembang Nheko merekomendasikan untuk tidak mengaktifkan pencadangan kunci online hingga pencadangan kunci online simetris tersedia. Tetap mengaktifkan? - + CACHED DICACHE @@ -2581,7 +2798,7 @@ Ini biasanya menyebabkan ikon aplikasi di bilah tugas untuk beranimasi.TIDAK DICACHE - + Scale factor Faktor skala @@ -2656,7 +2873,7 @@ Ini biasanya menyebabkan ikon aplikasi di bilah tugas untuk beranimasi.Sidik Jari Perangkat - + Session Keys Kunci Perangkat @@ -2676,17 +2893,17 @@ Ini biasanya menyebabkan ikon aplikasi di bilah tugas untuk beranimasi.ENKRIPSI - + GENERAL UMUM - + INTERFACE ANTARMUKA - + Plays media like GIFs or WEBPs only when explicitly hovering over them. Memainkan media seperti GIF atau WEBP ketika kursor di atas medianya. @@ -2726,7 +2943,7 @@ Ini biasanya menyebabkan ikon aplikasi di bilah tugas untuk beranimasi.Kunci untuk memverifikasi pengguna lain. Jika dicache, memverifikasi sebuah pengguna akan memverifikasi semua perangkat mereka. - + Self signing key Kunci penandatanganan diri @@ -2756,14 +2973,14 @@ Ini biasanya menyebabkan ikon aplikasi di bilah tugas untuk beranimasi.Semua File (*) - + Open Sessions File Buka File Sesi - + @@ -2771,19 +2988,19 @@ Ini biasanya menyebabkan ikon aplikasi di bilah tugas untuk beranimasi.Kesalahan - - + + File Password Kata Sandi File - + Enter the passphrase to decrypt the file: Masukkan kata sandi untuk mendekripsi filenya: - + The password cannot be empty Kata sandi tidak boleh kosong @@ -2798,6 +3015,14 @@ Ini biasanya menyebabkan ikon aplikasi di bilah tugas untuk beranimasi.File untuk menyimpan kunci sesi yang telah diekspor + + VerificationManager + + + No encrypted private chat found with this user. Create an encrypted private chat with this user and try again. + Tidak ada chat privat terenkripsi ditemukan dengan pengguna ini. Buat chat privat terenkripsi dengan pengguna ini dan coba lagi. + + Waiting @@ -2852,7 +3077,7 @@ Ini biasanya menyebabkan ikon aplikasi di bilah tugas untuk beranimasi. descriptiveTime - + Yesterday Kemarin @@ -2923,37 +3148,6 @@ Ini biasanya menyebabkan ikon aplikasi di bilah tugas untuk beranimasi.Buka fallbacknya, ikuti petunjuknya dan konfirmasi setelah menyelesaikannya. - - dialogs::JoinRoom - - - Join - Bergabung - - - - Cancel - Batalkan - - - - Room ID or alias - ID ruangan atau alias - - - - dialogs::LeaveRoom - - - Cancel - Batalkan - - - - Are you sure you want to leave? - Apakah Anda yakin untuk meninggalkan ruangan? - - dialogs::Logout @@ -3020,47 +3214,47 @@ Ukuran media: %2 %1 mengirim klip audio - + You sent an image Anda mengirim sebuah pesan - + %1 sent an image %1 mengirim sebuah gambar - + You sent a file Anda mengirim sebuah file - + %1 sent a file %1 mengirim sebuah file - + You sent a video Anda mengirim sebuah video - + %1 sent a video %1 mengirim sebuah video - + You sent a sticker Anda mengirim sebuah stiker - + %1 sent a sticker %1 mengirim sebuah stiker - + You sent a notification Anda mengirim sebuah notifikasi @@ -3075,7 +3269,7 @@ Ukuran media: %2 Anda: %1 - + %1: %2 %1: %2 @@ -3095,27 +3289,27 @@ Ukuran media: %2 Anda melakukan panggilan - + %1 placed a call %1 melakukan panggilan - + You answered a call Anda menjawab panggilan - + %1 answered a call %1 menjawab panggilan - + You ended a call Anda mengakhiri panggilan - + %1 ended a call %1 mengakhiri panggilan @@ -3123,7 +3317,7 @@ Ukuran media: %2 utils - + Unknown Message Type Tipe Pesan Tidak Dikenal diff --git a/resources/langs/nheko_it.ts b/resources/langs/nheko_it.ts index da8e2dc4..17e466c8 100644 --- a/resources/langs/nheko_it.ts +++ b/resources/langs/nheko_it.ts @@ -4,7 +4,7 @@ ActiveCallBar - + Calling... Chiamata in corso... @@ -56,7 +56,7 @@ CallInvite - + Video Call Chiamata video @@ -74,7 +74,7 @@ CallInviteBar - + Video Call Chiamata Video @@ -117,7 +117,7 @@ CallManager - + Entire screen Schermo completo @@ -125,23 +125,23 @@ ChatPage - + Failed to invite user: %1 Impossibile invitare l'utente: %1 - + Invited user: %1 Invitato utente: %1 - + Migrating the cache to the current version failed. This can have different reasons. Please open an issue and try to use an older version in the mean time. Alternatively you can try deleting the cache manually. Migrazione della cache alla versione corrente fallita. Questo può avere diverse cause. Per favore apri una issue e nel frattempo prova ad usare una versione più vecchia. In alternativa puoi provare a cancellare la cache manualmente. - + Confirm join Conferma collegamento @@ -151,23 +151,23 @@ Vuoi davvero collegarti a %1? - + Room %1 created. Stanza %1 creata. - - + + Confirm invite Conferma Invito - + Do you really want to invite %1 (%2)? Vuoi davvero inviare %1 (%2)? - + Failed to invite %1 to %2: %3 Impossibile invitare %1 a %2: %3 @@ -182,7 +182,7 @@ Vuoi davvero allontanare %1 (%2)? - + Kicked user: %1 Scacciato utente: %1 @@ -197,7 +197,7 @@ Vuoi veramente bannare %1 (%2)? - + Failed to ban %1 in %2: %3 Impossibile bannare %1 in %2: %3 @@ -217,7 +217,7 @@ Vuoi veramente reintegrare %1 (%2)? - + Failed to unban %1 in %2: %3 Impossibile rimuovere il ban di %1 in %2: %3 @@ -227,12 +227,12 @@ Rimosso il ban dall'utente: %1 - + Do you really want to start a private chat with %1? Sei sicuro di voler avviare una chat privata con %1? - + Cache migration failed! Migrazione della cache fallita! @@ -259,23 +259,23 @@ Impossibile ripristinare i dati salvati. Per favore accedi nuovamente. - + Failed to setup encryption keys. Server response: %1 %2. Please try again later. Impossibile configurare le chiavi crittografiche. Risposta del server: %1 %2. Per favore riprova in seguito. - - + + Please try to login again: %1 Per favore prova ad accedere nuovamente: %1 - + Failed to join room: %1 Impossibile accedere alla stanza: %1 - + You joined the room Sei entrato nella stanza @@ -285,17 +285,17 @@ Impossibile rimuovere l'invito: %1 - + Room creation failed: %1 Creazione della stanza fallita: %1 - + Failed to leave room: %1 Impossibile lasciare la stanza: %1 - + Failed to kick %1 from %2: %3 Fallita l'espulsione di %1 da %2: %3 @@ -354,7 +354,7 @@ CrossSigningSecrets - + Decrypt secrets Decifra i segreti @@ -364,12 +364,12 @@ Inserisci la chiave di recupero o la parola chiave per decriptare i tuoi segreti: - + Enter your recovery key or passphrase called %1 to decrypt your secrets: Inserisci la tua chiave di recupero o la parola chiave chiamata %1 per decifrare i tuoi segreti: - + Decryption failed Decrittazione fallita @@ -581,17 +581,26 @@ - Device verification timed out. Tempo di verifica del dispositivo scaduto. - + Other party canceled the verification. L'altra parte ha annullato la verifica. - + + Verification messages received out of order! + + + + + Unknown verification error. + + + + Close Chiudi @@ -612,7 +621,7 @@ - + Add images @@ -622,7 +631,7 @@ - + State key @@ -638,13 +647,13 @@ - + Use as Emoji - - + + Use as Sticker @@ -697,7 +706,7 @@ - + Private pack @@ -712,7 +721,7 @@ - + Enable globally @@ -735,7 +744,7 @@ InputBar - + Select a file Seleziona un file @@ -745,7 +754,7 @@ Tutti i File (*) - + Failed to upload media. Please try again. Impossibile inviare il file multimediale. Per favore riprova. @@ -753,7 +762,7 @@ InviteDialog - + Invite users to %1 @@ -784,6 +793,32 @@ Annulla + + JoinRoomDialog + + + Join room + + + + + Room ID or alias + ID della stanza o alias + + + + LeaveRoomDialog + + + Leave room + Lascia la stanza + + + + Are you sure you want to leave? + Sei sicuro di voler uscire? + + LoginPage @@ -850,25 +885,25 @@ Esempio: https://server.mio:8787 ACCEDI - + - + You have entered an invalid Matrix ID e.g @joe:matrix.org Hai inserito un ID Matrix non valido, es @joe:matrix.org - + Autodiscovery failed. Received malformed response. Ricerca automatica fallita. Ricevuta risposta malformata. - + Autodiscovery failed. Unknown error when requesting .well-known. Ricerca automatica fallita. Errore ignoto durante la richiesta di .well-known. - + The required endpoints were not found. Possibly not a Matrix server. Gli endpoint richiesti non sono stati trovati. Forse non è un server Matrix. @@ -878,26 +913,44 @@ Esempio: https://server.mio:8787 Ricevuta risposta malformata. Assicurati che il dominio dell'homeserver sia valido. - + An unknown error occured. Make sure the homeserver domain is valid. Avvenuto un errore sconosciuto. Assicurati che il dominio dell'homeserver sia valido. - + SSO LOGIN ACCESSO SSO - + Empty password Password vuota - + SSO login failed Accesso SSO fallito + + LogoutDialog + + + Log out + + + + + A call is in progress. Log out? + + + + + Are you sure you want to log out? + + + MessageDelegate @@ -1023,7 +1076,7 @@ Esempio: https://server.mio:8787 MessageView - + Edit Modifica @@ -1043,7 +1096,7 @@ Esempio: https://server.mio:8787 Opzioni - + &Copy @@ -1133,7 +1186,12 @@ Esempio: https://server.mio:8787 Richiesta di verifica ricevuta - + + To allow other users to see, which of your devices actually belong to you, you can verify them. This also allows key backup to work automatically. Verify an unverified device now? (Please make sure you have one of those devices available.) + + + + To allow other users to see, which of your devices actually belong to you, you can verify them. This also allows key backup to work automatically. Verify %1 now? Per permettere agli altri utenti di vedere che dispositivi ti appartengono, puoi verificarli. Questo inoltre permette alle chiavi di recupero di funzionare automaticamente. Verificare %1 adesso? @@ -1183,7 +1241,7 @@ Verificare %1 adesso? NotificationWarning - You will be pinging the whole room + You are about to notify the whole room @@ -1191,29 +1249,17 @@ Verificare %1 adesso? NotificationsManager - - + + %1 sent an encrypted message %1 ha inviato un messaggio criptato - - * %1 %2 - Format an emote message in a notification, %1 is the sender, %2 the message - * %1 %2 - - - + %1 replied: %2 Format a reply in a notification. %1 is the sender, %2 the message Risposta di %1: %2 - - - %1: %2 - Format a normal message in a notification. %1 is the sender, %2 the message - %1: %2 - @@ -1244,7 +1290,7 @@ Verificare %1 adesso? Nessun microfono trovato. - + Voice @@ -1275,7 +1321,7 @@ Verificare %1 adesso? QCoreApplication - + Create a unique profile, which allows you to log into several accounts at the same time and start multiple instances of nheko. @@ -1293,7 +1339,7 @@ Verificare %1 adesso? ReadReceipts - + Read receipts Ricevute di lettura @@ -1301,7 +1347,7 @@ Verificare %1 adesso? ReadReceiptsModel - + Yesterday, %1 @@ -1309,18 +1355,18 @@ Verificare %1 adesso? RegisterPage - + Username Nome utente - + The username must not be empty, and must contain only the characters a-z, 0-9, ., _, =, -, and /. Il nome utente non deve essere vuoto e deve contenere solo i caratteri a-z, 0-9, ., _, =, -, e /. - + Password Password @@ -1350,37 +1396,22 @@ Verificare %1 adesso? REGISTRATI - - No supported registration flows! - Non ci sono processi di registrazione supportati! - - - - Registration token - - - - - Please enter a valid registration token. - - - - + Autodiscovery failed. Received malformed response. Ricerca automatica fallita. Ricevuta risposta malformata. - + Autodiscovery failed. Unknown error when requesting .well-known. Ricerca automatica fallita. Errore ignoto durante la richiesta di .well-known. - + The required endpoints were not found. Possibly not a Matrix server. Gli endpoint richiesti non sono stati trovati. Forse non è un server Matrix. - + Received malformed response. Make sure the homeserver domain is valid. Ricevuta risposta malformata. Assicurati che il dominio dell'homeserver sia valido. @@ -1390,7 +1421,7 @@ Verificare %1 adesso? Avvenuto un errore sconosciuto. Assicurati che il dominio dell'homeserver sia valido. - + Password is not long enough (min 8 chars) La password non è abbastanza lunga (minimo 8 caratteri) @@ -1421,20 +1452,25 @@ Verificare %1 adesso? RoomDirectory - + Explore Public Rooms - + Search for public rooms + + + Choose custom homeserver + + RoomInfo - + no version stored nessuna versione memorizzata @@ -1451,16 +1487,6 @@ Verificare %1 adesso? Enter the tag you want to use: - - - Leave Room - - - - - Are you sure you want to leave this room? - - Leave room @@ -1492,7 +1518,7 @@ Verificare %1 adesso? - + Status Message @@ -1517,7 +1543,30 @@ Verificare %1 adesso? Disconnettiti - + + Encryption not set up + Cross-signing setup has not run yet. + + + + + Unverified login + The user just signed in with this device and hasn't verified their master key. + + + + + Please verify your other devices + There are unverified devices signed in to this account. + + + + + Close + Chiudi + + + Start a new chat Inizia una nuova discussione @@ -1545,12 +1594,12 @@ Verificare %1 adesso? RoomMembers - + Members of %1 - + %n people in %1 Summary above list of members @@ -1587,12 +1636,12 @@ Verificare %1 adesso? RoomSettings - + Room Settings - + %1 member(s) @@ -1698,12 +1747,12 @@ Verificare %1 adesso? - + Failed to enable encryption: %1 Impossibile abilitare la crittografia: %1 - + Select an avatar Scegli un avatar @@ -1723,8 +1772,8 @@ Verificare %1 adesso? Errore durante la lettura del file: %1 - - + + Failed to upload image: %s Impossibile fare l'upload dell'immagine: %s @@ -1732,21 +1781,49 @@ Verificare %1 adesso? RoomlistModel - + Pending invite. - + Previewing this room - + No preview available + + Root + + + Please enter your login password to continue: + + + + + Please enter a valid email address to continue: + + + + + Please enter a valid phone number to continue: + + + + + Please enter the token, which has been sent to you: + + + + + Wait for the confirmation link to arrive, then continue. + + + ScreenShare @@ -1804,20 +1881,98 @@ Verificare %1 adesso? SecretStorage - + Failed to connect to secret storage - Nheko could not connect to the secure storage to save encryption secrets to. This can have multiple reasons. Check if your D-Bus service is running and you have configured a service like KWallet, Gnome Secrets or the equivalent for your platform. If you are having trouble, feel free to open an issue here: https://github.com/Nheko-Reborn/nheko/issues + Nheko could not connect to the secure storage to save encryption secrets to. This can have multiple reasons. Check if your D-Bus service is running and you have configured a service like KWallet, Gnome Keyring, KeePassXC or the equivalent for your platform. If you are having trouble, feel free to open an issue here: https://github.com/Nheko-Reborn/nheko/issues + + + + + SelfVerificationCheck + + + This is your recovery key. You will need it to restore access to your encrypted messages and verification keys. Keep this safe. Don't share it with anyone and don't lose it! Do not pass go! Do not collect $200! + + + + + Encryption setup successfully + + + + + Failed to setup encryption: %1 + + + + + Setup Encryption + + + + + Hello and welcome to Matrix! +It seems like you are new. Before you can securely encrypt your messages, we need to setup a few small things. You can either press accept immediately or adjust a few basic options. We also try to explain a few of the basics. You can skip those parts, but they might prove to be helpful! + + + + + Activate Encryption + + + + + It seems like you have encryption already configured for this account. To be able to access your encrypted messages and make this device appear as trusted, you can either verify an existing device or (if you have one) enter your recovery passphrase. Please select one of the options below. +If you choose verify, you need to have the other device available. If you choose "enter passphrase", you will need your recovery key or passphrase. If you click cancel, you can choose to verify yourself at a later point. + + + + + verify + + + + + enter passphrase + + + + + SelfVerificationStatus + + + Failed to create keys for cross-signing! + + + + + Failed to create keys for online key backup! + + + + + Failed to create keys secure server side secret storage! + + + + + Encryption Setup + + + + + Encryption setup failed: %1 SingleImagePackModel - + Failed to update image pack: %1 @@ -1833,7 +1988,7 @@ Verificare %1 adesso? - + Failed to upload image: %1 @@ -1890,18 +2045,18 @@ Verificare %1 adesso? TimelineModel - + Message redaction failed: %1 Oscuramento del messaggio fallito: %1 - + Failed to encrypt event, sending aborted! - + Save image Salva immagine @@ -1921,7 +2076,7 @@ Verificare %1 adesso? Salva file - + %1 and %2 are typing. Multiple users are typing. First argument is a comma separated list of potentially multiple users. Second argument is the last user of that list. (If only one user is typing, %1 is empty. You should still use it in your string though to silence Qt warnings.) @@ -1930,7 +2085,7 @@ Verificare %1 adesso? - + %1 opened the room to the public. %1 ha aperto la stanza al pubblico. @@ -1970,12 +2125,12 @@ Verificare %1 adesso? %1 ha reso la cronologia della stanza visibile ai membri da questo momento in poi. - + %1 set the room history visible to members since they were invited. %1 ha reso la cronologia della stanza visibile ai membri dal momento in cui sono stati invitati. - + %1 set the room history visible to members since they joined the room. %1 ha reso la cronologia della stanza visibile ai membri dal momento in cui sono entrati nella stanza. @@ -1985,12 +2140,12 @@ Verificare %1 adesso? %1 ha cambiato i permessi della stanza. - + %1 was invited. %1 è stato invitato. - + %1 changed their avatar. %1 ha cambiato il suo avatar. @@ -2005,12 +2160,12 @@ Verificare %1 adesso? %1 è entrato. - + %1 joined via authorisation from %2's server. - + %1 rejected their invite. %1 ha rifiutato il suo invito. @@ -2040,27 +2195,27 @@ Verificare %1 adesso? %1 è stato bannato. - + Reason: %1 - + %1 redacted their knock. %1 ha oscurato la sua bussata. - + You joined this room. Sei entrato in questa stanza. - + %1 has changed their avatar and changed their display name to %2. - + %1 has changed their display name to %2. @@ -2097,7 +2252,12 @@ Verificare %1 adesso? Nessuna stanza aperta - + + No preview available + + + + %1 member(s) @@ -2122,28 +2282,20 @@ Verificare %1 adesso? - - TimelineViewManager - - - No encrypted private chat found with this user. Create an encrypted private chat with this user and try again. - - - TopBar - + Back to room list - + No room selected - + This room is not encrypted! @@ -2154,7 +2306,7 @@ Verificare %1 adesso? - This rooms contain verified devices and devices which have never changed their master key. + This room contains verified devices and devices which have never changed their master key. @@ -2201,10 +2353,35 @@ Verificare %1 adesso? Esci + + UIA + + + No available registration flows! + + + + + + + Registration aborted + + + + + Please enter a valid registration token. + + + + + Invalid token + + + UserProfile - + Global User Profile @@ -2214,7 +2391,7 @@ Verificare %1 adesso? - + Change avatar globally. @@ -2224,7 +2401,7 @@ Verificare %1 adesso? - + Change display name globally. @@ -2234,7 +2411,7 @@ Verificare %1 adesso? - + Room: %1 @@ -2244,18 +2421,18 @@ Verificare %1 adesso? - + Open the global profile for this user. - - + + Verify - + Start a private chat. @@ -2270,12 +2447,42 @@ Verificare %1 adesso? - + + Refresh device list. + + + + + Sign out this device. + + + + + Change device name. + + + + + Last seen %1 from %2 + + + + Unverify - + + Sign out device %1 + + + + + You signed out this device. + + + + Select an avatar Scegli un avatar @@ -2298,8 +2505,8 @@ Verificare %1 adesso? UserSettings - - + + Default @@ -2307,7 +2514,7 @@ Verificare %1 adesso? UserSettingsPage - + Minimize to tray Minimizza nella tray @@ -2317,22 +2524,22 @@ Verificare %1 adesso? Avvia nella tray - + Group's sidebar Barra laterale dei gruppi - + Circular Avatars Avatar Circolari - + profile: %1 - + Default @@ -2357,7 +2564,7 @@ Verificare %1 adesso? - + Keep the application running in the background after closing the client window. @@ -2372,6 +2579,16 @@ Verificare %1 adesso? OFF - square, ON - Circle. + + + Use identicons + + + + + Display an identicon instead of a letter when a user has not set an avatar. + + Show a column containing groups and tags next to the room list. @@ -2400,7 +2617,7 @@ be blurred. - + Privacy screen timeout (in seconds [0 - 3600]) @@ -2455,7 +2672,7 @@ If this is on, rooms which have active notifications (the small circle with a nu - + Read receipts Ricevute di lettura @@ -2466,7 +2683,7 @@ Status is displayed next to timestamps. - + Send messages as Markdown Invia messaggi come Markdown @@ -2553,7 +2770,7 @@ This usually causes the application icon in the task bar to animate in some fash - + Enable online key backup @@ -2563,7 +2780,7 @@ This usually causes the application icon in the task bar to animate in some fash - + CACHED @@ -2573,7 +2790,7 @@ This usually causes the application icon in the task bar to animate in some fash - + Scale factor Fattore di scala @@ -2648,7 +2865,7 @@ This usually causes the application icon in the task bar to animate in some fash Impronta digitale del dispositivo - + Session Keys Chiavi di Sessione @@ -2668,17 +2885,17 @@ This usually causes the application icon in the task bar to animate in some fash CRITTOGRAFIA - + GENERAL GENERALE - + INTERFACE INTERFACCIA - + Plays media like GIFs or WEBPs only when explicitly hovering over them. @@ -2718,7 +2935,7 @@ This usually causes the application icon in the task bar to animate in some fash - + Self signing key @@ -2748,14 +2965,14 @@ This usually causes the application icon in the task bar to animate in some fash Tutti i File (*) - + Open Sessions File Apri File delle Sessioni - + @@ -2763,19 +2980,19 @@ This usually causes the application icon in the task bar to animate in some fash Errore - - + + File Password Password del File - + Enter the passphrase to decrypt the file: Inserisci la passphrase per decriptare il file: - + The password cannot be empty La password non può essere vuota @@ -2790,6 +3007,14 @@ This usually causes the application icon in the task bar to animate in some fash File ove salvare le chiavi di sessione esportate + + VerificationManager + + + No encrypted private chat found with this user. Create an encrypted private chat with this user and try again. + + + Waiting @@ -2844,7 +3069,7 @@ This usually causes the application icon in the task bar to animate in some fash descriptiveTime - + Yesterday Ieri @@ -2915,37 +3140,6 @@ This usually causes the application icon in the task bar to animate in some fash Apri il ripiego, segui i passaggi e conferma dopo averli completati. - - dialogs::JoinRoom - - - Join - Entra - - - - Cancel - Annulla - - - - Room ID or alias - ID della stanza o alias - - - - dialogs::LeaveRoom - - - Cancel - Annulla - - - - Are you sure you want to leave? - Sei sicuro di voler uscire? - - dialogs::Logout @@ -3012,47 +3206,47 @@ Peso media: %2 %1 ha inviato una clip audio - + You sent an image Hai inviato un'immagine - + %1 sent an image %1 ha inviato un'immagine - + You sent a file Hai inviato un file - + %1 sent a file %1 ha inviato un file - + You sent a video Hai inviato un video - + %1 sent a video %1 ha inviato un video - + You sent a sticker Hai inviato uno sticker - + %1 sent a sticker %1 ha inviato uno sticker - + You sent a notification Hai inviato una notifica @@ -3067,7 +3261,7 @@ Peso media: %2 Tu: %1 - + %1: %2 %1: %2 @@ -3087,27 +3281,27 @@ Peso media: %2 - + %1 placed a call - + You answered a call - + %1 answered a call - + You ended a call - + %1 ended a call @@ -3115,7 +3309,7 @@ Peso media: %2 utils - + Unknown Message Type Tipo di Messaggio sconosciuto diff --git a/resources/langs/nheko_ja.ts b/resources/langs/nheko_ja.ts index 24817395..7ae33c58 100644 --- a/resources/langs/nheko_ja.ts +++ b/resources/langs/nheko_ja.ts @@ -4,7 +4,7 @@ ActiveCallBar - + Calling... @@ -56,7 +56,7 @@ CallInvite - + Video Call @@ -74,7 +74,7 @@ CallInviteBar - + Video Call @@ -117,7 +117,7 @@ CallManager - + Entire screen @@ -125,23 +125,23 @@ ChatPage - + Failed to invite user: %1 ユーザーを招待できませんでした: %1 - + Invited user: %1 招待されたユーザー: %1 - + Migrating the cache to the current version failed. This can have different reasons. Please open an issue and try to use an older version in the mean time. Alternatively you can try deleting the cache manually. - + Confirm join @@ -151,23 +151,23 @@ - + Room %1 created. - - + + Confirm invite - + Do you really want to invite %1 (%2)? - + Failed to invite %1 to %2: %3 %2に%1を招待できませんでした: %3 @@ -182,7 +182,7 @@ - + Kicked user: %1 一時的に追放されたユーザー: %1 @@ -197,7 +197,7 @@ - + Failed to ban %1 in %2: %3 %2で%1を永久追放できませんでした: %3 @@ -217,7 +217,7 @@ - + Failed to unban %1 in %2: %3 %2で%1の永久追放を解除できませんでした: %3 @@ -227,12 +227,12 @@ 永久追放を解除されたユーザー: %1 - + Do you really want to start a private chat with %1? - + Cache migration failed! @@ -259,23 +259,23 @@ セーブデータを復元できませんでした。もう一度ログインして下さい。 - + Failed to setup encryption keys. Server response: %1 %2. Please try again later. 暗号化鍵を設定できませんでした。サーバーの応答: %1 %2. 後でやり直して下さい。 - - + + Please try to login again: %1 もう一度ログインしてみて下さい: %1 - + Failed to join room: %1 部屋に参加できませんでした: %1 - + You joined the room 部屋に参加しました @@ -285,17 +285,17 @@ 招待を削除できませんでした: %1 - + Room creation failed: %1 部屋を作成できませんでした: %1 - + Failed to leave room: %1 部屋から出られませんでした: %1 - + Failed to kick %1 from %2: %3 @@ -354,7 +354,7 @@ CrossSigningSecrets - + Decrypt secrets @@ -364,12 +364,12 @@ - + Enter your recovery key or passphrase called %1 to decrypt your secrets: - + Decryption failed @@ -581,17 +581,26 @@ - Device verification timed out. - + Other party canceled the verification. - + + Verification messages received out of order! + + + + + Unknown verification error. + + + + Close 閉じる @@ -612,7 +621,7 @@ - + Add images @@ -622,7 +631,7 @@ - + State key @@ -638,13 +647,13 @@ - + Use as Emoji - - + + Use as Sticker @@ -697,7 +706,7 @@ - + Private pack @@ -712,7 +721,7 @@ - + Enable globally @@ -735,7 +744,7 @@ InputBar - + Select a file ファイルを選択 @@ -745,7 +754,7 @@ 全てのファイル (*) - + Failed to upload media. Please try again. メディアをアップロードできませんでした。やり直して下さい。 @@ -753,7 +762,7 @@ InviteDialog - + Invite users to %1 @@ -784,6 +793,32 @@ キャンセル + + JoinRoomDialog + + + Join room + + + + + Room ID or alias + 部屋のID又は別名 + + + + LeaveRoomDialog + + + Leave room + 部屋を出る + + + + Are you sure you want to leave? + 本当に退出しますか? + + LoginPage @@ -846,25 +881,25 @@ Example: https://server.my:8787 ログイン - + - + You have entered an invalid Matrix ID e.g @joe:matrix.org - + Autodiscovery failed. Received malformed response. 自動検出できませんでした。不正な形式の応答を受信しました。 - + Autodiscovery failed. Unknown error when requesting .well-known. 自動検出できませんでした。.well-known要求時の不明なエラー。 - + The required endpoints were not found. Possibly not a Matrix server. 必要な端点が見つかりません。Matrixサーバーではないかもしれません。 @@ -874,26 +909,44 @@ Example: https://server.my:8787 不正な形式の応答を受信しました。ホームサーバーのドメイン名が有効であるかを確認して下さい。 - + An unknown error occured. Make sure the homeserver domain is valid. 不明なエラーが発生しました。ホームサーバーのドメイン名が有効であるかを確認して下さい。 - + SSO LOGIN - + Empty password パスワードが入力されていません - + SSO login failed + + LogoutDialog + + + Log out + + + + + A call is in progress. Log out? + + + + + Are you sure you want to log out? + + + MessageDelegate @@ -1019,7 +1072,7 @@ Example: https://server.my:8787 MessageView - + Edit @@ -1039,7 +1092,7 @@ Example: https://server.my:8787 オプション - + &Copy @@ -1129,7 +1182,12 @@ Example: https://server.my:8787 - + + To allow other users to see, which of your devices actually belong to you, you can verify them. This also allows key backup to work automatically. Verify an unverified device now? (Please make sure you have one of those devices available.) + + + + To allow other users to see, which of your devices actually belong to you, you can verify them. This also allows key backup to work automatically. Verify %1 now? @@ -1178,7 +1236,7 @@ Example: https://server.my:8787 NotificationWarning - You will be pinging the whole room + You are about to notify the whole room @@ -1186,29 +1244,17 @@ Example: https://server.my:8787 NotificationsManager - - + + %1 sent an encrypted message %1が暗号化されたメッセージを送信しました - - * %1 %2 - Format an emote message in a notification, %1 is the sender, %2 the message - - - - + %1 replied: %2 Format a reply in a notification. %1 is the sender, %2 the message - - - %1: %2 - Format a normal message in a notification. %1 is the sender, %2 the message - %1: %2 - @@ -1239,7 +1285,7 @@ Example: https://server.my:8787 - + Voice @@ -1270,7 +1316,7 @@ Example: https://server.my:8787 QCoreApplication - + Create a unique profile, which allows you to log into several accounts at the same time and start multiple instances of nheko. @@ -1288,7 +1334,7 @@ Example: https://server.my:8787 ReadReceipts - + Read receipts 開封確認 @@ -1296,7 +1342,7 @@ Example: https://server.my:8787 ReadReceiptsModel - + Yesterday, %1 @@ -1304,18 +1350,18 @@ Example: https://server.my:8787 RegisterPage - + Username ユーザー名 - + The username must not be empty, and must contain only the characters a-z, 0-9, ., _, =, -, and /. - + Password パスワード @@ -1345,37 +1391,22 @@ Example: https://server.my:8787 登録 - - No supported registration flows! - - - - - Registration token - - - - - Please enter a valid registration token. - - - - + Autodiscovery failed. Received malformed response. 自動検出できませんでした。不正な形式の応答を受信しました。 - + Autodiscovery failed. Unknown error when requesting .well-known. 自動検出できませんでした。.well-known要求時の不明なエラー。 - + The required endpoints were not found. Possibly not a Matrix server. 必要な端点が見つかりません。Matrixサーバーではないかもしれません。 - + Received malformed response. Make sure the homeserver domain is valid. 不正な形式の応答を受信しました。ホームサーバーのドメイン名が有効であるかを確認して下さい。 @@ -1385,7 +1416,7 @@ Example: https://server.my:8787 不明なエラーが発生しました。ホームサーバーのドメイン名が有効であるかを確認して下さい。 - + Password is not long enough (min 8 chars) パスワード長が不足しています (最小8文字) @@ -1416,20 +1447,25 @@ Example: https://server.my:8787 RoomDirectory - + Explore Public Rooms - + Search for public rooms + + + Choose custom homeserver + + RoomInfo - + no version stored バージョンが保存されていません @@ -1446,16 +1482,6 @@ Example: https://server.my:8787 Enter the tag you want to use: - - - Leave Room - - - - - Are you sure you want to leave this room? - - Leave room @@ -1487,7 +1513,7 @@ Example: https://server.my:8787 - + Status Message @@ -1512,7 +1538,30 @@ Example: https://server.my:8787 ログアウト - + + Encryption not set up + Cross-signing setup has not run yet. + + + + + Unverified login + The user just signed in with this device and hasn't verified their master key. + + + + + Please verify your other devices + There are unverified devices signed in to this account. + + + + + Close + 閉じる + + + Start a new chat 新しいチャットを開始 @@ -1540,12 +1589,12 @@ Example: https://server.my:8787 RoomMembers - + Members of %1 - + %n people in %1 Summary above list of members @@ -1581,12 +1630,12 @@ Example: https://server.my:8787 RoomSettings - + Room Settings - + %1 member(s) @@ -1692,12 +1741,12 @@ Example: https://server.my:8787 - + Failed to enable encryption: %1 暗号化を有効にできませんでした: %1 - + Select an avatar アバターを選択 @@ -1717,8 +1766,8 @@ Example: https://server.my:8787 ファイルの読み込み時にエラーが発生しました: %1 - - + + Failed to upload image: %s 画像をアップロードできませんでした: %s @@ -1726,21 +1775,49 @@ Example: https://server.my:8787 RoomlistModel - + Pending invite. - + Previewing this room - + No preview available + + Root + + + Please enter your login password to continue: + + + + + Please enter a valid email address to continue: + + + + + Please enter a valid phone number to continue: + + + + + Please enter the token, which has been sent to you: + + + + + Wait for the confirmation link to arrive, then continue. + + + ScreenShare @@ -1798,20 +1875,98 @@ Example: https://server.my:8787 SecretStorage - + Failed to connect to secret storage - Nheko could not connect to the secure storage to save encryption secrets to. This can have multiple reasons. Check if your D-Bus service is running and you have configured a service like KWallet, Gnome Secrets or the equivalent for your platform. If you are having trouble, feel free to open an issue here: https://github.com/Nheko-Reborn/nheko/issues + Nheko could not connect to the secure storage to save encryption secrets to. This can have multiple reasons. Check if your D-Bus service is running and you have configured a service like KWallet, Gnome Keyring, KeePassXC or the equivalent for your platform. If you are having trouble, feel free to open an issue here: https://github.com/Nheko-Reborn/nheko/issues + + + + + SelfVerificationCheck + + + This is your recovery key. You will need it to restore access to your encrypted messages and verification keys. Keep this safe. Don't share it with anyone and don't lose it! Do not pass go! Do not collect $200! + + + + + Encryption setup successfully + + + + + Failed to setup encryption: %1 + + + + + Setup Encryption + + + + + Hello and welcome to Matrix! +It seems like you are new. Before you can securely encrypt your messages, we need to setup a few small things. You can either press accept immediately or adjust a few basic options. We also try to explain a few of the basics. You can skip those parts, but they might prove to be helpful! + + + + + Activate Encryption + + + + + It seems like you have encryption already configured for this account. To be able to access your encrypted messages and make this device appear as trusted, you can either verify an existing device or (if you have one) enter your recovery passphrase. Please select one of the options below. +If you choose verify, you need to have the other device available. If you choose "enter passphrase", you will need your recovery key or passphrase. If you click cancel, you can choose to verify yourself at a later point. + + + + + verify + + + + + enter passphrase + + + + + SelfVerificationStatus + + + Failed to create keys for cross-signing! + + + + + Failed to create keys for online key backup! + + + + + Failed to create keys secure server side secret storage! + + + + + Encryption Setup + + + + + Encryption setup failed: %1 SingleImagePackModel - + Failed to update image pack: %1 @@ -1827,7 +1982,7 @@ Example: https://server.my:8787 - + Failed to upload image: %1 @@ -1884,18 +2039,18 @@ Example: https://server.my:8787 TimelineModel - + Message redaction failed: %1 メッセージを編集できませんでした: %1 - + Failed to encrypt event, sending aborted! - + Save image 画像を保存 @@ -1915,7 +2070,7 @@ Example: https://server.my:8787 ファイルを保存 - + %1 and %2 are typing. Multiple users are typing. First argument is a comma separated list of potentially multiple users. Second argument is the last user of that list. (If only one user is typing, %1 is empty. You should still use it in your string though to silence Qt warnings.) @@ -1923,7 +2078,7 @@ Example: https://server.my:8787 - + %1 opened the room to the public. @@ -1963,12 +2118,12 @@ Example: https://server.my:8787 - + %1 set the room history visible to members since they were invited. - + %1 set the room history visible to members since they joined the room. @@ -1978,12 +2133,12 @@ Example: https://server.my:8787 - + %1 was invited. %1が招待されました。 - + %1 changed their avatar. %1がアバターを変更しました。 @@ -1998,12 +2153,12 @@ Example: https://server.my:8787 %1が参加しました。 - + %1 joined via authorisation from %2's server. - + %1 rejected their invite. %1が招待を拒否しました。 @@ -2033,27 +2188,27 @@ Example: https://server.my:8787 - + Reason: %1 - + %1 redacted their knock. %1がノックを編集しました。 - + You joined this room. - + %1 has changed their avatar and changed their display name to %2. - + %1 has changed their display name to %2. @@ -2090,7 +2245,12 @@ Example: https://server.my:8787 部屋が開いていません - + + No preview available + + + + %1 member(s) @@ -2115,28 +2275,20 @@ Example: https://server.my:8787 - - TimelineViewManager - - - No encrypted private chat found with this user. Create an encrypted private chat with this user and try again. - - - TopBar - + Back to room list - + No room selected - + This room is not encrypted! @@ -2147,7 +2299,7 @@ Example: https://server.my:8787 - This rooms contain verified devices and devices which have never changed their master key. + This room contains verified devices and devices which have never changed their master key. @@ -2194,10 +2346,35 @@ Example: https://server.my:8787 終了 + + UIA + + + No available registration flows! + + + + + + + Registration aborted + + + + + Please enter a valid registration token. + + + + + Invalid token + + + UserProfile - + Global User Profile @@ -2207,7 +2384,7 @@ Example: https://server.my:8787 - + Change avatar globally. @@ -2217,7 +2394,7 @@ Example: https://server.my:8787 - + Change display name globally. @@ -2227,7 +2404,7 @@ Example: https://server.my:8787 - + Room: %1 @@ -2237,18 +2414,18 @@ Example: https://server.my:8787 - + Open the global profile for this user. - - + + Verify - + Start a private chat. @@ -2263,12 +2440,42 @@ Example: https://server.my:8787 - + + Refresh device list. + + + + + Sign out this device. + + + + + Change device name. + + + + + Last seen %1 from %2 + + + + Unverify - + + Sign out device %1 + + + + + You signed out this device. + + + + Select an avatar アバターを選択 @@ -2291,8 +2498,8 @@ Example: https://server.my:8787 UserSettings - - + + Default @@ -2300,7 +2507,7 @@ Example: https://server.my:8787 UserSettingsPage - + Minimize to tray トレイへ最小化 @@ -2310,22 +2517,22 @@ Example: https://server.my:8787 トレイで起動 - + Group's sidebar グループサイドバー - + Circular Avatars 円形アバター - + profile: %1 - + Default @@ -2350,7 +2557,7 @@ Example: https://server.my:8787 - + Keep the application running in the background after closing the client window. @@ -2365,6 +2572,16 @@ Example: https://server.my:8787 OFF - square, ON - Circle. + + + Use identicons + + + + + Display an identicon instead of a letter when a user has not set an avatar. + + Show a column containing groups and tags next to the room list. @@ -2393,7 +2610,7 @@ be blurred. - + Privacy screen timeout (in seconds [0 - 3600]) @@ -2448,7 +2665,7 @@ If this is on, rooms which have active notifications (the small circle with a nu - + Read receipts 開封確認 @@ -2459,7 +2676,7 @@ Status is displayed next to timestamps. - + Send messages as Markdown メッセージをMarkdownとして送信 @@ -2546,7 +2763,7 @@ This usually causes the application icon in the task bar to animate in some fash - + Enable online key backup @@ -2556,7 +2773,7 @@ This usually causes the application icon in the task bar to animate in some fash - + CACHED @@ -2566,7 +2783,7 @@ This usually causes the application icon in the task bar to animate in some fash - + Scale factor 尺度係数 @@ -2641,7 +2858,7 @@ This usually causes the application icon in the task bar to animate in some fash デバイスの指紋 - + Session Keys セッション鍵 @@ -2661,17 +2878,17 @@ This usually causes the application icon in the task bar to animate in some fash 暗号化 - + GENERAL 全般 - + INTERFACE - + Plays media like GIFs or WEBPs only when explicitly hovering over them. @@ -2711,7 +2928,7 @@ This usually causes the application icon in the task bar to animate in some fash - + Self signing key @@ -2741,14 +2958,14 @@ This usually causes the application icon in the task bar to animate in some fash 全てのファイル (*) - + Open Sessions File セッションファイルを開く - + @@ -2756,19 +2973,19 @@ This usually causes the application icon in the task bar to animate in some fash エラー - - + + File Password ファイルのパスワード - + Enter the passphrase to decrypt the file: ファイルを復号するためのパスフレーズを入力して下さい: - + The password cannot be empty パスワードを空にはできません @@ -2783,6 +3000,14 @@ This usually causes the application icon in the task bar to animate in some fash エクスポートされたセッション鍵を保存するファイル + + VerificationManager + + + No encrypted private chat found with this user. Create an encrypted private chat with this user and try again. + + + Waiting @@ -2837,7 +3062,7 @@ This usually causes the application icon in the task bar to animate in some fash descriptiveTime - + Yesterday 昨日 @@ -2908,37 +3133,6 @@ This usually causes the application icon in the task bar to animate in some fash - - dialogs::JoinRoom - - - Join - 参加 - - - - Cancel - キャンセル - - - - Room ID or alias - 部屋のID又は別名 - - - - dialogs::LeaveRoom - - - Cancel - キャンセル - - - - Are you sure you want to leave? - 本当に退出しますか? - - dialogs::Logout @@ -3005,47 +3199,47 @@ Media size: %2 %1が音声データを送信しました - + You sent an image 画像を送信しました - + %1 sent an image %1が画像を送信しました - + You sent a file ファイルを送信しました - + %1 sent a file %1がファイルを送信しました - + You sent a video 動画を送信しました - + %1 sent a video %1が動画を送信しました - + You sent a sticker ステッカーを送信しました - + %1 sent a sticker %1がステッカーを送信しました - + You sent a notification 通知を送信しました @@ -3060,7 +3254,7 @@ Media size: %2 あなた: %1 - + %1: %2 %1: %2 @@ -3080,27 +3274,27 @@ Media size: %2 - + %1 placed a call - + You answered a call - + %1 answered a call - + You ended a call - + %1 ended a call @@ -3108,7 +3302,7 @@ Media size: %2 utils - + Unknown Message Type 不明なメッセージ型です diff --git a/resources/langs/nheko_ml.ts b/resources/langs/nheko_ml.ts index ba69189e..d8e25ff2 100644 --- a/resources/langs/nheko_ml.ts +++ b/resources/langs/nheko_ml.ts @@ -4,7 +4,7 @@ ActiveCallBar - + Calling... വിളിക്കുന്നു... @@ -56,7 +56,7 @@ CallInvite - + Video Call വീഡിയോ കോൾ @@ -74,7 +74,7 @@ CallInviteBar - + Video Call വീഡിയോ കോൾ @@ -117,7 +117,7 @@ CallManager - + Entire screen മുഴുവൻ സ്ക്രീൻ @@ -125,23 +125,23 @@ ChatPage - + Failed to invite user: %1 ഉപയോക്താവിനെ ക്ഷണിക്കുന്നതിൽ പരാജയപ്പെട്ടു: %1 - + Invited user: %1 ക്ഷണിച്ച ഉപയോക്താവ്:% 1 - + Migrating the cache to the current version failed. This can have different reasons. Please open an issue and try to use an older version in the mean time. Alternatively you can try deleting the cache manually. - + Confirm join ചേരുന്നത് ഉറപ്പാക്കുക @@ -151,23 +151,23 @@ നിങ്ങൾക്ക് %1 -ൽ ചേരാൻ ആഗ്രഹം ഉണ്ടോ? - + Room %1 created. %1 മുറി സൃഷ്ടിച്ചു - - + + Confirm invite ക്ഷണം ഉറപ്പാക്കു - + Do you really want to invite %1 (%2)? - + Failed to invite %1 to %2: %3 @@ -182,7 +182,7 @@ - + Kicked user: %1 ഉപയോക്താവിനെ പുറത്താക്കി: %1 @@ -197,7 +197,7 @@ - + Failed to ban %1 in %2: %3 @@ -217,7 +217,7 @@ - + Failed to unban %1 in %2: %3 @@ -227,12 +227,12 @@ - + Do you really want to start a private chat with %1? - + Cache migration failed! @@ -259,23 +259,23 @@ - + Failed to setup encryption keys. Server response: %1 %2. Please try again later. - - + + Please try to login again: %1 ദയവായി വീണ്ടും ലോഗിൻ ചെയ്യാൻ നോക്കുക: %1 - + Failed to join room: %1 മുറിയിൽ ചേരുന്നതിൽ പരാജയം: %1 - + You joined the room നിങ്ങൾ മുറിയിൽ ചേർന്നു @@ -285,17 +285,17 @@ - + Room creation failed: %1 മുറി സൃഷ്ടിക്കുന്നത് പരാജയപ്പെട്ടു: %1 - + Failed to leave room: %1 - + Failed to kick %1 from %2: %3 @@ -354,7 +354,7 @@ CrossSigningSecrets - + Decrypt secrets @@ -364,12 +364,12 @@ - + Enter your recovery key or passphrase called %1 to decrypt your secrets: - + Decryption failed @@ -581,17 +581,26 @@ - Device verification timed out. - + Other party canceled the verification. - + + Verification messages received out of order! + + + + + Unknown verification error. + + + + Close അടയ്‌ക്കുക @@ -612,7 +621,7 @@ - + Add images ചിത്രങ്ങൾ ചേർക്കുക @@ -622,7 +631,7 @@ സ്റ്റിക്കറുകൾ(*.png *.webp *.gif *.jpg *.jpeg) - + State key @@ -638,13 +647,13 @@ - + Use as Emoji ഇമോജി ആയി ഉപയോഗിക്കുക - - + + Use as Sticker സ്റ്റിക്കറായി ഉപയോഗിക്കുക @@ -697,7 +706,7 @@ പുതിയ മുറി പാക്ക് - + Private pack @@ -712,7 +721,7 @@ - + Enable globally @@ -735,7 +744,7 @@ InputBar - + Select a file ഒരു ഫയൽ തിരഞ്ഞെടുക്കുക @@ -745,7 +754,7 @@ എല്ലാ ഫയലുകളും (*) - + Failed to upload media. Please try again. @@ -753,7 +762,7 @@ InviteDialog - + Invite users to %1 %1 - ലേക്ക് ഉപയോക്താക്കളെ ക്ഷണിക്കുക @@ -784,6 +793,32 @@ റദ്ദാക്കു + + JoinRoomDialog + + + Join room + + + + + Room ID or alias + + + + + LeaveRoomDialog + + + Leave room + + + + + Are you sure you want to leave? + + + LoginPage @@ -846,25 +881,25 @@ Example: https://server.my:8787 പ്രവേശിക്കുക - + - + You have entered an invalid Matrix ID e.g @joe:matrix.org - + Autodiscovery failed. Received malformed response. - + Autodiscovery failed. Unknown error when requesting .well-known. - + The required endpoints were not found. Possibly not a Matrix server. @@ -874,26 +909,44 @@ Example: https://server.my:8787 - + An unknown error occured. Make sure the homeserver domain is valid. - + SSO LOGIN എസ് എസ് ഓ ലോഗിൻ - + Empty password - + SSO login failed + + LogoutDialog + + + Log out + + + + + A call is in progress. Log out? + + + + + Are you sure you want to log out? + + + MessageDelegate @@ -1019,7 +1072,7 @@ Example: https://server.my:8787 MessageView - + Edit തിരുത്തുക @@ -1039,7 +1092,7 @@ Example: https://server.my:8787 - + &Copy @@ -1129,7 +1182,12 @@ Example: https://server.my:8787 - + + To allow other users to see, which of your devices actually belong to you, you can verify them. This also allows key backup to work automatically. Verify an unverified device now? (Please make sure you have one of those devices available.) + + + + To allow other users to see, which of your devices actually belong to you, you can verify them. This also allows key backup to work automatically. Verify %1 now? @@ -1178,7 +1236,7 @@ Example: https://server.my:8787 NotificationWarning - You will be pinging the whole room + You are about to notify the whole room @@ -1186,29 +1244,17 @@ Example: https://server.my:8787 NotificationsManager - - + + %1 sent an encrypted message - - * %1 %2 - Format an emote message in a notification, %1 is the sender, %2 the message - * %1 %2 - - - + %1 replied: %2 Format a reply in a notification. %1 is the sender, %2 the message - - - %1: %2 - Format a normal message in a notification. %1 is the sender, %2 the message - %1: %2 - @@ -1239,7 +1285,7 @@ Example: https://server.my:8787 മൈക്രോഫോണൊന്നും കണ്ടെത്തിയില്ല. - + Voice @@ -1270,7 +1316,7 @@ Example: https://server.my:8787 QCoreApplication - + Create a unique profile, which allows you to log into several accounts at the same time and start multiple instances of nheko. @@ -1288,7 +1334,7 @@ Example: https://server.my:8787 ReadReceipts - + Read receipts @@ -1296,7 +1342,7 @@ Example: https://server.my:8787 ReadReceiptsModel - + Yesterday, %1 @@ -1304,18 +1350,18 @@ Example: https://server.my:8787 RegisterPage - + Username - + The username must not be empty, and must contain only the characters a-z, 0-9, ., _, =, -, and /. - + Password പാസ്‍വേഡ് @@ -1345,37 +1391,22 @@ Example: https://server.my:8787 - - No supported registration flows! - - - - - Registration token - - - - - Please enter a valid registration token. - - - - + Autodiscovery failed. Received malformed response. - + Autodiscovery failed. Unknown error when requesting .well-known. - + The required endpoints were not found. Possibly not a Matrix server. - + Received malformed response. Make sure the homeserver domain is valid. @@ -1385,7 +1416,7 @@ Example: https://server.my:8787 - + Password is not long enough (min 8 chars) @@ -1416,20 +1447,25 @@ Example: https://server.my:8787 RoomDirectory - + Explore Public Rooms - + Search for public rooms + + + Choose custom homeserver + + RoomInfo - + no version stored @@ -1446,16 +1482,6 @@ Example: https://server.my:8787 Enter the tag you want to use: - - - Leave Room - - - - - Are you sure you want to leave this room? - - Leave room @@ -1487,7 +1513,7 @@ Example: https://server.my:8787 - + Status Message @@ -1512,7 +1538,30 @@ Example: https://server.my:8787 - + + Encryption not set up + Cross-signing setup has not run yet. + + + + + Unverified login + The user just signed in with this device and hasn't verified their master key. + + + + + Please verify your other devices + There are unverified devices signed in to this account. + + + + + Close + അടയ്‌ക്കുക + + + Start a new chat @@ -1540,12 +1589,12 @@ Example: https://server.my:8787 RoomMembers - + Members of %1 - + %n people in %1 Summary above list of members @@ -1582,12 +1631,12 @@ Example: https://server.my:8787 RoomSettings - + Room Settings - + %1 member(s) @@ -1693,12 +1742,12 @@ Example: https://server.my:8787 - + Failed to enable encryption: %1 - + Select an avatar @@ -1718,8 +1767,8 @@ Example: https://server.my:8787 - - + + Failed to upload image: %s @@ -1727,21 +1776,49 @@ Example: https://server.my:8787 RoomlistModel - + Pending invite. - + Previewing this room - + No preview available + + Root + + + Please enter your login password to continue: + + + + + Please enter a valid email address to continue: + + + + + Please enter a valid phone number to continue: + + + + + Please enter the token, which has been sent to you: + + + + + Wait for the confirmation link to arrive, then continue. + + + ScreenShare @@ -1799,20 +1876,98 @@ Example: https://server.my:8787 SecretStorage - + Failed to connect to secret storage - Nheko could not connect to the secure storage to save encryption secrets to. This can have multiple reasons. Check if your D-Bus service is running and you have configured a service like KWallet, Gnome Secrets or the equivalent for your platform. If you are having trouble, feel free to open an issue here: https://github.com/Nheko-Reborn/nheko/issues + Nheko could not connect to the secure storage to save encryption secrets to. This can have multiple reasons. Check if your D-Bus service is running and you have configured a service like KWallet, Gnome Keyring, KeePassXC or the equivalent for your platform. If you are having trouble, feel free to open an issue here: https://github.com/Nheko-Reborn/nheko/issues + + + + + SelfVerificationCheck + + + This is your recovery key. You will need it to restore access to your encrypted messages and verification keys. Keep this safe. Don't share it with anyone and don't lose it! Do not pass go! Do not collect $200! + + + + + Encryption setup successfully + + + + + Failed to setup encryption: %1 + + + + + Setup Encryption + + + + + Hello and welcome to Matrix! +It seems like you are new. Before you can securely encrypt your messages, we need to setup a few small things. You can either press accept immediately or adjust a few basic options. We also try to explain a few of the basics. You can skip those parts, but they might prove to be helpful! + + + + + Activate Encryption + + + + + It seems like you have encryption already configured for this account. To be able to access your encrypted messages and make this device appear as trusted, you can either verify an existing device or (if you have one) enter your recovery passphrase. Please select one of the options below. +If you choose verify, you need to have the other device available. If you choose "enter passphrase", you will need your recovery key or passphrase. If you click cancel, you can choose to verify yourself at a later point. + + + + + verify + + + + + enter passphrase + + + + + SelfVerificationStatus + + + Failed to create keys for cross-signing! + + + + + Failed to create keys for online key backup! + + + + + Failed to create keys secure server side secret storage! + + + + + Encryption Setup + + + + + Encryption setup failed: %1 SingleImagePackModel - + Failed to update image pack: %1 @@ -1828,7 +1983,7 @@ Example: https://server.my:8787 - + Failed to upload image: %1 @@ -1885,18 +2040,18 @@ Example: https://server.my:8787 TimelineModel - + Message redaction failed: %1 - + Failed to encrypt event, sending aborted! - + Save image @@ -1916,7 +2071,7 @@ Example: https://server.my:8787 - + %1 and %2 are typing. Multiple users are typing. First argument is a comma separated list of potentially multiple users. Second argument is the last user of that list. (If only one user is typing, %1 is empty. You should still use it in your string though to silence Qt warnings.) @@ -1925,7 +2080,7 @@ Example: https://server.my:8787 - + %1 opened the room to the public. @@ -1965,12 +2120,12 @@ Example: https://server.my:8787 - + %1 set the room history visible to members since they were invited. - + %1 set the room history visible to members since they joined the room. @@ -1980,12 +2135,12 @@ Example: https://server.my:8787 - + %1 was invited. - + %1 changed their avatar. @@ -2000,12 +2155,12 @@ Example: https://server.my:8787 - + %1 joined via authorisation from %2's server. - + %1 rejected their invite. @@ -2035,27 +2190,27 @@ Example: https://server.my:8787 - + Reason: %1 - + %1 redacted their knock. - + You joined this room. നിങ്ങൾ ഈ മുറിയിൽ ചേർന്നു. - + %1 has changed their avatar and changed their display name to %2. - + %1 has changed their display name to %2. @@ -2092,7 +2247,12 @@ Example: https://server.my:8787 - + + No preview available + + + + %1 member(s) @@ -2117,28 +2277,20 @@ Example: https://server.my:8787 - - TimelineViewManager - - - No encrypted private chat found with this user. Create an encrypted private chat with this user and try again. - - - TopBar - + Back to room list - + No room selected - + This room is not encrypted! @@ -2149,7 +2301,7 @@ Example: https://server.my:8787 - This rooms contain verified devices and devices which have never changed their master key. + This room contains verified devices and devices which have never changed their master key. @@ -2196,10 +2348,35 @@ Example: https://server.my:8787 + + UIA + + + No available registration flows! + + + + + + + Registration aborted + + + + + Please enter a valid registration token. + + + + + Invalid token + + + UserProfile - + Global User Profile @@ -2209,7 +2386,7 @@ Example: https://server.my:8787 - + Change avatar globally. @@ -2219,7 +2396,7 @@ Example: https://server.my:8787 - + Change display name globally. @@ -2229,7 +2406,7 @@ Example: https://server.my:8787 - + Room: %1 @@ -2239,18 +2416,18 @@ Example: https://server.my:8787 - + Open the global profile for this user. - - + + Verify - + Start a private chat. @@ -2265,12 +2442,42 @@ Example: https://server.my:8787 - + + Refresh device list. + + + + + Sign out this device. + + + + + Change device name. + + + + + Last seen %1 from %2 + + + + Unverify - + + Sign out device %1 + + + + + You signed out this device. + + + + Select an avatar @@ -2293,8 +2500,8 @@ Example: https://server.my:8787 UserSettings - - + + Default @@ -2302,7 +2509,7 @@ Example: https://server.my:8787 UserSettingsPage - + Minimize to tray @@ -2312,22 +2519,22 @@ Example: https://server.my:8787 - + Group's sidebar - + Circular Avatars - + profile: %1 - + Default @@ -2352,7 +2559,7 @@ Example: https://server.my:8787 - + Keep the application running in the background after closing the client window. @@ -2367,6 +2574,16 @@ Example: https://server.my:8787 OFF - square, ON - Circle. + + + Use identicons + + + + + Display an identicon instead of a letter when a user has not set an avatar. + + Show a column containing groups and tags next to the room list. @@ -2395,7 +2612,7 @@ be blurred. - + Privacy screen timeout (in seconds [0 - 3600]) @@ -2450,7 +2667,7 @@ If this is on, rooms which have active notifications (the small circle with a nu - + Read receipts @@ -2461,7 +2678,7 @@ Status is displayed next to timestamps. - + Send messages as Markdown @@ -2548,7 +2765,7 @@ This usually causes the application icon in the task bar to animate in some fash - + Enable online key backup @@ -2558,7 +2775,7 @@ This usually causes the application icon in the task bar to animate in some fash - + CACHED @@ -2568,7 +2785,7 @@ This usually causes the application icon in the task bar to animate in some fash - + Scale factor @@ -2643,7 +2860,7 @@ This usually causes the application icon in the task bar to animate in some fash - + Session Keys @@ -2663,17 +2880,17 @@ This usually causes the application icon in the task bar to animate in some fash - + GENERAL - + INTERFACE - + Plays media like GIFs or WEBPs only when explicitly hovering over them. @@ -2713,7 +2930,7 @@ This usually causes the application icon in the task bar to animate in some fash - + Self signing key @@ -2743,14 +2960,14 @@ This usually causes the application icon in the task bar to animate in some fash എല്ലാ ഫയലുകളും (*) - + Open Sessions File - + @@ -2758,19 +2975,19 @@ This usually causes the application icon in the task bar to animate in some fash - - + + File Password - + Enter the passphrase to decrypt the file: - + The password cannot be empty @@ -2785,6 +3002,14 @@ This usually causes the application icon in the task bar to animate in some fash + + VerificationManager + + + No encrypted private chat found with this user. Create an encrypted private chat with this user and try again. + + + Waiting @@ -2839,7 +3064,7 @@ This usually causes the application icon in the task bar to animate in some fash descriptiveTime - + Yesterday @@ -2910,37 +3135,6 @@ This usually causes the application icon in the task bar to animate in some fash - - dialogs::JoinRoom - - - Join - - - - - Cancel - റദ്ദാക്കു - - - - Room ID or alias - - - - - dialogs::LeaveRoom - - - Cancel - റദ്ദാക്കു - - - - Are you sure you want to leave? - - - dialogs::Logout @@ -3005,47 +3199,47 @@ Media size: %2 - + You sent an image - + %1 sent an image - + You sent a file - + %1 sent a file - + You sent a video - + %1 sent a video - + You sent a sticker - + %1 sent a sticker - + You sent a notification @@ -3060,7 +3254,7 @@ Media size: %2 - + %1: %2 %1: %2 @@ -3080,27 +3274,27 @@ Media size: %2 - + %1 placed a call - + You answered a call - + %1 answered a call - + You ended a call നിങ്ങൾ ഒരു കോൾ അവസാനിപ്പിച്ചു - + %1 ended a call %1 ഒരു കോൾ അവസാനിപ്പിച്ചു @@ -3108,7 +3302,7 @@ Media size: %2 utils - + Unknown Message Type diff --git a/resources/langs/nheko_nl.ts b/resources/langs/nheko_nl.ts index 8a0f9319..1ff88a61 100644 --- a/resources/langs/nheko_nl.ts +++ b/resources/langs/nheko_nl.ts @@ -4,7 +4,7 @@ ActiveCallBar - + Calling... Bellen... @@ -56,7 +56,7 @@ CallInvite - + Video Call Video oproep @@ -74,7 +74,7 @@ CallInviteBar - + Video Call Video oproep @@ -117,7 +117,7 @@ CallManager - + Entire screen Gehele scherm @@ -125,23 +125,23 @@ ChatPage - + Failed to invite user: %1 Gebruiker uitnodigen mislukt: %1 - + Invited user: %1 Gebruiker uitgenodigd: %1 - + Migrating the cache to the current version failed. This can have different reasons. Please open an issue and try to use an older version in the mean time. Alternatively you can try deleting the cache manually. Het migreren can de cache naar de huidige versie is mislukt. Dit kan verscheidene redenen hebben. Maak a.u.b een issue aan en probeer in de tussentijd een oudere versie. Je kan ook proberen de cache handmatig te verwijderen. - + Confirm join Bevestig deelname @@ -151,23 +151,23 @@ Weet je zeker dat je %1 wil binnen gaan? - + Room %1 created. Kamer %1 gemaakt. - - + + Confirm invite Bevestig uitnodiging - + Do you really want to invite %1 (%2)? Weet je zeker dat je %1 (%2) wil uitnodigen? - + Failed to invite %1 to %2: %3 Uitnodigen van %1 naar %2 mislukt: %3 @@ -182,7 +182,7 @@ Weet je zeker dat je %1 (%2) uit de kamer wil verwijderen? - + Kicked user: %1 Uit kamer verwijderde gebruiker: %1 @@ -197,7 +197,7 @@ Weet je zeker dat je gebruiker %1 (%2) wil verbannen? - + Failed to ban %1 in %2: %3 Verbannen van %1 uit %2 mislukt: %3 @@ -217,7 +217,7 @@ Weet je zeker dat je %1 (%2) opnieuw wil toelaten? - + Failed to unban %1 in %2: %3 Opnieuw toelaten van %1 in %2 mislukt: %3 @@ -227,12 +227,12 @@ Toegelaten gebruiker: %1 - + Do you really want to start a private chat with %1? Weet je zeker dat je een privé chat wil beginnen met %1? - + Cache migration failed! Migreren van de cache is mislukt! @@ -259,23 +259,23 @@ Opgeslagen gegevens herstellen mislukt. Log a.u.b. opnieuw in. - + Failed to setup encryption keys. Server response: %1 %2. Please try again later. Instellen van de versleuteling is mislukt. Bericht van server: %1 %2. Probeer het a.u.b. later nog eens. - - + + Please try to login again: %1 Probeer a.u.b. opnieuw in te loggen: %1 - + Failed to join room: %1 Kamer binnengaan mislukt: %1 - + You joined the room Je bent de kamer binnengegaan. @@ -285,17 +285,17 @@ Uitnodiging verwijderen mislukt: %1 - + Room creation failed: %1 Kamer aanmaken mislukt: %1 - + Failed to leave room: %1 Kamer verlaten mislukt: %1 - + Failed to kick %1 from %2: %3 Kon %1 niet verwijderen uit %2: %3 @@ -354,7 +354,7 @@ CrossSigningSecrets - + Decrypt secrets Ontsleutel geheimen @@ -364,12 +364,12 @@ Voer je herstelsleutel of wachtwoordzin in om je geheimen te ontsleutelen: - + Enter your recovery key or passphrase called %1 to decrypt your secrets: Voer je herstelsleutel of wachtwoordzin in met de naam %1 om je geheimen te ontsleutelen: - + Decryption failed Ontsleutelen mislukt @@ -581,17 +581,26 @@ - Device verification timed out. Apparaatverificatie is verlopen. - + Other party canceled the verification. De andere kant heeft de verificatie geannuleerd. - + + Verification messages received out of order! + + + + + Unknown verification error. + + + + Close Sluiten @@ -612,7 +621,7 @@ Afbeeldingspakket aanpassen - + Add images Afbeeldingen toevoegen @@ -622,7 +631,7 @@ Stickers (*.png *.webp *.gif *.jpg *.jpeg) - + State key Staatsleutel @@ -638,13 +647,13 @@ - + Use as Emoji Gebruik als emoji - - + + Use as Sticker Gebruik als sticker @@ -697,7 +706,7 @@ Nieuw afbeeldingspakket voor kamer - + Private pack Privé afbeeldingspakket @@ -712,7 +721,7 @@ Globaal geactiveerd afbeeldingspakket - + Enable globally Globaal activeren @@ -735,7 +744,7 @@ InputBar - + Select a file Selecteer een bestand @@ -745,7 +754,7 @@ Alle bestanden (*) - + Failed to upload media. Please try again. Het is niet is gelukt om de media te versturen. Probeer het a.u.b. opnieuw. @@ -753,7 +762,7 @@ InviteDialog - + Invite users to %1 Nodig gebruikers uit naar %1 @@ -784,6 +793,32 @@ Annuleren + + JoinRoomDialog + + + Join room + + + + + Room ID or alias + Kamer ID of alias + + + + LeaveRoomDialog + + + Leave room + Kamer verlaten + + + + Are you sure you want to leave? + Weet je zeker dat je de kamer wil verlaten? + + LoginPage @@ -850,25 +885,25 @@ Voorbeeld: https://mijnserver.nl:8787 INLOGGEN - + - + You have entered an invalid Matrix ID e.g @joe:matrix.org Je hebt een ongeldige Matrix ID ingevuld. Correct voorbeeld: @jan:matrix.org - + Autodiscovery failed. Received malformed response. Automatische herkenning mislukt. Ongeldig antwoord ontvangen. - + Autodiscovery failed. Unknown error when requesting .well-known. Automatische herkenning mislukt. Onbekende fout tijdens het opvragen van .well-known. - + The required endpoints were not found. Possibly not a Matrix server. De vereiste aanspreekpunten werden niet gevonden. Mogelijk geen Matrix server. @@ -878,26 +913,44 @@ Voorbeeld: https://mijnserver.nl:8787 Ongeldig antwoord ontvangen. Zorg dat de thuisserver geldig is. - + An unknown error occured. Make sure the homeserver domain is valid. Een onbekende fout trad op. Zorg dat de thuisserver geldig is. - + SSO LOGIN SSO INLOGGEN - + Empty password Leeg wachtwoord - + SSO login failed SSO inloggen mislukt + + LogoutDialog + + + Log out + + + + + A call is in progress. Log out? + + + + + Are you sure you want to log out? + + + MessageDelegate @@ -1023,7 +1076,7 @@ Voorbeeld: https://mijnserver.nl:8787 MessageView - + Edit Bewerken @@ -1043,7 +1096,7 @@ Voorbeeld: https://mijnserver.nl:8787 Opties - + &Copy &Kopiëren @@ -1114,6 +1167,11 @@ Voorbeeld: https://mijnserver.nl:8787 Copy link to eve&nt Kopieer link naar gebeurte&nis + + + &Go to quoted message + + NewVerificationRequest @@ -1128,7 +1186,12 @@ Voorbeeld: https://mijnserver.nl:8787 Ontvangen verificatieverzoek - + + To allow other users to see, which of your devices actually belong to you, you can verify them. This also allows key backup to work automatically. Verify an unverified device now? (Please make sure you have one of those devices available.) + + + + To allow other users to see, which of your devices actually belong to you, you can verify them. This also allows key backup to work automatically. Verify %1 now? Om andere gebruikers te laten weten welke apparaten echt van jou zijn, kan je ze verifiëren. Dit zorgt ook dat reservesleutels automatisch werken. Nu %1 verifiëren? @@ -1177,37 +1240,25 @@ Voorbeeld: https://mijnserver.nl:8787 NotificationWarning - You will be pinging the whole room - Je gaat een melding versturen naar de hele kamer + You are about to notify the whole room + NotificationsManager - - + + %1 sent an encrypted message %1 stuurde een versleuteld bericht - - * %1 %2 - Format an emote message in a notification, %1 is the sender, %2 the message - * %1 %2 - - - + %1 replied: %2 Format a reply in a notification. %1 is the sender, %2 the message %1 antwoordde: %2 - - - %1: %2 - Format a normal message in a notification. %1 is the sender, %2 the message - %1: %2 - @@ -1238,7 +1289,7 @@ Voorbeeld: https://mijnserver.nl:8787 Geen microfoon gevonden. - + Voice Spraak @@ -1269,7 +1320,7 @@ Voorbeeld: https://mijnserver.nl:8787 QCoreApplication - + Create a unique profile, which allows you to log into several accounts at the same time and start multiple instances of nheko. Creëer een uniek profiel, waardoor je op meerdere accounts tegelijk kan inloggen, en meerdere kopieën van Nheko tegelijk kan starten. @@ -1287,7 +1338,7 @@ Voorbeeld: https://mijnserver.nl:8787 ReadReceipts - + Read receipts Leesbevestigingen @@ -1295,7 +1346,7 @@ Voorbeeld: https://mijnserver.nl:8787 ReadReceiptsModel - + Yesterday, %1 Gisteren, %1 @@ -1303,18 +1354,18 @@ Voorbeeld: https://mijnserver.nl:8787 RegisterPage - + Username Gebruikersnaam - + The username must not be empty, and must contain only the characters a-z, 0-9, ., _, =, -, and /. De gebruikersnaam mag niet leeg zijn, en mag alleen de volgende tekens bevatten: a-z, 0-9, ., _, =, -, en /. - + Password Wachtwoord @@ -1344,37 +1395,22 @@ Voorbeeld: https://mijnserver.nl:8787 REGISTREREN - - No supported registration flows! - Geen ondersteunde registratiestromingen! - - - - Registration token - Registratieteken - - - - Please enter a valid registration token. - Voer a.u.b een geldig registratieteken in. - - - + Autodiscovery failed. Received malformed response. Automatische herkenning mislukt. Onjuist gevormd antwoord ontvangen. - + Autodiscovery failed. Unknown error when requesting .well-known. Automatische herkenning mislukt. Onbekende fout bij opvragen van .well-known. - + The required endpoints were not found. Possibly not a Matrix server. De vereiste aanspreekpunten konden niet worden gevonden. Mogelijk geen Matrix server. - + Received malformed response. Make sure the homeserver domain is valid. Onjuist gevormd antwoord ontvangen. Zorg dat de thuisserver geldig is. @@ -1384,7 +1420,7 @@ Voorbeeld: https://mijnserver.nl:8787 Een onbekende fout trad op. Zorg dat de thuisserver geldig is. - + Password is not long enough (min 8 chars) Het wachtwoord is niet lang genoeg (minimaal 8 tekens) @@ -1415,20 +1451,25 @@ Voorbeeld: https://mijnserver.nl:8787 RoomDirectory - + Explore Public Rooms Verken openbare kamers - + Search for public rooms Zoek naar openbare kamers + + + Choose custom homeserver + + RoomInfo - + no version stored geen versie opgeslagen @@ -1445,16 +1486,6 @@ Voorbeeld: https://mijnserver.nl:8787 Enter the tag you want to use: Voer de markering in die je wil gebruiken: - - - Leave Room - Verlaat kamer - - - - Are you sure you want to leave this room? - Weet je zeker dat je de kamer wil verlaten? - Leave room @@ -1486,7 +1517,7 @@ Voorbeeld: https://mijnserver.nl:8787 Maak nieuwe markering... - + Status Message Statusbericht @@ -1511,7 +1542,30 @@ Voorbeeld: https://mijnserver.nl:8787 Uitloggen - + + Encryption not set up + Cross-signing setup has not run yet. + + + + + Unverified login + The user just signed in with this device and hasn't verified their master key. + + + + + Please verify your other devices + There are unverified devices signed in to this account. + + + + + Close + Sluiten + + + Start a new chat Nieuwe chat beginnen @@ -1539,12 +1593,12 @@ Voorbeeld: https://mijnserver.nl:8787 RoomMembers - + Members of %1 Deelnemers in %1 - + %n people in %1 Summary above list of members @@ -1581,12 +1635,12 @@ Voorbeeld: https://mijnserver.nl:8787 RoomSettings - + Room Settings Kamerinstellingen - + %1 member(s) %1 deelnemer(s) @@ -1692,12 +1746,12 @@ Voorbeeld: https://mijnserver.nl:8787 Kamerversie - + Failed to enable encryption: %1 Versleuteling kon niet worden ingeschakeld: %1 - + Select an avatar Kies een avatar @@ -1717,8 +1771,8 @@ Voorbeeld: https://mijnserver.nl:8787 Fout bij lezen van bestand: %1 - - + + Failed to upload image: %s Uploaden van afbeelding mislukt: %1 @@ -1726,21 +1780,49 @@ Voorbeeld: https://mijnserver.nl:8787 RoomlistModel - + Pending invite. Wachtende uitnodiging. - + Previewing this room Voorbeeld van deze kamer - + No preview available Geen voorbeeld beschikbaar + + Root + + + Please enter your login password to continue: + + + + + Please enter a valid email address to continue: + + + + + Please enter a valid phone number to continue: + + + + + Please enter the token, which has been sent to you: + + + + + Wait for the confirmation link to arrive, then continue. + + + ScreenShare @@ -1798,20 +1880,98 @@ Voorbeeld: https://mijnserver.nl:8787 SecretStorage - + Failed to connect to secret storage Verbinden met geheimopslag mislukt - Nheko could not connect to the secure storage to save encryption secrets to. This can have multiple reasons. Check if your D-Bus service is running and you have configured a service like KWallet, Gnome Secrets or the equivalent for your platform. If you are having trouble, feel free to open an issue here: https://github.com/Nheko-Reborn/nheko/issues - Nheko kon niet verbinden met de geheimopslag om sleutels in te bewaren. Dit kan verscheidene redenen hebben. Controleer of je D-Bus service draait en of je een service zoals KWallet, Gnome Secrets of een dergelijk equivalent voor je platform hebt ingesteld. Als je problemen ondervindt kan je ook altijd hier een issue openen: https://github.com/Nheko-Reborn/nheko/issues + Nheko could not connect to the secure storage to save encryption secrets to. This can have multiple reasons. Check if your D-Bus service is running and you have configured a service like KWallet, Gnome Keyring, KeePassXC or the equivalent for your platform. If you are having trouble, feel free to open an issue here: https://github.com/Nheko-Reborn/nheko/issues + + + + + SelfVerificationCheck + + + This is your recovery key. You will need it to restore access to your encrypted messages and verification keys. Keep this safe. Don't share it with anyone and don't lose it! Do not pass go! Do not collect $200! + + + + + Encryption setup successfully + + + + + Failed to setup encryption: %1 + + + + + Setup Encryption + + + + + Hello and welcome to Matrix! +It seems like you are new. Before you can securely encrypt your messages, we need to setup a few small things. You can either press accept immediately or adjust a few basic options. We also try to explain a few of the basics. You can skip those parts, but they might prove to be helpful! + + + + + Activate Encryption + + + + + It seems like you have encryption already configured for this account. To be able to access your encrypted messages and make this device appear as trusted, you can either verify an existing device or (if you have one) enter your recovery passphrase. Please select one of the options below. +If you choose verify, you need to have the other device available. If you choose "enter passphrase", you will need your recovery key or passphrase. If you click cancel, you can choose to verify yourself at a later point. + + + + + verify + + + + + enter passphrase + + + + + SelfVerificationStatus + + + Failed to create keys for cross-signing! + + + + + Failed to create keys for online key backup! + + + + + Failed to create keys secure server side secret storage! + + + + + Encryption Setup + + + + + Encryption setup failed: %1 + SingleImagePackModel - + Failed to update image pack: %1 Kon afbeeldingspakket niet updaten: %1 @@ -1827,7 +1987,7 @@ Voorbeeld: https://mijnserver.nl:8787 Kon afbeelding niet openen: %1 - + Failed to upload image: %1 Kon afbeelding niet uploaden: %1 @@ -1884,18 +2044,18 @@ Voorbeeld: https://mijnserver.nl:8787 TimelineModel - + Message redaction failed: %1 Bericht intrekken mislukt: %1 - + Failed to encrypt event, sending aborted! Kon evenement niet versleutelen, versturen geannuleerd! - + Save image Afbeelding opslaan @@ -1915,7 +2075,7 @@ Voorbeeld: https://mijnserver.nl:8787 Bestand opslaan - + %1 and %2 are typing. Multiple users are typing. First argument is a comma separated list of potentially multiple users. Second argument is the last user of that list. (If only one user is typing, %1 is empty. You should still use it in your string though to silence Qt warnings.) @@ -1924,7 +2084,7 @@ Voorbeeld: https://mijnserver.nl:8787 - + %1 opened the room to the public. %1 maakte de kamer openbaar. @@ -1964,12 +2124,12 @@ Voorbeeld: https://mijnserver.nl:8787 %1 maakte de kamergeschiedenis deelname-vereist. - + %1 set the room history visible to members since they were invited. %1 maakte de kamergeschiedenis zichtbaar vanaf het moment van uitnodigen. - + %1 set the room history visible to members since they joined the room. %1 maakte de kamergeschiedenis zichtbaar vanaf moment van deelname. @@ -1979,12 +2139,12 @@ Voorbeeld: https://mijnserver.nl:8787 %1 heeft de rechten van de kamer aangepast. - + %1 was invited. %1 is uitgenodigd. - + %1 changed their avatar. %1 is van avatar veranderd. @@ -1999,12 +2159,12 @@ Voorbeeld: https://mijnserver.nl:8787 %1 neemt nu deel. - + %1 joined via authorisation from %2's server. %1 neemt deel via autorisatie van %2's server. - + %1 rejected their invite. %1 heeft de uitnodiging geweigerd. @@ -2034,27 +2194,27 @@ Voorbeeld: https://mijnserver.nl:8787 %1 is verbannen. - + Reason: %1 Reden: %1 - + %1 redacted their knock. %1 heeft het aankloppen ingetrokken. - + You joined this room. Je neemt nu deel aan deze kamer. - + %1 has changed their avatar and changed their display name to %2. %1 is van avatar veranderd en heet nu %2. - + %1 has changed their display name to %2. %1 heet nu %2. @@ -2091,7 +2251,12 @@ Voorbeeld: https://mijnserver.nl:8787 Geen kamer open - + + No preview available + Geen voorbeeld beschikbaar + + + %1 member(s) %1 deelnemer(s) @@ -2116,28 +2281,20 @@ Voorbeeld: https://mijnserver.nl:8787 Terug naar kamerlijst - - TimelineViewManager - - - No encrypted private chat found with this user. Create an encrypted private chat with this user and try again. - Geen versleutelde chat gevonden met deze gebruiker. Maak een versleutelde chat aan met deze gebruiker en probeer het opnieuw. - - TopBar - + Back to room list Terug naar kamerlijst - + No room selected Geen kamer geselecteerd - + This room is not encrypted! Deze kamer is niet versleuteld! @@ -2148,8 +2305,8 @@ Voorbeeld: https://mijnserver.nl:8787 - This rooms contain verified devices and devices which have never changed their master key. - Deze kamer bevat geverifieerde apparaten en apparaten die nooit hun hoofdsleutel hebben aangepast. + This room contains verified devices and devices which have never changed their master key. + @@ -2195,10 +2352,35 @@ Voorbeeld: https://mijnserver.nl:8787 Afsluiten + + UIA + + + No available registration flows! + + + + + + + Registration aborted + + + + + Please enter a valid registration token. + Voer a.u.b een geldig registratieteken in. + + + + Invalid token + + + UserProfile - + Global User Profile Globaal gebruikersprofiel @@ -2208,7 +2390,7 @@ Voorbeeld: https://mijnserver.nl:8787 Kamerspecifiek gebruikersprofiel - + Change avatar globally. Verander avatar globaal. @@ -2218,7 +2400,7 @@ Voorbeeld: https://mijnserver.nl:8787 Verander avatar. Heeft alleen effect op deze kamer. - + Change display name globally. Verander weergavenaam globaal. @@ -2228,7 +2410,7 @@ Voorbeeld: https://mijnserver.nl:8787 Verander weergavenaam. Heeft alleen effect op deze kamer. - + Room: %1 Kamer: %1 @@ -2238,18 +2420,18 @@ Voorbeeld: https://mijnserver.nl:8787 Dit is een kamer-specifiek profiel. De weergavenaam en avatar kunnen verschillen van de globale versie. - + Open the global profile for this user. Open het globale profiel van deze gebruiker. - - + + Verify Verifiëren - + Start a private chat. Begin een privéchat. @@ -2264,12 +2446,42 @@ Voorbeeld: https://mijnserver.nl:8787 Verban de gebruiker. - + + Refresh device list. + + + + + Sign out this device. + + + + + Change device name. + + + + + Last seen %1 from %2 + + + + Unverify On-verifiëren - + + Sign out device %1 + + + + + You signed out this device. + + + + Select an avatar Kies een avatar @@ -2292,8 +2504,8 @@ Voorbeeld: https://mijnserver.nl:8787 UserSettings - - + + Default Standaard @@ -2301,7 +2513,7 @@ Voorbeeld: https://mijnserver.nl:8787 UserSettingsPage - + Minimize to tray Minimaliseren naar systeemvak @@ -2311,22 +2523,22 @@ Voorbeeld: https://mijnserver.nl:8787 Geminimaliseerd opstarten - + Group's sidebar Zijbalk met groepen - + Circular Avatars Ronde avatars - + profile: %1 profiel: %1 - + Default Standaard @@ -2351,7 +2563,7 @@ Voorbeeld: https://mijnserver.nl:8787 DOWNLOADEN - + Keep the application running in the background after closing the client window. Blijf draaien in de achtergrond na het sluiten van het scherm. @@ -2367,6 +2579,16 @@ OFF - square, ON - Circle. Verander het uiterlijk van avatars in de chats. UIT - vierkant, AAN - cirkel. + + + Use identicons + + + + + Display an identicon instead of a letter when a user has not set an avatar. + + Show a column containing groups and tags next to the room list. @@ -2397,7 +2619,7 @@ be blurred. worden geblurt. - + Privacy screen timeout (in seconds [0 - 3600]) Privacy scherm wachttijd (in seconden [0 - 3600]) @@ -2457,7 +2679,7 @@ Indien uitgeschakeld, staan kamers gesorteerd op de tijd van het laatst ontvange Indien ingeschakeld, staan kamers met actieve notificaties (het cirkeltje met een getal erin) bovenaan. Kamers die je hebt gedempt zullen nog steeds op tijd zijn gesorteerd, want die vind je blijkbaar niet zo belangrijk als de andere kamers. - + Read receipts Leesbevestigingen @@ -2469,7 +2691,7 @@ Status is displayed next to timestamps. De status staat naast de tijdsindicatie. - + Send messages as Markdown Verstuur berichten in Markdown @@ -2558,7 +2780,7 @@ Meestal zorgt dit dat het icoon in de taakbalk op een manier animeert of iets de Download van en upload naar de online reservesleutel. - + Enable online key backup Activeer online reservesleutelopslag @@ -2568,7 +2790,7 @@ Meestal zorgt dit dat het icoon in de taakbalk op een manier animeert of iets de De Nheko auteurs raden af om online reservesleutelopslag te gebruiken totdat symmetrische reservesleutelopslag beschikbaar is. Toch activeren? - + CACHED IN CACHE @@ -2578,7 +2800,7 @@ Meestal zorgt dit dat het icoon in de taakbalk op een manier animeert of iets de NIET IN CACHE - + Scale factor Schaalfactor @@ -2653,7 +2875,7 @@ Meestal zorgt dit dat het icoon in de taakbalk op een manier animeert of iets de Apparaat vingerafdruk - + Session Keys Sessiesleutels @@ -2673,17 +2895,17 @@ Meestal zorgt dit dat het icoon in de taakbalk op een manier animeert of iets de VERSLEUTELING - + GENERAL ALGEMEEN - + INTERFACE INTERFACE - + Plays media like GIFs or WEBPs only when explicitly hovering over them. Speelt media zoals GIFs en WebPs alleen af terwijl de muiscursor erboven hangt. @@ -2723,7 +2945,7 @@ Meestal zorgt dit dat het icoon in de taakbalk op een manier animeert of iets de De sleutel die wordt gebruikt om andere gebruikers te verifiëren. Indien gecached zal het verifiëren van een gebruiker alle apparaten van die gebruiker verifiëren. - + Self signing key Zelf ondertekenen sleutel @@ -2753,14 +2975,14 @@ Meestal zorgt dit dat het icoon in de taakbalk op een manier animeert of iets de Alle bestanden (*) - + Open Sessions File Open sessiebestand - + @@ -2768,19 +2990,19 @@ Meestal zorgt dit dat het icoon in de taakbalk op een manier animeert of iets de Fout - - + + File Password Wachtwoord voor bestand - + Enter the passphrase to decrypt the file: Voer de wachtwoordzin in om het bestand te ontsleutelen: - + The password cannot be empty Het wachtwoord kan niet leeg zijn @@ -2795,6 +3017,14 @@ Meestal zorgt dit dat het icoon in de taakbalk op een manier animeert of iets de Bestand om geëxporteerde sessiesleutels in op te slaan + + VerificationManager + + + No encrypted private chat found with this user. Create an encrypted private chat with this user and try again. + Geen versleutelde chat gevonden met deze gebruiker. Maak een versleutelde chat aan met deze gebruiker en probeer het opnieuw. + + Waiting @@ -2849,7 +3079,7 @@ Meestal zorgt dit dat het icoon in de taakbalk op een manier animeert of iets de descriptiveTime - + Yesterday Gisteren @@ -2920,37 +3150,6 @@ Meestal zorgt dit dat het icoon in de taakbalk op een manier animeert of iets de Open de fallback, volg de stappen, en bevestig nadat je klaar bent. - - dialogs::JoinRoom - - - Join - Deelnemen - - - - Cancel - Annuleren - - - - Room ID or alias - Kamer ID of alias - - - - dialogs::LeaveRoom - - - Cancel - Annuleren - - - - Are you sure you want to leave? - Weet je zeker dat je de kamer wil verlaten? - - dialogs::Logout @@ -3017,47 +3216,47 @@ Mediagrootte: %2 %1 verstuurde een audio clip - + You sent an image Je verstuurde een afbeelding - + %1 sent an image %1 verstuurde een afbeelding - + You sent a file Je verstuurde een bestand - + %1 sent a file %1 verstuurde een bestand - + You sent a video Je verstuurde een video - + %1 sent a video %1 verstuurde een video - + You sent a sticker Je verstuurde een sticker - + %1 sent a sticker %1 verstuurde een sticker - + You sent a notification Je verstuurde een notificatie @@ -3072,7 +3271,7 @@ Mediagrootte: %2 Jij: %1 - + %1: %2 %1: %2 @@ -3092,27 +3291,27 @@ Mediagrootte: %2 Je hebt een oproep geplaatst - + %1 placed a call %1 plaatste een oproep - + You answered a call Je beantwoordde een oproep - + %1 answered a call %1 beantwoordde een oproep - + You ended a call Je hing op - + %1 ended a call %1 hing op @@ -3120,12 +3319,9 @@ Mediagrootte: %2 utils - + Unknown Message Type Onbekend berichttype - - - diff --git a/resources/langs/nheko_pl.ts b/resources/langs/nheko_pl.ts index a285359f..0f625d44 100644 --- a/resources/langs/nheko_pl.ts +++ b/resources/langs/nheko_pl.ts @@ -4,7 +4,7 @@ ActiveCallBar - + Calling... @@ -56,7 +56,7 @@ CallInvite - + Video Call Połączenie Wideo @@ -74,7 +74,7 @@ CallInviteBar - + Video Call Połączenie Wideo @@ -117,7 +117,7 @@ CallManager - + Entire screen Cały ekran @@ -125,23 +125,23 @@ ChatPage - + Failed to invite user: %1 Nie udało się zaprosić użytkownika: %1 - + Invited user: %1 Zaproszono użytkownika %1 - + Migrating the cache to the current version failed. This can have different reasons. Please open an issue and try to use an older version in the mean time. Alternatively you can try deleting the cache manually. Migracja cachu do obecnej wersji nieudana. Przyczyny mogą być różne. Proszę zgłosić błąd i w miedzyczasie używać starszej wersji. Możesz również spróbuwać usunąć cache ręcznie. - + Confirm join Potwierdź dołączenie @@ -151,23 +151,23 @@ Czy na pewno chcesz dołączyć do %1? - + Room %1 created. Utworzono pokój %1. - - + + Confirm invite Potwierdź zaproszenie - + Do you really want to invite %1 (%2)? Czy na pewno chcesz zaprosić %1 (%2)? - + Failed to invite %1 to %2: %3 Zaproszenie %1 do %2 nieudane: %3 @@ -182,7 +182,7 @@ czy na pewno chcesz wykopać %1 (%2)? - + Kicked user: %1 Wykopano użytkownika: %1 @@ -197,7 +197,7 @@ Czy na pewno chcesz zablokować %1 (%2)? - + Failed to ban %1 in %2: %3 Nie udało się zbanować %1 w %2: %3 @@ -217,7 +217,7 @@ Czy na pewno chcesz odblokować %1 (%2)? - + Failed to unban %1 in %2: %3 Nie udało się odbanować %1 w %2: %3 @@ -227,12 +227,12 @@ Odblokowano użytkownika: %1 - + Do you really want to start a private chat with %1? Czy na pewno chcesz rozpocząć prywatny czat z %1? - + Cache migration failed! Nie udało się przenieść pamięci podręcznej! @@ -259,23 +259,23 @@ Nie udało się przywrócić zapisanych danych. Spróbuj zalogować się ponownie. - + Failed to setup encryption keys. Server response: %1 %2. Please try again later. Nie udało się ustawić kluczy szyfrujących. Odpowiedź serwera: %1 %2. Spróbuj ponownie później. - - + + Please try to login again: %1 Spróbuj zalogować się ponownie: %1 - + Failed to join room: %1 Nie udało się dołączyć do pokoju: %1 - + You joined the room Dołączyłeś do pokoju @@ -285,17 +285,17 @@ Nie udało się usunąć zaproszenia: %1 - + Room creation failed: %1 Tworzenie pokoju nie powiodło się: %1 - + Failed to leave room: %1 Nie udało się opuścić pokoju: %1 - + Failed to kick %1 from %2: %3 Nie udało się wykopać %1 z %2: %3 @@ -354,7 +354,7 @@ CrossSigningSecrets - + Decrypt secrets Odszyfruj sekrety @@ -364,12 +364,12 @@ Wprowadź swój klucz odzyskiwania lub frazę-klucz by odszyfrować swoje sekrety: - + Enter your recovery key or passphrase called %1 to decrypt your secrets: Wprowadź swój klucz odzyskiwania lub frazę klucz nazwaną: %1 by odszyfrować swoje sekrety: - + Decryption failed Odszyfrowywanie nieudane @@ -581,17 +581,26 @@ - Device verification timed out. Przekroczono limit czasu na weryfikację urządzenia. - + Other party canceled the verification. Druga strona anulowała weryfikację. - + + Verification messages received out of order! + + + + + Unknown verification error. + + + + Close Zamknij @@ -612,7 +621,7 @@ Edytowanie paczki obrazów - + Add images Dodaj obrazy @@ -622,7 +631,7 @@ Naklejki (*.png *.webp *.gif *.jpg *.jpeg) - + State key Unikalny klucz paczki @@ -638,13 +647,13 @@ - + Use as Emoji Użyj jako Emoji - - + + Use as Sticker Użyj jako Naklejki @@ -697,7 +706,7 @@ Nowa paczka pokoju - + Private pack Prywatna paczka @@ -712,7 +721,7 @@ Paczka włączona globalnie - + Enable globally Włącz globalnie @@ -735,7 +744,7 @@ InputBar - + Select a file Wybierz plik @@ -745,7 +754,7 @@ Wszystkie pliki (*) - + Failed to upload media. Please try again. Wysłanie mediów nie powiodło się. Spróbuj ponownie. @@ -753,7 +762,7 @@ InviteDialog - + Invite users to %1 Zaproś użytkowników do %1 @@ -784,6 +793,32 @@ Anuluj + + JoinRoomDialog + + + Join room + + + + + Room ID or alias + ID pokoju lub alias + + + + LeaveRoomDialog + + + Leave room + Opuść pokój + + + + Are you sure you want to leave? + Czy na pewno chcesz wyjść? + + LoginPage @@ -849,25 +884,25 @@ Przykład: https://server.my:8787 ZALOGUJ - + - + You have entered an invalid Matrix ID e.g @joe:matrix.org Wprowadzono nieprawidłowe Matrix ID. Przykład prawidłowego ID: @ania:matrix.org - + Autodiscovery failed. Received malformed response. Automatyczne odkrywanie zakończone niepowodzeniem. Otrzymano nieprawidłową odpowiedź. - + Autodiscovery failed. Unknown error when requesting .well-known. Automatyczne odkrywanie zakończone niepowodzeniem. Napotkano nieznany błąd. .well-known. - + The required endpoints were not found. Possibly not a Matrix server. Nie odnaleziono wymaganych punktów końcowych. To może nie być serwer Matriksa. @@ -877,26 +912,44 @@ Przykład: https://server.my:8787 Otrzymano nieprawidłową odpowiedź. Upewnij się, że domena serwera domowego jest prawidłowa. - + An unknown error occured. Make sure the homeserver domain is valid. Wystąpił nieznany błąd. Upewnij się, że domena serwera domowego jest prawidłowa. - + SSO LOGIN Logowanie SSO - + Empty password Puste hasło - + SSO login failed Logowanie SSO zakończone niepowodzeniem + + LogoutDialog + + + Log out + + + + + A call is in progress. Log out? + + + + + Are you sure you want to log out? + + + MessageDelegate @@ -1022,7 +1075,7 @@ Przykład: https://server.my:8787 MessageView - + Edit Edytuj @@ -1042,7 +1095,7 @@ Przykład: https://server.my:8787 Opcje - + &Copy &Kopiuj @@ -1132,7 +1185,12 @@ Przykład: https://server.my:8787 Otrzymano prośbę o weryfikację - + + To allow other users to see, which of your devices actually belong to you, you can verify them. This also allows key backup to work automatically. Verify an unverified device now? (Please make sure you have one of those devices available.) + + + + To allow other users to see, which of your devices actually belong to you, you can verify them. This also allows key backup to work automatically. Verify %1 now? Aby umośliwić innym użytkownikom identyfikację, które urządzenia faktycznie należą do Ciebie, możesz wykonać ich weryfikację. To również umożliwi automatyczny backup kluczy. Zweryfikować %1 teraz? @@ -1181,40 +1239,26 @@ Przykład: https://server.my:8787 NotificationWarning - You will be pinging the whole room - Będziesz pingował cały pokój + You are about to notify the whole room + NotificationsManager - - + + %1 sent an encrypted message %1 wysłał(a) zaszyfrowaną wiadomość - - * %1 %2 - Format an emote message in a notification, %1 is the sender, %2 the message - Format wiadomości emote w powiadomieniu, %1 to nadawca, %2 to wiadomość. - * %1 %2 - - - + %1 replied: %2 Format a reply in a notification. %1 is the sender, %2 the message Format wiadomości w powiadomieniu. %1 to nadawca, %2 to wiadomość. %1 odpisał(a): %2 - - - %1: %2 - Format a normal message in a notification. %1 is the sender, %2 the message - Format normalnej wiadomości w powiadomieniu. %1 to nadawca, %2 to wiadomość. - %1: %2 - @@ -1245,7 +1289,7 @@ Przykład: https://server.my:8787 Nie znaleziono mikrofonu. - + Voice Dźwięk @@ -1276,7 +1320,7 @@ Przykład: https://server.my:8787 QCoreApplication - + Create a unique profile, which allows you to log into several accounts at the same time and start multiple instances of nheko. Stwórz unikalny profil, który pozwoli Ci na zalogowanie się do kilku kont jednocześnie i uruchomienie wielu instancji Nheko. @@ -1294,7 +1338,7 @@ Przykład: https://server.my:8787 ReadReceipts - + Read receipts Potwierdzenia przeczytania @@ -1302,7 +1346,7 @@ Przykład: https://server.my:8787 ReadReceiptsModel - + Yesterday, %1 Wczoraj, %1 @@ -1310,18 +1354,18 @@ Przykład: https://server.my:8787 RegisterPage - + Username Nazwa użytkownika - + The username must not be empty, and must contain only the characters a-z, 0-9, ., _, =, -, and /. Nazwa użytkownika nie może być pusta i może zawierać wyłącznie znaki a-z, 0-9, ., _, =, -, i /. - + Password Hasło @@ -1351,37 +1395,22 @@ Przykład: https://server.my:8787 ZAREJESTRUJ - - No supported registration flows! - Nie wspierana procedura rejestracji! - - - - Registration token - Token rejestracji - - - - Please enter a valid registration token. - Proszę wprowadzić prawidłowy token rejestracji. - - - + Autodiscovery failed. Received malformed response. Automatyczne odkrywanie zakończone niepowodzeniem. Otrzymano nieprawidłową odpowiedź. - + Autodiscovery failed. Unknown error when requesting .well-known. Automatyczne odkrywanie zakończone niepowodzeniem. Napotkano nieznany błąd. .well-known. - + The required endpoints were not found. Possibly not a Matrix server. Nie odnaleziono wymaganych interfejsów. To może nie być serwer Matrix. - + Received malformed response. Make sure the homeserver domain is valid. Otrzymano nieprawidłową odpowiedź. Upewnij się, że domena homeserver-a jest prawidłowa. @@ -1391,7 +1420,7 @@ Przykład: https://server.my:8787 Wystąpił nieznany błąd. Upewnij się, że domena homeserver-a jest prawidłowa. - + Password is not long enough (min 8 chars) Hasło jest zbyt krótkie (min. 8 znaków) @@ -1422,20 +1451,25 @@ Przykład: https://server.my:8787 RoomDirectory - + Explore Public Rooms Przeglądaj Pokoje Publiczne - + Search for public rooms Szukaj publicznych pokojów + + + Choose custom homeserver + + RoomInfo - + no version stored wersja nie została zachowana @@ -1452,16 +1486,6 @@ Przykład: https://server.my:8787 Enter the tag you want to use: Wprowadź tag, którego chcesz użyć: - - - Leave Room - Opuść Pokój - - - - Are you sure you want to leave this room? - Na pewno chcesz opuścić ten pokój? - Leave room @@ -1493,7 +1517,7 @@ Przykład: https://server.my:8787 Utwórz nowy tag... - + Status Message Wiadomość Statusowa @@ -1518,7 +1542,30 @@ Przykład: https://server.my:8787 Wyloguj - + + Encryption not set up + Cross-signing setup has not run yet. + + + + + Unverified login + The user just signed in with this device and hasn't verified their master key. + + + + + Please verify your other devices + There are unverified devices signed in to this account. + + + + + Close + Zamknij + + + Start a new chat Utwórz nowy czat @@ -1546,12 +1593,12 @@ Przykład: https://server.my:8787 RoomMembers - + Members of %1 Obecni w %1 - + %n people in %1 Summary above list of members @@ -1589,12 +1636,12 @@ Przykład: https://server.my:8787 RoomSettings - + Room Settings Ustawienia Pokoju - + %1 member(s) %1 użytkownik(ów) @@ -1701,12 +1748,12 @@ Przykład: https://server.my:8787 Wersja Pokoju - + Failed to enable encryption: %1 Nie udało się włączyć szyfrowania: %1 - + Select an avatar Wybierz awatar @@ -1726,8 +1773,8 @@ Przykład: https://server.my:8787 Błąd czytania pliku: %1 - - + + Failed to upload image: %s Nie udało się wysłać obrazu: %s @@ -1735,21 +1782,49 @@ Przykład: https://server.my:8787 RoomlistModel - + Pending invite. Oczekujące zaproszenie. - + Previewing this room Podgląd tego pokoju - + No preview available Podgląd pokoju niedostępny + + Root + + + Please enter your login password to continue: + + + + + Please enter a valid email address to continue: + + + + + Please enter a valid phone number to continue: + + + + + Please enter the token, which has been sent to you: + + + + + Wait for the confirmation link to arrive, then continue. + + + ScreenShare @@ -1807,20 +1882,98 @@ Przykład: https://server.my:8787 SecretStorage - + Failed to connect to secret storage Błąd połączenia do menadżera sekretów - Nheko could not connect to the secure storage to save encryption secrets to. This can have multiple reasons. Check if your D-Bus service is running and you have configured a service like KWallet, Gnome Secrets or the equivalent for your platform. If you are having trouble, feel free to open an issue here: https://github.com/Nheko-Reborn/nheko/issues - Nheko nie udało się połączyć z menadżerem sekretów w celu zapisania sekretów służących do szyfrowania. Mogło to zostać spowodowane różnymi czynnikami. Sprawdź czy serwis D-Bus działa oraz czy został skonfigurowany i uruchomiony jeden z następujących serwisów: KWallet, Gnome Secrets lub ekwiwalent dla twojej platformy. Jeśli nadal będziesz miał(a) problem, możesz zgłosić błąd tutaj: https://github.com/Nheko-Reborn/nheko/issues + Nheko could not connect to the secure storage to save encryption secrets to. This can have multiple reasons. Check if your D-Bus service is running and you have configured a service like KWallet, Gnome Keyring, KeePassXC or the equivalent for your platform. If you are having trouble, feel free to open an issue here: https://github.com/Nheko-Reborn/nheko/issues + + + + + SelfVerificationCheck + + + This is your recovery key. You will need it to restore access to your encrypted messages and verification keys. Keep this safe. Don't share it with anyone and don't lose it! Do not pass go! Do not collect $200! + + + + + Encryption setup successfully + + + + + Failed to setup encryption: %1 + + + + + Setup Encryption + + + + + Hello and welcome to Matrix! +It seems like you are new. Before you can securely encrypt your messages, we need to setup a few small things. You can either press accept immediately or adjust a few basic options. We also try to explain a few of the basics. You can skip those parts, but they might prove to be helpful! + + + + + Activate Encryption + + + + + It seems like you have encryption already configured for this account. To be able to access your encrypted messages and make this device appear as trusted, you can either verify an existing device or (if you have one) enter your recovery passphrase. Please select one of the options below. +If you choose verify, you need to have the other device available. If you choose "enter passphrase", you will need your recovery key or passphrase. If you click cancel, you can choose to verify yourself at a later point. + + + + + verify + + + + + enter passphrase + + + + + SelfVerificationStatus + + + Failed to create keys for cross-signing! + + + + + Failed to create keys for online key backup! + + + + + Failed to create keys secure server side secret storage! + + + + + Encryption Setup + + + + + Encryption setup failed: %1 + SingleImagePackModel - + Failed to update image pack: %1 Nie udało się uaktualnić paczki obrazów: %1 @@ -1836,7 +1989,7 @@ Przykład: https://server.my:8787 Nie udało się otworzyć obrazu: %1 - + Failed to upload image: %1 Nie udało się wysłać obrazu: %1 @@ -1893,18 +2046,18 @@ Przykład: https://server.my:8787 TimelineModel - + Message redaction failed: %1 Cenzurowanie wiadomości nie powiodło się: %1 - + Failed to encrypt event, sending aborted! Szyfrowanie event-u nie powiodło się, wysyłanie anulowane! - + Save image Zapisz obraz @@ -1924,7 +2077,7 @@ Przykład: https://server.my:8787 Zapisz plik - + %1 and %2 are typing. Multiple users are typing. First argument is a comma separated list of potentially multiple users. Second argument is the last user of that list. (If only one user is typing, %1 is empty. You should still use it in your string though to silence Qt warnings.) @@ -1934,7 +2087,7 @@ Przykład: https://server.my:8787 - + %1 opened the room to the public. %1 zmienił(a) status pokoju na publiczny. @@ -1974,12 +2127,12 @@ Przykład: https://server.my:8787 %1 umożliwił członkom tego pokoju na dostęp od teraz. - + %1 set the room history visible to members since they were invited. %1 umożliwił dostęp do historii tego pokoju członkom od momentu kiedy zostali zaproszeni. - + %1 set the room history visible to members since they joined the room. %1 umożliwił dostęp do historii tego pokoju członkom od momentu kiedy dołączyli do pokoju. @@ -1989,12 +2142,12 @@ Przykład: https://server.my:8787 %1 zmienił(a) uprawnienia pokoju. - + %1 was invited. %1 został(a) zaproszona/y. - + %1 changed their avatar. %1 zmienił(a) swój awatar. @@ -2009,12 +2162,12 @@ Przykład: https://server.my:8787 %1 dołączył(a). - + %1 joined via authorisation from %2's server. %1 dołączyła dzięki autoryzacji serwera użytkownika %2. - + %1 rejected their invite. %1 odrzucił(a) zaproszenie. @@ -2044,27 +2197,27 @@ Przykład: https://server.my:8787 Użytkownik %1 został zbanowany. - + Reason: %1 Powód: %1 - + %1 redacted their knock. Użytkownik %1 ocenzurował własne pukanie. - + You joined this room. Dołączyłeś(-łaś) do tego pokoju. - + %1 has changed their avatar and changed their display name to %2. Użytkownik %1 zmienił swojego awatara i zmienił swoją nazwę wyświetlaną na %2. - + %1 has changed their display name to %2. Użytkownik %1 zmienił swoją nazwę wyświetlaną na %2. @@ -2101,7 +2254,12 @@ Przykład: https://server.my:8787 Brak otwartych pokojów - + + No preview available + Podgląd pokoju niedostępny + + + %1 member(s) %1 obeczny(ch) @@ -2126,28 +2284,20 @@ Przykład: https://server.my:8787 Wróc do listy pokoi - - TimelineViewManager - - - No encrypted private chat found with this user. Create an encrypted private chat with this user and try again. - Nie znaleziono zaszyfrowanego prywatnego czatu z tym użytkownikiem. Utwórz nowy zaszyfrowany prywatny czat z tym użytkownikiem i spróbuj ponownie. - - TopBar - + Back to room list Wróć do listy pokoi - + No room selected Nie wybrano pokoju - + This room is not encrypted! Ten pokój nie jest szyfrowany! @@ -2158,8 +2308,8 @@ Przykład: https://server.my:8787 - This rooms contain verified devices and devices which have never changed their master key. - Ten pokój zawiera wyłącznie zweryfikowane urządzenie które nigdy nie zmieniły swojego klucza głównego. + This room contains verified devices and devices which have never changed their master key. + @@ -2205,10 +2355,35 @@ Przykład: https://server.my:8787 Zakończ + + UIA + + + No available registration flows! + + + + + + + Registration aborted + + + + + Please enter a valid registration token. + Proszę wprowadzić prawidłowy token rejestracji. + + + + Invalid token + + + UserProfile - + Global User Profile Globalny Profil Użytkownika @@ -2218,7 +2393,7 @@ Przykład: https://server.my:8787 Profil Użytkownika Pokoju - + Change avatar globally. Zmień awatar globalnie. @@ -2228,7 +2403,7 @@ Przykład: https://server.my:8787 Zmień awatar wyłącznie dla bieżącego pokoju. - + Change display name globally. Zmień nazwę wyświetlaną globalnie. @@ -2238,7 +2413,7 @@ Przykład: https://server.my:8787 Zmień nazwę wyświetlaną wyłącznie dla bieżącego pokoju. - + Room: %1 Pokój: %1 @@ -2248,18 +2423,18 @@ Przykład: https://server.my:8787 To profil specyficzny dla pokoju. Nazwa użytkownika oraz awatar mogą być inne niż globalna nazwa użytkownika i globalny awatar. - + Open the global profile for this user. Otwórz globalny profil tego użytkownika. - - + + Verify Zweryfikuj - + Start a private chat. Rozpocznij prywatny czat. @@ -2274,12 +2449,42 @@ Przykład: https://server.my:8787 Zbanuj użytkownika. - + + Refresh device list. + + + + + Sign out this device. + + + + + Change device name. + + + + + Last seen %1 from %2 + + + + Unverify Udweryfikuj - + + Sign out device %1 + + + + + You signed out this device. + + + + Select an avatar Wybierz awatar @@ -2302,8 +2507,8 @@ Przykład: https://server.my:8787 UserSettings - - + + Default Domyślne @@ -2311,7 +2516,7 @@ Przykład: https://server.my:8787 UserSettingsPage - + Minimize to tray Zminimalizuj do paska zadań @@ -2321,22 +2526,22 @@ Przykład: https://server.my:8787 Rozpocznij na pasku zadań - + Group's sidebar Pasek boczny grupy - + Circular Avatars Okrągłe awatary - + profile: %1 profil: %1 - + Default Domyślny @@ -2361,7 +2566,7 @@ Przykład: https://server.my:8787 POBIERZ - + Keep the application running in the background after closing the client window. Pozostaw aplikację działającą w tle po zamknięciu okna. @@ -2377,6 +2582,16 @@ OFF - square, ON - Circle. Zmień wygląd awatarów w czasie. OFF - kwadrat, ON - koło. + + + Use identicons + + + + + Display an identicon instead of a letter when a user has not set an avatar. + + Show a column containing groups and tags next to the room list. @@ -2406,7 +2621,7 @@ be blurred. Kiedy okno traci fokus, historia zostanie rozmyta. - + Privacy screen timeout (in seconds [0 - 3600]) Opóźnienie ekranu prywatności (w sekundach [0 - 3600]) @@ -2465,7 +2680,7 @@ Gdy ta opcja jest wyłączona, pokoje będą sortowane wyłącznie po znaczniku Gdy ta opcja jest włączona, pokoje z aktywnymi powiadomieniami (mało kółko z numerkiem w środku) będą na początku. Pokoje, które są wyciszone, będą sortowane po znaczniku czasowym, ponieważ zdaje się, że nie uważasz ich za równie wazne co pozostałe pokoje. - + Read receipts Potwierdzenia przeczytania @@ -2477,7 +2692,7 @@ Status is displayed next to timestamps. Status jest wyświetlany obok znacznika czasu. - + Send messages as Markdown Wysyłaj wiadomości używając Markdown-u @@ -2566,7 +2781,7 @@ To zwykle sprawia, że ikona aplikacji w tacce systemowej jest animowana.Pobierz klucze szyfrowania wiadomości i umieść w szyfrowanym backup-ie kluczy online. - + Enable online key backup Włącz backup kluczy online @@ -2576,7 +2791,7 @@ To zwykle sprawia, że ikona aplikacji w tacce systemowej jest animowana.Autorzy Nheko nie zalecają włączenia backup-u kluczy online dopóki symetryczny backup kluczy online nie jest dostępny. Włączyć mimo to? - + CACHED ZAPISANE W CACHE-U @@ -2586,7 +2801,7 @@ To zwykle sprawia, że ikona aplikacji w tacce systemowej jest animowana.NIE ZAPISANE W CACHE-U - + Scale factor Współczynnik skalowania @@ -2661,7 +2876,7 @@ To zwykle sprawia, że ikona aplikacji w tacce systemowej jest animowana.Odcisk palca urządzenia - + Session Keys Klucze sesji @@ -2681,17 +2896,17 @@ To zwykle sprawia, że ikona aplikacji w tacce systemowej jest animowana.SZYFROWANIE - + GENERAL OGÓLNE - + INTERFACE INTERFEJS - + Plays media like GIFs or WEBPs only when explicitly hovering over them. Odtwarzaj media takie jak GIF czy WEBP tylko gdy wskazane przy użyciu myszy. @@ -2731,7 +2946,7 @@ To zwykle sprawia, że ikona aplikacji w tacce systemowej jest animowana.Klucz używany do weryfikacji innych użytkowników. Gdy zapisany w cache-u, weryfikacja użytkownika dokona weryfikacji wszystkich jego urządzeń. - + Self signing key Klucz samo-podpisywania @@ -2761,14 +2976,14 @@ To zwykle sprawia, że ikona aplikacji w tacce systemowej jest animowana.Wszystkie pliki (*) - + Open Sessions File Otwórz Plik Sesji - + @@ -2776,19 +2991,19 @@ To zwykle sprawia, że ikona aplikacji w tacce systemowej jest animowana.Błąd - - + + File Password Hasło Pliku - + Enter the passphrase to decrypt the file: Wpisz frazę do odszyfrowania pliku: - + The password cannot be empty Hasło nie może być puste @@ -2803,6 +3018,14 @@ To zwykle sprawia, że ikona aplikacji w tacce systemowej jest animowana.Plik do którego zostaną wyeksportowane klucze sesji + + VerificationManager + + + No encrypted private chat found with this user. Create an encrypted private chat with this user and try again. + Nie znaleziono zaszyfrowanego prywatnego czatu z tym użytkownikiem. Utwórz nowy zaszyfrowany prywatny czat z tym użytkownikiem i spróbuj ponownie. + + Waiting @@ -2857,7 +3080,7 @@ To zwykle sprawia, że ikona aplikacji w tacce systemowej jest animowana. descriptiveTime - + Yesterday Wczoraj @@ -2928,37 +3151,6 @@ To zwykle sprawia, że ikona aplikacji w tacce systemowej jest animowana.Otwórz w przeglądarce i postępuj zgodnie z instrukcją. - - dialogs::JoinRoom - - - Join - Dołącz - - - - Cancel - Anuluj - - - - Room ID or alias - ID pokoju lub alias - - - - dialogs::LeaveRoom - - - Cancel - Anuluj - - - - Are you sure you want to leave? - Czy na pewno chcesz wyjść? - - dialogs::Logout @@ -3025,47 +3217,47 @@ Rozmiar multimediów: %2 %1 wysłał(a) klip audio - + You sent an image Wysłałeś(aś) obraz - + %1 sent an image %1 wysłał(a) obraz - + You sent a file Wysłałeś(aś) plik - + %1 sent a file %1 wysłał(a) plik - + You sent a video Wysłałeś(aś) wideo - + %1 sent a video %1 wysłał(a) wideo - + You sent a sticker Wysłałeś(aś) naklejkę - + %1 sent a sticker %1 wysłał(a) naklejkę - + You sent a notification Wysłałeś(aś) powiadomienie @@ -3080,7 +3272,7 @@ Rozmiar multimediów: %2 Ty: %1 - + %1: %2 %1: %2 @@ -3100,27 +3292,27 @@ Rozmiar multimediów: %2 Wykonujesz telefon - + %1 placed a call %1 wykonuje telefon - + You answered a call Odebrałeś(aś) połączenie - + %1 answered a call %1 odebrał(a) połączenie - + You ended a call Zakończyłeś(aś) połączenie - + %1 ended a call %1 zakończył(a) połączenie @@ -3128,7 +3320,7 @@ Rozmiar multimediów: %2 utils - + Unknown Message Type Nieznany Typ Wiadomości diff --git a/resources/langs/nheko_pt_BR.ts b/resources/langs/nheko_pt_BR.ts index 046d2254..a9851709 100644 --- a/resources/langs/nheko_pt_BR.ts +++ b/resources/langs/nheko_pt_BR.ts @@ -4,7 +4,7 @@ ActiveCallBar - + Calling... Ligando... @@ -56,7 +56,7 @@ CallInvite - + Video Call Chamada de Vídeo @@ -74,7 +74,7 @@ CallInviteBar - + Video Call Chamada de Vídeo @@ -117,7 +117,7 @@ CallManager - + Entire screen Tela Inteira @@ -125,23 +125,23 @@ ChatPage - + Failed to invite user: %1 Falha ao convidar usuário: %1 - + Invited user: %1 Usuário convidado: %1 - + Migrating the cache to the current version failed. This can have different reasons. Please open an issue and try to use an older version in the mean time. Alternatively you can try deleting the cache manually. Falha ao migrar cache para versão atual. Isso pode ter diferentes razões. Por favor reporte o problema e tente usar uma versão antiga no meio tempo. Alternativamente, você pode tentar excluir o cache manualmente. - + Confirm join Confirmar entrada @@ -151,23 +151,23 @@ Deseja realmente entrar em %1? - + Room %1 created. Sala %1 criada. - - + + Confirm invite Confirmar convite - + Do you really want to invite %1 (%2)? Deseja realmente convidar %1 (%2)? - + Failed to invite %1 to %2: %3 Falha ao convidar %1 para %2: %3 @@ -182,7 +182,7 @@ Deseja realmente expulsar %1 (%2)? - + Kicked user: %1 Usuário expulso: %1 @@ -197,7 +197,7 @@ Deseja realmente banir %1 (%2)? - + Failed to ban %1 in %2: %3 Falha ao banir %1 em %2: %3 @@ -217,7 +217,7 @@ Deseja realmente desbanir %1 (%2)? - + Failed to unban %1 in %2: %3 Falha ao desbanir %1 em %2: %3 @@ -227,12 +227,12 @@ Usuário desbanido: %1 - + Do you really want to start a private chat with %1? Deseja realmente iniciar uma conversa privada com %1? - + Cache migration failed! Migração do cache falhou! @@ -259,23 +259,23 @@ Falha ao restaurar dados salvos. Por favor faça login novamente. - + Failed to setup encryption keys. Server response: %1 %2. Please try again later. Falha ao configurar chaves de criptografia. Resposta do servidor: %1 %2. Por favor tente novamente mais tarde. - - + + Please try to login again: %1 - + Failed to join room: %1 Falha ao entrar na sala: %1 - + You joined the room Você entrou na sala @@ -285,17 +285,17 @@ Falha ao remover o convite: %1 - + Room creation failed: %1 - + Failed to leave room: %1 Falha ao sair da sala: %1 - + Failed to kick %1 from %2: %3 Falha ao expulsar %1 de %2: %3 @@ -354,7 +354,7 @@ CrossSigningSecrets - + Decrypt secrets @@ -364,12 +364,12 @@ - + Enter your recovery key or passphrase called %1 to decrypt your secrets: - + Decryption failed @@ -581,17 +581,26 @@ - Device verification timed out. - + Other party canceled the verification. - + + Verification messages received out of order! + + + + + Unknown verification error. + + + + Close @@ -612,7 +621,7 @@ - + Add images @@ -622,7 +631,7 @@ - + State key @@ -638,13 +647,13 @@ - + Use as Emoji - - + + Use as Sticker @@ -697,7 +706,7 @@ - + Private pack @@ -712,7 +721,7 @@ - + Enable globally @@ -735,7 +744,7 @@ InputBar - + Select a file @@ -745,7 +754,7 @@ - + Failed to upload media. Please try again. @@ -753,7 +762,7 @@ InviteDialog - + Invite users to %1 @@ -784,6 +793,32 @@ Cancelar + + JoinRoomDialog + + + Join room + + + + + Room ID or alias + + + + + LeaveRoomDialog + + + Leave room + + + + + Are you sure you want to leave? + + + LoginPage @@ -846,25 +881,25 @@ Example: https://server.my:8787 - + - + You have entered an invalid Matrix ID e.g @joe:matrix.org - + Autodiscovery failed. Received malformed response. - + Autodiscovery failed. Unknown error when requesting .well-known. - + The required endpoints were not found. Possibly not a Matrix server. @@ -874,26 +909,44 @@ Example: https://server.my:8787 - + An unknown error occured. Make sure the homeserver domain is valid. - + SSO LOGIN - + Empty password - + SSO login failed + + LogoutDialog + + + Log out + + + + + A call is in progress. Log out? + + + + + Are you sure you want to log out? + + + MessageDelegate @@ -1019,7 +1072,7 @@ Example: https://server.my:8787 MessageView - + Edit @@ -1039,7 +1092,7 @@ Example: https://server.my:8787 - + &Copy @@ -1129,7 +1182,12 @@ Example: https://server.my:8787 - + + To allow other users to see, which of your devices actually belong to you, you can verify them. This also allows key backup to work automatically. Verify an unverified device now? (Please make sure you have one of those devices available.) + + + + To allow other users to see, which of your devices actually belong to you, you can verify them. This also allows key backup to work automatically. Verify %1 now? @@ -1178,7 +1236,7 @@ Example: https://server.my:8787 NotificationWarning - You will be pinging the whole room + You are about to notify the whole room @@ -1186,29 +1244,17 @@ Example: https://server.my:8787 NotificationsManager - - + + %1 sent an encrypted message - - * %1 %2 - Format an emote message in a notification, %1 is the sender, %2 the message - - - - + %1 replied: %2 Format a reply in a notification. %1 is the sender, %2 the message - - - %1: %2 - Format a normal message in a notification. %1 is the sender, %2 the message - - @@ -1239,7 +1285,7 @@ Example: https://server.my:8787 Nenhum microfone encontrado. - + Voice @@ -1270,7 +1316,7 @@ Example: https://server.my:8787 QCoreApplication - + Create a unique profile, which allows you to log into several accounts at the same time and start multiple instances of nheko. @@ -1288,7 +1334,7 @@ Example: https://server.my:8787 ReadReceipts - + Read receipts @@ -1296,7 +1342,7 @@ Example: https://server.my:8787 ReadReceiptsModel - + Yesterday, %1 @@ -1304,18 +1350,18 @@ Example: https://server.my:8787 RegisterPage - + Username - + The username must not be empty, and must contain only the characters a-z, 0-9, ., _, =, -, and /. - + Password @@ -1345,37 +1391,22 @@ Example: https://server.my:8787 - - No supported registration flows! - - - - - Registration token - - - - - Please enter a valid registration token. - - - - + Autodiscovery failed. Received malformed response. - + Autodiscovery failed. Unknown error when requesting .well-known. - + The required endpoints were not found. Possibly not a Matrix server. - + Received malformed response. Make sure the homeserver domain is valid. @@ -1385,7 +1416,7 @@ Example: https://server.my:8787 - + Password is not long enough (min 8 chars) @@ -1416,20 +1447,25 @@ Example: https://server.my:8787 RoomDirectory - + Explore Public Rooms - + Search for public rooms + + + Choose custom homeserver + + RoomInfo - + no version stored @@ -1446,16 +1482,6 @@ Example: https://server.my:8787 Enter the tag you want to use: - - - Leave Room - - - - - Are you sure you want to leave this room? - - Leave room @@ -1487,7 +1513,7 @@ Example: https://server.my:8787 - + Status Message @@ -1512,7 +1538,30 @@ Example: https://server.my:8787 - + + Encryption not set up + Cross-signing setup has not run yet. + + + + + Unverified login + The user just signed in with this device and hasn't verified their master key. + + + + + Please verify your other devices + There are unverified devices signed in to this account. + + + + + Close + + + + Start a new chat @@ -1540,12 +1589,12 @@ Example: https://server.my:8787 RoomMembers - + Members of %1 - + %n people in %1 Summary above list of members @@ -1582,12 +1631,12 @@ Example: https://server.my:8787 RoomSettings - + Room Settings - + %1 member(s) @@ -1693,12 +1742,12 @@ Example: https://server.my:8787 - + Failed to enable encryption: %1 - + Select an avatar @@ -1718,8 +1767,8 @@ Example: https://server.my:8787 - - + + Failed to upload image: %s @@ -1727,21 +1776,49 @@ Example: https://server.my:8787 RoomlistModel - + Pending invite. - + Previewing this room - + No preview available + + Root + + + Please enter your login password to continue: + + + + + Please enter a valid email address to continue: + + + + + Please enter a valid phone number to continue: + + + + + Please enter the token, which has been sent to you: + + + + + Wait for the confirmation link to arrive, then continue. + + + ScreenShare @@ -1799,20 +1876,98 @@ Example: https://server.my:8787 SecretStorage - + Failed to connect to secret storage - Nheko could not connect to the secure storage to save encryption secrets to. This can have multiple reasons. Check if your D-Bus service is running and you have configured a service like KWallet, Gnome Secrets or the equivalent for your platform. If you are having trouble, feel free to open an issue here: https://github.com/Nheko-Reborn/nheko/issues + Nheko could not connect to the secure storage to save encryption secrets to. This can have multiple reasons. Check if your D-Bus service is running and you have configured a service like KWallet, Gnome Keyring, KeePassXC or the equivalent for your platform. If you are having trouble, feel free to open an issue here: https://github.com/Nheko-Reborn/nheko/issues + + + + + SelfVerificationCheck + + + This is your recovery key. You will need it to restore access to your encrypted messages and verification keys. Keep this safe. Don't share it with anyone and don't lose it! Do not pass go! Do not collect $200! + + + + + Encryption setup successfully + + + + + Failed to setup encryption: %1 + + + + + Setup Encryption + + + + + Hello and welcome to Matrix! +It seems like you are new. Before you can securely encrypt your messages, we need to setup a few small things. You can either press accept immediately or adjust a few basic options. We also try to explain a few of the basics. You can skip those parts, but they might prove to be helpful! + + + + + Activate Encryption + + + + + It seems like you have encryption already configured for this account. To be able to access your encrypted messages and make this device appear as trusted, you can either verify an existing device or (if you have one) enter your recovery passphrase. Please select one of the options below. +If you choose verify, you need to have the other device available. If you choose "enter passphrase", you will need your recovery key or passphrase. If you click cancel, you can choose to verify yourself at a later point. + + + + + verify + + + + + enter passphrase + + + + + SelfVerificationStatus + + + Failed to create keys for cross-signing! + + + + + Failed to create keys for online key backup! + + + + + Failed to create keys secure server side secret storage! + + + + + Encryption Setup + + + + + Encryption setup failed: %1 SingleImagePackModel - + Failed to update image pack: %1 @@ -1828,7 +1983,7 @@ Example: https://server.my:8787 - + Failed to upload image: %1 @@ -1885,18 +2040,18 @@ Example: https://server.my:8787 TimelineModel - + Message redaction failed: %1 - + Failed to encrypt event, sending aborted! - + Save image @@ -1916,7 +2071,7 @@ Example: https://server.my:8787 - + %1 and %2 are typing. Multiple users are typing. First argument is a comma separated list of potentially multiple users. Second argument is the last user of that list. (If only one user is typing, %1 is empty. You should still use it in your string though to silence Qt warnings.) @@ -1925,7 +2080,7 @@ Example: https://server.my:8787 - + %1 opened the room to the public. @@ -1965,12 +2120,12 @@ Example: https://server.my:8787 - + %1 set the room history visible to members since they were invited. - + %1 set the room history visible to members since they joined the room. @@ -1980,12 +2135,12 @@ Example: https://server.my:8787 - + %1 was invited. - + %1 changed their avatar. @@ -2000,12 +2155,12 @@ Example: https://server.my:8787 - + %1 joined via authorisation from %2's server. - + %1 rejected their invite. @@ -2035,27 +2190,27 @@ Example: https://server.my:8787 - + Reason: %1 - + %1 redacted their knock. - + You joined this room. Você entrou nessa sala. - + %1 has changed their avatar and changed their display name to %2. - + %1 has changed their display name to %2. @@ -2092,7 +2247,12 @@ Example: https://server.my:8787 - + + No preview available + + + + %1 member(s) @@ -2117,28 +2277,20 @@ Example: https://server.my:8787 - - TimelineViewManager - - - No encrypted private chat found with this user. Create an encrypted private chat with this user and try again. - - - TopBar - + Back to room list - + No room selected - + This room is not encrypted! @@ -2149,7 +2301,7 @@ Example: https://server.my:8787 - This rooms contain verified devices and devices which have never changed their master key. + This room contains verified devices and devices which have never changed their master key. @@ -2196,10 +2348,35 @@ Example: https://server.my:8787 + + UIA + + + No available registration flows! + + + + + + + Registration aborted + + + + + Please enter a valid registration token. + + + + + Invalid token + + + UserProfile - + Global User Profile @@ -2209,7 +2386,7 @@ Example: https://server.my:8787 - + Change avatar globally. @@ -2219,7 +2396,7 @@ Example: https://server.my:8787 - + Change display name globally. @@ -2229,7 +2406,7 @@ Example: https://server.my:8787 - + Room: %1 @@ -2239,18 +2416,18 @@ Example: https://server.my:8787 - + Open the global profile for this user. - - + + Verify - + Start a private chat. @@ -2265,12 +2442,42 @@ Example: https://server.my:8787 - + + Refresh device list. + + + + + Sign out this device. + + + + + Change device name. + + + + + Last seen %1 from %2 + + + + Unverify - + + Sign out device %1 + + + + + You signed out this device. + + + + Select an avatar @@ -2293,8 +2500,8 @@ Example: https://server.my:8787 UserSettings - - + + Default @@ -2302,7 +2509,7 @@ Example: https://server.my:8787 UserSettingsPage - + Minimize to tray @@ -2312,22 +2519,22 @@ Example: https://server.my:8787 - + Group's sidebar - + Circular Avatars - + profile: %1 - + Default @@ -2352,7 +2559,7 @@ Example: https://server.my:8787 - + Keep the application running in the background after closing the client window. @@ -2367,6 +2574,16 @@ Example: https://server.my:8787 OFF - square, ON - Circle. + + + Use identicons + + + + + Display an identicon instead of a letter when a user has not set an avatar. + + Show a column containing groups and tags next to the room list. @@ -2395,7 +2612,7 @@ be blurred. - + Privacy screen timeout (in seconds [0 - 3600]) @@ -2450,7 +2667,7 @@ If this is on, rooms which have active notifications (the small circle with a nu - + Read receipts @@ -2461,7 +2678,7 @@ Status is displayed next to timestamps. - + Send messages as Markdown @@ -2548,7 +2765,7 @@ This usually causes the application icon in the task bar to animate in some fash - + Enable online key backup @@ -2558,7 +2775,7 @@ This usually causes the application icon in the task bar to animate in some fash - + CACHED @@ -2568,7 +2785,7 @@ This usually causes the application icon in the task bar to animate in some fash - + Scale factor @@ -2643,7 +2860,7 @@ This usually causes the application icon in the task bar to animate in some fash - + Session Keys @@ -2663,17 +2880,17 @@ This usually causes the application icon in the task bar to animate in some fash - + GENERAL - + INTERFACE - + Plays media like GIFs or WEBPs only when explicitly hovering over them. @@ -2713,7 +2930,7 @@ This usually causes the application icon in the task bar to animate in some fash - + Self signing key @@ -2743,14 +2960,14 @@ This usually causes the application icon in the task bar to animate in some fash - + Open Sessions File - + @@ -2758,19 +2975,19 @@ This usually causes the application icon in the task bar to animate in some fash - - + + File Password - + Enter the passphrase to decrypt the file: - + The password cannot be empty @@ -2785,6 +3002,14 @@ This usually causes the application icon in the task bar to animate in some fash + + VerificationManager + + + No encrypted private chat found with this user. Create an encrypted private chat with this user and try again. + + + Waiting @@ -2839,7 +3064,7 @@ This usually causes the application icon in the task bar to animate in some fash descriptiveTime - + Yesterday @@ -2910,37 +3135,6 @@ This usually causes the application icon in the task bar to animate in some fash - - dialogs::JoinRoom - - - Join - - - - - Cancel - Cancelar - - - - Room ID or alias - - - - - dialogs::LeaveRoom - - - Cancel - Cancelar - - - - Are you sure you want to leave? - - - dialogs::Logout @@ -3005,47 +3199,47 @@ Media size: %2 - + You sent an image - + %1 sent an image - + You sent a file - + %1 sent a file - + You sent a video - + %1 sent a video - + You sent a sticker - + %1 sent a sticker - + You sent a notification @@ -3060,7 +3254,7 @@ Media size: %2 - + %1: %2 @@ -3080,27 +3274,27 @@ Media size: %2 - + %1 placed a call - + You answered a call - + %1 answered a call - + You ended a call - + %1 ended a call @@ -3108,7 +3302,7 @@ Media size: %2 utils - + Unknown Message Type diff --git a/resources/langs/nheko_pt_PT.ts b/resources/langs/nheko_pt_PT.ts index d30c267e..69eab896 100644 --- a/resources/langs/nheko_pt_PT.ts +++ b/resources/langs/nheko_pt_PT.ts @@ -4,7 +4,7 @@ ActiveCallBar - + Calling... A chamar... @@ -56,7 +56,7 @@ CallInvite - + Video Call Videochamada @@ -74,7 +74,7 @@ CallInviteBar - + Video Call Videochamada @@ -117,7 +117,7 @@ CallManager - + Entire screen Ecrã inteiro @@ -125,23 +125,23 @@ ChatPage - + Failed to invite user: %1 Falha ao convidar utilizador: %1 - + Invited user: %1 Utilizador convidado: %1 - + Migrating the cache to the current version failed. This can have different reasons. Please open an issue and try to use an older version in the mean time. Alternatively you can try deleting the cache manually. A migração da cache para a versão atual falhou, e existem várias razões possíveis. Por favor abra um problema ("issue") e experimente usar uma versão mais antiga entretanto. Alternativamente, pode tentar apagar a cache manualmente. - + Confirm join Confirmar entrada @@ -151,23 +151,23 @@ Tem a certeza que quer entrar em %1? - + Room %1 created. Sala %1 criada. - - + + Confirm invite Confirmar convite - + Do you really want to invite %1 (%2)? Tem a certeza que quer convidar %1 (%2)? - + Failed to invite %1 to %2: %3 Falha ao convidar %1 para %2: %3 @@ -182,7 +182,7 @@ Tem a certeza que quer expulsar %1 (%2)? - + Kicked user: %1 Utilizador expulso: %1 @@ -197,7 +197,7 @@ Tem a certeza que quer banir %1 (%2)? - + Failed to ban %1 in %2: %3 Falha ao banir %1 em %2: %3 @@ -217,7 +217,7 @@ Tem a certeza que quer perdoar %1 (%2)? - + Failed to unban %1 in %2: %3 Falha ao perdoar %1 em %2: %3 @@ -227,12 +227,12 @@ Utilizador perdoado: %1 - + Do you really want to start a private chat with %1? Tem a certeza que quer começar uma conversa privada com %1? - + Cache migration failed! Falha ao migrar a cache! @@ -259,23 +259,23 @@ Falha ao restaurar dados guardados. Por favor, autentique-se novamente. - + Failed to setup encryption keys. Server response: %1 %2. Please try again later. Falha ao estabelecer chaves encriptadas. Resposta do servidor: %1 %2. Tente novamente mais tarde. - - + + Please try to login again: %1 Por favor, tente autenticar-se novamente: %1 - + Failed to join room: %1 Falha ao entrar em sala: %1 - + You joined the room Entrou na sala @@ -285,17 +285,17 @@ Falha ao remover convite: %1 - + Room creation failed: %1 Falha ao criar sala: %1 - + Failed to leave room: %1 Falha ao sair da sala: %1 - + Failed to kick %1 from %2: %3 Falha ao expulsar %1 de %2: %3 @@ -354,7 +354,7 @@ CrossSigningSecrets - + Decrypt secrets Desencriptar segredos @@ -364,12 +364,12 @@ Insira a sua chave de recuperação ou palavra-passe para desencriptar os seus segredos: - + Enter your recovery key or passphrase called %1 to decrypt your secrets: Insira a sua chave de recuperação ou palavra-passe chamada %1 para desencriptar os seus segredos: - + Decryption failed Falha ao desencriptar @@ -581,17 +581,26 @@ - Device verification timed out. A verificação do dispositivo expirou. - + Other party canceled the verification. A outra parte cancelou a verificação. - + + Verification messages received out of order! + + + + + Unknown verification error. + + + + Close Fechar @@ -612,7 +621,7 @@ A editar pacote de imagens - + Add images Adicionar imagens @@ -622,7 +631,7 @@ Autocolantes (*.png *.webp *.gif *.jpg *.jpeg) - + State key @@ -638,13 +647,13 @@ - + Use as Emoji Usar como emoji - - + + Use as Sticker Usar como autocolante @@ -697,7 +706,7 @@ Criar pacote de sala - + Private pack Pacote privado @@ -712,7 +721,7 @@ Pacote ativo globalmente - + Enable globally Ativar globalmente @@ -735,7 +744,7 @@ InputBar - + Select a file Selecionar um ficheiro @@ -745,7 +754,7 @@ Todos os ficheiros (*) - + Failed to upload media. Please try again. Falha ao carregar mídia. Por favor, tente novamente. @@ -753,7 +762,7 @@ InviteDialog - + Invite users to %1 Convidar utilizadores para %1 @@ -784,6 +793,32 @@ Cancelar + + JoinRoomDialog + + + Join room + + + + + Room ID or alias + + + + + LeaveRoomDialog + + + Leave room + Sair da sala + + + + Are you sure you want to leave? + + + LoginPage @@ -850,25 +885,25 @@ Exemplo: https://servidor.meu:8787 INCIAR SESSÃO - + - + You have entered an invalid Matrix ID e.g @joe:matrix.org Inseriu um ID Matrix inválido p. ex. @ze:matrix.org - + Autodiscovery failed. Received malformed response. Falha na descoberta automática. Reposta mal formatada recebida. - + Autodiscovery failed. Unknown error when requesting .well-known. Falha na descoberta automática. Erro desconhecido ao solicitar ".well-known". - + The required endpoints were not found. Possibly not a Matrix server. Não foi possível encontrar os funções ("endpoints") necessárias. Possivelmente não é um servidor Matrix. @@ -878,26 +913,44 @@ Exemplo: https://servidor.meu:8787 Resposta mal formada recebida. Certifique-se que o domínio do servidor está correto. - + An unknown error occured. Make sure the homeserver domain is valid. Erro desconhecido. Certifique-se que o domínio do servidor é válido. - + SSO LOGIN ENTRAR COM ISU (SSO) - + Empty password Palavra-passe vazia - + SSO login failed Falha no ISU (SSO) + + LogoutDialog + + + Log out + + + + + A call is in progress. Log out? + + + + + Are you sure you want to log out? + + + MessageDelegate @@ -1023,7 +1076,7 @@ Exemplo: https://servidor.meu:8787 MessageView - + Edit Editar @@ -1043,7 +1096,7 @@ Exemplo: https://servidor.meu:8787 Opções - + &Copy &Copiar @@ -1133,7 +1186,12 @@ Exemplo: https://servidor.meu:8787 Pedido de verificação recebido - + + To allow other users to see, which of your devices actually belong to you, you can verify them. This also allows key backup to work automatically. Verify an unverified device now? (Please make sure you have one of those devices available.) + + + + To allow other users to see, which of your devices actually belong to you, you can verify them. This also allows key backup to work automatically. Verify %1 now? Para que outros possam ver que dispositivos pertencem realmente a si, pode verificá-los. Isso permite, também, que a cópia de segurança de chaves funcione automaticamente. Verificar %1 agora? @@ -1182,37 +1240,25 @@ Exemplo: https://servidor.meu:8787 NotificationWarning - You will be pinging the whole room - Irá causar uma notificação para toda a sala + You are about to notify the whole room + NotificationsManager - - + + %1 sent an encrypted message %1 enviou uma mensagem encriptada - - * %1 %2 - Format an emote message in a notification, %1 is the sender, %2 the message - * %1 %2 - - - + %1 replied: %2 Format a reply in a notification. %1 is the sender, %2 the message %1 respondeu: %2 - - - %1: %2 - Format a normal message in a notification. %1 is the sender, %2 the message - %1: %2 - @@ -1243,7 +1289,7 @@ Exemplo: https://servidor.meu:8787 Nenhum microfone encontrado. - + Voice Voz @@ -1274,7 +1320,7 @@ Exemplo: https://servidor.meu:8787 QCoreApplication - + Create a unique profile, which allows you to log into several accounts at the same time and start multiple instances of nheko. Crie um perfil único que lhe permite entrar em várias contas simultaneamente e iniciar várias instâncias do Nheko. @@ -1292,7 +1338,7 @@ Exemplo: https://servidor.meu:8787 ReadReceipts - + Read receipts Recibos de leitura @@ -1300,7 +1346,7 @@ Exemplo: https://servidor.meu:8787 ReadReceiptsModel - + Yesterday, %1 Ontem, %1 @@ -1308,18 +1354,18 @@ Exemplo: https://servidor.meu:8787 RegisterPage - + Username Nome de utilizador - + The username must not be empty, and must contain only the characters a-z, 0-9, ., _, =, -, and /. O nome de utilizador não pode ser vazio e tem que conter apenas os caracteres a-z, 0-9, ., _, =, - e /. - + Password Palavra-passe @@ -1349,37 +1395,22 @@ Exemplo: https://servidor.meu:8787 REGISTAR - - No supported registration flows! - Nenhum percurso de registo suportado! - - - - Registration token - Testemunho de registo - - - - Please enter a valid registration token. - Por favor, insira um testemunho de registo válido. - - - + Autodiscovery failed. Received malformed response. Falha na descoberta automática. Resposta mal formada recebida. - + Autodiscovery failed. Unknown error when requesting .well-known. Falha na descoberta automática. Erro desconhecido ao requisitar ".well-known". - + The required endpoints were not found. Possibly not a Matrix server. Não foi possível encontrar os funções ("endpoints") necessárias. Possivelmente não é um servidor Matrix. - + Received malformed response. Make sure the homeserver domain is valid. Resposta mal formada recebida. Certifique-se que o domínio do servidor está correto. @@ -1389,7 +1420,7 @@ Exemplo: https://servidor.meu:8787 Erro desconhecido. Certifique-se que o domínio do servidor é válido. - + Password is not long enough (min 8 chars) A palavra-passe não é longa o suficiente (mín, 8 caracteres) @@ -1420,20 +1451,25 @@ Exemplo: https://servidor.meu:8787 RoomDirectory - + Explore Public Rooms Explorar salas públicas - + Search for public rooms Procurar por salas públicas + + + Choose custom homeserver + + RoomInfo - + no version stored nenhuma versão guardada @@ -1450,16 +1486,6 @@ Exemplo: https://servidor.meu:8787 Enter the tag you want to use: Insira a etiqueta que quer usar: - - - Leave Room - Sair da sala - - - - Are you sure you want to leave this room? - Tem a certeza que quer sair desta sala? - Leave room @@ -1491,7 +1517,7 @@ Exemplo: https://servidor.meu:8787 Criar nova etiqueta... - + Status Message Mensagem de estado @@ -1516,7 +1542,30 @@ Exemplo: https://servidor.meu:8787 Terminar sessão - + + Encryption not set up + Cross-signing setup has not run yet. + + + + + Unverified login + The user just signed in with this device and hasn't verified their master key. + + + + + Please verify your other devices + There are unverified devices signed in to this account. + + + + + Close + Fechar + + + Start a new chat Iniciar uma nova conversa @@ -1544,12 +1593,12 @@ Exemplo: https://servidor.meu:8787 RoomMembers - + Members of %1 Membros de %1 - + %n people in %1 Summary above list of members @@ -1586,12 +1635,12 @@ Exemplo: https://servidor.meu:8787 RoomSettings - + Room Settings Definições de sala - + %1 member(s) %1 membro(s) @@ -1697,12 +1746,12 @@ Exemplo: https://servidor.meu:8787 Versão da sala - + Failed to enable encryption: %1 Falha ao ativar encriptação: %1 - + Select an avatar Selecionar um ícone @@ -1722,8 +1771,8 @@ Exemplo: https://servidor.meu:8787 Erro ao ler ficheiro: %1 - - + + Failed to upload image: %s Falha ao carregar imagem: %s @@ -1731,21 +1780,49 @@ Exemplo: https://servidor.meu:8787 RoomlistModel - + Pending invite. Convite pendente. - + Previewing this room A pré-visualizar esta sala - + No preview available Pré-visualização não disponível + + Root + + + Please enter your login password to continue: + + + + + Please enter a valid email address to continue: + + + + + Please enter a valid phone number to continue: + + + + + Please enter the token, which has been sent to you: + + + + + Wait for the confirmation link to arrive, then continue. + + + ScreenShare @@ -1803,20 +1880,98 @@ Exemplo: https://servidor.meu:8787 SecretStorage - + Failed to connect to secret storage Falha ao ligar ao armazenamento secreto - Nheko could not connect to the secure storage to save encryption secrets to. This can have multiple reasons. Check if your D-Bus service is running and you have configured a service like KWallet, Gnome Secrets or the equivalent for your platform. If you are having trouble, feel free to open an issue here: https://github.com/Nheko-Reborn/nheko/issues - O Nheko não foi capaz de ligar ao armazenamento seguro para guardar os segredos de encriptação. Isto pode se dever a várias razões. Confirme se o serviço "D-Bus" está a correr e se configurou um serviço como o "KWallet", "Gnome Secrets" ou algo equivalente na sua plataforma. Se continuar com dificuldades, não hesite em abrir um problema aqui: https://github.com/Nheko-Reborn/nheko/issues (em Inglês) + Nheko could not connect to the secure storage to save encryption secrets to. This can have multiple reasons. Check if your D-Bus service is running and you have configured a service like KWallet, Gnome Keyring, KeePassXC or the equivalent for your platform. If you are having trouble, feel free to open an issue here: https://github.com/Nheko-Reborn/nheko/issues + + + + + SelfVerificationCheck + + + This is your recovery key. You will need it to restore access to your encrypted messages and verification keys. Keep this safe. Don't share it with anyone and don't lose it! Do not pass go! Do not collect $200! + + + + + Encryption setup successfully + + + + + Failed to setup encryption: %1 + + + + + Setup Encryption + + + + + Hello and welcome to Matrix! +It seems like you are new. Before you can securely encrypt your messages, we need to setup a few small things. You can either press accept immediately or adjust a few basic options. We also try to explain a few of the basics. You can skip those parts, but they might prove to be helpful! + + + + + Activate Encryption + + + + + It seems like you have encryption already configured for this account. To be able to access your encrypted messages and make this device appear as trusted, you can either verify an existing device or (if you have one) enter your recovery passphrase. Please select one of the options below. +If you choose verify, you need to have the other device available. If you choose "enter passphrase", you will need your recovery key or passphrase. If you click cancel, you can choose to verify yourself at a later point. + + + + + verify + + + + + enter passphrase + + + + + SelfVerificationStatus + + + Failed to create keys for cross-signing! + + + + + Failed to create keys for online key backup! + + + + + Failed to create keys secure server side secret storage! + + + + + Encryption Setup + + + + + Encryption setup failed: %1 + SingleImagePackModel - + Failed to update image pack: %1 Falha ao atualizar pacote de imagem: %1 @@ -1832,7 +1987,7 @@ Exemplo: https://servidor.meu:8787 Falha ao abrir imagem: %1 - + Failed to upload image: %1 Falha ao carregar imagem: %1 @@ -1889,18 +2044,18 @@ Exemplo: https://servidor.meu:8787 TimelineModel - + Message redaction failed: %1 Falha ao eliminar mensagem: %1 - + Failed to encrypt event, sending aborted! Falha ao encriptar evento, envio abortado! - + Save image Guardar imagem @@ -1920,7 +2075,7 @@ Exemplo: https://servidor.meu:8787 Guardar ficheiro - + %1 and %2 are typing. Multiple users are typing. First argument is a comma separated list of potentially multiple users. Second argument is the last user of that list. (If only one user is typing, %1 is empty. You should still use it in your string though to silence Qt warnings.) @@ -1929,7 +2084,7 @@ Exemplo: https://servidor.meu:8787 - + %1 opened the room to the public. %1 abriu a sala ao público. @@ -1969,12 +2124,12 @@ Exemplo: https://servidor.meu:8787 %1 tornou o histórico da sala, a partir deste momento, visível a membros - + %1 set the room history visible to members since they were invited. %1 tornou o histórico visível a membros desde o seu convite. - + %1 set the room history visible to members since they joined the room. %1 tornou o histórico da sala visível ao membros desde a sua entrada. @@ -1984,12 +2139,12 @@ Exemplo: https://servidor.meu:8787 %1 alterou as permissões da sala. - + %1 was invited. %1 foi convidado. - + %1 changed their avatar. %1 alterou o seu avatar. @@ -2004,12 +2159,12 @@ Exemplo: https://servidor.meu:8787 %1 entrou. - + %1 joined via authorisation from %2's server. %1 entrou com autorização do servidor de %2. - + %1 rejected their invite. %1 recusou o seu convite. @@ -2039,27 +2194,27 @@ Exemplo: https://servidor.meu:8787 %1 foi banido. - + Reason: %1 Razão: %1 - + %1 redacted their knock. %1 eliminou a sua "batida à porta". - + You joined this room. Entrou na sala. - + %1 has changed their avatar and changed their display name to %2. %1 alterou o seu avatar e também o seu nome de exibição para %2. - + %1 has changed their display name to %2. %1 alterou o seu nome de exibição para %2. @@ -2096,7 +2251,12 @@ Exemplo: https://servidor.meu:8787 Nenhuma sala aberta - + + No preview available + Pré-visualização não disponível + + + %1 member(s) %1 membro(s) @@ -2121,28 +2281,20 @@ Exemplo: https://servidor.meu:8787 Voltar à lista de salas - - TimelineViewManager - - - No encrypted private chat found with this user. Create an encrypted private chat with this user and try again. - Não foi encontrada nenhuma conversa privada e encriptada com este utilizador. Crie uma e tente novamente. - - TopBar - + Back to room list Voltar à lista de salas - + No room selected Nenhuma sala selecionada - + This room is not encrypted! Esta sala não é encriptada! @@ -2153,8 +2305,8 @@ Exemplo: https://servidor.meu:8787 - This rooms contain verified devices and devices which have never changed their master key. - Esta sala contém dispositivos verificados e outros que nunca alteraram a sua chave-mestra. + This room contains verified devices and devices which have never changed their master key. + @@ -2200,10 +2352,35 @@ Exemplo: https://servidor.meu:8787 Sair + + UIA + + + No available registration flows! + + + + + + + Registration aborted + + + + + Please enter a valid registration token. + Por favor, insira um testemunho de registo válido. + + + + Invalid token + + + UserProfile - + Global User Profile Perfil de utilizador global @@ -2213,7 +2390,7 @@ Exemplo: https://servidor.meu:8787 Perfil de utilizador na sala - + Change avatar globally. Alterar avatar globalmente. @@ -2223,7 +2400,7 @@ Exemplo: https://servidor.meu:8787 Alterar avatar. Irá apenas afetar esta sala. - + Change display name globally. Alterar nome de exibição globalmente. @@ -2233,7 +2410,7 @@ Exemplo: https://servidor.meu:8787 Alterar nome de exibição. Irá apenas afetar esta sala. - + Room: %1 Sala: %1 @@ -2243,18 +2420,18 @@ Exemplo: https://servidor.meu:8787 Este é um perfil específico desta sala. O nome e avatar do utilizador poderão ser diferentes dos seus homólogos globais. - + Open the global profile for this user. Abrir o perfil global deste utilizador. - - + + Verify Verificar - + Start a private chat. Iniciar uma conversa privada. @@ -2269,12 +2446,42 @@ Exemplo: https://servidor.meu:8787 Banir o utilizador. - + + Refresh device list. + + + + + Sign out this device. + + + + + Change device name. + + + + + Last seen %1 from %2 + + + + Unverify Anular verificação - + + Sign out device %1 + + + + + You signed out this device. + + + + Select an avatar Selecionar um avatar @@ -2297,8 +2504,8 @@ Exemplo: https://servidor.meu:8787 UserSettings - - + + Default Padrão @@ -2306,7 +2513,7 @@ Exemplo: https://servidor.meu:8787 UserSettingsPage - + Minimize to tray Minimizar para bandeja @@ -2316,22 +2523,22 @@ Exemplo: https://servidor.meu:8787 Iniciar na bandeja - + Group's sidebar Barra lateral do grupo - + Circular Avatars Avatares circulares - + profile: %1 perfil: %1 - + Default Padrão @@ -2356,7 +2563,7 @@ Exemplo: https://servidor.meu:8787 DESCARREGAR - + Keep the application running in the background after closing the client window. Manter a aplicação a correr em segundo plano depois de fechar a janela. @@ -2371,6 +2578,16 @@ Exemplo: https://servidor.meu:8787 OFF - square, ON - Circle. + + + Use identicons + + + + + Display an identicon instead of a letter when a user has not set an avatar. + + Show a column containing groups and tags next to the room list. @@ -2400,7 +2617,7 @@ be blurred. será desfocada. - + Privacy screen timeout (in seconds [0 - 3600]) Tempo de inatividade para ecrã de privacidade (em segundos [0 - 3600]) @@ -2460,7 +2677,7 @@ Se desativada, a lista de salas será apenas ordenada pela data da última mensa Se ativada, salas com notificações ativas (pequeno círculo com um número dentro) serão ordenadas no topo. Salas silenciadas continuarão a ser ordenadas por data, visto que não são consideradas tão importantes como as outras. - + Read receipts Recibos de leitura @@ -2472,7 +2689,7 @@ Status is displayed next to timestamps. Estado exibido ao lado da data. - + Send messages as Markdown Enviar mensagens como Markdown @@ -2559,7 +2776,7 @@ This usually causes the application icon in the task bar to animate in some fash - + Enable online key backup @@ -2569,7 +2786,7 @@ This usually causes the application icon in the task bar to animate in some fash - + CACHED @@ -2579,7 +2796,7 @@ This usually causes the application icon in the task bar to animate in some fash - + Scale factor @@ -2654,7 +2871,7 @@ This usually causes the application icon in the task bar to animate in some fash - + Session Keys @@ -2674,17 +2891,17 @@ This usually causes the application icon in the task bar to animate in some fash - + GENERAL - + INTERFACE - + Plays media like GIFs or WEBPs only when explicitly hovering over them. @@ -2724,7 +2941,7 @@ This usually causes the application icon in the task bar to animate in some fash - + Self signing key @@ -2746,22 +2963,22 @@ This usually causes the application icon in the task bar to animate in some fash Select a file - + Selecionar um ficheiro All Files (*) - + Todos os ficheiros (*) - + Open Sessions File - + @@ -2769,19 +2986,19 @@ This usually causes the application icon in the task bar to animate in some fash - - + + File Password - + Enter the passphrase to decrypt the file: - + The password cannot be empty @@ -2796,6 +3013,14 @@ This usually causes the application icon in the task bar to animate in some fash + + VerificationManager + + + No encrypted private chat found with this user. Create an encrypted private chat with this user and try again. + Não foi encontrada nenhuma conversa privada e encriptada com este utilizador. Crie uma e tente novamente. + + Waiting @@ -2839,18 +3064,18 @@ This usually causes the application icon in the task bar to animate in some fash REGISTER - + REGISTAR LOGIN - + INCIAR SESSÃO descriptiveTime - + Yesterday @@ -2921,37 +3146,6 @@ This usually causes the application icon in the task bar to animate in some fash - - dialogs::JoinRoom - - - Join - - - - - Cancel - Cancelar - - - - Room ID or alias - - - - - dialogs::LeaveRoom - - - Cancel - Cancelar - - - - Are you sure you want to leave? - - - dialogs::Logout @@ -3016,47 +3210,47 @@ Media size: %2 - + You sent an image - + %1 sent an image - + You sent a file - + %1 sent a file - + You sent a video - + %1 sent a video - + You sent a sticker - + %1 sent a sticker - + You sent a notification @@ -3071,9 +3265,9 @@ Media size: %2 - + %1: %2 - + %1: %2 @@ -3083,7 +3277,7 @@ Media size: %2 %1 sent an encrypted message - + %1 enviou uma mensagem encriptada @@ -3091,27 +3285,27 @@ Media size: %2 - + %1 placed a call - + You answered a call - + %1 answered a call - + You ended a call - + %1 ended a call @@ -3119,7 +3313,7 @@ Media size: %2 utils - + Unknown Message Type diff --git a/resources/langs/nheko_ro.ts b/resources/langs/nheko_ro.ts index 86afca01..6d28da46 100644 --- a/resources/langs/nheko_ro.ts +++ b/resources/langs/nheko_ro.ts @@ -4,7 +4,7 @@ ActiveCallBar - + Calling... @@ -56,7 +56,7 @@ CallInvite - + Video Call @@ -74,7 +74,7 @@ CallInviteBar - + Video Call @@ -117,7 +117,7 @@ CallManager - + Entire screen @@ -125,23 +125,23 @@ ChatPage - + Failed to invite user: %1 Nu s-a putut invita utilizatorul: %1 - + Invited user: %1 Utilizator invitat: %1 - + Migrating the cache to the current version failed. This can have different reasons. Please open an issue and try to use an older version in the mean time. Alternatively you can try deleting the cache manually. Nu s-a putut muta cache-ul pe versiunea curentă. Acest lucru poate avea diferite cauze. Vă rugăm să deschideți un issue și încercați să folosiți o versiune mai veche între timp. O altă opțiune ar fi să încercați să ștergeți cache-ul manual. - + Confirm join @@ -151,23 +151,23 @@ - + Room %1 created. Camera %1 a fost creată. - - + + Confirm invite - + Do you really want to invite %1 (%2)? - + Failed to invite %1 to %2: %3 Nu s-a putut invita %1 în %2: %3 @@ -182,7 +182,7 @@ - + Kicked user: %1 Utilizator eliminat: %1 @@ -197,7 +197,7 @@ - + Failed to ban %1 in %2: %3 Nu s-a putut interzice utilizatorul %1 în %2: %3 @@ -217,7 +217,7 @@ - + Failed to unban %1 in %2: %3 Nu s-a putut dezinterzice %1 în %2: %3 @@ -227,12 +227,12 @@ Utilizator dezinterzis: %1 - + Do you really want to start a private chat with %1? - + Cache migration failed! Nu s-a putut migra cache-ul! @@ -259,23 +259,23 @@ Nu s-au putut restabili datele salvate. Vă rugăm să vă reconectați. - + Failed to setup encryption keys. Server response: %1 %2. Please try again later. Nu s-au putut stabili cheile. Răspunsul serverului: %1 %2. Vă rugăm încercați mai târziu. - - + + Please try to login again: %1 Vă rugăm să vă reconectați: %1 - + Failed to join room: %1 Nu s-a putut alătura la cameră: %1 - + You joined the room V-ați alăturat camerei @@ -285,17 +285,17 @@ Nu s-a putut șterge invitația: %1 - + Room creation failed: %1 Nu s-a putut crea camera: %1 - + Failed to leave room: %1 Nu s-a putut părăsi camera: %1 - + Failed to kick %1 from %2: %3 @@ -354,7 +354,7 @@ CrossSigningSecrets - + Decrypt secrets @@ -364,12 +364,12 @@ - + Enter your recovery key or passphrase called %1 to decrypt your secrets: - + Decryption failed @@ -581,17 +581,26 @@ - Device verification timed out. - + Other party canceled the verification. - + + Verification messages received out of order! + + + + + Unknown verification error. + + + + Close Închide @@ -612,7 +621,7 @@ - + Add images @@ -622,7 +631,7 @@ - + State key @@ -638,13 +647,13 @@ - + Use as Emoji - - + + Use as Sticker @@ -697,7 +706,7 @@ - + Private pack @@ -712,7 +721,7 @@ - + Enable globally @@ -735,7 +744,7 @@ InputBar - + Select a file @@ -745,7 +754,7 @@ Toate fișierele (*) - + Failed to upload media. Please try again. @@ -753,7 +762,7 @@ InviteDialog - + Invite users to %1 @@ -784,6 +793,32 @@ + + JoinRoomDialog + + + Join room + + + + + Room ID or alias + IDul camerei sau alias + + + + LeaveRoomDialog + + + Leave room + Părăsește camera + + + + Are you sure you want to leave? + Sigur vrei să părăsești camera? + + LoginPage @@ -850,25 +885,25 @@ Exemplu: https://serverul.meu:8787 CONECTARE - + - + You have entered an invalid Matrix ID e.g @joe:matrix.org - + Autodiscovery failed. Received malformed response. Autodescoperirea a eșuat. Răspunsul primit este defectuos. - + Autodiscovery failed. Unknown error when requesting .well-known. Autodescoperirea a eșuat. Eroare necunoscută la solicitarea .well-known. - + The required endpoints were not found. Possibly not a Matrix server. Punctele finale necesare nu au fost găsite. Posibil a nu fi un server Matrix. @@ -878,26 +913,44 @@ Exemplu: https://serverul.meu:8787 Răspuns eronat primit. Verificați ca domeniul homeserverului să fie valid. - + An unknown error occured. Make sure the homeserver domain is valid. A apărut o eroare necunoscută. Verificați ca domeniul homeserverului să fie valid. - + SSO LOGIN CONECTARE SSO - + Empty password Parolă necompletată - + SSO login failed Conectarea SSO a eșuat + + LogoutDialog + + + Log out + + + + + A call is in progress. Log out? + + + + + Are you sure you want to log out? + + + MessageDelegate @@ -1023,7 +1076,7 @@ Exemplu: https://serverul.meu:8787 MessageView - + Edit @@ -1043,7 +1096,7 @@ Exemplu: https://serverul.meu:8787 Opțiuni - + &Copy @@ -1133,7 +1186,12 @@ Exemplu: https://serverul.meu:8787 - + + To allow other users to see, which of your devices actually belong to you, you can verify them. This also allows key backup to work automatically. Verify an unverified device now? (Please make sure you have one of those devices available.) + + + + To allow other users to see, which of your devices actually belong to you, you can verify them. This also allows key backup to work automatically. Verify %1 now? @@ -1182,7 +1240,7 @@ Exemplu: https://serverul.meu:8787 NotificationWarning - You will be pinging the whole room + You are about to notify the whole room @@ -1190,29 +1248,17 @@ Exemplu: https://serverul.meu:8787 NotificationsManager - - + + %1 sent an encrypted message %1 a trimis un mesaj criptat - - * %1 %2 - Format an emote message in a notification, %1 is the sender, %2 the message - - - - + %1 replied: %2 Format a reply in a notification. %1 is the sender, %2 the message - - - %1: %2 - Format a normal message in a notification. %1 is the sender, %2 the message - %1: %2 - @@ -1243,7 +1289,7 @@ Exemplu: https://serverul.meu:8787 - + Voice @@ -1274,7 +1320,7 @@ Exemplu: https://serverul.meu:8787 QCoreApplication - + Create a unique profile, which allows you to log into several accounts at the same time and start multiple instances of nheko. @@ -1292,7 +1338,7 @@ Exemplu: https://serverul.meu:8787 ReadReceipts - + Read receipts Confirmări de citire @@ -1300,7 +1346,7 @@ Exemplu: https://serverul.meu:8787 ReadReceiptsModel - + Yesterday, %1 @@ -1308,18 +1354,18 @@ Exemplu: https://serverul.meu:8787 RegisterPage - + Username Nume de utilizator - + The username must not be empty, and must contain only the characters a-z, 0-9, ., _, =, -, and /. Numele de utilizator nu poate fi gol, și trebuie să conțină doar caracterele a-z, 0-9, ., =, - și /. - + Password Parolă @@ -1349,37 +1395,22 @@ Exemplu: https://serverul.meu:8787 ÎNREGISTRARE - - No supported registration flows! - Fluxuri de înregistrare nesuportate! - - - - Registration token - - - - - Please enter a valid registration token. - - - - + Autodiscovery failed. Received malformed response. Autodescoperirea a eșuat. Răspunsul primit este defectuos. - + Autodiscovery failed. Unknown error when requesting .well-known. Autodescoperirea a eșuat. Eroare necunoscută la solicitarea .well-known. - + The required endpoints were not found. Possibly not a Matrix server. Punctele finale necesare nu au fost găsite. Posibil a nu fi un server Matrix. - + Received malformed response. Make sure the homeserver domain is valid. Răspuns eronat primit. Verificați ca domeniul homeserverului să fie valid. @@ -1389,7 +1420,7 @@ Exemplu: https://serverul.meu:8787 A apărut o eroare necunoscută. Verificați ca domeniul homeserverului să fie valid. - + Password is not long enough (min 8 chars) Parola nu este destul de lungă (minim 8 caractere) @@ -1420,20 +1451,25 @@ Exemplu: https://serverul.meu:8787 RoomDirectory - + Explore Public Rooms - + Search for public rooms + + + Choose custom homeserver + + RoomInfo - + no version stored nicio versiune stocată @@ -1450,16 +1486,6 @@ Exemplu: https://serverul.meu:8787 Enter the tag you want to use: - - - Leave Room - - - - - Are you sure you want to leave this room? - - Leave room @@ -1491,7 +1517,7 @@ Exemplu: https://serverul.meu:8787 - + Status Message @@ -1516,7 +1542,30 @@ Exemplu: https://serverul.meu:8787 Deconectare - + + Encryption not set up + Cross-signing setup has not run yet. + + + + + Unverified login + The user just signed in with this device and hasn't verified their master key. + + + + + Please verify your other devices + There are unverified devices signed in to this account. + + + + + Close + Închide + + + Start a new chat Începe o nouă conversație @@ -1544,12 +1593,12 @@ Exemplu: https://serverul.meu:8787 RoomMembers - + Members of %1 - + %n people in %1 Summary above list of members @@ -1587,12 +1636,12 @@ Exemplu: https://serverul.meu:8787 RoomSettings - + Room Settings - + %1 member(s) @@ -1698,12 +1747,12 @@ Exemplu: https://serverul.meu:8787 - + Failed to enable encryption: %1 Nu s-a putut activa criptarea: %1 - + Select an avatar Selectează un avatar @@ -1723,8 +1772,8 @@ Exemplu: https://serverul.meu:8787 Eroare întâmpinată la citirea fișierului: %1 - - + + Failed to upload image: %s Nu s-a putut încărca imaginea: %s @@ -1732,21 +1781,49 @@ Exemplu: https://serverul.meu:8787 RoomlistModel - + Pending invite. - + Previewing this room - + No preview available + + Root + + + Please enter your login password to continue: + + + + + Please enter a valid email address to continue: + + + + + Please enter a valid phone number to continue: + + + + + Please enter the token, which has been sent to you: + + + + + Wait for the confirmation link to arrive, then continue. + + + ScreenShare @@ -1804,20 +1881,98 @@ Exemplu: https://serverul.meu:8787 SecretStorage - + Failed to connect to secret storage - Nheko could not connect to the secure storage to save encryption secrets to. This can have multiple reasons. Check if your D-Bus service is running and you have configured a service like KWallet, Gnome Secrets or the equivalent for your platform. If you are having trouble, feel free to open an issue here: https://github.com/Nheko-Reborn/nheko/issues + Nheko could not connect to the secure storage to save encryption secrets to. This can have multiple reasons. Check if your D-Bus service is running and you have configured a service like KWallet, Gnome Keyring, KeePassXC or the equivalent for your platform. If you are having trouble, feel free to open an issue here: https://github.com/Nheko-Reborn/nheko/issues + + + + + SelfVerificationCheck + + + This is your recovery key. You will need it to restore access to your encrypted messages and verification keys. Keep this safe. Don't share it with anyone and don't lose it! Do not pass go! Do not collect $200! + + + + + Encryption setup successfully + + + + + Failed to setup encryption: %1 + + + + + Setup Encryption + + + + + Hello and welcome to Matrix! +It seems like you are new. Before you can securely encrypt your messages, we need to setup a few small things. You can either press accept immediately or adjust a few basic options. We also try to explain a few of the basics. You can skip those parts, but they might prove to be helpful! + + + + + Activate Encryption + + + + + It seems like you have encryption already configured for this account. To be able to access your encrypted messages and make this device appear as trusted, you can either verify an existing device or (if you have one) enter your recovery passphrase. Please select one of the options below. +If you choose verify, you need to have the other device available. If you choose "enter passphrase", you will need your recovery key or passphrase. If you click cancel, you can choose to verify yourself at a later point. + + + + + verify + + + + + enter passphrase + + + + + SelfVerificationStatus + + + Failed to create keys for cross-signing! + + + + + Failed to create keys for online key backup! + + + + + Failed to create keys secure server side secret storage! + + + + + Encryption Setup + + + + + Encryption setup failed: %1 SingleImagePackModel - + Failed to update image pack: %1 @@ -1833,7 +1988,7 @@ Exemplu: https://serverul.meu:8787 - + Failed to upload image: %1 @@ -1890,18 +2045,18 @@ Exemplu: https://serverul.meu:8787 TimelineModel - + Message redaction failed: %1 Redactare mesaj eșuată: %1 - + Failed to encrypt event, sending aborted! - + Save image Salvați imaginea @@ -1921,7 +2076,7 @@ Exemplu: https://serverul.meu:8787 Salvați fișier - + %1 and %2 are typing. Multiple users are typing. First argument is a comma separated list of potentially multiple users. Second argument is the last user of that list. (If only one user is typing, %1 is empty. You should still use it in your string though to silence Qt warnings.) @@ -1931,7 +2086,7 @@ Exemplu: https://serverul.meu:8787 - + %1 opened the room to the public. %1 a deschis camera publicului. @@ -1971,12 +2126,12 @@ Exemplu: https://serverul.meu:8787 %1 a făcut istoricul camerei accesibil membrilor din acest moment. - + %1 set the room history visible to members since they were invited. %1 a setat istoricul camerei accesibil participanților din momentul invitării. - + %1 set the room history visible to members since they joined the room. %1 a setat istoricul camerei accesibil membrilor din momentul alăturării. @@ -1986,12 +2141,12 @@ Exemplu: https://serverul.meu:8787 %1 a modificat permisiunile camerei. - + %1 was invited. %1 a fost invitat(ă). - + %1 changed their avatar. %1 și-a schimbat avatarul. @@ -2006,12 +2161,12 @@ Exemplu: https://serverul.meu:8787 %1 s-a alăturat. - + %1 joined via authorisation from %2's server. - + %1 rejected their invite. %1 a respins invitația. @@ -2041,27 +2196,27 @@ Exemplu: https://serverul.meu:8787 %1 a fost interzis(ă). - + Reason: %1 - + %1 redacted their knock. %1 și-a redactat ciocănitul. - + You joined this room. Te-ai alăturat camerei. - + %1 has changed their avatar and changed their display name to %2. - + %1 has changed their display name to %2. @@ -2098,7 +2253,12 @@ Exemplu: https://serverul.meu:8787 Nicio cameră deschisă - + + No preview available + + + + %1 member(s) @@ -2123,28 +2283,20 @@ Exemplu: https://serverul.meu:8787 - - TimelineViewManager - - - No encrypted private chat found with this user. Create an encrypted private chat with this user and try again. - - - TopBar - + Back to room list - + No room selected - + This room is not encrypted! @@ -2155,7 +2307,7 @@ Exemplu: https://serverul.meu:8787 - This rooms contain verified devices and devices which have never changed their master key. + This room contains verified devices and devices which have never changed their master key. @@ -2202,10 +2354,35 @@ Exemplu: https://serverul.meu:8787 Ieșire + + UIA + + + No available registration flows! + + + + + + + Registration aborted + + + + + Please enter a valid registration token. + + + + + Invalid token + + + UserProfile - + Global User Profile @@ -2215,7 +2392,7 @@ Exemplu: https://serverul.meu:8787 - + Change avatar globally. @@ -2225,7 +2402,7 @@ Exemplu: https://serverul.meu:8787 - + Change display name globally. @@ -2235,7 +2412,7 @@ Exemplu: https://serverul.meu:8787 - + Room: %1 @@ -2245,18 +2422,18 @@ Exemplu: https://serverul.meu:8787 - + Open the global profile for this user. - - + + Verify - + Start a private chat. @@ -2271,12 +2448,42 @@ Exemplu: https://serverul.meu:8787 - + + Refresh device list. + + + + + Sign out this device. + + + + + Change device name. + + + + + Last seen %1 from %2 + + + + Unverify - + + Sign out device %1 + + + + + You signed out this device. + + + + Select an avatar Selectează un avatar @@ -2299,8 +2506,8 @@ Exemplu: https://serverul.meu:8787 UserSettings - - + + Default @@ -2308,7 +2515,7 @@ Exemplu: https://serverul.meu:8787 UserSettingsPage - + Minimize to tray Minimizează în bara de notificări @@ -2318,22 +2525,22 @@ Exemplu: https://serverul.meu:8787 Pornește în bara de notificări - + Group's sidebar Bara laterală a grupului - + Circular Avatars Avatare rotunde - + profile: %1 - + Default @@ -2358,7 +2565,7 @@ Exemplu: https://serverul.meu:8787 - + Keep the application running in the background after closing the client window. @@ -2373,6 +2580,16 @@ Exemplu: https://serverul.meu:8787 OFF - square, ON - Circle. + + + Use identicons + + + + + Display an identicon instead of a letter when a user has not set an avatar. + + Show a column containing groups and tags next to the room list. @@ -2401,7 +2618,7 @@ be blurred. - + Privacy screen timeout (in seconds [0 - 3600]) @@ -2456,7 +2673,7 @@ If this is on, rooms which have active notifications (the small circle with a nu - + Read receipts Confirmări de citire @@ -2467,7 +2684,7 @@ Status is displayed next to timestamps. Vezi dacă mesajul tău a fost citit. Starea este afișată lângă timestampuri. - + Send messages as Markdown Trimite mesaje ca Markdown @@ -2554,7 +2771,7 @@ This usually causes the application icon in the task bar to animate in some fash - + Enable online key backup @@ -2564,7 +2781,7 @@ This usually causes the application icon in the task bar to animate in some fash - + CACHED @@ -2574,7 +2791,7 @@ This usually causes the application icon in the task bar to animate in some fash - + Scale factor Factor de dimensiune @@ -2649,7 +2866,7 @@ This usually causes the application icon in the task bar to animate in some fash Amprentă Dispozitiv - + Session Keys Chei de sesiune @@ -2669,17 +2886,17 @@ This usually causes the application icon in the task bar to animate in some fash CRIPTARE - + GENERAL GENERAL - + INTERFACE INTERFAȚĂ - + Plays media like GIFs or WEBPs only when explicitly hovering over them. @@ -2719,7 +2936,7 @@ This usually causes the application icon in the task bar to animate in some fash - + Self signing key @@ -2749,14 +2966,14 @@ This usually causes the application icon in the task bar to animate in some fash Toate fișierele (*) - + Open Sessions File Deschide fișierul de sesiuni - + @@ -2764,19 +2981,19 @@ This usually causes the application icon in the task bar to animate in some fash Eroare - - + + File Password Parolă fișier - + Enter the passphrase to decrypt the file: Introduceți parola pentru a decripta fișierul: - + The password cannot be empty Parola nu poate fi necompletată @@ -2791,6 +3008,14 @@ This usually causes the application icon in the task bar to animate in some fash Fișier pentru salvarea cheilor de sesiune exportate + + VerificationManager + + + No encrypted private chat found with this user. Create an encrypted private chat with this user and try again. + + + Waiting @@ -2845,7 +3070,7 @@ This usually causes the application icon in the task bar to animate in some fash descriptiveTime - + Yesterday Ieri @@ -2916,37 +3141,6 @@ This usually causes the application icon in the task bar to animate in some fash Deschideți fallback, urmăriți pașii și confirmați după ce i-ați completat. - - dialogs::JoinRoom - - - Join - Alăturare - - - - Cancel - Anulare - - - - Room ID or alias - IDul camerei sau alias - - - - dialogs::LeaveRoom - - - Cancel - Anulare - - - - Are you sure you want to leave? - Sigur vrei să părăsești camera? - - dialogs::Logout @@ -3013,47 +3207,47 @@ Dimensiune media: %2 %1 a trimis un clip audio - + You sent an image Ai trimis o imagine - + %1 sent an image %1 a trimis o imagine - + You sent a file Ai trimis un fișier - + %1 sent a file %1 a trimis un fișier - + You sent a video Ai trimis un video - + %1 sent a video %1 a trimis un video - + You sent a sticker Ai trimis un sticker - + %1 sent a sticker %1 a trimis un sticker - + You sent a notification Ai trimis o notificare @@ -3068,7 +3262,7 @@ Dimensiune media: %2 Tu: %1 - + %1: %2 %1: %2 @@ -3088,27 +3282,27 @@ Dimensiune media: %2 Ai inițiat un apel - + %1 placed a call %1 a inițiat un apel - + You answered a call Ai răspuns unui apel - + %1 answered a call %1 a răspuns unui apel - + You ended a call Ai încheiat un apel - + %1 ended a call %1 a încheiat un apel @@ -3116,7 +3310,7 @@ Dimensiune media: %2 utils - + Unknown Message Type Tip mesaj necunoscut diff --git a/resources/langs/nheko_ru.ts b/resources/langs/nheko_ru.ts index 7cfaab56..698d277f 100644 --- a/resources/langs/nheko_ru.ts +++ b/resources/langs/nheko_ru.ts @@ -4,7 +4,7 @@ ActiveCallBar - + Calling... Вызов... @@ -56,7 +56,7 @@ CallInvite - + Video Call Видео Звонок @@ -74,7 +74,7 @@ CallInviteBar - + Video Call Видео Звонок @@ -117,7 +117,7 @@ CallManager - + Entire screen Весь экран @@ -125,23 +125,23 @@ ChatPage - + Failed to invite user: %1 Не удалось пригласить пользователя: %1 - + Invited user: %1 Приглашенный пользователь: %1 - + Migrating the cache to the current version failed. This can have different reasons. Please open an issue and try to use an older version in the mean time. Alternatively you can try deleting the cache manually. Миграция кэша для текущей версии не удалась. Это может происходить по разным причинам. Пожалуйста сообщите о проблеме и попробуйте временно использовать старую версию. Так-же вы можете попробовать удалить кэш самостоятельно. - + Confirm join Подтвердить вход @@ -151,23 +151,23 @@ Вы действительно хотите присоединиться? - + Room %1 created. Комната %1 создана. - - + + Confirm invite Подтвердите приглашение - + Do you really want to invite %1 (%2)? Вы точно хотите пригласить %1 (%2)? - + Failed to invite %1 to %2: %3 Не удалось пригласить %1 в %2: %3 @@ -182,7 +182,7 @@ Вы точно хотите выгнать %1 (%2)? - + Kicked user: %1 Выгнанный пользователь: %1 @@ -197,7 +197,7 @@ Вы точно хотите заблокировать %1 (%2)? - + Failed to ban %1 in %2: %3 Не удалось заблокировать %1 в %2: %3 @@ -217,7 +217,7 @@ Вы точно хотите разблокировать %1 (%2)? - + Failed to unban %1 in %2: %3 Не удалось разблокировать %1 в %2: %3 @@ -227,12 +227,12 @@ Разблокированный пользователь: %1 - + Do you really want to start a private chat with %1? Вы действительно хотите начать личную переписку с %1? - + Cache migration failed! Миграция кэша не удалась! @@ -259,23 +259,23 @@ Не удалось восстановить сохраненные данные. Пожалуйста, войдите снова. - + Failed to setup encryption keys. Server response: %1 %2. Please try again later. Не удалось настроить ключи шифрования. Ответ сервера:%1 %2. Пожалуйста, попробуйте позже. - - + + Please try to login again: %1 Повторите попытку входа: %1 - + Failed to join room: %1 Не удалось присоединиться к комнате: %1 - + You joined the room Вы присоединились к комнате @@ -285,17 +285,17 @@ Не удалось отменить приглашение: %1 - + Room creation failed: %1 Не удалось создать комнату: %1 - + Failed to leave room: %1 Не удалось покинуть комнату: %1 - + Failed to kick %1 from %2: %3 Не удалось выгнать %1 из %2: %3 @@ -354,7 +354,7 @@ CrossSigningSecrets - + Decrypt secrets Расшифровать секреты @@ -364,12 +364,12 @@ Введите свой ключ восстановления или пароль для расшифровки секретов: - + Enter your recovery key or passphrase called %1 to decrypt your secrets: Введите свой ключ восстановления или пароль названный %1 для расшифровки Ваших секретов: - + Decryption failed Расшифровка не удалась @@ -581,17 +581,26 @@ - Device verification timed out. Время для верификации устройста закончилось. - + Other party canceled the verification. Другая сторона отменила верификацию. - + + Verification messages received out of order! + + + + + Unknown verification error. + + + + Close Закрыть @@ -612,7 +621,7 @@ - + Add images @@ -622,7 +631,7 @@ - + State key @@ -638,13 +647,13 @@ - + Use as Emoji - - + + Use as Sticker @@ -697,7 +706,7 @@ - + Private pack @@ -712,7 +721,7 @@ - + Enable globally @@ -735,7 +744,7 @@ InputBar - + Select a file Выберите файл @@ -745,7 +754,7 @@ Все файлы (*) - + Failed to upload media. Please try again. Не удалось загрузить медиа. Пожалуйста попробуйте ещё раз @@ -753,7 +762,7 @@ InviteDialog - + Invite users to %1 @@ -784,6 +793,32 @@ + + JoinRoomDialog + + + Join room + + + + + Room ID or alias + Идентификатор или псевдоним комнаты + + + + LeaveRoomDialog + + + Leave room + Покинуть комнату + + + + Are you sure you want to leave? + Вы действительно желаете выйти? + + LoginPage @@ -850,25 +885,25 @@ Example: https://server.my:8787 ВОЙТИ - + - + You have entered an invalid Matrix ID e.g @joe:matrix.org Вы ввели не правильный Matrix ID, @joe:matrix.org - + Autodiscovery failed. Received malformed response. Автообноружение не удалось. Получен поврежденный ответ. - + Autodiscovery failed. Unknown error when requesting .well-known. Автообноружение не удалось. Не известаня ошибка во время запроса .well-known. - + The required endpoints were not found. Possibly not a Matrix server. Необходимые конечные точки не найдены. Возможно, это не сервер Matrix. @@ -878,26 +913,44 @@ Example: https://server.my:8787 Получен неверный ответ. Убедитесь, что домен homeserver действителен. - + An unknown error occured. Make sure the homeserver domain is valid. Произошла неизвестная ошибка. Убедитесь, что домен homeserver действителен. - + SSO LOGIN SSO ВХОД - + Empty password Пустой пароль - + SSO login failed SSO вход не удался + + LogoutDialog + + + Log out + + + + + A call is in progress. Log out? + + + + + Are you sure you want to log out? + + + MessageDelegate @@ -1023,7 +1076,7 @@ Example: https://server.my:8787 MessageView - + Edit Редактировать @@ -1043,7 +1096,7 @@ Example: https://server.my:8787 Опции - + &Copy @@ -1133,7 +1186,12 @@ Example: https://server.my:8787 Получен Запрос Верификации - + + To allow other users to see, which of your devices actually belong to you, you can verify them. This also allows key backup to work automatically. Verify an unverified device now? (Please make sure you have one of those devices available.) + + + + To allow other users to see, which of your devices actually belong to you, you can verify them. This also allows key backup to work automatically. Verify %1 now? @@ -1182,7 +1240,7 @@ Example: https://server.my:8787 NotificationWarning - You will be pinging the whole room + You are about to notify the whole room @@ -1190,29 +1248,17 @@ Example: https://server.my:8787 NotificationsManager - - + + %1 sent an encrypted message %1 отправил зашифрованное сообщение - - * %1 %2 - Format an emote message in a notification, %1 is the sender, %2 the message - - - - + %1 replied: %2 Format a reply in a notification. %1 is the sender, %2 the message - - - %1: %2 - Format a normal message in a notification. %1 is the sender, %2 the message - %1: %2 - @@ -1243,7 +1289,7 @@ Example: https://server.my:8787 Микрофон не найден. - + Voice Голос @@ -1274,7 +1320,7 @@ Example: https://server.my:8787 QCoreApplication - + Create a unique profile, which allows you to log into several accounts at the same time and start multiple instances of nheko. Создать уникальный профиль, который позволяет вести несколько аккаунтов и запускать множество сущностей nheko. @@ -1292,7 +1338,7 @@ Example: https://server.my:8787 ReadReceipts - + Read receipts Просмотр получателей @@ -1300,7 +1346,7 @@ Example: https://server.my:8787 ReadReceiptsModel - + Yesterday, %1 @@ -1308,18 +1354,18 @@ Example: https://server.my:8787 RegisterPage - + Username Имя пользователя - + The username must not be empty, and must contain only the characters a-z, 0-9, ., _, =, -, and /. Имя пользователя не должно быть пустым и должно содержать только символы a-z, 0-9, ., _, =, -, и /. - + Password Пароль @@ -1349,37 +1395,22 @@ Example: https://server.my:8787 РЕГИСТРАЦИЯ - - No supported registration flows! - Нет поддреживаемых регистрационных потоков - - - - Registration token - - - - - Please enter a valid registration token. - - - - + Autodiscovery failed. Received malformed response. Автообноружение не удалось. Получен поврежденный ответ. - + Autodiscovery failed. Unknown error when requesting .well-known. Автообноружение не удалось. Не известаня ошибка во время запроса .well-known. - + The required endpoints were not found. Possibly not a Matrix server. Необходимые конечные точки не найдены. Возможно, это не сервер Matrix. - + Received malformed response. Make sure the homeserver domain is valid. Получен неверный ответ. Убедитесь, что домен homeserver действителен. @@ -1389,7 +1420,7 @@ Example: https://server.my:8787 Произошла неизвестная ошибка. Убедитесь, что домен homeserver действителен. - + Password is not long enough (min 8 chars) Слишком короткий пароль (минимум 8 символов) @@ -1420,20 +1451,25 @@ Example: https://server.my:8787 RoomDirectory - + Explore Public Rooms - + Search for public rooms + + + Choose custom homeserver + + RoomInfo - + no version stored нет сохраненной версии @@ -1450,16 +1486,6 @@ Example: https://server.my:8787 Enter the tag you want to use: - - - Leave Room - - - - - Are you sure you want to leave this room? - - Leave room @@ -1491,7 +1517,7 @@ Example: https://server.my:8787 - + Status Message @@ -1516,7 +1542,30 @@ Example: https://server.my:8787 Выйти - + + Encryption not set up + Cross-signing setup has not run yet. + + + + + Unverified login + The user just signed in with this device and hasn't verified their master key. + + + + + Please verify your other devices + There are unverified devices signed in to this account. + + + + + Close + Закрыть + + + Start a new chat Начать новый чат @@ -1544,12 +1593,12 @@ Example: https://server.my:8787 RoomMembers - + Members of %1 - + %n people in %1 Summary above list of members @@ -1587,12 +1636,12 @@ Example: https://server.my:8787 RoomSettings - + Room Settings Настройки комнаты - + %1 member(s) %1 участник(ов) @@ -1698,12 +1747,12 @@ Example: https://server.my:8787 Версия Комнаты - + Failed to enable encryption: %1 Не удалось включить шифрование: %1 - + Select an avatar Выберите аватар @@ -1723,8 +1772,8 @@ Example: https://server.my:8787 Ошибка во время прочтения файла: %1 - - + + Failed to upload image: %s Не удалось загрузить изображение: %s @@ -1732,21 +1781,49 @@ Example: https://server.my:8787 RoomlistModel - + Pending invite. - + Previewing this room - + No preview available + + Root + + + Please enter your login password to continue: + + + + + Please enter a valid email address to continue: + + + + + Please enter a valid phone number to continue: + + + + + Please enter the token, which has been sent to you: + + + + + Wait for the confirmation link to arrive, then continue. + + + ScreenShare @@ -1804,20 +1881,98 @@ Example: https://server.my:8787 SecretStorage - + Failed to connect to secret storage - Nheko could not connect to the secure storage to save encryption secrets to. This can have multiple reasons. Check if your D-Bus service is running and you have configured a service like KWallet, Gnome Secrets or the equivalent for your platform. If you are having trouble, feel free to open an issue here: https://github.com/Nheko-Reborn/nheko/issues + Nheko could not connect to the secure storage to save encryption secrets to. This can have multiple reasons. Check if your D-Bus service is running and you have configured a service like KWallet, Gnome Keyring, KeePassXC or the equivalent for your platform. If you are having trouble, feel free to open an issue here: https://github.com/Nheko-Reborn/nheko/issues + + + + + SelfVerificationCheck + + + This is your recovery key. You will need it to restore access to your encrypted messages and verification keys. Keep this safe. Don't share it with anyone and don't lose it! Do not pass go! Do not collect $200! + + + + + Encryption setup successfully + + + + + Failed to setup encryption: %1 + + + + + Setup Encryption + + + + + Hello and welcome to Matrix! +It seems like you are new. Before you can securely encrypt your messages, we need to setup a few small things. You can either press accept immediately or adjust a few basic options. We also try to explain a few of the basics. You can skip those parts, but they might prove to be helpful! + + + + + Activate Encryption + + + + + It seems like you have encryption already configured for this account. To be able to access your encrypted messages and make this device appear as trusted, you can either verify an existing device or (if you have one) enter your recovery passphrase. Please select one of the options below. +If you choose verify, you need to have the other device available. If you choose "enter passphrase", you will need your recovery key or passphrase. If you click cancel, you can choose to verify yourself at a later point. + + + + + verify + + + + + enter passphrase + + + + + SelfVerificationStatus + + + Failed to create keys for cross-signing! + + + + + Failed to create keys for online key backup! + + + + + Failed to create keys secure server side secret storage! + + + + + Encryption Setup + + + + + Encryption setup failed: %1 SingleImagePackModel - + Failed to update image pack: %1 @@ -1833,7 +1988,7 @@ Example: https://server.my:8787 - + Failed to upload image: %1 @@ -1890,18 +2045,18 @@ Example: https://server.my:8787 TimelineModel - + Message redaction failed: %1 Ошибка редактирования сообщения: %1 - + Failed to encrypt event, sending aborted! Не удалось зашифровать сообщение, отправка отменена! - + Save image Сохранить изображение @@ -1921,7 +2076,7 @@ Example: https://server.my:8787 Сохранить файл - + %1 and %2 are typing. Multiple users are typing. First argument is a comma separated list of potentially multiple users. Second argument is the last user of that list. (If only one user is typing, %1 is empty. You should still use it in your string though to silence Qt warnings.) @@ -1931,7 +2086,7 @@ Example: https://server.my:8787 - + %1 opened the room to the public. %1 сделал комнату публичной. @@ -1971,12 +2126,12 @@ Example: https://server.my:8787 %1 сделал историю сообщений видимо для участников с этого момента. - + %1 set the room history visible to members since they were invited. %1 сделал историю сообщений видимой для участников, с момента их приглашения. - + %1 set the room history visible to members since they joined the room. %1 сделал историю сообщений видимой для участников, с момента того, как они присоединились к комнате. @@ -1986,12 +2141,12 @@ Example: https://server.my:8787 %1 поменял разрешения для комнаты. - + %1 was invited. %1 был приглашен. - + %1 changed their avatar. %1 поменял свой аватар. @@ -2006,12 +2161,12 @@ Example: https://server.my:8787 %1 присоединился. - + %1 joined via authorisation from %2's server. - + %1 rejected their invite. %1 отклонил приглашение. @@ -2041,27 +2196,27 @@ Example: https://server.my:8787 %1 был заблокирован. - + Reason: %1 - + %1 redacted their knock. %1 отредактировал его "стук". - + You joined this room. Вы присоединились к этой комнате. - + %1 has changed their avatar and changed their display name to %2. - + %1 has changed their display name to %2. @@ -2098,7 +2253,12 @@ Example: https://server.my:8787 Комната не выбрана - + + No preview available + + + + %1 member(s) %1 участник(ов) @@ -2123,28 +2283,20 @@ Example: https://server.my:8787 Вернуться к списку комнат - - TimelineViewManager - - - No encrypted private chat found with this user. Create an encrypted private chat with this user and try again. - Не найдено личного чата с этим пользователем. Создайте зашифрованный личный чат с этим пользователем и попытайтесь еще раз. - - TopBar - + Back to room list Вернуться к списку комнат - + No room selected Комнаты не выбраны - + This room is not encrypted! @@ -2155,7 +2307,7 @@ Example: https://server.my:8787 - This rooms contain verified devices and devices which have never changed their master key. + This room contains verified devices and devices which have never changed their master key. @@ -2202,10 +2354,35 @@ Example: https://server.my:8787 Выйти + + UIA + + + No available registration flows! + + + + + + + Registration aborted + + + + + Please enter a valid registration token. + + + + + Invalid token + + + UserProfile - + Global User Profile Глобальный Пользовательский Профиль @@ -2215,7 +2392,7 @@ Example: https://server.my:8787 Поользовательский Профиль в Комнате - + Change avatar globally. @@ -2225,7 +2402,7 @@ Example: https://server.my:8787 - + Change display name globally. @@ -2235,7 +2412,7 @@ Example: https://server.my:8787 - + Room: %1 @@ -2245,18 +2422,18 @@ Example: https://server.my:8787 - + Open the global profile for this user. - - + + Verify Верифицировать - + Start a private chat. @@ -2271,12 +2448,42 @@ Example: https://server.my:8787 - + + Refresh device list. + + + + + Sign out this device. + + + + + Change device name. + + + + + Last seen %1 from %2 + + + + Unverify Отменить Верификацию - + + Sign out device %1 + + + + + You signed out this device. + + + + Select an avatar Выберите аватар @@ -2299,8 +2506,8 @@ Example: https://server.my:8787 UserSettings - - + + Default По умолчанию @@ -2308,7 +2515,7 @@ Example: https://server.my:8787 UserSettingsPage - + Minimize to tray Сворачивать в системную панель @@ -2318,22 +2525,22 @@ Example: https://server.my:8787 Запускать в системной панели - + Group's sidebar Боковая панель групп - + Circular Avatars Округлый Аватар - + profile: %1 профиль: %1 - + Default По умолчанию @@ -2358,7 +2565,7 @@ Example: https://server.my:8787 СКАЧАТЬ - + Keep the application running in the background after closing the client window. Держать приложение запущенным в фоне, после закрытия окна. @@ -2373,6 +2580,16 @@ Example: https://server.my:8787 OFF - square, ON - Circle. Поменять отображение пользовательского аватара в чатах. ВЫКЛ - квадратный, ВКЛ - округлый. + + + Use identicons + + + + + Display an identicon instead of a letter when a user has not set an avatar. + + Show a column containing groups and tags next to the room list. @@ -2401,7 +2618,7 @@ be blurred. - + Privacy screen timeout (in seconds [0 - 3600]) @@ -2458,7 +2675,7 @@ If this is on, rooms which have active notifications (the small circle with a nu Если это включено, комнаты в которых включены уведомления (маленькие кружки с числами) буду отсортированы на верху. Комнаты, которые вы заглушили, будут отсортированы по времени, пока вы не сделаете их важнее чем другие комнаты. - + Read receipts Просмотр получателей @@ -2470,7 +2687,7 @@ Status is displayed next to timestamps. Стату отображается за временем сообщения. - + Send messages as Markdown Посылать сообщение в формате Markdown @@ -2559,7 +2776,7 @@ This usually causes the application icon in the task bar to animate in some fash - + Enable online key backup @@ -2569,7 +2786,7 @@ This usually causes the application icon in the task bar to animate in some fash - + CACHED Закешировано @@ -2579,7 +2796,7 @@ This usually causes the application icon in the task bar to animate in some fash НЕ ЗАКЕШИРОВАНО - + Scale factor Масштаб @@ -2654,7 +2871,7 @@ This usually causes the application icon in the task bar to animate in some fash Отпечаток устройства - + Session Keys Ключи сеанса @@ -2674,17 +2891,17 @@ This usually causes the application icon in the task bar to animate in some fash ШИФРОВАНИЕ - + GENERAL ГЛАВНОЕ - + INTERFACE ИНТЕРФЕЙС - + Plays media like GIFs or WEBPs only when explicitly hovering over them. @@ -2724,7 +2941,7 @@ This usually causes the application icon in the task bar to animate in some fash - + Self signing key @@ -2754,14 +2971,14 @@ This usually causes the application icon in the task bar to animate in some fash Все файлы (*) - + Open Sessions File Открыть файл сеансов - + @@ -2769,20 +2986,20 @@ This usually causes the application icon in the task bar to animate in some fash Ошибка - - + + File Password Или введите пароль? Пароль файла - + Enter the passphrase to decrypt the file: Введите парольную фразу для расшифрования файла: - + The password cannot be empty Пароль не может быть пустым @@ -2797,6 +3014,14 @@ This usually causes the application icon in the task bar to animate in some fash Файл для сохранения экспортированных ключей сеанса + + VerificationManager + + + No encrypted private chat found with this user. Create an encrypted private chat with this user and try again. + Не найдено личного чата с этим пользователем. Создайте зашифрованный личный чат с этим пользователем и попытайтесь еще раз. + + Waiting @@ -2851,7 +3076,7 @@ This usually causes the application icon in the task bar to animate in some fash descriptiveTime - + Yesterday Вчера @@ -2922,37 +3147,6 @@ This usually causes the application icon in the task bar to animate in some fash Запустите резервный вариант, пройдите его шаги и подтвердите завершение. - - dialogs::JoinRoom - - - Join - Присоединиться - - - - Cancel - Отмена - - - - Room ID or alias - Идентификатор или псевдоним комнаты - - - - dialogs::LeaveRoom - - - Cancel - Отмена - - - - Are you sure you want to leave? - Вы действительно желаете выйти? - - dialogs::Logout @@ -3019,47 +3213,47 @@ Media size: %2 %1 отправил аудио запись - + You sent an image Вы отправили картинку - + %1 sent an image %1 отправил картинку - + You sent a file Вы отправили файл - + %1 sent a file %1 отправил файл - + You sent a video Вы отправили видео - + %1 sent a video %1 отправил видео - + You sent a sticker Вы отправили стикер - + %1 sent a sticker %1 отправил стикер - + You sent a notification Вы отправили оповещение @@ -3074,7 +3268,7 @@ Media size: %2 Вы: %1 - + %1: %2 %1: %2 @@ -3094,27 +3288,27 @@ Media size: %2 Вы начали звонок - + %1 placed a call %1 начал звонок - + You answered a call Вы ответили на звонок - + %1 answered a call %1 ответил на звонок - + You ended a call Вы закончили разговор - + %1 ended a call %1 Закончил разговор @@ -3122,7 +3316,7 @@ Media size: %2 utils - + Unknown Message Type Неизвестный Тип Сообщения diff --git a/resources/langs/nheko_si.ts b/resources/langs/nheko_si.ts index 645bfede..579ca1cf 100644 --- a/resources/langs/nheko_si.ts +++ b/resources/langs/nheko_si.ts @@ -4,7 +4,7 @@ ActiveCallBar - + Calling... @@ -56,7 +56,7 @@ CallInvite - + Video Call @@ -74,7 +74,7 @@ CallInviteBar - + Video Call @@ -117,7 +117,7 @@ CallManager - + Entire screen @@ -125,23 +125,23 @@ ChatPage - + Failed to invite user: %1 - + Invited user: %1 - + Migrating the cache to the current version failed. This can have different reasons. Please open an issue and try to use an older version in the mean time. Alternatively you can try deleting the cache manually. - + Confirm join @@ -151,23 +151,23 @@ - + Room %1 created. - - + + Confirm invite - + Do you really want to invite %1 (%2)? - + Failed to invite %1 to %2: %3 @@ -182,7 +182,7 @@ - + Kicked user: %1 @@ -197,7 +197,7 @@ - + Failed to ban %1 in %2: %3 @@ -217,7 +217,7 @@ - + Failed to unban %1 in %2: %3 @@ -227,12 +227,12 @@ - + Do you really want to start a private chat with %1? - + Cache migration failed! @@ -259,23 +259,23 @@ - + Failed to setup encryption keys. Server response: %1 %2. Please try again later. - - + + Please try to login again: %1 - + Failed to join room: %1 - + You joined the room @@ -285,17 +285,17 @@ - + Room creation failed: %1 - + Failed to leave room: %1 - + Failed to kick %1 from %2: %3 @@ -354,7 +354,7 @@ CrossSigningSecrets - + Decrypt secrets @@ -364,12 +364,12 @@ - + Enter your recovery key or passphrase called %1 to decrypt your secrets: - + Decryption failed @@ -581,17 +581,26 @@ - Device verification timed out. - + Other party canceled the verification. - + + Verification messages received out of order! + + + + + Unknown verification error. + + + + Close @@ -612,7 +621,7 @@ - + Add images @@ -622,7 +631,7 @@ - + State key @@ -638,13 +647,13 @@ - + Use as Emoji - - + + Use as Sticker @@ -697,7 +706,7 @@ - + Private pack @@ -712,7 +721,7 @@ - + Enable globally @@ -735,7 +744,7 @@ InputBar - + Select a file @@ -745,7 +754,7 @@ - + Failed to upload media. Please try again. @@ -753,7 +762,7 @@ InviteDialog - + Invite users to %1 @@ -784,6 +793,32 @@ + + JoinRoomDialog + + + Join room + + + + + Room ID or alias + + + + + LeaveRoomDialog + + + Leave room + + + + + Are you sure you want to leave? + + + LoginPage @@ -846,25 +881,25 @@ Example: https://server.my:8787 - + - + You have entered an invalid Matrix ID e.g @joe:matrix.org - + Autodiscovery failed. Received malformed response. - + Autodiscovery failed. Unknown error when requesting .well-known. - + The required endpoints were not found. Possibly not a Matrix server. @@ -874,26 +909,44 @@ Example: https://server.my:8787 - + An unknown error occured. Make sure the homeserver domain is valid. - + SSO LOGIN - + Empty password - + SSO login failed + + LogoutDialog + + + Log out + + + + + A call is in progress. Log out? + + + + + Are you sure you want to log out? + + + MessageDelegate @@ -1019,7 +1072,7 @@ Example: https://server.my:8787 MessageView - + Edit @@ -1039,7 +1092,7 @@ Example: https://server.my:8787 - + &Copy @@ -1129,7 +1182,12 @@ Example: https://server.my:8787 - + + To allow other users to see, which of your devices actually belong to you, you can verify them. This also allows key backup to work automatically. Verify an unverified device now? (Please make sure you have one of those devices available.) + + + + To allow other users to see, which of your devices actually belong to you, you can verify them. This also allows key backup to work automatically. Verify %1 now? @@ -1178,7 +1236,7 @@ Example: https://server.my:8787 NotificationWarning - You will be pinging the whole room + You are about to notify the whole room @@ -1186,29 +1244,17 @@ Example: https://server.my:8787 NotificationsManager - - + + %1 sent an encrypted message - - * %1 %2 - Format an emote message in a notification, %1 is the sender, %2 the message - - - - + %1 replied: %2 Format a reply in a notification. %1 is the sender, %2 the message - - - %1: %2 - Format a normal message in a notification. %1 is the sender, %2 the message - - @@ -1239,7 +1285,7 @@ Example: https://server.my:8787 - + Voice @@ -1270,7 +1316,7 @@ Example: https://server.my:8787 QCoreApplication - + Create a unique profile, which allows you to log into several accounts at the same time and start multiple instances of nheko. @@ -1288,7 +1334,7 @@ Example: https://server.my:8787 ReadReceipts - + Read receipts @@ -1296,7 +1342,7 @@ Example: https://server.my:8787 ReadReceiptsModel - + Yesterday, %1 @@ -1304,18 +1350,18 @@ Example: https://server.my:8787 RegisterPage - + Username - + The username must not be empty, and must contain only the characters a-z, 0-9, ., _, =, -, and /. - + Password @@ -1345,37 +1391,22 @@ Example: https://server.my:8787 - - No supported registration flows! - - - - - Registration token - - - - - Please enter a valid registration token. - - - - + Autodiscovery failed. Received malformed response. - + Autodiscovery failed. Unknown error when requesting .well-known. - + The required endpoints were not found. Possibly not a Matrix server. - + Received malformed response. Make sure the homeserver domain is valid. @@ -1385,7 +1416,7 @@ Example: https://server.my:8787 - + Password is not long enough (min 8 chars) @@ -1416,20 +1447,25 @@ Example: https://server.my:8787 RoomDirectory - + Explore Public Rooms - + Search for public rooms + + + Choose custom homeserver + + RoomInfo - + no version stored @@ -1446,16 +1482,6 @@ Example: https://server.my:8787 Enter the tag you want to use: - - - Leave Room - - - - - Are you sure you want to leave this room? - - Leave room @@ -1487,7 +1513,7 @@ Example: https://server.my:8787 - + Status Message @@ -1512,7 +1538,30 @@ Example: https://server.my:8787 - + + Encryption not set up + Cross-signing setup has not run yet. + + + + + Unverified login + The user just signed in with this device and hasn't verified their master key. + + + + + Please verify your other devices + There are unverified devices signed in to this account. + + + + + Close + + + + Start a new chat @@ -1540,12 +1589,12 @@ Example: https://server.my:8787 RoomMembers - + Members of %1 - + %n people in %1 Summary above list of members @@ -1582,12 +1631,12 @@ Example: https://server.my:8787 RoomSettings - + Room Settings - + %1 member(s) @@ -1693,12 +1742,12 @@ Example: https://server.my:8787 - + Failed to enable encryption: %1 - + Select an avatar @@ -1718,8 +1767,8 @@ Example: https://server.my:8787 - - + + Failed to upload image: %s @@ -1727,21 +1776,49 @@ Example: https://server.my:8787 RoomlistModel - + Pending invite. - + Previewing this room - + No preview available + + Root + + + Please enter your login password to continue: + + + + + Please enter a valid email address to continue: + + + + + Please enter a valid phone number to continue: + + + + + Please enter the token, which has been sent to you: + + + + + Wait for the confirmation link to arrive, then continue. + + + ScreenShare @@ -1799,20 +1876,98 @@ Example: https://server.my:8787 SecretStorage - + Failed to connect to secret storage - Nheko could not connect to the secure storage to save encryption secrets to. This can have multiple reasons. Check if your D-Bus service is running and you have configured a service like KWallet, Gnome Secrets or the equivalent for your platform. If you are having trouble, feel free to open an issue here: https://github.com/Nheko-Reborn/nheko/issues + Nheko could not connect to the secure storage to save encryption secrets to. This can have multiple reasons. Check if your D-Bus service is running and you have configured a service like KWallet, Gnome Keyring, KeePassXC or the equivalent for your platform. If you are having trouble, feel free to open an issue here: https://github.com/Nheko-Reborn/nheko/issues + + + + + SelfVerificationCheck + + + This is your recovery key. You will need it to restore access to your encrypted messages and verification keys. Keep this safe. Don't share it with anyone and don't lose it! Do not pass go! Do not collect $200! + + + + + Encryption setup successfully + + + + + Failed to setup encryption: %1 + + + + + Setup Encryption + + + + + Hello and welcome to Matrix! +It seems like you are new. Before you can securely encrypt your messages, we need to setup a few small things. You can either press accept immediately or adjust a few basic options. We also try to explain a few of the basics. You can skip those parts, but they might prove to be helpful! + + + + + Activate Encryption + + + + + It seems like you have encryption already configured for this account. To be able to access your encrypted messages and make this device appear as trusted, you can either verify an existing device or (if you have one) enter your recovery passphrase. Please select one of the options below. +If you choose verify, you need to have the other device available. If you choose "enter passphrase", you will need your recovery key or passphrase. If you click cancel, you can choose to verify yourself at a later point. + + + + + verify + + + + + enter passphrase + + + + + SelfVerificationStatus + + + Failed to create keys for cross-signing! + + + + + Failed to create keys for online key backup! + + + + + Failed to create keys secure server side secret storage! + + + + + Encryption Setup + + + + + Encryption setup failed: %1 SingleImagePackModel - + Failed to update image pack: %1 @@ -1828,7 +1983,7 @@ Example: https://server.my:8787 - + Failed to upload image: %1 @@ -1885,18 +2040,18 @@ Example: https://server.my:8787 TimelineModel - + Message redaction failed: %1 - + Failed to encrypt event, sending aborted! - + Save image @@ -1916,7 +2071,7 @@ Example: https://server.my:8787 - + %1 and %2 are typing. Multiple users are typing. First argument is a comma separated list of potentially multiple users. Second argument is the last user of that list. (If only one user is typing, %1 is empty. You should still use it in your string though to silence Qt warnings.) @@ -1925,7 +2080,7 @@ Example: https://server.my:8787 - + %1 opened the room to the public. @@ -1965,12 +2120,12 @@ Example: https://server.my:8787 - + %1 set the room history visible to members since they were invited. - + %1 set the room history visible to members since they joined the room. @@ -1980,12 +2135,12 @@ Example: https://server.my:8787 - + %1 was invited. - + %1 changed their avatar. @@ -2000,12 +2155,12 @@ Example: https://server.my:8787 - + %1 joined via authorisation from %2's server. - + %1 rejected their invite. @@ -2035,27 +2190,27 @@ Example: https://server.my:8787 - + Reason: %1 - + %1 redacted their knock. - + You joined this room. - + %1 has changed their avatar and changed their display name to %2. - + %1 has changed their display name to %2. @@ -2092,7 +2247,12 @@ Example: https://server.my:8787 - + + No preview available + + + + %1 member(s) @@ -2117,28 +2277,20 @@ Example: https://server.my:8787 - - TimelineViewManager - - - No encrypted private chat found with this user. Create an encrypted private chat with this user and try again. - - - TopBar - + Back to room list - + No room selected - + This room is not encrypted! @@ -2149,7 +2301,7 @@ Example: https://server.my:8787 - This rooms contain verified devices and devices which have never changed their master key. + This room contains verified devices and devices which have never changed their master key. @@ -2196,10 +2348,35 @@ Example: https://server.my:8787 + + UIA + + + No available registration flows! + + + + + + + Registration aborted + + + + + Please enter a valid registration token. + + + + + Invalid token + + + UserProfile - + Global User Profile @@ -2209,7 +2386,7 @@ Example: https://server.my:8787 - + Change avatar globally. @@ -2219,7 +2396,7 @@ Example: https://server.my:8787 - + Change display name globally. @@ -2229,7 +2406,7 @@ Example: https://server.my:8787 - + Room: %1 @@ -2239,18 +2416,18 @@ Example: https://server.my:8787 - + Open the global profile for this user. - - + + Verify - + Start a private chat. @@ -2265,12 +2442,42 @@ Example: https://server.my:8787 - + + Refresh device list. + + + + + Sign out this device. + + + + + Change device name. + + + + + Last seen %1 from %2 + + + + Unverify - + + Sign out device %1 + + + + + You signed out this device. + + + + Select an avatar @@ -2293,8 +2500,8 @@ Example: https://server.my:8787 UserSettings - - + + Default @@ -2302,7 +2509,7 @@ Example: https://server.my:8787 UserSettingsPage - + Minimize to tray @@ -2312,22 +2519,22 @@ Example: https://server.my:8787 - + Group's sidebar - + Circular Avatars - + profile: %1 - + Default @@ -2352,7 +2559,7 @@ Example: https://server.my:8787 - + Keep the application running in the background after closing the client window. @@ -2367,6 +2574,16 @@ Example: https://server.my:8787 OFF - square, ON - Circle. + + + Use identicons + + + + + Display an identicon instead of a letter when a user has not set an avatar. + + Show a column containing groups and tags next to the room list. @@ -2395,7 +2612,7 @@ be blurred. - + Privacy screen timeout (in seconds [0 - 3600]) @@ -2450,7 +2667,7 @@ If this is on, rooms which have active notifications (the small circle with a nu - + Read receipts @@ -2461,7 +2678,7 @@ Status is displayed next to timestamps. - + Send messages as Markdown @@ -2548,7 +2765,7 @@ This usually causes the application icon in the task bar to animate in some fash - + Enable online key backup @@ -2558,7 +2775,7 @@ This usually causes the application icon in the task bar to animate in some fash - + CACHED @@ -2568,7 +2785,7 @@ This usually causes the application icon in the task bar to animate in some fash - + Scale factor @@ -2643,7 +2860,7 @@ This usually causes the application icon in the task bar to animate in some fash - + Session Keys @@ -2663,17 +2880,17 @@ This usually causes the application icon in the task bar to animate in some fash - + GENERAL - + INTERFACE - + Plays media like GIFs or WEBPs only when explicitly hovering over them. @@ -2713,7 +2930,7 @@ This usually causes the application icon in the task bar to animate in some fash - + Self signing key @@ -2743,14 +2960,14 @@ This usually causes the application icon in the task bar to animate in some fash - + Open Sessions File - + @@ -2758,19 +2975,19 @@ This usually causes the application icon in the task bar to animate in some fash - - + + File Password - + Enter the passphrase to decrypt the file: - + The password cannot be empty @@ -2785,6 +3002,14 @@ This usually causes the application icon in the task bar to animate in some fash + + VerificationManager + + + No encrypted private chat found with this user. Create an encrypted private chat with this user and try again. + + + Waiting @@ -2839,7 +3064,7 @@ This usually causes the application icon in the task bar to animate in some fash descriptiveTime - + Yesterday @@ -2910,37 +3135,6 @@ This usually causes the application icon in the task bar to animate in some fash - - dialogs::JoinRoom - - - Join - - - - - Cancel - - - - - Room ID or alias - - - - - dialogs::LeaveRoom - - - Cancel - - - - - Are you sure you want to leave? - - - dialogs::Logout @@ -3005,47 +3199,47 @@ Media size: %2 - + You sent an image - + %1 sent an image - + You sent a file - + %1 sent a file - + You sent a video - + %1 sent a video - + You sent a sticker - + %1 sent a sticker - + You sent a notification @@ -3060,7 +3254,7 @@ Media size: %2 - + %1: %2 @@ -3080,27 +3274,27 @@ Media size: %2 - + %1 placed a call - + You answered a call - + %1 answered a call - + You ended a call - + %1 ended a call @@ -3108,7 +3302,7 @@ Media size: %2 utils - + Unknown Message Type diff --git a/resources/langs/nheko_sv.ts b/resources/langs/nheko_sv.ts index 80863b80..e19136db 100644 --- a/resources/langs/nheko_sv.ts +++ b/resources/langs/nheko_sv.ts @@ -4,7 +4,7 @@ ActiveCallBar - + Calling... Ringer upp... @@ -56,7 +56,7 @@ CallInvite - + Video Call Videosamtal @@ -74,7 +74,7 @@ CallInviteBar - + Video Call Videosamtal @@ -117,7 +117,7 @@ CallManager - + Entire screen @@ -125,23 +125,23 @@ ChatPage - + Failed to invite user: %1 Kunde inte bjuda in användare: %1 - + Invited user: %1 Bjöd in användare: %1 - + Migrating the cache to the current version failed. This can have different reasons. Please open an issue and try to use an older version in the mean time. Alternatively you can try deleting the cache manually. Kunde inte migrera cachen till den nuvarande versionen. Detta kan bero på flera anledningar, vänligen rapportera problemet och prova en äldre version under tiden. Du kan också försöka att manuellt radera cachen. - + Confirm join @@ -151,23 +151,23 @@ - + Room %1 created. Rum %1 skapat. - - + + Confirm invite Bekräfta inbjudan - + Do you really want to invite %1 (%2)? Är du säker på att du vill bjuda in %1 (%2)? - + Failed to invite %1 to %2: %3 Kunde inte bjuda in %1 till %2: %3 @@ -182,7 +182,7 @@ Är du säker på att du vill sparka ut %1 (%2)? - + Kicked user: %1 Sparkade ut användare: %1 @@ -197,7 +197,7 @@ Är du säker på att du vill bannlysa %1 (%2)? - + Failed to ban %1 in %2: %3 Kunde inte bannlysa %1 i %2: %3 @@ -217,7 +217,7 @@ Är du säker på att du vill häva bannlysningen av %1 (%2)? - + Failed to unban %1 in %2: %3 Kunde inte häva bannlysningen av %1 i %2: %3 @@ -227,12 +227,12 @@ Hävde bannlysningen av användare: %1 - + Do you really want to start a private chat with %1? - + Cache migration failed! Cache-migration misslyckades! @@ -259,23 +259,23 @@ Kunde inte återställa sparad data. Vänligen logga in på nytt. - + Failed to setup encryption keys. Server response: %1 %2. Please try again later. Kunde inte sätta upp krypteringsnycklar. Svar från servern: %1 %2. Vänligen försök igen senare. - - + + Please try to login again: %1 Vänligen försök logga in på nytt: %1 - + Failed to join room: %1 Kunde inte gå med i rum: %1 - + You joined the room Du gick med i rummet @@ -285,17 +285,17 @@ Kunde inte ta bort inbjudan: %1 - + Room creation failed: %1 Kunde inte skapa rum: %1 - + Failed to leave room: %1 Kunde inte lämna rum: %1 - + Failed to kick %1 from %2: %3 @@ -354,7 +354,7 @@ CrossSigningSecrets - + Decrypt secrets Dekryptera hemliga nycklar @@ -364,12 +364,12 @@ Ange din återställningsnyckel eller lösenfras för att dekryptera dina hemliga nycklar: - + Enter your recovery key or passphrase called %1 to decrypt your secrets: Ange din återställningsnyckel eller lösenfras vid namn %1 för att dekryptera dina hemliga nycklar: - + Decryption failed Dekryptering misslyckades @@ -581,17 +581,26 @@ - Device verification timed out. Enhetsverifikation tog för lång tid. - + Other party canceled the verification. Motparten avbröt verifikationen. - + + Verification messages received out of order! + + + + + Unknown verification error. + + + + Close Stäng @@ -612,7 +621,7 @@ - + Add images @@ -622,7 +631,7 @@ - + State key @@ -638,13 +647,13 @@ - + Use as Emoji - - + + Use as Sticker @@ -697,7 +706,7 @@ - + Private pack @@ -712,7 +721,7 @@ - + Enable globally @@ -735,7 +744,7 @@ InputBar - + Select a file Välj en fil @@ -745,7 +754,7 @@ Alla Filer (*) - + Failed to upload media. Please try again. Kunde inte ladda upp media. Vänligen försök igen. @@ -753,7 +762,7 @@ InviteDialog - + Invite users to %1 @@ -784,6 +793,32 @@ Avbryt + + JoinRoomDialog + + + Join room + + + + + Room ID or alias + Rum-ID eller alias + + + + LeaveRoomDialog + + + Leave room + Lämna rum + + + + Are you sure you want to leave? + Är du säker på att du vill lämna? + + LoginPage @@ -850,25 +885,25 @@ Exempel: https://server.my:8787 INLOGGNING - + - + You have entered an invalid Matrix ID e.g @joe:matrix.org - + Autodiscovery failed. Received malformed response. Autouppslag misslyckades. Mottog felkonstruerat svar. - + Autodiscovery failed. Unknown error when requesting .well-known. Autouppslag misslyckades. Okänt fel uppstod vid begäran av .well-known. - + The required endpoints were not found. Possibly not a Matrix server. Kunde inte hitta de nödvändiga ändpunkterna. Möjligtvis inte en Matrix-server. @@ -878,26 +913,44 @@ Exempel: https://server.my:8787 Mottog felkonstruerat svar. Se till att hemserver-domänen är giltig. - + An unknown error occured. Make sure the homeserver domain is valid. Ett okänt fel uppstod. Se till att hemserver-domänen är giltig. - + SSO LOGIN SSO INLOGGNING - + Empty password Tomt lösenord - + SSO login failed SSO-inloggning misslyckades + + LogoutDialog + + + Log out + + + + + A call is in progress. Log out? + + + + + Are you sure you want to log out? + + + MessageDelegate @@ -1023,7 +1076,7 @@ Exempel: https://server.my:8787 MessageView - + Edit @@ -1043,7 +1096,7 @@ Exempel: https://server.my:8787 Alternativ - + &Copy @@ -1133,7 +1186,12 @@ Exempel: https://server.my:8787 Mottog Verifikationsförfrågan - + + To allow other users to see, which of your devices actually belong to you, you can verify them. This also allows key backup to work automatically. Verify an unverified device now? (Please make sure you have one of those devices available.) + + + + To allow other users to see, which of your devices actually belong to you, you can verify them. This also allows key backup to work automatically. Verify %1 now? Du kan verifiera dina enheter för att låta andra användare se vilka av dem som faktiskt tillhör dig. Detta gör även att nyckelbackup fungerar automatiskt. Verifiera %1 nu? @@ -1182,7 +1240,7 @@ Exempel: https://server.my:8787 NotificationWarning - You will be pinging the whole room + You are about to notify the whole room @@ -1190,29 +1248,17 @@ Exempel: https://server.my:8787 NotificationsManager - - + + %1 sent an encrypted message %1 skickade ett krypterat meddelande - - * %1 %2 - Format an emote message in a notification, %1 is the sender, %2 the message - - - - + %1 replied: %2 Format a reply in a notification. %1 is the sender, %2 the message - - - %1: %2 - Format a normal message in a notification. %1 is the sender, %2 the message - %1: %2 - @@ -1243,7 +1289,7 @@ Exempel: https://server.my:8787 Ingen mikrofon kunde hittas. - + Voice Röst @@ -1274,7 +1320,7 @@ Exempel: https://server.my:8787 QCoreApplication - + Create a unique profile, which allows you to log into several accounts at the same time and start multiple instances of nheko. Skapa en unik profil, vilket tillåter dig att logga in på flera konton samtidigt och starta flera instanser av Nheko. @@ -1292,7 +1338,7 @@ Exempel: https://server.my:8787 ReadReceipts - + Read receipts Läskvitton @@ -1300,7 +1346,7 @@ Exempel: https://server.my:8787 ReadReceiptsModel - + Yesterday, %1 @@ -1308,18 +1354,18 @@ Exempel: https://server.my:8787 RegisterPage - + Username Användarnamn - + The username must not be empty, and must contain only the characters a-z, 0-9, ., _, =, -, and /. Användarnamnet kan inte vara tomt, och måste enbart innehålla tecknen a-z, 0-9, ., _, =, -, och /. - + Password Lösenord @@ -1349,37 +1395,22 @@ Exempel: https://server.my:8787 REGISTRERA - - No supported registration flows! - Inga stödda registreringsflöden! - - - - Registration token - - - - - Please enter a valid registration token. - - - - + Autodiscovery failed. Received malformed response. Autouppslag misslyckades. Mottog felkonstruerat svar. - + Autodiscovery failed. Unknown error when requesting .well-known. Autouppslag misslyckades. Okänt fel uppstod vid begäran av .well-known. - + The required endpoints were not found. Possibly not a Matrix server. Kunde inte hitta de nödvändiga ändpunkterna. Möjligtvis inte en Matrix-server. - + Received malformed response. Make sure the homeserver domain is valid. Mottog felkonstruerat svar. Se till att hemserver-domänen är giltig. @@ -1389,7 +1420,7 @@ Exempel: https://server.my:8787 Ett okänt fel uppstod. Se till att hemserver-domänen är giltig. - + Password is not long enough (min 8 chars) Lösenordet är inte långt nog (minst 8 tecken) @@ -1420,20 +1451,25 @@ Exempel: https://server.my:8787 RoomDirectory - + Explore Public Rooms - + Search for public rooms + + + Choose custom homeserver + + RoomInfo - + no version stored ingen version lagrad @@ -1450,16 +1486,6 @@ Exempel: https://server.my:8787 Enter the tag you want to use: - - - Leave Room - - - - - Are you sure you want to leave this room? - - Leave room @@ -1491,7 +1517,7 @@ Exempel: https://server.my:8787 - + Status Message @@ -1516,7 +1542,30 @@ Exempel: https://server.my:8787 Logga ut - + + Encryption not set up + Cross-signing setup has not run yet. + + + + + Unverified login + The user just signed in with this device and hasn't verified their master key. + + + + + Please verify your other devices + There are unverified devices signed in to this account. + + + + + Close + Stäng + + + Start a new chat Starta en ny chatt @@ -1544,12 +1593,12 @@ Exempel: https://server.my:8787 RoomMembers - + Members of %1 - + %n people in %1 Summary above list of members @@ -1586,12 +1635,12 @@ Exempel: https://server.my:8787 RoomSettings - + Room Settings - + %1 member(s) @@ -1697,12 +1746,12 @@ Exempel: https://server.my:8787 - + Failed to enable encryption: %1 Kunde inte aktivera kryptering: %1 - + Select an avatar Välj en avatar @@ -1722,8 +1771,8 @@ Exempel: https://server.my:8787 Kunde inte läsa filen: %1 - - + + Failed to upload image: %s Kunde inte ladda upp bilden: %s @@ -1731,21 +1780,49 @@ Exempel: https://server.my:8787 RoomlistModel - + Pending invite. - + Previewing this room - + No preview available + + Root + + + Please enter your login password to continue: + + + + + Please enter a valid email address to continue: + + + + + Please enter a valid phone number to continue: + + + + + Please enter the token, which has been sent to you: + + + + + Wait for the confirmation link to arrive, then continue. + + + ScreenShare @@ -1803,20 +1880,98 @@ Exempel: https://server.my:8787 SecretStorage - + Failed to connect to secret storage - Nheko could not connect to the secure storage to save encryption secrets to. This can have multiple reasons. Check if your D-Bus service is running and you have configured a service like KWallet, Gnome Secrets or the equivalent for your platform. If you are having trouble, feel free to open an issue here: https://github.com/Nheko-Reborn/nheko/issues + Nheko could not connect to the secure storage to save encryption secrets to. This can have multiple reasons. Check if your D-Bus service is running and you have configured a service like KWallet, Gnome Keyring, KeePassXC or the equivalent for your platform. If you are having trouble, feel free to open an issue here: https://github.com/Nheko-Reborn/nheko/issues + + + + + SelfVerificationCheck + + + This is your recovery key. You will need it to restore access to your encrypted messages and verification keys. Keep this safe. Don't share it with anyone and don't lose it! Do not pass go! Do not collect $200! + + + + + Encryption setup successfully + + + + + Failed to setup encryption: %1 + + + + + Setup Encryption + + + + + Hello and welcome to Matrix! +It seems like you are new. Before you can securely encrypt your messages, we need to setup a few small things. You can either press accept immediately or adjust a few basic options. We also try to explain a few of the basics. You can skip those parts, but they might prove to be helpful! + + + + + Activate Encryption + + + + + It seems like you have encryption already configured for this account. To be able to access your encrypted messages and make this device appear as trusted, you can either verify an existing device or (if you have one) enter your recovery passphrase. Please select one of the options below. +If you choose verify, you need to have the other device available. If you choose "enter passphrase", you will need your recovery key or passphrase. If you click cancel, you can choose to verify yourself at a later point. + + + + + verify + + + + + enter passphrase + + + + + SelfVerificationStatus + + + Failed to create keys for cross-signing! + + + + + Failed to create keys for online key backup! + + + + + Failed to create keys secure server side secret storage! + + + + + Encryption Setup + + + + + Encryption setup failed: %1 SingleImagePackModel - + Failed to update image pack: %1 @@ -1832,7 +1987,7 @@ Exempel: https://server.my:8787 - + Failed to upload image: %1 @@ -1889,18 +2044,18 @@ Exempel: https://server.my:8787 TimelineModel - + Message redaction failed: %1 Kunde inte maskera meddelande: %1 - + Failed to encrypt event, sending aborted! Kunde inte kryptera event, sändning avbruten! - + Save image Spara bild @@ -1920,7 +2075,7 @@ Exempel: https://server.my:8787 Spara fil - + %1 and %2 are typing. Multiple users are typing. First argument is a comma separated list of potentially multiple users. Second argument is the last user of that list. (If only one user is typing, %1 is empty. You should still use it in your string though to silence Qt warnings.) @@ -1929,7 +2084,7 @@ Exempel: https://server.my:8787 - + %1 opened the room to the public. %1 öppnade rummet till allmänheten. @@ -1969,12 +2124,12 @@ Exempel: https://server.my:8787 %1 gjorde rummets historik läslig för medlemmar från och med nu. - + %1 set the room history visible to members since they were invited. %1 gjorde rummets historik läslig för medlemmar sedan de blev inbjudna. - + %1 set the room history visible to members since they joined the room. %1 gjorde rummets historik läslig för medlemmar sedan de gick med i rummet. @@ -1984,12 +2139,12 @@ Exempel: https://server.my:8787 %1 har ändrat rummets behörigheter. - + %1 was invited. %1 blev inbjuden. - + %1 changed their avatar. %1 ändrade sin avatar. @@ -2004,12 +2159,12 @@ Exempel: https://server.my:8787 %1 gick med. - + %1 joined via authorisation from %2's server. - + %1 rejected their invite. %1 avvisade sin inbjudan. @@ -2039,27 +2194,27 @@ Exempel: https://server.my:8787 %1 blev bannlyst. - + Reason: %1 - + %1 redacted their knock. %1 maskerade sin knackning. - + You joined this room. Du gick med i detta rum. - + %1 has changed their avatar and changed their display name to %2. - + %1 has changed their display name to %2. @@ -2096,7 +2251,12 @@ Exempel: https://server.my:8787 Inget rum öppet - + + No preview available + + + + %1 member(s) @@ -2121,28 +2281,20 @@ Exempel: https://server.my:8787 Tillbaka till rumlista - - TimelineViewManager - - - No encrypted private chat found with this user. Create an encrypted private chat with this user and try again. - Ingen krypterad privat chatt med denna användare kunde hittas. Skapa en krypterad privat chatt med användaren och försök igen. - - TopBar - + Back to room list Tillbaka till rumlista - + No room selected Inget rum markerat - + This room is not encrypted! @@ -2153,7 +2305,7 @@ Exempel: https://server.my:8787 - This rooms contain verified devices and devices which have never changed their master key. + This room contains verified devices and devices which have never changed their master key. @@ -2200,10 +2352,35 @@ Exempel: https://server.my:8787 Avsluta + + UIA + + + No available registration flows! + + + + + + + Registration aborted + + + + + Please enter a valid registration token. + + + + + Invalid token + + + UserProfile - + Global User Profile @@ -2213,7 +2390,7 @@ Exempel: https://server.my:8787 - + Change avatar globally. @@ -2223,7 +2400,7 @@ Exempel: https://server.my:8787 - + Change display name globally. @@ -2233,7 +2410,7 @@ Exempel: https://server.my:8787 - + Room: %1 @@ -2243,18 +2420,18 @@ Exempel: https://server.my:8787 - + Open the global profile for this user. - - + + Verify Bekräfta - + Start a private chat. @@ -2269,12 +2446,42 @@ Exempel: https://server.my:8787 - + + Refresh device list. + + + + + Sign out this device. + + + + + Change device name. + + + + + Last seen %1 from %2 + + + + Unverify - + + Sign out device %1 + + + + + You signed out this device. + + + + Select an avatar Välj en avatar @@ -2297,8 +2504,8 @@ Exempel: https://server.my:8787 UserSettings - - + + Default @@ -2306,7 +2513,7 @@ Exempel: https://server.my:8787 UserSettingsPage - + Minimize to tray Minimera till systemtråg @@ -2316,22 +2523,22 @@ Exempel: https://server.my:8787 Starta i systemtråg - + Group's sidebar Gruppens sidofält - + Circular Avatars Cirkulära avatarer - + profile: %1 profil: %1 - + Default @@ -2356,7 +2563,7 @@ Exempel: https://server.my:8787 LADDA NED - + Keep the application running in the background after closing the client window. Håll applikationen igång i bakgrunden efter att ha stängt klienten. @@ -2372,6 +2579,16 @@ OFF - square, ON - Circle. Ändra utseendet av användaravatarer i chattar. AV - Kvadrat, PÅ - Cirkel. + + + Use identicons + + + + + Display an identicon instead of a letter when a user has not set an avatar. + + Show a column containing groups and tags next to the room list. @@ -2401,7 +2618,7 @@ be blurred. - + Privacy screen timeout (in seconds [0 - 3600]) @@ -2459,7 +2676,7 @@ Om denna inställning är av kommer listan över rum endast sorteras efter när Om denna inställning är på kommer rum med aktiva notifikationer (den lilla cirkeln med ett nummer i) sorteras högst upp. Rum som du stängt av notifikationer för kommer fortfarande sorteras efter när det sista meddelandet skickades, eftersom du inte verkar tycka att de är lika viktiga som andra rum. - + Read receipts Läskvitton @@ -2471,7 +2688,7 @@ Status is displayed next to timestamps. Status visas bredvid tidsstämpel. - + Send messages as Markdown Skicka meddelanden som Markdown @@ -2560,7 +2777,7 @@ Detta gör vanligtvis att ikonen i aktivitetsfältet animeras på något sätt.< - + Enable online key backup @@ -2570,7 +2787,7 @@ Detta gör vanligtvis att ikonen i aktivitetsfältet animeras på något sätt.< - + CACHED SPARAD @@ -2580,7 +2797,7 @@ Detta gör vanligtvis att ikonen i aktivitetsfältet animeras på något sätt.< EJ SPARAD - + Scale factor Storleksfaktor @@ -2655,7 +2872,7 @@ Detta gör vanligtvis att ikonen i aktivitetsfältet animeras på något sätt.< Enhetsfingeravtryck - + Session Keys Sessionsnycklar @@ -2675,17 +2892,17 @@ Detta gör vanligtvis att ikonen i aktivitetsfältet animeras på något sätt.< KRYPTERING - + GENERAL ALLMÄNT - + INTERFACE GRÄNSSNITT - + Plays media like GIFs or WEBPs only when explicitly hovering over them. @@ -2725,7 +2942,7 @@ Detta gör vanligtvis att ikonen i aktivitetsfältet animeras på något sätt.< Nyckeln för att verifiera andra användare. Om den är sparad lokalt, kommer alla enheter tillhörande en användare verifieras när användaren verifieras. - + Self signing key Självsigneringsnyckel @@ -2755,14 +2972,14 @@ Detta gör vanligtvis att ikonen i aktivitetsfältet animeras på något sätt.< Alla Filer (*) - + Open Sessions File Öppna sessionsfil - + @@ -2770,19 +2987,19 @@ Detta gör vanligtvis att ikonen i aktivitetsfältet animeras på något sätt.< Fel - - + + File Password Fillösenord - + Enter the passphrase to decrypt the file: Ange lösenfrasen för att dekryptera filen: - + The password cannot be empty Lösenordet kan inte vara tomt @@ -2797,6 +3014,14 @@ Detta gör vanligtvis att ikonen i aktivitetsfältet animeras på något sätt.< Fil för att spara de exporterade sessionsnycklarna + + VerificationManager + + + No encrypted private chat found with this user. Create an encrypted private chat with this user and try again. + Ingen krypterad privat chatt med denna användare kunde hittas. Skapa en krypterad privat chatt med användaren och försök igen. + + Waiting @@ -2851,7 +3076,7 @@ Detta gör vanligtvis att ikonen i aktivitetsfältet animeras på något sätt.< descriptiveTime - + Yesterday Igår @@ -2922,37 +3147,6 @@ Detta gör vanligtvis att ikonen i aktivitetsfältet animeras på något sätt.< Öppna reserven, följ stegen och bekräfta när du slutfört dem. - - dialogs::JoinRoom - - - Join - Gå med - - - - Cancel - Avbryt - - - - Room ID or alias - Rum-ID eller alias - - - - dialogs::LeaveRoom - - - Cancel - Avbryt - - - - Are you sure you want to leave? - Är du säker på att du vill lämna? - - dialogs::Logout @@ -3019,47 +3213,47 @@ Mediastorlek: %2 %1 skickade ett ljudklipp - + You sent an image Du skickade en bild - + %1 sent an image %1 skickade en bild - + You sent a file Du skickade en fil - + %1 sent a file %1 skickade en fil - + You sent a video Du skickade en video - + %1 sent a video %1 skickade en video - + You sent a sticker Du skickade en sticker - + %1 sent a sticker %1 skickade en sticker - + You sent a notification Du skickade en notis @@ -3074,7 +3268,7 @@ Mediastorlek: %2 Du: %1 - + %1: %2 %1: %2 @@ -3094,27 +3288,27 @@ Mediastorlek: %2 Du ringde upp - + %1 placed a call %1 ringde upp - + You answered a call Du besvarade ett samtal - + %1 answered a call %1 besvarade ett samtal - + You ended a call Du lade på - + %1 ended a call %1 lade på @@ -3122,7 +3316,7 @@ Mediastorlek: %2 utils - + Unknown Message Type Okänd meddelandetyp diff --git a/resources/langs/nheko_zh_CN.ts b/resources/langs/nheko_zh_CN.ts index 167c4a06..36cfb0a0 100644 --- a/resources/langs/nheko_zh_CN.ts +++ b/resources/langs/nheko_zh_CN.ts @@ -4,7 +4,7 @@ ActiveCallBar - + Calling... @@ -56,7 +56,7 @@ CallInvite - + Video Call @@ -74,7 +74,7 @@ CallInviteBar - + Video Call @@ -117,7 +117,7 @@ CallManager - + Entire screen @@ -125,23 +125,23 @@ ChatPage - + Failed to invite user: %1 邀请用户失败: %1 - + Invited user: %1 邀请已发送: %1 - + Migrating the cache to the current version failed. This can have different reasons. Please open an issue and try to use an older version in the mean time. Alternatively you can try deleting the cache manually. 无法迁移缓存到目前版本,可能有多种原因引发此类问题。您可以新建一个议题并继续使用之前版本,或者您可以尝试手动删除缓存。 - + Confirm join @@ -151,23 +151,23 @@ - + Room %1 created. 房间“%1”已创建 - - + + Confirm invite - + Do you really want to invite %1 (%2)? - + Failed to invite %1 to %2: %3 @@ -182,7 +182,7 @@ - + Kicked user: %1 踢出用户: %1 @@ -197,7 +197,7 @@ - + Failed to ban %1 in %2: %3 @@ -217,7 +217,7 @@ - + Failed to unban %1 in %2: %3 @@ -227,12 +227,12 @@ 解禁用户: %1 - + Do you really want to start a private chat with %1? - + Cache migration failed! 缓存迁移失败! @@ -259,23 +259,23 @@ 恢复保存的数据失败。请重新登录。 - + Failed to setup encryption keys. Server response: %1 %2. Please try again later. 设置密钥失败。服务器返回信息: %1 %2。请稍后再试。 - - + + Please try to login again: %1 请尝试再次登录:%1 - + Failed to join room: %1 无法加入房间: %1 - + You joined the room 您已加入房间 @@ -285,17 +285,17 @@ 无法移除邀请: %1 - + Room creation failed: %1 创建聊天室失败:%1 - + Failed to leave room: %1 离开聊天室失败:%1 - + Failed to kick %1 from %2: %3 @@ -354,7 +354,7 @@ CrossSigningSecrets - + Decrypt secrets @@ -364,12 +364,12 @@ - + Enter your recovery key or passphrase called %1 to decrypt your secrets: - + Decryption failed @@ -581,17 +581,26 @@ - Device verification timed out. - + Other party canceled the verification. - + + Verification messages received out of order! + + + + + Unknown verification error. + + + + Close @@ -612,7 +621,7 @@ - + Add images @@ -622,7 +631,7 @@ - + State key @@ -638,13 +647,13 @@ - + Use as Emoji - - + + Use as Sticker @@ -697,7 +706,7 @@ - + Private pack @@ -712,7 +721,7 @@ - + Enable globally @@ -735,7 +744,7 @@ InputBar - + Select a file 选择一个文件 @@ -745,7 +754,7 @@ 所有文件(*) - + Failed to upload media. Please try again. @@ -753,7 +762,7 @@ InviteDialog - + Invite users to %1 @@ -784,6 +793,32 @@ 取消 + + JoinRoomDialog + + + Join room + + + + + Room ID or alias + 聊天室 ID 或别名 + + + + LeaveRoomDialog + + + Leave room + 离开聊天室 + + + + Are you sure you want to leave? + 你确定要离开吗? + + LoginPage @@ -846,25 +881,25 @@ Example: https://server.my:8787 登录 - + - + You have entered an invalid Matrix ID e.g @joe:matrix.org - + Autodiscovery failed. Received malformed response. - + Autodiscovery failed. Unknown error when requesting .well-known. - + The required endpoints were not found. Possibly not a Matrix server. 没找到要求的终端。可能不是一个 Matrix 服务器。 @@ -874,26 +909,44 @@ Example: https://server.my:8787 收到形式错误的响应。请确认服务器域名合法。 - + An unknown error occured. Make sure the homeserver domain is valid. 发生了一个未知错误。请确认服务器域名合法。 - + SSO LOGIN - + Empty password 空密码 - + SSO login failed + + LogoutDialog + + + Log out + + + + + A call is in progress. Log out? + + + + + Are you sure you want to log out? + + + MessageDelegate @@ -1019,7 +1072,7 @@ Example: https://server.my:8787 MessageView - + Edit @@ -1039,7 +1092,7 @@ Example: https://server.my:8787 - + &Copy @@ -1129,7 +1182,12 @@ Example: https://server.my:8787 - + + To allow other users to see, which of your devices actually belong to you, you can verify them. This also allows key backup to work automatically. Verify an unverified device now? (Please make sure you have one of those devices available.) + + + + To allow other users to see, which of your devices actually belong to you, you can verify them. This also allows key backup to work automatically. Verify %1 now? @@ -1178,7 +1236,7 @@ Example: https://server.my:8787 NotificationWarning - You will be pinging the whole room + You are about to notify the whole room @@ -1186,29 +1244,17 @@ Example: https://server.my:8787 NotificationsManager - - + + %1 sent an encrypted message - - * %1 %2 - Format an emote message in a notification, %1 is the sender, %2 the message - - - - + %1 replied: %2 Format a reply in a notification. %1 is the sender, %2 the message - - - %1: %2 - Format a normal message in a notification. %1 is the sender, %2 the message - - @@ -1239,7 +1285,7 @@ Example: https://server.my:8787 - + Voice @@ -1270,7 +1316,7 @@ Example: https://server.my:8787 QCoreApplication - + Create a unique profile, which allows you to log into several accounts at the same time and start multiple instances of nheko. @@ -1288,7 +1334,7 @@ Example: https://server.my:8787 ReadReceipts - + Read receipts 阅读回执 @@ -1296,7 +1342,7 @@ Example: https://server.my:8787 ReadReceiptsModel - + Yesterday, %1 @@ -1304,18 +1350,18 @@ Example: https://server.my:8787 RegisterPage - + Username 用户名 - + The username must not be empty, and must contain only the characters a-z, 0-9, ., _, =, -, and /. - + Password 密码 @@ -1345,37 +1391,22 @@ Example: https://server.my:8787 注册 - - No supported registration flows! - - - - - Registration token - - - - - Please enter a valid registration token. - - - - + Autodiscovery failed. Received malformed response. - + Autodiscovery failed. Unknown error when requesting .well-known. - + The required endpoints were not found. Possibly not a Matrix server. 没找到要求的终端。可能不是一个 Matrix 服务器。 - + Received malformed response. Make sure the homeserver domain is valid. 收到形式错误的响应。请确认服务器域名合法。 @@ -1385,7 +1416,7 @@ Example: https://server.my:8787 发生了一个未知错误。请确认服务器域名合法。 - + Password is not long enough (min 8 chars) 密码不够长(至少8个字符) @@ -1416,20 +1447,25 @@ Example: https://server.my:8787 RoomDirectory - + Explore Public Rooms - + Search for public rooms + + + Choose custom homeserver + + RoomInfo - + no version stored @@ -1446,16 +1482,6 @@ Example: https://server.my:8787 Enter the tag you want to use: - - - Leave Room - - - - - Are you sure you want to leave this room? - - Leave room @@ -1487,7 +1513,7 @@ Example: https://server.my:8787 - + Status Message @@ -1512,7 +1538,30 @@ Example: https://server.my:8787 登出 - + + Encryption not set up + Cross-signing setup has not run yet. + + + + + Unverified login + The user just signed in with this device and hasn't verified their master key. + + + + + Please verify your other devices + There are unverified devices signed in to this account. + + + + + Close + + + + Start a new chat 开始新的聊天 @@ -1540,12 +1589,12 @@ Example: https://server.my:8787 RoomMembers - + Members of %1 - + %n people in %1 Summary above list of members @@ -1581,12 +1630,12 @@ Example: https://server.my:8787 RoomSettings - + Room Settings - + %1 member(s) @@ -1692,12 +1741,12 @@ Example: https://server.my:8787 - + Failed to enable encryption: %1 启用加密失败:%1 - + Select an avatar 选择一个头像 @@ -1717,8 +1766,8 @@ Example: https://server.my:8787 - - + + Failed to upload image: %s 上传图像失败:%s @@ -1726,21 +1775,49 @@ Example: https://server.my:8787 RoomlistModel - + Pending invite. - + Previewing this room - + No preview available + + Root + + + Please enter your login password to continue: + + + + + Please enter a valid email address to continue: + + + + + Please enter a valid phone number to continue: + + + + + Please enter the token, which has been sent to you: + + + + + Wait for the confirmation link to arrive, then continue. + + + ScreenShare @@ -1798,20 +1875,98 @@ Example: https://server.my:8787 SecretStorage - + Failed to connect to secret storage - Nheko could not connect to the secure storage to save encryption secrets to. This can have multiple reasons. Check if your D-Bus service is running and you have configured a service like KWallet, Gnome Secrets or the equivalent for your platform. If you are having trouble, feel free to open an issue here: https://github.com/Nheko-Reborn/nheko/issues + Nheko could not connect to the secure storage to save encryption secrets to. This can have multiple reasons. Check if your D-Bus service is running and you have configured a service like KWallet, Gnome Keyring, KeePassXC or the equivalent for your platform. If you are having trouble, feel free to open an issue here: https://github.com/Nheko-Reborn/nheko/issues + + + + + SelfVerificationCheck + + + This is your recovery key. You will need it to restore access to your encrypted messages and verification keys. Keep this safe. Don't share it with anyone and don't lose it! Do not pass go! Do not collect $200! + + + + + Encryption setup successfully + + + + + Failed to setup encryption: %1 + + + + + Setup Encryption + + + + + Hello and welcome to Matrix! +It seems like you are new. Before you can securely encrypt your messages, we need to setup a few small things. You can either press accept immediately or adjust a few basic options. We also try to explain a few of the basics. You can skip those parts, but they might prove to be helpful! + + + + + Activate Encryption + + + + + It seems like you have encryption already configured for this account. To be able to access your encrypted messages and make this device appear as trusted, you can either verify an existing device or (if you have one) enter your recovery passphrase. Please select one of the options below. +If you choose verify, you need to have the other device available. If you choose "enter passphrase", you will need your recovery key or passphrase. If you click cancel, you can choose to verify yourself at a later point. + + + + + verify + + + + + enter passphrase + + + + + SelfVerificationStatus + + + Failed to create keys for cross-signing! + + + + + Failed to create keys for online key backup! + + + + + Failed to create keys secure server side secret storage! + + + + + Encryption Setup + + + + + Encryption setup failed: %1 SingleImagePackModel - + Failed to update image pack: %1 @@ -1827,7 +1982,7 @@ Example: https://server.my:8787 - + Failed to upload image: %1 @@ -1884,18 +2039,18 @@ Example: https://server.my:8787 TimelineModel - + Message redaction failed: %1 删除消息失败:%1 - + Failed to encrypt event, sending aborted! - + Save image 保存图像 @@ -1915,7 +2070,7 @@ Example: https://server.my:8787 - + %1 and %2 are typing. Multiple users are typing. First argument is a comma separated list of potentially multiple users. Second argument is the last user of that list. (If only one user is typing, %1 is empty. You should still use it in your string though to silence Qt warnings.) @@ -1923,7 +2078,7 @@ Example: https://server.my:8787 - + %1 opened the room to the public. @@ -1963,12 +2118,12 @@ Example: https://server.my:8787 - + %1 set the room history visible to members since they were invited. - + %1 set the room history visible to members since they joined the room. @@ -1978,12 +2133,12 @@ Example: https://server.my:8787 - + %1 was invited. - + %1 changed their avatar. @@ -1998,12 +2153,12 @@ Example: https://server.my:8787 - + %1 joined via authorisation from %2's server. - + %1 rejected their invite. @@ -2033,27 +2188,27 @@ Example: https://server.my:8787 - + Reason: %1 - + %1 redacted their knock. - + You joined this room. 您已加入此房间 - + %1 has changed their avatar and changed their display name to %2. - + %1 has changed their display name to %2. @@ -2090,7 +2245,12 @@ Example: https://server.my:8787 - + + No preview available + + + + %1 member(s) @@ -2115,28 +2275,20 @@ Example: https://server.my:8787 - - TimelineViewManager - - - No encrypted private chat found with this user. Create an encrypted private chat with this user and try again. - - - TopBar - + Back to room list - + No room selected - + This room is not encrypted! @@ -2147,7 +2299,7 @@ Example: https://server.my:8787 - This rooms contain verified devices and devices which have never changed their master key. + This room contains verified devices and devices which have never changed their master key. @@ -2194,10 +2346,35 @@ Example: https://server.my:8787 退出 + + UIA + + + No available registration flows! + + + + + + + Registration aborted + + + + + Please enter a valid registration token. + + + + + Invalid token + + + UserProfile - + Global User Profile @@ -2207,7 +2384,7 @@ Example: https://server.my:8787 - + Change avatar globally. @@ -2217,7 +2394,7 @@ Example: https://server.my:8787 - + Change display name globally. @@ -2227,7 +2404,7 @@ Example: https://server.my:8787 - + Room: %1 @@ -2237,18 +2414,18 @@ Example: https://server.my:8787 - + Open the global profile for this user. - - + + Verify - + Start a private chat. @@ -2263,12 +2440,42 @@ Example: https://server.my:8787 - + + Refresh device list. + + + + + Sign out this device. + + + + + Change device name. + + + + + Last seen %1 from %2 + + + + Unverify - + + Sign out device %1 + + + + + You signed out this device. + + + + Select an avatar 选择一个头像 @@ -2291,8 +2498,8 @@ Example: https://server.my:8787 UserSettings - - + + Default @@ -2300,7 +2507,7 @@ Example: https://server.my:8787 UserSettingsPage - + Minimize to tray 最小化至托盘 @@ -2310,22 +2517,22 @@ Example: https://server.my:8787 在托盘启动 - + Group's sidebar 群组侧边栏 - + Circular Avatars - + profile: %1 - + Default @@ -2350,7 +2557,7 @@ Example: https://server.my:8787 - + Keep the application running in the background after closing the client window. @@ -2365,6 +2572,16 @@ Example: https://server.my:8787 OFF - square, ON - Circle. + + + Use identicons + + + + + Display an identicon instead of a letter when a user has not set an avatar. + + Show a column containing groups and tags next to the room list. @@ -2393,7 +2610,7 @@ be blurred. - + Privacy screen timeout (in seconds [0 - 3600]) @@ -2448,7 +2665,7 @@ If this is on, rooms which have active notifications (the small circle with a nu - + Read receipts 阅读回执 @@ -2459,7 +2676,7 @@ Status is displayed next to timestamps. - + Send messages as Markdown @@ -2546,7 +2763,7 @@ This usually causes the application icon in the task bar to animate in some fash - + Enable online key backup @@ -2556,7 +2773,7 @@ This usually causes the application icon in the task bar to animate in some fash - + CACHED @@ -2566,7 +2783,7 @@ This usually causes the application icon in the task bar to animate in some fash - + Scale factor @@ -2641,7 +2858,7 @@ This usually causes the application icon in the task bar to animate in some fash 设备指纹 - + Session Keys 会话密钥 @@ -2661,17 +2878,17 @@ This usually causes the application icon in the task bar to animate in some fash 加密 - + GENERAL 通用 - + INTERFACE - + Plays media like GIFs or WEBPs only when explicitly hovering over them. @@ -2711,7 +2928,7 @@ This usually causes the application icon in the task bar to animate in some fash - + Self signing key @@ -2741,14 +2958,14 @@ This usually causes the application icon in the task bar to animate in some fash 所有文件(*) - + Open Sessions File 打开会话文件 - + @@ -2756,19 +2973,19 @@ This usually causes the application icon in the task bar to animate in some fash 错误 - - + + File Password 文件密码 - + Enter the passphrase to decrypt the file: 输入密码以解密文件: - + The password cannot be empty 密码不能为空 @@ -2783,6 +3000,14 @@ This usually causes the application icon in the task bar to animate in some fash 保存导出的会话密钥的文件 + + VerificationManager + + + No encrypted private chat found with this user. Create an encrypted private chat with this user and try again. + + + Waiting @@ -2837,7 +3062,7 @@ This usually causes the application icon in the task bar to animate in some fash descriptiveTime - + Yesterday @@ -2908,37 +3133,6 @@ This usually causes the application icon in the task bar to animate in some fash - - dialogs::JoinRoom - - - Join - - - - - Cancel - 取消 - - - - Room ID or alias - 聊天室 ID 或别名 - - - - dialogs::LeaveRoom - - - Cancel - 取消 - - - - Are you sure you want to leave? - 你确定要离开吗? - - dialogs::Logout @@ -3005,47 +3199,47 @@ Media size: %2 - + You sent an image - + %1 sent an image - + You sent a file - + %1 sent a file - + You sent a video - + %1 sent a video - + You sent a sticker - + %1 sent a sticker - + You sent a notification @@ -3060,7 +3254,7 @@ Media size: %2 - + %1: %2 @@ -3080,27 +3274,27 @@ Media size: %2 - + %1 placed a call - + You answered a call - + %1 answered a call - + You ended a call - + %1 ended a call @@ -3108,7 +3302,7 @@ Media size: %2 utils - + Unknown Message Type diff --git a/src/Cache.cpp b/src/Cache.cpp index 5e28a0b8..58eb2630 100644 --- a/src/Cache.cpp +++ b/src/Cache.cpp @@ -766,11 +766,11 @@ fatalSecretError() QCoreApplication::translate("SecretStorage", "Failed to connect to secret storage"), QCoreApplication::translate( "SecretStorage", - "Nheko could not connect to the secure storage to save encryption secrets to. " - "This can have multiple reasons. Check if your D-Bus service is running and " - "you have configured a service like KWallet, Gnome Secrets or the equivalent " - "for your platform. If you are having trouble, feel free to open an issue " - "here: https://github.com/Nheko-Reborn/nheko/issues")); + "Nheko could not connect to the secure storage to save encryption secrets to. This can " + "have multiple reasons. Check if your D-Bus service is running and you have configured a " + "service like KWallet, Gnome Keyring, KeePassXC or the equivalent for your platform. If " + "you are having trouble, feel free to open an issue here: " + "https://github.com/Nheko-Reborn/nheko/issues")); QCoreApplication::exit(1); exit(1);