From 716992b7611f36cb57d830cfb646d36e5ed1d911 Mon Sep 17 00:00:00 2001 From: Nicolas Werner Date: Sun, 11 Oct 2020 00:09:47 +0200 Subject: [PATCH 01/33] Update translations --- resources/langs/nheko_cs.ts | 364 ++++++++++++++++++++++----- resources/langs/nheko_de.ts | 364 ++++++++++++++++++++++----- resources/langs/nheko_el.ts | 364 ++++++++++++++++++++++----- resources/langs/nheko_en.ts | 364 ++++++++++++++++++++++----- resources/langs/nheko_eo.ts | 368 ++++++++++++++++++++++----- resources/langs/nheko_et.ts | 364 ++++++++++++++++++++++----- resources/langs/nheko_fi.ts | 364 ++++++++++++++++++++++----- resources/langs/nheko_fr.ts | 444 +++++++++++++++++++++++++-------- resources/langs/nheko_it.ts | 364 ++++++++++++++++++++++----- resources/langs/nheko_ja.ts | 364 ++++++++++++++++++++++----- resources/langs/nheko_nl.ts | 364 ++++++++++++++++++++++----- resources/langs/nheko_pl.ts | 364 ++++++++++++++++++++++----- resources/langs/nheko_pt_PT.ts | 364 ++++++++++++++++++++++----- resources/langs/nheko_ro.ts | 364 ++++++++++++++++++++++----- resources/langs/nheko_ru.ts | 364 ++++++++++++++++++++++----- resources/langs/nheko_si.ts | 364 ++++++++++++++++++++++----- resources/langs/nheko_zh_CN.ts | 364 ++++++++++++++++++++++----- 17 files changed, 5244 insertions(+), 1028 deletions(-) diff --git a/resources/langs/nheko_cs.ts b/resources/langs/nheko_cs.ts index 38443412..9a79837a 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,7 +58,7 @@ ChatPage - + Failed to invite user: %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,84 @@ 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. + + 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 @@ -596,7 +767,7 @@ Example: https://server.my:8787 RoomInfo - + no version stored @@ -697,26 +868,44 @@ Example: https://server.my:8787 StatusIndicator - + Failed - + Sent - + Received - + Read + + Success + + + Successful Verification + + + + + Verification successful! Both sides verified their devices! + + + + + Close + + + TextInputWidget @@ -741,7 +930,7 @@ Example: https://server.my:8787 - + Select a file @@ -751,7 +940,7 @@ Example: https://server.my:8787 - + Place a call @@ -769,20 +958,20 @@ Example: https://server.my:8787 TimelineModel - + Message redaction failed: %1 - + - + Failed to encrypt event, sending aborted! - + Save image @@ -922,12 +1111,12 @@ Example: https://server.my:8787 - + You joined this room. - + Rejected the knock from %1. @@ -951,17 +1140,17 @@ Example: https://server.my:8787 TimelineRow - + React - + Reply - + Options @@ -969,63 +1158,63 @@ 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 - + Back to room list - + No room selected - + Room options @@ -1035,26 +1224,34 @@ Example: https://server.my:8787 - + Members - + Leave room - + Settings - + Close + + TimelineViewManager + + + No share room with this user found. Create an encrypted room with this user and try again. + + + TrayIcon @@ -1111,10 +1308,33 @@ Example: https://server.my:8787 + + UserProfile + + + Verify + + + + + Ban the user + + + + + Start a private chat + + + + + Kick the user + + + UserSettingsPage - + Minimize to tray @@ -1134,12 +1354,12 @@ Example: https://server.my:8787 - + CALLS - + Keep the application running in the background after closing the client window. @@ -1342,7 +1562,7 @@ This usually causes the application icon in the task bar to animate in some fash - + GENERAL @@ -1352,7 +1572,7 @@ This usually causes the application icon in the task bar to animate in some fash - + Emoji Font Family @@ -1403,6 +1623,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..5dd76df1 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,7 +58,7 @@ ChatPage - + Failed to invite user: %1 Nutzer konnte nicht eingeladen werden: %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,84 @@ 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... + + 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 @@ -600,7 +771,7 @@ Beispiel: https://mein.server:8787 RoomInfo - + no version stored keine Version gespeichert @@ -701,26 +872,44 @@ 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 @@ -745,7 +934,7 @@ Beispiel: https://mein.server:8787 Emoji - + Select a file Datei auswählen @@ -755,7 +944,7 @@ Beispiel: https://mein.server:8787 Alle Dateien (*) - + Place a call @@ -773,20 +962,20 @@ 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 +1114,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 +1143,17 @@ Beispiel: https://mein.server:8787 TimelineRow - + React - + Reply Antworten - + Options Optionen @@ -972,63 +1161,63 @@ 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 - + Back to room list - + No room selected - + Room options Raumoptionen @@ -1038,26 +1227,34 @@ Beispiel: https://mein.server:8787 Benutzer einladen - + Members Teilnehmer - + Leave room Raum verlassen - + Settings Einstellungen - + Close Schließen + + TimelineViewManager + + + No share room with this user found. Create an encrypted room with this user and try again. + + + TrayIcon @@ -1114,10 +1311,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 +1357,12 @@ Beispiel: https://mein.server:8787 Runde Profilbilder - + CALLS - + Keep the application running in the background after closing the client window. @@ -1345,7 +1565,7 @@ This usually causes the application icon in the task bar to animate in some fash VERSCHLÜSSELUNG - + GENERAL ALLGEMEINES @@ -1355,7 +1575,7 @@ This usually causes the application icon in the task bar to animate in some fash OBERFLÄCHE - + Emoji Font Family Emojischriftart @@ -1406,6 +1626,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..02cc7878 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,7 +58,7 @@ ChatPage - + Failed to invite user: %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,84 @@ 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... + + 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 @@ -596,7 +767,7 @@ Example: https://server.my:8787 RoomInfo - + no version stored @@ -697,26 +868,44 @@ Example: https://server.my:8787 StatusIndicator - + Failed - + Sent - + Received - + Read + + Success + + + Successful Verification + + + + + Verification successful! Both sides verified their devices! + + + + + Close + + + TextInputWidget @@ -741,7 +930,7 @@ Example: https://server.my:8787 - + Select a file Διάλεξε ένα αρχείο @@ -751,7 +940,7 @@ Example: https://server.my:8787 Όλα τα αρχεία (*) - + Place a call @@ -769,20 +958,20 @@ Example: https://server.my:8787 TimelineModel - + Message redaction failed: %1 - + - + Failed to encrypt event, sending aborted! - + Save image Αποθήκευση Εικόνας @@ -921,12 +1110,12 @@ Example: https://server.my:8787 - + You joined this room. - + Rejected the knock from %1. @@ -950,17 +1139,17 @@ Example: https://server.my:8787 TimelineRow - + React - + Reply - + Options @@ -968,63 +1157,63 @@ 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 - + Back to room list - + No room selected - + Room options @@ -1034,26 +1223,34 @@ Example: https://server.my:8787 Προσκάλεσε χρήστες - + Members Μέλη - + Leave room Βγές - + Settings Ρυθμίσεις - + Close + + TimelineViewManager + + + No share room with this user found. Create an encrypted room with this user and try again. + + + TrayIcon @@ -1110,10 +1307,33 @@ Example: https://server.my:8787 + + UserProfile + + + Verify + + + + + Ban the user + + + + + Start a private chat + + + + + Kick the user + + + UserSettingsPage - + Minimize to tray Ελαχιστοποίηση @@ -1133,12 +1353,12 @@ Example: https://server.my:8787 - + CALLS - + Keep the application running in the background after closing the client window. @@ -1341,7 +1561,7 @@ This usually causes the application icon in the task bar to animate in some fash - + GENERAL ΓΕΝΙΚΑ @@ -1351,7 +1571,7 @@ This usually causes the application icon in the task bar to animate in some fash - + Emoji Font Family @@ -1402,6 +1622,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..cd2a42a5 100644 --- a/resources/langs/nheko_en.ts +++ b/resources/langs/nheko_en.ts @@ -1,10 +1,56 @@ + + ActiveCallBar + + + Initiating... + + + + + Calling... + + + + + Connecting... + + + + + Unmute Mic + + + + + Mute Mic + + + + + AwaitingVerificationConfirmation + + + Awaiting Confirmation + + + + + Waiting for other side to complete verification. + + + + + Cancel + Cancel + + Cache - + You joined this room. You joined this room. @@ -12,7 +58,7 @@ ChatPage - + Failed to invite user: %1 Failed to invite user: %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 Search - + People 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 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 + + + + + Other client does not support our verification protocol. + + + + + Key mismatch detected! + + + + + + Device verification timed out. + + + + + 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,84 @@ 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. + + 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 + Cancel + + + + Deny + + + + + Start verification + + + + + Accept + Accept + + Placeholder @@ -600,7 +771,7 @@ Example: https://server.my:8787 RoomInfo - + no version stored no version stored @@ -701,26 +872,44 @@ Example: https://server.my:8787 StatusIndicator - + Failed Failed - + Sent Sent - + Received Received - + Read Read + + Success + + + Successful Verification + + + + + Verification successful! Both sides verified their devices! + + + + + Close + Close + + TextInputWidget @@ -745,7 +934,7 @@ Example: https://server.my:8787 Emoji - + Select a file Select a file @@ -755,7 +944,7 @@ Example: https://server.my:8787 All Files (*) - + Place a call Place a call @@ -773,20 +962,20 @@ 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 +1114,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 +1143,17 @@ Example: https://server.my:8787 TimelineRow - + React React - + Reply Reply - + Options Options @@ -972,63 +1161,63 @@ 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 - + Back to room list Back to room list - + No room selected No room selected - + Room options Room options @@ -1038,26 +1227,34 @@ Example: https://server.my:8787 Invite users - + Members Members - + Leave room Leave room - + Settings Settings - + Close Close + + TimelineViewManager + + + No share room with this user found. Create an encrypted room with this user and try again. + + + TrayIcon @@ -1114,10 +1311,33 @@ Example: https://server.my:8787 Offline + + UserProfile + + + Verify + + + + + Ban the user + + + + + Start a private chat + + + + + Kick the user + + + UserSettingsPage - + Minimize to tray Minimize to tray @@ -1137,12 +1357,12 @@ Example: https://server.my:8787 Circular Avatars - + 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. @@ -1353,7 +1573,7 @@ This usually causes the application icon in the task bar to animate in some fash ENCRYPTION - + GENERAL GENERAL @@ -1363,7 +1583,7 @@ This usually causes the application icon in the task bar to animate in some fash INTERFACE - + Emoji Font Family Emoji Font Family @@ -1414,6 +1634,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 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 + Cancel + + WelcomePage diff --git a/resources/langs/nheko_eo.ts b/resources/langs/nheko_eo.ts index fafddab8..0aef02e8 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,7 +58,7 @@ ChatPage - + Failed to invite user: %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,84 @@ 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... + + 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 @@ -570,7 +741,7 @@ Ekzemplo: https://servisto.mia:8787 REGISTER - + REGISTRU @@ -601,7 +772,7 @@ Ekzemplo: https://servisto.mia:8787 RoomInfo - + no version stored @@ -704,27 +875,45 @@ 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 @@ -749,7 +938,7 @@ Ekzemplo: https://servisto.mia:8787 - + Select a file @@ -759,7 +948,7 @@ Ekzemplo: https://servisto.mia:8787 Ĉiuj dosieroj (*) - + Place a call @@ -777,20 +966,20 @@ Ekzemplo: https://servisto.mia:8787 TimelineModel - + Message redaction failed: %1 - + - + Failed to encrypt event, sending aborted! - + Save image @@ -931,12 +1120,12 @@ Ekzemplo: https://servisto.mia:8787 - + You joined this room. Vi aliĝis ĉi tiun ĉambron. - + Rejected the knock from %1. @@ -960,17 +1149,17 @@ Ekzemplo: https://servisto.mia:8787 TimelineRow - + React Reagu - + Reply Respondu - + Options Ebloj @@ -978,63 +1167,63 @@ 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 - + Back to room list - + No room selected - + Room options @@ -1044,26 +1233,34 @@ Ekzemplo: https://servisto.mia:8787 - + Members Membroj - + Leave room Forlasu la ĉambron - + Settings - + Agordoj - + Close + + TimelineViewManager + + + No share room with this user found. Create an encrypted room with this user and try again. + + + TrayIcon @@ -1120,10 +1317,33 @@ Ekzemplo: https://servisto.mia:8787 + + UserProfile + + + Verify + + + + + Ban the user + + + + + Start a private chat + + + + + Kick the user + + + UserSettingsPage - + Minimize to tray @@ -1143,12 +1363,12 @@ Ekzemplo: https://servisto.mia:8787 - + CALLS - + Keep the application running in the background after closing the client window. @@ -1351,7 +1571,7 @@ This usually causes the application icon in the task bar to animate in some fash - + GENERAL @@ -1361,7 +1581,7 @@ This usually causes the application icon in the task bar to animate in some fash - + Emoji Font Family @@ -1412,6 +1632,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..b13e16b6 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,7 +58,7 @@ ChatPage - + Failed to invite user: %1 Kutse saatmine kasutajale 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,84 @@ 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. + + 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 @@ -600,7 +771,7 @@ Näiteks: https://server.minu:8787 RoomInfo - + no version stored salvestatud versiooni ei leidu @@ -701,26 +872,44 @@ 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 @@ -745,7 +934,7 @@ Näiteks: https://server.minu:8787 Emoji - + Select a file Vali fail @@ -755,7 +944,7 @@ Näiteks: https://server.minu:8787 Kõik failid (*) - + Place a call Helista @@ -773,20 +962,20 @@ 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 +1114,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 +1143,17 @@ Näiteks: https://server.minu:8787 TimelineRow - + React Reageeri - + Reply Vasta - + Options Valikud @@ -972,63 +1161,63 @@ 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 - + Back to room list Tagasi jututubade loendisse - + No room selected Jututuba on valimata - + Room options Jututoa valikud @@ -1038,26 +1227,34 @@ Näiteks: https://server.minu:8787 Kutsu kasutajaid - + Members Liikmed - + Leave room Lahku jututoast - + Settings Seadistused - + Close Sulge + + TimelineViewManager + + + No share room with this user found. Create an encrypted room with this user and try again. + + + TrayIcon @@ -1114,10 +1311,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 +1357,12 @@ Näiteks: https://server.minu:8787 Ümmargused tunnuspildid - + CALLS KÕNED - + Keep the application running in the background after closing the client window. Peale akna sulgemist jäta rakendus taustal tööle. @@ -1353,7 +1573,7 @@ See tavaliselt tähendab, et rakenduse ikoon tegumiribal annab mingit sorti anim KRÜPTIMINE - + GENERAL ÜLDISED SEADISTUSED @@ -1363,7 +1583,7 @@ See tavaliselt tähendab, et rakenduse ikoon tegumiribal annab mingit sorti anim LIIDES - + Emoji Font Family Fondiperekond emojide jaoks @@ -1414,6 +1634,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 + + + WelcomePage diff --git a/resources/langs/nheko_fi.ts b/resources/langs/nheko_fi.ts index 192c07b2..a8171557 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,7 +58,7 @@ ChatPage - + Failed to invite user: %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,84 @@ 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... + + 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 @@ -596,7 +767,7 @@ Example: https://server.my:8787 RoomInfo - + no version stored ei tallennettua versiota @@ -697,26 +868,44 @@ Example: https://server.my:8787 StatusIndicator - + Failed - + Sent - + Received - + Read + + Success + + + Successful Verification + + + + + Verification successful! Both sides verified their devices! + + + + + Close + Sulje + + TextInputWidget @@ -741,7 +930,7 @@ Example: https://server.my:8787 Emoji - + Select a file Valitse tiedosto @@ -751,7 +940,7 @@ Example: https://server.my:8787 Kaikki tiedostot (*) - + Place a call @@ -769,20 +958,20 @@ 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 +1110,12 @@ Example: https://server.my:8787 - + You joined this room. - + Rejected the knock from %1. @@ -950,17 +1139,17 @@ Example: https://server.my:8787 TimelineRow - + React - + Reply - + Options @@ -968,63 +1157,63 @@ 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 - + Back to room list - + No room selected - + Room options Huonevaihtoehdot @@ -1034,26 +1223,34 @@ Example: https://server.my:8787 Kutsu käyttäjiä - + Members Jäsenet - + Leave room Poistu huoneesta - + Settings Asetukset - + Close Sulje + + TimelineViewManager + + + No share room with this user found. Create an encrypted room with this user and try again. + + + TrayIcon @@ -1110,10 +1307,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 +1353,12 @@ Example: https://server.my:8787 - + CALLS - + Keep the application running in the background after closing the client window. @@ -1341,7 +1561,7 @@ This usually causes the application icon in the task bar to animate in some fash SALAUS - + GENERAL YLEISET ASETUKSET @@ -1351,7 +1571,7 @@ This usually causes the application icon in the task bar to animate in some fash - + Emoji Font Family @@ -1402,6 +1622,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..2958f056 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,7 +58,7 @@ ChatPage - + Failed to invite user: %1 Échec lors de l'invitation de %1 @@ -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 @@ -115,7 +161,7 @@ Cache migration failed! - Échec de la migration du cache ! + Échec de la migration du cache ! @@ -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,90 @@ 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… + + 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é : @@ -539,7 +710,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 +745,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 ! @@ -600,7 +771,7 @@ Exemple : https ://monserveur.example.com :8787 RoomInfo - + no version stored pas de version enregistrée @@ -615,7 +786,7 @@ Exemple : https ://monserveur.example.com :8787 Tag room as: - Étiqueter le salon comme : + Étiqueter le salon comme : @@ -657,7 +828,7 @@ Exemple : https ://monserveur.example.com :8787 Tag: Tag name prompt - Étiquette : + Étiquette : @@ -701,26 +872,44 @@ 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 @@ -745,7 +934,7 @@ Exemple : https ://monserveur.example.com :8787 Emoji - + Select a file Sélectionnez un fichier @@ -755,7 +944,7 @@ Exemple : https ://monserveur.example.com :8787 Tous les types de fichiers (*) - + Place a call Appeler @@ -773,20 +962,20 @@ 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 +1114,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 +1127,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 +1143,17 @@ Exemple : https ://monserveur.example.com :8787 TimelineRow - + React Réagir - + Reply Répondre - + Options Options @@ -972,63 +1161,63 @@ 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 - + Back to room list Revenir à la liste des salons - + No room selected Pas de salon sélectionné - + Room options Options du salon @@ -1038,26 +1227,34 @@ Exemple : https ://monserveur.example.com :8787 Inviter des utilisateurs - + Members Membres - + Leave room Quitter le salon - + Settings Paramètres - + Close Fermer + + TimelineViewManager + + + No share room with this user found. Create an encrypted room with this user and try again. + + + TrayIcon @@ -1091,7 +1288,7 @@ Exemple : https ://monserveur.example.com :8787 Status: - Statut : + Statut : @@ -1114,10 +1311,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 +1357,12 @@ Exemple : https ://monserveur.example.com :8787 Avatars circulaires - + 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. @@ -1354,7 +1574,7 @@ Cela met l'application en évidence dans la barre des tâches.CHIFFREMENT - + GENERAL GÉNÉRAL @@ -1364,7 +1584,7 @@ Cela met l'application en évidence dans la barre des tâches.INTERFACE - + Emoji Font Family Nom de Police Emoji @@ -1396,7 +1616,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 +1627,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 +1635,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 +2013,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 +2033,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 +2072,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 +2188,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..1c71b43e 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,7 +58,7 @@ ChatPage - + Failed to invite user: %1 Impossibile invitare l'utente: %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,84 @@ 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... + + 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 @@ -600,7 +771,7 @@ Esempio: https://server.mio:8787 RoomInfo - + no version stored nessuna versione memorizzata @@ -701,26 +872,44 @@ 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 @@ -745,7 +934,7 @@ Esempio: https://server.mio:8787 Emoji - + Select a file Seleziona un file @@ -755,7 +944,7 @@ Esempio: https://server.mio:8787 Tutti i file (*) - + Place a call @@ -773,20 +962,20 @@ 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 +1114,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 +1143,17 @@ Esempio: https://server.mio:8787 TimelineRow - + React - + Reply Rispondi - + Options Opzioni @@ -972,63 +1161,63 @@ 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 - + Back to room list - + No room selected - + Room options Opzioni della stanza @@ -1038,26 +1227,34 @@ Esempio: https://server.mio:8787 Invita utenti - + Members Membri - + Leave room Lascia la stanza - + Settings Impostazioni - + Close Chiudi + + TimelineViewManager + + + No share room with this user found. Create an encrypted room with this user and try again. + + + TrayIcon @@ -1114,10 +1311,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 +1357,12 @@ Esempio: https://server.mio:8787 Avatar Circolari - + CALLS - + Keep the application running in the background after closing the client window. @@ -1345,7 +1565,7 @@ This usually causes the application icon in the task bar to animate in some fash CRITTOGRAFIA - + GENERAL GENERALE @@ -1355,7 +1575,7 @@ This usually causes the application icon in the task bar to animate in some fash INTERFACCIA - + Emoji Font Family Famiglia dei caratteri delle Emoji @@ -1406,6 +1626,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..4c53bab0 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,7 +58,7 @@ ChatPage - + Failed to invite user: %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,84 @@ 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... + + 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 @@ -596,7 +767,7 @@ Example: https://server.my:8787 RoomInfo - + no version stored バージョンが保存されていません @@ -697,26 +868,44 @@ Example: https://server.my:8787 StatusIndicator - + Failed 失敗 - + Sent 送信済み - + Received 受信済み - + Read 既読 + + Success + + + Successful Verification + + + + + Verification successful! Both sides verified their devices! + + + + + Close + 閉じる + + TextInputWidget @@ -741,7 +930,7 @@ Example: https://server.my:8787 絵文字 - + Select a file ファイルを選択 @@ -751,7 +940,7 @@ Example: https://server.my:8787 全てのファイル (*) - + Place a call @@ -769,20 +958,20 @@ Example: https://server.my:8787 TimelineModel - + Message redaction failed: %1 メッセージを編集できませんでした: %1 - + - + Failed to encrypt event, sending aborted! - + Save image 画像を保存 @@ -920,12 +1109,12 @@ Example: https://server.my:8787 %1がノックを編集しました。 - + You joined this room. - + Rejected the knock from %1. %1からのノックを拒否しました。 @@ -949,17 +1138,17 @@ Example: https://server.my:8787 TimelineRow - + React - + Reply 返信 - + Options オプション @@ -967,63 +1156,63 @@ 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 部屋が開いていません - + Back to room list - + No room selected - + Room options 部屋のオプション @@ -1033,26 +1222,34 @@ Example: https://server.my:8787 ユーザーを招待 - + Members メンバー - + Leave room 部屋を出る - + Settings 設定 - + Close 閉じる + + TimelineViewManager + + + No share room with this user found. Create an encrypted room with this user and try again. + + + TrayIcon @@ -1109,10 +1306,33 @@ Example: https://server.my:8787 + + UserProfile + + + Verify + + + + + Ban the user + + + + + Start a private chat + + + + + Kick the user + + + UserSettingsPage - + Minimize to tray トレイへ最小化 @@ -1132,12 +1352,12 @@ Example: https://server.my:8787 円形アバター - + CALLS - + Keep the application running in the background after closing the client window. @@ -1340,7 +1560,7 @@ This usually causes the application icon in the task bar to animate in some fash 暗号化 - + GENERAL 全般 @@ -1350,7 +1570,7 @@ This usually causes the application icon in the task bar to animate in some fash - + Emoji Font Family @@ -1401,6 +1621,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..5cdbbe9e 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,7 +58,7 @@ ChatPage - + Failed to invite user: %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,84 @@ 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... + + 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 @@ -596,7 +767,7 @@ Example: https://server.my:8787 RoomInfo - + no version stored @@ -697,26 +868,44 @@ Example: https://server.my:8787 StatusIndicator - + Failed - + Sent - + Received - + Read + + Success + + + Successful Verification + + + + + Verification successful! Both sides verified their devices! + + + + + Close + + + TextInputWidget @@ -741,7 +930,7 @@ Example: https://server.my:8787 - + Select a file Kies een bestand @@ -751,7 +940,7 @@ Example: https://server.my:8787 Alle bestanden (*) - + Place a call @@ -769,20 +958,20 @@ Example: https://server.my:8787 TimelineModel - + Message redaction failed: %1 - + - + Failed to encrypt event, sending aborted! - + Save image Afbeelding opslaan @@ -921,12 +1110,12 @@ Example: https://server.my:8787 - + You joined this room. - + Rejected the knock from %1. @@ -950,17 +1139,17 @@ Example: https://server.my:8787 TimelineRow - + React - + Reply - + Options @@ -968,63 +1157,63 @@ 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 - + Back to room list - + No room selected - + Room options @@ -1034,26 +1223,34 @@ Example: https://server.my:8787 Gebruikers uitnodigen - + Members Leden - + Leave room Kamer verlaten - + Settings Instellingen - + Close + + TimelineViewManager + + + No share room with this user found. Create an encrypted room with this user and try again. + + + TrayIcon @@ -1110,10 +1307,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 +1353,12 @@ Example: https://server.my:8787 - + CALLS - + Keep the application running in the background after closing the client window. @@ -1341,7 +1561,7 @@ This usually causes the application icon in the task bar to animate in some fash - + GENERAL ALGEMEEN @@ -1351,7 +1571,7 @@ This usually causes the application icon in the task bar to animate in some fash - + Emoji Font Family @@ -1402,6 +1622,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..4e3c1b0b 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,7 +58,7 @@ ChatPage - + Failed to invite user: %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,84 @@ 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... + + 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 @@ -596,7 +767,7 @@ Example: https://server.my:8787 RoomInfo - + no version stored @@ -697,26 +868,44 @@ Example: https://server.my:8787 StatusIndicator - + Failed - + Sent - + Received - + Read + + Success + + + Successful Verification + + + + + Verification successful! Both sides verified their devices! + + + + + Close + + + TextInputWidget @@ -741,7 +930,7 @@ Example: https://server.my:8787 Emoji - + Select a file Wybierz plik @@ -751,7 +940,7 @@ Example: https://server.my:8787 Wszystkie pliki (*) - + Place a call @@ -769,20 +958,20 @@ 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 +1111,12 @@ Example: https://server.my:8787 - + You joined this room. - + Rejected the knock from %1. @@ -951,17 +1140,17 @@ Example: https://server.my:8787 TimelineRow - + React - + Reply - + Options @@ -969,63 +1158,63 @@ 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 - + Back to room list - + No room selected - + Room options Ustawienia pokoju @@ -1035,26 +1224,34 @@ Example: https://server.my:8787 Zaproś użytkowników - + Members Członkowie - + Leave room Opuść pokój - + Settings Ustawienia - + Close + + TimelineViewManager + + + No share room with this user found. Create an encrypted room with this user and try again. + + + TrayIcon @@ -1111,10 +1308,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 +1354,12 @@ Example: https://server.my:8787 - + CALLS - + Keep the application running in the background after closing the client window. @@ -1342,7 +1562,7 @@ This usually causes the application icon in the task bar to animate in some fash SZYFROWANIE - + GENERAL OGÓLNE @@ -1352,7 +1572,7 @@ This usually causes the application icon in the task bar to animate in some fash - + Emoji Font Family @@ -1403,6 +1623,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..a4a337b1 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,7 +58,7 @@ ChatPage - + Failed to invite user: %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,84 @@ 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. + + 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 @@ -596,7 +767,7 @@ Example: https://server.my:8787 RoomInfo - + no version stored @@ -697,26 +868,44 @@ Example: https://server.my:8787 StatusIndicator - + Failed - + Sent - + Received - + Read + + Success + + + Successful Verification + + + + + Verification successful! Both sides verified their devices! + + + + + Close + + + TextInputWidget @@ -741,7 +930,7 @@ Example: https://server.my:8787 - + Select a file @@ -751,7 +940,7 @@ Example: https://server.my:8787 - + Place a call @@ -769,20 +958,20 @@ Example: https://server.my:8787 TimelineModel - + Message redaction failed: %1 - + - + Failed to encrypt event, sending aborted! - + Save image @@ -921,12 +1110,12 @@ Example: https://server.my:8787 - + You joined this room. - + Rejected the knock from %1. @@ -950,17 +1139,17 @@ Example: https://server.my:8787 TimelineRow - + React - + Reply - + Options @@ -968,63 +1157,63 @@ 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 - + Back to room list - + No room selected - + Room options @@ -1034,26 +1223,34 @@ Example: https://server.my:8787 - + Members - + Leave room - + Settings - + Close + + TimelineViewManager + + + No share room with this user found. Create an encrypted room with this user and try again. + + + TrayIcon @@ -1110,10 +1307,33 @@ Example: https://server.my:8787 + + UserProfile + + + Verify + + + + + Ban the user + + + + + Start a private chat + + + + + Kick the user + + + UserSettingsPage - + Minimize to tray @@ -1133,12 +1353,12 @@ Example: https://server.my:8787 - + CALLS - + Keep the application running in the background after closing the client window. @@ -1341,7 +1561,7 @@ This usually causes the application icon in the task bar to animate in some fash - + GENERAL @@ -1351,7 +1571,7 @@ This usually causes the application icon in the task bar to animate in some fash - + Emoji Font Family @@ -1402,6 +1622,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..30476860 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,7 +58,7 @@ ChatPage - + Failed to invite user: %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,84 @@ 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... + + 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 @@ -596,7 +767,7 @@ Example: https://server.my:8787 RoomInfo - + no version stored @@ -697,26 +868,44 @@ Example: https://server.my:8787 StatusIndicator - + Failed - + Sent - + Received - + Read + + Success + + + Successful Verification + + + + + Verification successful! Both sides verified their devices! + + + + + Close + + + TextInputWidget @@ -741,7 +930,7 @@ Example: https://server.my:8787 - + Select a file @@ -751,7 +940,7 @@ Example: https://server.my:8787 - + Place a call @@ -769,20 +958,20 @@ Example: https://server.my:8787 TimelineModel - + Message redaction failed: %1 - + - + Failed to encrypt event, sending aborted! - + Save image @@ -922,12 +1111,12 @@ Example: https://server.my:8787 - + You joined this room. - + Rejected the knock from %1. @@ -951,17 +1140,17 @@ Example: https://server.my:8787 TimelineRow - + React - + Reply - + Options @@ -969,63 +1158,63 @@ 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 - + Back to room list - + No room selected - + Room options @@ -1035,26 +1224,34 @@ Example: https://server.my:8787 - + Members - + Leave room - + Settings - + Close + + TimelineViewManager + + + No share room with this user found. Create an encrypted room with this user and try again. + + + TrayIcon @@ -1111,10 +1308,33 @@ Example: https://server.my:8787 + + UserProfile + + + Verify + + + + + Ban the user + + + + + Start a private chat + + + + + Kick the user + + + UserSettingsPage - + Minimize to tray @@ -1134,12 +1354,12 @@ Example: https://server.my:8787 - + CALLS - + Keep the application running in the background after closing the client window. @@ -1342,7 +1562,7 @@ This usually causes the application icon in the task bar to animate in some fash - + GENERAL @@ -1352,7 +1572,7 @@ This usually causes the application icon in the task bar to animate in some fash - + Emoji Font Family @@ -1403,6 +1623,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..5625d591 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,7 +58,7 @@ ChatPage - + Failed to invite user: %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,84 @@ 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... + + 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 @@ -596,7 +767,7 @@ Example: https://server.my:8787 RoomInfo - + no version stored @@ -697,26 +868,44 @@ Example: https://server.my:8787 StatusIndicator - + Failed - + Sent - + Received - + Read + + Success + + + Successful Verification + + + + + Verification successful! Both sides verified their devices! + + + + + Close + Закрыть + + TextInputWidget @@ -741,7 +930,7 @@ Example: https://server.my:8787 - + Select a file Выберите файл @@ -751,7 +940,7 @@ Example: https://server.my:8787 Все файлы (*) - + Place a call @@ -769,20 +958,20 @@ Example: https://server.my:8787 TimelineModel - + Message redaction failed: %1 Ошибка редактирования сообщения: %1 - + - + Failed to encrypt event, sending aborted! - + Save image Сохранить изображение @@ -922,12 +1111,12 @@ Example: https://server.my:8787 - + You joined this room. - + Rejected the knock from %1. @@ -951,17 +1140,17 @@ Example: https://server.my:8787 TimelineRow - + React - + Reply - + Options @@ -969,63 +1158,63 @@ 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 - + Back to room list - + No room selected - + Room options Настройки комнаты @@ -1035,26 +1224,34 @@ Example: https://server.my:8787 Пригласить пользователей - + Members Участники - + Leave room Покинуть комнату - + Settings Настройки - + Close Закрыть + + TimelineViewManager + + + No share room with this user found. Create an encrypted room with this user and try again. + + + TrayIcon @@ -1111,10 +1308,33 @@ Example: https://server.my:8787 + + UserProfile + + + Verify + + + + + Ban the user + + + + + Start a private chat + + + + + Kick the user + + + UserSettingsPage - + Minimize to tray Сворачивать в системную панель @@ -1134,12 +1354,12 @@ Example: https://server.my:8787 - + CALLS - + Keep the application running in the background after closing the client window. @@ -1342,7 +1562,7 @@ This usually causes the application icon in the task bar to animate in some fash ШИФРОВАНИЕ - + GENERAL ГЛАВНОЕ @@ -1352,7 +1572,7 @@ This usually causes the application icon in the task bar to animate in some fash - + Emoji Font Family @@ -1404,6 +1624,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..b53bec5d 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,7 +58,7 @@ ChatPage - + Failed to invite user: %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,84 @@ 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... + + 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 @@ -596,7 +767,7 @@ Example: https://server.my:8787 RoomInfo - + no version stored @@ -697,26 +868,44 @@ Example: https://server.my:8787 StatusIndicator - + Failed - + Sent - + Received - + Read + + Success + + + Successful Verification + + + + + Verification successful! Both sides verified their devices! + + + + + Close + + + TextInputWidget @@ -741,7 +930,7 @@ Example: https://server.my:8787 - + Select a file @@ -751,7 +940,7 @@ Example: https://server.my:8787 - + Place a call @@ -769,20 +958,20 @@ Example: https://server.my:8787 TimelineModel - + Message redaction failed: %1 - + - + Failed to encrypt event, sending aborted! - + Save image @@ -921,12 +1110,12 @@ Example: https://server.my:8787 - + You joined this room. - + Rejected the knock from %1. @@ -950,17 +1139,17 @@ Example: https://server.my:8787 TimelineRow - + React - + Reply - + Options @@ -968,63 +1157,63 @@ 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 - + Back to room list - + No room selected - + Room options @@ -1034,26 +1223,34 @@ Example: https://server.my:8787 - + Members - + Leave room - + Settings - + Close + + TimelineViewManager + + + No share room with this user found. Create an encrypted room with this user and try again. + + + TrayIcon @@ -1110,10 +1307,33 @@ Example: https://server.my:8787 + + UserProfile + + + Verify + + + + + Ban the user + + + + + Start a private chat + + + + + Kick the user + + + UserSettingsPage - + Minimize to tray @@ -1133,12 +1353,12 @@ Example: https://server.my:8787 - + CALLS - + Keep the application running in the background after closing the client window. @@ -1341,7 +1561,7 @@ This usually causes the application icon in the task bar to animate in some fash - + GENERAL @@ -1351,7 +1571,7 @@ This usually causes the application icon in the task bar to animate in some fash - + Emoji Font Family @@ -1402,6 +1622,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..ae7a871a 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,7 +58,7 @@ ChatPage - + Failed to invite user: %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,84 @@ 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... + + 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 @@ -596,7 +767,7 @@ Example: https://server.my:8787 RoomInfo - + no version stored @@ -697,26 +868,44 @@ Example: https://server.my:8787 StatusIndicator - + Failed - + Sent - + Received - + Read + + Success + + + Successful Verification + + + + + Verification successful! Both sides verified their devices! + + + + + Close + + + TextInputWidget @@ -741,7 +930,7 @@ Example: https://server.my:8787 - + Select a file 选择一个文件 @@ -751,7 +940,7 @@ Example: https://server.my:8787 所有文件(*) - + Place a call @@ -769,20 +958,20 @@ Example: https://server.my:8787 TimelineModel - + Message redaction failed: %1 删除消息失败:%1 - + - + Failed to encrypt event, sending aborted! - + Save image 保存图像 @@ -920,12 +1109,12 @@ Example: https://server.my:8787 - + You joined this room. - + Rejected the knock from %1. @@ -949,17 +1138,17 @@ Example: https://server.my:8787 TimelineRow - + React - + Reply - + Options @@ -967,63 +1156,63 @@ 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 - + Back to room list - + No room selected - + Room options 聊天室选项 @@ -1033,26 +1222,34 @@ Example: https://server.my:8787 邀请用户 - + Members 成员 - + Leave room 离开聊天室 - + Settings 设置 - + Close + + TimelineViewManager + + + No share room with this user found. Create an encrypted room with this user and try again. + + + TrayIcon @@ -1109,10 +1306,33 @@ Example: https://server.my:8787 + + UserProfile + + + Verify + + + + + Ban the user + + + + + Start a private chat + + + + + Kick the user + + + UserSettingsPage - + Minimize to tray 最小化至托盘 @@ -1132,12 +1352,12 @@ Example: https://server.my:8787 - + CALLS - + Keep the application running in the background after closing the client window. @@ -1340,7 +1560,7 @@ This usually causes the application icon in the task bar to animate in some fash 加密 - + GENERAL 通用 @@ -1350,7 +1570,7 @@ This usually causes the application icon in the task bar to animate in some fash - + Emoji Font Family @@ -1401,6 +1621,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 From d075a90024388913e81c6c40607482252057d71a Mon Sep 17 00:00:00 2001 From: Nicolas Werner Date: Mon, 12 Oct 2020 22:05:55 +0200 Subject: [PATCH 02/33] Fix emoji filter losing focus --- resources/qml/emoji/EmojiPicker.qml | 1 + 1 file changed, 1 insertion(+) diff --git a/resources/qml/emoji/EmojiPicker.qml b/resources/qml/emoji/EmojiPicker.qml index 3a5ee57a..0e277415 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 { From 62d0bdbb77826bdbb9a778c56b8534d4b0c2c551 Mon Sep 17 00:00:00 2001 From: Nicolas Werner Date: Tue, 13 Oct 2020 00:14:28 +0200 Subject: [PATCH 03/33] Fix small typo in robustness logic --- src/Cache.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Cache.cpp b/src/Cache.cpp index 088b6fc6..5a0740f1 100644 --- a/src/Cache.cpp +++ b/src/Cache.cpp @@ -1638,7 +1638,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, From a0c2a174eaf1446e8ed42a2e396c1bb9ce885b7e Mon Sep 17 00:00:00 2001 From: kamathmanu Date: Tue, 13 Oct 2020 16:24:42 -0400 Subject: [PATCH 04/33] Display filename on mouse hover --- resources/qml/delegates/ImageMessage.qml | 31 +++++++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-) diff --git a/resources/qml/delegates/ImageMessage.qml b/resources/qml/delegates/ImageMessage.qml index e2c78fbe..8307bdd6 100644 --- a/resources/qml/delegates/ImageMessage.qml +++ b/resources/qml/delegates/ImageMessage.qml @@ -31,11 +31,40 @@ 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: "black" + opacity: 0.75 + Text { + id: imgcaption + + anchors.fill: parent + 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 + font.pointSize: 11 + color: "white" + } + } + } + } } From 70b5e1e08eb80dd54b15789cf85632aa8a74cf81 Mon Sep 17 00:00:00 2001 From: Weblate Date: Tue, 13 Oct 2020 23:19:42 -0400 Subject: [PATCH 05/33] Translated using Weblate (Estonian) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Currently translated at 90.2% (363 of 402 strings) Co-authored-by: Priit Jõerüüt Translate-URL: https://weblate.nheko.im/projects/nheko/nheko-master/et/ Translation: Nheko/nheko --- resources/langs/nheko_et.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/resources/langs/nheko_et.ts b/resources/langs/nheko_et.ts index b13e16b6..4804f797 100644 --- a/resources/langs/nheko_et.ts +++ b/resources/langs/nheko_et.ts @@ -456,7 +456,7 @@ Close - Sulge + Sulge @@ -681,7 +681,7 @@ Näiteks: https://server.minu:8787 Accept - Nõustu + Nõustu @@ -907,7 +907,7 @@ Näiteks: https://server.minu:8787 Close - Sulge + Sulge @@ -1659,7 +1659,7 @@ See tavaliselt tähendab, et rakenduse ikoon tegumiribal annab mingit sorti anim Cancel - + Tühista From e3c665661393b7495d275bc23fb68a158c5df3fb Mon Sep 17 00:00:00 2001 From: kamathmanu Date: Wed, 14 Oct 2020 18:07:09 -0400 Subject: [PATCH 06/33] Avoid hardcoded font size --- resources/qml/delegates/ImageMessage.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/qml/delegates/ImageMessage.qml b/resources/qml/delegates/ImageMessage.qml index 8307bdd6..3f6d9951 100644 --- a/resources/qml/delegates/ImageMessage.qml +++ b/resources/qml/delegates/ImageMessage.qml @@ -61,10 +61,10 @@ Item { 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 - font.pointSize: 11 color: "white" } } } + } } From e1c4f7d516b2096c5b133fb7610a8a83755503a0 Mon Sep 17 00:00:00 2001 From: kamathmanu Date: Thu, 15 Oct 2020 11:52:49 -0400 Subject: [PATCH 07/33] Make Text element a sibling of the Rectangle to avoid the text from depending on the rectangle's opacity property. Switch to Nheko theme colors --- resources/qml/delegates/ImageMessage.qml | 28 ++++++++++++------------ 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/resources/qml/delegates/ImageMessage.qml b/resources/qml/delegates/ImageMessage.qml index 3f6d9951..6ac5ee15 100644 --- a/resources/qml/delegates/ImageMessage.qml +++ b/resources/qml/delegates/ImageMessage.qml @@ -43,28 +43,28 @@ Item { anchors.fill: parent visible: mouseArea.containsMouse - + Rectangle { id: container + width: parent.width implicitHeight: imgcaption.implicitHeight anchors.bottom: overlay.bottom - color: "black" + color: colors.window opacity: 0.75 + } - Text { - id: imgcaption - - anchors.fill: parent - 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: "white" - } + 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 } } - } } From bc7494473b57bfd3f98e5e0b24996e3579979f39 Mon Sep 17 00:00:00 2001 From: Nicolas Werner Date: Sat, 17 Oct 2020 00:12:26 +0200 Subject: [PATCH 08/33] Answer key requests for all our sessions --- src/Olm.cpp | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/src/Olm.cpp b/src/Olm.cpp index f4cb2209..1d86b4d7 100644 --- a/src/Olm.cpp +++ b/src/Olm.cpp @@ -500,12 +500,14 @@ handle_key_request_message(const mtx::events::DeviceEventwarn("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; } @@ -523,13 +525,14 @@ handle_key_request_message(const mtx::events::DeviceEvent Date: Sat, 17 Oct 2020 00:57:29 +0200 Subject: [PATCH 09/33] Fix crash on exit --- src/ChatPage.cpp | 6 +++--- src/RoomInfoListItem.cpp | 8 ++------ src/RoomInfoListItem.h | 7 +------ src/RoomList.cpp | 16 +++------------- src/RoomList.h | 1 - src/timeline/TimelineViewManager.cpp | 23 +++++++++++------------ src/timeline/TimelineViewManager.h | 5 +---- 7 files changed, 21 insertions(+), 45 deletions(-) diff --git a/src/ChatPage.cpp b/src/ChatPage.cpp index 8e93c0f4..e61df263 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()); @@ -590,8 +592,6 @@ ChatPage::ChatPage(QSharedPointer userSettings, QWidget *parent) connectCallMessage(); connectCallMessage(); connectCallMessage(); - - instance_ = this; } void 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/timeline/TimelineViewManager.cpp b/src/timeline/TimelineViewManager.cpp index 7c81ca8f..783584c9 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(); @@ -284,7 +281,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, @@ -461,7 +459,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 @@ -489,7 +487,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(); @@ -512,7 +510,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 9a2a6467..5e441562 100644 --- a/src/timeline/TimelineViewManager.h +++ b/src/timeline/TimelineViewManager.h @@ -41,9 +41,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); @@ -176,7 +174,6 @@ private: bool isInitialSync_ = true; bool isNarrowView_ = false; - QSharedPointer settings; QHash userColors; QHash> dvList; From c25fd947a8412c19ec5a491c25cdc555cdcab756 Mon Sep 17 00:00:00 2001 From: Nicolas Werner Date: Sun, 18 Oct 2020 00:52:35 +0200 Subject: [PATCH 10/33] Automatic key sharing with trusted users --- src/Olm.cpp | 16 ++++++++++- src/UserSettingsPage.cpp | 62 ++++++++++++++++++++++++++-------------- src/UserSettingsPage.h | 7 +++++ 3 files changed, 62 insertions(+), 23 deletions(-) diff --git a/src/Olm.cpp b/src/Olm.cpp index 1d86b4d7..059733c6 100644 --- a/src/Olm.cpp +++ b/src/Olm.cpp @@ -9,6 +9,7 @@ #include "DeviceVerificationFlow.h" #include "Logging.h" #include "MatrixClient.h" +#include "UserSettingsPage.h" #include "Utils.h" static const std::string STORAGE_SECRET_KEY("secret"); @@ -519,7 +520,20 @@ 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; + break; + } + } + } + + if (!utils::respondsToKeyRequests(req.content.room_id) && !verifiedDevice) { nhlog::crypto()->debug("ignoring all key requests for room {}", req.content.room_id); return; diff --git a/src/UserSettingsPage.cpp b/src/UserSettingsPage.cpp index 7d81e663..8f049a29 100644 --- a/src/UserSettingsPage.cpp +++ b/src/UserSettingsPage.cpp @@ -72,8 +72,10 @@ 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(); + 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(); @@ -291,6 +293,16 @@ UserSettings::setUseStunServer(bool useStunServer) save(); } +void +UserSettings::setShareKeysWithTrustedUsers(bool shareKeys) +{ + if (shareKeys == shareKeysWithTrustedUsers_) + return; + shareKeysWithTrustedUsers_ = shareKeys; + emit shareKeysWithTrustedUsersChanged(shareKeys); + save(); +} + void UserSettings::setDefaultAudioSource(const QString &defaultAudioSource) { @@ -374,6 +386,7 @@ UserSettings::save() settings.setValue("avatar_circles", avatarCircles_); settings.setValue("decrypt_sidebar", decryptSidebar_); + settings.setValue("share_keys_with_trusted_users", shareKeysWithTrustedUsers_); settings.setValue("font_size", baseFontSize_); settings.setValue("typing_notifications", typingNotifications_); settings.setValue("minor_events", sortByImportance_); @@ -439,26 +452,27 @@ 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}; - 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}; + scaleFactorCombo_ = new QComboBox{this}; + fontSizeCombo_ = new QComboBox{this}; + fontSelectionCombo_ = new QComboBox{this}; + emojiFontSelectionCombo_ = new QComboBox{this}; + timelineMaxWidthSpin_ = new QSpinBox{this}; if (!settings_->tray()) startInTrayToggle_->setDisabled(true); @@ -653,6 +667,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); diff --git a/src/UserSettingsPage.h b/src/UserSettingsPage.h index e947bfae..e4a34893 100644 --- a/src/UserSettingsPage.h +++ b/src/UserSettingsPage.h @@ -75,6 +75,8 @@ class UserSettings : public QObject bool useStunServer READ useStunServer WRITE setUseStunServer NOTIFY useStunServerChanged) Q_PROPERTY(QString defaultAudioSource READ defaultAudioSource WRITE setDefaultAudioSource NOTIFY defaultAudioSourceChanged) + Q_PROPERTY(bool shareKeysWithTrustedUsers READ shareKeysWithTrustedUsers WRITE + setShareKeysWithTrustedUsers NOTIFY shareKeysWithTrustedUsersChanged) public: UserSettings(); @@ -113,6 +115,7 @@ public: void setPresence(Presence state); void setUseStunServer(bool state); void setDefaultAudioSource(const QString &deviceName); + void setShareKeysWithTrustedUsers(bool state); QString theme() const { return !theme_.isEmpty() ? theme_ : defaultTheme_; } bool messageHoverHighlight() const { return messageHoverHighlight_; } @@ -140,6 +143,7 @@ public: Presence presence() const { return presence_; } bool useStunServer() const { return useStunServer_; } QString defaultAudioSource() const { return defaultAudioSource_; } + bool shareKeysWithTrustedUsers() const { return shareKeysWithTrustedUsers_; } signals: void groupViewStateChanged(bool state); @@ -164,6 +168,7 @@ signals: void presenceChanged(Presence state); void useStunServerChanged(bool state); void defaultAudioSourceChanged(const QString &deviceName); + void shareKeysWithTrustedUsersChanged(bool state); private: // Default to system theme if QT_QPA_PLATFORMTHEME var is set. @@ -186,6 +191,7 @@ private: bool hasAlertOnNotification_; bool avatarCircles_; bool decryptSidebar_; + bool shareKeysWithTrustedUsers_; int timelineMaxWidth_; double baseFontSize_; QString font_; @@ -248,6 +254,7 @@ private: Toggle *avatarCircles_; Toggle *useStunServer_; Toggle *decryptSidebar_; + Toggle *shareKeysWithTrustedUsers_; QLabel *deviceFingerprintValue_; QLabel *deviceIdValue_; From 1f68bc60b5a35f3b39a5a9d537c6ee6cdd0ede44 Mon Sep 17 00:00:00 2001 From: Nicolas Werner Date: Sun, 18 Oct 2020 02:02:14 +0200 Subject: [PATCH 11/33] Actually forward keys using m.forwared_room_key --- src/Olm.cpp | 47 ++++++++++++++++++++++++++--------------------- src/Olm.h | 2 +- 2 files changed, 27 insertions(+), 22 deletions(-) diff --git a/src/Olm.cpp b/src/Olm.cpp index 059733c6..fee685a3 100644 --- a/src/Olm.cpp +++ b/src/Olm.cpp @@ -1,8 +1,8 @@ +#include "Olm.h" + #include #include -#include "Olm.h" - #include "Cache.h" #include "Cache_p.h" #include "ChatPage.h" @@ -494,9 +494,6 @@ 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; } @@ -528,6 +525,7 @@ handle_key_request_message(const mtx::events::DeviceEventverified_devices) { if (dev == req.content.requesting_device_id) { verifiedDevice = true; + nhlog::crypto()->debug("Verified device: {}", dev); break; } } @@ -543,18 +541,24 @@ handle_key_request_message(const mtx::events::DeviceEventidentity_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}; @@ -613,17 +617,13 @@ send_megolm_key_to_device(const std::string &user_id, 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) { + [payload, 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, @@ -665,8 +665,12 @@ send_megolm_key_to_device(const std::string &user_id, 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); + mtx::events::DeviceEvent + room_key; + room_key.content = payload; + room_key.type = mtx::events::EventType::ForwardedRoomKey; + device_msg = olm::client()->create_olm_encrypted_content( + olm_session.get(), json(room_key).dump(), pks.curve25519); cache::saveOlmSession(pks.curve25519, std::move(olm_session)); } catch (const json::exception &e) { @@ -682,7 +686,7 @@ send_megolm_key_to_device(const std::string &user_id, body["messages"][user_id][device_id] = device_msg; nhlog::net()->info( - "sending m.room_key event to {}:{}", user_id, device_id); + "sending m.forwarded_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) { @@ -692,7 +696,8 @@ send_megolm_key_to_device(const std::string &user_id, err->matrix_error.error); } - nhlog::net()->info("m.room_key send to {}", user_id); + nhlog::net()->info("m.forwarded_room_key send to {}", + user_id); }); }); }); diff --git a/src/Olm.h b/src/Olm.h index 7b97039b..cda9f29a 100644 --- a/src/Olm.h +++ b/src/Olm.h @@ -108,6 +108,6 @@ handle_key_request_message(const mtx::events::DeviceEvent Date: Sun, 18 Oct 2020 22:30:42 +0200 Subject: [PATCH 12/33] Hide room name, if not loaded yet --- resources/qml/TimelineView.qml | 2 +- resources/qml/delegates/ImageMessage.qml | 12 ++++++++---- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/resources/qml/TimelineView.qml b/resources/qml/TimelineView.qml index ab0148e9..1f5f406a 100644 --- a/resources/qml/TimelineView.qml +++ b/resources/qml/TimelineView.qml @@ -159,6 +159,7 @@ Page { } ColumnLayout { + visible: TimelineManager.timeline != null anchors.fill: parent Rectangle { @@ -287,7 +288,6 @@ Page { 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 diff --git a/resources/qml/delegates/ImageMessage.qml b/resources/qml/delegates/ImageMessage.qml index 6ac5ee15..5c3dac95 100644 --- a/resources/qml/delegates/ImageMessage.qml +++ b/resources/qml/delegates/ImageMessage.qml @@ -32,6 +32,7 @@ Item { MouseArea { id: mouseArea + enabled: model.data.type == MtxEvent.ImageMessage && img.status == Image.Ready hoverEnabled: true anchors.fill: parent @@ -40,23 +41,23 @@ Item { Item { id: overlay - + anchors.fill: parent visible: mouseArea.containsMouse Rectangle { id: container - + width: parent.width implicitHeight: imgcaption.implicitHeight - anchors.bottom: overlay.bottom + anchors.bottom: overlay.bottom color: colors.window opacity: 0.75 } Text { id: imgcaption - + anchors.fill: container elide: Text.ElideMiddle horizontalAlignment: Text.AlignHCenter @@ -65,6 +66,9 @@ Item { text: model.data.filename ? model.data.filename : model.data.body color: colors.text } + } + } + } From aa9b453f8109b70c9e65fd21f2f3fcce0e73b544 Mon Sep 17 00:00:00 2001 From: Nicolas Werner Date: Tue, 20 Oct 2020 13:46:05 +0200 Subject: [PATCH 13/33] Store timestamp with olm sessions --- src/Cache.cpp | 145 +++++++++++++++++++++++++++++++-- src/Cache.h | 6 +- src/CacheCryptoStructs.h | 10 +++ src/Cache_p.h | 8 +- src/Olm.cpp | 26 ++++-- src/Olm.h | 5 ++ src/timeline/EventStore.cpp | 6 ++ src/timeline/EventStore.h | 1 + src/timeline/TimelineModel.cpp | 3 +- 9 files changed, 191 insertions(+), 19 deletions(-) diff --git a/src/Cache.cpp b/src/Cache.cpp index 5a0740f1..d9db99b0 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!"); @@ -3629,6 +3740,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) @@ -4114,9 +4238,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 +4254,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..24b6df9e 100644 --- a/src/Cache.h +++ b/src/Cache.h @@ -292,11 +292,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..62b1ad37 100644 --- a/src/Cache_p.h +++ b/src/Cache_p.h @@ -266,10 +266,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(); @@ -565,7 +569,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) diff --git a/src/Olm.cpp b/src/Olm.cpp index fee685a3..e3b0de27 100644 --- a/src/Olm.cpp +++ b/src/Olm.cpp @@ -47,7 +47,7 @@ handle_to_device_messages(const std::vectorwarn( @@ -56,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)) { @@ -250,7 +246,8 @@ 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)); + 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()); @@ -318,7 +315,8 @@ 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())); + 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, @@ -672,7 +670,9 @@ send_megolm_key_to_device(const std::string &user_id, device_msg = olm::client()->create_olm_encrypted_content( olm_session.get(), json(room_key).dump(), pks.curve25519); - cache::saveOlmSession(pks.curve25519, std::move(olm_session)); + cache::saveOlmSession(pks.curve25519, + std::move(olm_session), + QDateTime::currentMSecsSinceEpoch()); } catch (const json::exception &e) { nhlog::crypto()->warn("creating outbound session: {}", e.what()); @@ -749,4 +749,14 @@ 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 "*" +void +send_encrypted_to_device_messages(const std::map> targets, + const mtx::events::collections::DeviceEvents &event) +{ + (void)targets; + (void)event; +} + } // namespace olm diff --git a/src/Olm.h b/src/Olm.h index cda9f29a..2556a22d 100644 --- a/src/Olm.h +++ b/src/Olm.h @@ -110,4 +110,9 @@ send_megolm_key_to_device(const std::string &user_id, const std::string &device_id, const mtx::events::msg::ForwardedRoomKey &payload); +//! Send encrypted to device messages, targets is a map from userid to device ids or "*" +void +send_encrypted_to_device_messages(const std::map> targets, + const mtx::events::collections::DeviceEvents &event); + } // namespace olm diff --git a/src/timeline/EventStore.cpp b/src/timeline/EventStore.cpp index d3c5c3fa..3564ffc0 100644 --- a/src/timeline/EventStore.cpp +++ b/src/timeline/EventStore.cpp @@ -54,6 +54,9 @@ 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; + uint64_t newFirst = cache::client()->saveOldMessages(room_id_, res); if (newFirst == first) fetchMore(); @@ -687,6 +690,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..7f8e2396 100644 --- a/src/timeline/EventStore.h +++ b/src/timeline/EventStore.h @@ -123,4 +123,5 @@ private: 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..5db6aa00 100644 --- a/src/timeline/TimelineModel.cpp +++ b/src/timeline/TimelineModel.cpp @@ -1138,7 +1138,8 @@ TimelineModel::handleClaimedKeys( pks.at(user_id).at(device_id).curve25519); try { - cache::saveOlmSession(id_key, std::move(s)); + cache::saveOlmSession( + id_key, std::move(s), QDateTime::currentMSecsSinceEpoch()); } catch (const lmdb::error &e) { nhlog::db()->critical("failed to save outbound olm session: {}", e.what()); From 983690c94f1577c439cb41f0a5c77b64cfb4adee Mon Sep 17 00:00:00 2001 From: Nicolas Werner Date: Tue, 20 Oct 2020 18:10:09 +0200 Subject: [PATCH 14/33] Share code for sending encrypted olm messages --- src/Olm.cpp | 395 +++++++++++++++++++++------------ src/Olm.h | 6 +- src/timeline/TimelineModel.cpp | 223 +++---------------- src/timeline/TimelineModel.h | 6 - 4 files changed, 278 insertions(+), 352 deletions(-) diff --git a/src/Olm.cpp b/src/Olm.cpp index e3b0de27..730a3ea5 100644 --- a/src/Olm.cpp +++ b/src/Olm.cpp @@ -558,149 +558,13 @@ send_megolm_key_to_device(const std::string &user_id, const std::string &device_id, 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; - } - - mtx::requests::ClaimKeys claim_keys; - claim_keys.one_time_keys[user_id][device_id] = mtx::crypto::SIGNED_CURVE25519; - - http::client()->claim_keys( - claim_keys, - [payload, 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")); - - mtx::events::DeviceEvent - room_key; - room_key.content = payload; - room_key.type = mtx::events::EventType::ForwardedRoomKey; - device_msg = olm::client()->create_olm_encrypted_content( - olm_session.get(), json(room_key).dump(), pks.curve25519); - - cache::saveOlmSession(pks.curve25519, - std::move(olm_session), - QDateTime::currentMSecsSinceEpoch()); - } 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.forwarded_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.forwarded_room_key send to {}", - user_id); - }); - }); - }); + std::map> targets; + targets[user_id] = {device_id}; + send_encrypted_to_device_messages(targets, room_key); } DecryptionResult @@ -750,13 +614,252 @@ 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 "*" +//! 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) + const mtx::events::collections::DeviceEvents &event, + bool force_new_session) { - (void)targets; - (void)event; + nlohmann::json ev_json = std::visit([](const auto &e) { return json(e); }, event); + + std::map> keysToQuery; + mtx::requests::ClaimKeys claims; + std::map> + messages; + std::map> pks; + + 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 { + 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 { + 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 2556a22d..ce362e26 100644 --- a/src/Olm.h +++ b/src/Olm.h @@ -110,9 +110,11 @@ send_megolm_key_to_device(const std::string &user_id, const std::string &device_id, const mtx::events::msg::ForwardedRoomKey &payload); -//! Send encrypted to device messages, targets is a map from userid to device ids or "*" +//! 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); + const mtx::events::collections::DeviceEvents &event, + bool force_new_session = false); } // namespace olm diff --git a/src/timeline/TimelineModel.cpp b/src/timeline/TimelineModel.cpp index 5db6aa00..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,87 +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), 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()); - } - - 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..9f250b33 100644 --- a/src/timeline/TimelineModel.h +++ b/src/timeline/TimelineModel.h @@ -297,12 +297,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); From cea7f4574f92929c31cd89706075e769d2bfcf56 Mon Sep 17 00:00:00 2001 From: Nicolas Werner Date: Tue, 20 Oct 2020 19:46:37 +0200 Subject: [PATCH 15/33] Clean up key requests + autoreload --- CMakeLists.txt | 2 +- io.github.NhekoReborn.Nheko.json | 2 +- src/ChatPage.cpp | 6 ++++ src/ChatPage.h | 2 ++ src/Olm.cpp | 47 +++++++------------------ src/Olm.h | 8 ++--- src/timeline/EventStore.cpp | 51 ++++++++++++++++++++-------- src/timeline/EventStore.h | 8 +++++ src/timeline/TimelineModel.h | 4 +++ src/timeline/TimelineViewManager.cpp | 9 +++++ src/timeline/TimelineViewManager.h | 1 + 11 files changed, 84 insertions(+), 56 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 2f83a865..4f6a6a38 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 da9958e14e035fbf22d498074d381b2ea0092a9d ) FetchContent_MakeAvailable(MatrixClient) else() diff --git a/io.github.NhekoReborn.Nheko.json b/io.github.NhekoReborn.Nheko.json index 59f4fa46..5f08dee0 100644 --- a/io.github.NhekoReborn.Nheko.json +++ b/io.github.NhekoReborn.Nheko.json @@ -146,7 +146,7 @@ "name": "mtxclient", "sources": [ { - "commit": "ad5575bc24089dc385e97d9ace026414b618775c", + "commit": "da9958e14e035fbf22d498074d381b2ea0092a9d", "type": "git", "url": "https://github.com/Nheko-Reborn/mtxclient.git" } diff --git a/src/ChatPage.cpp b/src/ChatPage.cpp index e61df263..8b6f1123 100644 --- a/src/ChatPage.cpp +++ b/src/ChatPage.cpp @@ -1249,6 +1249,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..bf649cc9 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(); diff --git a/src/Olm.cpp b/src/Olm.cpp index 730a3ea5..79b00774 100644 --- a/src/Olm.cpp +++ b/src/Olm.cpp @@ -388,9 +388,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 @@ -401,48 +402,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)); diff --git a/src/Olm.h b/src/Olm.h index ce362e26..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 &); diff --git a/src/timeline/EventStore.cpp b/src/timeline/EventStore.cpp index 3564ffc0..22809a20 100644 --- a/src/timeline/EventStore.cpp +++ b/src/timeline/EventStore.cpp @@ -212,6 +212,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) { @@ -294,18 +316,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); - // } - //} } } } @@ -501,7 +511,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 " @@ -512,8 +522,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 ({}, {}, {})", diff --git a/src/timeline/EventStore.h b/src/timeline/EventStore.h index 7f8e2396..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,13 @@ 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.h b/src/timeline/TimelineModel.h index 9f250b33..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; diff --git a/src/timeline/TimelineViewManager.cpp b/src/timeline/TimelineViewManager.cpp index 783584c9..1e8ed243 100644 --- a/src/timeline/TimelineViewManager.cpp +++ b/src/timeline/TimelineViewManager.cpp @@ -440,6 +440,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) { diff --git a/src/timeline/TimelineViewManager.h b/src/timeline/TimelineViewManager.h index 5e441562..b6f8b443 100644 --- a/src/timeline/TimelineViewManager.h +++ b/src/timeline/TimelineViewManager.h @@ -92,6 +92,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); From 911b461e5d25a30fea1174e6d9b7b9303a21e011 Mon Sep 17 00:00:00 2001 From: Nicolas Werner Date: Tue, 20 Oct 2020 21:35:49 +0200 Subject: [PATCH 16/33] Fix corrupt channel + add additional debugging --- CMakeLists.txt | 2 +- io.github.NhekoReborn.Nheko.json | 2 +- src/Olm.cpp | 9 +++++++++ 3 files changed, 11 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 4f6a6a38..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 da9958e14e035fbf22d498074d381b2ea0092a9d + GIT_TAG 6432e89a3465e58ed838dd2abdcb0f91bd4f05b0 ) FetchContent_MakeAvailable(MatrixClient) else() diff --git a/io.github.NhekoReborn.Nheko.json b/io.github.NhekoReborn.Nheko.json index 5f08dee0..c461ceaa 100644 --- a/io.github.NhekoReborn.Nheko.json +++ b/io.github.NhekoReborn.Nheko.json @@ -146,7 +146,7 @@ "name": "mtxclient", "sources": [ { - "commit": "da9958e14e035fbf22d498074d381b2ea0092a9d", + "commit": "6432e89a3465e58ed838dd2abdcb0f91bd4f05b0", "type": "git", "url": "https://github.com/Nheko-Reborn/mtxclient.git" } diff --git a/src/Olm.cpp b/src/Olm.cpp index 79b00774..d6e8c10b 100644 --- a/src/Olm.cpp +++ b/src/Olm.cpp @@ -246,6 +246,8 @@ handle_pre_key_olm_message(const std::string &sender, nhlog::crypto()->debug("decrypted message: \n {}", plaintext.dump(2)); try { + nhlog::crypto()->debug("New olm session: {}", + mtx::crypto::session_id(inbound_session.get())); cache::saveOlmSession( sender_key, std::move(inbound_session), QDateTime::currentMSecsSinceEpoch()); } catch (const lmdb::error &e) { @@ -315,6 +317,8 @@ 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); + 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) { @@ -651,6 +655,8 @@ send_encrypted_to_device_messages(const std::map(); 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()); @@ -722,6 +728,9 @@ send_encrypted_to_device_messages(const std::map(); try { + nhlog::crypto()->debug( + "Updated olm session: {}", + mtx::crypto::session_id(session.get())); cache::saveOlmSession( id_key, std::move(session), From 8a188ac6f7a4f0f4ebad009c9fd932b005a718c9 Mon Sep 17 00:00:00 2001 From: LorenDB Date: Mon, 19 Oct 2020 20:38:18 -0400 Subject: [PATCH 17/33] Use QFontComboBox as per issue 238 (deleted comments) Note: I have deleted commented code as per the suggestion of @utkarsh2102. Note 2: I have fixed all the diffs, etc. --- src/UserSettingsPage.cpp | 7 ++----- src/UserSettingsPage.h | 3 ++- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/src/UserSettingsPage.cpp b/src/UserSettingsPage.cpp index 8f049a29..4278180b 100644 --- a/src/UserSettingsPage.cpp +++ b/src/UserSettingsPage.cpp @@ -18,6 +18,7 @@ #include #include #include +#include #include #include #include @@ -470,7 +471,7 @@ UserSettingsPage::UserSettingsPage(QSharedPointer settings, QWidge useStunServer_ = new Toggle{this}; scaleFactorCombo_ = new QComboBox{this}; fontSizeCombo_ = new QComboBox{this}; - fontSelectionCombo_ = new QComboBox{this}; + fontSelectionCombo_ = new QFontComboBox{this}; emojiFontSelectionCombo_ = new QComboBox{this}; timelineMaxWidthSpin_ = new QSpinBox{this}; @@ -490,10 +491,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? diff --git a/src/UserSettingsPage.h b/src/UserSettingsPage.h index e4a34893..37355602 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; @@ -261,7 +262,7 @@ private: QComboBox *themeCombo_; QComboBox *scaleFactorCombo_; QComboBox *fontSizeCombo_; - QComboBox *fontSelectionCombo_; + QFontComboBox *fontSelectionCombo_; QComboBox *emojiFontSelectionCombo_; QSpinBox *timelineMaxWidthSpin_; From ce29bf00c7941d6ee10276aab62cca71e8bb335d Mon Sep 17 00:00:00 2001 From: LorenDB Date: Wed, 21 Oct 2020 22:17:51 -0400 Subject: [PATCH 18/33] Delete text on Ctrl-U --- src/TextInputWidget.cpp | 3 +++ 1 file changed, 3 insertions(+) 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(); From 66445c507c4ea9322c91306e7ee0d145b8dbd94d Mon Sep 17 00:00:00 2001 From: Nicolas Werner Date: Thu, 22 Oct 2020 20:48:28 +0200 Subject: [PATCH 19/33] Trade CPU usage for 30MB of members in RAM --- src/Cache.cpp | 115 +++++++--------------------------------- src/Cache.h | 13 ----- src/Cache_p.h | 36 ++----------- src/ChatPage.cpp | 2 - src/Olm.cpp | 4 +- src/TextInputWidget.cpp | 4 +- src/Utils.cpp | 2 +- 7 files changed, 30 insertions(+), 146 deletions(-) diff --git a/src/Cache.cpp b/src/Cache.cpp index d9db99b0..090af913 100644 --- a/src/Cache.cpp +++ b/src/Cache.cpp @@ -2280,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_); - 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 @@ -3145,15 +3133,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; } @@ -3161,9 +3146,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; } @@ -3171,41 +3155,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 @@ -3793,28 +3747,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) { @@ -3826,13 +3758,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) diff --git a/src/Cache.h b/src/Cache.h index 24b6df9e..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(); diff --git a/src/Cache_p.h b/src/Cache_p.h index 62b1ad37..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); @@ -308,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, @@ -364,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; } } @@ -602,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 8b6f1123..103905d1 100644 --- a/src/ChatPage.cpp +++ b/src/ChatPage.cpp @@ -774,8 +774,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()); diff --git a/src/Olm.cpp b/src/Olm.cpp index d6e8c10b..8cc9bed8 100644 --- a/src/Olm.cpp +++ b/src/Olm.cpp @@ -417,8 +417,8 @@ send_key_request_for(mtx::events::EncryptedEvent e, e.content.session_id); mtx::events::msg::KeyRequest request; - request.action = !cancel ? mtx::events::msg::RequestAction::Request - : mtx::events::msg::RequestAction::Cancellation; + request.action = !cancel ? mtx::events::msg::RequestAction::Request + : mtx::events::msg::RequestAction::Cancellation; request.algorithm = MEGOLM_ALGO; request.room_id = e.room_id; request.sender_key = e.content.sender_key; diff --git a/src/TextInputWidget.cpp b/src/TextInputWidget.cpp index 22e8aafc..37a65f9e 100644 --- a/src/TextInputWidget.cpp +++ b/src/TextInputWidget.cpp @@ -450,8 +450,8 @@ FilteredTextEdit::completerRect() auto item_height = completer_->popup()->sizeHintForRow(0); auto max_height = item_height * completer_->maxVisibleItems(); auto height = (completer_->completionCount() > completer_->maxVisibleItems()) - ? max_height - : completer_->completionCount() * item_height; + ? max_height + : completer_->completionCount() * item_height; rect.setWidth(completer_->popup()->sizeHintForColumn(0)); rect.moveBottom(-height); return rect; diff --git a/src/Utils.cpp b/src/Utils.cpp index 0bfc82c3..38dbba22 100644 --- a/src/Utils.cpp +++ b/src/Utils.cpp @@ -638,7 +638,7 @@ utils::luminance(const QColor &col) qreal lumRgb[3]; for (int i = 0; i < 3; i++) { - qreal v = colRgb[i] / 255.0; + qreal v = colRgb[i] / 255.0; v <= 0.03928 ? lumRgb[i] = v / 12.92 : lumRgb[i] = qPow((v + 0.055) / 1.055, 2.4); } From e959443831c35f5e5478c049f007b961e55356c0 Mon Sep 17 00:00:00 2001 From: Nicolas Werner Date: Thu, 22 Oct 2020 21:02:39 +0200 Subject: [PATCH 20/33] Speed up rendering the timeline by a LOT by reducing clipping Sadly still required for replies, otherwise this would be perfect. --- resources/qml/delegates/NoticeMessage.qml | 2 +- resources/qml/delegates/TextMessage.qml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) 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/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
 }

From e537ece88d9c79bcdf26f05aaea16ff57799d26e Mon Sep 17 00:00:00 2001
From: Nicolas Werner 
Date: Fri, 23 Oct 2020 16:09:56 +0200
Subject: [PATCH 21/33] Reload message on m.room_key

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

diff --git a/src/Olm.cpp b/src/Olm.cpp
index 8cc9bed8..6e68bd42 100644
--- a/src/Olm.cpp
+++ b/src/Olm.cpp
@@ -369,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
@@ -417,8 +419,8 @@ send_key_request_for(mtx::events::EncryptedEvent e,
                                e.content.session_id);
 
         mtx::events::msg::KeyRequest request;
-        request.action               = !cancel ? mtx::events::msg::RequestAction::Request
-                                               : mtx::events::msg::RequestAction::Cancellation;
+        request.action = !cancel ? mtx::events::msg::RequestAction::Request
+                                 : mtx::events::msg::RequestAction::Cancellation;
         request.algorithm            = MEGOLM_ALGO;
         request.room_id              = e.room_id;
         request.sender_key           = e.content.sender_key;

From 1ab20e0ee318629d37fd8eef92b472750f6f1d5a Mon Sep 17 00:00:00 2001
From: Nicolas Werner 
Date: Fri, 23 Oct 2020 16:59:18 +0200
Subject: [PATCH 22/33] Fix share keys setting not working

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

diff --git a/src/UserSettingsPage.cpp b/src/UserSettingsPage.cpp
index 4278180b..5558b246 100644
--- a/src/UserSettingsPage.cpp
+++ b/src/UserSettingsPage.cpp
@@ -299,6 +299,7 @@ UserSettings::setShareKeysWithTrustedUsers(bool shareKeys)
 {
         if (shareKeys == shareKeysWithTrustedUsers_)
                 return;
+
         shareKeysWithTrustedUsers_ = shareKeys;
         emit shareKeysWithTrustedUsersChanged(shareKeys);
         save();
@@ -736,6 +737,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);
         });
@@ -810,6 +815,7 @@ 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());

From 4797b9fa9651c2bb5a35ca67497c2f0eb4956820 Mon Sep 17 00:00:00 2001
From: Nicolas Werner 
Date: Fri, 23 Oct 2020 19:42:12 +0200
Subject: [PATCH 23/33] Fix infinite pagination properly now.

---
 src/Cache.cpp               | 13 ++++++++++++-
 src/TextInputWidget.cpp     |  4 ++--
 src/Utils.cpp               |  2 +-
 src/timeline/EventStore.cpp |  5 ++++-
 4 files changed, 19 insertions(+), 5 deletions(-)

diff --git a/src/Cache.cpp b/src/Cache.cpp
index 090af913..3ffa9fd2 100644
--- a/src/Cache.cpp
+++ b/src/Cache.cpp
@@ -2712,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) {
diff --git a/src/TextInputWidget.cpp b/src/TextInputWidget.cpp
index 37a65f9e..22e8aafc 100644
--- a/src/TextInputWidget.cpp
+++ b/src/TextInputWidget.cpp
@@ -450,8 +450,8 @@ FilteredTextEdit::completerRect()
         auto item_height = completer_->popup()->sizeHintForRow(0);
         auto max_height  = item_height * completer_->maxVisibleItems();
         auto height      = (completer_->completionCount() > completer_->maxVisibleItems())
-                             ? max_height
-                             : completer_->completionCount() * item_height;
+                        ? max_height
+                        : completer_->completionCount() * item_height;
         rect.setWidth(completer_->popup()->sizeHintForColumn(0));
         rect.moveBottom(-height);
         return rect;
diff --git a/src/Utils.cpp b/src/Utils.cpp
index 38dbba22..0bfc82c3 100644
--- a/src/Utils.cpp
+++ b/src/Utils.cpp
@@ -638,7 +638,7 @@ utils::luminance(const QColor &col)
         qreal lumRgb[3];
 
         for (int i = 0; i < 3; i++) {
-                qreal v = colRgb[i] / 255.0;
+                qreal v                  = colRgb[i] / 255.0;
                 v <= 0.03928 ? lumRgb[i] = v / 12.92 : lumRgb[i] = qPow((v + 0.055) / 1.055, 2.4);
         }
 
diff --git a/src/timeline/EventStore.cpp b/src/timeline/EventStore.cpp
index 22809a20..38292f49 100644
--- a/src/timeline/EventStore.cpp
+++ b/src/timeline/EventStore.cpp
@@ -54,8 +54,11 @@ EventStore::EventStore(std::string room_id, QObject *)
           &EventStore::oldMessagesRetrieved,
           this,
           [this](const mtx::responses::Messages &res) {
-                  if (cache::client()->previousBatchToken(room_id_) == res.end)
+                  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)

From 53734607cf93d626c16f1b2ae72a7dea24d8460e Mon Sep 17 00:00:00 2001
From: Nicolas Werner 
Date: Sat, 24 Oct 2020 14:42:18 +0200
Subject: [PATCH 24/33] Fix copying text

---
 resources/qml/MatrixText.qml | 2 --
 1 file changed, 2 deletions(-)

diff --git a/resources/qml/MatrixText.qml b/resources/qml/MatrixText.qml
index 6c96a539..f1ccf561 100644
--- a/resources/qml/MatrixText.qml
+++ b/resources/qml/MatrixText.qml
@@ -7,7 +7,6 @@ TextEdit {
     readOnly: true
     wrapMode: Text.Wrap
     selectByMouse: true
-    activeFocusOnPress: false
     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
     }

From 3172811ca784c7bdaa02ad4e0a59a8f0ef381280 Mon Sep 17 00:00:00 2001
From: Nicolas Werner 
Date: Sat, 24 Oct 2020 16:21:00 +0200
Subject: [PATCH 25/33] Add mobile mode which improves scrolling

---
 resources/qml/MatrixText.qml |  2 +-
 src/UserSettingsPage.cpp     | 21 +++++++++++++++++++++
 src/UserSettingsPage.h       |  6 ++++++
 3 files changed, 28 insertions(+), 1 deletion(-)

diff --git a/resources/qml/MatrixText.qml b/resources/qml/MatrixText.qml
index f1ccf561..a5781c73 100644
--- a/resources/qml/MatrixText.qml
+++ b/resources/qml/MatrixText.qml
@@ -6,7 +6,7 @@ TextEdit {
     textFormat: TextEdit.RichText
     readOnly: true
     wrapMode: Text.Wrap
-    selectByMouse: true
+    selectByMouse: !Settings.mobileMode
     color: colors.text
     onLinkActivated: {
         if (/^https:\/\/matrix.to\/#\/(@.*)$/.test(link)) {
diff --git a/src/UserSettingsPage.cpp b/src/UserSettingsPage.cpp
index 5558b246..6fd31de3 100644
--- a/src/UserSettingsPage.cpp
+++ b/src/UserSettingsPage.cpp
@@ -75,6 +75,7 @@ UserSettings::load()
         decryptSidebar_      = settings.value("user/decrypt_sidebar", true).toBool();
         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_ =
@@ -123,6 +124,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)
 {
@@ -389,6 +400,7 @@ 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_);
@@ -470,6 +482,7 @@ UserSettingsPage::UserSettingsPage(QSharedPointer settings, QWidge
         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};
@@ -637,6 +650,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_,
@@ -728,6 +744,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);
         });
@@ -820,6 +840,7 @@ UserSettingsPage::showEvent(QShowEvent *)
         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 37355602..ffe98542 100644
--- a/src/UserSettingsPage.h
+++ b/src/UserSettingsPage.h
@@ -67,6 +67,7 @@ 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(
@@ -99,6 +100,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);
@@ -130,6 +132,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_; }
@@ -163,6 +166,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);
@@ -193,6 +197,7 @@ private:
         bool avatarCircles_;
         bool decryptSidebar_;
         bool shareKeysWithTrustedUsers_;
+        bool mobileMode_;
         int timelineMaxWidth_;
         double baseFontSize_;
         QString font_;
@@ -256,6 +261,7 @@ private:
         Toggle *useStunServer_;
         Toggle *decryptSidebar_;
         Toggle *shareKeysWithTrustedUsers_;
+        Toggle *mobileMode_;
         QLabel *deviceFingerprintValue_;
         QLabel *deviceIdValue_;
 

From a2bd266c113416a41564839835fe339dd76eabb9 Mon Sep 17 00:00:00 2001
From: LorenDB 
Date: Wed, 21 Oct 2020 19:20:02 -0400
Subject: [PATCH 26/33] Add option for unique config

---
 README.md                |  1 +
 src/ChatPage.cpp         | 16 ++-------------
 src/ChatPage.h           |  3 +--
 src/MainWindow.cpp       | 22 +++++++++++++++++----
 src/MainWindow.h         |  6 +++++-
 src/UserSettingsPage.cpp |  3 +++
 src/main.cpp             | 42 +++++++++++++++++++++++++++++++++++++---
 7 files changed, 69 insertions(+), 24 deletions(-)

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/src/ChatPage.cpp b/src/ChatPage.cpp
index 8b6f1123..d5a7fe87 100644
--- a/src/ChatPage.cpp
+++ b/src/ChatPage.cpp
@@ -272,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,
@@ -626,7 +626,7 @@ ChatPage::resetUI()
         user_info_widget_->reset();
         view_manager_->clearAll();
 
-        showUnreadMessageNotification(0);
+        emit unreadMessages(0);
 }
 
 void
@@ -751,18 +751,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()
 {
diff --git a/src/ChatPage.h b/src/ChatPage.h
index bf649cc9..a29cea7b 100644
--- a/src/ChatPage.h
+++ b/src/ChatPage.h
@@ -130,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);
@@ -188,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..c019b24b 100644
--- a/src/MainWindow.cpp
+++ b/src/MainWindow.cpp
@@ -53,10 +53,11 @@
 
 MainWindow *MainWindow::instance_ = nullptr;
 
-MainWindow::MainWindow(QWidget *parent)
-  : QMainWindow(parent)
+MainWindow::MainWindow(const QString profile, QWidget *parent)
+  : QMainWindow(parent),
+    profile_{ profile }
 {
-        setWindowTitle("nheko");
+        setWindowTitle(0);
         setObjectName("MainWindow");
 
         modal_ = new OverlayModal(this);
@@ -104,7 +105,7 @@ MainWindow::MainWindow(QWidget *parent)
         connect(
           chat_page_, &ChatPage::showOverlayProgressBar, this, &MainWindow::showOverlayProgressBar);
         connect(
-          chat_page_, SIGNAL(changeWindowTitle(QString)), this, SLOT(setWindowTitle(QString)));
+          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 +179,19 @@ 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/UserSettingsPage.cpp b/src/UserSettingsPage.cpp
index 4278180b..059fcb47 100644
--- a/src/UserSettingsPage.cpp
+++ b/src/UserSettingsPage.cpp
@@ -36,6 +36,7 @@
 #include 
 #include 
 #include 
+#include 
 
 #include "Cache.h"
 #include "Config.h"
@@ -433,6 +434,8 @@ 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;
diff --git a/src/main.cpp b/src/main.cpp
index e02ffa36..61cb4fbe 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -104,10 +104,37 @@ 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 +164,15 @@ 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 +217,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()));

From 3d7bb22df4a408ab837710f2e44810df975a667c Mon Sep 17 00:00:00 2001
From: Nicolas Werner 
Date: Sat, 24 Oct 2020 18:07:14 +0200
Subject: [PATCH 27/33] Fix login

---
 src/Cache.cpp | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/Cache.cpp b/src/Cache.cpp
index 3ffa9fd2..993fbfe7 100644
--- a/src/Cache.cpp
+++ b/src/Cache.cpp
@@ -2284,7 +2284,7 @@ std::optional
 Cache::getMember(const std::string &room_id, const std::string &user_id)
 {
         try {
-                auto txn = lmdb::txn::begin(env_);
+                auto txn = lmdb::txn::begin(env_, nullptr, MDB_RDONLY);
 
                 auto membersdb = getMembersDb(txn, room_id);
 

From 3a0e55e97b667feaf3d9e9e0296664db246d4033 Mon Sep 17 00:00:00 2001
From: Nicolas Werner 
Date: Mon, 26 Oct 2020 13:50:44 +0100
Subject: [PATCH 28/33] Qml message input mockup

---
 resources/qml/Avatar.qml                      |   2 +-
 resources/qml/Reactions.qml                   |   2 +-
 resources/qml/TimelineRow.qml                 |   2 +-
 resources/qml/TimelineView.qml                | 549 +++++++++++-------
 resources/qml/delegates/FileMessage.qml       |   2 +-
 resources/qml/delegates/Pill.qml              |   2 +-
 .../qml/delegates/PlayableMediaMessage.qml    |   2 +-
 resources/qml/emoji/EmojiPicker.qml           |   4 +-
 src/UserSettingsPage.cpp                      |  20 +-
 9 files changed, 342 insertions(+), 243 deletions(-)

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/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/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 1f5f406a..3124d462 100644
--- a/resources/qml/TimelineView.qml
+++ b/resources/qml/TimelineView.qml
@@ -161,6 +161,7 @@ Page {
         ColumnLayout {
             visible: TimelineManager.timeline != null
             anchors.fill: parent
+            spacing: 0
 
             Rectangle {
                 id: topBar
@@ -168,7 +169,7 @@ Page {
                 Layout.fillWidth: true
                 implicitHeight: topLayout.height + 16
                 z: 3
-                color: colors.base
+                color: colors.window
 
                 MouseArea {
                     anchors.fill: parent
@@ -283,143 +284,156 @@ Page {
 
             }
 
-            ListView {
-                id: chat
+            Rectangle {
+                Layout.fillWidth: true
+                height: 1
+                z: 3
+                color: colors.mid
+            }
 
-                property int delegateMaxWidth: (Settings.timelineMaxWidth > 100 && (parent.width - Settings.timelineMaxWidth) > scrollbar.width * 2) ? Settings.timelineMaxWidth : (parent.width - scrollbar.width * 2)
-
-                cacheBuffer: 400
+            Rectangle {
                 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;
+                color: colors.base
 
-                } // Mark last event as read, since we are at the bottom
+                ListView {
+                    id: chat
 
-                ScrollHelper {
-                    flickable: parent
+                    property int delegateMaxWidth: (Settings.timelineMaxWidth > 100 && (parent.width - Settings.timelineMaxWidth) > scrollbar.width * 2) ? Settings.timelineMaxWidth : (parent.width - scrollbar.width * 2)
+
+                    cacheBuffer: 400
                     anchors.fill: parent
-                }
+                    model: TimelineManager.timeline
+                    boundsBehavior: Flickable.StopAtBounds
+                    pixelAligned: true
+                    spacing: 4
+                    verticalLayoutDirection: ListView.BottomToTop
+                    onCountChanged: {
+                        if (atYEnd)
+                            model.currentIndex = 0;
 
-                Shortcut {
-                    sequence: StandardKey.MoveToPreviousPage
-                    onActivated: {
-                        chat.contentY = chat.contentY - chat.height / 2;
-                        chat.returnToBounds();
-                    }
-                }
+                    } // Mark last event as read, since we are at the bottom
 
-                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 {
+                    ScrollHelper {
+                        flickable: parent
+                        anchors.fill: parent
                     }
 
-                }
+                    Shortcut {
+                        sequence: StandardKey.MoveToPreviousPage
+                        onActivated: {
+                            chat.contentY = chat.contentY - chat.height / 2;
+                            chat.returnToBounds();
+                        }
+                    }
 
-                section {
-                    property: "section"
-                }
+                    Shortcut {
+                        sequence: StandardKey.MoveToNextPage
+                        onActivated: {
+                            chat.contentY = chat.contentY + chat.height / 2;
+                            chat.returnToBounds();
+                        }
+                    }
 
-                Component {
-                    id: sectionHeader
+                    Shortcut {
+                        sequence: StandardKey.Cancel
+                        onActivated: chat.model.reply = undefined
+                    }
 
-                    Column {
-                        property var modelData
-                        property string section
-                        property string nextSection
+                    Shortcut {
+                        sequence: "Alt+Up"
+                        onActivated: chat.model.reply = chat.model.indexToId(chat.model.reply ? chat.model.idToIndex(chat.model.reply) + 1 : 0)
+                    }
 
-                        topPadding: 4
-                        bottomPadding: 4
-                        spacing: 8
-                        visible: !!modelData
-                        width: parent.width
-                        height: (section.includes(" ") ? dateBubble.height + 8 + userName.height : userName.height) + 8
+                    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;
+                        }
+                    }
 
-                        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
-                            }
+                    Component {
+                        id: userProfileComponent
 
+                        UserProfile {
                         }
 
-                        Row {
-                            height: userName.height
+                    }
+
+                    section {
+                        property: "section"
+                    }
+
+                    Component {
+                        id: sectionHeader
+
+                        Column {
+                            property var modelData
+                            property string section
+                            property string nextSection
+
+                            topPadding: 4
+                            bottomPadding: 4
                             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
-                                }
-
-                            }
+                            visible: !!modelData
+                            width: parent.width
+                            height: (section.includes(" ") ? dateBubble.height + 8 + userName.height : userName.height) + 8
 
                             Label {
-                                id: userName
+                                id: dateBubble
 
-                                text: TimelineManager.escapeEmoji(modelData.userName)
-                                color: TimelineManager.userColor(modelData.userId, colors.window)
-                                textFormat: Text.RichText
+                                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
+                                    }
 
-                                MouseArea {
-                                    anchors.fill: parent
-                                    Layout.alignment: Qt.AlignHCenter
-                                    onClicked: chat.model.openUserProfile(modelData.userId)
-                                    cursorShape: Qt.PointingHandCursor
-                                    propagateComposedEvents: true
                                 }
 
                             }
@@ -428,140 +442,130 @@ Page {
 
                     }
 
-                }
-
-                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;
-                        }
+                    ScrollBar.vertical: ScrollBar {
+                        id: scrollbar
                     }
 
-                    TimelineRow {
-                        id: timelinerow
+                    delegate: Item {
+                        id: wrapper
 
-                        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;
+                        // 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
                         }
 
-                        target: chat
                     }
 
-                }
+                    footer: BusyIndicator {
+                        anchors.horizontalCenter: parent.horizontalCenter
+                        running: chat.model && chat.model.paginationInProgress
+                        height: 50
+                        width: 50
+                        z: 3
+                    }
 
-                footer: BusyIndicator {
-                    anchors.horizontalCenter: parent.horizontalCenter
-                    running: chat.model && chat.model.paginationInProgress
-                    height: 50
-                    width: 50
-                    z: 3
                 }
 
             }
 
             Item {
-                id: chatFooter
-
-                implicitHeight: Math.max(fontMetrics.height * 1.2, footerContent.height)
+                implicitHeight: Math.max(fontMetrics.height * 1.2, typingDisplay.height)
                 Layout.fillWidth: true
-                z: 3
 
-                Column {
-                    id: footerContent
+                Rectangle {
+                    id: typingRect
 
+                    color: colors.base
                     anchors.left: parent.left
                     anchors.right: parent.right
+                    height: (chat.model && chat.model.typingUsers.length > 0) ? typingDisplay.height : 0
+                    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: chat.model ? chat.model.formatTypingUsers(chat.model.typingUsers, colors.base) : ""
+                        textFormat: Text.RichText
+                    }
+
+                }
+
+            }
+
+            Rectangle {
+                id: replyPopup
+
+                Layout.fillWidth: true
+                visible: chat.model && chat.model.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: 10
+                    anchors.right: closeReplyButton.left
+                    anchors.rightMargin: 20
                     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
-                        }
-
+                    modelData: chat.model ? chat.model.getDump(chat.model.reply, chat.model.id) : {
                     }
+                    userColor: TimelineManager.userColor(modelData.userId, colors.window)
+                }
 
-                    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
-                        }
-
-                    }
+                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
                 }
 
             }
@@ -571,6 +575,99 @@ Page {
                 z: 3
             }
 
+            Rectangle {
+                Layout.fillWidth: true
+                z: 3
+                height: 1
+                color: colors.mid
+            }
+
+            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/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/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/emoji/EmojiPicker.qml b/resources/qml/emoji/EmojiPicker.qml
index 0e277415..afe16350 100644
--- a/resources/qml/emoji/EmojiPicker.qml
+++ b/resources/qml/emoji/EmojiPicker.qml
@@ -161,7 +161,7 @@ Popup {
         Rectangle {
             Layout.fillWidth: true
             Layout.preferredHeight: 1
-            color: emojiPopup.colors.dark
+            color: emojiPopup.colors.alternateBase
         }
 
         // Category picker row
@@ -281,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/src/UserSettingsPage.cpp b/src/UserSettingsPage.cpp
index 53539407..d4d5dcb9 100644
--- a/src/UserSettingsPage.cpp
+++ b/src/UserSettingsPage.cpp
@@ -339,17 +339,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");
@@ -357,17 +358,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");

From 3d64df41dac45693d1e2626909f0e55d4b7a6f47 Mon Sep 17 00:00:00 2001
From: Nicolas Werner 
Date: Mon, 26 Oct 2020 14:57:54 +0100
Subject: [PATCH 29/33] Split up TimelineView into separate components

---
 resources/qml/ActiveCallBar.qml   |   2 +-
 resources/qml/MessageInput.qml    |  90 ++++++
 resources/qml/MessageView.qml     | 202 ++++++++++++
 resources/qml/ReplyPopup.qml      |  47 +++
 resources/qml/TimelineView.qml    | 495 ++----------------------------
 resources/qml/TopBar.qml          | 127 ++++++++
 resources/qml/TypingIndicator.qml |  35 +++
 resources/res.qrc                 |   5 +
 8 files changed, 531 insertions(+), 472 deletions(-)
 create mode 100644 resources/qml/MessageInput.qml
 create mode 100644 resources/qml/MessageView.qml
 create mode 100644 resources/qml/ReplyPopup.qml
 create mode 100644 resources/qml/TopBar.qml
 create mode 100644 resources/qml/TypingIndicator.qml

diff --git a/resources/qml/ActiveCallBar.qml b/resources/qml/ActiveCallBar.qml
index 9344738e..dcdec9ef 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
 
     RowLayout {
         id: rowLayout
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/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/TimelineView.qml b/resources/qml/TimelineView.qml
index 3124d462..023bae00 100644
--- a/resources/qml/TimelineView.qml
+++ b/resources/qml/TimelineView.qml
@@ -43,12 +43,21 @@ Page {
 
     }
 
+    Component {
+        id: userProfileComponent
+
+        UserProfile {
+        }
+
+    }
+
     Menu {
         id: messageContextMenu
 
         property string eventId
         property int eventType
         property bool isEncrypted
+        property var room: TimelineManager.timeline
 
         function show(eventId_, eventType_, isEncrypted_, showAt_, position) {
             eventId = eventId_;
@@ -69,12 +78,12 @@ Page {
 
         MenuItem {
             text: qsTr("Reply")
-            onClicked: chat.model.replyAction(messageContextMenu.eventId)
+            onClicked: room.replyAction(messageContextMenu.eventId)
         }
 
         MenuItem {
             text: qsTr("Read receipts")
-            onTriggered: chat.model.readReceiptsAction(messageContextMenu.eventId)
+            onTriggered: room.readReceiptsAction(messageContextMenu.eventId)
         }
 
         MenuItem {
@@ -83,19 +92,19 @@ Page {
 
         MenuItem {
             text: qsTr("View raw message")
-            onTriggered: chat.model.viewRawMessage(messageContextMenu.eventId)
+            onTriggered: room.viewRawMessage(messageContextMenu.eventId)
         }
 
         MenuItem {
             visible: messageContextMenu.isEncrypted
             height: visible ? implicitHeight : 0
             text: qsTr("View decrypted raw message")
-            onTriggered: chat.model.viewDecryptedRawMessage(messageContextMenu.eventId)
+            onTriggered: room.viewDecryptedRawMessage(messageContextMenu.eventId)
         }
 
         MenuItem {
             text: qsTr("Redact message")
-            onTriggered: chat.model.redactEvent(messageContextMenu.eventId)
+            onTriggered: room.redactEvent(messageContextMenu.eventId)
         }
 
         MenuItem {
@@ -163,125 +172,7 @@ Page {
             anchors.fill: parent
             spacing: 0
 
-            Rectangle {
-                id: topBar
-
-                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: 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()
-                            }
-
-                        }
-
-                    }
-
-                }
-
+            TopBar {
             }
 
             Rectangle {
@@ -296,276 +187,18 @@ Page {
                 Layout.fillHeight: true
                 color: colors.base
 
-                ListView {
-                    id: chat
-
-                    property int delegateMaxWidth: (Settings.timelineMaxWidth > 100 && (parent.width - Settings.timelineMaxWidth) > scrollbar.width * 2) ? Settings.timelineMaxWidth : (parent.width - scrollbar.width * 2)
-
-                    cacheBuffer: 400
+                ColumnLayout {
                     anchors.fill: parent
-                    model: TimelineManager.timeline
-                    boundsBehavior: Flickable.StopAtBounds
-                    pixelAligned: true
-                    spacing: 4
-                    verticalLayoutDirection: ListView.BottomToTop
-                    onCountChanged: {
-                        if (atYEnd)
-                            model.currentIndex = 0;
+                    spacing: 0
 
-                    } // Mark last event as read, since we are at the bottom
-
-                    ScrollHelper {
-                        flickable: parent
-                        anchors.fill: parent
+                    MessageView {
+                        Layout.fillWidth: true
+                        Layout.fillHeight: true
                     }
 
-                    Shortcut {
-                        sequence: StandardKey.MoveToPreviousPage
-                        onActivated: {
-                            chat.contentY = chat.contentY - chat.height / 2;
-                            chat.returnToBounds();
-                        }
+                    TypingIndicator {
                     }
 
-                    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.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
-                    }
-
-                }
-
-            }
-
-            Item {
-                implicitHeight: Math.max(fontMetrics.height * 1.2, typingDisplay.height)
-                Layout.fillWidth: true
-
-                Rectangle {
-                    id: typingRect
-
-                    color: colors.base
-                    anchors.left: parent.left
-                    anchors.right: parent.right
-                    height: (chat.model && chat.model.typingUsers.length > 0) ? typingDisplay.height : 0
-                    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: chat.model ? chat.model.formatTypingUsers(chat.model.typingUsers, colors.base) : ""
-                        textFormat: Text.RichText
-                    }
-
-                }
-
-            }
-
-            Rectangle {
-                id: replyPopup
-
-                Layout.fillWidth: true
-                visible: chat.model && chat.model.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: 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
                 }
 
             }
@@ -582,90 +215,10 @@ Page {
                 color: colors.mid
             }
 
-            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
-                    }
-
-                }
+            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/res.qrc b/resources/res.qrc
index 87216e30..d11b78bc 100644
--- a/resources/res.qrc
+++ b/resources/res.qrc
@@ -121,6 +121,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

From 0e7baa21aba53f5f5f0c96592c4ab1bff2316430 Mon Sep 17 00:00:00 2001
From: Nicolas Werner 
Date: Mon, 26 Oct 2020 21:32:08 +0100
Subject: [PATCH 30/33] Disable new MessageInput, until you can paste images

---
 resources/qml/TimelineView.qml  | 4 ++--
 resources/styles/nheko-dark.qss | 5 +----
 resources/styles/nheko.qss      | 6 ------
 resources/styles/system.qss     | 4 +---
 4 files changed, 4 insertions(+), 15 deletions(-)

diff --git a/resources/qml/TimelineView.qml b/resources/qml/TimelineView.qml
index 023bae00..516c6860 100644
--- a/resources/qml/TimelineView.qml
+++ b/resources/qml/TimelineView.qml
@@ -218,8 +218,8 @@ Page {
             ReplyPopup {
             }
 
-            MessageInput {
-            }
+            //MessageInput {
+            //}
 
         }
 
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);
 }

From 62651fab026e9323cc9b694f6f135b19c0a47005 Mon Sep 17 00:00:00 2001
From: Joseph Donofry 
Date: Mon, 26 Oct 2020 16:37:03 -0400
Subject: [PATCH 31/33] Run update-translations

---
 resources/langs/nheko_cs.ts    | 146 ++++++++++++++++++++----------
 resources/langs/nheko_de.ts    | 146 ++++++++++++++++++++----------
 resources/langs/nheko_el.ts    | 146 ++++++++++++++++++++----------
 resources/langs/nheko_en.ts    | 160 ++++++++++++++++++++++-----------
 resources/langs/nheko_eo.ts    | 150 +++++++++++++++++++++----------
 resources/langs/nheko_et.ts    | 160 ++++++++++++++++++++++-----------
 resources/langs/nheko_fi.ts    | 146 ++++++++++++++++++++----------
 resources/langs/nheko_fr.ts    | 160 ++++++++++++++++++++++-----------
 resources/langs/nheko_it.ts    | 146 ++++++++++++++++++++----------
 resources/langs/nheko_ja.ts    | 146 ++++++++++++++++++++----------
 resources/langs/nheko_nl.ts    | 146 ++++++++++++++++++++----------
 resources/langs/nheko_pl.ts    | 146 ++++++++++++++++++++----------
 resources/langs/nheko_pt_PT.ts | 146 ++++++++++++++++++++----------
 resources/langs/nheko_ro.ts    | 146 ++++++++++++++++++++----------
 resources/langs/nheko_ru.ts    | 146 ++++++++++++++++++++----------
 resources/langs/nheko_si.ts    | 146 ++++++++++++++++++++----------
 resources/langs/nheko_zh_CN.ts | 146 ++++++++++++++++++++----------
 17 files changed, 1740 insertions(+), 788 deletions(-)

diff --git a/resources/langs/nheko_cs.ts b/resources/langs/nheko_cs.ts
index 9a79837a..318ceb64 100644
--- a/resources/langs/nheko_cs.ts
+++ b/resources/langs/nheko_cs.ts
@@ -50,7 +50,7 @@
 
     Cache
     
-        
+        
         You joined this room.
         
     
@@ -58,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.
         
     
@@ -154,12 +154,12 @@
         
     
     
-        
+        
         Failed to upload media. Please try again.
         
     
     
-        
+        
         Cache migration failed!
         
     
@@ -174,7 +174,7 @@
         
     
     
-        
+        
         Failed to restore OLM account. Please login again.
         
     
@@ -190,12 +190,12 @@
     
     
         
-        
+        
         Please try to login again: %1
         
     
     
-        
+        
         Failed to join room: %1
         
     
@@ -304,7 +304,7 @@
 
     EmojiPicker
     
-        
+        
         
         Search
         
@@ -389,13 +389,13 @@
 
     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.
@@ -637,6 +637,14 @@ Example: https://server.my:8787
         
     
 
+
+    MessageInput
+    
+        
+        Write a message...
+        
+    
+
 
     NewVerificationRequest
     
@@ -688,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
     
@@ -764,10 +790,18 @@ Example: https://server.my:8787
         
     
 
+
+    ReplyPopup
+    
+        
+        Close
+        
+    
+
 
     RoomInfo
     
-        
+        
         no version stored
         
     
@@ -827,7 +861,7 @@ Example: https://server.my:8787
         
     
     
-        
+        
         Accept
         
     
@@ -909,7 +943,7 @@ Example: https://server.my:8787
 
     TextInputWidget
     
-        
+        
         Send a file
         
     
@@ -963,15 +997,14 @@ Example: https://server.my:8787
         
     
     
-        
-        
-        
+        
+        
         
         Failed to encrypt event, sending aborted!
         
     
     
-        
+        
         Save image
         
     
@@ -1111,12 +1144,12 @@ Example: https://server.my:8787
         
     
     
-        
+        
         You joined this room.
         
     
     
-        
+        
         Rejected the knock from %1.
         
     
@@ -1158,7 +1191,7 @@ Example: https://server.my:8787
 
     TimelineView
     
-        
+        
         React
         
     
@@ -1202,8 +1235,19 @@ Example: https://server.my:8787
         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
         
     
@@ -1238,19 +1282,6 @@ Example: https://server.my:8787
         Settings
         
     
-    
-        
-        Close
-        
-    
-
-
-    TimelineViewManager
-    
-        
-        No share room with this user found. Create an encrypted room with this user and try again.
-        
-    
 
 
     TrayIcon
@@ -1334,7 +1365,7 @@ Example: https://server.my:8787
 
     UserSettingsPage
     
-        
+        
         Minimize to tray
         
     
@@ -1354,7 +1385,12 @@ Example: https://server.my:8787
         
     
     
-        
+        
+        profile: %1
+        
+    
+    
+        
         CALLS
         
     
@@ -1497,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
         
     
@@ -1542,7 +1588,7 @@ This usually causes the application icon in the task bar to animate in some fash
         
     
     
-        
+        
         Session Keys
         
     
@@ -1562,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
         
     
diff --git a/resources/langs/nheko_de.ts b/resources/langs/nheko_de.ts
index 5dd76df1..7afd8c51 100644
--- a/resources/langs/nheko_de.ts
+++ b/resources/langs/nheko_de.ts
@@ -50,7 +50,7 @@
 
     Cache
     
-        
+        
         You joined this room.
         Du bist dem Raum beigetreten.
     
@@ -58,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.
     
@@ -154,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!
     
@@ -174,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.
     
@@ -190,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
     
@@ -304,7 +304,7 @@
 
     EmojiPicker
     
-        
+        
         
         Search
         Suche
@@ -389,13 +389,13 @@
 
     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.
@@ -641,6 +641,14 @@ Beispiel: https://mein.server:8787
         
     
 
+
+    MessageInput
+    
+        
+        Write a message...
+        Schreibe eine Nachricht…
+    
+
 
     NewVerificationRequest
     
@@ -692,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
     
@@ -768,10 +794,18 @@ Beispiel: https://mein.server:8787
         Ungültiger Servername
     
 
+
+    ReplyPopup
+    
+        
+        Close
+        Schließen
+    
+
 
     RoomInfo
     
-        
+        
         no version stored
         keine Version gespeichert
     
@@ -831,7 +865,7 @@ Beispiel: https://mein.server:8787
         
     
     
-        
+        
         Accept
         Akzeptieren
     
@@ -913,7 +947,7 @@ Beispiel: https://mein.server:8787
 
     TextInputWidget
     
-        
+        
         Send a file
         Versende Datei
     
@@ -967,15 +1001,14 @@ Beispiel: https://mein.server:8787
         Nachricht zurückziehen fehlgeschlagen: %1
     
     
-        
-        
-        
+        
+        
         
         Failed to encrypt event, sending aborted!
         
     
     
-        
+        
         Save image
         Bild speichern
     
@@ -1114,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.
     
@@ -1161,7 +1194,7 @@ Beispiel: https://mein.server:8787
 
     TimelineView
     
-        
+        
         React
         
     
@@ -1205,8 +1238,19 @@ Beispiel: https://mein.server:8787
         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
         
     
@@ -1241,19 +1285,6 @@ Beispiel: https://mein.server:8787
         Settings
         Einstellungen
     
-    
-        
-        Close
-        Schließen
-    
-
-
-    TimelineViewManager
-    
-        
-        No share room with this user found. Create an encrypted room with this user and try again.
-        
-    
 
 
     TrayIcon
@@ -1337,7 +1368,7 @@ Beispiel: https://mein.server:8787
 
     UserSettingsPage
     
-        
+        
         Minimize to tray
         Ins Benachrichtigungsfeld minimieren
     
@@ -1357,7 +1388,12 @@ Beispiel: https://mein.server:8787
         Runde Profilbilder
     
     
-        
+        
+        profile: %1
+        
+    
+    
+        
         CALLS
         
     
@@ -1500,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
     
@@ -1545,7 +1591,7 @@ This usually causes the application icon in the task bar to animate in some fash
         Gerätefingerabdruck
     
     
-        
+        
         Session Keys
         Sitzungsschlüssel
     
@@ -1565,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
     
diff --git a/resources/langs/nheko_el.ts b/resources/langs/nheko_el.ts
index 02cc7878..d943c6f1 100644
--- a/resources/langs/nheko_el.ts
+++ b/resources/langs/nheko_el.ts
@@ -50,7 +50,7 @@
 
     Cache
     
-        
+        
         You joined this room.
         
     
@@ -58,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.
         
     
@@ -154,12 +154,12 @@
         
     
     
-        
+        
         Failed to upload media. Please try again.
         
     
     
-        
+        
         Cache migration failed!
         
     
@@ -174,7 +174,7 @@
         
     
     
-        
+        
         Failed to restore OLM account. Please login again.
         
     
@@ -190,12 +190,12 @@
     
     
         
-        
+        
         Please try to login again: %1
         
     
     
-        
+        
         Failed to join room: %1
         
     
@@ -304,7 +304,7 @@
 
     EmojiPicker
     
-        
+        
         
         Search
         
@@ -389,13 +389,13 @@
 
     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.
@@ -637,6 +637,14 @@ Example: https://server.my:8787
         
     
 
+
+    MessageInput
+    
+        
+        Write a message...
+        Γράψε ένα μήνυμα...
+    
+
 
     NewVerificationRequest
     
@@ -688,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
     
@@ -764,10 +790,18 @@ Example: https://server.my:8787
         Λανθασμένο όνομα διακομιστή
     
 
+
+    ReplyPopup
+    
+        
+        Close
+        
+    
+
 
     RoomInfo
     
-        
+        
         no version stored
         
     
@@ -827,7 +861,7 @@ Example: https://server.my:8787
         
     
     
-        
+        
         Accept
         Αποδοχή
     
@@ -909,7 +943,7 @@ Example: https://server.my:8787
 
     TextInputWidget
     
-        
+        
         Send a file
         
     
@@ -963,15 +997,14 @@ Example: https://server.my:8787
         
     
     
-        
-        
-        
+        
+        
         
         Failed to encrypt event, sending aborted!
         
     
     
-        
+        
         Save image
         Αποθήκευση Εικόνας
     
@@ -1110,12 +1143,12 @@ Example: https://server.my:8787
         
     
     
-        
+        
         You joined this room.
         
     
     
-        
+        
         Rejected the knock from %1.
         
     
@@ -1157,7 +1190,7 @@ Example: https://server.my:8787
 
     TimelineView
     
-        
+        
         React
         
     
@@ -1201,8 +1234,19 @@ Example: https://server.my:8787
         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
         
     
@@ -1237,19 +1281,6 @@ Example: https://server.my:8787
         Settings
         Ρυθμίσεις
     
-    
-        
-        Close
-        
-    
-
-
-    TimelineViewManager
-    
-        
-        No share room with this user found. Create an encrypted room with this user and try again.
-        
-    
 
 
     TrayIcon
@@ -1333,7 +1364,7 @@ Example: https://server.my:8787
 
     UserSettingsPage
     
-        
+        
         Minimize to tray
         Ελαχιστοποίηση
     
@@ -1353,7 +1384,12 @@ Example: https://server.my:8787
         
     
     
-        
+        
+        profile: %1
+        
+    
+    
+        
         CALLS
         
     
@@ -1496,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
         
     
@@ -1541,7 +1587,7 @@ This usually causes the application icon in the task bar to animate in some fash
         
     
     
-        
+        
         Session Keys
         
     
@@ -1561,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
         
     
diff --git a/resources/langs/nheko_en.ts b/resources/langs/nheko_en.ts
index cd2a42a5..8389a960 100644
--- a/resources/langs/nheko_en.ts
+++ b/resources/langs/nheko_en.ts
@@ -50,7 +50,7 @@
 
     Cache
     
-        
+        
         You joined this room.
         You joined this room.
     
@@ -58,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.
     
@@ -154,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!
     
@@ -174,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.
     
@@ -190,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
     
@@ -304,7 +304,7 @@
 
     EmojiPicker
     
-        
+        
         
         Search
         Search
@@ -389,13 +389,13 @@
 
     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.
@@ -641,6 +641,14 @@ Example: https://server.my:8787
         %1 ended the call.
     
 
+
+    MessageInput
+    
+        
+        Write a message...
+        Write a message…
+    
+
 
     NewVerificationRequest
     
@@ -692,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.
+        
+    
+    
+        
+        profile
+        
+    
+    
+        
+        profile name
+        
+    
+
 
     QuickSwitcher
     
@@ -768,10 +794,18 @@ Example: https://server.my:8787
         Invalid server name
     
 
+
+    ReplyPopup
+    
+        
+        Close
+        Close
+    
+
 
     RoomInfo
     
-        
+        
         no version stored
         no version stored
     
@@ -831,7 +865,7 @@ Example: https://server.my:8787
         Tag:
     
     
-        
+        
         Accept
         Accept
     
@@ -913,7 +947,7 @@ Example: https://server.my:8787
 
     TextInputWidget
     
-        
+        
         Send a file
         Send a file
     
@@ -967,15 +1001,14 @@ Example: https://server.my:8787
         Message redaction failed: %1
     
     
-        
-        
-        
+        
+        
         
         Failed to encrypt event, sending aborted!
         Failed to encrypt event, sending aborted!
     
     
-        
+        
         Save image
         Save image
     
@@ -1114,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.
     
@@ -1161,7 +1194,7 @@ Example: https://server.my:8787
 
     TimelineView
     
-        
+        
         React
         React
     
@@ -1205,54 +1238,52 @@ Example: https://server.my:8787
         No room open
         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
-        Back to room list
+        Back to room list
     
     
         
         
         No room selected
-        No room selected
+        No room selected
     
     
         
         Room options
-        Room options
+        Room options
     
     
         
         Invite users
-        Invite users
+        Invite users
     
     
         
         Members
-        Members
+        Members
     
     
         
         Leave room
-        Leave room
+        Leave room
     
     
         
         Settings
-        Settings
-    
-    
-        
-        Close
-        Close
-    
-
-
-    TimelineViewManager
-    
-        
-        No share room with this user found. Create an encrypted room with this user and try again.
-        
+        Settings
     
 
 
@@ -1337,7 +1368,7 @@ Example: https://server.my:8787
 
     UserSettingsPage
     
-        
+        
         Minimize to tray
         Minimize to tray
     
@@ -1357,7 +1388,12 @@ Example: https://server.my:8787
         Circular Avatars
     
     
-        
+        
+        profile: %1
+        
+    
+    
+        
         CALLS
         CALLS
     
@@ -1508,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
+        
+    
+    
+        
+        Will prevent text selection in the timeline to make scrolling easier.
+        
+    
+    
+        
         Scale factor
         Scale factor
     
@@ -1553,7 +1599,7 @@ This usually causes the application icon in the task bar to animate in some fash
         Device Fingerprint
     
     
-        
+        
         Session Keys
         Session Keys
     
@@ -1573,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
+        
+    
+    
+        
+        Automatically replies to key requests from other users, if they are verified.
+        
+    
+    
+        
         Open Sessions File
         Open Sessions File
     
diff --git a/resources/langs/nheko_eo.ts b/resources/langs/nheko_eo.ts
index 0aef02e8..4e5a9bc4 100644
--- a/resources/langs/nheko_eo.ts
+++ b/resources/langs/nheko_eo.ts
@@ -50,7 +50,7 @@
 
     Cache
     
-        
+        
         You joined this room.
         Vi aliĝis ĉi tiun ĉambron.
     
@@ -58,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.
@@ -155,12 +155,12 @@
         
     
     
-        
+        
         Failed to upload media. Please try again.
         
     
     
-        
+        
         Cache migration failed!
         
     
@@ -175,7 +175,7 @@
         
     
     
-        
+        
         Failed to restore OLM account. Please login again.
         
     
@@ -191,12 +191,12 @@
     
     
         
-        
+        
         Please try to login again: %1
         
     
     
-        
+        
         Failed to join room: %1
         
     
@@ -305,7 +305,7 @@
 
     EmojiPicker
     
-        
+        
         
         Search
         Trovu
@@ -390,13 +390,13 @@
 
     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.
@@ -642,6 +642,14 @@ Ekzemplo: https://servisto.mia:8787
         
     
 
+
+    MessageInput
+    
+        
+        Write a message...
+        Skribu mesaĝon...
+    
+
 
     NewVerificationRequest
     
@@ -693,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
     
@@ -769,10 +795,18 @@ Ekzemplo: https://servisto.mia:8787
         
     
 
+
+    ReplyPopup
+    
+        
+        Close
+        
+    
+
 
     RoomInfo
     
-        
+        
         no version stored
         
     
@@ -833,7 +867,7 @@ Ekzemplo: https://servisto.mia:8787
         
     
     
-        
+        
         Accept
         
     
@@ -917,7 +951,7 @@ Ekzemplo: https://servisto.mia:8787
 
     TextInputWidget
     
-        
+        
         Send a file
         Sendu dosieron
     
@@ -971,15 +1005,14 @@ Ekzemplo: https://servisto.mia:8787
         
     
     
-        
-        
-        
+        
+        
         
         Failed to encrypt event, sending aborted!
         
     
     
-        
+        
         Save image
         
     
@@ -1120,12 +1153,12 @@ Ekzemplo: https://servisto.mia:8787
         
     
     
-        
+        
         You joined this room.
         Vi aliĝis ĉi tiun ĉambron.
     
     
-        
+        
         Rejected the knock from %1.
         
     
@@ -1167,7 +1200,7 @@ Ekzemplo: https://servisto.mia:8787
 
     TimelineView
     
-        
+        
         React
         Reagu
     
@@ -1211,8 +1244,19 @@ Ekzemplo: https://servisto.mia:8787
         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
         
     
@@ -1235,31 +1279,18 @@ Ekzemplo: https://servisto.mia:8787
     
         
         Members
-        Membroj
+        Membroj
     
     
         
         Leave room
-        Forlasu la ĉambron
+        
     
     
         
         Settings
         Agordoj
     
-    
-        
-        Close
-        
-    
-
-
-    TimelineViewManager
-    
-        
-        No share room with this user found. Create an encrypted room with this user and try again.
-        
-    
 
 
     TrayIcon
@@ -1343,7 +1374,7 @@ Ekzemplo: https://servisto.mia:8787
 
     UserSettingsPage
     
-        
+        
         Minimize to tray
         
     
@@ -1363,7 +1394,12 @@ Ekzemplo: https://servisto.mia:8787
         
     
     
-        
+        
+        profile: %1
+        
+    
+    
+        
         CALLS
         
     
@@ -1506,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
         
     
@@ -1551,7 +1597,7 @@ This usually causes the application icon in the task bar to animate in some fash
         
     
     
-        
+        
         Session Keys
         
     
@@ -1571,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
         
     
diff --git a/resources/langs/nheko_et.ts b/resources/langs/nheko_et.ts
index 4804f797..b1f20985 100644
--- a/resources/langs/nheko_et.ts
+++ b/resources/langs/nheko_et.ts
@@ -50,7 +50,7 @@
 
     Cache
     
-        
+        
         You joined this room.
         Sa liitusid selle jututoaga.
     
@@ -58,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.
     
@@ -154,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!
     
@@ -174,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.
     
@@ -190,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
     
@@ -304,7 +304,7 @@
 
     EmojiPicker
     
-        
+        
         
         Search
         Otsi
@@ -389,13 +389,13 @@
 
     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.
@@ -641,6 +641,14 @@ Näiteks: https://server.minu:8787
         %1 lõpetas kõne.
     
 
+
+    MessageInput
+    
+        
+        Write a message...
+        Kirjuta sõnum…
+    
+
 
     NewVerificationRequest
     
@@ -692,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
     
@@ -768,10 +794,18 @@ Näiteks: https://server.minu:8787
         Vigane koduserveri nimi
     
 
+
+    ReplyPopup
+    
+        
+        Close
+        Sulge
+    
+
 
     RoomInfo
     
-        
+        
         no version stored
         salvestatud versiooni ei leidu
     
@@ -831,7 +865,7 @@ Näiteks: https://server.minu:8787
         Silt:
     
     
-        
+        
         Accept
         Nõustu
     
@@ -913,7 +947,7 @@ Näiteks: https://server.minu:8787
 
     TextInputWidget
     
-        
+        
         Send a file
         Saada fail
     
@@ -967,15 +1001,14 @@ Näiteks: https://server.minu:8787
         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
     
@@ -1114,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.
     
@@ -1161,7 +1194,7 @@ Näiteks: https://server.minu:8787
 
     TimelineView
     
-        
+        
         React
         Reageeri
     
@@ -1205,54 +1238,52 @@ Näiteks: https://server.minu:8787
         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
-    
-
-
-    TimelineViewManager
-    
-        
-        No share room with this user found. Create an encrypted room with this user and try again.
-        
+        Seadistused
     
 
 
@@ -1337,7 +1368,7 @@ Näiteks: https://server.minu:8787
 
     UserSettingsPage
     
-        
+        
         Minimize to tray
         Vähenda tegumiribale
     
@@ -1357,7 +1388,12 @@ Näiteks: https://server.minu:8787
         Ümmargused tunnuspildid
     
     
-        
+        
+        profile: %1
+        
+    
+    
+        
         CALLS
         KÕNED
     
@@ -1508,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
     
@@ -1553,7 +1599,7 @@ See tavaliselt tähendab, et rakenduse ikoon tegumiribal annab mingit sorti anim
         Seadme sõrmejälg
     
     
-        
+        
         Session Keys
         Sessioonivõtmed
     
@@ -1573,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
     
diff --git a/resources/langs/nheko_fi.ts b/resources/langs/nheko_fi.ts
index a8171557..bc44634c 100644
--- a/resources/langs/nheko_fi.ts
+++ b/resources/langs/nheko_fi.ts
@@ -50,7 +50,7 @@
 
     Cache
     
-        
+        
         You joined this room.
         
     
@@ -58,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.
         
     
@@ -154,12 +154,12 @@
         
     
     
-        
+        
         Failed to upload media. Please try again.
         
     
     
-        
+        
         Cache migration failed!
         
     
@@ -174,7 +174,7 @@
         
     
     
-        
+        
         Failed to restore OLM account. Please login again.
         OLM-tilin palauttaminen epäonnistui. Ole hyvä ja kirjaudu sisään uudelleen.
     
@@ -190,12 +190,12 @@
     
     
         
-        
+        
         Please try to login again: %1
         Ole hyvä ja yritä kirjautua sisään uudelleen: %1
     
     
-        
+        
         Failed to join room: %1
         
     
@@ -304,7 +304,7 @@
 
     EmojiPicker
     
-        
+        
         
         Search
         
@@ -389,13 +389,13 @@
 
     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.
@@ -637,6 +637,14 @@ Example: https://server.my:8787
         
     
 
+
+    MessageInput
+    
+        
+        Write a message...
+        Kirjoita viesti…
+    
+
 
     NewVerificationRequest
     
@@ -688,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
     
@@ -764,10 +790,18 @@ Example: https://server.my:8787
         Epäkelpo palvelimen nimi
     
 
+
+    ReplyPopup
+    
+        
+        Close
+        Sulje
+    
+
 
     RoomInfo
     
-        
+        
         no version stored
         ei tallennettua versiota
     
@@ -827,7 +861,7 @@ Example: https://server.my:8787
         
     
     
-        
+        
         Accept
         Hyväksy
     
@@ -909,7 +943,7 @@ Example: https://server.my:8787
 
     TextInputWidget
     
-        
+        
         Send a file
         Lähetä tiedosto
     
@@ -963,15 +997,14 @@ Example: https://server.my:8787
         Viestin poisto epäonnistui: %1
     
     
-        
-        
-        
+        
+        
         
         Failed to encrypt event, sending aborted!
         
     
     
-        
+        
         Save image
         Tallenna kuva
     
@@ -1110,12 +1143,12 @@ Example: https://server.my:8787
         
     
     
-        
+        
         You joined this room.
         
     
     
-        
+        
         Rejected the knock from %1.
         
     
@@ -1157,7 +1190,7 @@ Example: https://server.my:8787
 
     TimelineView
     
-        
+        
         React
         
     
@@ -1201,8 +1234,19 @@ Example: https://server.my:8787
         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
         
     
@@ -1237,19 +1281,6 @@ Example: https://server.my:8787
         Settings
         Asetukset
     
-    
-        
-        Close
-        Sulje
-    
-
-
-    TimelineViewManager
-    
-        
-        No share room with this user found. Create an encrypted room with this user and try again.
-        
-    
 
 
     TrayIcon
@@ -1333,7 +1364,7 @@ Example: https://server.my:8787
 
     UserSettingsPage
     
-        
+        
         Minimize to tray
         Pienennä ilmoitusalueelle
     
@@ -1353,7 +1384,12 @@ Example: https://server.my:8787
         
     
     
-        
+        
+        profile: %1
+        
+    
+    
+        
         CALLS
         
     
@@ -1496,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
     
@@ -1541,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
     
@@ -1561,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
     
diff --git a/resources/langs/nheko_fr.ts b/resources/langs/nheko_fr.ts
index 2958f056..f8f8380b 100644
--- a/resources/langs/nheko_fr.ts
+++ b/resources/langs/nheko_fr.ts
@@ -50,7 +50,7 @@
 
     Cache
     
-        
+        
         You joined this room.
         Vous avez rejoint ce salon.
     
@@ -58,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éé.
     
@@ -154,12 +154,12 @@
         %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 !
     
@@ -174,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.
     
@@ -190,12 +190,12 @@
     
     
         
-        
+        
         Please try to login again: %1
         Veuillez vous reconnecter : %1
     
     
-        
+        
         Failed to join room: %1
         Impossible de rejoindre le salon : %1
     
@@ -304,7 +304,7 @@
 
     EmojiPicker
     
-        
+        
         
         Search
         Chercher
@@ -389,13 +389,13 @@
 
     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.
@@ -641,6 +641,14 @@ Exemple : https ://monserveur.example.com :8787
         Négociation de l'appel…
     
 
+
+    MessageInput
+    
+        
+        Write a message...
+        Écrivez un message…
+    
+
 
     NewVerificationRequest
     
@@ -692,6 +700,24 @@ Exemple : https ://monserveur.example.com :8787
         É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
+        
+    
+
 
     QuickSwitcher
     
@@ -768,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
     
@@ -831,7 +865,7 @@ Exemple : https ://monserveur.example.com :8787
         Étiquette :
     
     
-        
+        
         Accept
         Accepter
     
@@ -913,7 +947,7 @@ Exemple : https ://monserveur.example.com :8787
 
     TextInputWidget
     
-        
+        
         Send a file
         Envoyer un fichier
     
@@ -967,15 +1001,14 @@ Exemple : https ://monserveur.example.com :8787
         Échec de la suppression du message : %1
     
     
-        
-        
-        
+        
+        
         
         Failed to encrypt event, sending aborted!
         Échec du chiffrement de l'évènement, envoi abandonné !
     
     
-        
+        
         Save image
         Enregistrer l'image
     
@@ -1114,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.
     
@@ -1161,7 +1194,7 @@ Exemple : https ://monserveur.example.com :8787
 
     TimelineView
     
-        
+        
         React
         Réagir
     
@@ -1205,54 +1238,52 @@ Exemple : https ://monserveur.example.com :8787
         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
-    
-
-
-    TimelineViewManager
-    
-        
-        No share room with this user found. Create an encrypted room with this user and try again.
-        
+        Paramètres
     
 
 
@@ -1337,7 +1368,7 @@ Exemple : https ://monserveur.example.com :8787
 
     UserSettingsPage
     
-        
+        
         Minimize to tray
         Réduire à la barre des tâches
     
@@ -1357,7 +1388,12 @@ Exemple : https ://monserveur.example.com :8787
         Avatars circulaires
     
     
-        
+        
+        profile: %1
+        
+    
+    
+        
         CALLS
         APPELS
     
@@ -1509,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
     
@@ -1554,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
     
@@ -1574,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
     
diff --git a/resources/langs/nheko_it.ts b/resources/langs/nheko_it.ts
index 1c71b43e..b8efa774 100644
--- a/resources/langs/nheko_it.ts
+++ b/resources/langs/nheko_it.ts
@@ -50,7 +50,7 @@
 
     Cache
     
-        
+        
         You joined this room.
         Sei entrato in questa stanza.
     
@@ -58,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.
     
@@ -154,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!
     
@@ -174,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.
     
@@ -190,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
     
@@ -304,7 +304,7 @@
 
     EmojiPicker
     
-        
+        
         
         Search
         
@@ -389,13 +389,13 @@
 
     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.
@@ -641,6 +641,14 @@ Esempio: https://server.mio:8787
         
     
 
+
+    MessageInput
+    
+        
+        Write a message...
+        Scrivi un messaggio…
+    
+
 
     NewVerificationRequest
     
@@ -692,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
     
@@ -768,10 +794,18 @@ Esempio: https://server.mio:8787
         Nome del server non valido
     
 
+
+    ReplyPopup
+    
+        
+        Close
+        Chiudi
+    
+
 
     RoomInfo
     
-        
+        
         no version stored
         nessuna versione memorizzata
     
@@ -831,7 +865,7 @@ Esempio: https://server.mio:8787
         
     
     
-        
+        
         Accept
         Accetta
     
@@ -913,7 +947,7 @@ Esempio: https://server.mio:8787
 
     TextInputWidget
     
-        
+        
         Send a file
         Invia un file
     
@@ -967,15 +1001,14 @@ Esempio: https://server.mio:8787
         Oscuramento del messaggio fallito: %1
     
     
-        
-        
-        
+        
+        
         
         Failed to encrypt event, sending aborted!
         
     
     
-        
+        
         Save image
         Salva immagine
     
@@ -1114,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.
     
@@ -1161,7 +1194,7 @@ Esempio: https://server.mio:8787
 
     TimelineView
     
-        
+        
         React
         
     
@@ -1205,8 +1238,19 @@ Esempio: https://server.mio:8787
         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
         
     
@@ -1241,19 +1285,6 @@ Esempio: https://server.mio:8787
         Settings
         Impostazioni
     
-    
-        
-        Close
-        Chiudi
-    
-
-
-    TimelineViewManager
-    
-        
-        No share room with this user found. Create an encrypted room with this user and try again.
-        
-    
 
 
     TrayIcon
@@ -1337,7 +1368,7 @@ Esempio: https://server.mio:8787
 
     UserSettingsPage
     
-        
+        
         Minimize to tray
         Minimizza nella tray
     
@@ -1357,7 +1388,12 @@ Esempio: https://server.mio:8787
         Avatar Circolari
     
     
-        
+        
+        profile: %1
+        
+    
+    
+        
         CALLS
         
     
@@ -1500,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
     
@@ -1545,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
     
@@ -1565,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
     
diff --git a/resources/langs/nheko_ja.ts b/resources/langs/nheko_ja.ts
index 4c53bab0..26d6188c 100644
--- a/resources/langs/nheko_ja.ts
+++ b/resources/langs/nheko_ja.ts
@@ -50,7 +50,7 @@
 
     Cache
     
-        
+        
         You joined this room.
         
     
@@ -58,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.
         
     
@@ -154,12 +154,12 @@
         永久追放を解除されたユーザー: %1
     
     
-        
+        
         Failed to upload media. Please try again.
         メディアをアップロードできませんでした。やり直して下さい。
     
     
-        
+        
         Cache migration failed!
         
     
@@ -174,7 +174,7 @@
         
     
     
-        
+        
         Failed to restore OLM account. Please login again.
         OLMアカウントを復元できませんでした。もう一度ログインして下さい。
     
@@ -190,12 +190,12 @@
     
     
         
-        
+        
         Please try to login again: %1
         もう一度ログインしてみて下さい: %1
     
     
-        
+        
         Failed to join room: %1
         部屋に参加できませんでした: %1
     
@@ -304,7 +304,7 @@
 
     EmojiPicker
     
-        
+        
         
         Search
         
@@ -389,13 +389,13 @@
 
     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.
@@ -637,6 +637,14 @@ Example: https://server.my:8787
         
     
 
+
+    MessageInput
+    
+        
+        Write a message...
+        メッセージを書く...
+    
+
 
     NewVerificationRequest
     
@@ -688,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
     
@@ -764,10 +790,18 @@ Example: https://server.my:8787
         無効なサーバー名です
     
 
+
+    ReplyPopup
+    
+        
+        Close
+        閉じる
+    
+
 
     RoomInfo
     
-        
+        
         no version stored
         バージョンが保存されていません
     
@@ -827,7 +861,7 @@ Example: https://server.my:8787
         
     
     
-        
+        
         Accept
         容認
     
@@ -909,7 +943,7 @@ Example: https://server.my:8787
 
     TextInputWidget
     
-        
+        
         Send a file
         ファイルを送信
     
@@ -963,15 +997,14 @@ Example: https://server.my:8787
         メッセージを編集できませんでした: %1
     
     
-        
-        
-        
+        
+        
         
         Failed to encrypt event, sending aborted!
         
     
     
-        
+        
         Save image
         画像を保存
     
@@ -1109,12 +1142,12 @@ Example: https://server.my:8787
         %1がノックを編集しました。
     
     
-        
+        
         You joined this room.
         
     
     
-        
+        
         Rejected the knock from %1.
         %1からのノックを拒否しました。
     
@@ -1156,7 +1189,7 @@ Example: https://server.my:8787
 
     TimelineView
     
-        
+        
         React
         
     
@@ -1200,8 +1233,19 @@ Example: https://server.my:8787
         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
         
     
@@ -1236,19 +1280,6 @@ Example: https://server.my:8787
         Settings
         設定
     
-    
-        
-        Close
-        閉じる
-    
-
-
-    TimelineViewManager
-    
-        
-        No share room with this user found. Create an encrypted room with this user and try again.
-        
-    
 
 
     TrayIcon
@@ -1332,7 +1363,7 @@ Example: https://server.my:8787
 
     UserSettingsPage
     
-        
+        
         Minimize to tray
         トレイへ最小化
     
@@ -1352,7 +1383,12 @@ Example: https://server.my:8787
         円形アバター
     
     
-        
+        
+        profile: %1
+        
+    
+    
+        
         CALLS
         
     
@@ -1495,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
         尺度係数
     
@@ -1540,7 +1586,7 @@ This usually causes the application icon in the task bar to animate in some fash
         デバイスの指紋
     
     
-        
+        
         Session Keys
         セッション鍵
     
@@ -1560,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
         セッションファイルを開く
     
diff --git a/resources/langs/nheko_nl.ts b/resources/langs/nheko_nl.ts
index 5cdbbe9e..d6d91c3e 100644
--- a/resources/langs/nheko_nl.ts
+++ b/resources/langs/nheko_nl.ts
@@ -50,7 +50,7 @@
 
     Cache
     
-        
+        
         You joined this room.
         
     
@@ -58,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.
         
     
@@ -154,12 +154,12 @@
         
     
     
-        
+        
         Failed to upload media. Please try again.
         
     
     
-        
+        
         Cache migration failed!
         
     
@@ -174,7 +174,7 @@
         
     
     
-        
+        
         Failed to restore OLM account. Please login again.
         
     
@@ -190,12 +190,12 @@
     
     
         
-        
+        
         Please try to login again: %1
         
     
     
-        
+        
         Failed to join room: %1
         
     
@@ -304,7 +304,7 @@
 
     EmojiPicker
     
-        
+        
         
         Search
         
@@ -389,13 +389,13 @@
 
     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.
@@ -637,6 +637,14 @@ Example: https://server.my:8787
         
     
 
+
+    MessageInput
+    
+        
+        Write a message...
+        Typ een bericht...
+    
+
 
     NewVerificationRequest
     
@@ -688,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
     
@@ -764,10 +790,18 @@ Example: https://server.my:8787
         Ongeldige servernaam
     
 
+
+    ReplyPopup
+    
+        
+        Close
+        
+    
+
 
     RoomInfo
     
-        
+        
         no version stored
         
     
@@ -827,7 +861,7 @@ Example: https://server.my:8787
         
     
     
-        
+        
         Accept
         Accepteren
     
@@ -909,7 +943,7 @@ Example: https://server.my:8787
 
     TextInputWidget
     
-        
+        
         Send a file
         
     
@@ -963,15 +997,14 @@ Example: https://server.my:8787
         
     
     
-        
-        
-        
+        
+        
         
         Failed to encrypt event, sending aborted!
         
     
     
-        
+        
         Save image
         Afbeelding opslaan
     
@@ -1110,12 +1143,12 @@ Example: https://server.my:8787
         
     
     
-        
+        
         You joined this room.
         
     
     
-        
+        
         Rejected the knock from %1.
         
     
@@ -1157,7 +1190,7 @@ Example: https://server.my:8787
 
     TimelineView
     
-        
+        
         React
         
     
@@ -1201,8 +1234,19 @@ Example: https://server.my:8787
         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
         
     
@@ -1237,19 +1281,6 @@ Example: https://server.my:8787
         Settings
         Instellingen
     
-    
-        
-        Close
-        
-    
-
-
-    TimelineViewManager
-    
-        
-        No share room with this user found. Create an encrypted room with this user and try again.
-        
-    
 
 
     TrayIcon
@@ -1333,7 +1364,7 @@ Example: https://server.my:8787
 
     UserSettingsPage
     
-        
+        
         Minimize to tray
         Minimaliseren naar systeemvak
     
@@ -1353,7 +1384,12 @@ Example: https://server.my:8787
         
     
     
-        
+        
+        profile: %1
+        
+    
+    
+        
         CALLS
         
     
@@ -1496,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
         
     
@@ -1541,7 +1587,7 @@ This usually causes the application icon in the task bar to animate in some fash
         
     
     
-        
+        
         Session Keys
         
     
@@ -1561,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
         
     
diff --git a/resources/langs/nheko_pl.ts b/resources/langs/nheko_pl.ts
index 4e3c1b0b..2479268e 100644
--- a/resources/langs/nheko_pl.ts
+++ b/resources/langs/nheko_pl.ts
@@ -50,7 +50,7 @@
 
     Cache
     
-        
+        
         You joined this room.
         
     
@@ -58,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.
         
     
@@ -154,12 +154,12 @@
         
     
     
-        
+        
         Failed to upload media. Please try again.
         
     
     
-        
+        
         Cache migration failed!
         
     
@@ -174,7 +174,7 @@
         
     
     
-        
+        
         Failed to restore OLM account. Please login again.
         Nie udało się przywrócić konta OLM. Spróbuj zalogować się ponownie.
     
@@ -190,12 +190,12 @@
     
     
         
-        
+        
         Please try to login again: %1
         Spróbuj zalogować się ponownie: %1
     
     
-        
+        
         Failed to join room: %1
         
     
@@ -304,7 +304,7 @@
 
     EmojiPicker
     
-        
+        
         
         Search
         
@@ -389,13 +389,13 @@
 
     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.
@@ -637,6 +637,14 @@ Example: https://server.my:8787
         
     
 
+
+    MessageInput
+    
+        
+        Write a message...
+        Napisz wiadomość…
+    
+
 
     NewVerificationRequest
     
@@ -688,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
     
@@ -764,10 +790,18 @@ Example: https://server.my:8787
         Nieprawidłowa nazwa serwera
     
 
+
+    ReplyPopup
+    
+        
+        Close
+        
+    
+
 
     RoomInfo
     
-        
+        
         no version stored
         
     
@@ -827,7 +861,7 @@ Example: https://server.my:8787
         
     
     
-        
+        
         Accept
         Akceptuj
     
@@ -909,7 +943,7 @@ Example: https://server.my:8787
 
     TextInputWidget
     
-        
+        
         Send a file
         Wyślij plik
     
@@ -963,15 +997,14 @@ Example: https://server.my:8787
         Redagowanie wiadomości nie powiodło się: %1
     
     
-        
-        
-        
+        
+        
         
         Failed to encrypt event, sending aborted!
         
     
     
-        
+        
         Save image
         Zapisz obraz
     
@@ -1111,12 +1144,12 @@ Example: https://server.my:8787
         
     
     
-        
+        
         You joined this room.
         
     
     
-        
+        
         Rejected the knock from %1.
         
     
@@ -1158,7 +1191,7 @@ Example: https://server.my:8787
 
     TimelineView
     
-        
+        
         React
         
     
@@ -1202,8 +1235,19 @@ Example: https://server.my:8787
         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
         
     
@@ -1238,19 +1282,6 @@ Example: https://server.my:8787
         Settings
         Ustawienia
     
-    
-        
-        Close
-        
-    
-
-
-    TimelineViewManager
-    
-        
-        No share room with this user found. Create an encrypted room with this user and try again.
-        
-    
 
 
     TrayIcon
@@ -1334,7 +1365,7 @@ Example: https://server.my:8787
 
     UserSettingsPage
     
-        
+        
         Minimize to tray
         Zminimalizuj do paska zadań
     
@@ -1354,7 +1385,12 @@ Example: https://server.my:8787
         
     
     
-        
+        
+        profile: %1
+        
+    
+    
+        
         CALLS
         
     
@@ -1497,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
         
     
@@ -1542,7 +1588,7 @@ This usually causes the application icon in the task bar to animate in some fash
         Odcisk palca urządzenia
     
     
-        
+        
         Session Keys
         
     
@@ -1562,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
         
     
diff --git a/resources/langs/nheko_pt_PT.ts b/resources/langs/nheko_pt_PT.ts
index a4a337b1..70921b70 100644
--- a/resources/langs/nheko_pt_PT.ts
+++ b/resources/langs/nheko_pt_PT.ts
@@ -50,7 +50,7 @@
 
     Cache
     
-        
+        
         You joined this room.
         
     
@@ -58,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.
         
     
@@ -154,12 +154,12 @@
         
     
     
-        
+        
         Failed to upload media. Please try again.
         
     
     
-        
+        
         Cache migration failed!
         
     
@@ -174,7 +174,7 @@
         
     
     
-        
+        
         Failed to restore OLM account. Please login again.
         
     
@@ -190,12 +190,12 @@
     
     
         
-        
+        
         Please try to login again: %1
         
     
     
-        
+        
         Failed to join room: %1
         
     
@@ -304,7 +304,7 @@
 
     EmojiPicker
     
-        
+        
         
         Search
         
@@ -389,13 +389,13 @@
 
     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.
@@ -637,6 +637,14 @@ Example: https://server.my:8787
         
     
 
+
+    MessageInput
+    
+        
+        Write a message...
+        
+    
+
 
     NewVerificationRequest
     
@@ -688,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
     
@@ -764,10 +790,18 @@ Example: https://server.my:8787
         
     
 
+
+    ReplyPopup
+    
+        
+        Close
+        
+    
+
 
     RoomInfo
     
-        
+        
         no version stored
         
     
@@ -827,7 +861,7 @@ Example: https://server.my:8787
         
     
     
-        
+        
         Accept
         
     
@@ -909,7 +943,7 @@ Example: https://server.my:8787
 
     TextInputWidget
     
-        
+        
         Send a file
         
     
@@ -963,15 +997,14 @@ Example: https://server.my:8787
         
     
     
-        
-        
-        
+        
+        
         
         Failed to encrypt event, sending aborted!
         
     
     
-        
+        
         Save image
         
     
@@ -1110,12 +1143,12 @@ Example: https://server.my:8787
         
     
     
-        
+        
         You joined this room.
         
     
     
-        
+        
         Rejected the knock from %1.
         
     
@@ -1157,7 +1190,7 @@ Example: https://server.my:8787
 
     TimelineView
     
-        
+        
         React
         
     
@@ -1201,8 +1234,19 @@ Example: https://server.my:8787
         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
         
     
@@ -1237,19 +1281,6 @@ Example: https://server.my:8787
         Settings
         
     
-    
-        
-        Close
-        
-    
-
-
-    TimelineViewManager
-    
-        
-        No share room with this user found. Create an encrypted room with this user and try again.
-        
-    
 
 
     TrayIcon
@@ -1333,7 +1364,7 @@ Example: https://server.my:8787
 
     UserSettingsPage
     
-        
+        
         Minimize to tray
         
     
@@ -1353,7 +1384,12 @@ Example: https://server.my:8787
         
     
     
-        
+        
+        profile: %1
+        
+    
+    
+        
         CALLS
         
     
@@ -1496,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
         
     
@@ -1541,7 +1587,7 @@ This usually causes the application icon in the task bar to animate in some fash
         
     
     
-        
+        
         Session Keys
         
     
@@ -1561,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
         
     
diff --git a/resources/langs/nheko_ro.ts b/resources/langs/nheko_ro.ts
index 30476860..3ee30c1f 100644
--- a/resources/langs/nheko_ro.ts
+++ b/resources/langs/nheko_ro.ts
@@ -50,7 +50,7 @@
 
     Cache
     
-        
+        
         You joined this room.
         
     
@@ -58,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.
         
     
@@ -154,12 +154,12 @@
         
     
     
-        
+        
         Failed to upload media. Please try again.
         
     
     
-        
+        
         Cache migration failed!
         
     
@@ -174,7 +174,7 @@
         
     
     
-        
+        
         Failed to restore OLM account. Please login again.
         
     
@@ -190,12 +190,12 @@
     
     
         
-        
+        
         Please try to login again: %1
         
     
     
-        
+        
         Failed to join room: %1
         
     
@@ -304,7 +304,7 @@
 
     EmojiPicker
     
-        
+        
         
         Search
         
@@ -389,13 +389,13 @@
 
     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.
@@ -637,6 +637,14 @@ Example: https://server.my:8787
         
     
 
+
+    MessageInput
+    
+        
+        Write a message...
+        
+    
+
 
     NewVerificationRequest
     
@@ -688,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
     
@@ -764,10 +790,18 @@ Example: https://server.my:8787
         
     
 
+
+    ReplyPopup
+    
+        
+        Close
+        
+    
+
 
     RoomInfo
     
-        
+        
         no version stored
         
     
@@ -827,7 +861,7 @@ Example: https://server.my:8787
         
     
     
-        
+        
         Accept
         
     
@@ -909,7 +943,7 @@ Example: https://server.my:8787
 
     TextInputWidget
     
-        
+        
         Send a file
         
     
@@ -963,15 +997,14 @@ Example: https://server.my:8787
         
     
     
-        
-        
-        
+        
+        
         
         Failed to encrypt event, sending aborted!
         
     
     
-        
+        
         Save image
         
     
@@ -1111,12 +1144,12 @@ Example: https://server.my:8787
         
     
     
-        
+        
         You joined this room.
         
     
     
-        
+        
         Rejected the knock from %1.
         
     
@@ -1158,7 +1191,7 @@ Example: https://server.my:8787
 
     TimelineView
     
-        
+        
         React
         
     
@@ -1202,8 +1235,19 @@ Example: https://server.my:8787
         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
         
     
@@ -1238,19 +1282,6 @@ Example: https://server.my:8787
         Settings
         
     
-    
-        
-        Close
-        
-    
-
-
-    TimelineViewManager
-    
-        
-        No share room with this user found. Create an encrypted room with this user and try again.
-        
-    
 
 
     TrayIcon
@@ -1334,7 +1365,7 @@ Example: https://server.my:8787
 
     UserSettingsPage
     
-        
+        
         Minimize to tray
         
     
@@ -1354,7 +1385,12 @@ Example: https://server.my:8787
         
     
     
-        
+        
+        profile: %1
+        
+    
+    
+        
         CALLS
         
     
@@ -1497,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
         
     
@@ -1542,7 +1588,7 @@ This usually causes the application icon in the task bar to animate in some fash
         
     
     
-        
+        
         Session Keys
         
     
@@ -1562,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
         
     
diff --git a/resources/langs/nheko_ru.ts b/resources/langs/nheko_ru.ts
index 5625d591..bc349646 100644
--- a/resources/langs/nheko_ru.ts
+++ b/resources/langs/nheko_ru.ts
@@ -50,7 +50,7 @@
 
     Cache
     
-        
+        
         You joined this room.
         
     
@@ -58,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.
         
     
@@ -154,12 +154,12 @@
         
     
     
-        
+        
         Failed to upload media. Please try again.
         
     
     
-        
+        
         Cache migration failed!
         
     
@@ -174,7 +174,7 @@
         
     
     
-        
+        
         Failed to restore OLM account. Please login again.
         Не удалось восстановить учетную запись OLM. Пожалуйста, войдите снова.
     
@@ -190,12 +190,12 @@
     
     
         
-        
+        
         Please try to login again: %1
         Повторите попытку входа: %1
     
     
-        
+        
         Failed to join room: %1
         
     
@@ -304,7 +304,7 @@
 
     EmojiPicker
     
-        
+        
         
         Search
         
@@ -389,13 +389,13 @@
 
     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.
@@ -637,6 +637,14 @@ Example: https://server.my:8787
         
     
 
+
+    MessageInput
+    
+        
+        Write a message...
+        Написать сообщение...
+    
+
 
     NewVerificationRequest
     
@@ -688,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
     
@@ -764,10 +790,18 @@ Example: https://server.my:8787
         Неверное имя сервера
     
 
+
+    ReplyPopup
+    
+        
+        Close
+        Закрыть
+    
+
 
     RoomInfo
     
-        
+        
         no version stored
         
     
@@ -827,7 +861,7 @@ Example: https://server.my:8787
         
     
     
-        
+        
         Accept
         Принять
     
@@ -909,7 +943,7 @@ Example: https://server.my:8787
 
     TextInputWidget
     
-        
+        
         Send a file
         Отправить файл
     
@@ -963,15 +997,14 @@ Example: https://server.my:8787
         Ошибка редактирования сообщения: %1
     
     
-        
-        
-        
+        
+        
         
         Failed to encrypt event, sending aborted!
         
     
     
-        
+        
         Save image
         Сохранить изображение
     
@@ -1111,12 +1144,12 @@ Example: https://server.my:8787
         
     
     
-        
+        
         You joined this room.
         
     
     
-        
+        
         Rejected the knock from %1.
         
     
@@ -1158,7 +1191,7 @@ Example: https://server.my:8787
 
     TimelineView
     
-        
+        
         React
         
     
@@ -1202,8 +1235,19 @@ Example: https://server.my:8787
         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
         
     
@@ -1238,19 +1282,6 @@ Example: https://server.my:8787
         Settings
         Настройки
     
-    
-        
-        Close
-        Закрыть
-    
-
-
-    TimelineViewManager
-    
-        
-        No share room with this user found. Create an encrypted room with this user and try again.
-        
-    
 
 
     TrayIcon
@@ -1334,7 +1365,7 @@ Example: https://server.my:8787
 
     UserSettingsPage
     
-        
+        
         Minimize to tray
         Сворачивать в системную панель
     
@@ -1354,7 +1385,12 @@ Example: https://server.my:8787
         
     
     
-        
+        
+        profile: %1
+        
+    
+    
+        
         CALLS
         
     
@@ -1497,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
         Масштаб
     
@@ -1542,7 +1588,7 @@ This usually causes the application icon in the task bar to animate in some fash
         Отпечаток устройства
     
     
-        
+        
         Session Keys
         Ключи сеанса
     
@@ -1562,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
         Открыть файл сеансов
     
diff --git a/resources/langs/nheko_si.ts b/resources/langs/nheko_si.ts
index b53bec5d..c63e58e3 100644
--- a/resources/langs/nheko_si.ts
+++ b/resources/langs/nheko_si.ts
@@ -50,7 +50,7 @@
 
     Cache
     
-        
+        
         You joined this room.
         
     
@@ -58,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.
         
     
@@ -154,12 +154,12 @@
         
     
     
-        
+        
         Failed to upload media. Please try again.
         
     
     
-        
+        
         Cache migration failed!
         
     
@@ -174,7 +174,7 @@
         
     
     
-        
+        
         Failed to restore OLM account. Please login again.
         
     
@@ -190,12 +190,12 @@
     
     
         
-        
+        
         Please try to login again: %1
         
     
     
-        
+        
         Failed to join room: %1
         
     
@@ -304,7 +304,7 @@
 
     EmojiPicker
     
-        
+        
         
         Search
         
@@ -389,13 +389,13 @@
 
     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.
@@ -637,6 +637,14 @@ Example: https://server.my:8787
         
     
 
+
+    MessageInput
+    
+        
+        Write a message...
+        
+    
+
 
     NewVerificationRequest
     
@@ -688,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
     
@@ -764,10 +790,18 @@ Example: https://server.my:8787
         
     
 
+
+    ReplyPopup
+    
+        
+        Close
+        
+    
+
 
     RoomInfo
     
-        
+        
         no version stored
         
     
@@ -827,7 +861,7 @@ Example: https://server.my:8787
         
     
     
-        
+        
         Accept
         
     
@@ -909,7 +943,7 @@ Example: https://server.my:8787
 
     TextInputWidget
     
-        
+        
         Send a file
         
     
@@ -963,15 +997,14 @@ Example: https://server.my:8787
         
     
     
-        
-        
-        
+        
+        
         
         Failed to encrypt event, sending aborted!
         
     
     
-        
+        
         Save image
         
     
@@ -1110,12 +1143,12 @@ Example: https://server.my:8787
         
     
     
-        
+        
         You joined this room.
         
     
     
-        
+        
         Rejected the knock from %1.
         
     
@@ -1157,7 +1190,7 @@ Example: https://server.my:8787
 
     TimelineView
     
-        
+        
         React
         
     
@@ -1201,8 +1234,19 @@ Example: https://server.my:8787
         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
         
     
@@ -1237,19 +1281,6 @@ Example: https://server.my:8787
         Settings
         
     
-    
-        
-        Close
-        
-    
-
-
-    TimelineViewManager
-    
-        
-        No share room with this user found. Create an encrypted room with this user and try again.
-        
-    
 
 
     TrayIcon
@@ -1333,7 +1364,7 @@ Example: https://server.my:8787
 
     UserSettingsPage
     
-        
+        
         Minimize to tray
         
     
@@ -1353,7 +1384,12 @@ Example: https://server.my:8787
         
     
     
-        
+        
+        profile: %1
+        
+    
+    
+        
         CALLS
         
     
@@ -1496,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
         
     
@@ -1541,7 +1587,7 @@ This usually causes the application icon in the task bar to animate in some fash
         
     
     
-        
+        
         Session Keys
         
     
@@ -1561,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
         
     
diff --git a/resources/langs/nheko_zh_CN.ts b/resources/langs/nheko_zh_CN.ts
index ae7a871a..eee53d95 100644
--- a/resources/langs/nheko_zh_CN.ts
+++ b/resources/langs/nheko_zh_CN.ts
@@ -50,7 +50,7 @@
 
     Cache
     
-        
+        
         You joined this room.
         
     
@@ -58,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.
         
     
@@ -154,12 +154,12 @@
         
     
     
-        
+        
         Failed to upload media. Please try again.
         
     
     
-        
+        
         Cache migration failed!
         
     
@@ -174,7 +174,7 @@
         
     
     
-        
+        
         Failed to restore OLM account. Please login again.
         恢复 OLM 账户失败。请重新登录。
     
@@ -190,12 +190,12 @@
     
     
         
-        
+        
         Please try to login again: %1
         请尝试再次登录:%1
     
     
-        
+        
         Failed to join room: %1
         
     
@@ -304,7 +304,7 @@
 
     EmojiPicker
     
-        
+        
         
         Search
         
@@ -389,13 +389,13 @@
 
     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.
@@ -637,6 +637,14 @@ Example: https://server.my:8787
         
     
 
+
+    MessageInput
+    
+        
+        Write a message...
+        写一条消息...
+    
+
 
     NewVerificationRequest
     
@@ -688,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
     
@@ -764,10 +790,18 @@ Example: https://server.my:8787
         无效的服务器名
     
 
+
+    ReplyPopup
+    
+        
+        Close
+        
+    
+
 
     RoomInfo
     
-        
+        
         no version stored
         
     
@@ -827,7 +861,7 @@ Example: https://server.my:8787
         
     
     
-        
+        
         Accept
         接受
     
@@ -909,7 +943,7 @@ Example: https://server.my:8787
 
     TextInputWidget
     
-        
+        
         Send a file
         发送一个文件
     
@@ -963,15 +997,14 @@ Example: https://server.my:8787
         删除消息失败:%1
     
     
-        
-        
-        
+        
+        
         
         Failed to encrypt event, sending aborted!
         
     
     
-        
+        
         Save image
         保存图像
     
@@ -1109,12 +1142,12 @@ Example: https://server.my:8787
         
     
     
-        
+        
         You joined this room.
         
     
     
-        
+        
         Rejected the knock from %1.
         
     
@@ -1156,7 +1189,7 @@ Example: https://server.my:8787
 
     TimelineView
     
-        
+        
         React
         
     
@@ -1200,8 +1233,19 @@ Example: https://server.my:8787
         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
         
     
@@ -1236,19 +1280,6 @@ Example: https://server.my:8787
         Settings
         设置
     
-    
-        
-        Close
-        
-    
-
-
-    TimelineViewManager
-    
-        
-        No share room with this user found. Create an encrypted room with this user and try again.
-        
-    
 
 
     TrayIcon
@@ -1332,7 +1363,7 @@ Example: https://server.my:8787
 
     UserSettingsPage
     
-        
+        
         Minimize to tray
         最小化至托盘
     
@@ -1352,7 +1383,12 @@ Example: https://server.my:8787
         
     
     
-        
+        
+        profile: %1
+        
+    
+    
+        
         CALLS
         
     
@@ -1495,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
         
     
@@ -1540,7 +1586,7 @@ This usually causes the application icon in the task bar to animate in some fash
         设备指纹
     
     
-        
+        
         Session Keys
         会话密钥
     
@@ -1560,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
         打开会话文件
     

From 7f2d18c36dabbcae0d0637d489371c2b71869e67 Mon Sep 17 00:00:00 2001
From: Nicolas Werner 
Date: Mon, 26 Oct 2020 21:44:31 +0100
Subject: [PATCH 32/33] Fix small scope issue

---
 resources/qml/TimelineView.qml | 11 +++++------
 1 file changed, 5 insertions(+), 6 deletions(-)

diff --git a/resources/qml/TimelineView.qml b/resources/qml/TimelineView.qml
index 516c6860..de047e8a 100644
--- a/resources/qml/TimelineView.qml
+++ b/resources/qml/TimelineView.qml
@@ -57,7 +57,6 @@ Page {
         property string eventId
         property int eventType
         property bool isEncrypted
-        property var room: TimelineManager.timeline
 
         function show(eventId_, eventType_, isEncrypted_, showAt_, position) {
             eventId = eventId_;
@@ -78,12 +77,12 @@ Page {
 
         MenuItem {
             text: qsTr("Reply")
-            onClicked: room.replyAction(messageContextMenu.eventId)
+            onClicked: TimelineManager.timeline.replyAction(messageContextMenu.eventId)
         }
 
         MenuItem {
             text: qsTr("Read receipts")
-            onTriggered: room.readReceiptsAction(messageContextMenu.eventId)
+            onTriggered: TimelineManager.timeline.readReceiptsAction(messageContextMenu.eventId)
         }
 
         MenuItem {
@@ -92,19 +91,19 @@ Page {
 
         MenuItem {
             text: qsTr("View raw message")
-            onTriggered: room.viewRawMessage(messageContextMenu.eventId)
+            onTriggered: TimelineManager.timeline.viewRawMessage(messageContextMenu.eventId)
         }
 
         MenuItem {
             visible: messageContextMenu.isEncrypted
             height: visible ? implicitHeight : 0
             text: qsTr("View decrypted raw message")
-            onTriggered: room.viewDecryptedRawMessage(messageContextMenu.eventId)
+            onTriggered: TimelineManager.timeline.viewDecryptedRawMessage(messageContextMenu.eventId)
         }
 
         MenuItem {
             text: qsTr("Redact message")
-            onTriggered: room.redactEvent(messageContextMenu.eventId)
+            onTriggered: TimelineManager.timeline.redactEvent(messageContextMenu.eventId)
         }
 
         MenuItem {

From 8047442661296f280999f4e97dc6fddd32a2a187 Mon Sep 17 00:00:00 2001
From: Weblate 
Date: Mon, 26 Oct 2020 17:26:42 -0400
Subject: [PATCH 33/33] Translated using Weblate (English)

Currently translated at 100.0% (411 of 411 strings)

Co-authored-by: Joseph Donofry 
Translate-URL: https://weblate.nheko.im/projects/nheko/nheko-master/en/
Translation: Nheko/nheko
---
 resources/langs/nheko_en.ts | 120 ++++++++++++++++++------------------
 1 file changed, 60 insertions(+), 60 deletions(-)

diff --git a/resources/langs/nheko_en.ts b/resources/langs/nheko_en.ts
index 8389a960..12e030b1 100644
--- a/resources/langs/nheko_en.ts
+++ b/resources/langs/nheko_en.ts
@@ -6,27 +6,27 @@
     
         
         Initiating...
-        
+        Initiating…
     
     
         
         Calling...
-        
+        Calling…
     
     
         
         Connecting...
-        
+        Connecting…
     
     
         
         Unmute Mic
-        
+        Unmute Mic
     
     
         
         Mute Mic
-        
+        Mute Mic
     
 
 
@@ -34,17 +34,17 @@
     
         
         Awaiting Confirmation
-        
+        Awaiting Confirmation
     
     
         
         Waiting for other side to complete verification.
-        
+        Waiting for other side to complete verification.
     
     
         
         Cancel
-        Cancel
+        Cancel
     
 
 
@@ -260,22 +260,22 @@
     
         
         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!
     
 
 
@@ -355,22 +355,22 @@
     
         
         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!
     
 
 
@@ -430,33 +430,33 @@
     
         
         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
+        Close
     
 
 
@@ -646,7 +646,7 @@ Example: https://server.my:8787
     
         
         Write a message...
-        Write a message…
+        Write a message…
     
 
 
@@ -654,42 +654,42 @@ Example: https://server.my:8787
     
         
         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
+        Cancel
     
     
         
         Deny
-        
+        Deny
     
     
         
         Start verification
-        
+        Start verification
     
     
         
         Accept
-        Accept
+        Accept
     
 
 
@@ -705,17 +705,17 @@ Example: https://server.my:8787
     
         
         Create a unique profile, which allows you to log into several accounts at the same time and start multiple instances of nheko.
-        
+        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
     
 
 
@@ -799,7 +799,7 @@ Example: https://server.my:8787
     
         
         Close
-        Close
+        Close
     
 
 
@@ -931,17 +931,17 @@ Example: https://server.my:8787
     
         
         Successful Verification
-        
+        Successful Verification
     
     
         
         Verification successful! Both sides verified their devices!
-        
+        Verification successful!  Both sides verified their devices!
     
     
         
         Close
-        Close
+        Close
     
 
 
@@ -1244,7 +1244,7 @@ Example: https://server.my:8787
     
         
         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.
     
 
 
@@ -1252,38 +1252,38 @@ Example: https://server.my:8787
     
         
         Back to room list
-        Back to room list
+        Back to room list
     
     
         
         
         No room selected
-        No room selected
+        No room selected
     
     
         
         Room options
-        Room options
+        Room options
     
     
         
         Invite users
-        Invite users
+        Invite users
     
     
         
         Members
-        Members
+        Members
     
     
         
         Leave room
-        Leave room
+        Leave room
     
     
         
         Settings
-        Settings
+        Settings
     
 
 
@@ -1347,22 +1347,22 @@ Example: https://server.my:8787
     
         
         Verify
-        
+        Verify
     
     
         
         Ban the user
-        
+        Ban the user
     
     
         
         Start a private chat
-        
+        Start a private chat
     
     
         
         Kick the user
-        
+        Kick the user
     
 
 
@@ -1390,7 +1390,7 @@ Example: https://server.my:8787
     
         
         profile: %1
-        
+        profile: %1
     
     
         
@@ -1546,12 +1546,12 @@ This usually causes the application icon in the task bar to animate in some fash
     
         
         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.
     
     
         
@@ -1636,12 +1636,12 @@ This usually causes the application icon in the task bar to animate in some fash
     
         
         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.
     
     
         
@@ -1695,27 +1695,27 @@ This usually causes the application icon in the task bar to animate in some fash
     
         
         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
+        Cancel