diff --git a/CMakeLists.txt b/CMakeLists.txt index 2f83a865..2fa839be 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -340,7 +340,7 @@ if(USE_BUNDLED_MTXCLIENT) FetchContent_Declare( MatrixClient GIT_REPOSITORY https://github.com/Nheko-Reborn/mtxclient.git - GIT_TAG ad5575bc24089dc385e97d9ace026414b618775c + GIT_TAG 6432e89a3465e58ed838dd2abdcb0f91bd4f05b0 ) FetchContent_MakeAvailable(MatrixClient) else() diff --git a/README.md b/README.md index 92b55460..ee42cb0c 100644 --- a/README.md +++ b/README.md @@ -41,6 +41,7 @@ Specifically there is support for: - Basic communities support. - Room switcher (ctrl-K). - Light, Dark & System themes. +- Creating separate profiles (command line only, use `--profile=name`). ## Installation diff --git a/io.github.NhekoReborn.Nheko.json b/io.github.NhekoReborn.Nheko.json index 59f4fa46..c461ceaa 100644 --- a/io.github.NhekoReborn.Nheko.json +++ b/io.github.NhekoReborn.Nheko.json @@ -146,7 +146,7 @@ "name": "mtxclient", "sources": [ { - "commit": "ad5575bc24089dc385e97d9ace026414b618775c", + "commit": "6432e89a3465e58ed838dd2abdcb0f91bd4f05b0", "type": "git", "url": "https://github.com/Nheko-Reborn/mtxclient.git" } diff --git a/resources/langs/nheko_cs.ts b/resources/langs/nheko_cs.ts index 38443412..318ceb64 100644 --- a/resources/langs/nheko_cs.ts +++ b/resources/langs/nheko_cs.ts @@ -1,10 +1,56 @@ + + ActiveCallBar + + + Initiating... + + + + + Calling... + + + + + Connecting... + + + + + Unmute Mic + + + + + Mute Mic + + + + + AwaitingVerificationConfirmation + + + Awaiting Confirmation + + + + + Waiting for other side to complete verification. + + + + + Cancel + + + Cache - + You joined this room. @@ -12,23 +58,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. - + Room %1 created. @@ -108,12 +154,12 @@ - + Failed to upload media. Please try again. - + Cache migration failed! @@ -128,7 +174,7 @@ - + Failed to restore OLM account. Please login again. @@ -144,12 +190,12 @@ - + Please try to login again: %1 - + Failed to join room: %1 @@ -209,6 +255,29 @@ + + 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 @@ -235,13 +304,13 @@ EmojiPicker - - + + Search - + People @@ -281,10 +350,33 @@ + + 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! + + + EncryptionIndicator - + Encrypted @@ -297,25 +389,27 @@ EventStore - + -- Encrypted Event (No keys found for decryption) -- Placeholder, when the message was not decrypted yet or can't be decrypted. - + + -- 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. @@ -331,6 +425,40 @@ + + Failed + + + Verification failed + + + + + Other client does not support our verification protocol. + + + + + Key mismatch detected! + + + + + + Device verification timed out. + + + + + Other party canceled the verification. + + + + + Close + + + InviteeItem @@ -443,18 +571,18 @@ Example: https://server.my:8787 MessageDelegate - - + + redacted - + Encryption enabled - + room name changed to: %1 @@ -464,7 +592,7 @@ Example: https://server.my:8787 - + topic changed to: %1 @@ -474,41 +602,92 @@ Example: https://server.my:8787 - + %1 created and configured room: %2 - + %1 placed a voice call. - + %1 placed a video call. - + %1 placed a call. - + Negotiating call... - + %1 answered the call. - + %1 ended the call. + + MessageInput + + + Write a message... + + + + + NewVerificationRequest + + + Send Device Verification Request + + + + + Recieved Device Verification Request + + + + + To ensure that no malicious user can eavesdrop on your encrypted communications, you can verify this device. + + + + + The device was requested to be verified + + + + + Cancel + + + + + Deny + + + + + Start verification + + + + + Accept + + + Placeholder @@ -517,6 +696,24 @@ 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. + + + + + profile + + + + + profile name + + + QuickSwitcher @@ -593,10 +790,18 @@ Example: https://server.my:8787 + + ReplyPopup + + + Close + + + RoomInfo - + no version stored @@ -656,7 +861,7 @@ Example: https://server.my:8787 - + Accept @@ -697,30 +902,48 @@ Example: https://server.my:8787 StatusIndicator - + Failed - + Sent - + Received - + Read + + Success + + + Successful Verification + + + + + Verification successful! Both sides verified their devices! + + + + + Close + + + TextInputWidget - + Send a file @@ -741,7 +964,7 @@ Example: https://server.my:8787 - + Select a file @@ -751,7 +974,7 @@ Example: https://server.my:8787 - + Place a call @@ -769,20 +992,19 @@ Example: https://server.my:8787 TimelineModel - + Message redaction failed: %1 - - - + + Failed to encrypt event, sending aborted! - + Save image @@ -922,12 +1144,12 @@ Example: https://server.my:8787 - + You joined this room. - + Rejected the knock from %1. @@ -951,17 +1173,17 @@ Example: https://server.my:8787 TimelineRow - + React - + Reply - + Options @@ -969,63 +1191,74 @@ Example: https://server.my:8787 TimelineView - + React - + Reply - + Read receipts - + Mark as read - + View raw message - + View decrypted raw message - + Redact message - + Save as - + No room open + + + TimelineViewManager - + + No share room with this user found. Create an encrypted room with this user and try again. + + + + + TopBar + + Back to room list - + No room selected - + Room options @@ -1035,25 +1268,20 @@ Example: https://server.my:8787 - + Members - + Leave room - + Settings - - - Close - - TrayIcon @@ -1111,10 +1339,33 @@ Example: https://server.my:8787 + + UserProfile + + + Verify + + + + + Ban the user + + + + + Start a private chat + + + + + Kick the user + + + UserSettingsPage - + Minimize to tray @@ -1134,12 +1385,17 @@ Example: https://server.my:8787 - + + profile: %1 + + + + CALLS - + Keep the application running in the background after closing the client window. @@ -1277,7 +1533,17 @@ This usually causes the application icon in the task bar to animate in some fash - + + Mobile mode + + + + + Will prevent text selection in the timeline to make scrolling easier. + + + + Scale factor @@ -1322,7 +1588,7 @@ This usually causes the application icon in the task bar to animate in some fash - + Session Keys @@ -1342,22 +1608,32 @@ This usually causes the application icon in the task bar to animate in some fash - + GENERAL - + INTERFACE - + Emoji Font Family - + + Share keys with trusted users + + + + + Automatically replies to key requests from other users, if they are verified. + + + + Open Sessions File @@ -1403,6 +1679,34 @@ This usually causes the application icon in the task bar to animate in some fash + + Waiting + + + Waiting for other party + + + + + Waiting for other side to accept the verification request. + + + + + Waiting for other side to continue the verification request. + + + + + Waiting for other side to complete the verification request. + + + + + Cancel + + + WelcomePage diff --git a/resources/langs/nheko_de.ts b/resources/langs/nheko_de.ts index 0958017f..7afd8c51 100644 --- a/resources/langs/nheko_de.ts +++ b/resources/langs/nheko_de.ts @@ -1,10 +1,56 @@ + + ActiveCallBar + + + Initiating... + + + + + Calling... + + + + + Connecting... + + + + + Unmute Mic + + + + + Mute Mic + + + + + AwaitingVerificationConfirmation + + + Awaiting Confirmation + + + + + Waiting for other side to complete verification. + + + + + Cancel + Abbrechen + + Cache - + You joined this room. Du bist dem Raum beigetreten. @@ -12,23 +58,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 das Cache manuell löschen. - + Room %1 created. Raum %1 erzeugt. @@ -108,12 +154,12 @@ Verbannung von %1 wurde aufgehoben. - + Failed to upload media. Please try again. Medienupload fehlgeschlagen. Bitte versuche es erneut. - + Cache migration failed! Cache migration fehlgeschlagen! @@ -128,7 +174,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. @@ -144,12 +190,12 @@ - + Please try to login again: %1 Bitte melde dich erneut an: %1 - + Failed to join room: %1 Konnte Raum nicht betreten: %1 @@ -209,6 +255,29 @@ (community) + + 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 @@ -235,13 +304,13 @@ EmojiPicker - - + + Search Suche - + People Leute @@ -281,10 +350,33 @@ Flaggen + + 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! + + + EncryptionIndicator - + Encrypted Verschlüsselt @@ -297,25 +389,27 @@ 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) -- - + + -- 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) -- @@ -331,6 +425,40 @@ + + Failed + + + Verification failed + + + + + Other client does not support our verification protocol. + + + + + Key mismatch detected! + + + + + + Device verification timed out. + + + + + Other party canceled the verification. + + + + + Close + Schließen + + InviteeItem @@ -447,18 +575,18 @@ Beispiel: https://mein.server:8787 MessageDelegate - - + + redacted gelöscht - + Encryption enabled Verschlüsselung aktiviert - + room name changed to: %1 Raumname wurde gändert auf: %1 @@ -468,7 +596,7 @@ Beispiel: https://mein.server:8787 Raumname wurde entfernt - + topic changed to: %1 Raumthema wurde geändert auf: %1 @@ -478,41 +606,92 @@ Beispiel: https://mein.server:8787 Raumthema wurde entfernt. - + %1 created and configured room: %2 %1 hat den Raum erstellt: %2 - + %1 placed a voice call. - + %1 placed a video call. - + %1 placed a call. - + %1 answered the call. %1 hat den Anruf angenommen. - + %1 ended the call. %1 hat den Anruf beendet. - + Negotiating call... + + MessageInput + + + Write a message... + Schreibe eine Nachricht… + + + + NewVerificationRequest + + + Send Device Verification Request + + + + + Recieved Device Verification Request + + + + + To ensure that no malicious user can eavesdrop on your encrypted communications, you can verify this device. + + + + + The device was requested to be verified + + + + + Cancel + Abbrechen + + + + Deny + + + + + Start verification + + + + + Accept + Akzeptieren + + Placeholder @@ -521,6 +700,24 @@ Beispiel: https://mein.server:8787 Unimplementiertes 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 + + + QuickSwitcher @@ -597,10 +794,18 @@ Beispiel: https://mein.server:8787 Ungültiger Servername + + ReplyPopup + + + Close + Schließen + + RoomInfo - + no version stored keine Version gespeichert @@ -660,7 +865,7 @@ Beispiel: https://mein.server:8787 - + Accept Akzeptieren @@ -701,30 +906,48 @@ Beispiel: https://mein.server:8787 StatusIndicator - + Failed Fehlgeschlagen - + Sent Gesendet - + Received Empfangen - + Read Gelesen + + Success + + + Successful Verification + + + + + Verification successful! Both sides verified their devices! + + + + + Close + Schließen + + TextInputWidget - + Send a file Versende Datei @@ -745,7 +968,7 @@ Beispiel: https://mein.server:8787 Emoji - + Select a file Datei auswählen @@ -755,7 +978,7 @@ Beispiel: https://mein.server:8787 Alle Dateien (*) - + Place a call @@ -773,20 +996,19 @@ Beispiel: https://mein.server:8787 TimelineModel - + Message redaction failed: %1 Nachricht zurückziehen fehlgeschlagen: %1 - - - + + Failed to encrypt event, sending aborted! - + Save image Bild speichern @@ -925,12 +1147,12 @@ Beispiel: https://mein.server:8787 %1 hat das Anklopfen zurückgezogen. - + You joined this room. Du bist dem Raum beigetreten. - + Rejected the knock from %1. Hat das Anklopfen von %1 abgewiesen. @@ -954,17 +1176,17 @@ Beispiel: https://mein.server:8787 TimelineRow - + React - + Reply Antworten - + Options Optionen @@ -972,63 +1194,74 @@ Beispiel: https://mein.server:8787 TimelineView - + React - + Reply Antworten - + Read receipts Lesebestätigungen - + Mark as read Als gelesen markieren - + View raw message Zeige rohen Nachrichteninhalt - + View decrypted raw message Zeige rohen, entschlüsselten Nachrichteninhalt - + Redact message Nachricht löschen - + Save as Speichern unter... - + No room open Kein Raum geöffnet + + + TimelineViewManager - + + No share room with this user found. Create an encrypted room with this user and try again. + + + + + TopBar + + Back to room list - + No room selected - + Room options Raumoptionen @@ -1038,25 +1271,20 @@ Beispiel: https://mein.server:8787 Benutzer einladen - + Members Teilnehmer - + Leave room Raum verlassen - + Settings Einstellungen - - - Close - Schließen - TrayIcon @@ -1114,10 +1342,33 @@ Beispiel: https://mein.server:8787 + + UserProfile + + + Verify + + + + + Ban the user + + + + + Start a private chat + + + + + Kick the user + + + UserSettingsPage - + Minimize to tray Ins Benachrichtigungsfeld minimieren @@ -1137,12 +1388,17 @@ Beispiel: https://mein.server:8787 Runde Profilbilder - + + profile: %1 + + + + CALLS - + Keep the application running in the background after closing the client window. @@ -1280,7 +1536,17 @@ This usually causes the application icon in the task bar to animate in some fash - + + Mobile mode + + + + + Will prevent text selection in the timeline to make scrolling easier. + + + + Scale factor Skalierungsfaktor @@ -1325,7 +1591,7 @@ This usually causes the application icon in the task bar to animate in some fash Gerätefingerabdruck - + Session Keys Sitzungsschlüssel @@ -1345,22 +1611,32 @@ This usually causes the application icon in the task bar to animate in some fash VERSCHLÜSSELUNG - + GENERAL ALLGEMEINES - + INTERFACE OBERFLÄCHE - + Emoji Font Family Emojischriftart - + + Share keys with trusted users + + + + + Automatically replies to key requests from other users, if they are verified. + + + + Open Sessions File Öffne Sessions Datei @@ -1406,6 +1682,34 @@ This usually causes the application icon in the task bar to animate in some fash Datei zum Speichern der zu exportierenden Sitzungsschlüssel + + Waiting + + + Waiting for other party + + + + + Waiting for other side to accept the verification request. + + + + + Waiting for other side to continue the verification request. + + + + + Waiting for other side to complete the verification request. + + + + + Cancel + Abbrechen + + WelcomePage diff --git a/resources/langs/nheko_el.ts b/resources/langs/nheko_el.ts index d0a46653..d943c6f1 100644 --- a/resources/langs/nheko_el.ts +++ b/resources/langs/nheko_el.ts @@ -1,10 +1,56 @@ + + ActiveCallBar + + + Initiating... + + + + + Calling... + + + + + Connecting... + + + + + Unmute Mic + + + + + Mute Mic + + + + + AwaitingVerificationConfirmation + + + Awaiting Confirmation + + + + + Waiting for other side to complete verification. + + + + + Cancel + Άκυρο + + Cache - + You joined this room. @@ -12,23 +58,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. - + Room %1 created. @@ -108,12 +154,12 @@ - + Failed to upload media. Please try again. - + Cache migration failed! @@ -128,7 +174,7 @@ - + Failed to restore OLM account. Please login again. @@ -144,12 +190,12 @@ - + Please try to login again: %1 - + Failed to join room: %1 @@ -209,6 +255,29 @@ + + 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 @@ -235,13 +304,13 @@ EmojiPicker - - + + Search - + People @@ -281,10 +350,33 @@ Σημαίες + + 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! + + + EncryptionIndicator - + Encrypted @@ -297,25 +389,27 @@ EventStore - + -- Encrypted Event (No keys found for decryption) -- Placeholder, when the message was not decrypted yet or can't be decrypted. - + + -- 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. @@ -331,6 +425,40 @@ + + Failed + + + Verification failed + + + + + Other client does not support our verification protocol. + + + + + Key mismatch detected! + + + + + + Device verification timed out. + + + + + Other party canceled the verification. + + + + + Close + + + InviteeItem @@ -443,18 +571,18 @@ Example: https://server.my:8787 MessageDelegate - - + + redacted - + Encryption enabled - + room name changed to: %1 @@ -464,7 +592,7 @@ Example: https://server.my:8787 - + topic changed to: %1 @@ -474,41 +602,92 @@ Example: https://server.my:8787 - + %1 created and configured room: %2 - + %1 placed a voice call. - + %1 placed a video call. - + %1 placed a call. - + %1 answered the call. - + %1 ended the call. - + Negotiating call... + + MessageInput + + + Write a message... + Γράψε ένα μήνυμα... + + + + NewVerificationRequest + + + Send Device Verification Request + + + + + Recieved Device Verification Request + + + + + To ensure that no malicious user can eavesdrop on your encrypted communications, you can verify this device. + + + + + The device was requested to be verified + + + + + Cancel + Άκυρο + + + + Deny + + + + + Start verification + + + + + Accept + Αποδοχή + + Placeholder @@ -517,6 +696,24 @@ 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. + + + + + profile + + + + + profile name + + + QuickSwitcher @@ -593,10 +790,18 @@ Example: https://server.my:8787 Λανθασμένο όνομα διακομιστή + + ReplyPopup + + + Close + + + RoomInfo - + no version stored @@ -656,7 +861,7 @@ Example: https://server.my:8787 - + Accept Αποδοχή @@ -697,30 +902,48 @@ Example: https://server.my:8787 StatusIndicator - + Failed - + Sent - + Received - + Read + + Success + + + Successful Verification + + + + + Verification successful! Both sides verified their devices! + + + + + Close + + + TextInputWidget - + Send a file @@ -741,7 +964,7 @@ Example: https://server.my:8787 - + Select a file Διάλεξε ένα αρχείο @@ -751,7 +974,7 @@ Example: https://server.my:8787 Όλα τα αρχεία (*) - + Place a call @@ -769,20 +992,19 @@ Example: https://server.my:8787 TimelineModel - + Message redaction failed: %1 - - - + + Failed to encrypt event, sending aborted! - + Save image Αποθήκευση Εικόνας @@ -921,12 +1143,12 @@ Example: https://server.my:8787 - + You joined this room. - + Rejected the knock from %1. @@ -950,17 +1172,17 @@ Example: https://server.my:8787 TimelineRow - + React - + Reply - + Options @@ -968,63 +1190,74 @@ Example: https://server.my:8787 TimelineView - + React - + Reply - + Read receipts - + Mark as read - + View raw message - + View decrypted raw message - + Redact message - + Save as - + No room open + + + TimelineViewManager - + + No share room with this user found. Create an encrypted room with this user and try again. + + + + + TopBar + + Back to room list - + No room selected - + Room options @@ -1034,25 +1267,20 @@ Example: https://server.my:8787 Προσκάλεσε χρήστες - + Members Μέλη - + Leave room Βγές - + Settings Ρυθμίσεις - - - Close - - TrayIcon @@ -1110,10 +1338,33 @@ Example: https://server.my:8787 + + UserProfile + + + Verify + + + + + Ban the user + + + + + Start a private chat + + + + + Kick the user + + + UserSettingsPage - + Minimize to tray Ελαχιστοποίηση @@ -1133,12 +1384,17 @@ Example: https://server.my:8787 - + + profile: %1 + + + + CALLS - + Keep the application running in the background after closing the client window. @@ -1276,7 +1532,17 @@ This usually causes the application icon in the task bar to animate in some fash - + + Mobile mode + + + + + Will prevent text selection in the timeline to make scrolling easier. + + + + Scale factor @@ -1321,7 +1587,7 @@ This usually causes the application icon in the task bar to animate in some fash - + Session Keys @@ -1341,22 +1607,32 @@ This usually causes the application icon in the task bar to animate in some fash - + GENERAL ΓΕΝΙΚΑ - + INTERFACE - + Emoji Font Family - + + Share keys with trusted users + + + + + Automatically replies to key requests from other users, if they are verified. + + + + Open Sessions File @@ -1402,6 +1678,34 @@ This usually causes the application icon in the task bar to animate in some fash + + Waiting + + + Waiting for other party + + + + + Waiting for other side to accept the verification request. + + + + + Waiting for other side to continue the verification request. + + + + + Waiting for other side to complete the verification request. + + + + + Cancel + Άκυρο + + WelcomePage diff --git a/resources/langs/nheko_en.ts b/resources/langs/nheko_en.ts index bfbbd987..12e030b1 100644 --- a/resources/langs/nheko_en.ts +++ b/resources/langs/nheko_en.ts @@ -1,10 +1,56 @@ + + ActiveCallBar + + + Initiating... + Initiating… + + + + Calling... + Calling… + + + + Connecting... + Connecting… + + + + Unmute Mic + Unmute Mic + + + + Mute Mic + Mute Mic + + + + AwaitingVerificationConfirmation + + + Awaiting Confirmation + Awaiting Confirmation + + + + Waiting for other side to complete verification. + Waiting for other side to complete verification. + + + + Cancel + Cancel + + Cache - + You joined this room. You joined this room. @@ -12,23 +58,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. - + Room %1 created. Room %1 created. @@ -108,12 +154,12 @@ Unbanned user: %1 - + Failed to upload media. Please try again. Failed to upload media. Please try again. - + Cache migration failed! Cache migration failed! @@ -128,7 +174,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. @@ -144,12 +190,12 @@ - + Please try to login again: %1 Please try to login again: %1 - + Failed to join room: %1 Failed to join room: %1 @@ -209,6 +255,29 @@ (community) + + DigitVerification + + + Verification Code + 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! + 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 do not match! + + + + They match! + They match! + + EditModal @@ -235,13 +304,13 @@ EmojiPicker - - + + Search Search - + People People @@ -281,10 +350,33 @@ Flags + + EmojiVerification + + + Verification Code + 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! + 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 do not match! + + + + They match! + They match! + + EncryptionIndicator - + Encrypted Encrypted @@ -297,25 +389,27 @@ 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) -- - + + -- 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) -- @@ -331,6 +425,40 @@ -- Message by unverified device! -- + + Failed + + + Verification failed + Verification failed + + + + Other client does not support our verification protocol. + Other client does not support our verification protocol. + + + + Key mismatch detected! + Key mismatch detected! + + + + + Device verification timed out. + Device verification timed out. + + + + Other party canceled the verification. + Other party canceled the verification. + + + + Close + Close + + InviteeItem @@ -447,18 +575,18 @@ Example: https://server.my:8787 MessageDelegate - - + + redacted redacted - + Encryption enabled Encryption enabled - + room name changed to: %1 room name changed to: %1 @@ -468,7 +596,7 @@ Example: https://server.my:8787 removed room name - + topic changed to: %1 topic changed to: %1 @@ -478,41 +606,92 @@ Example: https://server.my:8787 removed topic - + %1 created and configured room: %2 %1 created and configured room: %2 - + %1 placed a voice call. %1 placed a voice call. - + %1 placed a video call. %1 placed a video call. - + %1 placed a call. %1 placed a call. - + Negotiating call... Negotiating call… - + %1 answered the call. %1 answered the call. - + %1 ended the call. %1 ended the call. + + MessageInput + + + Write a message... + Write a message… + + + + NewVerificationRequest + + + Send Device Verification Request + Send Device Verification Request + + + + Recieved Device Verification Request + Received Device Verification Request + + + + To ensure that no malicious user can eavesdrop on your encrypted communications, you can verify this device. + To ensure that no malicious user can eavesdrop on your encrypted communications, you can verify this device. + + + + The device was requested to be verified + The device was requested to be verified. + + + + Cancel + Cancel + + + + Deny + Deny + + + + Start verification + Start verification + + + + Accept + Accept + + Placeholder @@ -521,6 +700,24 @@ Example: https://server.my:8787 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. + Create a unique profile, which allows you to log into several accounts at the same time and start multiple instances of nheko. + + + + profile + profile + + + + profile name + profile name + + QuickSwitcher @@ -597,10 +794,18 @@ Example: https://server.my:8787 Invalid server name + + ReplyPopup + + + Close + Close + + RoomInfo - + no version stored no version stored @@ -660,7 +865,7 @@ Example: https://server.my:8787 Tag: - + Accept Accept @@ -701,30 +906,48 @@ Example: https://server.my:8787 StatusIndicator - + Failed Failed - + Sent Sent - + Received Received - + Read Read + + Success + + + Successful Verification + Successful Verification + + + + Verification successful! Both sides verified their devices! + Verification successful! Both sides verified their devices! + + + + Close + Close + + TextInputWidget - + Send a file Send a file @@ -745,7 +968,7 @@ Example: https://server.my:8787 Emoji - + Select a file Select a file @@ -755,7 +978,7 @@ Example: https://server.my:8787 All Files (*) - + Place a call Place a call @@ -773,20 +996,19 @@ 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 @@ -925,12 +1147,12 @@ Example: https://server.my:8787 %1 redacted their knock. - + You joined this room. You joined this room. - + Rejected the knock from %1. Rejected the knock from %1. @@ -954,17 +1176,17 @@ Example: https://server.my:8787 TimelineRow - + React React - + Reply Reply - + Options Options @@ -972,63 +1194,74 @@ Example: https://server.my:8787 TimelineView - + React React - + Reply Reply - + Read receipts Read receipts - + Mark as read Mark as read - + View raw message View raw message - + View decrypted raw message View decrypted raw message - + Redact message Redact message - + Save as Save as - + No room open No room open + + + TimelineViewManager - + + No share room with this user found. Create an encrypted room with this user and try again. + No shared room with this user found. Create an encrypted room with this user and try again. + + + + TopBar + + Back to room list Back to room list - + No room selected No room selected - + Room options Room options @@ -1038,25 +1271,20 @@ Example: https://server.my:8787 Invite users - + Members Members - + Leave room Leave room - + Settings Settings - - - Close - Close - TrayIcon @@ -1114,10 +1342,33 @@ Example: https://server.my:8787 Offline + + UserProfile + + + Verify + Verify + + + + Ban the user + Ban the user + + + + Start a private chat + Start a private chat + + + + Kick the user + Kick the user + + UserSettingsPage - + Minimize to tray Minimize to tray @@ -1137,12 +1388,17 @@ Example: https://server.my:8787 Circular Avatars - + + profile: %1 + profile: %1 + + + CALLS CALLS - + Keep the application running in the background after closing the client window. Keep the application running in the background after closing the client window. @@ -1288,7 +1544,17 @@ 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. - + + Mobile mode + Mobile mode + + + + Will prevent text selection in the timeline to make scrolling easier. + Will prevent text selection in the timeline to make scrolling easier. + + + Scale factor Scale factor @@ -1333,7 +1599,7 @@ This usually causes the application icon in the task bar to animate in some fash Device Fingerprint - + Session Keys Session Keys @@ -1353,22 +1619,32 @@ This usually causes the application icon in the task bar to animate in some fash ENCRYPTION - + GENERAL GENERAL - + INTERFACE INTERFACE - + Emoji Font Family Emoji Font Family - + + Share keys with trusted users + Share keys with trusted users + + + + Automatically replies to key requests from other users, if they are verified. + Automatically replies to key requests from other users, if they are verified. + + + Open Sessions File Open Sessions File @@ -1414,6 +1690,34 @@ This usually causes the application icon in the task bar to animate in some fash File to save the exported session keys + + Waiting + + + Waiting for other party + Waiting for other party… + + + + Waiting for other side to accept the verification request. + Waiting for other side to accept the verification request… + + + + Waiting for other side to continue the verification request. + Waiting for other side to continue the verification request… + + + + Waiting for other side to complete the verification request. + Waiting for other side to complete the verification request… + + + + Cancel + Cancel + + WelcomePage diff --git a/resources/langs/nheko_eo.ts b/resources/langs/nheko_eo.ts index fafddab8..4e5a9bc4 100644 --- a/resources/langs/nheko_eo.ts +++ b/resources/langs/nheko_eo.ts @@ -1,10 +1,56 @@ + + ActiveCallBar + + + Initiating... + + + + + Calling... + + + + + Connecting... + + + + + Unmute Mic + + + + + Mute Mic + + + + + AwaitingVerificationConfirmation + + + Awaiting Confirmation + + + + + Waiting for other side to complete verification. + + + + + Cancel + + + Cache - + You joined this room. Vi aliĝis ĉi tiun ĉambron. @@ -12,23 +58,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. - + Room %1 created. I believe that the -at ending is correct here. Ĉambro %1 farit. @@ -109,12 +155,12 @@ - + Failed to upload media. Please try again. - + Cache migration failed! @@ -129,7 +175,7 @@ - + Failed to restore OLM account. Please login again. @@ -145,12 +191,12 @@ - + Please try to login again: %1 - + Failed to join room: %1 @@ -210,6 +256,29 @@ + + 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 @@ -236,13 +305,13 @@ EmojiPicker - - + + Search Trovu - + People Homoj @@ -282,10 +351,33 @@ Flagoj + + 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! + + + EncryptionIndicator - + Encrypted @@ -298,25 +390,27 @@ EventStore - + -- Encrypted Event (No keys found for decryption) -- Placeholder, when the message was not decrypted yet or can't be decrypted. - + + -- 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. @@ -332,6 +426,40 @@ + + Failed + + + Verification failed + + + + + Other client does not support our verification protocol. + + + + + Key mismatch detected! + + + + + + Device verification timed out. + + + + + Other party canceled the verification. + + + + + Close + + + InviteeItem @@ -448,18 +576,18 @@ Ekzemplo: https://servisto.mia:8787 MessageDelegate - - + + redacted - + Encryption enabled - + room name changed to: %1 Nomo da ĉambro ŝanĝiĝis al: %1 @@ -469,7 +597,7 @@ Ekzemplo: https://servisto.mia:8787 - + topic changed to: %1 @@ -479,41 +607,92 @@ Ekzemplo: https://servisto.mia:8787 - + %1 created and configured room: %2 - + %1 placed a voice call. - + %1 placed a video call. - + %1 placed a call. - + %1 answered the call. - + %1 ended the call. - + Negotiating call... + + MessageInput + + + Write a message... + Skribu mesaĝon... + + + + NewVerificationRequest + + + Send Device Verification Request + + + + + Recieved Device Verification Request + + + + + To ensure that no malicious user can eavesdrop on your encrypted communications, you can verify this device. + + + + + The device was requested to be verified + + + + + Cancel + + + + + Deny + + + + + Start verification + + + + + Accept + + + Placeholder @@ -522,6 +701,24 @@ Ekzemplo: https://servisto.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. + + + + + profile + + + + + profile name + + + QuickSwitcher @@ -570,7 +767,7 @@ Ekzemplo: https://servisto.mia:8787 REGISTER - + REGISTRU @@ -598,10 +795,18 @@ Ekzemplo: https://servisto.mia:8787 + + ReplyPopup + + + Close + + + RoomInfo - + no version stored @@ -662,7 +867,7 @@ Ekzemplo: https://servisto.mia:8787 - + Accept @@ -704,31 +909,49 @@ Ekzemplo: https://servisto.mia:8787 StatusIndicator - + Failed Estas malsukcesa - + Sent Estas sendita - + Received Estas ricevita - + Read Could this simply be "lega"? Estas lega + + Success + + + Successful Verification + + + + + Verification successful! Both sides verified their devices! + + + + + Close + + + TextInputWidget - + Send a file Sendu dosieron @@ -749,7 +972,7 @@ Ekzemplo: https://servisto.mia:8787 - + Select a file @@ -759,7 +982,7 @@ Ekzemplo: https://servisto.mia:8787 Ĉiuj dosieroj (*) - + Place a call @@ -777,20 +1000,19 @@ Ekzemplo: https://servisto.mia:8787 TimelineModel - + Message redaction failed: %1 - - - + + Failed to encrypt event, sending aborted! - + Save image @@ -931,12 +1153,12 @@ Ekzemplo: https://servisto.mia:8787 - + You joined this room. Vi aliĝis ĉi tiun ĉambron. - + Rejected the knock from %1. @@ -960,17 +1182,17 @@ Ekzemplo: https://servisto.mia:8787 TimelineRow - + React Reagu - + Reply Respondu - + Options Ebloj @@ -978,63 +1200,74 @@ Ekzemplo: https://servisto.mia:8787 TimelineView - + React Reagu - + Reply Respondu - + Read receipts - + Mark as read - + View raw message - + View decrypted raw message - + Redact message - + Save as - + No room open + + + TimelineViewManager - + + No share room with this user found. Create an encrypted room with this user and try again. + + + + + TopBar + + Back to room list - + No room selected - + Room options @@ -1044,24 +1277,19 @@ Ekzemplo: https://servisto.mia:8787 - + Members - Membroj + Membroj - + Leave room - Forlasu la ĉambron + - + Settings - - - - - Close - + Agordoj @@ -1120,10 +1348,33 @@ Ekzemplo: https://servisto.mia:8787 + + UserProfile + + + Verify + + + + + Ban the user + + + + + Start a private chat + + + + + Kick the user + + + UserSettingsPage - + Minimize to tray @@ -1143,12 +1394,17 @@ Ekzemplo: https://servisto.mia:8787 - + + profile: %1 + + + + CALLS - + Keep the application running in the background after closing the client window. @@ -1286,7 +1542,17 @@ This usually causes the application icon in the task bar to animate in some fash - + + Mobile mode + + + + + Will prevent text selection in the timeline to make scrolling easier. + + + + Scale factor @@ -1331,7 +1597,7 @@ This usually causes the application icon in the task bar to animate in some fash - + Session Keys @@ -1351,22 +1617,32 @@ This usually causes the application icon in the task bar to animate in some fash - + GENERAL - + INTERFACE - + Emoji Font Family - + + Share keys with trusted users + + + + + Automatically replies to key requests from other users, if they are verified. + + + + Open Sessions File @@ -1412,6 +1688,34 @@ This usually causes the application icon in the task bar to animate in some fash + + Waiting + + + Waiting for other party + + + + + Waiting for other side to accept the verification request. + + + + + Waiting for other side to continue the verification request. + + + + + Waiting for other side to complete the verification request. + + + + + Cancel + + + WelcomePage diff --git a/resources/langs/nheko_et.ts b/resources/langs/nheko_et.ts index f3a51bb9..b1f20985 100644 --- a/resources/langs/nheko_et.ts +++ b/resources/langs/nheko_et.ts @@ -1,10 +1,56 @@ + + ActiveCallBar + + + Initiating... + + + + + Calling... + + + + + Connecting... + + + + + Unmute Mic + + + + + Mute Mic + + + + + AwaitingVerificationConfirmation + + + Awaiting Confirmation + + + + + Waiting for other side to complete verification. + + + + + Cancel + + + Cache - + You joined this room. Sa liitusid selle jututoaga. @@ -12,23 +58,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. - + Room %1 created. %1 jututuba on loodud. @@ -108,12 +154,12 @@ Suhtluskeeld eemaldatud: %1 - + Failed to upload media. Please try again. Meediafailide üleslaadimine ei õnnestunud. Palun proovi uuesti. - + Cache migration failed! Puhvri versiooniuuendus ebaõnnestus! @@ -128,7 +174,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. @@ -144,12 +190,12 @@ - + Please try to login again: %1 Palun proovi uuesti sisse logida: %1 - + Failed to join room: %1 Jututoaga liitumine ei õnnestunud: %1 @@ -209,6 +255,29 @@ (kogukond) + + 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 @@ -235,13 +304,13 @@ EmojiPicker - - + + Search Otsi - + People Inimesed @@ -281,10 +350,33 @@ Lipud + + 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! + + + EncryptionIndicator - + Encrypted Krüptitud @@ -297,25 +389,27 @@ 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) -- - + + -- 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) -- @@ -331,6 +425,40 @@ -- Sõnum verifitseerimata seadmest! -- + + Failed + + + Verification failed + + + + + Other client does not support our verification protocol. + + + + + Key mismatch detected! + + + + + + Device verification timed out. + + + + + Other party canceled the verification. + + + + + Close + Sulge + + InviteeItem @@ -447,18 +575,18 @@ Näiteks: https://server.minu:8787 MessageDelegate - - + + redacted muudetud - + Encryption enabled Krüptimine on kasutusel - + room name changed to: %1 jututoa uus nimi on: %1 @@ -468,7 +596,7 @@ Näiteks: https://server.minu:8787 eemaldas jututoa nime - + topic changed to: %1 jututoa uus teema on: %1 @@ -478,41 +606,92 @@ Näiteks: https://server.minu:8787 teema on eemaldatud - + %1 created and configured room: %2 %1 lõi ja seadistas jututoa: %2 - + %1 placed a voice call. %1 helistas. - + %1 placed a video call. %1 alustas videokõnet. - + %1 placed a call. %1 helistas. - + Negotiating call... Ühendan kõnet… - + %1 answered the call. %1 vastas kõnele. - + %1 ended the call. %1 lõpetas kõne. + + MessageInput + + + Write a message... + Kirjuta sõnum… + + + + NewVerificationRequest + + + Send Device Verification Request + + + + + Recieved Device Verification Request + + + + + To ensure that no malicious user can eavesdrop on your encrypted communications, you can verify this device. + + + + + The device was requested to be verified + + + + + Cancel + + + + + Deny + + + + + Start verification + + + + + Accept + Nõustu + + Placeholder @@ -521,6 +700,24 @@ Näiteks: https://server.minu:8787 implementeerimata sündmus: + + 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 + + + QuickSwitcher @@ -597,10 +794,18 @@ Näiteks: https://server.minu:8787 Vigane koduserveri nimi + + ReplyPopup + + + Close + Sulge + + RoomInfo - + no version stored salvestatud versiooni ei leidu @@ -660,7 +865,7 @@ Näiteks: https://server.minu:8787 Silt: - + Accept Nõustu @@ -701,30 +906,48 @@ Näiteks: https://server.minu:8787 StatusIndicator - + Failed Ebaõnnestus - + Sent Saadetud - + Received Vastuvõetud - + Read Loetud + + Success + + + Successful Verification + + + + + Verification successful! Both sides verified their devices! + + + + + Close + Sulge + + TextInputWidget - + Send a file Saada fail @@ -745,7 +968,7 @@ Näiteks: https://server.minu:8787 Emoji - + Select a file Vali fail @@ -755,7 +978,7 @@ Näiteks: https://server.minu:8787 Kõik failid (*) - + Place a call Helista @@ -773,20 +996,19 @@ 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 @@ -925,12 +1147,12 @@ Näiteks: https://server.minu:8787 %1 muutis oma koputust jututoa uksele. - + You joined this room. Sa liitusid jututoaga. - + Rejected the knock from %1. Lükkas tagasi %1 koputuse jututoa uksele. @@ -954,17 +1176,17 @@ Näiteks: https://server.minu:8787 TimelineRow - + React Reageeri - + Reply Vasta - + Options Valikud @@ -972,90 +1194,96 @@ Näiteks: https://server.minu:8787 TimelineView - + React Reageeri - + Reply Vasta - + Read receipts Lugemisteatised - + Mark as read Märgi loetuks - + View raw message Näita sõnumi lähtekoodi - + View decrypted raw message Näita sõnumi dekrüptitud lähtekoodi - + Redact message Muuda sõnumit - + Save as Salvesta kui - + No room open Ühtegi jututuba pole avatud + + + TimelineViewManager - + + No share room with this user found. Create an encrypted room with this user and try again. + + + + + TopBar + + Back to room list - Tagasi jututubade loendisse + Tagasi jututubade loendisse - + No room selected - Jututuba on valimata + Jututuba on valimata - + Room options - Jututoa valikud + Jututoa valikud Invite users - Kutsu kasutajaid + Kutsu kasutajaid - + Members - Liikmed + Liikmed - + Leave room - Lahku jututoast + Lahku jututoast - + Settings - Seadistused - - - - Close - Sulge + Seadistused @@ -1114,10 +1342,33 @@ Näiteks: https://server.minu:8787 Pole võrgus + + UserProfile + + + Verify + + + + + Ban the user + + + + + Start a private chat + + + + + Kick the user + + + UserSettingsPage - + Minimize to tray Vähenda tegumiribale @@ -1137,12 +1388,17 @@ Näiteks: https://server.minu:8787 Ümmargused tunnuspildid - + + profile: %1 + + + + CALLS KÕNED - + Keep the application running in the background after closing the client window. Peale akna sulgemist jäta rakendus taustal tööle. @@ -1288,7 +1544,17 @@ 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. - + + Mobile mode + + + + + Will prevent text selection in the timeline to make scrolling easier. + + + + Scale factor Mastaabitegur @@ -1333,7 +1599,7 @@ See tavaliselt tähendab, et rakenduse ikoon tegumiribal annab mingit sorti anim Seadme sõrmejälg - + Session Keys Sessioonivõtmed @@ -1353,22 +1619,32 @@ See tavaliselt tähendab, et rakenduse ikoon tegumiribal annab mingit sorti anim KRÜPTIMINE - + GENERAL ÜLDISED SEADISTUSED - + INTERFACE LIIDES - + Emoji Font Family Fondiperekond emojide jaoks - + + Share keys with trusted users + + + + + Automatically replies to key requests from other users, if they are verified. + + + + Open Sessions File Ava sessioonide fail @@ -1414,6 +1690,34 @@ See tavaliselt tähendab, et rakenduse ikoon tegumiribal annab mingit sorti anim Fail, kuhu salvestad eksporditavad sessiooni krüptovõtmed + + Waiting + + + Waiting for other party + + + + + Waiting for other side to accept the verification request. + + + + + Waiting for other side to continue the verification request. + + + + + Waiting for other side to complete the verification request. + + + + + Cancel + Tühista + + WelcomePage diff --git a/resources/langs/nheko_fi.ts b/resources/langs/nheko_fi.ts index 192c07b2..bc44634c 100644 --- a/resources/langs/nheko_fi.ts +++ b/resources/langs/nheko_fi.ts @@ -1,10 +1,56 @@ + + ActiveCallBar + + + Initiating... + + + + + Calling... + + + + + Connecting... + + + + + Unmute Mic + + + + + Mute Mic + + + + + AwaitingVerificationConfirmation + + + Awaiting Confirmation + + + + + Waiting for other side to complete verification. + + + + + Cancel + Peruuta + + Cache - + You joined this room. @@ -12,23 +58,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. - + Room %1 created. @@ -108,12 +154,12 @@ - + Failed to upload media. Please try again. - + Cache migration failed! @@ -128,7 +174,7 @@ - + Failed to restore OLM account. Please login again. OLM-tilin palauttaminen epäonnistui. Ole hyvä ja kirjaudu sisään uudelleen. @@ -144,12 +190,12 @@ - + Please try to login again: %1 Ole hyvä ja yritä kirjautua sisään uudelleen: %1 - + Failed to join room: %1 @@ -209,6 +255,29 @@ (community) + + 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 @@ -235,13 +304,13 @@ EmojiPicker - - + + Search - + People @@ -281,10 +350,33 @@ Liput + + 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! + + + EncryptionIndicator - + Encrypted @@ -297,25 +389,27 @@ EventStore - + -- Encrypted Event (No keys found for decryption) -- Placeholder, when the message was not decrypted yet or can't be decrypted. -- Salattu viesti (salauksen purkuavaimia ei löydetty) -- - + + -- Decryption Error (failed to retrieve megolm keys from db) -- Placeholder, when the message can't be decrypted, because the DB access failed. -- Virhe purkaessa salausta (megolm-avaimien hakeminen tietokannasta epäonnistui) -- - + + -- 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. -- Virhe purkaessa salausta (%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. -- Salattu viesti (tuntematon viestityyppi) -- @@ -331,6 +425,40 @@ + + Failed + + + Verification failed + + + + + Other client does not support our verification protocol. + + + + + Key mismatch detected! + + + + + + Device verification timed out. + + + + + Other party canceled the verification. + + + + + Close + Sulje + + InviteeItem @@ -443,18 +571,18 @@ Example: https://server.my:8787 MessageDelegate - - + + redacted - + Encryption enabled - + room name changed to: %1 @@ -464,7 +592,7 @@ Example: https://server.my:8787 - + topic changed to: %1 @@ -474,41 +602,92 @@ Example: https://server.my:8787 - + %1 created and configured room: %2 - + %1 placed a voice call. - + %1 placed a video call. - + %1 placed a call. - + %1 answered the call. - + %1 ended the call. - + Negotiating call... + + MessageInput + + + Write a message... + Kirjoita viesti… + + + + NewVerificationRequest + + + Send Device Verification Request + + + + + Recieved Device Verification Request + + + + + To ensure that no malicious user can eavesdrop on your encrypted communications, you can verify this device. + + + + + The device was requested to be verified + + + + + Cancel + Peruuta + + + + Deny + + + + + Start verification + + + + + Accept + Hyväksy + + Placeholder @@ -517,6 +696,24 @@ 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. + + + + + profile + + + + + profile name + + + QuickSwitcher @@ -593,10 +790,18 @@ Example: https://server.my:8787 Epäkelpo palvelimen nimi + + ReplyPopup + + + Close + Sulje + + RoomInfo - + no version stored ei tallennettua versiota @@ -656,7 +861,7 @@ Example: https://server.my:8787 - + Accept Hyväksy @@ -697,30 +902,48 @@ Example: https://server.my:8787 StatusIndicator - + Failed - + Sent - + Received - + Read + + Success + + + Successful Verification + + + + + Verification successful! Both sides verified their devices! + + + + + Close + Sulje + + TextInputWidget - + Send a file Lähetä tiedosto @@ -741,7 +964,7 @@ Example: https://server.my:8787 Emoji - + Select a file Valitse tiedosto @@ -751,7 +974,7 @@ Example: https://server.my:8787 Kaikki tiedostot (*) - + Place a call @@ -769,20 +992,19 @@ Example: https://server.my:8787 TimelineModel - + Message redaction failed: %1 Viestin poisto epäonnistui: %1 - - - + + Failed to encrypt event, sending aborted! - + Save image Tallenna kuva @@ -921,12 +1143,12 @@ Example: https://server.my:8787 - + You joined this room. - + Rejected the knock from %1. @@ -950,17 +1172,17 @@ Example: https://server.my:8787 TimelineRow - + React - + Reply - + Options @@ -968,63 +1190,74 @@ Example: https://server.my:8787 TimelineView - + React - + Reply - + Read receipts Lukukuittaukset - + Mark as read - + View raw message - + View decrypted raw message - + Redact message - + Save as - + No room open + + + TimelineViewManager - + + No share room with this user found. Create an encrypted room with this user and try again. + + + + + TopBar + + Back to room list - + No room selected - + Room options Huonevaihtoehdot @@ -1034,25 +1267,20 @@ Example: https://server.my:8787 Kutsu käyttäjiä - + Members Jäsenet - + Leave room Poistu huoneesta - + Settings Asetukset - - - Close - Sulje - TrayIcon @@ -1110,10 +1338,33 @@ Example: https://server.my:8787 + + UserProfile + + + Verify + + + + + Ban the user + + + + + Start a private chat + + + + + Kick the user + + + UserSettingsPage - + Minimize to tray Pienennä ilmoitusalueelle @@ -1133,12 +1384,17 @@ Example: https://server.my:8787 - + + profile: %1 + + + + CALLS - + Keep the application running in the background after closing the client window. @@ -1276,7 +1532,17 @@ This usually causes the application icon in the task bar to animate in some fash - + + Mobile mode + + + + + Will prevent text selection in the timeline to make scrolling easier. + + + + Scale factor Mittakerroin @@ -1321,7 +1587,7 @@ This usually causes the application icon in the task bar to animate in some fash Laitteen sormenjälki - + Session Keys Istunnon avaimet @@ -1341,22 +1607,32 @@ This usually causes the application icon in the task bar to animate in some fash SALAUS - + GENERAL YLEISET ASETUKSET - + INTERFACE - + Emoji Font Family - + + Share keys with trusted users + + + + + Automatically replies to key requests from other users, if they are verified. + + + + Open Sessions File Avaa Istuntoavaintiedosto @@ -1402,6 +1678,34 @@ This usually causes the application icon in the task bar to animate in some fash Tiedosto, johon viedyt istuntoavaimet tallennetaan + + Waiting + + + Waiting for other party + + + + + Waiting for other side to accept the verification request. + + + + + Waiting for other side to continue the verification request. + + + + + Waiting for other side to complete the verification request. + + + + + Cancel + Peruuta + + WelcomePage diff --git a/resources/langs/nheko_fr.ts b/resources/langs/nheko_fr.ts index 48047e32..f8f8380b 100644 --- a/resources/langs/nheko_fr.ts +++ b/resources/langs/nheko_fr.ts @@ -1,10 +1,56 @@ + + ActiveCallBar + + + Initiating... + + + + + Calling... + + + + + Connecting... + + + + + Unmute Mic + + + + + Mute Mic + + + + + AwaitingVerificationConfirmation + + + Awaiting Confirmation + + + + + Waiting for other side to complete verification. + + + + + Cancel + Annuler + + Cache - + You joined this room. Vous avez rejoint ce salon. @@ -12,23 +58,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. - + Room %1 created. Salon %1 créé. @@ -40,12 +86,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 @@ -55,7 +101,7 @@ Do you really want to kick %1 (%2)? - Voulez-vous vraiment expulser %1 (%2) ? + Voulez-vous vraiment expulser %1 (%2) ? @@ -75,7 +121,7 @@ Do you really want to ban %1 (%2)? - Voulez-vous vraiment bannir %1 (%2) ? + Voulez-vous vraiment bannir %1 (%2) ? @@ -95,12 +141,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 @@ -108,14 +154,14 @@ %1 n'est plus banni(e) - + Failed to upload media. Please try again. Échec de l'envoi du média. Veuillez réessayer. - + Cache migration failed! - Échec de la migration du cache ! + Échec de la migration du cache ! @@ -128,7 +174,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. @@ -140,18 +186,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 @@ -161,17 +207,17 @@ 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 @@ -209,6 +255,29 @@ (communauté) + + 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 @@ -235,13 +304,13 @@ EmojiPicker - - + + Search Chercher - + People Personnes @@ -281,41 +350,66 @@ Drapeaux + + 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! + + + EncryptionIndicator - + Encrypted Chiffré This message is not encrypted! - Ce message n'est pas chiffré ! + Ce message n'est pas chiffré ! 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é pour le déchiffrement) -- - + + -- 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) -- @@ -323,12 +417,46 @@ -- Replay attack! This message index was reused! -- - -- Attaque par rejeu (replay attack) ! Cet index de message a été réutilisé ! -- + -- Attaque par rejeu (replay attack) ! Cet index de message a été réutilisé ! -- -- Message by unverified device! -- - -- Message d'un appareil non vérifié  -- + -- Message d'un appareil non vérifié  -- + + + + Failed + + + Verification failed + + + + + Other client does not support our verification protocol. + + + + + Key mismatch detected! + + + + + + Device verification timed out. + + + + + Other party canceled the verification. + + + + + Close + Fermer @@ -357,9 +485,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. @@ -382,7 +510,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 @@ -447,20 +575,20 @@ Exemple : https ://monserveur.example.com :8787 MessageDelegate - - + + redacted effacé - + Encryption enabled Chiffrement activé - + room name changed to: %1 - nom du salon changé en : %1 + nom du salon changé en : %1 @@ -468,9 +596,9 @@ Exemple : https ://monserveur.example.com :8787 nom du salon retiré - + topic changed to: %1 - sujet changé pour : %1 + sujet changé pour : %1 @@ -478,47 +606,116 @@ Exemple : https ://monserveur.example.com :8787 sujet retiré - + %1 created and configured room: %2 - %1 a créé et configuré le salon : %2 + %1 a créé et configuré le salon : %2 - + %1 placed a voice call. %1 a effectué un appel vocal. - + %1 placed a video call. %1 a effectué un appel vidéo. - + %1 placed a call. %1 a appelé. - + %1 answered the call. %1 a répondu à l'appel. - + %1 ended the call. 1% a terminé l'appel. - + Negotiating call... Négociation de l'appel… + + MessageInput + + + Write a message... + Écrivez un message… + + + + NewVerificationRequest + + + Send Device Verification Request + + + + + Recieved Device Verification Request + + + + + To ensure that no malicious user can eavesdrop on your encrypted communications, you can verify this device. + + + + + The device was requested to be verified + + + + + Cancel + Annuler + + + + Deny + + + + + Start verification + + + + + Accept + Accepter + + Placeholder 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. + + + + + profile + + + + + profile name + @@ -539,7 +736,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 « . _ = - / ». @@ -574,7 +771,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 ! @@ -597,10 +794,18 @@ Exemple : https ://monserveur.example.com :8787 Le nom du serveur est invalide + + ReplyPopup + + + Close + Fermer + + RoomInfo - + no version stored pas de version enregistrée @@ -615,7 +820,7 @@ Exemple : https ://monserveur.example.com :8787 Tag room as: - Étiqueter le salon comme : + Étiqueter le salon comme : @@ -657,10 +862,10 @@ Exemple : https ://monserveur.example.com :8787 Tag: Tag name prompt - Étiquette : + Étiquette : - + Accept Accepter @@ -701,30 +906,48 @@ Exemple : https ://monserveur.example.com :8787 StatusIndicator - + Failed Échec - + Sent Envoyé - + Received Reçu - + Read Lu + + Success + + + Successful Verification + + + + + Verification successful! Both sides verified their devices! + + + + + Close + Fermer + + TextInputWidget - + Send a file Envoyer un fichier @@ -745,7 +968,7 @@ Exemple : https ://monserveur.example.com :8787 Emoji - + Select a file Sélectionnez un fichier @@ -755,7 +978,7 @@ Exemple : https ://monserveur.example.com :8787 Tous les types de fichiers (*) - + Place a call Appeler @@ -773,20 +996,19 @@ 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 @@ -925,12 +1147,12 @@ Exemple : https ://monserveur.example.com :8787 %1 ne frappe plus au salon. - + You joined this room. Vous avez rejoint ce salon. - + Rejected the knock from %1. %1 a été rejeté après avoir frappé au salon. @@ -938,12 +1160,12 @@ 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é ! Reason: %1 - Raison : %1 + Raison : %1 @@ -954,17 +1176,17 @@ Exemple : https ://monserveur.example.com :8787 TimelineRow - + React Réagir - + Reply Répondre - + Options Options @@ -972,90 +1194,96 @@ Exemple : https ://monserveur.example.com :8787 TimelineView - + React Réagir - + Reply Réponse - + Read receipts Accusés de lecture - + Mark as read Marquer comme lu - + View raw message Voir le message brut - + View decrypted raw message Voir le message déchiffré brut - + Redact message Effacer le message - + Save as Enregistrer sous - + No room open Aucun salon ouvert + + + TimelineViewManager - + + No share room with this user found. Create an encrypted room with this user and try again. + + + + + TopBar + + Back to room list - Revenir à la liste des salons + Revenir à la liste des salons - + No room selected - Pas de salon sélectionné + Pas de salon sélectionné - + Room options - Options du salon + Options du salon Invite users - Inviter des utilisateurs + Inviter des utilisateurs - + Members - Membres + Membres - + Leave room - Quitter le salon + Quitter le salon - + Settings - Paramètres - - - - Close - Fermer + Paramètres @@ -1091,7 +1319,7 @@ Exemple : https ://monserveur.example.com :8787 Status: - Statut : + Statut : @@ -1114,10 +1342,33 @@ Exemple : https ://monserveur.example.com :8787 Hors ligne + + UserProfile + + + Verify + + + + + Ban the user + + + + + Start a private chat + + + + + Kick the user + + + UserSettingsPage - + Minimize to tray Réduire à la barre des tâches @@ -1137,12 +1388,17 @@ Exemple : https ://monserveur.example.com :8787 Avatars circulaires - + + profile: %1 + + + + CALLS APPELS - + 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. @@ -1289,7 +1545,17 @@ 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. - + + Mobile mode + + + + + Will prevent text selection in the timeline to make scrolling easier. + + + + Scale factor Facteur d'échelle @@ -1334,7 +1600,7 @@ Cela met l'application en évidence dans la barre des tâches.Empreinte de l'appareil - + Session Keys Clés de session @@ -1354,22 +1620,32 @@ Cela met l'application en évidence dans la barre des tâches.CHIFFREMENT - + GENERAL GÉNÉRAL - + INTERFACE INTERFACE - + Emoji Font Family Nom de Police Emoji - + + Share keys with trusted users + + + + + Automatically replies to key requests from other users, if they are verified. + + + + Open Sessions File Ouvrir fichier de sessions @@ -1396,7 +1672,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  : @@ -1407,7 +1683,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  : @@ -1415,6 +1691,34 @@ Cela met l'application en évidence dans la barre des tâches.Fichier où sauvegarder les clés de session exportées + + Waiting + + + Waiting for other party + + + + + Waiting for other side to accept the verification request. + + + + + Waiting for other side to continue the verification request. + + + + + Waiting for other side to complete the verification request. + + + + + Cancel + Annuler + + WelcomePage @@ -1765,7 +2069,7 @@ attendant que la vérification des appareils soit opérationnelle. Failed to enable encryption: %1 - Échec de l'activation du chiffrement  : %1 + Échec de l'activation du chiffrement  : %1 @@ -1785,13 +2089,13 @@ attendant que la vérification des appareils soit opérationnelle. 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 @@ -1824,7 +2128,7 @@ attendant que la vérification des appareils soit opérationnelle. Do you really want to invite %1 (%2) to a direct chat? - Voulez-vous vraiment inviter %1 (%2) dans un chat privé  ? + Voulez-vous vraiment inviter %1 (%2) dans un chat privé  ? @@ -1940,12 +2244,12 @@ attendant que la vérification des appareils soit opérationnelle. You: %1 - Vous  : %1 + Vous  : %1 %1: %2 - %1  : %2 + %1  : %2 diff --git a/resources/langs/nheko_it.ts b/resources/langs/nheko_it.ts index 4b9c32d4..b8efa774 100644 --- a/resources/langs/nheko_it.ts +++ b/resources/langs/nheko_it.ts @@ -1,10 +1,56 @@ + + ActiveCallBar + + + Initiating... + + + + + Calling... + + + + + Connecting... + + + + + Unmute Mic + + + + + Mute Mic + + + + + AwaitingVerificationConfirmation + + + Awaiting Confirmation + + + + + Waiting for other side to complete verification. + + + + + Cancel + Annulla + + Cache - + You joined this room. Sei entrato in questa stanza. @@ -12,23 +58,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. - + Room %1 created. Stanza %1 creata. @@ -108,12 +154,12 @@ Rimosso il ban dall'utente: %1 - + Failed to upload media. Please try again. Impossibile inviare il file multimediale. Per favore riprova. - + Cache migration failed! Migrazione della cache fallita! @@ -128,7 +174,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. @@ -144,12 +190,12 @@ - + Please try to login again: %1 Per favore prova ad accedere nuovamente: %1 - + Failed to join room: %1 Impossibile accedere alla stanza: %1 @@ -209,6 +255,29 @@ (comunità) + + 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 @@ -235,13 +304,13 @@ EmojiPicker - - + + Search - + People @@ -281,10 +350,33 @@ Bandiere + + 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! + + + EncryptionIndicator - + Encrypted Criptato @@ -297,25 +389,27 @@ EventStore - + -- Encrypted Event (No keys found for decryption) -- Placeholder, when the message was not decrypted yet or can't be decrypted. -- Evento Criptato (Chiavi per la decriptazione non trovate) -- - + + -- 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) -- @@ -331,6 +425,40 @@ + + Failed + + + Verification failed + + + + + Other client does not support our verification protocol. + + + + + Key mismatch detected! + + + + + + Device verification timed out. + + + + + Other party canceled the verification. + + + + + Close + Chiudi + + InviteeItem @@ -447,18 +575,18 @@ Esempio: https://server.mio:8787 MessageDelegate - - + + redacted oscurato - + Encryption enabled Crittografia abilitata - + room name changed to: %1 nome della stanza cambiato in: %1 @@ -468,7 +596,7 @@ Esempio: https://server.mio:8787 nome della stanza rimosso - + topic changed to: %1 argomento cambiato in: %1 @@ -478,41 +606,92 @@ Esempio: https://server.mio:8787 argomento rimosso - + %1 created and configured room: %2 %1 creato e configurata stanza: %2 - + %1 placed a voice call. - + %1 placed a video call. - + %1 placed a call. - + %1 answered the call. - + %1 ended the call. - + Negotiating call... + + MessageInput + + + Write a message... + Scrivi un messaggio… + + + + NewVerificationRequest + + + Send Device Verification Request + + + + + Recieved Device Verification Request + + + + + To ensure that no malicious user can eavesdrop on your encrypted communications, you can verify this device. + + + + + The device was requested to be verified + + + + + Cancel + Annulla + + + + Deny + + + + + Start verification + + + + + Accept + Accetta + + Placeholder @@ -521,6 +700,24 @@ Esempio: https://server.mio:8787 event non implementato: + + 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 + + + QuickSwitcher @@ -597,10 +794,18 @@ Esempio: https://server.mio:8787 Nome del server non valido + + ReplyPopup + + + Close + Chiudi + + RoomInfo - + no version stored nessuna versione memorizzata @@ -660,7 +865,7 @@ Esempio: https://server.mio:8787 - + Accept Accetta @@ -701,30 +906,48 @@ Esempio: https://server.mio:8787 StatusIndicator - + Failed Fallito - + Sent Inviato - + Received Ricevuto - + Read Letto + + Success + + + Successful Verification + + + + + Verification successful! Both sides verified their devices! + + + + + Close + Chiudi + + TextInputWidget - + Send a file Invia un file @@ -745,7 +968,7 @@ Esempio: https://server.mio:8787 Emoji - + Select a file Seleziona un file @@ -755,7 +978,7 @@ Esempio: https://server.mio:8787 Tutti i file (*) - + Place a call @@ -773,20 +996,19 @@ Esempio: https://server.mio:8787 TimelineModel - + Message redaction failed: %1 Oscuramento del messaggio fallito: %1 - - - + + Failed to encrypt event, sending aborted! - + Save image Salva immagine @@ -925,12 +1147,12 @@ Esempio: https://server.mio:8787 %1 ha oscurato la sua bussata. - + You joined this room. Sei entrato in questa stanza. - + Rejected the knock from %1. Rifiutata la bussata di %1. @@ -954,17 +1176,17 @@ Esempio: https://server.mio:8787 TimelineRow - + React - + Reply Rispondi - + Options Opzioni @@ -972,63 +1194,74 @@ Esempio: https://server.mio:8787 TimelineView - + React - + Reply Risposta - + Read receipts Leggi le ricevute - + Mark as read Segna come letto - + View raw message Mostra il messaggio grezzo - + View decrypted raw message Mostra il messaggio grezzo decriptato - + Redact message Oscura messaggio - + Save as Salva come - + No room open Nessuna stanza aperta + + + TimelineViewManager - + + No share room with this user found. Create an encrypted room with this user and try again. + + + + + TopBar + + Back to room list - + No room selected - + Room options Opzioni della stanza @@ -1038,25 +1271,20 @@ Esempio: https://server.mio:8787 Invita utenti - + Members Membri - + Leave room Lascia la stanza - + Settings Impostazioni - - - Close - Chiudi - TrayIcon @@ -1114,10 +1342,33 @@ Esempio: https://server.mio:8787 + + UserProfile + + + Verify + + + + + Ban the user + + + + + Start a private chat + + + + + Kick the user + + + UserSettingsPage - + Minimize to tray Minimizza nella tray @@ -1137,12 +1388,17 @@ Esempio: https://server.mio:8787 Avatar Circolari - + + profile: %1 + + + + CALLS - + Keep the application running in the background after closing the client window. @@ -1280,7 +1536,17 @@ This usually causes the application icon in the task bar to animate in some fash - + + Mobile mode + + + + + Will prevent text selection in the timeline to make scrolling easier. + + + + Scale factor Fattore di scala @@ -1325,7 +1591,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 @@ -1345,22 +1611,32 @@ This usually causes the application icon in the task bar to animate in some fash CRITTOGRAFIA - + GENERAL GENERALE - + INTERFACE INTERFACCIA - + Emoji Font Family Famiglia dei caratteri delle Emoji - + + Share keys with trusted users + + + + + Automatically replies to key requests from other users, if they are verified. + + + + Open Sessions File Apri File delle Sessioni @@ -1406,6 +1682,34 @@ This usually causes the application icon in the task bar to animate in some fash File ove salvare le chiavi di sessione esportate + + Waiting + + + Waiting for other party + + + + + Waiting for other side to accept the verification request. + + + + + Waiting for other side to continue the verification request. + + + + + Waiting for other side to complete the verification request. + + + + + Cancel + Annulla + + WelcomePage diff --git a/resources/langs/nheko_ja.ts b/resources/langs/nheko_ja.ts index 79fdd9a4..26d6188c 100644 --- a/resources/langs/nheko_ja.ts +++ b/resources/langs/nheko_ja.ts @@ -1,10 +1,56 @@ + + ActiveCallBar + + + Initiating... + + + + + Calling... + + + + + Connecting... + + + + + Unmute Mic + + + + + Mute Mic + + + + + AwaitingVerificationConfirmation + + + Awaiting Confirmation + + + + + Waiting for other side to complete verification. + + + + + Cancel + キャンセル + + Cache - + You joined this room. @@ -12,23 +58,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. - + Room %1 created. @@ -108,12 +154,12 @@ 永久追放を解除されたユーザー: %1 - + Failed to upload media. Please try again. メディアをアップロードできませんでした。やり直して下さい。 - + Cache migration failed! @@ -128,7 +174,7 @@ - + Failed to restore OLM account. Please login again. OLMアカウントを復元できませんでした。もう一度ログインして下さい。 @@ -144,12 +190,12 @@ - + Please try to login again: %1 もう一度ログインしてみて下さい: %1 - + Failed to join room: %1 部屋に参加できませんでした: %1 @@ -209,6 +255,29 @@ (コミュニティー) + + 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 @@ -235,13 +304,13 @@ EmojiPicker - - + + Search - + People @@ -281,10 +350,33 @@ + + 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! + + + EncryptionIndicator - + Encrypted 暗号化されています @@ -297,25 +389,27 @@ EventStore - + -- Encrypted Event (No keys found for decryption) -- Placeholder, when the message was not decrypted yet or can't be decrypted. -- 暗号化イベント (復号鍵が見つかりません) -- - + + -- 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. -- 暗号化イベント (不明なイベント型です) -- @@ -331,6 +425,40 @@ + + Failed + + + Verification failed + + + + + Other client does not support our verification protocol. + + + + + Key mismatch detected! + + + + + + Device verification timed out. + + + + + Other party canceled the verification. + + + + + Close + 閉じる + + InviteeItem @@ -443,18 +571,18 @@ Example: https://server.my:8787 MessageDelegate - - + + redacted 編集済み - + Encryption enabled 暗号化が有効です - + room name changed to: %1 部屋名が変更されました: %1 @@ -464,7 +592,7 @@ Example: https://server.my:8787 部屋名が削除されました - + topic changed to: %1 話題が変更されました: %1 @@ -474,41 +602,92 @@ Example: https://server.my:8787 話題が削除されました - + %1 created and configured room: %2 - + %1 placed a voice call. - + %1 placed a video call. - + %1 placed a call. - + %1 answered the call. - + %1 ended the call. - + Negotiating call... + + MessageInput + + + Write a message... + メッセージを書く... + + + + NewVerificationRequest + + + Send Device Verification Request + + + + + Recieved Device Verification Request + + + + + To ensure that no malicious user can eavesdrop on your encrypted communications, you can verify this device. + + + + + The device was requested to be verified + + + + + Cancel + キャンセル + + + + Deny + + + + + Start verification + + + + + Accept + 容認 + + Placeholder @@ -517,6 +696,24 @@ 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. + + + + + profile + + + + + profile name + + + QuickSwitcher @@ -593,10 +790,18 @@ Example: https://server.my:8787 無効なサーバー名です + + ReplyPopup + + + Close + 閉じる + + RoomInfo - + no version stored バージョンが保存されていません @@ -656,7 +861,7 @@ Example: https://server.my:8787 - + Accept 容認 @@ -697,30 +902,48 @@ Example: https://server.my:8787 StatusIndicator - + Failed 失敗 - + Sent 送信済み - + Received 受信済み - + Read 既読 + + Success + + + Successful Verification + + + + + Verification successful! Both sides verified their devices! + + + + + Close + 閉じる + + TextInputWidget - + Send a file ファイルを送信 @@ -741,7 +964,7 @@ Example: https://server.my:8787 絵文字 - + Select a file ファイルを選択 @@ -751,7 +974,7 @@ Example: https://server.my:8787 全てのファイル (*) - + Place a call @@ -769,20 +992,19 @@ Example: https://server.my:8787 TimelineModel - + Message redaction failed: %1 メッセージを編集できませんでした: %1 - - - + + Failed to encrypt event, sending aborted! - + Save image 画像を保存 @@ -920,12 +1142,12 @@ Example: https://server.my:8787 %1がノックを編集しました。 - + You joined this room. - + Rejected the knock from %1. %1からのノックを拒否しました。 @@ -949,17 +1171,17 @@ Example: https://server.my:8787 TimelineRow - + React - + Reply 返信 - + Options オプション @@ -967,63 +1189,74 @@ Example: https://server.my:8787 TimelineView - + React - + Reply 返信 - + Read receipts 開封確認 - + Mark as read 既読にする - + View raw message ソースを見る - + View decrypted raw message - + Redact message メッセージを編集 - + Save as 名前を付けて保存 - + No room open 部屋が開いていません + + + TimelineViewManager - + + No share room with this user found. Create an encrypted room with this user and try again. + + + + + TopBar + + Back to room list - + No room selected - + Room options 部屋のオプション @@ -1033,25 +1266,20 @@ Example: https://server.my:8787 ユーザーを招待 - + Members メンバー - + Leave room 部屋を出る - + Settings 設定 - - - Close - 閉じる - TrayIcon @@ -1109,10 +1337,33 @@ Example: https://server.my:8787 + + UserProfile + + + Verify + + + + + Ban the user + + + + + Start a private chat + + + + + Kick the user + + + UserSettingsPage - + Minimize to tray トレイへ最小化 @@ -1132,12 +1383,17 @@ Example: https://server.my:8787 円形アバター - + + profile: %1 + + + + CALLS - + Keep the application running in the background after closing the client window. @@ -1275,7 +1531,17 @@ This usually causes the application icon in the task bar to animate in some fash - + + Mobile mode + + + + + Will prevent text selection in the timeline to make scrolling easier. + + + + Scale factor 尺度係数 @@ -1320,7 +1586,7 @@ This usually causes the application icon in the task bar to animate in some fash デバイスの指紋 - + Session Keys セッション鍵 @@ -1340,22 +1606,32 @@ This usually causes the application icon in the task bar to animate in some fash 暗号化 - + GENERAL 全般 - + INTERFACE - + Emoji Font Family - + + Share keys with trusted users + + + + + Automatically replies to key requests from other users, if they are verified. + + + + Open Sessions File セッションファイルを開く @@ -1401,6 +1677,34 @@ This usually causes the application icon in the task bar to animate in some fash エクスポートされたセッション鍵を保存するファイル + + Waiting + + + Waiting for other party + + + + + Waiting for other side to accept the verification request. + + + + + Waiting for other side to continue the verification request. + + + + + Waiting for other side to complete the verification request. + + + + + Cancel + キャンセル + + WelcomePage diff --git a/resources/langs/nheko_nl.ts b/resources/langs/nheko_nl.ts index e957c578..d6d91c3e 100644 --- a/resources/langs/nheko_nl.ts +++ b/resources/langs/nheko_nl.ts @@ -1,10 +1,56 @@ + + ActiveCallBar + + + Initiating... + + + + + Calling... + + + + + Connecting... + + + + + Unmute Mic + + + + + Mute Mic + + + + + AwaitingVerificationConfirmation + + + Awaiting Confirmation + + + + + Waiting for other side to complete verification. + + + + + Cancel + Annuleren + + Cache - + You joined this room. @@ -12,23 +58,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. - + Room %1 created. @@ -108,12 +154,12 @@ - + Failed to upload media. Please try again. - + Cache migration failed! @@ -128,7 +174,7 @@ - + Failed to restore OLM account. Please login again. @@ -144,12 +190,12 @@ - + Please try to login again: %1 - + Failed to join room: %1 @@ -209,6 +255,29 @@ + + 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 @@ -235,13 +304,13 @@ EmojiPicker - - + + Search - + People @@ -281,10 +350,33 @@ Vlaggen + + 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! + + + EncryptionIndicator - + Encrypted @@ -297,25 +389,27 @@ EventStore - + -- Encrypted Event (No keys found for decryption) -- Placeholder, when the message was not decrypted yet or can't be decrypted. - + + -- 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. @@ -331,6 +425,40 @@ + + Failed + + + Verification failed + + + + + Other client does not support our verification protocol. + + + + + Key mismatch detected! + + + + + + Device verification timed out. + + + + + Other party canceled the verification. + + + + + Close + + + InviteeItem @@ -443,18 +571,18 @@ Example: https://server.my:8787 MessageDelegate - - + + redacted - + Encryption enabled - + room name changed to: %1 @@ -464,7 +592,7 @@ Example: https://server.my:8787 - + topic changed to: %1 @@ -474,41 +602,92 @@ Example: https://server.my:8787 - + %1 created and configured room: %2 - + %1 placed a voice call. - + %1 placed a video call. - + %1 placed a call. - + %1 answered the call. - + %1 ended the call. - + Negotiating call... + + MessageInput + + + Write a message... + Typ een bericht... + + + + NewVerificationRequest + + + Send Device Verification Request + + + + + Recieved Device Verification Request + + + + + To ensure that no malicious user can eavesdrop on your encrypted communications, you can verify this device. + + + + + The device was requested to be verified + + + + + Cancel + Annuleren + + + + Deny + + + + + Start verification + + + + + Accept + Accepteren + + Placeholder @@ -517,6 +696,24 @@ 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. + + + + + profile + + + + + profile name + + + QuickSwitcher @@ -593,10 +790,18 @@ Example: https://server.my:8787 Ongeldige servernaam + + ReplyPopup + + + Close + + + RoomInfo - + no version stored @@ -656,7 +861,7 @@ Example: https://server.my:8787 - + Accept Accepteren @@ -697,30 +902,48 @@ Example: https://server.my:8787 StatusIndicator - + Failed - + Sent - + Received - + Read + + Success + + + Successful Verification + + + + + Verification successful! Both sides verified their devices! + + + + + Close + + + TextInputWidget - + Send a file @@ -741,7 +964,7 @@ Example: https://server.my:8787 - + Select a file Kies een bestand @@ -751,7 +974,7 @@ Example: https://server.my:8787 Alle bestanden (*) - + Place a call @@ -769,20 +992,19 @@ Example: https://server.my:8787 TimelineModel - + Message redaction failed: %1 - - - + + Failed to encrypt event, sending aborted! - + Save image Afbeelding opslaan @@ -921,12 +1143,12 @@ Example: https://server.my:8787 - + You joined this room. - + Rejected the knock from %1. @@ -950,17 +1172,17 @@ Example: https://server.my:8787 TimelineRow - + React - + Reply - + Options @@ -968,63 +1190,74 @@ Example: https://server.my:8787 TimelineView - + React - + Reply - + Read receipts Leesbevestigingen - + Mark as read - + View raw message - + View decrypted raw message - + Redact message - + Save as - + No room open + + + TimelineViewManager - + + No share room with this user found. Create an encrypted room with this user and try again. + + + + + TopBar + + Back to room list - + No room selected - + Room options @@ -1034,25 +1267,20 @@ Example: https://server.my:8787 Gebruikers uitnodigen - + Members Leden - + Leave room Kamer verlaten - + Settings Instellingen - - - Close - - TrayIcon @@ -1110,10 +1338,33 @@ Example: https://server.my:8787 + + UserProfile + + + Verify + + + + + Ban the user + + + + + Start a private chat + + + + + Kick the user + + + UserSettingsPage - + Minimize to tray Minimaliseren naar systeemvak @@ -1133,12 +1384,17 @@ Example: https://server.my:8787 - + + profile: %1 + + + + CALLS - + Keep the application running in the background after closing the client window. @@ -1276,7 +1532,17 @@ This usually causes the application icon in the task bar to animate in some fash - + + Mobile mode + + + + + Will prevent text selection in the timeline to make scrolling easier. + + + + Scale factor @@ -1321,7 +1587,7 @@ This usually causes the application icon in the task bar to animate in some fash - + Session Keys @@ -1341,22 +1607,32 @@ This usually causes the application icon in the task bar to animate in some fash - + GENERAL ALGEMEEN - + INTERFACE - + Emoji Font Family - + + Share keys with trusted users + + + + + Automatically replies to key requests from other users, if they are verified. + + + + Open Sessions File @@ -1402,6 +1678,34 @@ This usually causes the application icon in the task bar to animate in some fash + + Waiting + + + Waiting for other party + + + + + Waiting for other side to accept the verification request. + + + + + Waiting for other side to continue the verification request. + + + + + Waiting for other side to complete the verification request. + + + + + Cancel + Annuleren + + WelcomePage diff --git a/resources/langs/nheko_pl.ts b/resources/langs/nheko_pl.ts index fd4657d7..2479268e 100644 --- a/resources/langs/nheko_pl.ts +++ b/resources/langs/nheko_pl.ts @@ -1,10 +1,56 @@ + + ActiveCallBar + + + Initiating... + + + + + Calling... + + + + + Connecting... + + + + + Unmute Mic + + + + + Mute Mic + + + + + AwaitingVerificationConfirmation + + + Awaiting Confirmation + + + + + Waiting for other side to complete verification. + + + + + Cancel + Anuluj + + Cache - + You joined this room. @@ -12,23 +58,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. - + Room %1 created. @@ -108,12 +154,12 @@ - + Failed to upload media. Please try again. - + Cache migration failed! @@ -128,7 +174,7 @@ - + Failed to restore OLM account. Please login again. Nie udało się przywrócić konta OLM. Spróbuj zalogować się ponownie. @@ -144,12 +190,12 @@ - + Please try to login again: %1 Spróbuj zalogować się ponownie: %1 - + Failed to join room: %1 @@ -209,6 +255,29 @@ + + 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 @@ -235,13 +304,13 @@ EmojiPicker - - + + Search - + People @@ -281,10 +350,33 @@ Flagi + + 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! + + + EncryptionIndicator - + Encrypted @@ -297,25 +389,27 @@ EventStore - + -- Encrypted Event (No keys found for decryption) -- Placeholder, when the message was not decrypted yet or can't be decrypted. - + + -- 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. @@ -331,6 +425,40 @@ + + Failed + + + Verification failed + + + + + Other client does not support our verification protocol. + + + + + Key mismatch detected! + + + + + + Device verification timed out. + + + + + Other party canceled the verification. + + + + + Close + + + InviteeItem @@ -443,18 +571,18 @@ Example: https://server.my:8787 MessageDelegate - - + + redacted - + Encryption enabled - + room name changed to: %1 @@ -464,7 +592,7 @@ Example: https://server.my:8787 - + topic changed to: %1 @@ -474,41 +602,92 @@ Example: https://server.my:8787 - + %1 created and configured room: %2 - + %1 placed a voice call. - + %1 placed a video call. - + %1 placed a call. - + %1 answered the call. - + %1 ended the call. - + Negotiating call... + + MessageInput + + + Write a message... + Napisz wiadomość… + + + + NewVerificationRequest + + + Send Device Verification Request + + + + + Recieved Device Verification Request + + + + + To ensure that no malicious user can eavesdrop on your encrypted communications, you can verify this device. + + + + + The device was requested to be verified + + + + + Cancel + Anuluj + + + + Deny + + + + + Start verification + + + + + Accept + Akceptuj + + Placeholder @@ -517,6 +696,24 @@ 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. + + + + + profile + + + + + profile name + + + QuickSwitcher @@ -593,10 +790,18 @@ Example: https://server.my:8787 Nieprawidłowa nazwa serwera + + ReplyPopup + + + Close + + + RoomInfo - + no version stored @@ -656,7 +861,7 @@ Example: https://server.my:8787 - + Accept Akceptuj @@ -697,30 +902,48 @@ Example: https://server.my:8787 StatusIndicator - + Failed - + Sent - + Received - + Read + + Success + + + Successful Verification + + + + + Verification successful! Both sides verified their devices! + + + + + Close + + + TextInputWidget - + Send a file Wyślij plik @@ -741,7 +964,7 @@ Example: https://server.my:8787 Emoji - + Select a file Wybierz plik @@ -751,7 +974,7 @@ Example: https://server.my:8787 Wszystkie pliki (*) - + Place a call @@ -769,20 +992,19 @@ Example: https://server.my:8787 TimelineModel - + Message redaction failed: %1 Redagowanie wiadomości nie powiodło się: %1 - - - + + Failed to encrypt event, sending aborted! - + Save image Zapisz obraz @@ -922,12 +1144,12 @@ Example: https://server.my:8787 - + You joined this room. - + Rejected the knock from %1. @@ -951,17 +1173,17 @@ Example: https://server.my:8787 TimelineRow - + React - + Reply - + Options @@ -969,63 +1191,74 @@ Example: https://server.my:8787 TimelineView - + React - + Reply - + Read receipts Potwierdzenia przeczytania - + Mark as read - + View raw message - + View decrypted raw message - + Redact message - + Save as - + No room open + + + TimelineViewManager - + + No share room with this user found. Create an encrypted room with this user and try again. + + + + + TopBar + + Back to room list - + No room selected - + Room options Ustawienia pokoju @@ -1035,25 +1268,20 @@ Example: https://server.my:8787 Zaproś użytkowników - + Members Członkowie - + Leave room Opuść pokój - + Settings Ustawienia - - - Close - - TrayIcon @@ -1111,10 +1339,33 @@ Example: https://server.my:8787 + + UserProfile + + + Verify + + + + + Ban the user + + + + + Start a private chat + + + + + Kick the user + + + UserSettingsPage - + Minimize to tray Zminimalizuj do paska zadań @@ -1134,12 +1385,17 @@ Example: https://server.my:8787 - + + profile: %1 + + + + CALLS - + Keep the application running in the background after closing the client window. @@ -1277,7 +1533,17 @@ This usually causes the application icon in the task bar to animate in some fash - + + Mobile mode + + + + + Will prevent text selection in the timeline to make scrolling easier. + + + + Scale factor @@ -1322,7 +1588,7 @@ This usually causes the application icon in the task bar to animate in some fash Odcisk palca urządzenia - + Session Keys @@ -1342,22 +1608,32 @@ This usually causes the application icon in the task bar to animate in some fash SZYFROWANIE - + GENERAL OGÓLNE - + INTERFACE - + Emoji Font Family - + + Share keys with trusted users + + + + + Automatically replies to key requests from other users, if they are verified. + + + + Open Sessions File @@ -1403,6 +1679,34 @@ This usually causes the application icon in the task bar to animate in some fash + + Waiting + + + Waiting for other party + + + + + Waiting for other side to accept the verification request. + + + + + Waiting for other side to continue the verification request. + + + + + Waiting for other side to complete the verification request. + + + + + Cancel + Anuluj + + WelcomePage diff --git a/resources/langs/nheko_pt_PT.ts b/resources/langs/nheko_pt_PT.ts index 66e07bb4..70921b70 100644 --- a/resources/langs/nheko_pt_PT.ts +++ b/resources/langs/nheko_pt_PT.ts @@ -1,10 +1,56 @@ + + ActiveCallBar + + + Initiating... + + + + + Calling... + + + + + Connecting... + + + + + Unmute Mic + + + + + Mute Mic + + + + + AwaitingVerificationConfirmation + + + Awaiting Confirmation + + + + + Waiting for other side to complete verification. + + + + + Cancel + + + Cache - + You joined this room. @@ -12,23 +58,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. - + Room %1 created. @@ -108,12 +154,12 @@ - + Failed to upload media. Please try again. - + Cache migration failed! @@ -128,7 +174,7 @@ - + Failed to restore OLM account. Please login again. @@ -144,12 +190,12 @@ - + Please try to login again: %1 - + Failed to join room: %1 @@ -209,6 +255,29 @@ + + 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 @@ -235,13 +304,13 @@ EmojiPicker - - + + Search - + People @@ -281,10 +350,33 @@ + + 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! + + + EncryptionIndicator - + Encrypted @@ -297,25 +389,27 @@ EventStore - + -- Encrypted Event (No keys found for decryption) -- Placeholder, when the message was not decrypted yet or can't be decrypted. - + + -- 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. @@ -331,6 +425,40 @@ + + Failed + + + Verification failed + + + + + Other client does not support our verification protocol. + + + + + Key mismatch detected! + + + + + + Device verification timed out. + + + + + Other party canceled the verification. + + + + + Close + + + InviteeItem @@ -443,18 +571,18 @@ Example: https://server.my:8787 MessageDelegate - - + + redacted - + Encryption enabled - + room name changed to: %1 @@ -464,7 +592,7 @@ Example: https://server.my:8787 - + topic changed to: %1 @@ -474,41 +602,92 @@ Example: https://server.my:8787 - + %1 created and configured room: %2 - + %1 placed a voice call. - + %1 placed a video call. - + %1 placed a call. - + Negotiating call... - + %1 answered the call. - + %1 ended the call. + + MessageInput + + + Write a message... + + + + + NewVerificationRequest + + + Send Device Verification Request + + + + + Recieved Device Verification Request + + + + + To ensure that no malicious user can eavesdrop on your encrypted communications, you can verify this device. + + + + + The device was requested to be verified + + + + + Cancel + + + + + Deny + + + + + Start verification + + + + + Accept + + + Placeholder @@ -517,6 +696,24 @@ 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. + + + + + profile + + + + + profile name + + + QuickSwitcher @@ -593,10 +790,18 @@ Example: https://server.my:8787 + + ReplyPopup + + + Close + + + RoomInfo - + no version stored @@ -656,7 +861,7 @@ Example: https://server.my:8787 - + Accept @@ -697,30 +902,48 @@ Example: https://server.my:8787 StatusIndicator - + Failed - + Sent - + Received - + Read + + Success + + + Successful Verification + + + + + Verification successful! Both sides verified their devices! + + + + + Close + + + TextInputWidget - + Send a file @@ -741,7 +964,7 @@ Example: https://server.my:8787 - + Select a file @@ -751,7 +974,7 @@ Example: https://server.my:8787 - + Place a call @@ -769,20 +992,19 @@ Example: https://server.my:8787 TimelineModel - + Message redaction failed: %1 - - - + + Failed to encrypt event, sending aborted! - + Save image @@ -921,12 +1143,12 @@ Example: https://server.my:8787 - + You joined this room. - + Rejected the knock from %1. @@ -950,17 +1172,17 @@ Example: https://server.my:8787 TimelineRow - + React - + Reply - + Options @@ -968,63 +1190,74 @@ Example: https://server.my:8787 TimelineView - + React - + Reply - + Read receipts - + Mark as read - + View raw message - + View decrypted raw message - + Redact message - + Save as - + No room open + + + TimelineViewManager - + + No share room with this user found. Create an encrypted room with this user and try again. + + + + + TopBar + + Back to room list - + No room selected - + Room options @@ -1034,25 +1267,20 @@ Example: https://server.my:8787 - + Members - + Leave room - + Settings - - - Close - - TrayIcon @@ -1110,10 +1338,33 @@ Example: https://server.my:8787 + + UserProfile + + + Verify + + + + + Ban the user + + + + + Start a private chat + + + + + Kick the user + + + UserSettingsPage - + Minimize to tray @@ -1133,12 +1384,17 @@ Example: https://server.my:8787 - + + profile: %1 + + + + CALLS - + Keep the application running in the background after closing the client window. @@ -1276,7 +1532,17 @@ This usually causes the application icon in the task bar to animate in some fash - + + Mobile mode + + + + + Will prevent text selection in the timeline to make scrolling easier. + + + + Scale factor @@ -1321,7 +1587,7 @@ This usually causes the application icon in the task bar to animate in some fash - + Session Keys @@ -1341,22 +1607,32 @@ This usually causes the application icon in the task bar to animate in some fash - + GENERAL - + INTERFACE - + Emoji Font Family - + + Share keys with trusted users + + + + + Automatically replies to key requests from other users, if they are verified. + + + + Open Sessions File @@ -1402,6 +1678,34 @@ This usually causes the application icon in the task bar to animate in some fash + + Waiting + + + Waiting for other party + + + + + Waiting for other side to accept the verification request. + + + + + Waiting for other side to continue the verification request. + + + + + Waiting for other side to complete the verification request. + + + + + Cancel + + + WelcomePage diff --git a/resources/langs/nheko_ro.ts b/resources/langs/nheko_ro.ts index 9468bb28..3ee30c1f 100644 --- a/resources/langs/nheko_ro.ts +++ b/resources/langs/nheko_ro.ts @@ -1,10 +1,56 @@ + + ActiveCallBar + + + Initiating... + + + + + Calling... + + + + + Connecting... + + + + + Unmute Mic + + + + + Mute Mic + + + + + AwaitingVerificationConfirmation + + + Awaiting Confirmation + + + + + Waiting for other side to complete verification. + + + + + Cancel + + + Cache - + You joined this room. @@ -12,23 +58,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. - + Room %1 created. @@ -108,12 +154,12 @@ - + Failed to upload media. Please try again. - + Cache migration failed! @@ -128,7 +174,7 @@ - + Failed to restore OLM account. Please login again. @@ -144,12 +190,12 @@ - + Please try to login again: %1 - + Failed to join room: %1 @@ -209,6 +255,29 @@ + + 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 @@ -235,13 +304,13 @@ EmojiPicker - - + + Search - + People @@ -281,10 +350,33 @@ + + 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! + + + EncryptionIndicator - + Encrypted @@ -297,25 +389,27 @@ EventStore - + -- Encrypted Event (No keys found for decryption) -- Placeholder, when the message was not decrypted yet or can't be decrypted. - + + -- 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. @@ -331,6 +425,40 @@ + + Failed + + + Verification failed + + + + + Other client does not support our verification protocol. + + + + + Key mismatch detected! + + + + + + Device verification timed out. + + + + + Other party canceled the verification. + + + + + Close + + + InviteeItem @@ -443,18 +571,18 @@ Example: https://server.my:8787 MessageDelegate - - + + redacted - + Encryption enabled - + room name changed to: %1 @@ -464,7 +592,7 @@ Example: https://server.my:8787 - + topic changed to: %1 @@ -474,41 +602,92 @@ Example: https://server.my:8787 - + %1 created and configured room: %2 - + %1 placed a voice call. - + %1 placed a video call. - + %1 placed a call. - + %1 answered the call. - + %1 ended the call. - + Negotiating call... + + MessageInput + + + Write a message... + + + + + NewVerificationRequest + + + Send Device Verification Request + + + + + Recieved Device Verification Request + + + + + To ensure that no malicious user can eavesdrop on your encrypted communications, you can verify this device. + + + + + The device was requested to be verified + + + + + Cancel + + + + + Deny + + + + + Start verification + + + + + Accept + + + Placeholder @@ -517,6 +696,24 @@ 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. + + + + + profile + + + + + profile name + + + QuickSwitcher @@ -593,10 +790,18 @@ Example: https://server.my:8787 + + ReplyPopup + + + Close + + + RoomInfo - + no version stored @@ -656,7 +861,7 @@ Example: https://server.my:8787 - + Accept @@ -697,30 +902,48 @@ Example: https://server.my:8787 StatusIndicator - + Failed - + Sent - + Received - + Read + + Success + + + Successful Verification + + + + + Verification successful! Both sides verified their devices! + + + + + Close + + + TextInputWidget - + Send a file @@ -741,7 +964,7 @@ Example: https://server.my:8787 - + Select a file @@ -751,7 +974,7 @@ Example: https://server.my:8787 - + Place a call @@ -769,20 +992,19 @@ Example: https://server.my:8787 TimelineModel - + Message redaction failed: %1 - - - + + Failed to encrypt event, sending aborted! - + Save image @@ -922,12 +1144,12 @@ Example: https://server.my:8787 - + You joined this room. - + Rejected the knock from %1. @@ -951,17 +1173,17 @@ Example: https://server.my:8787 TimelineRow - + React - + Reply - + Options @@ -969,63 +1191,74 @@ Example: https://server.my:8787 TimelineView - + React - + Reply - + Read receipts - + Mark as read - + View raw message - + View decrypted raw message - + Redact message - + Save as - + No room open + + + TimelineViewManager - + + No share room with this user found. Create an encrypted room with this user and try again. + + + + + TopBar + + Back to room list - + No room selected - + Room options @@ -1035,25 +1268,20 @@ Example: https://server.my:8787 - + Members - + Leave room - + Settings - - - Close - - TrayIcon @@ -1111,10 +1339,33 @@ Example: https://server.my:8787 + + UserProfile + + + Verify + + + + + Ban the user + + + + + Start a private chat + + + + + Kick the user + + + UserSettingsPage - + Minimize to tray @@ -1134,12 +1385,17 @@ Example: https://server.my:8787 - + + profile: %1 + + + + CALLS - + Keep the application running in the background after closing the client window. @@ -1277,7 +1533,17 @@ This usually causes the application icon in the task bar to animate in some fash - + + Mobile mode + + + + + Will prevent text selection in the timeline to make scrolling easier. + + + + Scale factor @@ -1322,7 +1588,7 @@ This usually causes the application icon in the task bar to animate in some fash - + Session Keys @@ -1342,22 +1608,32 @@ This usually causes the application icon in the task bar to animate in some fash - + GENERAL - + INTERFACE - + Emoji Font Family - + + Share keys with trusted users + + + + + Automatically replies to key requests from other users, if they are verified. + + + + Open Sessions File @@ -1403,6 +1679,34 @@ This usually causes the application icon in the task bar to animate in some fash + + Waiting + + + Waiting for other party + + + + + Waiting for other side to accept the verification request. + + + + + Waiting for other side to continue the verification request. + + + + + Waiting for other side to complete the verification request. + + + + + Cancel + + + WelcomePage diff --git a/resources/langs/nheko_ru.ts b/resources/langs/nheko_ru.ts index 95cef767..bc349646 100644 --- a/resources/langs/nheko_ru.ts +++ b/resources/langs/nheko_ru.ts @@ -1,10 +1,56 @@ + + ActiveCallBar + + + Initiating... + + + + + Calling... + + + + + Connecting... + + + + + Unmute Mic + + + + + Mute Mic + + + + + AwaitingVerificationConfirmation + + + Awaiting Confirmation + + + + + Waiting for other side to complete verification. + + + + + Cancel + + + Cache - + You joined this room. @@ -12,23 +58,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. - + Room %1 created. @@ -108,12 +154,12 @@ - + Failed to upload media. Please try again. - + Cache migration failed! @@ -128,7 +174,7 @@ - + Failed to restore OLM account. Please login again. Не удалось восстановить учетную запись OLM. Пожалуйста, войдите снова. @@ -144,12 +190,12 @@ - + Please try to login again: %1 Повторите попытку входа: %1 - + Failed to join room: %1 @@ -209,6 +255,29 @@ (сообщество) + + 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 @@ -235,13 +304,13 @@ EmojiPicker - - + + Search - + People @@ -281,10 +350,33 @@ + + 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! + + + EncryptionIndicator - + Encrypted @@ -297,25 +389,27 @@ EventStore - + -- Encrypted Event (No keys found for decryption) -- Placeholder, when the message was not decrypted yet or can't be decrypted. - + + -- 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. @@ -331,6 +425,40 @@ + + Failed + + + Verification failed + + + + + Other client does not support our verification protocol. + + + + + Key mismatch detected! + + + + + + Device verification timed out. + + + + + Other party canceled the verification. + + + + + Close + Закрыть + + InviteeItem @@ -443,18 +571,18 @@ Example: https://server.my:8787 MessageDelegate - - + + redacted - + Encryption enabled - + room name changed to: %1 @@ -464,7 +592,7 @@ Example: https://server.my:8787 - + topic changed to: %1 @@ -474,41 +602,92 @@ Example: https://server.my:8787 - + %1 created and configured room: %2 - + %1 placed a voice call. - + %1 placed a video call. - + %1 placed a call. - + %1 answered the call. - + %1 ended the call. - + Negotiating call... + + MessageInput + + + Write a message... + Написать сообщение... + + + + NewVerificationRequest + + + Send Device Verification Request + + + + + Recieved Device Verification Request + + + + + To ensure that no malicious user can eavesdrop on your encrypted communications, you can verify this device. + + + + + The device was requested to be verified + + + + + Cancel + + + + + Deny + + + + + Start verification + + + + + Accept + Принять + + Placeholder @@ -517,6 +696,24 @@ 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. + + + + + profile + + + + + profile name + + + QuickSwitcher @@ -593,10 +790,18 @@ Example: https://server.my:8787 Неверное имя сервера + + ReplyPopup + + + Close + Закрыть + + RoomInfo - + no version stored @@ -656,7 +861,7 @@ Example: https://server.my:8787 - + Accept Принять @@ -697,30 +902,48 @@ Example: https://server.my:8787 StatusIndicator - + Failed - + Sent - + Received - + Read + + Success + + + Successful Verification + + + + + Verification successful! Both sides verified their devices! + + + + + Close + Закрыть + + TextInputWidget - + Send a file Отправить файл @@ -741,7 +964,7 @@ Example: https://server.my:8787 - + Select a file Выберите файл @@ -751,7 +974,7 @@ Example: https://server.my:8787 Все файлы (*) - + Place a call @@ -769,20 +992,19 @@ Example: https://server.my:8787 TimelineModel - + Message redaction failed: %1 Ошибка редактирования сообщения: %1 - - - + + Failed to encrypt event, sending aborted! - + Save image Сохранить изображение @@ -922,12 +1144,12 @@ Example: https://server.my:8787 - + You joined this room. - + Rejected the knock from %1. @@ -951,17 +1173,17 @@ Example: https://server.my:8787 TimelineRow - + React - + Reply - + Options @@ -969,63 +1191,74 @@ Example: https://server.my:8787 TimelineView - + React - + Reply - + Read receipts Подтверждать прочтение - + Mark as read - + View raw message - + View decrypted raw message - + Redact message - + Save as - + No room open + + + TimelineViewManager - + + No share room with this user found. Create an encrypted room with this user and try again. + + + + + TopBar + + Back to room list - + No room selected - + Room options Настройки комнаты @@ -1035,25 +1268,20 @@ Example: https://server.my:8787 Пригласить пользователей - + Members Участники - + Leave room Покинуть комнату - + Settings Настройки - - - Close - Закрыть - TrayIcon @@ -1111,10 +1339,33 @@ Example: https://server.my:8787 + + UserProfile + + + Verify + + + + + Ban the user + + + + + Start a private chat + + + + + Kick the user + + + UserSettingsPage - + Minimize to tray Сворачивать в системную панель @@ -1134,12 +1385,17 @@ Example: https://server.my:8787 - + + profile: %1 + + + + CALLS - + Keep the application running in the background after closing the client window. @@ -1277,7 +1533,17 @@ This usually causes the application icon in the task bar to animate in some fash - + + Mobile mode + + + + + Will prevent text selection in the timeline to make scrolling easier. + + + + Scale factor Масштаб @@ -1322,7 +1588,7 @@ This usually causes the application icon in the task bar to animate in some fash Отпечаток устройства - + Session Keys Ключи сеанса @@ -1342,22 +1608,32 @@ This usually causes the application icon in the task bar to animate in some fash ШИФРОВАНИЕ - + GENERAL ГЛАВНОЕ - + INTERFACE - + Emoji Font Family - + + Share keys with trusted users + + + + + Automatically replies to key requests from other users, if they are verified. + + + + Open Sessions File Открыть файл сеансов @@ -1404,6 +1680,34 @@ This usually causes the application icon in the task bar to animate in some fash Файл для сохранения экспортированных ключей сеанса + + Waiting + + + Waiting for other party + + + + + Waiting for other side to accept the verification request. + + + + + Waiting for other side to continue the verification request. + + + + + Waiting for other side to complete the verification request. + + + + + Cancel + + + WelcomePage diff --git a/resources/langs/nheko_si.ts b/resources/langs/nheko_si.ts index d490b4d1..c63e58e3 100644 --- a/resources/langs/nheko_si.ts +++ b/resources/langs/nheko_si.ts @@ -1,10 +1,56 @@ + + ActiveCallBar + + + Initiating... + + + + + Calling... + + + + + Connecting... + + + + + Unmute Mic + + + + + Mute Mic + + + + + AwaitingVerificationConfirmation + + + Awaiting Confirmation + + + + + Waiting for other side to complete verification. + + + + + Cancel + + + Cache - + You joined this room. @@ -12,23 +58,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. - + Room %1 created. @@ -108,12 +154,12 @@ - + Failed to upload media. Please try again. - + Cache migration failed! @@ -128,7 +174,7 @@ - + Failed to restore OLM account. Please login again. @@ -144,12 +190,12 @@ - + Please try to login again: %1 - + Failed to join room: %1 @@ -209,6 +255,29 @@ + + 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 @@ -235,13 +304,13 @@ EmojiPicker - - + + Search - + People @@ -281,10 +350,33 @@ + + 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! + + + EncryptionIndicator - + Encrypted @@ -297,25 +389,27 @@ EventStore - + -- Encrypted Event (No keys found for decryption) -- Placeholder, when the message was not decrypted yet or can't be decrypted. - + + -- 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. @@ -331,6 +425,40 @@ + + Failed + + + Verification failed + + + + + Other client does not support our verification protocol. + + + + + Key mismatch detected! + + + + + + Device verification timed out. + + + + + Other party canceled the verification. + + + + + Close + + + InviteeItem @@ -443,18 +571,18 @@ Example: https://server.my:8787 MessageDelegate - - + + redacted - + Encryption enabled - + room name changed to: %1 @@ -464,7 +592,7 @@ Example: https://server.my:8787 - + topic changed to: %1 @@ -474,41 +602,92 @@ Example: https://server.my:8787 - + %1 created and configured room: %2 - + %1 placed a voice call. - + %1 placed a video call. - + %1 placed a call. - + %1 answered the call. - + %1 ended the call. - + Negotiating call... + + MessageInput + + + Write a message... + + + + + NewVerificationRequest + + + Send Device Verification Request + + + + + Recieved Device Verification Request + + + + + To ensure that no malicious user can eavesdrop on your encrypted communications, you can verify this device. + + + + + The device was requested to be verified + + + + + Cancel + + + + + Deny + + + + + Start verification + + + + + Accept + + + Placeholder @@ -517,6 +696,24 @@ 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. + + + + + profile + + + + + profile name + + + QuickSwitcher @@ -593,10 +790,18 @@ Example: https://server.my:8787 + + ReplyPopup + + + Close + + + RoomInfo - + no version stored @@ -656,7 +861,7 @@ Example: https://server.my:8787 - + Accept @@ -697,30 +902,48 @@ Example: https://server.my:8787 StatusIndicator - + Failed - + Sent - + Received - + Read + + Success + + + Successful Verification + + + + + Verification successful! Both sides verified their devices! + + + + + Close + + + TextInputWidget - + Send a file @@ -741,7 +964,7 @@ Example: https://server.my:8787 - + Select a file @@ -751,7 +974,7 @@ Example: https://server.my:8787 - + Place a call @@ -769,20 +992,19 @@ Example: https://server.my:8787 TimelineModel - + Message redaction failed: %1 - - - + + Failed to encrypt event, sending aborted! - + Save image @@ -921,12 +1143,12 @@ Example: https://server.my:8787 - + You joined this room. - + Rejected the knock from %1. @@ -950,17 +1172,17 @@ Example: https://server.my:8787 TimelineRow - + React - + Reply - + Options @@ -968,63 +1190,74 @@ Example: https://server.my:8787 TimelineView - + React - + Reply - + Read receipts - + Mark as read - + View raw message - + View decrypted raw message - + Redact message - + Save as - + No room open + + + TimelineViewManager - + + No share room with this user found. Create an encrypted room with this user and try again. + + + + + TopBar + + Back to room list - + No room selected - + Room options @@ -1034,25 +1267,20 @@ Example: https://server.my:8787 - + Members - + Leave room - + Settings - - - Close - - TrayIcon @@ -1110,10 +1338,33 @@ Example: https://server.my:8787 + + UserProfile + + + Verify + + + + + Ban the user + + + + + Start a private chat + + + + + Kick the user + + + UserSettingsPage - + Minimize to tray @@ -1133,12 +1384,17 @@ Example: https://server.my:8787 - + + profile: %1 + + + + CALLS - + Keep the application running in the background after closing the client window. @@ -1276,7 +1532,17 @@ This usually causes the application icon in the task bar to animate in some fash - + + Mobile mode + + + + + Will prevent text selection in the timeline to make scrolling easier. + + + + Scale factor @@ -1321,7 +1587,7 @@ This usually causes the application icon in the task bar to animate in some fash - + Session Keys @@ -1341,22 +1607,32 @@ This usually causes the application icon in the task bar to animate in some fash - + GENERAL - + INTERFACE - + Emoji Font Family - + + Share keys with trusted users + + + + + Automatically replies to key requests from other users, if they are verified. + + + + Open Sessions File @@ -1402,6 +1678,34 @@ This usually causes the application icon in the task bar to animate in some fash + + Waiting + + + Waiting for other party + + + + + Waiting for other side to accept the verification request. + + + + + Waiting for other side to continue the verification request. + + + + + Waiting for other side to complete the verification request. + + + + + Cancel + + + WelcomePage diff --git a/resources/langs/nheko_zh_CN.ts b/resources/langs/nheko_zh_CN.ts index e4927ff6..eee53d95 100644 --- a/resources/langs/nheko_zh_CN.ts +++ b/resources/langs/nheko_zh_CN.ts @@ -1,10 +1,56 @@ + + ActiveCallBar + + + Initiating... + + + + + Calling... + + + + + Connecting... + + + + + Unmute Mic + + + + + Mute Mic + + + + + AwaitingVerificationConfirmation + + + Awaiting Confirmation + + + + + Waiting for other side to complete verification. + + + + + Cancel + 取消 + + Cache - + You joined this room. @@ -12,23 +58,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. - + Room %1 created. @@ -108,12 +154,12 @@ - + Failed to upload media. Please try again. - + Cache migration failed! @@ -128,7 +174,7 @@ - + Failed to restore OLM account. Please login again. 恢复 OLM 账户失败。请重新登录。 @@ -144,12 +190,12 @@ - + Please try to login again: %1 请尝试再次登录:%1 - + Failed to join room: %1 @@ -209,6 +255,29 @@ + + 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 @@ -235,13 +304,13 @@ EmojiPicker - - + + Search - + People @@ -281,10 +350,33 @@ 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! + + + EncryptionIndicator - + Encrypted @@ -297,25 +389,27 @@ EventStore - + -- Encrypted Event (No keys found for decryption) -- Placeholder, when the message was not decrypted yet or can't be decrypted. - + + -- 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. @@ -331,6 +425,40 @@ + + Failed + + + Verification failed + + + + + Other client does not support our verification protocol. + + + + + Key mismatch detected! + + + + + + Device verification timed out. + + + + + Other party canceled the verification. + + + + + Close + + + InviteeItem @@ -443,18 +571,18 @@ Example: https://server.my:8787 MessageDelegate - - + + redacted - + Encryption enabled - + room name changed to: %1 @@ -464,7 +592,7 @@ Example: https://server.my:8787 - + topic changed to: %1 @@ -474,41 +602,92 @@ Example: https://server.my:8787 - + %1 created and configured room: %2 - + %1 placed a voice call. - + %1 placed a video call. - + %1 placed a call. - + %1 answered the call. - + %1 ended the call. - + Negotiating call... + + MessageInput + + + Write a message... + 写一条消息... + + + + NewVerificationRequest + + + Send Device Verification Request + + + + + Recieved Device Verification Request + + + + + To ensure that no malicious user can eavesdrop on your encrypted communications, you can verify this device. + + + + + The device was requested to be verified + + + + + Cancel + 取消 + + + + Deny + + + + + Start verification + + + + + Accept + 接受 + + Placeholder @@ -517,6 +696,24 @@ 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. + + + + + profile + + + + + profile name + + + QuickSwitcher @@ -593,10 +790,18 @@ Example: https://server.my:8787 无效的服务器名 + + ReplyPopup + + + Close + + + RoomInfo - + no version stored @@ -656,7 +861,7 @@ Example: https://server.my:8787 - + Accept 接受 @@ -697,30 +902,48 @@ Example: https://server.my:8787 StatusIndicator - + Failed - + Sent - + Received - + Read + + Success + + + Successful Verification + + + + + Verification successful! Both sides verified their devices! + + + + + Close + + + TextInputWidget - + Send a file 发送一个文件 @@ -741,7 +964,7 @@ Example: https://server.my:8787 - + Select a file 选择一个文件 @@ -751,7 +974,7 @@ Example: https://server.my:8787 所有文件(*) - + Place a call @@ -769,20 +992,19 @@ Example: https://server.my:8787 TimelineModel - + Message redaction failed: %1 删除消息失败:%1 - - - + + Failed to encrypt event, sending aborted! - + Save image 保存图像 @@ -920,12 +1142,12 @@ Example: https://server.my:8787 - + You joined this room. - + Rejected the knock from %1. @@ -949,17 +1171,17 @@ Example: https://server.my:8787 TimelineRow - + React - + Reply - + Options @@ -967,63 +1189,74 @@ Example: https://server.my:8787 TimelineView - + React - + Reply - + Read receipts 阅读回执 - + Mark as read - + View raw message - + View decrypted raw message - + Redact message - + Save as - + No room open + + + TimelineViewManager - + + No share room with this user found. Create an encrypted room with this user and try again. + + + + + TopBar + + Back to room list - + No room selected - + Room options 聊天室选项 @@ -1033,25 +1266,20 @@ Example: https://server.my:8787 邀请用户 - + Members 成员 - + Leave room 离开聊天室 - + Settings 设置 - - - Close - - TrayIcon @@ -1109,10 +1337,33 @@ Example: https://server.my:8787 + + UserProfile + + + Verify + + + + + Ban the user + + + + + Start a private chat + + + + + Kick the user + + + UserSettingsPage - + Minimize to tray 最小化至托盘 @@ -1132,12 +1383,17 @@ Example: https://server.my:8787 - + + profile: %1 + + + + CALLS - + Keep the application running in the background after closing the client window. @@ -1275,7 +1531,17 @@ This usually causes the application icon in the task bar to animate in some fash - + + Mobile mode + + + + + Will prevent text selection in the timeline to make scrolling easier. + + + + Scale factor @@ -1320,7 +1586,7 @@ This usually causes the application icon in the task bar to animate in some fash 设备指纹 - + Session Keys 会话密钥 @@ -1340,22 +1606,32 @@ This usually causes the application icon in the task bar to animate in some fash 加密 - + GENERAL 通用 - + INTERFACE - + Emoji Font Family - + + Share keys with trusted users + + + + + Automatically replies to key requests from other users, if they are verified. + + + + Open Sessions File 打开会话文件 @@ -1401,6 +1677,34 @@ This usually causes the application icon in the task bar to animate in some fash 保存导出的会话密钥的文件 + + Waiting + + + Waiting for other party + + + + + Waiting for other side to accept the verification request. + + + + + Waiting for other side to continue the verification request. + + + + + Waiting for other side to complete the verification request. + + + + + Cancel + 取消 + + WelcomePage diff --git a/resources/qml/ActiveCallBar.qml b/resources/qml/ActiveCallBar.qml index 49b5d059..7137197b 100644 --- a/resources/qml/ActiveCallBar.qml +++ b/resources/qml/ActiveCallBar.qml @@ -8,7 +8,7 @@ Rectangle { visible: TimelineManager.callState != WebRTCState.DISCONNECTED color: "#2ECC71" - implicitHeight: rowLayout.height + 8 + implicitHeight: visible ? rowLayout.height + 8 : 0 MouseArea { anchors.fill: parent diff --git a/resources/qml/Avatar.qml b/resources/qml/Avatar.qml index a247bffe..34b029a6 100644 --- a/resources/qml/Avatar.qml +++ b/resources/qml/Avatar.qml @@ -13,7 +13,7 @@ Rectangle { width: 48 height: 48 radius: Settings.avatarCircles ? height / 2 : 3 - color: colors.base + color: colors.alternateBase Label { anchors.fill: parent diff --git a/resources/qml/MatrixText.qml b/resources/qml/MatrixText.qml index 6c96a539..a5781c73 100644 --- a/resources/qml/MatrixText.qml +++ b/resources/qml/MatrixText.qml @@ -6,8 +6,7 @@ TextEdit { textFormat: TextEdit.RichText readOnly: true wrapMode: Text.Wrap - selectByMouse: true - activeFocusOnPress: false + selectByMouse: !Settings.mobileMode color: colors.text onLinkActivated: { if (/^https:\/\/matrix.to\/#\/(@.*)$/.test(link)) { @@ -29,7 +28,6 @@ TextEdit { id: ma anchors.fill: parent - propagateComposedEvents: true acceptedButtons: Qt.NoButton cursorShape: parent.hoveredLink ? Qt.PointingHandCursor : Qt.ArrowCursor } diff --git a/resources/qml/MessageInput.qml b/resources/qml/MessageInput.qml new file mode 100644 index 00000000..71da9cae --- /dev/null +++ b/resources/qml/MessageInput.qml @@ -0,0 +1,90 @@ +import QtQuick 2.9 +import QtQuick.Controls 2.3 +import QtQuick.Layouts 1.2 +import QtQuick.Window 2.2 + +Rectangle { + color: colors.window + Layout.fillWidth: true + Layout.preferredHeight: textInput.height + Layout.minimumHeight: 40 + + RowLayout { + id: inputBar + + anchors.fill: parent + spacing: 16 + + ImageButton { + Layout.alignment: Qt.AlignBottom + hoverEnabled: true + width: 22 + height: 22 + image: ":/icons/icons/ui/place-call.png" + Layout.topMargin: 8 + Layout.bottomMargin: 8 + Layout.leftMargin: 16 + } + + ImageButton { + Layout.alignment: Qt.AlignBottom + hoverEnabled: true + width: 22 + height: 22 + image: ":/icons/icons/ui/paper-clip-outline.png" + Layout.topMargin: 8 + Layout.bottomMargin: 8 + } + + ScrollView { + id: textInput + + Layout.alignment: Qt.AlignBottom + Layout.maximumHeight: Window.height / 4 + Layout.fillWidth: true + + TextArea { + placeholderText: qsTr("Write a message...") + placeholderTextColor: colors.buttonText + color: colors.text + wrapMode: TextEdit.Wrap + + MouseArea { + // workaround for wrong cursor shape on some platforms + anchors.fill: parent + acceptedButtons: Qt.NoButton + cursorShape: Qt.IBeamCursor + } + + background: Rectangle { + color: colors.window + } + + } + + } + + ImageButton { + Layout.alignment: Qt.AlignRight | Qt.AlignBottom + hoverEnabled: true + width: 22 + height: 22 + image: ":/icons/icons/ui/smile.png" + Layout.topMargin: 8 + Layout.bottomMargin: 8 + } + + ImageButton { + Layout.alignment: Qt.AlignRight | Qt.AlignBottom + hoverEnabled: true + width: 22 + height: 22 + image: ":/icons/icons/ui/cursor.png" + Layout.topMargin: 8 + Layout.bottomMargin: 8 + Layout.rightMargin: 16 + } + + } + +} diff --git a/resources/qml/MessageView.qml b/resources/qml/MessageView.qml new file mode 100644 index 00000000..09220a71 --- /dev/null +++ b/resources/qml/MessageView.qml @@ -0,0 +1,202 @@ +import "./delegates" +import QtGraphicalEffects 1.0 +import QtQuick 2.9 +import QtQuick.Controls 2.3 +import QtQuick.Layouts 1.2 +import QtQuick.Window 2.2 +import im.nheko 1.0 + +ListView { + id: chat + + property int delegateMaxWidth: (Settings.timelineMaxWidth > 100 && (parent.width - Settings.timelineMaxWidth) > scrollbar.width * 2) ? Settings.timelineMaxWidth : (parent.width - scrollbar.width * 2) + + Layout.fillWidth: true + Layout.fillHeight: true + cacheBuffer: 400 + model: TimelineManager.timeline + boundsBehavior: Flickable.StopAtBounds + pixelAligned: true + spacing: 4 + verticalLayoutDirection: ListView.BottomToTop + onCountChanged: { + if (atYEnd) + model.currentIndex = 0; + + } // Mark last event as read, since we are at the bottom + + ScrollHelper { + flickable: parent + anchors.fill: parent + } + + Shortcut { + sequence: StandardKey.MoveToPreviousPage + onActivated: { + chat.contentY = chat.contentY - chat.height / 2; + chat.returnToBounds(); + } + } + + Shortcut { + sequence: StandardKey.MoveToNextPage + onActivated: { + chat.contentY = chat.contentY + chat.height / 2; + chat.returnToBounds(); + } + } + + Shortcut { + sequence: StandardKey.Cancel + onActivated: chat.model.reply = undefined + } + + Shortcut { + sequence: "Alt+Up" + onActivated: chat.model.reply = chat.model.indexToId(chat.model.reply ? chat.model.idToIndex(chat.model.reply) + 1 : 0) + } + + Shortcut { + sequence: "Alt+Down" + onActivated: { + var idx = chat.model.reply ? chat.model.idToIndex(chat.model.reply) - 1 : -1; + chat.model.reply = idx >= 0 ? chat.model.indexToId(idx) : undefined; + } + } + + section { + property: "section" + } + + Component { + id: sectionHeader + + Column { + property var modelData + property string section + property string nextSection + + topPadding: 4 + bottomPadding: 4 + spacing: 8 + visible: !!modelData + width: parent.width + height: (section.includes(" ") ? dateBubble.height + 8 + userName.height : userName.height) + 8 + + Label { + id: dateBubble + + anchors.horizontalCenter: parent ? parent.horizontalCenter : undefined + visible: section.includes(" ") + text: chat.model.formatDateSeparator(modelData.timestamp) + color: colors.text + height: fontMetrics.height * 1.4 + width: contentWidth * 1.2 + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + + background: Rectangle { + radius: parent.height / 2 + color: colors.window + } + + } + + Row { + height: userName.height + spacing: 8 + + Avatar { + width: avatarSize + height: avatarSize + url: chat.model.avatarUrl(modelData.userId).replace("mxc://", "image://MxcImage/") + displayName: modelData.userName + userid: modelData.userId + + MouseArea { + anchors.fill: parent + onClicked: chat.model.openUserProfile(modelData.userId) + cursorShape: Qt.PointingHandCursor + propagateComposedEvents: true + } + + } + + Label { + id: userName + + text: TimelineManager.escapeEmoji(modelData.userName) + color: TimelineManager.userColor(modelData.userId, colors.window) + textFormat: Text.RichText + + MouseArea { + anchors.fill: parent + Layout.alignment: Qt.AlignHCenter + onClicked: chat.model.openUserProfile(modelData.userId) + cursorShape: Qt.PointingHandCursor + propagateComposedEvents: true + } + + } + + } + + } + + } + + ScrollBar.vertical: ScrollBar { + id: scrollbar + } + + delegate: Item { + id: wrapper + + // This would normally be previousSection, but our model's order is inverted. + property bool sectionBoundary: (ListView.nextSection != "" && ListView.nextSection !== ListView.section) || model.index === chat.count - 1 + property Item section + + anchors.horizontalCenter: parent ? parent.horizontalCenter : undefined + width: chat.delegateMaxWidth + height: section ? section.height + timelinerow.height : timelinerow.height + onSectionBoundaryChanged: { + if (sectionBoundary) { + var properties = { + "modelData": model.dump, + "section": ListView.section, + "nextSection": ListView.nextSection + }; + section = sectionHeader.createObject(wrapper, properties); + } else { + section.destroy(); + section = null; + } + } + + TimelineRow { + id: timelinerow + + y: section ? section.y + section.height : 0 + } + + Connections { + function onMovementEnded() { + if (y + height + 2 * chat.spacing > chat.contentY + chat.height && y < chat.contentY + chat.height) + chat.model.currentIndex = index; + + } + + target: chat + } + + } + + footer: BusyIndicator { + anchors.horizontalCenter: parent.horizontalCenter + running: chat.model && chat.model.paginationInProgress + height: 50 + width: 50 + z: 3 + } + +} diff --git a/resources/qml/Reactions.qml b/resources/qml/Reactions.qml index 6487f512..836087ef 100644 --- a/resources/qml/Reactions.qml +++ b/resources/qml/Reactions.qml @@ -83,7 +83,7 @@ Flow { implicitWidth: reaction.implicitWidth implicitHeight: reaction.implicitHeight border.color: (reaction.hovered || modelData.selfReactedEvent !== '') ? colors.highlight : colors.text - color: modelData.selfReactedEvent !== '' ? Qt.hsla(highlightHue, highlightSat, highlightLight, 0.2) : colors.base + color: modelData.selfReactedEvent !== '' ? Qt.hsla(highlightHue, highlightSat, highlightLight, 0.2) : colors.window border.width: 1 radius: reaction.height / 2 } diff --git a/resources/qml/ReplyPopup.qml b/resources/qml/ReplyPopup.qml new file mode 100644 index 00000000..4659e075 --- /dev/null +++ b/resources/qml/ReplyPopup.qml @@ -0,0 +1,47 @@ +import "./delegates/" +import QtQuick 2.9 +import QtQuick.Controls 2.3 +import QtQuick.Layouts 1.2 +import im.nheko 1.0 + +Rectangle { + id: replyPopup + + property var room: TimelineManager.timeline + + Layout.fillWidth: true + visible: room && room.reply + // Height of child, plus margins, plus border + implicitHeight: replyPreview.height + 10 + color: colors.window + z: 3 + + Reply { + id: replyPreview + + anchors.left: parent.left + anchors.leftMargin: 2 * 22 + 3 * 16 + anchors.right: closeReplyButton.left + anchors.rightMargin: 2 * 22 + 3 * 16 + anchors.bottom: parent.bottom + modelData: room ? room.getDump(room.reply, room.id) : { + } + userColor: TimelineManager.userColor(modelData.userId, colors.window) + } + + ImageButton { + id: closeReplyButton + + anchors.right: parent.right + anchors.rightMargin: 15 + anchors.top: replyPreview.top + hoverEnabled: true + width: 16 + height: 16 + image: ":/icons/icons/ui/remove-symbol.png" + ToolTip.visible: closeReplyButton.hovered + ToolTip.text: qsTr("Close") + onClicked: room.reply = undefined + } + +} diff --git a/resources/qml/TimelineRow.qml b/resources/qml/TimelineRow.qml index 38597673..57fded90 100644 --- a/resources/qml/TimelineRow.qml +++ b/resources/qml/TimelineRow.qml @@ -48,7 +48,7 @@ Item { Reply { visible: model.replyTo modelData: chat.model.getDump(model.replyTo, model.id) - userColor: TimelineManager.userColor(modelData.userId, colors.window) + userColor: TimelineManager.userColor(modelData.userId, colors.base) } // actual message content diff --git a/resources/qml/TimelineView.qml b/resources/qml/TimelineView.qml index d69d5568..9d00442c 100644 --- a/resources/qml/TimelineView.qml +++ b/resources/qml/TimelineView.qml @@ -43,6 +43,14 @@ Page { } + Component { + id: userProfileComponent + + UserProfile { + } + + } + Menu { id: messageContextMenu @@ -69,12 +77,12 @@ Page { MenuItem { text: qsTr("Reply") - onClicked: chat.model.replyAction(messageContextMenu.eventId) + onClicked: TimelineManager.timeline.replyAction(messageContextMenu.eventId) } MenuItem { text: qsTr("Read receipts") - onTriggered: chat.model.readReceiptsAction(messageContextMenu.eventId) + onTriggered: TimelineManager.timeline.readReceiptsAction(messageContextMenu.eventId) } MenuItem { @@ -83,19 +91,19 @@ Page { MenuItem { text: qsTr("View raw message") - onTriggered: chat.model.viewRawMessage(messageContextMenu.eventId) + onTriggered: TimelineManager.timeline.viewRawMessage(messageContextMenu.eventId) } MenuItem { visible: messageContextMenu.isEncrypted height: visible ? implicitHeight : 0 text: qsTr("View decrypted raw message") - onTriggered: chat.model.viewDecryptedRawMessage(messageContextMenu.eventId) + onTriggered: TimelineManager.timeline.viewDecryptedRawMessage(messageContextMenu.eventId) } MenuItem { text: qsTr("Redact message") - onTriggered: chat.model.redactEvent(messageContextMenu.eventId) + onTriggered: TimelineManager.timeline.redactEvent(messageContextMenu.eventId) } MenuItem { @@ -159,425 +167,52 @@ Page { } ColumnLayout { + visible: TimelineManager.timeline != null anchors.fill: parent + spacing: 0 + + TopBar { + } Rectangle { - id: topBar - Layout.fillWidth: true - implicitHeight: topLayout.height + 16 + height: 1 z: 3 + color: colors.mid + } + + Rectangle { + Layout.fillWidth: true + Layout.fillHeight: true color: colors.base - MouseArea { + ColumnLayout { anchors.fill: parent - onClicked: TimelineManager.openRoomSettings() - } + spacing: 0 - GridLayout { - //Layout.margins: 8 - - id: topLayout - - anchors.left: parent.left - anchors.right: parent.right - anchors.margins: 8 - anchors.verticalCenter: parent.verticalCenter - - ImageButton { - id: backToRoomsButton - - Layout.column: 0 - Layout.row: 0 - Layout.rowSpan: 2 - Layout.alignment: Qt.AlignVCenter - visible: TimelineManager.isNarrowView - image: ":/icons/icons/ui/angle-pointing-to-left.png" - ToolTip.visible: hovered - ToolTip.text: qsTr("Back to room list") - onClicked: TimelineManager.backToRooms() - } - - Avatar { - Layout.column: 1 - Layout.row: 0 - Layout.rowSpan: 2 - Layout.alignment: Qt.AlignVCenter - width: avatarSize - height: avatarSize - url: chat.model ? chat.model.roomAvatarUrl.replace("mxc://", "image://MxcImage/") : "" - displayName: chat.model ? chat.model.roomName : qsTr("No room selected") - - MouseArea { - anchors.fill: parent - onClicked: TimelineManager.openRoomSettings() - } - - } - - Label { - Layout.fillWidth: true - Layout.column: 2 - Layout.row: 0 - color: colors.text - font.pointSize: fontMetrics.font.pointSize * 1.1 - text: chat.model ? chat.model.roomName : qsTr("No room selected") - - MouseArea { - anchors.fill: parent - onClicked: TimelineManager.openRoomSettings() - } - - } - - MatrixText { - Layout.fillWidth: true - Layout.column: 2 - Layout.row: 1 - Layout.maximumHeight: fontMetrics.lineSpacing * 2 // show 2 lines - clip: true - text: chat.model ? chat.model.roomTopic : "" - } - - ImageButton { - id: roomOptionsButton - - Layout.column: 3 - Layout.row: 0 - Layout.rowSpan: 2 - Layout.alignment: Qt.AlignVCenter - image: ":/icons/icons/ui/vertical-ellipsis.png" - ToolTip.visible: hovered - ToolTip.text: qsTr("Room options") - onClicked: roomOptionsMenu.popup(roomOptionsButton) - - Menu { - id: roomOptionsMenu - - MenuItem { - text: qsTr("Invite users") - onTriggered: TimelineManager.openInviteUsersDialog() - } - - MenuItem { - text: qsTr("Members") - onTriggered: TimelineManager.openMemberListDialog() - } - - MenuItem { - text: qsTr("Leave room") - onTriggered: TimelineManager.openLeaveRoomDialog() - } - - MenuItem { - text: qsTr("Settings") - onTriggered: TimelineManager.openRoomSettings() - } - - } - - } - - } - - } - - StackLayout { - id: stackLayout - currentIndex: 0 - - Connections { - target: TimelineManager - function onActiveTimelineChanged() { - stackLayout.currentIndex = 0; - } - } - - ListView { - id: chat - - property int delegateMaxWidth: (Settings.timelineMaxWidth > 100 && (parent.width - Settings.timelineMaxWidth) > scrollbar.width * 2) ? Settings.timelineMaxWidth : (parent.width - scrollbar.width * 2) - - visible: TimelineManager.timeline != null - cacheBuffer: 400 - Layout.fillWidth: true - Layout.fillHeight: true - model: TimelineManager.timeline - boundsBehavior: Flickable.StopAtBounds - pixelAligned: true - spacing: 4 - verticalLayoutDirection: ListView.BottomToTop - onCountChanged: { - if (atYEnd) - model.currentIndex = 0; - - } // Mark last event as read, since we are at the bottom - - ScrollHelper { - flickable: parent - anchors.fill: parent - } - - Shortcut { - sequence: StandardKey.MoveToPreviousPage - onActivated: { - chat.contentY = chat.contentY - chat.height / 2; - chat.returnToBounds(); - } - } - - Shortcut { - sequence: StandardKey.MoveToNextPage - onActivated: { - chat.contentY = chat.contentY + chat.height / 2; - chat.returnToBounds(); - } - } - - Shortcut { - sequence: StandardKey.Cancel - onActivated: chat.model.reply = undefined - } - - Shortcut { - sequence: "Alt+Up" - onActivated: chat.model.reply = chat.model.indexToId(chat.model.reply ? chat.model.idToIndex(chat.model.reply) + 1 : 0) - } - - Shortcut { - sequence: "Alt+Down" - onActivated: { - var idx = chat.model.reply ? chat.model.idToIndex(chat.model.reply) - 1 : -1; - chat.model.reply = idx >= 0 ? chat.model.indexToId(idx) : undefined; - } - } - - Component { - id: userProfileComponent - - UserProfile { - } - - } - - section { - property: "section" - } - - Component { - id: sectionHeader - - Column { - property var modelData - property string section - property string nextSection - - topPadding: 4 - bottomPadding: 4 - spacing: 8 - visible: !!modelData - width: parent.width - height: (section.includes(" ") ? dateBubble.height + 8 + userName.height : userName.height) + 8 - - Label { - id: dateBubble - - anchors.horizontalCenter: parent ? parent.horizontalCenter : undefined - visible: section.includes(" ") - text: chat.model.formatDateSeparator(modelData.timestamp) - color: colors.text - height: fontMetrics.height * 1.4 - width: contentWidth * 1.2 - horizontalAlignment: Text.AlignHCenter - verticalAlignment: Text.AlignVCenter - - background: Rectangle { - radius: parent.height / 2 - color: colors.base - } - - } - - Row { - height: userName.height - spacing: 8 - - Avatar { - width: avatarSize - height: avatarSize - url: chat.model.avatarUrl(modelData.userId).replace("mxc://", "image://MxcImage/") - displayName: modelData.userName - userid: modelData.userId - - MouseArea { - anchors.fill: parent - onClicked: chat.model.openUserProfile(modelData.userId) - cursorShape: Qt.PointingHandCursor - propagateComposedEvents: true - } - - } - - Label { - id: userName - - text: TimelineManager.escapeEmoji(modelData.userName) - color: TimelineManager.userColor(modelData.userId, colors.window) - textFormat: Text.RichText - - MouseArea { - anchors.fill: parent - Layout.alignment: Qt.AlignHCenter - onClicked: chat.model.openUserProfile(modelData.userId) - cursorShape: Qt.PointingHandCursor - propagateComposedEvents: true - } - - } - - } - - } - - } - - ScrollBar.vertical: ScrollBar { - id: scrollbar - } - - delegate: Item { - id: wrapper - - // This would normally be previousSection, but our model's order is inverted. - property bool sectionBoundary: (ListView.nextSection != "" && ListView.nextSection !== ListView.section) || model.index === chat.count - 1 - property Item section - - anchors.horizontalCenter: parent ? parent.horizontalCenter : undefined - width: chat.delegateMaxWidth - height: section ? section.height + timelinerow.height : timelinerow.height - onSectionBoundaryChanged: { - if (sectionBoundary) { - var properties = { - "modelData": model.dump, - "section": ListView.section, - "nextSection": ListView.nextSection - }; - section = sectionHeader.createObject(wrapper, properties); - } else { - section.destroy(); - section = null; - } - } - - TimelineRow { - id: timelinerow - - y: section ? section.y + section.height : 0 - } + StackLayout { + id: stackLayout + currentIndex: 0 Connections { - function onMovementEnded() { - if (y + height + 2 * chat.spacing > chat.contentY + chat.height && y < chat.contentY + chat.height) - chat.model.currentIndex = index; - + target: TimelineManager + function onActiveTimelineChanged() { + stackLayout.currentIndex = 0; } - - target: chat } + MessageView { + Layout.fillWidth: true + Layout.fillHeight: true + } + + Loader { + source: TimelineManager.onVideoCall ? "VideoCall.qml" : "" + onLoaded: TimelineManager.setVideoCallItem() + } } - footer: BusyIndicator { - anchors.horizontalCenter: parent.horizontalCenter - running: chat.model && chat.model.paginationInProgress - height: 50 - width: 50 - z: 3 - } - - } - - Loader { - id: videoCallLoader - source: TimelineManager.onVideoCall ? "VideoCall.qml" : "" - onLoaded: TimelineManager.setVideoCallItem() - } - } - - Item { - id: chatFooter - - implicitHeight: Math.max(fontMetrics.height * 1.2, footerContent.height) - Layout.fillWidth: true - z: 3 - - Column { - id: footerContent - - anchors.left: parent.left - anchors.right: parent.right - anchors.bottom: parent.bottom - - Rectangle { - id: typingRect - - anchors.left: parent.left - anchors.right: parent.right - color: (chat.model && chat.model.typingUsers.length > 0) ? colors.window : "transparent" - height: typingDisplay.height - - Label { - id: typingDisplay - - anchors.left: parent.left - anchors.leftMargin: 10 - anchors.right: parent.right - anchors.rightMargin: 10 - color: colors.text - text: chat.model ? chat.model.formatTypingUsers(chat.model.typingUsers, colors.window) : "" - textFormat: Text.RichText - } - - } - - Rectangle { - id: replyPopup - - anchors.left: parent.left - anchors.right: parent.right - visible: chat.model && chat.model.reply - // Height of child, plus margins, plus border - height: replyPreview.height + 10 - color: colors.base - - Reply { - id: replyPreview - - anchors.left: parent.left - anchors.leftMargin: 10 - anchors.right: closeReplyButton.left - anchors.rightMargin: 20 - anchors.bottom: parent.bottom - modelData: chat.model ? chat.model.getDump(chat.model.reply, chat.model.id) : { - } - userColor: TimelineManager.userColor(modelData.userId, colors.window) - } - - ImageButton { - id: closeReplyButton - - anchors.right: parent.right - anchors.rightMargin: 15 - anchors.top: replyPreview.top - hoverEnabled: true - width: 16 - height: 16 - image: ":/icons/icons/ui/remove-symbol.png" - ToolTip.visible: closeReplyButton.hovered - ToolTip.text: qsTr("Close") - onClicked: chat.model.reply = undefined - } - + TypingIndicator { } } @@ -589,6 +224,19 @@ Page { z: 3 } + Rectangle { + Layout.fillWidth: true + z: 3 + height: 1 + color: colors.mid + } + + ReplyPopup { + } + + //MessageInput { + //} + } } diff --git a/resources/qml/TopBar.qml b/resources/qml/TopBar.qml new file mode 100644 index 00000000..181b9ba4 --- /dev/null +++ b/resources/qml/TopBar.qml @@ -0,0 +1,127 @@ +import QtQuick 2.9 +import QtQuick.Controls 2.3 +import QtQuick.Layouts 1.2 +import im.nheko 1.0 + +Rectangle { + id: topBar + + property var room: TimelineManager.timeline + + Layout.fillWidth: true + implicitHeight: topLayout.height + 16 + z: 3 + color: colors.window + + MouseArea { + anchors.fill: parent + onClicked: TimelineManager.openRoomSettings() + } + + GridLayout { + //Layout.margins: 8 + + id: topLayout + + anchors.left: parent.left + anchors.right: parent.right + anchors.margins: 8 + anchors.verticalCenter: parent.verticalCenter + + ImageButton { + id: backToRoomsButton + + Layout.column: 0 + Layout.row: 0 + Layout.rowSpan: 2 + Layout.alignment: Qt.AlignVCenter + visible: TimelineManager.isNarrowView + image: ":/icons/icons/ui/angle-pointing-to-left.png" + ToolTip.visible: hovered + ToolTip.text: qsTr("Back to room list") + onClicked: TimelineManager.backToRooms() + } + + Avatar { + Layout.column: 1 + Layout.row: 0 + Layout.rowSpan: 2 + Layout.alignment: Qt.AlignVCenter + width: avatarSize + height: avatarSize + url: room ? room.roomAvatarUrl.replace("mxc://", "image://MxcImage/") : "" + displayName: room ? room.roomName : qsTr("No room selected") + + MouseArea { + anchors.fill: parent + onClicked: TimelineManager.openRoomSettings() + } + + } + + Label { + Layout.fillWidth: true + Layout.column: 2 + Layout.row: 0 + color: colors.text + font.pointSize: fontMetrics.font.pointSize * 1.1 + text: room ? room.roomName : qsTr("No room selected") + + MouseArea { + anchors.fill: parent + onClicked: TimelineManager.openRoomSettings() + } + + } + + MatrixText { + Layout.fillWidth: true + Layout.column: 2 + Layout.row: 1 + Layout.maximumHeight: fontMetrics.lineSpacing * 2 // show 2 lines + clip: true + text: room ? room.roomTopic : "" + } + + ImageButton { + id: roomOptionsButton + + Layout.column: 3 + Layout.row: 0 + Layout.rowSpan: 2 + Layout.alignment: Qt.AlignVCenter + image: ":/icons/icons/ui/vertical-ellipsis.png" + ToolTip.visible: hovered + ToolTip.text: qsTr("Room options") + onClicked: roomOptionsMenu.popup(roomOptionsButton) + + Menu { + id: roomOptionsMenu + + MenuItem { + text: qsTr("Invite users") + onTriggered: TimelineManager.openInviteUsersDialog() + } + + MenuItem { + text: qsTr("Members") + onTriggered: TimelineManager.openMemberListDialog() + } + + MenuItem { + text: qsTr("Leave room") + onTriggered: TimelineManager.openLeaveRoomDialog() + } + + MenuItem { + text: qsTr("Settings") + onTriggered: TimelineManager.openRoomSettings() + } + + } + + } + + } + +} diff --git a/resources/qml/TypingIndicator.qml b/resources/qml/TypingIndicator.qml new file mode 100644 index 00000000..239fd662 --- /dev/null +++ b/resources/qml/TypingIndicator.qml @@ -0,0 +1,35 @@ +import QtQuick 2.9 +import QtQuick.Controls 2.3 +import QtQuick.Layouts 1.2 +import im.nheko 1.0 + +Item { + property var room: TimelineManager.timeline + + implicitHeight: Math.max(fontMetrics.height * 1.2, typingDisplay.height) + Layout.fillWidth: true + + Rectangle { + id: typingRect + + visible: (room && room.typingUsers.length > 0) + color: colors.base + anchors.fill: parent + z: 3 + + Label { + id: typingDisplay + + anchors.left: parent.left + anchors.leftMargin: 10 + anchors.right: parent.right + anchors.rightMargin: 10 + anchors.bottom: parent.bottom + color: colors.text + text: room ? room.formatTypingUsers(room.typingUsers, colors.base) : "" + textFormat: Text.RichText + } + + } + +} diff --git a/resources/qml/delegates/FileMessage.qml b/resources/qml/delegates/FileMessage.qml index c6f213ee..ffd1e82b 100644 --- a/resources/qml/delegates/FileMessage.qml +++ b/resources/qml/delegates/FileMessage.qml @@ -65,7 +65,7 @@ Item { } Rectangle { - color: colors.dark + color: colors.alternateBase z: -1 radius: 10 height: row.height + 24 diff --git a/resources/qml/delegates/ImageMessage.qml b/resources/qml/delegates/ImageMessage.qml index e2c78fbe..5c3dac95 100644 --- a/resources/qml/delegates/ImageMessage.qml +++ b/resources/qml/delegates/ImageMessage.qml @@ -31,11 +31,44 @@ Item { fillMode: Image.PreserveAspectFit MouseArea { + id: mouseArea + enabled: model.data.type == MtxEvent.ImageMessage && img.status == Image.Ready + hoverEnabled: true anchors.fill: parent onClicked: TimelineManager.openImageOverlay(model.data.url, model.data.id) } + Item { + id: overlay + + anchors.fill: parent + visible: mouseArea.containsMouse + + Rectangle { + id: container + + width: parent.width + implicitHeight: imgcaption.implicitHeight + anchors.bottom: overlay.bottom + color: 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: model.data.filename ? model.data.filename : model.data.body + color: colors.text + } + + } + } } diff --git a/resources/qml/delegates/NoticeMessage.qml b/resources/qml/delegates/NoticeMessage.qml index d9a7a3e7..67a69055 100644 --- a/resources/qml/delegates/NoticeMessage.qml +++ b/resources/qml/delegates/NoticeMessage.qml @@ -2,5 +2,5 @@ TextMessage { font.italic: true color: colors.buttonText height: isReply ? Math.min(chat.height / 8, implicitHeight) : undefined - clip: true + clip: isReply } diff --git a/resources/qml/delegates/Pill.qml b/resources/qml/delegates/Pill.qml index 4acf2bef..88f6c7fd 100644 --- a/resources/qml/delegates/Pill.qml +++ b/resources/qml/delegates/Pill.qml @@ -9,7 +9,7 @@ Label { background: Rectangle { radius: parent.height / 2 - color: colors.dark + color: colors.alternateBase } } diff --git a/resources/qml/delegates/PlayableMediaMessage.qml b/resources/qml/delegates/PlayableMediaMessage.qml index 9ad115c7..be22687f 100644 --- a/resources/qml/delegates/PlayableMediaMessage.qml +++ b/resources/qml/delegates/PlayableMediaMessage.qml @@ -8,7 +8,7 @@ Rectangle { id: bg radius: 10 - color: colors.dark + color: colors.alternateBase height: Math.round(content.height + 24) width: parent ? parent.width : undefined diff --git a/resources/qml/delegates/TextMessage.qml b/resources/qml/delegates/TextMessage.qml index 69f2f0e3..8a1c116e 100644 --- a/resources/qml/delegates/TextMessage.qml +++ b/resources/qml/delegates/TextMessage.qml @@ -7,6 +7,6 @@ MatrixText { text: "" + formatted.replace("
", "
")
     width: parent ? parent.width : undefined
     height: isReply ? Math.round(Math.min(timelineRoot.height / 8, implicitHeight)) : undefined
-    clip: true
+    clip: isReply
     font.pointSize: (Settings.enlargeEmojiOnlyMessages && model.data.isOnlyEmoji > 0 && model.data.isOnlyEmoji < 4) ? Settings.fontSize * 3 : Settings.fontSize
 }
diff --git a/resources/qml/emoji/EmojiPicker.qml b/resources/qml/emoji/EmojiPicker.qml
index 3a5ee57a..afe16350 100644
--- a/resources/qml/emoji/EmojiPicker.qml
+++ b/resources/qml/emoji/EmojiPicker.qml
@@ -59,6 +59,7 @@ Popup {
             cellHeight: 52
             boundsBehavior: Flickable.StopAtBounds
             clip: true
+            currentIndex: -1 // prevent sorting from stealing focus
 
             // Individual emoji
             delegate: AbstractButton {
@@ -160,7 +161,7 @@ Popup {
         Rectangle {
             Layout.fillWidth: true
             Layout.preferredHeight: 1
-            color: emojiPopup.colors.dark
+            color: emojiPopup.colors.alternateBase
         }
 
         // Category picker row
@@ -280,7 +281,7 @@ Popup {
                 Layout.preferredWidth: 1
                 implicitWidth: 1
                 height: parent.height
-                color: emojiPopup.colors.dark
+                color: emojiPopup.colors.alternateBase
             }
 
             // Search Button is special
diff --git a/resources/res.qrc b/resources/res.qrc
index dc5c9969..4eba0bca 100644
--- a/resources/res.qrc
+++ b/resources/res.qrc
@@ -122,6 +122,11 @@
         qtquickcontrols2.conf
 
         qml/TimelineView.qml
+        qml/TopBar.qml
+        qml/MessageView.qml
+        qml/MessageInput.qml
+        qml/TypingIndicator.qml
+        qml/ReplyPopup.qml
         qml/ActiveCallBar.qml
         qml/Avatar.qml
         qml/ImageButton.qml
diff --git a/resources/styles/nheko-dark.qss b/resources/styles/nheko-dark.qss
index 3676f874..547e4256 100644
--- a/resources/styles/nheko-dark.qss
+++ b/resources/styles/nheko-dark.qss
@@ -205,9 +205,7 @@ TextField {
     qproperty-labelColor: #caccd1;
 }
 
-SideBarActions,
-TopRoomBar
-{
+SideBarActions {
     border: none;
     border-top: 1px solid #202228;
     background-color: #2d3139;
@@ -215,7 +213,6 @@ TopRoomBar
 
 TextInputWidget {
     border: none;
-    border-top: 1px solid #2d3139;
 }
 
 TextInputWidget,
diff --git a/resources/styles/nheko.qss b/resources/styles/nheko.qss
index 8604ad30..81d97f65 100644
--- a/resources/styles/nheko.qss
+++ b/resources/styles/nheko.qss
@@ -233,7 +233,6 @@ QLineEdit {
 
 TextInputWidget {
     border: none;
-    border-top: 1px solid #dcdcdc;
 }
 
 SideBarActions {
@@ -241,11 +240,6 @@ SideBarActions {
     border-top: 1px solid #dcdcdc;
 }
 
-TopRoomBar {
-    border: none;
-    border-bottom: 1px solid #dcdcdc;
-}
-
 Toggle {
     qproperty-activeColor: #38a3d8;
     qproperty-disabledColor: gray;
diff --git a/resources/styles/system.qss b/resources/styles/system.qss
index 01951aff..d65c8baa 100644
--- a/resources/styles/system.qss
+++ b/resources/styles/system.qss
@@ -28,11 +28,9 @@ UserMentionsWidget > TimelineItem {
 SideBarActions,
 TextInputWidget {
     border: none;
-    border-top: 1px solid palette(mid);
 }
 
-UserInfoWidget,
-TopRoomBar {
+UserInfoWidget {
     border: none;
     border-bottom: 1px solid palette(mid);
 }
diff --git a/src/Cache.cpp b/src/Cache.cpp
index 088b6fc6..993fbfe7 100644
--- a/src/Cache.cpp
+++ b/src/Cache.cpp
@@ -40,7 +40,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.07.05");
+static const std::string CURRENT_CACHE_FORMAT_VERSION("2020.10.20");
 static const std::string SECRET("secret");
 
 static lmdb::val NEXT_BATCH_KEY("next_batch");
@@ -437,7 +437,9 @@ Cache::getOutboundMegolmSession(const std::string &room_id)
 //
 
 void
-Cache::saveOlmSession(const std::string &curve25519, mtx::crypto::OlmSessionPtr session)
+Cache::saveOlmSession(const std::string &curve25519,
+                      mtx::crypto::OlmSessionPtr session,
+                      uint64_t timestamp)
 {
         using namespace mtx::crypto;
 
@@ -447,7 +449,11 @@ Cache::saveOlmSession(const std::string &curve25519, mtx::crypto::OlmSessionPtr
         const auto pickled    = pickle(session.get(), SECRET);
         const auto session_id = mtx::crypto::session_id(session.get());
 
-        lmdb::dbi_put(txn, db, lmdb::val(session_id), lmdb::val(pickled));
+        StoredOlmSession stored_session;
+        stored_session.pickled_session = pickled;
+        stored_session.last_message_ts = timestamp;
+
+        lmdb::dbi_put(txn, db, lmdb::val(session_id), lmdb::val(json(stored_session).dump()));
 
         txn.commit();
 }
@@ -466,13 +472,44 @@ Cache::getOlmSession(const std::string &curve25519, const std::string &session_i
         txn.commit();
 
         if (found) {
-                auto data = std::string(pickled.data(), pickled.size());
-                return unpickle(data, SECRET);
+                std::string_view raw(pickled.data(), pickled.size());
+                auto data = json::parse(raw).get();
+                return unpickle(data.pickled_session, SECRET);
         }
 
         return std::nullopt;
 }
 
+std::optional
+Cache::getLatestOlmSession(const std::string &curve25519)
+{
+        using namespace mtx::crypto;
+
+        auto txn = lmdb::txn::begin(env_);
+        auto db  = getOlmSessionsDb(txn, curve25519);
+
+        std::string session_id, pickled_session;
+        std::vector res;
+
+        std::optional currentNewest;
+
+        auto cursor = lmdb::cursor::open(txn, db);
+        while (cursor.get(session_id, pickled_session, MDB_NEXT)) {
+                auto data =
+                  json::parse(std::string_view(pickled_session.data(), pickled_session.size()))
+                    .get();
+                if (!currentNewest || currentNewest->last_message_ts < data.last_message_ts)
+                        currentNewest = data;
+        }
+        cursor.close();
+
+        txn.commit();
+
+        return currentNewest
+                 ? std::optional(unpickle(currentNewest->pickled_session, SECRET))
+                 : std::nullopt;
+}
+
 std::vector
 Cache::getOlmSessions(const std::string &curve25519)
 {
@@ -828,6 +865,80 @@ Cache::runMigrations()
                    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 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, dbName.c_str());
+
+                                   std::string 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 = 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());
+                                           lmdb::dbi_put(txn,
+                                                         newDb,
+                                                         lmdb::val(key),
+                                                         lmdb::val(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;
+           }},
         };
 
         nhlog::db()->info("Running migrations, this may take a while!");
@@ -1638,7 +1749,7 @@ Cache::getTimelineIndex(const std::string &room_id, std::string_view event_id)
 
         lmdb::dbi orderDb{0};
         try {
-                orderDb = getOrderToMessageDb(txn, room_id);
+                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,
@@ -2169,34 +2280,22 @@ Cache::joinedRooms()
         return room_ids;
 }
 
-void
-Cache::populateMembers()
+std::optional
+Cache::getMember(const std::string &room_id, const std::string &user_id)
 {
-        auto rooms = joinedRooms();
-        nhlog::db()->info("loading {} rooms", rooms.size());
+        try {
+                auto txn = lmdb::txn::begin(env_, nullptr, MDB_RDONLY);
 
-        auto txn = lmdb::txn::begin(env_);
+                auto membersdb = getMembersDb(txn, room_id);
 
-        for (const auto &room : rooms) {
-                const auto roomid = QString::fromStdString(room);
-
-                auto membersdb = getMembersDb(txn, room);
-                auto cursor    = lmdb::cursor::open(txn, membersdb);
-
-                std::string user_id, info;
-                while (cursor.get(user_id, info, MDB_NEXT)) {
-                        MemberInfo m = json::parse(info);
-
-                        const auto userid = QString::fromStdString(user_id);
-
-                        insertDisplayName(roomid, userid, QString::fromStdString(m.name));
-                        insertAvatarUrl(roomid, userid, QString::fromStdString(m.avatar_url));
+                lmdb::val info;
+                if (lmdb::dbi_get(txn, membersdb, lmdb::val(user_id), info)) {
+                        MemberInfo m = json::parse(std::string_view(info.data(), info.size()));
+                        return m;
                 }
-
-                cursor.close();
+        } catch (...) {
         }
-
-        txn.commit();
+        return std::nullopt;
 }
 
 std::vector
@@ -2613,8 +2712,19 @@ Cache::saveOldMessages(const std::string &room_id, const mtx::responses::Message
                 }
         }
 
-        if (res.chunk.empty())
+        if (res.chunk.empty()) {
+                if (lmdb::dbi_get(txn, orderDb, lmdb::val(&index, sizeof(index)), val)) {
+                        auto orderEntry = json::parse(std::string_view(val.data(), val.size()));
+                        orderEntry["prev_batch"] = res.end;
+                        lmdb::dbi_put(txn,
+                                      orderDb,
+                                      lmdb::val(&index, sizeof(index)),
+                                      lmdb::val(orderEntry.dump()));
+                        nhlog::db()->debug("saving '{}'", orderEntry.dump());
+                        txn.commit();
+                }
                 return index;
+        }
 
         std::string event_id_val;
         for (const auto &e : res.chunk) {
@@ -3034,15 +3144,12 @@ Cache::roomMembers(const std::string &room_id)
         return members;
 }
 
-QHash Cache::DisplayNames;
-QHash Cache::AvatarUrls;
-
 QString
 Cache::displayName(const QString &room_id, const QString &user_id)
 {
-        auto fmt = QString("%1 %2").arg(room_id).arg(user_id);
-        if (DisplayNames.contains(fmt))
-                return DisplayNames[fmt];
+        if (auto info = getMember(room_id.toStdString(), user_id.toStdString());
+            info && !info->name.empty())
+                return QString::fromStdString(info->name);
 
         return user_id;
 }
@@ -3050,9 +3157,8 @@ Cache::displayName(const QString &room_id, const QString &user_id)
 std::string
 Cache::displayName(const std::string &room_id, const std::string &user_id)
 {
-        auto fmt = QString::fromStdString(room_id + " " + user_id);
-        if (DisplayNames.contains(fmt))
-                return DisplayNames[fmt].toStdString();
+        if (auto info = getMember(room_id, user_id); info && !info->name.empty())
+                return info->name;
 
         return user_id;
 }
@@ -3060,41 +3166,11 @@ Cache::displayName(const std::string &room_id, const std::string &user_id)
 QString
 Cache::avatarUrl(const QString &room_id, const QString &user_id)
 {
-        auto fmt = QString("%1 %2").arg(room_id).arg(user_id);
-        if (AvatarUrls.contains(fmt))
-                return AvatarUrls[fmt];
+        if (auto info = getMember(room_id.toStdString(), user_id.toStdString());
+            info && !info->avatar_url.empty())
+                return QString::fromStdString(info->avatar_url);
 
-        return QString();
-}
-
-void
-Cache::insertDisplayName(const QString &room_id,
-                         const QString &user_id,
-                         const QString &display_name)
-{
-        auto fmt = QString("%1 %2").arg(room_id).arg(user_id);
-        DisplayNames.insert(fmt, display_name);
-}
-
-void
-Cache::removeDisplayName(const QString &room_id, const QString &user_id)
-{
-        auto fmt = QString("%1 %2").arg(room_id).arg(user_id);
-        DisplayNames.remove(fmt);
-}
-
-void
-Cache::insertAvatarUrl(const QString &room_id, const QString &user_id, const QString &avatar_url)
-{
-        auto fmt = QString("%1 %2").arg(room_id).arg(user_id);
-        AvatarUrls.insert(fmt, avatar_url);
-}
-
-void
-Cache::removeAvatarUrl(const QString &room_id, const QString &user_id)
-{
-        auto fmt = QString("%1 %2").arg(room_id).arg(user_id);
-        AvatarUrls.remove(fmt);
+        return "";
 }
 
 mtx::presence::PresenceState
@@ -3629,6 +3705,19 @@ from_json(const nlohmann::json &obj, MegolmSessionIndex &msg)
         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;
+}
+void
+from_json(const nlohmann::json &obj, StoredOlmSession &msg)
+{
+        msg.last_message_ts = obj.at("ts").get();
+        msg.pickled_session = obj.at("s").get();
+}
+
 namespace cache {
 void
 init(const QString &user_id)
@@ -3669,28 +3758,6 @@ avatarUrl(const QString &room_id, const QString &user_id)
         return instance_->avatarUrl(room_id, user_id);
 }
 
-void
-removeDisplayName(const QString &room_id, const QString &user_id)
-{
-        instance_->removeDisplayName(room_id, user_id);
-}
-void
-removeAvatarUrl(const QString &room_id, const QString &user_id)
-{
-        instance_->removeAvatarUrl(room_id, user_id);
-}
-
-void
-insertDisplayName(const QString &room_id, const QString &user_id, const QString &display_name)
-{
-        instance_->insertDisplayName(room_id, user_id, display_name);
-}
-void
-insertAvatarUrl(const QString &room_id, const QString &user_id, const QString &avatar_url)
-{
-        instance_->insertAvatarUrl(room_id, user_id, avatar_url);
-}
-
 mtx::presence::PresenceState
 presenceState(const std::string &user_id)
 {
@@ -3702,13 +3769,6 @@ statusMessage(const std::string &user_id)
         return instance_->statusMessage(user_id);
 }
 
-//! Load saved data for the display names & avatars.
-void
-populateMembers()
-{
-        instance_->populateMembers();
-}
-
 // user cache stores user keys
 std::optional
 userKeys(const std::string &user_id)
@@ -4114,9 +4174,11 @@ inboundMegolmSessionExists(const MegolmSessionIndex &index)
 // Olm Sessions
 //
 void
-saveOlmSession(const std::string &curve25519, mtx::crypto::OlmSessionPtr session)
+saveOlmSession(const std::string &curve25519,
+               mtx::crypto::OlmSessionPtr session,
+               uint64_t timestamp)
 {
-        instance_->saveOlmSession(curve25519, std::move(session));
+        instance_->saveOlmSession(curve25519, std::move(session), timestamp);
 }
 std::vector
 getOlmSessions(const std::string &curve25519)
@@ -4128,6 +4190,11 @@ getOlmSession(const std::string &curve25519, const std::string &session_id)
 {
         return instance_->getOlmSession(curve25519, session_id);
 }
+std::optional
+getLatestOlmSession(const std::string &curve25519)
+{
+        return instance_->getLatestOlmSession(curve25519);
+}
 
 void
 saveOlmAccount(const std::string &pickled)
diff --git a/src/Cache.h b/src/Cache.h
index cd96708e..98e6cb75 100644
--- a/src/Cache.h
+++ b/src/Cache.h
@@ -44,16 +44,6 @@ displayName(const QString &room_id, const QString &user_id);
 QString
 avatarUrl(const QString &room_id, const QString &user_id);
 
-void
-removeDisplayName(const QString &room_id, const QString &user_id);
-void
-removeAvatarUrl(const QString &room_id, const QString &user_id);
-
-void
-insertDisplayName(const QString &room_id, const QString &user_id, const QString &display_name);
-void
-insertAvatarUrl(const QString &room_id, const QString &user_id, const QString &avatar_url);
-
 // presence
 mtx::presence::PresenceState
 presenceState(const std::string &user_id);
@@ -74,9 +64,6 @@ markDeviceVerified(const std::string &user_id, const std::string &device);
 void
 markDeviceUnverified(const std::string &user_id, const std::string &device);
 
-//! Load saved data for the display names & avatars.
-void
-populateMembers();
 std::vector
 joinedRooms();
 
@@ -292,11 +279,15 @@ inboundMegolmSessionExists(const MegolmSessionIndex &index);
 // Olm Sessions
 //
 void
-saveOlmSession(const std::string &curve25519, mtx::crypto::OlmSessionPtr session);
+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);
diff --git a/src/CacheCryptoStructs.h b/src/CacheCryptoStructs.h
index 935d6493..a693e233 100644
--- a/src/CacheCryptoStructs.h
+++ b/src/CacheCryptoStructs.h
@@ -66,6 +66,16 @@ struct OlmSessionStorage
         std::mutex group_inbound_mtx;
 };
 
+struct StoredOlmSession
+{
+        std::uint64_t last_message_ts = 0;
+        std::string pickled_session;
+};
+void
+to_json(nlohmann::json &obj, const StoredOlmSession &msg);
+void
+from_json(const nlohmann::json &obj, StoredOlmSession &msg);
+
 //! Verification status of a single user
 struct VerificationStatus
 {
diff --git a/src/Cache_p.h b/src/Cache_p.h
index b3f4c58c..a32793ea 100644
--- a/src/Cache_p.h
+++ b/src/Cache_p.h
@@ -46,9 +46,9 @@ class Cache : public QObject
 public:
         Cache(const QString &userId, QObject *parent = nullptr);
 
-        static std::string displayName(const std::string &room_id, const std::string &user_id);
-        static QString displayName(const QString &room_id, const QString &user_id);
-        static 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);
@@ -71,18 +71,6 @@ public:
         void markDeviceVerified(const std::string &user_id, const std::string &device);
         void markDeviceUnverified(const std::string &user_id, const std::string &device);
 
-        static void removeDisplayName(const QString &room_id, const QString &user_id);
-        static void removeAvatarUrl(const QString &room_id, const QString &user_id);
-
-        static void insertDisplayName(const QString &room_id,
-                                      const QString &user_id,
-                                      const QString &display_name);
-        static void insertAvatarUrl(const QString &room_id,
-                                    const QString &user_id,
-                                    const QString &avatar_url);
-
-        //! Load saved data for the display names & avatars.
-        void populateMembers();
         std::vector joinedRooms();
 
         QMap roomInfo(bool withInvites = true);
@@ -266,10 +254,14 @@ public:
         //
         // Olm Sessions
         //
-        void saveOlmSession(const std::string &curve25519, mtx::crypto::OlmSessionPtr session);
+        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();
@@ -304,6 +296,8 @@ private:
         QString getInviteRoomTopic(lmdb::txn &txn, lmdb::dbi &statesdb);
         QString getInviteRoomAvatarUrl(lmdb::txn &txn, lmdb::dbi &statesdb, lmdb::dbi &membersdb);
 
+        std::optional getMember(const std::string &room_id, const std::string &user_id);
+
         std::string getLastEventId(lmdb::txn &txn, const std::string &room_id);
         DescInfo getLastMessageInfo(lmdb::txn &txn, const std::string &room_id);
         void saveTimelineMessages(lmdb::txn &txn,
@@ -360,25 +354,12 @@ private:
                                               lmdb::val(e->state_key),
                                               lmdb::val(json(tmp).dump()));
 
-                                insertDisplayName(QString::fromStdString(room_id),
-                                                  QString::fromStdString(e->state_key),
-                                                  QString::fromStdString(display_name));
-
-                                insertAvatarUrl(QString::fromStdString(room_id),
-                                                QString::fromStdString(e->state_key),
-                                                QString::fromStdString(e->content.avatar_url));
-
                                 break;
                         }
                         default: {
                                 lmdb::dbi_del(
                                   txn, membersdb, lmdb::val(e->state_key), lmdb::val(""));
 
-                                removeDisplayName(QString::fromStdString(room_id),
-                                                  QString::fromStdString(e->state_key));
-                                removeAvatarUrl(QString::fromStdString(room_id),
-                                                QString::fromStdString(e->state_key));
-
                                 break;
                         }
                         }
@@ -565,7 +546,7 @@ private:
         lmdb::dbi getOlmSessionsDb(lmdb::txn &txn, const std::string &curve25519_key)
         {
                 return lmdb::dbi::open(
-                  txn, std::string("olm_sessions/" + curve25519_key).c_str(), MDB_CREATE);
+                  txn, std::string("olm_sessions.v2/" + curve25519_key).c_str(), MDB_CREATE);
         }
 
         QString getDisplayName(const mtx::events::StateEvent &event)
@@ -598,9 +579,6 @@ private:
         QString localUserId_;
         QString cacheDirectory_;
 
-        static QHash DisplayNames;
-        static QHash AvatarUrls;
-
         OlmSessionStorage session_storage;
         VerificationStorage verification_storage;
 };
diff --git a/src/ChatPage.cpp b/src/ChatPage.cpp
index e0ac31ab..c86c6128 100644
--- a/src/ChatPage.cpp
+++ b/src/ChatPage.cpp
@@ -73,6 +73,8 @@ ChatPage::ChatPage(QSharedPointer userSettings, QWidget *parent)
 {
         setObjectName("chatPage");
 
+        instance_ = this;
+
         qRegisterMetaType>();
         qRegisterMetaType>();
         qRegisterMetaType();
@@ -124,7 +126,7 @@ ChatPage::ChatPage(QSharedPointer userSettings, QWidget *parent)
         contentLayout_->setSpacing(0);
         contentLayout_->setMargin(0);
 
-        view_manager_ = new TimelineViewManager(userSettings_, &callManager_, this);
+        view_manager_ = new TimelineViewManager(&callManager_, this);
 
         contentLayout_->addWidget(view_manager_->getWidget());
 
@@ -270,7 +272,7 @@ ChatPage::ChatPage(QSharedPointer userSettings, QWidget *parent)
         connect(room_list_,
                 SIGNAL(totalUnreadMessageCountUpdated(int)),
                 this,
-                SLOT(showUnreadMessageNotification(int)));
+                SIGNAL(unreadMessages(int)));
 
         connect(text_input_,
                 &TextInputWidget::sendTextMessage,
@@ -593,8 +595,6 @@ ChatPage::ChatPage(QSharedPointer userSettings, QWidget *parent)
         connectCallMessage();
         connectCallMessage();
         connectCallMessage();
-
-        instance_ = this;
 }
 
 void
@@ -629,7 +629,7 @@ ChatPage::resetUI()
         user_info_widget_->reset();
         view_manager_->clearAll();
 
-        showUnreadMessageNotification(0);
+        emit unreadMessages(0);
 }
 
 void
@@ -754,18 +754,6 @@ ChatPage::bootstrap(QString userid, QString homeserver, QString token)
         tryInitialSync();
 }
 
-void
-ChatPage::showUnreadMessageNotification(int count)
-{
-        emit unreadMessages(count);
-
-        // TODO: Make the default title a const.
-        if (count == 0)
-                emit changeWindowTitle("nheko");
-        else
-                emit changeWindowTitle(QString("nheko (%1)").arg(count));
-}
-
 void
 ChatPage::loadStateFromCache()
 {
@@ -777,8 +765,6 @@ ChatPage::loadStateFromCache()
                 cache::restoreSessions();
                 olm::client()->load(cache::restoreOlmAccount(), STORAGE_SECRET_KEY);
 
-                cache::populateMembers();
-
                 emit initializeEmptyViews(cache::roomMessages());
                 emit initializeRoomList(cache::roomInfo());
                 emit initializeMentions(cache::getTimelineMentions());
@@ -1252,6 +1238,12 @@ ChatPage::unbanUser(QString userid, QString reason)
           reason.trimmed().toStdString());
 }
 
+void
+ChatPage::receivedSessionKey(const std::string &room_id, const std::string &session_id)
+{
+        view_manager_->receivedSessionKey(room_id, session_id);
+}
+
 void
 ChatPage::sendTypingNotifications()
 {
diff --git a/src/ChatPage.h b/src/ChatPage.h
index f0e12ab5..a29cea7b 100644
--- a/src/ChatPage.h
+++ b/src/ChatPage.h
@@ -107,6 +107,8 @@ public slots:
         void banUser(QString userid, QString reason);
         void unbanUser(QString userid, QString reason);
 
+        void receivedSessionKey(const std::string &room_id, const std::string &session_id);
+
 signals:
         void connectionLost();
         void connectionRestored();
@@ -128,7 +130,7 @@ signals:
 
         void contentLoaded();
         void closing();
-        void changeWindowTitle(const QString &msg);
+        void changeWindowTitle(const int);
         void unreadMessages(int count);
         void showNotification(const QString &msg);
         void showLoginPage(const QString &msg);
@@ -186,7 +188,6 @@ signals:
         void receivedDeviceVerificationDone(const mtx::events::msg::KeyVerificationDone &message);
 
 private slots:
-        void showUnreadMessageNotification(int count);
         void logout();
         void removeRoom(const QString &room_id);
         void dropToLoginPage(const QString &msg);
diff --git a/src/MainWindow.cpp b/src/MainWindow.cpp
index b6ad8bbe..f7c9fbf0 100644
--- a/src/MainWindow.cpp
+++ b/src/MainWindow.cpp
@@ -53,10 +53,11 @@
 
 MainWindow *MainWindow::instance_ = nullptr;
 
-MainWindow::MainWindow(QWidget *parent)
+MainWindow::MainWindow(const QString profile, QWidget *parent)
   : QMainWindow(parent)
+  , profile_{profile}
 {
-        setWindowTitle("nheko");
+        setWindowTitle(0);
         setObjectName("MainWindow");
 
         modal_ = new OverlayModal(this);
@@ -103,8 +104,7 @@ MainWindow::MainWindow(QWidget *parent)
         connect(chat_page_, &ChatPage::closing, this, &MainWindow::showWelcomePage);
         connect(
           chat_page_, &ChatPage::showOverlayProgressBar, this, &MainWindow::showOverlayProgressBar);
-        connect(
-          chat_page_, SIGNAL(changeWindowTitle(QString)), this, SLOT(setWindowTitle(QString)));
+        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_->loginError(msg);
@@ -178,6 +178,18 @@ MainWindow::MainWindow(QWidget *parent)
         }
 }
 
+void
+MainWindow::setWindowTitle(int notificationCount)
+{
+        QString name = "nheko";
+        if (!profile_.isEmpty())
+                name += " | " + profile_;
+        if (notificationCount > 0) {
+                name.append(QString{" (%1)"}.arg(notificationCount));
+        }
+        QMainWindow::setWindowTitle(name);
+}
+
 void
 MainWindow::showEvent(QShowEvent *event)
 {
diff --git a/src/MainWindow.h b/src/MainWindow.h
index e66f299f..2f9ff897 100644
--- a/src/MainWindow.h
+++ b/src/MainWindow.h
@@ -62,7 +62,7 @@ class MainWindow : public QMainWindow
         Q_OBJECT
 
 public:
-        explicit MainWindow(QWidget *parent = nullptr);
+        explicit MainWindow(const QString name, QWidget *parent = nullptr);
 
         static MainWindow *instance() { return instance_; };
         void saveCurrentWindowSize();
@@ -113,6 +113,8 @@ private slots:
         void showOverlayProgressBar();
         void removeOverlayProgressBar();
 
+        virtual void setWindowTitle(int notificationCount);
+
 private:
         bool loadJdenticonPlugin();
 
@@ -147,4 +149,6 @@ private:
         LoadingIndicator *spinner_ = nullptr;
 
         JdenticonInterface *jdenticonInteface_ = nullptr;
+
+        QString profile_;
 };
diff --git a/src/Olm.cpp b/src/Olm.cpp
index f4cb2209..6e68bd42 100644
--- a/src/Olm.cpp
+++ b/src/Olm.cpp
@@ -1,14 +1,15 @@
+#include "Olm.h"
+
 #include 
 #include 
 
-#include "Olm.h"
-
 #include "Cache.h"
 #include "Cache_p.h"
 #include "ChatPage.h"
 #include "DeviceVerificationFlow.h"
 #include "Logging.h"
 #include "MatrixClient.h"
+#include "UserSettingsPage.h"
 #include "Utils.h"
 
 static const std::string STORAGE_SECRET_KEY("secret");
@@ -46,7 +47,7 @@ handle_to_device_messages(const std::vectorwarn(
@@ -55,10 +56,6 @@ handle_to_device_messages(const std::vectorwarn("validation error for olm message: {} {}",
                                                       e.what(),
                                                       j_msg.dump(2));
-
-                                nhlog::crypto()->warn("validation error for olm message: {} {}",
-                                                      e.what(),
-                                                      j_msg.dump(2));
                         }
 
                 } else if (msg_type == to_string(mtx::events::EventType::RoomKeyRequest)) {
@@ -249,7 +246,10 @@ handle_pre_key_olm_message(const std::string &sender,
         nhlog::crypto()->debug("decrypted message: \n {}", plaintext.dump(2));
 
         try {
-                cache::saveOlmSession(sender_key, std::move(inbound_session));
+                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());
@@ -317,7 +317,10 @@ try_olm_decryption(const std::string &sender_key, const mtx::events::msg::OlmCip
 
                 try {
                         text = olm::client()->decrypt_message(session->get(), msg.type, msg.body);
-                        cache::saveOlmSession(id, std::move(session.value()));
+                        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,
@@ -366,6 +369,8 @@ create_inbound_megolm_session(const mtx::events::DeviceEventinfo(
           "established inbound megolm session ({}, {})", roomKey.content.room_id, roomKey.sender);
+
+        ChatPage::instance()->receivedSessionKey(index.room_id, index.session_id);
 }
 
 void
@@ -389,9 +394,10 @@ import_inbound_megolm_session(
                 return;
         }
 
-        // TODO(Nico): Reload messages encrypted with this key.
         nhlog::crypto()->info(
           "established inbound megolm session ({}, {})", roomKey.content.room_id, roomKey.sender);
+
+        ChatPage::instance()->receivedSessionKey(index.room_id, index.session_id);
 }
 
 void
@@ -402,48 +408,24 @@ mark_keys_as_published()
 }
 
 void
-request_keys(const std::string &room_id, const std::string &event_id)
-{
-        nhlog::crypto()->info("requesting keys for event {} at {}", event_id, room_id);
-
-        http::client()->get_event(
-          room_id,
-          event_id,
-          [event_id, room_id](const mtx::events::collections::TimelineEvents &res,
-                              mtx::http::RequestErr err) {
-                  using namespace mtx::events;
-
-                  if (err) {
-                          nhlog::net()->warn(
-                            "failed to retrieve event {} from {}", event_id, room_id);
-                          return;
-                  }
-
-                  if (!std::holds_alternative>(res)) {
-                          nhlog::net()->info(
-                            "retrieved event is not encrypted: {} from {}", event_id, room_id);
-                          return;
-                  }
-
-                  olm::send_key_request_for(room_id, std::get>(res));
-          });
-}
-
-void
-send_key_request_for(const std::string &room_id,
-                     const mtx::events::EncryptedEvent &e)
+send_key_request_for(mtx::events::EncryptedEvent e,
+                     const std::string &request_id,
+                     bool cancel)
 {
         using namespace mtx::events;
 
-        nhlog::crypto()->debug("sending key request: {}", json(e).dump(2));
+        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               = mtx::events::msg::RequestAction::Request;
+        request.action = !cancel ? mtx::events::msg::RequestAction::Request
+                                 : mtx::events::msg::RequestAction::Cancellation;
         request.algorithm            = MEGOLM_ALGO;
-        request.room_id              = room_id;
+        request.room_id              = e.room_id;
         request.sender_key           = e.content.sender_key;
         request.session_id           = e.content.session_id;
-        request.request_id           = "key_request." + http::client()->generate_txn_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));
@@ -493,19 +475,18 @@ handle_key_request_message(const mtx::events::DeviceEventwarn("requested session not found in room: {}",
                                       req.content.room_id);
 
-                nhlog::crypto()->warn("requested session not found in room: {}",
-                                      req.content.room_id);
-
                 return;
         }
 
         // Check that the requested session_id and the one we have saved match.
-        const auto session = cache::getOutboundMegolmSession(req.content.room_id);
-        if (req.content.session_id != session.data.session_id) {
-                nhlog::crypto()->warn("session id of retrieved session doesn't match the request: "
-                                      "requested({}), ours({})",
-                                      req.content.session_id,
-                                      session.data.session_id);
+        MegolmSessionIndex index{};
+        index.room_id    = req.content.room_id;
+        index.session_id = req.content.session_id;
+        index.sender_key = olm::client()->identity_keys().curve25519;
+
+        const auto session = cache::getInboundMegolmSession(index);
+        if (!session) {
+                nhlog::crypto()->warn("No session with id {} in db", req.content.session_id);
                 return;
         }
 
@@ -517,168 +498,56 @@ handle_key_request_message(const mtx::events::DeviceEventuserSettings()->shareKeysWithTrustedUsers()) {
+                for (const auto &dev : verificationStatus->verified_devices) {
+                        if (dev == req.content.requesting_device_id) {
+                                verifiedDevice = true;
+                                nhlog::crypto()->debug("Verified device: {}", dev);
+                                break;
+                        }
+                }
+        }
+
+        if (!utils::respondsToKeyRequests(req.content.room_id) && !verifiedDevice) {
                 nhlog::crypto()->debug("ignoring all key requests for room {}",
                                        req.content.room_id);
                 return;
         }
 
+        auto session_key = mtx::crypto::export_session(session);
         //
         // Prepare the m.room_key event.
         //
-        auto payload = json{{"algorithm", "m.megolm.v1.aes-sha2"},
-                            {"room_id", req.content.room_id},
-                            {"session_id", req.content.session_id},
-                            {"session_key", session.data.session_key}};
+        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;
 
-        send_megolm_key_to_device(req.sender, req.content.requesting_device_id, payload);
+        // TODO(Nico): Figure out if this is correct
+        forward_key.sender_claimed_ed25519_key      = olm::client()->identity_keys().ed25519;
+        forward_key.forwarding_curve25519_key_chain = {};
+
+        send_megolm_key_to_device(req.sender, req.content.requesting_device_id, forward_key);
 }
 
 void
 send_megolm_key_to_device(const std::string &user_id,
                           const std::string &device_id,
-                          const json &payload)
+                          const mtx::events::msg::ForwardedRoomKey &payload)
 {
-        mtx::requests::QueryKeys req;
-        req.device_keys[user_id] = {device_id};
+        mtx::events::DeviceEvent room_key;
+        room_key.content = payload;
+        room_key.type    = mtx::events::EventType::ForwardedRoomKey;
 
-        http::client()->query_keys(
-          req,
-          [payload, user_id, device_id](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()->warn("retrieved device keys from {}, {}", user_id, device_id);
-
-                  if (res.device_keys.empty()) {
-                          nhlog::net()->warn("no devices retrieved {}", user_id);
-                          return;
-                  }
-
-                  auto device = res.device_keys.begin()->second;
-                  if (device.empty()) {
-                          nhlog::net()->warn("no keys retrieved from user, device {}", user_id);
-                          return;
-                  }
-
-                  const auto device_keys = device.begin()->second.keys;
-                  const auto curveKey    = "curve25519:" + device_id;
-                  const auto edKey       = "ed25519:" + device_id;
-
-                  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);
-                          return;
-                  }
-
-                  DevicePublicKeys pks;
-                  pks.ed25519    = device_keys.at(edKey);
-                  pks.curve25519 = device_keys.at(curveKey);
-
-                  try {
-                          if (!mtx::crypto::verify_identity_signature(json(device.begin()->second),
-                                                                      DeviceId(device_id),
-                                                                      UserId(user_id))) {
-                                  nhlog::crypto()->warn("failed to verify identity keys: {}",
-                                                        json(device).dump(2));
-                                  return;
-                          }
-                  } catch (const json::exception &e) {
-                          nhlog::crypto()->warn("failed to parse device key json: {}", e.what());
-                          return;
-                  } catch (const mtx::crypto::olm_exception &e) {
-                          nhlog::crypto()->warn("failed to verify device key json: {}", e.what());
-                          return;
-                  }
-
-                  auto room_key = olm::client()
-                                    ->create_room_key_event(UserId(user_id), pks.ed25519, payload)
-                                    .dump();
-
-                  mtx::requests::ClaimKeys claim_keys;
-                  claim_keys.one_time_keys[user_id][device_id] = mtx::crypto::SIGNED_CURVE25519;
-
-                  http::client()->claim_keys(
-                    claim_keys,
-                    [room_key, user_id, device_id, pks](const mtx::responses::ClaimKeys &res,
-                                                        mtx::http::RequestErr err) {
-                            if (err) {
-                                    nhlog::net()->warn("claim keys error: {} {} {}",
-                                                       err->matrix_error.error,
-                                                       err->parse_error,
-                                                       static_cast(err->status_code));
-                                    return;
-                            }
-
-                            nhlog::net()->info("claimed keys for {}", user_id);
-
-                            if (res.one_time_keys.size() == 0) {
-                                    nhlog::net()->info("no one-time keys found for user_id: {}",
-                                                       user_id);
-                                    return;
-                            }
-
-                            if (res.one_time_keys.find(user_id) == res.one_time_keys.end()) {
-                                    nhlog::net()->info("no one-time keys found for user_id: {}",
-                                                       user_id);
-                                    return;
-                            }
-
-                            auto retrieved_devices = res.one_time_keys.at(user_id);
-                            if (retrieved_devices.empty()) {
-                                    nhlog::net()->info("claiming keys for {}: no retrieved devices",
-                                                       device_id);
-                                    return;
-                            }
-
-                            json body;
-                            body["messages"][user_id] = json::object();
-
-                            auto device = retrieved_devices.begin()->second;
-                            nhlog::net()->debug("{} : \n {}", device_id, device.dump(2));
-
-                            json device_msg;
-
-                            try {
-                                    auto olm_session = olm::client()->create_outbound_session(
-                                      pks.curve25519, device.begin()->at("key"));
-
-                                    device_msg = olm::client()->create_olm_encrypted_content(
-                                      olm_session.get(), room_key, pks.curve25519);
-
-                                    cache::saveOlmSession(pks.curve25519, std::move(olm_session));
-                            } catch (const json::exception &e) {
-                                    nhlog::crypto()->warn("creating outbound session: {}",
-                                                          e.what());
-                                    return;
-                            } catch (const mtx::crypto::olm_exception &e) {
-                                    nhlog::crypto()->warn("creating outbound session: {}",
-                                                          e.what());
-                                    return;
-                            }
-
-                            body["messages"][user_id][device_id] = device_msg;
-
-                            nhlog::net()->info(
-                              "sending m.room_key event to {}:{}", user_id, device_id);
-                            http::client()->send_to_device(
-                              "m.room.encrypted", body, [user_id](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 send to {}", user_id);
-                              });
-                    });
-          });
+        std::map> targets;
+        targets[user_id] = {device_id};
+        send_encrypted_to_device_messages(targets, room_key);
 }
 
 DecryptionResult
@@ -727,4 +596,258 @@ decryptEvent(const MegolmSessionIndex &index,
 
         return {std::nullopt, std::nullopt, std::move(te.data)};
 }
+
+//! Send encrypted to device messages, targets is a map from userid to device ids or {} for all
+//! devices
+void
+send_encrypted_to_device_messages(const std::map> targets,
+                                  const mtx::events::collections::DeviceEvents &event,
+                                  bool force_new_session)
+{
+        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;
+
+        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);
+
+                        auto session =
+                          cache::getLatestOlmSession(d.keys.at("curve25519:" + device));
+                        if (!session || force_new_session) {
+                                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);
+                                continue;
+                        }
+
+                        messages[mtx::identifiers::parse(user)][device] =
+                          olm::client()
+                            ->create_olm_encrypted_content(session->get(),
+                                                           ev_json,
+                                                           UserId(user),
+                                                           d.keys.at("ed25519:" + device),
+                                                           d.keys.at("curve25519:" + device))
+                            .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;
+                                        }
+
+                                        // TODO: Verify signatures
+                                        auto otk = rd.second.begin()->at("key");
+
+                                        auto id_key = pks.at(user_id).at(device_id).curve25519;
+                                        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),
+                                              pks.at(user_id).at(device_id).ed25519,
+                                              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);
+                                          }
+                                  });
+                };
+        };
+
+        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](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);
+
+                                          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;
+                                          }
+
+                                          deviceKeys[user_id].emplace(device_id, pks);
+                                          claim_keys.one_time_keys[user.first][device_id] =
+                                            mtx::crypto::SIGNED_CURVE25519;
+
+                                          nhlog::net()->info("{}", device_id.get());
+                                          nhlog::net()->info("  curve25519 {}", pks.curve25519);
+                                          nhlog::net()->info("  ed25519 {}", pks.ed25519);
+                                  }
+                          }
+
+                          http::client()->claim_keys(claim_keys, BindPks(deviceKeys));
+                  });
+        }
+}
+
 } // namespace olm
diff --git a/src/Olm.h b/src/Olm.h
index 7b97039b..322affa1 100644
--- a/src/Olm.h
+++ b/src/Olm.h
@@ -96,11 +96,9 @@ mark_keys_as_published();
 
 //! Request the encryption keys from sender's device for the given event.
 void
-request_keys(const std::string &room_id, const std::string &event_id);
-
-void
-send_key_request_for(const std::string &room_id,
-                     const mtx::events::EncryptedEvent &);
+send_key_request_for(mtx::events::EncryptedEvent e,
+                     const std::string &request_id,
+                     bool cancel = false);
 
 void
 handle_key_request_message(const mtx::events::DeviceEvent &);
@@ -108,6 +106,13 @@ handle_key_request_message(const mtx::events::DeviceEvent> targets,
+                                  const mtx::events::collections::DeviceEvents &event,
+                                  bool force_new_session = false);
 
 } // namespace olm
diff --git a/src/RoomInfoListItem.cpp b/src/RoomInfoListItem.cpp
index f234b59b..985ab1b9 100644
--- a/src/RoomInfoListItem.cpp
+++ b/src/RoomInfoListItem.cpp
@@ -203,10 +203,7 @@ RoomInfoListItem::init(QWidget *parent)
         });
 }
 
-RoomInfoListItem::RoomInfoListItem(QString room_id,
-                                   const RoomInfo &info,
-                                   QSharedPointer userSettings,
-                                   QWidget *parent)
+RoomInfoListItem::RoomInfoListItem(QString room_id, const RoomInfo &info, QWidget *parent)
   : QWidget(parent)
   , roomType_{info.is_invite ? RoomType::Invited : RoomType::Joined}
   , roomId_(std::move(room_id))
@@ -214,7 +211,6 @@ RoomInfoListItem::RoomInfoListItem(QString room_id,
   , isPressed_(false)
   , unreadMsgCount_(0)
   , unreadHighlightedMsgCount_(0)
-  , settings(userSettings)
 {
         init(parent);
 }
@@ -451,7 +447,7 @@ RoomInfoListItem::calculateImportance() const
         // returns ImportanceDisabled or Invite
         if (isInvite()) {
                 return Invite;
-        } else if (!settings->sortByImportance()) {
+        } else if (!ChatPage::instance()->userSettings()->sortByImportance()) {
                 return ImportanceDisabled;
         } else if (unreadHighlightedMsgCount_) {
                 return NewMentions;
diff --git a/src/RoomInfoListItem.h b/src/RoomInfoListItem.h
index e609f4d8..da5a1bc4 100644
--- a/src/RoomInfoListItem.h
+++ b/src/RoomInfoListItem.h
@@ -64,10 +64,7 @@ class RoomInfoListItem : public QWidget
         Q_PROPERTY(QColor btnTextColor READ btnTextColor WRITE setBtnTextColor)
 
 public:
-        RoomInfoListItem(QString room_id,
-                         const RoomInfo &info,
-                         QSharedPointer userSettings,
-                         QWidget *parent = nullptr);
+        RoomInfoListItem(QString room_id, const RoomInfo &info, QWidget *parent = nullptr);
 
         void updateUnreadMessageCount(int count, int highlightedCount);
         void clearUnreadMessageCount() { updateUnreadMessageCount(0, 0); };
@@ -220,6 +217,4 @@ private:
 
         QColor bubbleBgColor_;
         QColor bubbleFgColor_;
-
-        QSharedPointer settings;
 };
diff --git a/src/RoomList.cpp b/src/RoomList.cpp
index b4c507b5..8c9e296f 100644
--- a/src/RoomList.cpp
+++ b/src/RoomList.cpp
@@ -35,7 +35,6 @@
 
 RoomList::RoomList(QSharedPointer userSettings, QWidget *parent)
   : QWidget(parent)
-  , settings(userSettings)
 {
         topLayout_ = new QVBoxLayout(this);
         topLayout_->setSpacing(0);
@@ -76,7 +75,7 @@ RoomList::RoomList(QSharedPointer userSettings, QWidget *parent)
 void
 RoomList::addRoom(const QString &room_id, const RoomInfo &info)
 {
-        auto room_item = new RoomInfoListItem(room_id, info, settings, scrollArea_);
+        auto room_item = new RoomInfoListItem(room_id, info, scrollArea_);
         room_item->setRoomName(QString::fromStdString(std::move(info.name)));
 
         connect(room_item, &RoomInfoListItem::clicked, this, &RoomList::highlightSelectedRoom);
@@ -84,7 +83,7 @@ RoomList::addRoom(const QString &room_id, const RoomInfo &info)
                 MainWindow::instance()->openLeaveRoomDialog(room_id);
         });
 
-        QSharedPointer roomWidget(room_item);
+        QSharedPointer roomWidget(room_item, &QObject::deleteLater);
         rooms_.emplace(room_id, roomWidget);
         rooms_sort_cache_.push_back(roomWidget);
 
@@ -164,11 +163,6 @@ RoomList::initialize(const QMap &info)
 
         // prevent flickering and save time sorting over and over again
         setUpdatesEnabled(false);
-        disconnect(settings.data(),
-                   &UserSettings::roomSortingChanged,
-                   this,
-                   &RoomList::sortRoomsByLastMessage);
-
         for (auto it = info.begin(); it != info.end(); it++) {
                 if (it.value().is_invite)
                         addInvitedRoom(it.key(), it.value());
@@ -179,10 +173,6 @@ RoomList::initialize(const QMap &info)
         for (auto it = info.begin(); it != info.end(); it++)
                 updateRoomDescription(it.key(), it.value().msgInfo);
 
-        connect(settings.data(),
-                &UserSettings::roomSortingChanged,
-                this,
-                &RoomList::sortRoomsByLastMessage);
         setUpdatesEnabled(true);
 
         if (rooms_.empty())
@@ -505,7 +495,7 @@ RoomList::updateRoom(const QString &room_id, const RoomInfo &info)
 void
 RoomList::addInvitedRoom(const QString &room_id, const RoomInfo &info)
 {
-        auto room_item = new RoomInfoListItem(room_id, info, settings, scrollArea_);
+        auto room_item = new RoomInfoListItem(room_id, info, scrollArea_);
 
         connect(room_item, &RoomInfoListItem::acceptInvite, this, &RoomList::acceptInvite);
         connect(room_item, &RoomInfoListItem::declineInvite, this, &RoomList::declineInvite);
diff --git a/src/RoomList.h b/src/RoomList.h
index d3470666..d50c7de1 100644
--- a/src/RoomList.h
+++ b/src/RoomList.h
@@ -104,5 +104,4 @@ private:
         QString selectedRoom_;
 
         bool isSortPending_ = false;
-        QSharedPointer settings;
 };
diff --git a/src/TextInputWidget.cpp b/src/TextInputWidget.cpp
index 22e8aafc..e6a10f0a 100644
--- a/src/TextInputWidget.cpp
+++ b/src/TextInputWidget.cpp
@@ -165,6 +165,9 @@ FilteredTextEdit::keyPressEvent(QKeyEvent *event)
                 MacHelper::showEmojiWindow();
 #endif
 
+        if (event->modifiers() == Qt::ControlModifier && event->key() == Qt::Key_U)
+                QTextEdit::setText("");
+
         if (!isModifier) {
                 if (!typingTimer_->isActive())
                         emit startedTyping();
diff --git a/src/UserSettingsPage.cpp b/src/UserSettingsPage.cpp
index f04193c9..308ec9a2 100644
--- a/src/UserSettingsPage.cpp
+++ b/src/UserSettingsPage.cpp
@@ -17,7 +17,9 @@
 
 #include 
 #include 
+#include 
 #include 
+#include 
 #include 
 #include 
 #include 
@@ -73,8 +75,11 @@ UserSettings::load()
         font_                = settings.value("user/font_family", "default").toString();
         avatarCircles_       = settings.value("user/avatar_circles", true).toBool();
         decryptSidebar_      = settings.value("user/decrypt_sidebar", true).toBool();
-        emojiFont_           = settings.value("user/emoji_font_family", "default").toString();
-        baseFontSize_        = settings.value("user/font_size", QFont().pointSizeF()).toDouble();
+        shareKeysWithTrustedUsers_ =
+          settings.value("user/share_keys_with_trusted_users", true).toBool();
+        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();
         presence_ =
           settings.value("user/presence", QVariant::fromValue(Presence::AutomaticPresence))
             .value();
@@ -124,6 +129,16 @@ UserSettings::setStartInTray(bool state)
         save();
 }
 
+void
+UserSettings::setMobileMode(bool state)
+{
+        if (state == mobileMode_)
+                return;
+        mobileMode_ = state;
+        emit mobileModeChanged(state);
+        save();
+}
+
 void
 UserSettings::setGroupView(bool state)
 {
@@ -295,6 +310,17 @@ UserSettings::setUseStunServer(bool useStunServer)
         save();
 }
 
+void
+UserSettings::setShareKeysWithTrustedUsers(bool shareKeys)
+{
+        if (shareKeys == shareKeysWithTrustedUsers_)
+                return;
+
+        shareKeysWithTrustedUsers_ = shareKeys;
+        emit shareKeysWithTrustedUsersChanged(shareKeys);
+        save();
+}
+
 void
 UserSettings::setMicrophone(QString microphone)
 {
@@ -347,17 +373,18 @@ UserSettings::applyTheme()
                   /*windowText*/ QColor("#333"),
                   /*button*/ QColor("#333"),
                   /*light*/ QColor(0xef, 0xef, 0xef),
-                  /*dark*/ QColor(220, 220, 220),
-                  /*mid*/ QColor(110, 110, 110),
+                  /*dark*/ QColor(110, 110, 110),
+                  /*mid*/ QColor(220, 220, 220),
                   /*text*/ QColor("#333"),
                   /*bright_text*/ QColor("#333"),
-                  /*base*/ QColor("#eee"),
+                  /*base*/ QColor("#fff"),
                   /*window*/ QColor("white"));
+                lightActive.setColor(QPalette::AlternateBase, QColor("#eee"));
                 lightActive.setColor(QPalette::Highlight, QColor("#38a3d8"));
                 lightActive.setColor(QPalette::ToolTipBase, lightActive.base().color());
                 lightActive.setColor(QPalette::ToolTipText, lightActive.text().color());
                 lightActive.setColor(QPalette::Link, QColor("#0077b5"));
-                lightActive.setColor(QPalette::ButtonText, QColor(Qt::gray));
+                lightActive.setColor(QPalette::ButtonText, QColor("#495057"));
                 QApplication::setPalette(lightActive);
         } else if (this->theme() == "dark") {
                 stylefile.setFileName(":/styles/styles/nheko-dark.qss");
@@ -365,17 +392,18 @@ UserSettings::applyTheme()
                   /*windowText*/ QColor("#caccd1"),
                   /*button*/ QColor(0xff, 0xff, 0xff),
                   /*light*/ QColor("#caccd1"),
-                  /*dark*/ QColor("#2d3139"),
-                  /*mid*/ QColor(110, 110, 110),
+                  /*dark*/ QColor(110, 110, 110),
+                  /*mid*/ QColor("#202228"),
                   /*text*/ QColor("#caccd1"),
                   /*bright_text*/ QColor(0xff, 0xff, 0xff),
-                  /*base*/ QColor("#2d3139"),
-                  /*window*/ QColor("#202228"));
+                  /*base*/ QColor("#202228"),
+                  /*window*/ QColor("#2d3139"));
+                darkActive.setColor(QPalette::AlternateBase, QColor("#2d3139"));
                 darkActive.setColor(QPalette::Highlight, QColor("#38a3d8"));
                 darkActive.setColor(QPalette::ToolTipBase, darkActive.base().color());
                 darkActive.setColor(QPalette::ToolTipText, darkActive.text().color());
                 darkActive.setColor(QPalette::Link, QColor("#38a3d8"));
-                darkActive.setColor(QPalette::ButtonText, QColor(0x90, 0x90, 0x90));
+                darkActive.setColor(QPalette::ButtonText, "#727274");
                 QApplication::setPalette(darkActive);
         } else {
                 stylefile.setFileName(":/styles/styles/system.qss");
@@ -408,6 +436,8 @@ UserSettings::save()
 
         settings.setValue("avatar_circles", avatarCircles_);
         settings.setValue("decrypt_sidebar", decryptSidebar_);
+        settings.setValue("share_keys_with_trusted_users", shareKeysWithTrustedUsers_);
+        settings.setValue("mobile_mode", mobileMode_);
         settings.setValue("font_size", baseFontSize_);
         settings.setValue("typing_notifications", typingNotifications_);
         settings.setValue("minor_events", sortByImportance_);
@@ -456,6 +486,9 @@ UserSettingsPage::UserSettingsPage(QSharedPointer settings, QWidge
         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);
 
         topBarLayout_ = new QHBoxLayout;
@@ -476,30 +509,32 @@ UserSettingsPage::UserSettingsPage(QSharedPointer settings, QWidge
         general_->setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Fixed);
         general_->setFont(font);
 
-        trayToggle_               = new Toggle{this};
-        startInTrayToggle_        = new Toggle{this};
-        avatarCircles_            = new Toggle{this};
-        decryptSidebar_           = 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};
-        desktopNotifications_     = new Toggle{this};
-        alertOnNotification_      = new Toggle{this};
-        useStunServer_            = new Toggle{this};
-        scaleFactorCombo_         = new QComboBox{this};
-        fontSizeCombo_            = new QComboBox{this};
-        fontSelectionCombo_       = new QComboBox{this};
-        emojiFontSelectionCombo_  = new QComboBox{this};
-        microphoneCombo_          = new QComboBox{this};
-        cameraCombo_              = new QComboBox{this};
-        cameraResolutionCombo_    = new QComboBox{this};
-        cameraFrameRateCombo_     = new QComboBox{this};
-        timelineMaxWidthSpin_     = new QSpinBox{this};
+        trayToggle_                = new Toggle{this};
+        startInTrayToggle_         = new Toggle{this};
+        avatarCircles_             = new Toggle{this};
+        decryptSidebar_            = new Toggle(this);
+        shareKeysWithTrustedUsers_ = 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};
+        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};
+        microphoneCombo_           = new QComboBox{this};
+        cameraCombo_               = new QComboBox{this};
+        cameraResolutionCombo_     = new QComboBox{this};
+        cameraFrameRateCombo_      = new QComboBox{this};
+        timelineMaxWidthSpin_      = new QSpinBox{this};
 
         if (!settings_->tray())
                 startInTrayToggle_->setDisabled(true);
@@ -517,10 +552,6 @@ UserSettingsPage::UserSettingsPage(QSharedPointer settings, QWidge
                 fontSizeCombo_->addItem(QString("%1 ").arg(QString::number(option)));
 
         QFontDatabase fontDb;
-        auto fontFamilies = fontDb.families();
-        for (const auto &family : fontFamilies) {
-                fontSelectionCombo_->addItem(family);
-        }
 
         // TODO: Is there a way to limit to just emojis, rather than
         // all emoji fonts?
@@ -666,6 +697,9 @@ UserSettingsPage::UserSettingsPage(QSharedPointer settings, QWidge
         formLayout_->addRow(uiLabel_);
         formLayout_->addRow(new HorizontalLine{this});
 
+        boxWrap(tr("Mobile mode"),
+                mobileMode_,
+                tr("Will prevent text selection in the timeline to make scrolling easier."));
 #if !defined(Q_OS_MAC)
         boxWrap(tr("Scale factor"),
                 scaleFactorCombo_,
@@ -702,6 +736,10 @@ UserSettingsPage::UserSettingsPage(QSharedPointer settings, QWidge
         formLayout_->addRow(new HorizontalLine{this});
         boxWrap(tr("Device ID"), deviceIdValue_);
         boxWrap(tr("Device Fingerprint"), deviceFingerprintValue_);
+        boxWrap(
+          tr("Share keys with trusted users"),
+          shareKeysWithTrustedUsers_,
+          tr("Automatically replies to key requests from other users, if they are verified."));
         formLayout_->addRow(new HorizontalLine{this});
         formLayout_->addRow(sessionKeysLabel, sessionKeysLayout);
 
@@ -793,6 +831,10 @@ UserSettingsPage::UserSettingsPage(QSharedPointer settings, QWidge
                 settings_->setStartInTray(!disabled);
         });
 
+        connect(mobileMode_, &Toggle::toggled, this, [this](bool disabled) {
+                settings_->setMobileMode(!disabled);
+        });
+
         connect(groupViewToggle_, &Toggle::toggled, this, [this](bool disabled) {
                 settings_->setGroupView(!disabled);
         });
@@ -802,6 +844,10 @@ UserSettingsPage::UserSettingsPage(QSharedPointer settings, QWidge
                 emit decryptSidebarChanged();
         });
 
+        connect(shareKeysWithTrustedUsers_, &Toggle::toggled, this, [this](bool disabled) {
+                settings_->setShareKeysWithTrustedUsers(!disabled);
+        });
+
         connect(avatarCircles_, &Toggle::toggled, this, [this](bool disabled) {
                 settings_->setAvatarCircles(!disabled);
         });
@@ -876,10 +922,12 @@ UserSettingsPage::showEvent(QShowEvent *)
         startInTrayToggle_->setState(!settings_->startInTray());
         groupViewToggle_->setState(!settings_->groupView());
         decryptSidebar_->setState(!settings_->decryptSidebar());
+        shareKeysWithTrustedUsers_->setState(!settings_->shareKeysWithTrustedUsers());
         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());
diff --git a/src/UserSettingsPage.h b/src/UserSettingsPage.h
index 9d291303..0bd0ac84 100644
--- a/src/UserSettingsPage.h
+++ b/src/UserSettingsPage.h
@@ -27,6 +27,7 @@ class Toggle;
 class QLabel;
 class QFormLayout;
 class QComboBox;
+class QFontComboBox;
 class QSpinBox;
 class QHBoxLayout;
 class QVBoxLayout;
@@ -66,19 +67,22 @@ class UserSettings : public QObject
                      decryptSidebarChanged)
         Q_PROPERTY(int timelineMaxWidth READ timelineMaxWidth WRITE setTimelineMaxWidth NOTIFY
                      timelineMaxWidthChanged)
+        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(
-          bool useStunServer READ useStunServer WRITE setUseStunServer NOTIFY useStunServerChanged)
         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(
+          bool useStunServer READ useStunServer WRITE setUseStunServer NOTIFY useStunServerChanged)
+        Q_PROPERTY(bool shareKeysWithTrustedUsers READ shareKeysWithTrustedUsers WRITE
+                     setShareKeysWithTrustedUsers NOTIFY shareKeysWithTrustedUsersChanged)
 
 public:
         UserSettings();
@@ -100,6 +104,7 @@ public:
         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);
@@ -120,6 +125,7 @@ public:
         void setCameraResolution(QString resolution);
         void setCameraFrameRate(QString frameRate);
         void setUseStunServer(bool state);
+        void setShareKeysWithTrustedUsers(bool state);
 
         QString theme() const { return !theme_.isEmpty() ? theme_ : defaultTheme_; }
         bool messageHoverHighlight() const { return messageHoverHighlight_; }
@@ -133,6 +139,7 @@ public:
         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_; }
@@ -150,6 +157,7 @@ public:
         QString cameraResolution() const { return cameraResolution_; }
         QString cameraFrameRate() const { return cameraFrameRate_; }
         bool useStunServer() const { return useStunServer_; }
+        bool shareKeysWithTrustedUsers() const { return shareKeysWithTrustedUsers_; }
 
 signals:
         void groupViewStateChanged(bool state);
@@ -168,6 +176,7 @@ signals:
         void avatarCirclesChanged(bool state);
         void decryptSidebarChanged(bool state);
         void timelineMaxWidthChanged(int state);
+        void mobileModeChanged(bool mode);
         void fontSizeChanged(double state);
         void fontChanged(QString state);
         void emojiFontChanged(QString state);
@@ -177,6 +186,7 @@ signals:
         void cameraResolutionChanged(QString resolution);
         void cameraFrameRateChanged(QString frameRate);
         void useStunServerChanged(bool state);
+        void shareKeysWithTrustedUsersChanged(bool state);
 
 private:
         // Default to system theme if QT_QPA_PLATFORMTHEME var is set.
@@ -199,6 +209,8 @@ private:
         bool hasAlertOnNotification_;
         bool avatarCircles_;
         bool decryptSidebar_;
+        bool shareKeysWithTrustedUsers_;
+        bool mobileMode_;
         int timelineMaxWidth_;
         double baseFontSize_;
         QString font_;
@@ -264,13 +276,15 @@ private:
         Toggle *avatarCircles_;
         Toggle *useStunServer_;
         Toggle *decryptSidebar_;
+        Toggle *shareKeysWithTrustedUsers_;
+        Toggle *mobileMode_;
         QLabel *deviceFingerprintValue_;
         QLabel *deviceIdValue_;
 
         QComboBox *themeCombo_;
         QComboBox *scaleFactorCombo_;
         QComboBox *fontSizeCombo_;
-        QComboBox *fontSelectionCombo_;
+        QFontComboBox *fontSelectionCombo_;
         QComboBox *emojiFontSelectionCombo_;
         QComboBox *microphoneCombo_;
         QComboBox *cameraCombo_;
diff --git a/src/main.cpp b/src/main.cpp
index e02ffa36..6fbccf5c 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -104,10 +104,32 @@ createCacheDirectory()
 int
 main(int argc, char *argv[])
 {
-        // needed for settings so need to register before any settings are read to prevent warings
+        // needed for settings so need to register before any settings are read to prevent warnings
         qRegisterMetaType();
 
-        QCoreApplication::setApplicationName("nheko");
+        // This is some hacky programming, but it's necessary (AFAIK?) to get the unique config name
+        // parsed before the app name is set.
+        QString appName{"nheko"};
+        for (int i = 0; i < argc; ++i) {
+                if (QString{argv[i]}.startsWith("--profile=")) {
+                        QString q{argv[i]};
+                        q.remove("--profile=");
+                        appName += "-" + q;
+                } else if (QString{argv[i]}.startsWith("--p=")) {
+                        QString q{argv[i]};
+                        q.remove("-p=");
+                        appName += "-" + q;
+                } else if (QString{argv[i]} == "--profile" || QString{argv[i]} == "-p") {
+                        if (i < argc - 1) // if i is less than argc - 1, we still have a parameter
+                                          // left to process as the name
+                        {
+                                ++i; // the next arg is the name, so increment
+                                appName += "-" + QString{argv[i]};
+                        }
+                }
+        }
+
+        QCoreApplication::setApplicationName(appName);
         QCoreApplication::setApplicationVersion(nheko::version);
         QCoreApplication::setOrganizationName("nheko");
         QCoreApplication::setAttribute(Qt::AA_DontCreateNativeWidgetSiblings);
@@ -137,6 +159,19 @@ main(int argc, char *argv[])
         parser.addVersionOption();
         QCommandLineOption debugOption("debug", "Enable debug output");
         parser.addOption(debugOption);
+
+        // This option is not actually parsed via Qt due to the need to parse it before the app
+        // name is set. It only exists to keep Qt from complaining about the --profile/-p
+        // option and thereby crashing the app.
+        QCommandLineOption configName(
+          QStringList() << "p"
+                        << "profile",
+          QCoreApplication::tr("Create a unique profile, which allows you to log into several "
+                               "accounts at the same time and start multiple instances of nheko."),
+          QCoreApplication::tr("profile"),
+          QCoreApplication::tr("profile name"));
+        parser.addOption(configName);
+
         parser.process(app);
 
         app.setWindowIcon(QIcon(":/logos/nheko.png"));
@@ -181,7 +216,7 @@ main(int argc, char *argv[])
         appTranslator.load(QLocale(), "nheko", "_", ":/translations");
         app.installTranslator(&appTranslator);
 
-        MainWindow w;
+        MainWindow w{(appName == "nheko" ? "" : appName.remove("nheko-"))};
 
         // Move the MainWindow to the center
         w.move(screenCenter(w.width(), w.height()));
diff --git a/src/timeline/EventStore.cpp b/src/timeline/EventStore.cpp
index d3c5c3fa..38292f49 100644
--- a/src/timeline/EventStore.cpp
+++ b/src/timeline/EventStore.cpp
@@ -54,6 +54,12 @@ EventStore::EventStore(std::string room_id, QObject *)
           &EventStore::oldMessagesRetrieved,
           this,
           [this](const mtx::responses::Messages &res) {
+                  if (cache::client()->previousBatchToken(room_id_) == res.end) {
+                          noMoreMessages = true;
+                          emit fetchedMore();
+                          return;
+                  }
+
                   uint64_t newFirst = cache::client()->saveOldMessages(room_id_, res);
                   if (newFirst == first)
                           fetchMore();
@@ -209,6 +215,28 @@ EventStore::clearTimeline()
         emit endResetModel();
 }
 
+void
+EventStore::receivedSessionKey(const std::string &session_id)
+{
+        if (!pending_key_requests.count(session_id))
+                return;
+
+        auto request = pending_key_requests.at(session_id);
+        pending_key_requests.erase(session_id);
+
+        olm::send_key_request_for(request.events.front(), request.request_id, true);
+
+        for (const auto &e : request.events) {
+                auto idx = idToIndex(e.event_id);
+                if (idx) {
+                        decryptedEvents_.remove({room_id_, e.event_id});
+                        events_by_id_.remove({room_id_, e.event_id});
+                        events_.remove({room_id_, toInternalIdx(*idx)});
+                        emit dataChanged(*idx, *idx);
+                }
+        }
+}
+
 void
 EventStore::handleSync(const mtx::responses::Timeline &events)
 {
@@ -291,18 +319,6 @@ EventStore::handleSync(const mtx::responses::Timeline &events)
                               *d_event)) {
                                 handle_room_verification(*d_event);
                         }
-                        // else {
-                        //        // only the key.verification.ready sent by localuser's other
-                        //        device
-                        //        // is of significance as it is used for detecting accepted request
-                        //        if (std::get_if>(d_event)) {
-                        //                auto msg = std::get_if>(d_event);
-                        //                ChatPage::instance()->receivedDeviceVerificationReady(
-                        //                  msg->content);
-                        //        }
-                        //}
                 }
         }
 }
@@ -498,7 +514,7 @@ EventStore::decryptEvent(const IdIndex &idx,
 
         if (decryptionResult.error) {
                 switch (*decryptionResult.error) {
-                case olm::DecryptionErrorCode::MissingSession:
+                case olm::DecryptionErrorCode::MissingSession: {
                         dummy.content.body =
                           tr("-- Encrypted Event (No keys found for decryption) --",
                              "Placeholder, when the message was not decrypted yet or can't be "
@@ -509,8 +525,21 @@ EventStore::decryptEvent(const IdIndex &idx,
                                               index.session_id,
                                               e.sender);
                         // TODO: Check if this actually works and look in key backup
-                        olm::send_key_request_for(room_id_, e);
+                        auto copy    = e;
+                        copy.room_id = room_id_;
+                        if (pending_key_requests.count(e.content.session_id)) {
+                                pending_key_requests.at(e.content.session_id)
+                                  .events.push_back(copy);
+                        } else {
+                                PendingKeyRequests request;
+                                request.request_id =
+                                  "key_request." + http::client()->generate_txn_id();
+                                request.events.push_back(copy);
+                                olm::send_key_request_for(copy, request.request_id);
+                                pending_key_requests[e.content.session_id] = request;
+                        }
                         break;
+                }
                 case olm::DecryptionErrorCode::DbError:
                         nhlog::db()->critical(
                           "failed to retrieve megolm session with index ({}, {}, {})",
@@ -687,6 +716,9 @@ EventStore::get(std::string_view id, std::string_view related_to, bool decrypt)
 void
 EventStore::fetchMore()
 {
+        if (noMoreMessages)
+                return;
+
         mtx::http::MessagesOpts opts;
         opts.room_id = room_id_;
         opts.from    = cache::client()->previousBatchToken(room_id_);
diff --git a/src/timeline/EventStore.h b/src/timeline/EventStore.h
index 954e271c..2d5fb1be 100644
--- a/src/timeline/EventStore.h
+++ b/src/timeline/EventStore.h
@@ -104,6 +104,7 @@ signals:
 
 public slots:
         void addPending(mtx::events::collections::TimelineEvents event);
+        void receivedSessionKey(const std::string &session_id);
         void clearTimeline();
 
 private:
@@ -121,6 +122,14 @@ private:
         static QCache events_;
         static QCache events_by_id_;
 
+        struct PendingKeyRequests
+        {
+                std::string request_id;
+                std::vector> events;
+        };
+        std::map pending_key_requests;
+
         std::string current_txn;
         int current_txn_error_count = 0;
+        bool noMoreMessages         = false;
 };
diff --git a/src/timeline/TimelineModel.cpp b/src/timeline/TimelineModel.cpp
index 359e95bc..8b80ea51 100644
--- a/src/timeline/TimelineModel.cpp
+++ b/src/timeline/TimelineModel.cpp
@@ -930,11 +930,12 @@ TimelineModel::sendEncryptedMessage(mtx::events::RoomEvent msg, mtx::events::
                 const auto session_id  = mtx::crypto::session_id(outbound_session.get());
                 const auto session_key = mtx::crypto::session_key(outbound_session.get());
 
-                // TODO: needs to be moved in the lib.
-                auto megolm_payload = json{{"algorithm", "m.megolm.v1.aes-sha2"},
-                                           {"room_id", room_id},
-                                           {"session_id", session_id},
-                                           {"session_key", session_key}};
+                mtx::events::DeviceEvent megolm_payload;
+                megolm_payload.content.algorithm   = "m.megolm.v1.aes-sha2";
+                megolm_payload.content.room_id     = room_id;
+                megolm_payload.content.session_id  = session_id;
+                megolm_payload.content.session_key = session_key;
+                megolm_payload.type                = mtx::events::EventType::RoomKey;
 
                 // Saving the new megolm session.
                 // TODO: Maybe it's too early to save.
@@ -958,122 +959,29 @@ TimelineModel::sendEncryptedMessage(mtx::events::RoomEvent msg, mtx::events::
                 const auto members = cache::roomMembers(room_id);
                 nhlog::ui()->info("retrieved {} members for {}", members.size(), room_id);
 
-                auto keeper =
-                  std::make_shared([room_id, doc, txn_id = msg.event_id, this]() {
-                          try {
-                                  mtx::events::EncryptedEvent event;
-                                  event.content = olm::encrypt_group_message(
-                                    room_id, http::client()->device_id(), doc);
-                                  event.event_id         = txn_id;
-                                  event.room_id          = room_id;
-                                  event.sender           = http::client()->user_id().to_string();
-                                  event.type             = mtx::events::EventType::RoomEncrypted;
-                                  event.origin_server_ts = QDateTime::currentMSecsSinceEpoch();
-
-                                  emit this->addPendingMessageToStore(event);
-                          } catch (const lmdb::error &e) {
-                                  nhlog::db()->critical(
-                                    "failed to save megolm outbound session: {}", e.what());
-                                  emit ChatPage::instance()->showNotification(
-                                    tr("Failed to encrypt event, sending aborted!"));
-                          }
-                  });
-
-                mtx::requests::QueryKeys req;
+                std::map> targets;
                 for (const auto &member : members)
-                        req.device_keys[member] = {};
+                        targets[member] = {};
 
-                http::client()->query_keys(
-                  req,
-                  [keeper = std::move(keeper), megolm_payload, txn_id = msg.event_id, this](
-                    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));
-                                  emit ChatPage::instance()->showNotification(
-                                    tr("Failed to encrypt event, sending aborted!"));
-                                  return;
-                          }
+                olm::send_encrypted_to_device_messages(targets, megolm_payload);
 
-                          mtx::requests::ClaimKeys claim_keys;
+                try {
+                        mtx::events::EncryptedEvent event;
+                        event.content =
+                          olm::encrypt_group_message(room_id, http::client()->device_id(), doc);
+                        event.event_id         = msg.event_id;
+                        event.room_id          = room_id;
+                        event.sender           = http::client()->user_id().to_string();
+                        event.type             = mtx::events::EventType::RoomEncrypted;
+                        event.origin_server_ts = QDateTime::currentMSecsSinceEpoch();
 
-                          // Mapping from user id to a device_id with valid identity keys to the
-                          // generated room_key event used for sharing the megolm session.
-                          std::map> room_key_msgs;
-                          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);
-
-                                          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 room_key = olm::client()
-                                                            ->create_room_key_event(
-                                                              user_id, pks.ed25519, megolm_payload)
-                                                            .dump();
-
-                                          room_key_msgs[user_id].emplace(device_id, room_key);
-                                          deviceKeys[user_id].emplace(device_id, pks);
-                                          claim_keys.one_time_keys[user.first][device_id] =
-                                            mtx::crypto::SIGNED_CURVE25519;
-
-                                          nhlog::net()->info("{}", device_id.get());
-                                          nhlog::net()->info("  curve25519 {}", pks.curve25519);
-                                          nhlog::net()->info("  ed25519 {}", pks.ed25519);
-                                  }
-                          }
-
-                          http::client()->claim_keys(claim_keys,
-                                                     std::bind(&TimelineModel::handleClaimedKeys,
-                                                               this,
-                                                               keeper,
-                                                               room_key_msgs,
-                                                               deviceKeys,
-                                                               std::placeholders::_1,
-                                                               std::placeholders::_2));
-                  });
+                        emit this->addPendingMessageToStore(event);
+                } catch (const lmdb::error &e) {
+                        nhlog::db()->critical("failed to save megolm outbound session: {}",
+                                              e.what());
+                        emit ChatPage::instance()->showNotification(
+                          tr("Failed to encrypt event, sending aborted!"));
+                }
 
                 // TODO: Let the user know about the errors.
         } catch (const lmdb::error &e) {
@@ -1089,86 +997,6 @@ TimelineModel::sendEncryptedMessage(mtx::events::RoomEvent msg, mtx::events::
         }
 }
 
-void
-TimelineModel::handleClaimedKeys(
-  std::shared_ptr keeper,
-  const std::map> &room_keys,
-  const std::map> &pks,
-  const mtx::responses::ClaimKeys &res,
-  mtx::http::RequestErr err)
-{
-        if (err) {
-                nhlog::net()->warn("claim keys error: {} {} {}",
-                                   err->matrix_error.error,
-                                   err->parse_error,
-                                   static_cast(err->status_code));
-                return;
-        }
-
-        // Payload with all the to_device message to be sent.
-        nlohmann::json body;
-
-        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);
-                        return;
-                }
-
-                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;
-                        }
-
-                        // TODO: Verify signatures
-                        auto otk = rd.second.begin()->at("key");
-
-                        auto id_key = pks.at(user_id).at(device_id).curve25519;
-                        auto s      = olm::client()->create_outbound_session(id_key, otk);
-
-                        auto device_msg = olm::client()->create_olm_encrypted_content(
-                          s.get(),
-                          room_keys.at(user_id).at(device_id),
-                          pks.at(user_id).at(device_id).curve25519);
-
-                        try {
-                                cache::saveOlmSession(id_key, std::move(s));
-                        } 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());
-                        }
-
-                        body["messages"][user_id][device_id] = device_msg;
-                }
-
-                nhlog::net()->info("send_to_device: {}", user_id);
-        }
-
-        http::client()->send_to_device(
-          mtx::events::to_string(mtx::events::EventType::RoomEncrypted),
-          http::client()->generate_txn_id(),
-          body,
-          [keeper](mtx::http::RequestErr err) {
-                  if (err) {
-                          nhlog::net()->warn("failed to send "
-                                             "send_to_device "
-                                             "message: {}",
-                                             err->matrix_error.error);
-                  }
-
-                  (void)keeper;
-          });
-}
-
 struct SendMessageVisitor
 {
         explicit SendMessageVisitor(TimelineModel *model)
diff --git a/src/timeline/TimelineModel.h b/src/timeline/TimelineModel.h
index 3234a20c..e1fb9196 100644
--- a/src/timeline/TimelineModel.h
+++ b/src/timeline/TimelineModel.h
@@ -264,6 +264,10 @@ public slots:
         }
         void setDecryptDescription(bool decrypt) { decryptDescription = decrypt; }
         void clearTimeline() { events.clearTimeline(); }
+        void receivedSessionKey(const std::string &session_key)
+        {
+                events.receivedSessionKey(session_key);
+        }
 
         QString roomName() const;
         QString roomTopic() const;
@@ -297,12 +301,6 @@ signals:
 private:
         template
         void sendEncryptedMessage(mtx::events::RoomEvent msg, mtx::events::EventType eventType);
-        void handleClaimedKeys(
-          std::shared_ptr keeper,
-          const std::map> &room_keys,
-          const std::map> &pks,
-          const mtx::responses::ClaimKeys &res,
-          mtx::http::RequestErr err);
         void readEvent(const std::string &id);
 
         void setPaginationInProgress(const bool paginationInProgress);
diff --git a/src/timeline/TimelineViewManager.cpp b/src/timeline/TimelineViewManager.cpp
index 353f7065..598af31e 100644
--- a/src/timeline/TimelineViewManager.cpp
+++ b/src/timeline/TimelineViewManager.cpp
@@ -30,7 +30,7 @@ namespace msgs = mtx::events::msg;
 void
 TimelineViewManager::updateEncryptedDescriptions()
 {
-        auto decrypt = settings->decryptSidebar();
+        auto decrypt = ChatPage::instance()->userSettings()->decryptSidebar();
         QHash>::iterator i;
         for (i = models.begin(); i != models.end(); ++i) {
                 auto ptr = i.value();
@@ -47,10 +47,10 @@ TimelineViewManager::updateColorPalette()
 {
         userColors.clear();
 
-        if (settings->theme() == "light") {
+        if (ChatPage::instance()->userSettings()->theme() == "light") {
                 view->rootContext()->setContextProperty("currentActivePalette", QPalette());
                 view->rootContext()->setContextProperty("currentInactivePalette", QPalette());
-        } else if (settings->theme() == "dark") {
+        } else if (ChatPage::instance()->userSettings()->theme() == "dark") {
                 view->rootContext()->setContextProperty("currentActivePalette", QPalette());
                 view->rootContext()->setContextProperty("currentInactivePalette", QPalette());
         } else {
@@ -84,14 +84,11 @@ TimelineViewManager::userStatus(QString id) const
         return QString::fromStdString(cache::statusMessage(id.toStdString()));
 }
 
-TimelineViewManager::TimelineViewManager(QSharedPointer userSettings,
-                                         CallManager *callManager,
-                                         ChatPage *parent)
+TimelineViewManager::TimelineViewManager(CallManager *callManager, ChatPage *parent)
   : imgProvider(new MxcImageProvider())
   , colorImgProvider(new ColorImageProvider())
   , blurhashProvider(new BlurhashProvider())
   , callManager_(callManager)
-  , settings(userSettings)
 {
         qRegisterMetaType();
         qRegisterMetaType();
@@ -133,7 +130,7 @@ TimelineViewManager::TimelineViewManager(QSharedPointer userSettin
           });
         qmlRegisterSingletonType(
           "im.nheko", 1, 0, "Settings", [](QQmlEngine *, QJSEngine *) -> QObject * {
-                  return self->settings.data();
+                  return ChatPage::instance()->userSettings().data();
           });
 
         qRegisterMetaType();
@@ -295,7 +292,8 @@ TimelineViewManager::addRoom(const QString &room_id)
 {
         if (!models.contains(room_id)) {
                 QSharedPointer newRoom(new TimelineModel(this, room_id));
-                newRoom->setDecryptDescription(settings->decryptSidebar());
+                newRoom->setDecryptDescription(
+                  ChatPage::instance()->userSettings()->decryptSidebar());
 
                 connect(newRoom.data(),
                         &TimelineModel::newEncryptedImage,
@@ -453,6 +451,15 @@ TimelineViewManager::updateReadReceipts(const QString &room_id,
         }
 }
 
+void
+TimelineViewManager::receivedSessionKey(const std::string &room_id, const std::string &session_id)
+{
+        auto room = models.find(QString::fromStdString(room_id));
+        if (room != models.end()) {
+                room.value()->receivedSessionKey(session_id);
+        }
+}
+
 void
 TimelineViewManager::initWithMessages(const std::map &msgs)
 {
@@ -472,7 +479,7 @@ TimelineViewManager::queueTextMessage(const QString &msg)
         mtx::events::msg::Text text = {};
         text.body                   = msg.trimmed().toStdString();
 
-        if (settings->markdown()) {
+        if (ChatPage::instance()->userSettings()->markdown()) {
                 text.formatted_body = utils::markdownToHtml(msg).toStdString();
 
                 // Don't send formatted_body, when we don't need to
@@ -500,7 +507,7 @@ TimelineViewManager::queueTextMessage(const QString &msg)
 
                 // NOTE(Nico): rich replies always need a formatted_body!
                 text.format = "org.matrix.custom.html";
-                if (settings->markdown())
+                if (ChatPage::instance()->userSettings()->markdown())
                         text.formatted_body =
                           utils::getFormattedQuoteBody(related, utils::markdownToHtml(msg))
                             .toStdString();
@@ -523,7 +530,8 @@ TimelineViewManager::queueEmoteMessage(const QString &msg)
         mtx::events::msg::Emote emote;
         emote.body = msg.trimmed().toStdString();
 
-        if (html != msg.trimmed().toHtmlEscaped() && settings->markdown()) {
+        if (html != msg.trimmed().toHtmlEscaped() &&
+            ChatPage::instance()->userSettings()->markdown()) {
                 emote.formatted_body = html.toStdString();
                 emote.format         = "org.matrix.custom.html";
         }
diff --git a/src/timeline/TimelineViewManager.h b/src/timeline/TimelineViewManager.h
index 1a2d4c4e..895c4b39 100644
--- a/src/timeline/TimelineViewManager.h
+++ b/src/timeline/TimelineViewManager.h
@@ -42,9 +42,7 @@ class TimelineViewManager : public QObject
         Q_PROPERTY(bool isMicMuted READ isMicMuted NOTIFY micMuteChanged)
 
 public:
-        TimelineViewManager(QSharedPointer userSettings,
-                            CallManager *callManager,
-                            ChatPage *parent = nullptr);
+        TimelineViewManager(CallManager *callManager, ChatPage *parent = nullptr);
         QWidget *getWidget() const { return container; }
 
         void sync(const mtx::responses::Rooms &rooms);
@@ -98,6 +96,7 @@ signals:
 
 public slots:
         void updateReadReceipts(const QString &room_id, const std::vector &event_ids);
+        void receivedSessionKey(const std::string &room_id, const std::string &session_id);
         void initWithMessages(const std::map &msgs);
 
         void setHistoryView(const QString &room_id);
@@ -180,7 +179,6 @@ private:
         bool isInitialSync_ = true;
         bool isNarrowView_  = false;
 
-        QSharedPointer settings;
         QHash userColors;
 
         QHash> dvList;