diff --git a/CMakeLists.txt b/CMakeLists.txt index 78900535..7b26602c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -281,11 +281,9 @@ set(SRC_FILES src/dialogs/CreateRoom.cpp src/dialogs/FallbackAuth.cpp src/dialogs/ImageOverlay.cpp - src/dialogs/InviteUsers.cpp src/dialogs/JoinRoom.cpp src/dialogs/LeaveRoom.cpp src/dialogs/Logout.cpp - src/dialogs/MemberList.cpp src/dialogs/PreviewUploadOverlay.cpp src/dialogs/ReCaptcha.cpp src/dialogs/ReadReceipts.cpp @@ -346,15 +344,19 @@ set(SRC_FILES src/CompletionProxyModel.cpp src/DeviceVerificationFlow.cpp src/EventAccessors.cpp - src/InviteeItem.cpp + src/InviteesModel.cpp src/Logging.cpp src/LoginPage.cpp src/MainWindow.cpp src/MatrixClient.cpp + src/MemberList.cpp src/MxcImageProvider.cpp src/Olm.cpp src/RegisterPage.cpp src/SSOHandler.cpp + src/CombinedImagePackModel.cpp + src/SingleImagePackModel.cpp + src/ImagePackListModel.cpp src/TrayIcon.cpp src/UserSettingsPage.cpp src/UsersModel.cpp @@ -380,7 +382,7 @@ if(USE_BUNDLED_MTXCLIENT) FetchContent_Declare( MatrixClient GIT_REPOSITORY https://github.com/Nheko-Reborn/mtxclient.git - GIT_TAG 1c277e9ac69aafdaf6888ce595b21dc86e970f28 + GIT_TAG 316a4040785ee2eabac7ef5ce7b4acb71c48f6eb ) set(BUILD_LIB_EXAMPLES OFF CACHE INTERNAL "") set(BUILD_LIB_TESTS OFF CACHE INTERNAL "") @@ -491,11 +493,9 @@ qt5_wrap_cpp(MOC_HEADERS src/dialogs/CreateRoom.h src/dialogs/FallbackAuth.h src/dialogs/ImageOverlay.h - src/dialogs/InviteUsers.h src/dialogs/JoinRoom.h src/dialogs/LeaveRoom.h src/dialogs/Logout.h - src/dialogs/MemberList.h src/dialogs/PreviewUploadOverlay.h src/dialogs/RawMessage.h src/dialogs/ReCaptcha.h @@ -553,12 +553,16 @@ qt5_wrap_cpp(MOC_HEADERS src/Clipboard.h src/CompletionProxyModel.h src/DeviceVerificationFlow.h - src/InviteeItem.h + src/InviteesModel.h src/LoginPage.h src/MainWindow.h + src/MemberList.h src/MxcImageProvider.h src/RegisterPage.h src/SSOHandler.h + src/CombinedImagePackModel.h + src/SingleImagePackModel.h + src/ImagePackListModel.h src/TrayIcon.h src/UserSettingsPage.h src/UsersModel.h diff --git a/io.github.NhekoReborn.Nheko.yaml b/io.github.NhekoReborn.Nheko.yaml index b6f468db..0fa450b3 100644 --- a/io.github.NhekoReborn.Nheko.yaml +++ b/io.github.NhekoReborn.Nheko.yaml @@ -161,7 +161,7 @@ modules: buildsystem: cmake-ninja name: mtxclient sources: - - commit: 1c277e9ac69aafdaf6888ce595b21dc86e970f28 + - commit: 316a4040785ee2eabac7ef5ce7b4acb71c48f6eb type: git url: https://github.com/Nheko-Reborn/mtxclient.git - config-opts: diff --git a/nheko-nightly.flatpakref b/nheko-nightly.flatpakref index 7d27bdfe..74e47ecd 100644 --- a/nheko-nightly.flatpakref +++ b/nheko-nightly.flatpakref @@ -3,6 +3,7 @@ Title=Nheko Nightly Name=io.github.NhekoReborn.Nheko Branch=master Url=https://flatpak.neko.dev/repo/nightly +SuggestRemoteName=nheko-nightlies Homepage=https://nheko-reborn.github.io/ Icon=https://nheko.im/nheko-reborn/nheko/-/raw/master/resources/nheko.svg RuntimeRepo=https://dl.flathub.org/repo/flathub.flatpakrepo diff --git a/nheko-nightly.flatpakrepo b/nheko-nightly.flatpakrepo index 4fb1bc55..680558af 100644 --- a/nheko-nightly.flatpakrepo +++ b/nheko-nightly.flatpakrepo @@ -1,6 +1,7 @@ [Flatpak Repo] Title=Nheko Nightly Url=https://flatpak.neko.dev/repo/nightly +SuggestRemoteName=nheko-nightlies Homepage=https://nheko.im/ Comment=Nheko nightly release repository Description=Nheko nightly release repository diff --git a/resources/icons/ui/sticky-note-solid.svg b/resources/icons/ui/sticky-note-solid.svg new file mode 100644 index 00000000..bc36d474 --- /dev/null +++ b/resources/icons/ui/sticky-note-solid.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/resources/langs/nheko_cs.ts b/resources/langs/nheko_cs.ts index c544bf6d..7c1134aa 100644 --- a/resources/langs/nheko_cs.ts +++ b/resources/langs/nheko_cs.ts @@ -38,7 +38,7 @@ AwaitingVerificationConfirmation - + Awaiting Confirmation @@ -48,7 +48,7 @@ - + Cancel @@ -125,7 +125,7 @@ ChatPage - + Failed to invite user: %1 @@ -157,12 +157,12 @@ - + Confirm invite - + Do you really want to invite %1 (%2)? @@ -227,12 +227,12 @@ - + Do you really want to start a private chat with %1? - + Cache migration failed! @@ -352,7 +352,7 @@ CrossSigningSecrets - + Decrypt secrets @@ -426,12 +426,12 @@ EmojiPicker - + Search - + People @@ -607,7 +607,7 @@ InputBar - + Select a file @@ -617,16 +617,42 @@ - + Failed to upload media. Please try again. - InviteeItem + InviteDialog - - Remove + + Invite users to %1 + + + + + User ID to invite + + + + + @joe:matrix.org + Example user id. The name 'joe' can be localized however you want. + + + + + Add + + + + + Invite + + + + + Cancel @@ -740,28 +766,15 @@ Example: https://server.my:8787 - - MemberList - - - Room members - - - - - OK - - - MessageDelegate - + Encryption enabled - + room name changed to: %1 @@ -771,7 +784,7 @@ Example: https://server.my:8787 - + topic changed to: %1 @@ -781,17 +794,17 @@ Example: https://server.my:8787 - + %1 changed the room avatar - + %1 created and configured room: %2 - + %1 placed a voice call. @@ -806,23 +819,23 @@ Example: https://server.my:8787 - + Negotiating call... - + %1 answered the call. - + removed - + %1 ended the call. @@ -830,7 +843,7 @@ Example: https://server.my:8787 MessageInput - + Hang up @@ -851,6 +864,11 @@ Example: https://server.my:8787 + Stickers + + + + Emoji @@ -868,17 +886,17 @@ Example: https://server.my:8787 MessageView - + Edit - + React - + Reply @@ -888,7 +906,7 @@ Example: https://server.my:8787 - + &Copy @@ -1096,7 +1114,7 @@ Example: https://server.my:8787 Placeholder - + unimplemented event: @@ -1216,7 +1234,7 @@ Example: https://server.my:8787 ReplyPopup - + Close @@ -1229,7 +1247,7 @@ Example: https://server.my:8787 RoomInfo - + no version stored @@ -1237,7 +1255,7 @@ Example: https://server.my:8787 RoomList - + New tag @@ -1277,17 +1295,7 @@ Example: https://server.my:8787 - - Accept - - - - - Decline - - - - + Status Message @@ -1337,20 +1345,43 @@ Example: https://server.my:8787 + + RoomMembers + + + Members of %1 + + + + + %n people in %1 + Summary above list of members + + + + + + + + + Invite more people + + + RoomSettings - + Room Settings - + %1 member(s) - + SETTINGS @@ -1432,11 +1463,6 @@ Example: https://server.my:8787 Room Version - - - OK - - Failed to enable encryption: %1 @@ -1469,6 +1495,24 @@ Example: https://server.my:8787 + + RoomlistModel + + + Pending invite. + + + + + Previewing this room + + + + + No preview available + + + ScreenShare @@ -1526,7 +1570,7 @@ Example: https://server.my:8787 StatusIndicator - + Failed @@ -1546,6 +1590,14 @@ Example: https://server.my:8787 + + StickerPicker + + + Search + + + Success @@ -1567,7 +1619,7 @@ Example: https://server.my:8787 TimelineModel - + Message redaction failed: %1 @@ -1578,7 +1630,7 @@ Example: https://server.my:8787 - + Save image @@ -1713,12 +1765,12 @@ Example: https://server.my:8787 - + You joined this room. - + %1 has changed their avatar and changed their display name to %2. @@ -1747,7 +1799,7 @@ Example: https://server.my:8787 TimelineRow - + Edited @@ -1755,17 +1807,32 @@ Example: https://server.my:8787 TimelineView - + No room open - + %1 member(s) - + + join the conversation + + + + + accept invite + + + + + decline invite + + + + Back to room list @@ -1773,7 +1840,7 @@ Example: https://server.my:8787 TimelineViewManager - + No encrypted private chat found with this user. Create an encrypted private chat with this user and try again. @@ -1781,18 +1848,17 @@ Example: https://server.my:8787 TopBar - + Back to room list - - + No room selected - + Room options @@ -1833,7 +1899,7 @@ Example: https://server.my:8787 UserProfile - + Global User Profile @@ -1843,7 +1909,7 @@ Example: https://server.my:8787 - + Verify @@ -1892,7 +1958,7 @@ Example: https://server.my:8787 UserSettings - + Default @@ -1901,7 +1967,7 @@ Example: https://server.my:8787 UserSettingsPage - + Minimize to tray @@ -2347,7 +2413,7 @@ This usually causes the application icon in the task bar to animate in some fash Waiting - + Waiting for other party… @@ -2398,7 +2464,7 @@ This usually causes the application icon in the task bar to animate in some fash descriptiveTime - + Yesterday @@ -2469,19 +2535,6 @@ This usually causes the application icon in the task bar to animate in some fash - - dialogs::InviteUsers - - - Cancel - - - - - User ID to invite - - - dialogs::JoinRoom diff --git a/resources/langs/nheko_de.ts b/resources/langs/nheko_de.ts index 58c209a2..3cd35781 100644 --- a/resources/langs/nheko_de.ts +++ b/resources/langs/nheko_de.ts @@ -38,7 +38,7 @@ AwaitingVerificationConfirmation - + Awaiting Confirmation Auf Bestätigung warten @@ -48,7 +48,7 @@ Wartet darauf, dass die andere Seite die Verifizierung abschließt. - + Cancel Abbrechen @@ -125,7 +125,7 @@ ChatPage - + Failed to invite user: %1 Nutzer konnte nicht eingeladen werden: %1 @@ -157,12 +157,12 @@ - + Confirm invite Einladung bestätigen - + Do you really want to invite %1 (%2)? Nutzer %1 (%2) wirklich einladen? @@ -227,12 +227,12 @@ Verbannung aufgehoben: %1 - + Do you really want to start a private chat with %1? Möchtest du wirklich eine private Konversation mit %1 beginnen? - + Cache migration failed! Migration des Caches fehlgeschlagen! @@ -352,7 +352,7 @@ CrossSigningSecrets - + Decrypt secrets Geheimnisse entschlüsseln @@ -426,12 +426,12 @@ EmojiPicker - + Search Suche - + People Leute @@ -607,7 +607,7 @@ InputBar - + Select a file Datei auswählen @@ -617,17 +617,43 @@ Alle Dateien (*) - + Failed to upload media. Please try again. Medienupload fehlgeschlagen. Bitte versuche es erneut. - InviteeItem + InviteDialog - - Remove - Löschen + + Invite users to %1 + Lade Benutzer in %1 ein + + + + User ID to invite + Benutzer-ID, die eingeladen werden soll + + + + @joe:matrix.org + Example user id. The name 'joe' can be localized however you want. + @joe:matrix.org + + + + Add + Hinzufügen + + + + Invite + Einladen + + + + Cancel + Abbrechen @@ -744,23 +770,10 @@ Beispiel: https://mein.server:8787 SSO Anmeldung fehlgeschlagen - - MemberList - - - Room members - Teilnehmerliste - - - - OK - OK - - MessageDelegate - + removed entfernt @@ -771,7 +784,7 @@ Beispiel: https://mein.server:8787 Verschlüsselung aktiviert - + room name changed to: %1 Raumname wurde gändert auf: %1 @@ -781,7 +794,7 @@ Beispiel: https://mein.server:8787 Raumname wurde entfernt - + topic changed to: %1 Raumthema wurde geändert auf: %1 @@ -791,17 +804,17 @@ Beispiel: https://mein.server:8787 Thema wurde entfernt - + %1 changed the room avatar %1 hat dem Raumavatar geändert - + %1 created and configured room: %2 %1 hat den Raum erstellt: %2 - + %1 placed a voice call. %1 hat einen Sprachanruf gestartet. @@ -816,17 +829,17 @@ Beispiel: https://mein.server:8787 %1 hat angerufen. - + %1 answered the call. %1 hat den Anruf angenommen. - + %1 ended the call. %1 hat den Anruf beendet. - + Negotiating call... Wählt… @@ -834,7 +847,7 @@ Beispiel: https://mein.server:8787 MessageInput - + Hang up Auflegen @@ -855,6 +868,11 @@ Beispiel: https://mein.server:8787 + Stickers + Sticker + + + Emoji Emoji @@ -872,17 +890,17 @@ Beispiel: https://mein.server:8787 MessageView - + Edit Bearbeiten - + React Reaktion senden - + Reply Antworten @@ -892,7 +910,7 @@ Beispiel: https://mein.server:8787 Optionen - + &Copy &Kopieren @@ -1100,7 +1118,7 @@ Beispiel: https://mein.server:8787 Placeholder - + unimplemented event: Unimplementiertes Event: @@ -1220,7 +1238,7 @@ Beispiel: https://mein.server:8787 ReplyPopup - + Close Schließen @@ -1233,7 +1251,7 @@ Beispiel: https://mein.server:8787 RoomInfo - + no version stored keine Version gespeichert @@ -1241,7 +1259,7 @@ Beispiel: https://mein.server:8787 RoomList - + New tag Neuer Tag @@ -1281,17 +1299,7 @@ Beispiel: https://mein.server:8787 Neuen Tag erstellen... - - Accept - Akzeptieren - - - - Decline - Ablehnen - - - + Status Message Statusnachricht @@ -1341,20 +1349,42 @@ Beispiel: https://mein.server:8787 Benutzereinstellungen + + RoomMembers + + + Members of %1 + Teilnehmer in %1 + + + + %n people in %1 + Summary above list of members + + %n Person in %1 + %n Personen in %1 + + + + + Invite more people + Lade mehr Leute ein + + RoomSettings - + Room Settings Raumeinstellungen - + %1 member(s) %1 Teilnehmer - + SETTINGS EINSTELLUNGEN @@ -1436,11 +1466,6 @@ Beispiel: https://mein.server:8787 Room Version Raumversion - - - OK - OK - Failed to enable encryption: %1 @@ -1473,6 +1498,24 @@ Beispiel: https://mein.server:8787 Hochladen des Bildes fehlgeschlagen: %s + + RoomlistModel + + + Pending invite. + Offene Einladung. + + + + Previewing this room + Vorschau dieses Raums + + + + No preview available + Keine Vorschau verfügbar + + ScreenShare @@ -1530,7 +1573,7 @@ Beispiel: https://mein.server:8787 StatusIndicator - + Failed Fehlgeschlagen @@ -1550,6 +1593,14 @@ Beispiel: https://mein.server:8787 Gelesen + + StickerPicker + + + Search + Suche + + Success @@ -1571,7 +1622,7 @@ Beispiel: https://mein.server:8787 TimelineModel - + Message redaction failed: %1 Nachricht zurückziehen fehlgeschlagen: %1 @@ -1582,7 +1633,7 @@ Beispiel: https://mein.server:8787 Event konnte nicht verschlüsselt werden, senden wurde abgebrochen! - + Save image Bild speichern @@ -1716,12 +1767,12 @@ Beispiel: https://mein.server:8787 %1 hat das Anklopfen zurückgezogen. - + You joined this room. Du bist dem Raum beigetreten. - + %1 has changed their avatar and changed their display name to %2. %1 hat den eigenen Avatar und Namen geändert zu %2. @@ -1750,7 +1801,7 @@ Beispiel: https://mein.server:8787 TimelineRow - + Edited Bearbeitet @@ -1758,17 +1809,32 @@ Beispiel: https://mein.server:8787 TimelineView - + No room open Kein Raum geöffnet - + %1 member(s) %1 Teilnehmer - + + join the conversation + An der Unterhaltung teilnehmen + + + + accept invite + Einladung annehmen + + + + decline invite + Einladung ablehnen + + + Back to room list Zurück zur Raumliste @@ -1776,7 +1842,7 @@ Beispiel: https://mein.server:8787 TimelineViewManager - + No encrypted private chat found with this user. Create an encrypted private chat with this user and try again. Keinen verschlüsselten Chat mit diesem User gefunden. Erstelle einen verschlüsselten 1:1 Chat mit diesem Nutzer und versuche es erneut. @@ -1784,18 +1850,17 @@ Beispiel: https://mein.server:8787 TopBar - + Back to room list Zurück zur Raumliste - - + No room selected Kein Raum ausgewählt - + Room options Raumoptionen @@ -1836,7 +1901,7 @@ Beispiel: https://mein.server:8787 UserProfile - + Global User Profile Globales Nutzerprofil @@ -1846,7 +1911,7 @@ Beispiel: https://mein.server:8787 Raumspezifisches Nutzerprofil - + Verify Verifizieren @@ -1895,7 +1960,7 @@ Beispiel: https://mein.server:8787 UserSettings - + Default Standard @@ -1904,7 +1969,7 @@ Beispiel: https://mein.server:8787 UserSettingsPage - + Minimize to tray Ins Benachrichtigungsfeld minimieren @@ -2360,7 +2425,7 @@ Normalerweise animiert das den Taskbaricon oder färbt das Fenster orange ein. Waiting - + Waiting for other party… Auf Gegenseite warten… @@ -2411,7 +2476,7 @@ Normalerweise animiert das den Taskbaricon oder färbt das Fenster orange ein. descriptiveTime - + Yesterday Gestern @@ -2482,19 +2547,6 @@ Normalerweise animiert das den Taskbaricon oder färbt das Fenster orange ein.Öffne das Fallback, folge den Anweisungen und bestätige nach Abschluss via "Bestätigen". - - dialogs::InviteUsers - - - Cancel - Abbrechen - - - - User ID to invite - Benutzer-ID, die eingeladen werden soll - - dialogs::JoinRoom diff --git a/resources/langs/nheko_el.ts b/resources/langs/nheko_el.ts index d5d5f323..6df73f73 100644 --- a/resources/langs/nheko_el.ts +++ b/resources/langs/nheko_el.ts @@ -38,7 +38,7 @@ AwaitingVerificationConfirmation - + Awaiting Confirmation @@ -48,7 +48,7 @@ - + Cancel Άκυρο @@ -125,7 +125,7 @@ ChatPage - + Failed to invite user: %1 @@ -157,12 +157,12 @@ - + Confirm invite - + Do you really want to invite %1 (%2)? @@ -227,12 +227,12 @@ - + Do you really want to start a private chat with %1? - + Cache migration failed! @@ -352,7 +352,7 @@ CrossSigningSecrets - + Decrypt secrets @@ -426,12 +426,12 @@ EmojiPicker - + Search - + People @@ -607,7 +607,7 @@ InputBar - + Select a file Διάλεξε ένα αρχείο @@ -617,18 +617,44 @@ Όλα τα αρχεία (*) - + Failed to upload media. Please try again. - InviteeItem + InviteDialog - - Remove + + Invite users to %1 + + + User ID to invite + Όνομα χρήστη + + + + @joe:matrix.org + Example user id. The name 'joe' can be localized however you want. + + + + + Add + + + + + Invite + + + + + Cancel + Άκυρο + LoginPage @@ -740,23 +766,10 @@ Example: https://server.my:8787 - - MemberList - - - Room members - Μέλη - - - - OK - - - MessageDelegate - + removed @@ -767,7 +780,7 @@ Example: https://server.my:8787 - + room name changed to: %1 @@ -777,7 +790,7 @@ Example: https://server.my:8787 - + topic changed to: %1 @@ -787,17 +800,17 @@ Example: https://server.my:8787 - + %1 changed the room avatar - + %1 created and configured room: %2 - + %1 placed a voice call. @@ -812,17 +825,17 @@ Example: https://server.my:8787 - + %1 answered the call. - + %1 ended the call. - + Negotiating call... @@ -830,7 +843,7 @@ Example: https://server.my:8787 MessageInput - + Hang up @@ -851,6 +864,11 @@ Example: https://server.my:8787 + Stickers + + + + Emoji @@ -868,17 +886,17 @@ Example: https://server.my:8787 MessageView - + Edit - + React - + Reply @@ -888,7 +906,7 @@ Example: https://server.my:8787 - + &Copy @@ -1096,7 +1114,7 @@ Example: https://server.my:8787 Placeholder - + unimplemented event: @@ -1216,7 +1234,7 @@ Example: https://server.my:8787 ReplyPopup - + Close @@ -1229,7 +1247,7 @@ Example: https://server.my:8787 RoomInfo - + no version stored @@ -1237,7 +1255,7 @@ Example: https://server.my:8787 RoomList - + New tag @@ -1277,17 +1295,7 @@ Example: https://server.my:8787 - - Accept - Αποδοχή - - - - Decline - Απόρριψη - - - + Status Message @@ -1337,20 +1345,42 @@ Example: https://server.my:8787 + + RoomMembers + + + Members of %1 + + + + + %n people in %1 + Summary above list of members + + + + + + + + Invite more people + + + RoomSettings - + Room Settings - + %1 member(s) - + SETTINGS @@ -1432,11 +1462,6 @@ Example: https://server.my:8787 Room Version - - - OK - - Failed to enable encryption: %1 @@ -1469,6 +1494,24 @@ Example: https://server.my:8787 + + RoomlistModel + + + Pending invite. + + + + + Previewing this room + + + + + No preview available + + + ScreenShare @@ -1526,7 +1569,7 @@ Example: https://server.my:8787 StatusIndicator - + Failed @@ -1546,6 +1589,14 @@ Example: https://server.my:8787 + + StickerPicker + + + Search + + + Success @@ -1567,7 +1618,7 @@ Example: https://server.my:8787 TimelineModel - + Message redaction failed: %1 @@ -1578,7 +1629,7 @@ Example: https://server.my:8787 - + Save image Αποθήκευση Εικόνας @@ -1712,12 +1763,12 @@ Example: https://server.my:8787 - + You joined this room. - + %1 has changed their avatar and changed their display name to %2. @@ -1746,7 +1797,7 @@ Example: https://server.my:8787 TimelineRow - + Edited @@ -1754,17 +1805,32 @@ Example: https://server.my:8787 TimelineView - + No room open - + %1 member(s) - + + join the conversation + + + + + accept invite + + + + + decline invite + + + + Back to room list @@ -1772,7 +1838,7 @@ Example: https://server.my:8787 TimelineViewManager - + No encrypted private chat found with this user. Create an encrypted private chat with this user and try again. @@ -1780,18 +1846,17 @@ Example: https://server.my:8787 TopBar - + Back to room list - - + No room selected - + Room options @@ -1832,7 +1897,7 @@ Example: https://server.my:8787 UserProfile - + Global User Profile @@ -1842,7 +1907,7 @@ Example: https://server.my:8787 - + Verify @@ -1891,7 +1956,7 @@ Example: https://server.my:8787 UserSettings - + Default @@ -1900,7 +1965,7 @@ Example: https://server.my:8787 UserSettingsPage - + Minimize to tray Ελαχιστοποίηση @@ -2346,7 +2411,7 @@ This usually causes the application icon in the task bar to animate in some fash Waiting - + Waiting for other party… @@ -2397,7 +2462,7 @@ This usually causes the application icon in the task bar to animate in some fash descriptiveTime - + Yesterday @@ -2468,19 +2533,6 @@ This usually causes the application icon in the task bar to animate in some fash - - dialogs::InviteUsers - - - Cancel - Άκυρο - - - - User ID to invite - Όνομα χρήστη - - dialogs::JoinRoom diff --git a/resources/langs/nheko_en.ts b/resources/langs/nheko_en.ts index 7d3f8276..97a67b06 100644 --- a/resources/langs/nheko_en.ts +++ b/resources/langs/nheko_en.ts @@ -38,7 +38,7 @@ AwaitingVerificationConfirmation - + Awaiting Confirmation Awaiting Confirmation @@ -48,7 +48,7 @@ Waiting for other side to complete verification. - + Cancel Cancel @@ -125,7 +125,7 @@ ChatPage - + Failed to invite user: %1 Failed to invite user: %1 @@ -157,12 +157,12 @@ - + Confirm invite Confirm invite - + Do you really want to invite %1 (%2)? Do you really want to invite %1 (%2)? @@ -227,12 +227,12 @@ Unbanned user: %1 - + Do you really want to start a private chat with %1? Do you really want to start a private chat with %1? - + Cache migration failed! Cache migration failed! @@ -352,7 +352,7 @@ CrossSigningSecrets - + Decrypt secrets Decrypt secrets @@ -426,12 +426,12 @@ EmojiPicker - + Search Search - + People People @@ -607,7 +607,7 @@ InputBar - + Select a file Select a file @@ -617,17 +617,43 @@ All Files (*) - + Failed to upload media. Please try again. Failed to upload media. Please try again. - InviteeItem + InviteDialog - - Remove - Remove + + Invite users to %1 + Invite users to %1 + + + + User ID to invite + User ID to invite + + + + @joe:matrix.org + Example user id. The name 'joe' can be localized however you want. + @joe:matrix.org + + + + Add + Add + + + + Invite + Invite + + + + Cancel + Cancel @@ -744,28 +770,15 @@ Example: https://server.my:8787 SSO login failed - - MemberList - - - Room members - Room members - - - - OK - OK - - MessageDelegate - + Encryption enabled Encryption enabled - + room name changed to: %1 room name changed to: %1 @@ -775,7 +788,7 @@ Example: https://server.my:8787 removed room name - + topic changed to: %1 topic changed to: %1 @@ -785,17 +798,17 @@ Example: https://server.my:8787 removed topic - + %1 changed the room avatar %1 changed the room avatar - + %1 created and configured room: %2 %1 created and configured room: %2 - + %1 placed a voice call. %1 placed a voice call. @@ -810,23 +823,23 @@ Example: https://server.my:8787 %1 placed a call. - + Negotiating call... Negotiating call… - + %1 answered the call. %1 answered the call. - + removed removed - + %1 ended the call. %1 ended the call. @@ -834,7 +847,7 @@ Example: https://server.my:8787 MessageInput - + Hang up Hang up @@ -855,6 +868,11 @@ Example: https://server.my:8787 + Stickers + Stickers + + + Emoji Emoji @@ -872,17 +890,17 @@ Example: https://server.my:8787 MessageView - + Edit Edit - + React React - + Reply Reply @@ -892,7 +910,7 @@ Example: https://server.my:8787 Options - + &Copy &Copy @@ -1100,7 +1118,7 @@ Example: https://server.my:8787 Placeholder - + unimplemented event: unimplemented event: @@ -1220,7 +1238,7 @@ Example: https://server.my:8787 ReplyPopup - + Close Close @@ -1233,7 +1251,7 @@ Example: https://server.my:8787 RoomInfo - + no version stored no version stored @@ -1241,7 +1259,7 @@ Example: https://server.my:8787 RoomList - + New tag New tag @@ -1281,17 +1299,7 @@ Example: https://server.my:8787 Create new tag... - - Accept - Accept - - - - Decline - Decline - - - + Status Message Status Message @@ -1341,20 +1349,42 @@ Example: https://server.my:8787 User settings + + RoomMembers + + + Members of %1 + Members of %1 + + + + %n people in %1 + Summary above list of members + + %n person in %1 + %n people in %1 + + + + + Invite more people + Invite more people + + RoomSettings - + Room Settings Room Settings - + %1 member(s) %1 member(s) - + SETTINGS SETTINGS @@ -1438,11 +1468,6 @@ E2E implementation until device verification is completed. Room Version Room Version - - - OK - OK - Failed to enable encryption: %1 @@ -1475,6 +1500,24 @@ E2E implementation until device verification is completed. Failed to upload image: %s + + RoomlistModel + + + Pending invite. + Pending invite. + + + + Previewing this room + Previewing this room + + + + No preview available + No preview available + + ScreenShare @@ -1532,7 +1575,7 @@ E2E implementation until device verification is completed. StatusIndicator - + Failed Failed @@ -1552,6 +1595,14 @@ E2E implementation until device verification is completed. Read + + StickerPicker + + + Search + Search + + Success @@ -1573,7 +1624,7 @@ E2E implementation until device verification is completed. TimelineModel - + Message redaction failed: %1 Message redaction failed: %1 @@ -1584,7 +1635,7 @@ E2E implementation until device verification is completed. Failed to encrypt event, sending aborted! - + Save image Save image @@ -1718,12 +1769,12 @@ E2E implementation until device verification is completed. %1 redacted their knock. - + You joined this room. You joined this room. - + %1 has changed their avatar and changed their display name to %2. %1 has changed their avatar and changed their display name to %2. @@ -1752,7 +1803,7 @@ E2E implementation until device verification is completed. TimelineRow - + Edited Edited @@ -1760,17 +1811,32 @@ E2E implementation until device verification is completed. TimelineView - + No room open No room open - + %1 member(s) %1 member(s) - + + join the conversation + join the conversation + + + + accept invite + accept invite + + + + decline invite + decline invite + + + Back to room list Back to room list @@ -1778,7 +1844,7 @@ E2E implementation until device verification is completed. TimelineViewManager - + No encrypted private chat found with this user. Create an encrypted private chat with this user and try again. No encrypted private chat found with this user. Create an encrypted private chat with this user and try again. @@ -1786,18 +1852,17 @@ E2E implementation until device verification is completed. TopBar - + Back to room list Back to room list - - + No room selected No room selected - + Room options Room options @@ -1838,7 +1903,7 @@ E2E implementation until device verification is completed. UserProfile - + Global User Profile Global User Profile @@ -1848,7 +1913,7 @@ E2E implementation until device verification is completed. Room User Profile - + Verify Verify @@ -1897,7 +1962,7 @@ E2E implementation until device verification is completed. UserSettings - + Default Default @@ -1906,7 +1971,7 @@ E2E implementation until device verification is completed. UserSettingsPage - + Minimize to tray Minimize to tray @@ -2363,7 +2428,7 @@ This usually causes the application icon in the task bar to animate in some fash Waiting - + Waiting for other party… Waiting for other party… @@ -2414,7 +2479,7 @@ This usually causes the application icon in the task bar to animate in some fash descriptiveTime - + Yesterday Yesterday @@ -2485,19 +2550,6 @@ This usually causes the application icon in the task bar to animate in some fash Open the fallback, follow the steps and confirm after completing them. - - dialogs::InviteUsers - - - Cancel - Cancel - - - - User ID to invite - User ID to invite - - dialogs::JoinRoom diff --git a/resources/langs/nheko_eo.ts b/resources/langs/nheko_eo.ts index 26a67d49..f3529f28 100644 --- a/resources/langs/nheko_eo.ts +++ b/resources/langs/nheko_eo.ts @@ -38,7 +38,7 @@ AwaitingVerificationConfirmation - + Awaiting Confirmation Atendante konfirmon @@ -48,7 +48,7 @@ Atendante kontrolon venontan de la alia flanko. - + Cancel Nuligi @@ -125,7 +125,7 @@ ChatPage - + Failed to invite user: %1 Malsukcesis inviti uzanton: %1 @@ -158,12 +158,12 @@ - + Confirm invite Konfirmu inviton - + Do you really want to invite %1 (%2)? Ĉu vi certe volas inviti uzanton %1 (%2)? @@ -228,12 +228,12 @@ Malforbaris uzanton: %1 - + Do you really want to start a private chat with %1? Ĉu vi certe volas komenci privatan babilon kun %1? - + Cache migration failed! Malsukcesis migrado de kaŝmemoro! @@ -353,7 +353,7 @@ CrossSigningSecrets - + Decrypt secrets Malĉifri sekretojn @@ -427,12 +427,12 @@ EmojiPicker - + Search Serĉu - + People Homoj @@ -608,7 +608,7 @@ InputBar - + Select a file Elektu dosieron @@ -618,17 +618,43 @@ Ĉiuj dosieroj (*) - + Failed to upload media. Please try again. Malsukcesis alŝuti vidaŭdaĵojn. Bonvolu reprovi. - InviteeItem + InviteDialog - - Remove - Forigi + + Invite users to %1 + + + + + User ID to invite + + + + + @joe:matrix.org + Example user id. The name 'joe' can be localized however you want. + + + + + Add + + + + + Invite + + + + + Cancel + Nuligi @@ -747,23 +773,10 @@ Ekzemplo: https://servilo.mia:8787 Malsukcesis ununura saluto - - MemberList - - - Room members - Membroj de la ĉambro - - - - OK - Bone - - MessageDelegate - + removed forigita @@ -774,7 +787,7 @@ Ekzemplo: https://servilo.mia:8787 - + room name changed to: %1 Nomo da ĉambro ŝanĝiĝis al: %1 @@ -784,7 +797,7 @@ Ekzemplo: https://servilo.mia:8787 - + topic changed to: %1 @@ -794,17 +807,17 @@ Ekzemplo: https://servilo.mia:8787 - + %1 changed the room avatar - + %1 created and configured room: %2 - + %1 placed a voice call. %1 metis voĉvokon. @@ -819,17 +832,17 @@ Ekzemplo: https://servilo.mia:8787 %1 metis vokon. - + %1 answered the call. %1 respondis la vokon. - + %1 ended the call. %1 finis la vokon. - + Negotiating call... Traktante vokon… @@ -837,7 +850,7 @@ Ekzemplo: https://servilo.mia:8787 MessageInput - + Hang up @@ -858,6 +871,11 @@ Ekzemplo: https://servilo.mia:8787 + Stickers + + + + Emoji Bildosignoj @@ -875,17 +893,17 @@ Ekzemplo: https://servilo.mia:8787 MessageView - + Edit Redakti - + React Reagi - + Reply Respondi @@ -895,7 +913,7 @@ Ekzemplo: https://servilo.mia:8787 Elektebloj - + &Copy @@ -1103,7 +1121,7 @@ Ekzemplo: https://servilo.mia:8787 Placeholder - + unimplemented event: neprogramita okazo: @@ -1223,7 +1241,7 @@ Ekzemplo: https://servilo.mia:8787 ReplyPopup - + Close Fermi @@ -1236,7 +1254,7 @@ Ekzemplo: https://servilo.mia:8787 RoomInfo - + no version stored @@ -1244,7 +1262,7 @@ Ekzemplo: https://servilo.mia:8787 RoomList - + New tag @@ -1284,17 +1302,7 @@ Ekzemplo: https://servilo.mia:8787 - - Accept - Akcepti - - - - Decline - Rifuzi - - - + Status Message @@ -1344,20 +1352,42 @@ Ekzemplo: https://servilo.mia:8787 Agordoj de uzanto + + RoomMembers + + + Members of %1 + + + + + %n people in %1 + Summary above list of members + + + + + + + + Invite more people + + + RoomSettings - + Room Settings Agordoj de ĉambro - + %1 member(s) %1 ĉambrano(j) - + SETTINGS AGORDOJ @@ -1439,11 +1469,6 @@ Ekzemplo: https://servilo.mia:8787 Room Version Versio de ĉambro - - - OK - Bone - Failed to enable encryption: %1 @@ -1476,6 +1501,24 @@ Ekzemplo: https://servilo.mia:8787 Malsukcesis alŝuti bildon: %s + + RoomlistModel + + + Pending invite. + + + + + Previewing this room + + + + + No preview available + + + ScreenShare @@ -1533,7 +1576,7 @@ Ekzemplo: https://servilo.mia:8787 StatusIndicator - + Failed Estas malsukcesa @@ -1554,6 +1597,14 @@ Ekzemplo: https://servilo.mia:8787 Estas legita + + StickerPicker + + + Search + Serĉu + + Success @@ -1575,7 +1626,7 @@ Ekzemplo: https://servilo.mia:8787 TimelineModel - + Message redaction failed: %1 @@ -1586,7 +1637,7 @@ Ekzemplo: https://servilo.mia:8787 - + Save image Konservi bildon @@ -1722,12 +1773,12 @@ Ekzemplo: https://servilo.mia:8787 - + You joined this room. Vi aliĝis ĉi tiun ĉambron. - + %1 has changed their avatar and changed their display name to %2. @@ -1756,7 +1807,7 @@ Ekzemplo: https://servilo.mia:8787 TimelineRow - + Edited Redaktita @@ -1764,17 +1815,32 @@ Ekzemplo: https://servilo.mia:8787 TimelineView - + No room open - + %1 member(s) %1 ĉambrano(j) - + + join the conversation + + + + + accept invite + + + + + decline invite + + + + Back to room list @@ -1782,7 +1848,7 @@ Ekzemplo: https://servilo.mia:8787 TimelineViewManager - + No encrypted private chat found with this user. Create an encrypted private chat with this user and try again. @@ -1790,18 +1856,17 @@ Ekzemplo: https://servilo.mia:8787 TopBar - + Back to room list - - + No room selected - + Room options @@ -1842,7 +1907,7 @@ Ekzemplo: https://servilo.mia:8787 UserProfile - + Global User Profile @@ -1852,7 +1917,7 @@ Ekzemplo: https://servilo.mia:8787 - + Verify @@ -1901,7 +1966,7 @@ Ekzemplo: https://servilo.mia:8787 UserSettings - + Default @@ -1910,7 +1975,7 @@ Ekzemplo: https://servilo.mia:8787 UserSettingsPage - + Minimize to tray @@ -2373,7 +2438,7 @@ This usually causes the application icon in the task bar to animate in some fash Waiting - + Waiting for other party… @@ -2425,7 +2490,7 @@ This usually causes the application icon in the task bar to animate in some fash descriptiveTime - + Yesterday Hieraŭ @@ -2496,19 +2561,6 @@ This usually causes the application icon in the task bar to animate in some fash - - dialogs::InviteUsers - - - Cancel - Nuligi - - - - User ID to invite - - - dialogs::JoinRoom diff --git a/resources/langs/nheko_es.ts b/resources/langs/nheko_es.ts index 6318b9c4..8eb4675a 100644 --- a/resources/langs/nheko_es.ts +++ b/resources/langs/nheko_es.ts @@ -38,7 +38,7 @@ AwaitingVerificationConfirmation - + Awaiting Confirmation Esperando confirmación @@ -48,7 +48,7 @@ Esperando a que la otra parte complete la verificación. - + Cancel Cancelar @@ -125,7 +125,7 @@ ChatPage - + Failed to invite user: %1 No se pudo invitar al usuario: %1 @@ -157,12 +157,12 @@ - + Confirm invite Confirmar invitación - + Do you really want to invite %1 (%2)? @@ -227,12 +227,12 @@ - + Do you really want to start a private chat with %1? - + Cache migration failed! @@ -352,7 +352,7 @@ CrossSigningSecrets - + Decrypt secrets @@ -426,12 +426,12 @@ EmojiPicker - + Search - + People @@ -607,7 +607,7 @@ InputBar - + Select a file @@ -617,18 +617,44 @@ - + Failed to upload media. Please try again. - InviteeItem + InviteDialog - - Remove + + Invite users to %1 + + + User ID to invite + + + + + @joe:matrix.org + Example user id. The name 'joe' can be localized however you want. + + + + + Add + + + + + Invite + + + + + Cancel + Cancelar + LoginPage @@ -740,28 +766,15 @@ Example: https://server.my:8787 - - MemberList - - - Room members - - - - - OK - - - MessageDelegate - + Encryption enabled - + room name changed to: %1 @@ -771,7 +784,7 @@ Example: https://server.my:8787 - + topic changed to: %1 @@ -781,17 +794,17 @@ Example: https://server.my:8787 - + %1 changed the room avatar - + %1 created and configured room: %2 - + %1 placed a voice call. @@ -806,23 +819,23 @@ Example: https://server.my:8787 - + Negotiating call... - + %1 answered the call. - + removed - + %1 ended the call. @@ -830,7 +843,7 @@ Example: https://server.my:8787 MessageInput - + Hang up @@ -851,6 +864,11 @@ Example: https://server.my:8787 + Stickers + + + + Emoji @@ -868,17 +886,17 @@ Example: https://server.my:8787 MessageView - + Edit - + React - + Reply @@ -888,7 +906,7 @@ Example: https://server.my:8787 - + &Copy @@ -1096,7 +1114,7 @@ Example: https://server.my:8787 Placeholder - + unimplemented event: @@ -1216,7 +1234,7 @@ Example: https://server.my:8787 ReplyPopup - + Close @@ -1229,7 +1247,7 @@ Example: https://server.my:8787 RoomInfo - + no version stored @@ -1237,7 +1255,7 @@ Example: https://server.my:8787 RoomList - + New tag @@ -1277,17 +1295,7 @@ Example: https://server.my:8787 - - Accept - Aceptar - - - - Decline - Rechazar - - - + Status Message @@ -1337,20 +1345,42 @@ Example: https://server.my:8787 + + RoomMembers + + + Members of %1 + + + + + %n people in %1 + Summary above list of members + + + + + + + + Invite more people + + + RoomSettings - + Room Settings - + %1 member(s) - + SETTINGS @@ -1432,11 +1462,6 @@ Example: https://server.my:8787 Room Version - - - OK - - Failed to enable encryption: %1 @@ -1469,6 +1494,24 @@ Example: https://server.my:8787 + + RoomlistModel + + + Pending invite. + + + + + Previewing this room + + + + + No preview available + + + ScreenShare @@ -1526,7 +1569,7 @@ Example: https://server.my:8787 StatusIndicator - + Failed @@ -1546,6 +1589,14 @@ Example: https://server.my:8787 + + StickerPicker + + + Search + + + Success @@ -1567,7 +1618,7 @@ Example: https://server.my:8787 TimelineModel - + Message redaction failed: %1 @@ -1578,7 +1629,7 @@ Example: https://server.my:8787 - + Save image @@ -1722,12 +1773,12 @@ Example: https://server.my:8787 - + You joined this room. Te has unido a esta sala. - + Rejected the knock from %1. @@ -1746,7 +1797,7 @@ Example: https://server.my:8787 TimelineRow - + Edited @@ -1754,17 +1805,32 @@ Example: https://server.my:8787 TimelineView - + No room open - + %1 member(s) - + + join the conversation + + + + + accept invite + + + + + decline invite + + + + Back to room list @@ -1772,7 +1838,7 @@ Example: https://server.my:8787 TimelineViewManager - + No encrypted private chat found with this user. Create an encrypted private chat with this user and try again. @@ -1780,18 +1846,17 @@ Example: https://server.my:8787 TopBar - + Back to room list - - + No room selected - + Room options @@ -1832,7 +1897,7 @@ Example: https://server.my:8787 UserProfile - + Global User Profile @@ -1842,7 +1907,7 @@ Example: https://server.my:8787 - + Verify @@ -1891,7 +1956,7 @@ Example: https://server.my:8787 UserSettings - + Default @@ -1900,7 +1965,7 @@ Example: https://server.my:8787 UserSettingsPage - + Minimize to tray @@ -2346,7 +2411,7 @@ This usually causes the application icon in the task bar to animate in some fash Waiting - + Waiting for other party… @@ -2397,7 +2462,7 @@ This usually causes the application icon in the task bar to animate in some fash descriptiveTime - + Yesterday @@ -2468,19 +2533,6 @@ This usually causes the application icon in the task bar to animate in some fash - - dialogs::InviteUsers - - - Cancel - Cancelar - - - - User ID to invite - - - dialogs::JoinRoom diff --git a/resources/langs/nheko_et.ts b/resources/langs/nheko_et.ts index 198ec332..20261395 100644 --- a/resources/langs/nheko_et.ts +++ b/resources/langs/nheko_et.ts @@ -38,7 +38,7 @@ AwaitingVerificationConfirmation - + Awaiting Confirmation Ootan kinnitust @@ -48,7 +48,7 @@ Ootan et teine osapool lõpetaks verifitseerimise. - + Cancel Katkesta @@ -125,7 +125,7 @@ ChatPage - + Failed to invite user: %1 Kutse saatmine kasutajale ei õnnestunud: %1 @@ -157,12 +157,12 @@ - + Confirm invite Kinnita kutse - + Do you really want to invite %1 (%2)? Kas sa tõesti soovid saata kutset kasutajale %1 (%2)? @@ -227,12 +227,12 @@ Suhtluskeeld eemaldatud: %1 - + Do you really want to start a private chat with %1? Kas sa kindlasti soovid alustada otsevestlust kasutajaga %1? - + Cache migration failed! Puhvri versiooniuuendus ebaõnnestus! @@ -352,7 +352,7 @@ CrossSigningSecrets - + Decrypt secrets Dekrüpti andmed @@ -426,12 +426,12 @@ EmojiPicker - + Search Otsi - + People Inimesed @@ -607,7 +607,7 @@ InputBar - + Select a file Vali fail @@ -617,17 +617,43 @@ Kõik failid (*) - + Failed to upload media. Please try again. Meediafailide üleslaadimine ei õnnestunud. Palun proovi uuesti. - InviteeItem + InviteDialog - - Remove - Eemalda + + Invite users to %1 + + + + + User ID to invite + Kasutajatunnus, kellele soovid kutset saata + + + + @joe:matrix.org + Example user id. The name 'joe' can be localized however you want. + + + + + Add + + + + + Invite + + + + + Cancel + @@ -744,28 +770,15 @@ Näiteks: https://server.minu:8787 Ühekordne sisselogimine ei õnnestunud - - MemberList - - - Room members - Jututoa liikmed - - - - OK - Sobib - - MessageDelegate - + Encryption enabled Krüptimine on kasutusel - + room name changed to: %1 jututoa uus nimi on: %1 @@ -775,7 +788,7 @@ Näiteks: https://server.minu:8787 eemaldas jututoa nime - + topic changed to: %1 jututoa uus teema on: %1 @@ -785,17 +798,17 @@ Näiteks: https://server.minu:8787 teema on eemaldatud - + %1 changed the room avatar %1 muutis jututoa tunnuspilti - + %1 created and configured room: %2 %1 lõi ja seadistas jututoa: %2 - + %1 placed a voice call. %1 helistas. @@ -810,23 +823,23 @@ Näiteks: https://server.minu:8787 %1 helistas. - + Negotiating call... Ühendan kõnet… - + %1 answered the call. %1 vastas kõnele. - + removed eemaldatud - + %1 ended the call. %1 lõpetas kõne. @@ -834,7 +847,7 @@ Näiteks: https://server.minu:8787 MessageInput - + Hang up Lõpeta kõne @@ -855,6 +868,11 @@ Näiteks: https://server.minu:8787 + Stickers + + + + Emoji Emoji @@ -872,17 +890,17 @@ Näiteks: https://server.minu:8787 MessageView - + Edit Muuda - + React Reageeri - + Reply Vasta @@ -892,7 +910,7 @@ Näiteks: https://server.minu:8787 Valikud - + &Copy &Kopeeri @@ -1100,7 +1118,7 @@ Näiteks: https://server.minu:8787 Placeholder - + unimplemented event: implementeerimata sündmus: @@ -1220,7 +1238,7 @@ Näiteks: https://server.minu:8787 ReplyPopup - + Close Sulge @@ -1233,7 +1251,7 @@ Näiteks: https://server.minu:8787 RoomInfo - + no version stored salvestatud versiooni ei leidu @@ -1241,7 +1259,7 @@ Näiteks: https://server.minu:8787 RoomList - + New tag Uus silt @@ -1281,17 +1299,7 @@ Näiteks: https://server.minu:8787 Loo uus silt... - - Accept - Nõustu - - - - Decline - Keeldu - - - + Status Message Olekuteade @@ -1341,20 +1349,42 @@ Näiteks: https://server.minu:8787 Kasutaja seadistused + + RoomMembers + + + Members of %1 + + + + + %n people in %1 + Summary above list of members + + + + + + + + Invite more people + + + RoomSettings - + Room Settings Jututoa seadistused - + %1 member(s) %1 liige(t) - + SETTINGS SEADISTUSED @@ -1438,11 +1468,6 @@ testimiseks seni, kuni terviklik seadmete verifitseerimine on implementeeritud.< Room Version Jututoa versioon - - - OK - Sobib - Failed to enable encryption: %1 @@ -1475,6 +1500,24 @@ testimiseks seni, kuni terviklik seadmete verifitseerimine on implementeeritud.< Viga faili üleslaadimisel: %1 + + RoomlistModel + + + Pending invite. + + + + + Previewing this room + + + + + No preview available + + + ScreenShare @@ -1532,7 +1575,7 @@ testimiseks seni, kuni terviklik seadmete verifitseerimine on implementeeritud.< StatusIndicator - + Failed Ebaõnnestus @@ -1552,6 +1595,14 @@ testimiseks seni, kuni terviklik seadmete verifitseerimine on implementeeritud.< Loetud + + StickerPicker + + + Search + Otsi + + Success @@ -1573,7 +1624,7 @@ testimiseks seni, kuni terviklik seadmete verifitseerimine on implementeeritud.< TimelineModel - + Message redaction failed: %1 Sõnumi ümbersõnastamine ebaõnnestus: %1 @@ -1584,7 +1635,7 @@ testimiseks seni, kuni terviklik seadmete verifitseerimine on implementeeritud.< Sündmuse krüptimine ei õnnestunud, katkestame saatmise! - + Save image Salvesta pilt @@ -1718,12 +1769,12 @@ testimiseks seni, kuni terviklik seadmete verifitseerimine on implementeeritud.< %1 muutis oma koputust jututoa uksele. - + You joined this room. Sa liitusid jututoaga. - + %1 has changed their avatar and changed their display name to %2. %1 muutis oma tunnuspilti ja seadistas uueks kuvatavaks nimeks %2. @@ -1752,7 +1803,7 @@ testimiseks seni, kuni terviklik seadmete verifitseerimine on implementeeritud.< TimelineRow - + Edited Muudetud @@ -1760,17 +1811,32 @@ testimiseks seni, kuni terviklik seadmete verifitseerimine on implementeeritud.< TimelineView - + No room open Ühtegi jututuba pole avatud - + %1 member(s) %1 liige(t) - + + join the conversation + + + + + accept invite + + + + + decline invite + + + + Back to room list Tagasi jututubade loendisse @@ -1778,7 +1844,7 @@ testimiseks seni, kuni terviklik seadmete verifitseerimine on implementeeritud.< TimelineViewManager - + No encrypted private chat found with this user. Create an encrypted private chat with this user and try again. Ühtegi krüptitud vestlust selle kasutajaga ei leidunud. Palun loo temaga krüptitud vestlus ja proovi uuesti. @@ -1786,18 +1852,17 @@ testimiseks seni, kuni terviklik seadmete verifitseerimine on implementeeritud.< TopBar - + Back to room list Tagasi jututubade loendisse - - + No room selected Jututuba on valimata - + Room options Jututoa valikud @@ -1838,7 +1903,7 @@ testimiseks seni, kuni terviklik seadmete verifitseerimine on implementeeritud.< UserProfile - + Global User Profile Üldine kasutajaprofiil @@ -1848,7 +1913,7 @@ testimiseks seni, kuni terviklik seadmete verifitseerimine on implementeeritud.< Kasutajaprofiil jututoas - + Verify Verifitseeri @@ -1897,7 +1962,7 @@ testimiseks seni, kuni terviklik seadmete verifitseerimine on implementeeritud.< UserSettings - + Default Vaikimisi @@ -1906,7 +1971,7 @@ testimiseks seni, kuni terviklik seadmete verifitseerimine on implementeeritud.< UserSettingsPage - + Minimize to tray Vähenda tegumiribale @@ -2363,7 +2428,7 @@ See tavaliselt tähendab, et rakenduse ikoon tegumiribal annab mingit sorti anim Waiting - + Waiting for other party… Ootan teise osapoole tegevust… @@ -2414,7 +2479,7 @@ See tavaliselt tähendab, et rakenduse ikoon tegumiribal annab mingit sorti anim descriptiveTime - + Yesterday Eile @@ -2485,19 +2550,6 @@ See tavaliselt tähendab, et rakenduse ikoon tegumiribal annab mingit sorti anim Ava kasutaja registreerimise tagavaravariant, läbi kõik sammud ja kinnita seda, kui kõik valmis on. - - dialogs::InviteUsers - - - Cancel - Tühista - - - - User ID to invite - Kasutajatunnus, kellele soovid kutset saata - - dialogs::JoinRoom diff --git a/resources/langs/nheko_fi.ts b/resources/langs/nheko_fi.ts index 9c48e98f..e884e0ac 100644 --- a/resources/langs/nheko_fi.ts +++ b/resources/langs/nheko_fi.ts @@ -38,7 +38,7 @@ AwaitingVerificationConfirmation - + Awaiting Confirmation Odotetaan vahvistusta @@ -48,7 +48,7 @@ - + Cancel Peruuta @@ -125,7 +125,7 @@ ChatPage - + Failed to invite user: %1 @@ -157,12 +157,12 @@ - + Confirm invite - + Do you really want to invite %1 (%2)? @@ -227,12 +227,12 @@ - + Do you really want to start a private chat with %1? - + Cache migration failed! @@ -352,7 +352,7 @@ CrossSigningSecrets - + Decrypt secrets @@ -426,12 +426,12 @@ EmojiPicker - + Search Hae - + People Ihmiset @@ -607,7 +607,7 @@ InputBar - + Select a file Valitse tiedosto @@ -617,17 +617,43 @@ Kaikki Tiedostot (*) - + Failed to upload media. Please try again. - InviteeItem + InviteDialog - - Remove - Poista + + Invite users to %1 + + + + + User ID to invite + Käyttäjätunnus kutsuttavaksi + + + + @joe:matrix.org + Example user id. The name 'joe' can be localized however you want. + + + + + Add + + + + + Invite + + + + + Cancel + Peruuta @@ -740,23 +766,10 @@ Example: https://server.my:8787 - - MemberList - - - Room members - Huoneen jäsenet - - - - OK - OK - - MessageDelegate - + removed @@ -767,7 +780,7 @@ Example: https://server.my:8787 Salaus on käytössä - + room name changed to: %1 huoneen nimi muutettu: %1 @@ -777,7 +790,7 @@ Example: https://server.my:8787 - + topic changed to: %1 @@ -787,17 +800,17 @@ Example: https://server.my:8787 - + %1 changed the room avatar - + %1 created and configured room: %2 - + %1 placed a voice call. @@ -812,17 +825,17 @@ Example: https://server.my:8787 %1 soitti puhelun. - + %1 answered the call. %1 vastasi puheluun. - + %1 ended the call. - + Negotiating call... @@ -830,7 +843,7 @@ Example: https://server.my:8787 MessageInput - + Hang up @@ -851,6 +864,11 @@ Example: https://server.my:8787 + Stickers + + + + Emoji Emoji @@ -868,17 +886,17 @@ Example: https://server.my:8787 MessageView - + Edit Muokkaa - + React Reagoi - + Reply Vastaa @@ -888,7 +906,7 @@ Example: https://server.my:8787 Asetukset - + &Copy @@ -1096,7 +1114,7 @@ Example: https://server.my:8787 Placeholder - + unimplemented event: @@ -1216,7 +1234,7 @@ Example: https://server.my:8787 ReplyPopup - + Close Sulje @@ -1229,7 +1247,7 @@ Example: https://server.my:8787 RoomInfo - + no version stored ei tallennettua versiota @@ -1237,7 +1255,7 @@ Example: https://server.my:8787 RoomList - + New tag @@ -1277,17 +1295,7 @@ Example: https://server.my:8787 - - Accept - Hyväksy - - - - Decline - Hylkää - - - + Status Message @@ -1337,20 +1345,42 @@ Example: https://server.my:8787 Käyttäjäasetukset + + RoomMembers + + + Members of %1 + + + + + %n people in %1 + Summary above list of members + + + + + + + + Invite more people + + + RoomSettings - + Room Settings - + %1 member(s) - + SETTINGS @@ -1432,11 +1462,6 @@ Example: https://server.my:8787 Room Version Huoneen versio - - - OK - OK - Failed to enable encryption: %1 @@ -1469,6 +1494,24 @@ Example: https://server.my:8787 Kuvan lähetys epäonnistui: %s + + RoomlistModel + + + Pending invite. + + + + + Previewing this room + + + + + No preview available + + + ScreenShare @@ -1526,7 +1569,7 @@ Example: https://server.my:8787 StatusIndicator - + Failed @@ -1546,6 +1589,14 @@ Example: https://server.my:8787 + + StickerPicker + + + Search + Hae + + Success @@ -1567,7 +1618,7 @@ Example: https://server.my:8787 TimelineModel - + Message redaction failed: %1 Viestin muokkaus epäonnistui: %1 @@ -1578,7 +1629,7 @@ Example: https://server.my:8787 - + Save image Tallenna kuva @@ -1712,12 +1763,12 @@ Example: https://server.my:8787 - + You joined this room. Sinä liityit tähän huoneeseen. - + %1 has changed their avatar and changed their display name to %2. @@ -1746,7 +1797,7 @@ Example: https://server.my:8787 TimelineRow - + Edited Muokattu @@ -1754,17 +1805,32 @@ Example: https://server.my:8787 TimelineView - + No room open - + %1 member(s) - + + join the conversation + + + + + accept invite + + + + + decline invite + + + + Back to room list @@ -1772,7 +1838,7 @@ Example: https://server.my:8787 TimelineViewManager - + No encrypted private chat found with this user. Create an encrypted private chat with this user and try again. @@ -1780,18 +1846,17 @@ Example: https://server.my:8787 TopBar - + Back to room list - - + No room selected - + Room options Huoneen asetukset @@ -1832,7 +1897,7 @@ Example: https://server.my:8787 UserProfile - + Global User Profile @@ -1842,7 +1907,7 @@ Example: https://server.my:8787 - + Verify @@ -1891,7 +1956,7 @@ Example: https://server.my:8787 UserSettings - + Default @@ -1900,7 +1965,7 @@ Example: https://server.my:8787 UserSettingsPage - + Minimize to tray Pienennä ilmoitusalueelle @@ -2346,7 +2411,7 @@ This usually causes the application icon in the task bar to animate in some fash Waiting - + Waiting for other party… @@ -2397,7 +2462,7 @@ This usually causes the application icon in the task bar to animate in some fash descriptiveTime - + Yesterday Eilen @@ -2468,19 +2533,6 @@ This usually causes the application icon in the task bar to animate in some fash - - dialogs::InviteUsers - - - Cancel - Peruuta - - - - User ID to invite - Käyttäjätunnus kutsuttavaksi - - dialogs::JoinRoom diff --git a/resources/langs/nheko_fr.ts b/resources/langs/nheko_fr.ts index b6345d62..fb3c0e11 100644 --- a/resources/langs/nheko_fr.ts +++ b/resources/langs/nheko_fr.ts @@ -38,7 +38,7 @@ AwaitingVerificationConfirmation - + Awaiting Confirmation Attente de confirmation @@ -48,7 +48,7 @@ Attente de la vérification par le correspondant. - + Cancel Annuler @@ -125,7 +125,7 @@ ChatPage - + Failed to invite user: %1 Échec lors de l'invitation de %1 @@ -157,12 +157,12 @@ - + Confirm invite Confirmer l'invitation - + Do you really want to invite %1 (%2)? Voulez-vous vraiment inviter %1 (%2) ? @@ -227,12 +227,12 @@ %1 n'est plus banni(e) - + Do you really want to start a private chat with %1? Voulez-vous vraimer commencer une discussion privée avec %1 ? - + Cache migration failed! Échec de la migration du cache ! @@ -352,7 +352,7 @@ CrossSigningSecrets - + Decrypt secrets Déchiffrer les secrets @@ -426,12 +426,12 @@ EmojiPicker - + Search Chercher - + People Personnes @@ -607,7 +607,7 @@ InputBar - + Select a file Sélectionnez un fichier @@ -617,17 +617,43 @@ Tous les types de fichiers (*) - + Failed to upload media. Please try again. Échec de l'envoi du média. Veuillez réessayer. - InviteeItem + InviteDialog - - Remove - Retirer + + Invite users to %1 + + + + + User ID to invite + Identifiant d'utilisateur à inviter + + + + @joe:matrix.org + Example user id. The name 'joe' can be localized however you want. + + + + + Add + + + + + Invite + + + + + Cancel + Annuler @@ -744,23 +770,10 @@ Exemple : https ://monserveur.example.com :8787 Échec de la connexion SSO - - MemberList - - - Room members - Membres du salon - - - - OK - OK - - MessageDelegate - + removed retiré @@ -771,7 +784,7 @@ Exemple : https ://monserveur.example.com :8787 Chiffrement activé - + room name changed to: %1 nom du salon changé en : %1 @@ -781,7 +794,7 @@ Exemple : https ://monserveur.example.com :8787 nom du salon retiré - + topic changed to: %1 sujet changé pour : %1 @@ -791,17 +804,17 @@ Exemple : https ://monserveur.example.com :8787 sujet retiré - + %1 changed the room avatar - + %1 created and configured room: %2 %1 a créé et configuré le salon : %2 - + %1 placed a voice call. %1 a effectué un appel vocal. @@ -816,17 +829,17 @@ Exemple : https ://monserveur.example.com :8787 %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… @@ -834,7 +847,7 @@ Exemple : https ://monserveur.example.com :8787 MessageInput - + Hang up Raccrocher @@ -855,6 +868,11 @@ Exemple : https ://monserveur.example.com :8787 + Stickers + + + + Emoji Émoji @@ -872,17 +890,17 @@ Exemple : https ://monserveur.example.com :8787 MessageView - + Edit Modifier - + React Réagir - + Reply Répondre @@ -892,7 +910,7 @@ Exemple : https ://monserveur.example.com :8787 Options - + &Copy @@ -1100,7 +1118,7 @@ Exemple : https ://monserveur.example.com :8787 Placeholder - + unimplemented event: Évènement non implémenté : @@ -1220,7 +1238,7 @@ Exemple : https ://monserveur.example.com :8787 ReplyPopup - + Close Fermer @@ -1233,7 +1251,7 @@ Exemple : https ://monserveur.example.com :8787 RoomInfo - + no version stored pas de version enregistrée @@ -1241,7 +1259,7 @@ Exemple : https ://monserveur.example.com :8787 RoomList - + New tag @@ -1281,17 +1299,7 @@ Exemple : https ://monserveur.example.com :8787 - - Accept - - - - - Decline - - - - + Status Message @@ -1341,20 +1349,42 @@ Exemple : https ://monserveur.example.com :8787 Paramètres utilisateur + + RoomMembers + + + Members of %1 + + + + + %n people in %1 + Summary above list of members + + + + + + + + Invite more people + + + RoomSettings - + Room Settings Configuration du salon - + %1 member(s) %1 membre(s) - + SETTINGS CONFIGURATION @@ -1438,11 +1468,6 @@ tester le chiffrement de bout en bout tant que la vérification des appareils n& Room Version Version du salon - - - OK - OK - Failed to enable encryption: %1 @@ -1475,6 +1500,24 @@ tester le chiffrement de bout en bout tant que la vérification des appareils n& Échec de l'envoi de l'image  : %s + + RoomlistModel + + + Pending invite. + + + + + Previewing this room + + + + + No preview available + + + ScreenShare @@ -1532,7 +1575,7 @@ tester le chiffrement de bout en bout tant que la vérification des appareils n& StatusIndicator - + Failed Échec @@ -1552,6 +1595,14 @@ tester le chiffrement de bout en bout tant que la vérification des appareils n& Lu + + StickerPicker + + + Search + Chercher + + Success @@ -1573,7 +1624,7 @@ tester le chiffrement de bout en bout tant que la vérification des appareils n& TimelineModel - + Message redaction failed: %1 Échec de la suppression du message : %1 @@ -1584,7 +1635,7 @@ tester le chiffrement de bout en bout tant que la vérification des appareils n& Échec du chiffrement de l'évènement, envoi abandonné ! - + Save image Enregistrer l'image @@ -1718,12 +1769,12 @@ tester le chiffrement de bout en bout tant que la vérification des appareils n& %1 ne frappe plus au salon. - + You joined this room. Vous avez rejoint ce salon. - + %1 has changed their avatar and changed their display name to %2. @@ -1752,7 +1803,7 @@ tester le chiffrement de bout en bout tant que la vérification des appareils n& TimelineRow - + Edited Modifié @@ -1760,17 +1811,32 @@ tester le chiffrement de bout en bout tant que la vérification des appareils n& TimelineView - + No room open Aucun salon ouvert - + %1 member(s) %1 membre(s) - + + join the conversation + + + + + accept invite + + + + + decline invite + + + + Back to room list Revenir à la liste des salons @@ -1778,7 +1844,7 @@ tester le chiffrement de bout en bout tant que la vérification des appareils n& TimelineViewManager - + No encrypted private chat found with this user. Create an encrypted private chat with this user and try again. Pas de discussion privée et chiffrée trouvée avec cet utilisateur. Créez-en une et réessayez. @@ -1786,18 +1852,17 @@ tester le chiffrement de bout en bout tant que la vérification des appareils n& TopBar - + Back to room list Revenir à la liste des salons - - + No room selected Pas de salon sélectionné - + Room options Options du salon @@ -1838,7 +1903,7 @@ tester le chiffrement de bout en bout tant que la vérification des appareils n& UserProfile - + Global User Profile Profil général de l'utilisateur @@ -1848,7 +1913,7 @@ tester le chiffrement de bout en bout tant que la vérification des appareils n& Profil utilisateur spécifique au salon - + Verify Vérifier @@ -1897,7 +1962,7 @@ tester le chiffrement de bout en bout tant que la vérification des appareils n& UserSettings - + Default Défaut @@ -1906,7 +1971,7 @@ tester le chiffrement de bout en bout tant que la vérification des appareils n& UserSettingsPage - + Minimize to tray Réduire à la barre des tâches @@ -2365,7 +2430,7 @@ Cela met l'application en évidence dans la barre des tâches. Waiting - + Waiting for other party… Attente du correspondant… @@ -2416,7 +2481,7 @@ Cela met l'application en évidence dans la barre des tâches. descriptiveTime - + Yesterday Hier @@ -2487,19 +2552,6 @@ Cela met l'application en évidence dans la barre des tâches.Ouvrez la solution de repli, suivez les étapes et confirmez après les avoir terminées. - - dialogs::InviteUsers - - - Cancel - Annuler - - - - User ID to invite - Identifiant d'utilisateur à inviter - - dialogs::JoinRoom diff --git a/resources/langs/nheko_hu.ts b/resources/langs/nheko_hu.ts index e989a6ce..a85f9ff3 100644 --- a/resources/langs/nheko_hu.ts +++ b/resources/langs/nheko_hu.ts @@ -38,7 +38,7 @@ AwaitingVerificationConfirmation - + Awaiting Confirmation Várakozás megerősítésre @@ -48,7 +48,7 @@ Várakozás a másik oldalra a hitelesítés befejezéséhez. - + Cancel Mégse @@ -125,7 +125,7 @@ ChatPage - + Failed to invite user: %1 Nem sikerült meghívni a felhasználót: %1 @@ -157,12 +157,12 @@ - + Confirm invite Meghívás megerősítése - + Do you really want to invite %1 (%2)? Biztos, hogy meg akarod hívni a következő felhasználót: %1 (%2)? @@ -227,12 +227,12 @@ Kitiltás feloldva a felhasználónak: %1 - + Do you really want to start a private chat with %1? Biztosan privát csevegést akarsz indítani %1 felhasználóval? - + Cache migration failed! Gyorsítótár migráció nem sikerült! @@ -352,7 +352,7 @@ CrossSigningSecrets - + Decrypt secrets Titkos tároló feloldása @@ -426,12 +426,12 @@ EmojiPicker - + Search Keresés - + People Emberek @@ -607,7 +607,7 @@ InputBar - + Select a file Fájl kiválasztása @@ -617,17 +617,43 @@ Minden fájl (*) - + Failed to upload media. Please try again. Nem sikerült feltölteni a médiafájlt. Kérlek, próbáld újra! - InviteeItem + InviteDialog - - Remove - Eltávolítás + + Invite users to %1 + + + + + User ID to invite + Meghívandó felhasználó azonosítója + + + + @joe:matrix.org + Example user id. The name 'joe' can be localized however you want. + + + + + Add + + + + + Invite + + + + + Cancel + Mégse @@ -744,28 +770,15 @@ Példa: https://szerver.em:8787 SSO bejelentkezés nem sikerült - - MemberList - - - Room members - Szobatagok - - - - OK - OK - - MessageDelegate - + Encryption enabled Titkosítás bekapcsolva - + room name changed to: %1 a szoba neve megváltoztatva erre: %1 @@ -775,7 +788,7 @@ Példa: https://szerver.em:8787 szobanév eltávolítva - + topic changed to: %1 a téma megváltoztatva erre: %1 @@ -785,17 +798,17 @@ Példa: https://szerver.em:8787 téma eltávolítva - + %1 changed the room avatar - + %1 created and configured room: %2 %1 létrehozta és beállította a következő szobát: %2 - + %1 placed a voice call. %1 hanghívást kezdeményezett. @@ -810,23 +823,23 @@ Példa: https://szerver.em:8787 %1 hívást kezdeményezett. - + Negotiating call... Hívás előkészítése… - + %1 answered the call. %1 fogadta a hívást. - + removed eltávolítva - + %1 ended the call. %1 befejezte a hívást. @@ -834,7 +847,7 @@ Példa: https://szerver.em:8787 MessageInput - + Hang up Hívás befejezése @@ -855,6 +868,11 @@ Példa: https://szerver.em:8787 + Stickers + + + + Emoji Hangulatjelek @@ -872,17 +890,17 @@ Példa: https://szerver.em:8787 MessageView - + Edit Szerkesztés - + React Reakció - + Reply Válasz @@ -892,7 +910,7 @@ Példa: https://szerver.em:8787 Műveletek - + &Copy @@ -1100,7 +1118,7 @@ Példa: https://szerver.em:8787 Placeholder - + unimplemented event: nem implementált esemény: @@ -1220,7 +1238,7 @@ Példa: https://szerver.em:8787 ReplyPopup - + Close Bezárás @@ -1233,7 +1251,7 @@ Példa: https://szerver.em:8787 RoomInfo - + no version stored nincs tárolva verzió @@ -1241,7 +1259,7 @@ Példa: https://szerver.em:8787 RoomList - + New tag @@ -1281,17 +1299,7 @@ Példa: https://szerver.em:8787 - - Accept - Elfogadás - - - - Decline - Elutasítás - - - + Status Message @@ -1341,20 +1349,41 @@ Példa: https://szerver.em:8787 Felhasználói beállítások + + RoomMembers + + + Members of %1 + + + + + %n people in %1 + Summary above list of members + + + + + + + Invite more people + + + RoomSettings - + Room Settings Szobabeállítások - + %1 member(s) %1 tag - + SETTINGS BEÁLLÍTÁSOK @@ -1438,11 +1467,6 @@ végpontig (E2E) titkosítás tesztelésére, amíg be nincs fejezve az eszközh Room Version Szoba verziója - - - OK - OK - Failed to enable encryption: %1 @@ -1475,6 +1499,24 @@ végpontig (E2E) titkosítás tesztelésére, amíg be nincs fejezve az eszközh Nem sikerült a kép feltöltése: %s + + RoomlistModel + + + Pending invite. + + + + + Previewing this room + + + + + No preview available + + + ScreenShare @@ -1532,7 +1574,7 @@ végpontig (E2E) titkosítás tesztelésére, amíg be nincs fejezve az eszközh StatusIndicator - + Failed Sikertelen @@ -1552,6 +1594,14 @@ végpontig (E2E) titkosítás tesztelésére, amíg be nincs fejezve az eszközh Elolvasva + + StickerPicker + + + Search + Keresés + + Success @@ -1573,7 +1623,7 @@ végpontig (E2E) titkosítás tesztelésére, amíg be nincs fejezve az eszközh TimelineModel - + Message redaction failed: %1 Az üzenet visszavonása nem sikerült: %1 @@ -1584,7 +1634,7 @@ végpontig (E2E) titkosítás tesztelésére, amíg be nincs fejezve az eszközh Nem sikerült titkosítani az eseményt, küldés megszakítva! - + Save image Kép mentése @@ -1717,12 +1767,12 @@ végpontig (E2E) titkosítás tesztelésére, amíg be nincs fejezve az eszközh %1 visszavonta a kopogását. - + You joined this room. Csatlakoztál ehhez a szobához. - + %1 has changed their avatar and changed their display name to %2. @@ -1751,7 +1801,7 @@ végpontig (E2E) titkosítás tesztelésére, amíg be nincs fejezve az eszközh TimelineRow - + Edited Szerkesztve @@ -1759,17 +1809,32 @@ végpontig (E2E) titkosítás tesztelésére, amíg be nincs fejezve az eszközh TimelineView - + No room open Nincs nyitott szoba - + %1 member(s) %1 tag - + + join the conversation + + + + + accept invite + + + + + decline invite + + + + Back to room list Vissza a szobák listájára @@ -1777,7 +1842,7 @@ végpontig (E2E) titkosítás tesztelésére, amíg be nincs fejezve az eszközh TimelineViewManager - + No encrypted private chat found with this user. Create an encrypted private chat with this user and try again. Nem található titkosított privát csevegés ezzel a felhasználóval. Hozz létre egy titkosított privát csevegést vele, és próbáld újra! @@ -1785,18 +1850,17 @@ végpontig (E2E) titkosítás tesztelésére, amíg be nincs fejezve az eszközh TopBar - + Back to room list Vissza a szobák listájára - - + No room selected Nincs kiválasztva szoba - + Room options Szoba beállításai @@ -1837,7 +1901,7 @@ végpontig (E2E) titkosítás tesztelésére, amíg be nincs fejezve az eszközh UserProfile - + Global User Profile Globális felhasználói profil @@ -1847,7 +1911,7 @@ végpontig (E2E) titkosítás tesztelésére, amíg be nincs fejezve az eszközh Szobai felhasználói profil - + Verify Hitelesítés @@ -1896,7 +1960,7 @@ végpontig (E2E) titkosítás tesztelésére, amíg be nincs fejezve az eszközh UserSettings - + Default Alapértelmezett @@ -1905,7 +1969,7 @@ végpontig (E2E) titkosítás tesztelésére, amíg be nincs fejezve az eszközh UserSettingsPage - + Minimize to tray Kicsinyítés a tálcára @@ -2363,7 +2427,7 @@ Ettől általában animálttá válik az alkalmazásablakok listáján szereplő Waiting - + Waiting for other party… Várakozás a másik félre… @@ -2414,7 +2478,7 @@ Ettől általában animálttá válik az alkalmazásablakok listáján szereplő descriptiveTime - + Yesterday Tegnap @@ -2485,19 +2549,6 @@ Ettől általában animálttá válik az alkalmazásablakok listáján szereplő Nyisd meg a fallback-ket, kövesd az utasításokat, és erősítsd meg, ha végeztél velük! - - dialogs::InviteUsers - - - Cancel - Mégse - - - - User ID to invite - Meghívandó felhasználó azonosítója - - dialogs::JoinRoom diff --git a/resources/langs/nheko_it.ts b/resources/langs/nheko_it.ts index b0b8ec48..6056b31a 100644 --- a/resources/langs/nheko_it.ts +++ b/resources/langs/nheko_it.ts @@ -38,7 +38,7 @@ AwaitingVerificationConfirmation - + Awaiting Confirmation In attesa di conferma @@ -48,7 +48,7 @@ In attesa della conferma dall'altra parte per la verifica. - + Cancel Annulla @@ -125,7 +125,7 @@ ChatPage - + Failed to invite user: %1 Impossibile invitare l'utente: %1 @@ -157,12 +157,12 @@ - + Confirm invite Conferma Invito - + Do you really want to invite %1 (%2)? Vuoi davvero inviare %1 (%2)? @@ -227,12 +227,12 @@ Rimosso il ban dall'utente: %1 - + Do you really want to start a private chat with %1? Sei sicuro di voler avviare una chat privata con %1? - + Cache migration failed! Migrazione della cache fallita! @@ -352,7 +352,7 @@ CrossSigningSecrets - + Decrypt secrets Decifra i segreti @@ -426,12 +426,12 @@ EmojiPicker - + Search Cerca - + People Membri @@ -607,7 +607,7 @@ InputBar - + Select a file Seleziona un file @@ -617,17 +617,43 @@ Tutti i File (*) - + Failed to upload media. Please try again. Impossibile inviare il file multimediale. Per favore riprova. - InviteeItem + InviteDialog - - Remove - Rimuovi + + Invite users to %1 + + + + + User ID to invite + ID utente da invitare + + + + @joe:matrix.org + Example user id. The name 'joe' can be localized however you want. + + + + + Add + + + + + Invite + + + + + Cancel + Annulla @@ -744,23 +770,10 @@ Esempio: https://server.mio:8787 Accesso SSO fallito - - MemberList - - - Room members - Membri della stanza - - - - OK - OK - - MessageDelegate - + removed rimosso @@ -771,7 +784,7 @@ Esempio: https://server.mio:8787 Crittografia abilitata - + room name changed to: %1 nome della stanza cambiato in: %1 @@ -781,7 +794,7 @@ Esempio: https://server.mio:8787 nome della stanza rimosso - + topic changed to: %1 argomento cambiato in: %1 @@ -791,17 +804,17 @@ Esempio: https://server.mio:8787 argomento rimosso - + %1 changed the room avatar - + %1 created and configured room: %2 %1 creato e configurata stanza: %2 - + %1 placed a voice call. %1 ha avviato una chiamata audio. @@ -816,17 +829,17 @@ Esempio: https://server.mio:8787 - + %1 answered the call. %1 ha risposto alla chiamata. - + %1 ended the call. %1 ha terminato la chiamata. - + Negotiating call... @@ -834,7 +847,7 @@ Esempio: https://server.mio:8787 MessageInput - + Hang up Termina @@ -855,6 +868,11 @@ Esempio: https://server.mio:8787 + Stickers + + + + Emoji Emoji @@ -872,17 +890,17 @@ Esempio: https://server.mio:8787 MessageView - + Edit Modifica - + React Reagisci - + Reply Risposta @@ -892,7 +910,7 @@ Esempio: https://server.mio:8787 Opzioni - + &Copy @@ -1101,7 +1119,7 @@ Verificare %1 adesso? Placeholder - + unimplemented event: evento non implementato: @@ -1221,7 +1239,7 @@ Verificare %1 adesso? ReplyPopup - + Close Chiudi @@ -1234,7 +1252,7 @@ Verificare %1 adesso? RoomInfo - + no version stored nessuna versione memorizzata @@ -1242,7 +1260,7 @@ Verificare %1 adesso? RoomList - + New tag @@ -1282,17 +1300,7 @@ Verificare %1 adesso? - - Accept - Accetta - - - - Decline - Rifiuta - - - + Status Message @@ -1342,20 +1350,42 @@ Verificare %1 adesso? Impostazioni utente + + RoomMembers + + + Members of %1 + + + + + %n people in %1 + Summary above list of members + + + + + + + + Invite more people + + + RoomSettings - + Room Settings - + %1 member(s) - + SETTINGS @@ -1437,11 +1467,6 @@ Verificare %1 adesso? Room Version - - - OK - OK - Failed to enable encryption: %1 @@ -1474,6 +1499,24 @@ Verificare %1 adesso? Impossibile fare l'upload dell'immagine: %s + + RoomlistModel + + + Pending invite. + + + + + Previewing this room + + + + + No preview available + + + ScreenShare @@ -1531,7 +1574,7 @@ Verificare %1 adesso? StatusIndicator - + Failed Fallito @@ -1551,6 +1594,14 @@ Verificare %1 adesso? Letto + + StickerPicker + + + Search + Cerca + + Success @@ -1572,7 +1623,7 @@ Verificare %1 adesso? TimelineModel - + Message redaction failed: %1 Oscuramento del messaggio fallito: %1 @@ -1583,7 +1634,7 @@ Verificare %1 adesso? - + Save image Salva immagine @@ -1717,12 +1768,12 @@ Verificare %1 adesso? %1 ha oscurato la sua bussata. - + You joined this room. Sei entrato in questa stanza. - + %1 has changed their avatar and changed their display name to %2. @@ -1751,7 +1802,7 @@ Verificare %1 adesso? TimelineRow - + Edited @@ -1759,17 +1810,32 @@ Verificare %1 adesso? TimelineView - + No room open Nessuna stanza aperta - + %1 member(s) - + + join the conversation + + + + + accept invite + + + + + decline invite + + + + Back to room list @@ -1777,7 +1843,7 @@ Verificare %1 adesso? TimelineViewManager - + No encrypted private chat found with this user. Create an encrypted private chat with this user and try again. @@ -1785,18 +1851,17 @@ Verificare %1 adesso? TopBar - + Back to room list - - + No room selected - + Room options Opzioni della stanza @@ -1837,7 +1902,7 @@ Verificare %1 adesso? UserProfile - + Global User Profile @@ -1847,7 +1912,7 @@ Verificare %1 adesso? - + Verify @@ -1896,7 +1961,7 @@ Verificare %1 adesso? UserSettings - + Default @@ -1905,7 +1970,7 @@ Verificare %1 adesso? UserSettingsPage - + Minimize to tray Minimizza nella tray @@ -2351,7 +2416,7 @@ This usually causes the application icon in the task bar to animate in some fash Waiting - + Waiting for other party… @@ -2402,7 +2467,7 @@ This usually causes the application icon in the task bar to animate in some fash descriptiveTime - + Yesterday Ieri @@ -2473,19 +2538,6 @@ This usually causes the application icon in the task bar to animate in some fash Apri il ripiego, segui i passaggi e conferma dopo averli completati. - - dialogs::InviteUsers - - - Cancel - Annulla - - - - User ID to invite - ID utente da invitare - - dialogs::JoinRoom diff --git a/resources/langs/nheko_ja.ts b/resources/langs/nheko_ja.ts index c7872ce0..a8c18795 100644 --- a/resources/langs/nheko_ja.ts +++ b/resources/langs/nheko_ja.ts @@ -38,7 +38,7 @@ AwaitingVerificationConfirmation - + Awaiting Confirmation @@ -48,7 +48,7 @@ - + Cancel キャンセル @@ -125,7 +125,7 @@ ChatPage - + Failed to invite user: %1 ユーザーを招待できませんでした: %1 @@ -157,12 +157,12 @@ - + Confirm invite - + Do you really want to invite %1 (%2)? @@ -227,12 +227,12 @@ 永久追放を解除されたユーザー: %1 - + Do you really want to start a private chat with %1? - + Cache migration failed! @@ -352,7 +352,7 @@ CrossSigningSecrets - + Decrypt secrets @@ -426,12 +426,12 @@ EmojiPicker - + Search - + People @@ -607,7 +607,7 @@ InputBar - + Select a file ファイルを選択 @@ -617,17 +617,43 @@ 全てのファイル (*) - + Failed to upload media. Please try again. メディアをアップロードできませんでした。やり直して下さい。 - InviteeItem + InviteDialog - - Remove - 削除 + + Invite users to %1 + + + + + User ID to invite + 招待するユーザーのID + + + + @joe:matrix.org + Example user id. The name 'joe' can be localized however you want. + + + + + Add + + + + + Invite + + + + + Cancel + キャンセル @@ -740,23 +766,10 @@ Example: https://server.my:8787 - - MemberList - - - Room members - 部屋の参加者 - - - - OK - OK - - MessageDelegate - + removed @@ -767,7 +780,7 @@ Example: https://server.my:8787 暗号化が有効です - + room name changed to: %1 部屋名が変更されました: %1 @@ -777,7 +790,7 @@ Example: https://server.my:8787 部屋名が削除されました - + topic changed to: %1 話題が変更されました: %1 @@ -787,17 +800,17 @@ Example: https://server.my:8787 話題が削除されました - + %1 changed the room avatar - + %1 created and configured room: %2 - + %1 placed a voice call. @@ -812,17 +825,17 @@ Example: https://server.my:8787 - + %1 answered the call. - + %1 ended the call. - + Negotiating call... @@ -830,7 +843,7 @@ Example: https://server.my:8787 MessageInput - + Hang up @@ -851,6 +864,11 @@ Example: https://server.my:8787 + Stickers + + + + Emoji 絵文字 @@ -868,17 +886,17 @@ Example: https://server.my:8787 MessageView - + Edit - + React - + Reply 返信 @@ -888,7 +906,7 @@ Example: https://server.my:8787 オプション - + &Copy @@ -1096,7 +1114,7 @@ Example: https://server.my:8787 Placeholder - + unimplemented event: 未実装のイベント: @@ -1216,7 +1234,7 @@ Example: https://server.my:8787 ReplyPopup - + Close 閉じる @@ -1229,7 +1247,7 @@ Example: https://server.my:8787 RoomInfo - + no version stored バージョンが保存されていません @@ -1237,7 +1255,7 @@ Example: https://server.my:8787 RoomList - + New tag @@ -1277,17 +1295,7 @@ Example: https://server.my:8787 - - Accept - 容認 - - - - Decline - 拒否 - - - + Status Message @@ -1337,20 +1345,41 @@ Example: https://server.my:8787 ユーザー設定 + + RoomMembers + + + Members of %1 + + + + + %n people in %1 + Summary above list of members + + + + + + + Invite more people + + + RoomSettings - + Room Settings - + %1 member(s) - + SETTINGS @@ -1432,11 +1461,6 @@ Example: https://server.my:8787 Room Version - - - OK - OK - Failed to enable encryption: %1 @@ -1469,6 +1493,24 @@ Example: https://server.my:8787 画像をアップロードできませんでした: %s + + RoomlistModel + + + Pending invite. + + + + + Previewing this room + + + + + No preview available + + + ScreenShare @@ -1526,7 +1568,7 @@ Example: https://server.my:8787 StatusIndicator - + Failed 失敗 @@ -1546,6 +1588,14 @@ Example: https://server.my:8787 既読 + + StickerPicker + + + Search + + + Success @@ -1567,7 +1617,7 @@ Example: https://server.my:8787 TimelineModel - + Message redaction failed: %1 メッセージを編集できませんでした: %1 @@ -1578,7 +1628,7 @@ Example: https://server.my:8787 - + Save image 画像を保存 @@ -1711,12 +1761,12 @@ Example: https://server.my:8787 %1がノックを編集しました。 - + You joined this room. - + %1 has changed their avatar and changed their display name to %2. @@ -1745,7 +1795,7 @@ Example: https://server.my:8787 TimelineRow - + Edited @@ -1753,17 +1803,32 @@ Example: https://server.my:8787 TimelineView - + No room open 部屋が開いていません - + %1 member(s) - + + join the conversation + + + + + accept invite + + + + + decline invite + + + + Back to room list @@ -1771,7 +1836,7 @@ Example: https://server.my:8787 TimelineViewManager - + No encrypted private chat found with this user. Create an encrypted private chat with this user and try again. @@ -1779,18 +1844,17 @@ Example: https://server.my:8787 TopBar - + Back to room list - - + No room selected - + Room options 部屋のオプション @@ -1831,7 +1895,7 @@ Example: https://server.my:8787 UserProfile - + Global User Profile @@ -1841,7 +1905,7 @@ Example: https://server.my:8787 - + Verify @@ -1890,7 +1954,7 @@ Example: https://server.my:8787 UserSettings - + Default @@ -1899,7 +1963,7 @@ Example: https://server.my:8787 UserSettingsPage - + Minimize to tray トレイへ最小化 @@ -2345,7 +2409,7 @@ This usually causes the application icon in the task bar to animate in some fash Waiting - + Waiting for other party… @@ -2396,7 +2460,7 @@ This usually causes the application icon in the task bar to animate in some fash descriptiveTime - + Yesterday 昨日 @@ -2467,19 +2531,6 @@ This usually causes the application icon in the task bar to animate in some fash - - dialogs::InviteUsers - - - Cancel - キャンセル - - - - User ID to invite - 招待するユーザーのID - - dialogs::JoinRoom diff --git a/resources/langs/nheko_ml.ts b/resources/langs/nheko_ml.ts index 0bdf3b63..aeb704eb 100644 --- a/resources/langs/nheko_ml.ts +++ b/resources/langs/nheko_ml.ts @@ -38,7 +38,7 @@ AwaitingVerificationConfirmation - + Awaiting Confirmation സ്ഥിരീകരണത്തിനായി കാത്തിരിക്കുന്നു @@ -48,7 +48,7 @@ - + Cancel റദ്ദാക്കു @@ -125,7 +125,7 @@ ChatPage - + Failed to invite user: %1 ഉപയോക്താവിനെ ക്ഷണിക്കുന്നതിൽ പരാജയപ്പെട്ടു: %1 @@ -157,12 +157,12 @@ - + Confirm invite ക്ഷണം ഉറപ്പാക്കു - + Do you really want to invite %1 (%2)? @@ -227,12 +227,12 @@ - + Do you really want to start a private chat with %1? - + Cache migration failed! @@ -352,7 +352,7 @@ CrossSigningSecrets - + Decrypt secrets @@ -426,12 +426,12 @@ EmojiPicker - + Search തിരയുക - + People ആളുകൾ @@ -607,7 +607,7 @@ InputBar - + Select a file ഒരു ഫയൽ തിരഞ്ഞെടുക്കുക @@ -617,17 +617,43 @@ - + Failed to upload media. Please try again. - InviteeItem + InviteDialog - - Remove - നീക്കംചെയ്യുക + + Invite users to %1 + + + + + User ID to invite + + + + + @joe:matrix.org + Example user id. The name 'joe' can be localized however you want. + + + + + Add + + + + + Invite + + + + + Cancel + റദ്ദാക്കു @@ -740,28 +766,15 @@ Example: https://server.my:8787 - - MemberList - - - Room members - - - - - OK - ശരി - - MessageDelegate - + Encryption enabled - + room name changed to: %1 @@ -771,7 +784,7 @@ Example: https://server.my:8787 - + topic changed to: %1 @@ -781,17 +794,17 @@ Example: https://server.my:8787 - + %1 changed the room avatar - + %1 created and configured room: %2 - + %1 placed a voice call. @@ -806,23 +819,23 @@ Example: https://server.my:8787 - + Negotiating call... - + %1 answered the call. - + removed നീക്കംചെയ്‌തു - + %1 ended the call. @@ -830,7 +843,7 @@ Example: https://server.my:8787 MessageInput - + Hang up @@ -851,6 +864,11 @@ Example: https://server.my:8787 + Stickers + + + + Emoji ഇമോജി @@ -868,17 +886,17 @@ Example: https://server.my:8787 MessageView - + Edit - + React - + Reply @@ -888,7 +906,7 @@ Example: https://server.my:8787 - + &Copy @@ -1096,7 +1114,7 @@ Example: https://server.my:8787 Placeholder - + unimplemented event: @@ -1216,7 +1234,7 @@ Example: https://server.my:8787 ReplyPopup - + Close അടയ്‌ക്കുക @@ -1229,7 +1247,7 @@ Example: https://server.my:8787 RoomInfo - + no version stored @@ -1237,7 +1255,7 @@ Example: https://server.my:8787 RoomList - + New tag @@ -1277,17 +1295,7 @@ Example: https://server.my:8787 - - Accept - - - - - Decline - നിരസിക്കുക - - - + Status Message @@ -1337,20 +1345,42 @@ Example: https://server.my:8787 + + RoomMembers + + + Members of %1 + + + + + %n people in %1 + Summary above list of members + + + + + + + + Invite more people + + + RoomSettings - + Room Settings - + %1 member(s) - + SETTINGS @@ -1432,11 +1462,6 @@ Example: https://server.my:8787 Room Version - - - OK - ശരി - Failed to enable encryption: %1 @@ -1469,6 +1494,24 @@ Example: https://server.my:8787 + + RoomlistModel + + + Pending invite. + + + + + Previewing this room + + + + + No preview available + + + ScreenShare @@ -1526,7 +1569,7 @@ Example: https://server.my:8787 StatusIndicator - + Failed @@ -1546,6 +1589,14 @@ Example: https://server.my:8787 + + StickerPicker + + + Search + തിരയുക + + Success @@ -1567,7 +1618,7 @@ Example: https://server.my:8787 TimelineModel - + Message redaction failed: %1 @@ -1578,7 +1629,7 @@ Example: https://server.my:8787 - + Save image @@ -1712,12 +1763,12 @@ Example: https://server.my:8787 - + You joined this room. നിങ്ങൾ ഈ മുറിയിൽ ചേർന്നു. - + %1 has changed their avatar and changed their display name to %2. @@ -1746,7 +1797,7 @@ Example: https://server.my:8787 TimelineRow - + Edited @@ -1754,17 +1805,32 @@ Example: https://server.my:8787 TimelineView - + No room open - + %1 member(s) - + + join the conversation + + + + + accept invite + + + + + decline invite + + + + Back to room list @@ -1772,7 +1838,7 @@ Example: https://server.my:8787 TimelineViewManager - + No encrypted private chat found with this user. Create an encrypted private chat with this user and try again. @@ -1780,18 +1846,17 @@ Example: https://server.my:8787 TopBar - + Back to room list - - + No room selected - + Room options @@ -1832,7 +1897,7 @@ Example: https://server.my:8787 UserProfile - + Global User Profile @@ -1842,7 +1907,7 @@ Example: https://server.my:8787 - + Verify @@ -1891,7 +1956,7 @@ Example: https://server.my:8787 UserSettings - + Default @@ -1900,7 +1965,7 @@ Example: https://server.my:8787 UserSettingsPage - + Minimize to tray @@ -2346,7 +2411,7 @@ This usually causes the application icon in the task bar to animate in some fash Waiting - + Waiting for other party… @@ -2397,7 +2462,7 @@ This usually causes the application icon in the task bar to animate in some fash descriptiveTime - + Yesterday @@ -2468,19 +2533,6 @@ This usually causes the application icon in the task bar to animate in some fash - - dialogs::InviteUsers - - - Cancel - റദ്ദാക്കു - - - - User ID to invite - - - dialogs::JoinRoom diff --git a/resources/langs/nheko_nl.ts b/resources/langs/nheko_nl.ts index 3f2a147f..ba3ceec1 100644 --- a/resources/langs/nheko_nl.ts +++ b/resources/langs/nheko_nl.ts @@ -38,7 +38,7 @@ AwaitingVerificationConfirmation - + Awaiting Confirmation @@ -48,7 +48,7 @@ - + Cancel Annuleren @@ -125,7 +125,7 @@ ChatPage - + Failed to invite user: %1 Gebruiker uitnodigen mislukt: %1 @@ -157,12 +157,12 @@ - + Confirm invite - + Do you really want to invite %1 (%2)? @@ -227,12 +227,12 @@ - + Do you really want to start a private chat with %1? - + Cache migration failed! @@ -352,7 +352,7 @@ CrossSigningSecrets - + Decrypt secrets @@ -426,12 +426,12 @@ EmojiPicker - + Search - + People @@ -607,7 +607,7 @@ InputBar - + Select a file Kies een bestand @@ -617,18 +617,44 @@ Alle bestanden (*) - + Failed to upload media. Please try again. - InviteeItem + InviteDialog - - Remove + + Invite users to %1 + + + User ID to invite + Uit te nodigen gebruikers-id + + + + @joe:matrix.org + Example user id. The name 'joe' can be localized however you want. + + + + + Add + + + + + Invite + + + + + Cancel + Annuleren + LoginPage @@ -740,23 +766,10 @@ Example: https://server.my:8787 - - MemberList - - - Room members - Kamerleden - - - - OK - - - MessageDelegate - + removed @@ -767,7 +780,7 @@ Example: https://server.my:8787 - + room name changed to: %1 @@ -777,7 +790,7 @@ Example: https://server.my:8787 - + topic changed to: %1 @@ -787,17 +800,17 @@ Example: https://server.my:8787 - + %1 changed the room avatar - + %1 created and configured room: %2 - + %1 placed a voice call. @@ -812,17 +825,17 @@ Example: https://server.my:8787 - + %1 answered the call. - + %1 ended the call. - + Negotiating call... @@ -830,7 +843,7 @@ Example: https://server.my:8787 MessageInput - + Hang up @@ -851,6 +864,11 @@ Example: https://server.my:8787 + Stickers + + + + Emoji @@ -868,17 +886,17 @@ Example: https://server.my:8787 MessageView - + Edit - + React - + Reply @@ -888,7 +906,7 @@ Example: https://server.my:8787 - + &Copy @@ -1096,7 +1114,7 @@ Example: https://server.my:8787 Placeholder - + unimplemented event: @@ -1216,7 +1234,7 @@ Example: https://server.my:8787 ReplyPopup - + Close @@ -1229,7 +1247,7 @@ Example: https://server.my:8787 RoomInfo - + no version stored @@ -1237,7 +1255,7 @@ Example: https://server.my:8787 RoomList - + New tag @@ -1277,17 +1295,7 @@ Example: https://server.my:8787 - - Accept - Accepteren - - - - Decline - Afwijzen - - - + Status Message @@ -1337,20 +1345,42 @@ Example: https://server.my:8787 + + RoomMembers + + + Members of %1 + + + + + %n people in %1 + Summary above list of members + + + + + + + + Invite more people + + + RoomSettings - + Room Settings - + %1 member(s) - + SETTINGS @@ -1432,11 +1462,6 @@ Example: https://server.my:8787 Room Version - - - OK - - Failed to enable encryption: %1 @@ -1469,6 +1494,24 @@ Example: https://server.my:8787 + + RoomlistModel + + + Pending invite. + + + + + Previewing this room + + + + + No preview available + + + ScreenShare @@ -1526,7 +1569,7 @@ Example: https://server.my:8787 StatusIndicator - + Failed @@ -1546,6 +1589,14 @@ Example: https://server.my:8787 + + StickerPicker + + + Search + + + Success @@ -1567,7 +1618,7 @@ Example: https://server.my:8787 TimelineModel - + Message redaction failed: %1 @@ -1578,7 +1629,7 @@ Example: https://server.my:8787 - + Save image Afbeelding opslaan @@ -1712,12 +1763,12 @@ Example: https://server.my:8787 - + You joined this room. Je bent lid geworden van deze kamer. - + %1 has changed their avatar and changed their display name to %2. @@ -1746,7 +1797,7 @@ Example: https://server.my:8787 TimelineRow - + Edited @@ -1754,17 +1805,32 @@ Example: https://server.my:8787 TimelineView - + No room open - + %1 member(s) - + + join the conversation + + + + + accept invite + + + + + decline invite + + + + Back to room list @@ -1772,7 +1838,7 @@ Example: https://server.my:8787 TimelineViewManager - + No encrypted private chat found with this user. Create an encrypted private chat with this user and try again. @@ -1780,18 +1846,17 @@ Example: https://server.my:8787 TopBar - + Back to room list - - + No room selected - + Room options @@ -1832,7 +1897,7 @@ Example: https://server.my:8787 UserProfile - + Global User Profile @@ -1842,7 +1907,7 @@ Example: https://server.my:8787 - + Verify @@ -1891,7 +1956,7 @@ Example: https://server.my:8787 UserSettings - + Default @@ -1900,7 +1965,7 @@ Example: https://server.my:8787 UserSettingsPage - + Minimize to tray Minimaliseren naar systeemvak @@ -2346,7 +2411,7 @@ This usually causes the application icon in the task bar to animate in some fash Waiting - + Waiting for other party… @@ -2397,7 +2462,7 @@ This usually causes the application icon in the task bar to animate in some fash descriptiveTime - + Yesterday @@ -2468,19 +2533,6 @@ This usually causes the application icon in the task bar to animate in some fash - - dialogs::InviteUsers - - - Cancel - Annuleren - - - - User ID to invite - Uit te nodigen gebruikers-id - - dialogs::JoinRoom diff --git a/resources/langs/nheko_pl.ts b/resources/langs/nheko_pl.ts index 6abcd147..4b6c31f2 100644 --- a/resources/langs/nheko_pl.ts +++ b/resources/langs/nheko_pl.ts @@ -38,7 +38,7 @@ AwaitingVerificationConfirmation - + Awaiting Confirmation Oczekiwanie na potwierdzenie @@ -48,7 +48,7 @@ Oczekiwanie na dokończenie weryfikacji przez drugą stronę. - + Cancel Anuluj @@ -125,7 +125,7 @@ ChatPage - + Failed to invite user: %1 Nie udało się zaprosić użytkownika: %1 @@ -157,12 +157,12 @@ - + Confirm invite - + Do you really want to invite %1 (%2)? Czy na pewno chcesz zaprosić %1 (%2)? @@ -227,12 +227,12 @@ Odblokowano użytkownika: %1 - + Do you really want to start a private chat with %1? - + Cache migration failed! Nie udało się przenieść pamięci podręcznej! @@ -352,7 +352,7 @@ CrossSigningSecrets - + Decrypt secrets @@ -426,12 +426,12 @@ EmojiPicker - + Search Szukaj - + People Ludzie @@ -607,7 +607,7 @@ InputBar - + Select a file Wybierz plik @@ -617,17 +617,43 @@ Wszystkie pliki (*) - + Failed to upload media. Please try again. - InviteeItem + InviteDialog - - Remove - Usuń + + Invite users to %1 + + + + + User ID to invite + ID użytkownika do zaproszenia + + + + @joe:matrix.org + Example user id. The name 'joe' can be localized however you want. + + + + + Add + + + + + Invite + + + + + Cancel + Anuluj @@ -742,23 +768,10 @@ Example: https://server.my:8787 Logowanie SSO zakończone niepowodzeniem - - MemberList - - - Room members - Członkowie pokoju - - - - OK - OK - - MessageDelegate - + removed @@ -769,7 +782,7 @@ Example: https://server.my:8787 Szyfrowanie włączone - + room name changed to: %1 Nazwa pokoju zmieniona na: %1 @@ -779,7 +792,7 @@ Example: https://server.my:8787 usunięto nazwę pokoju - + topic changed to: %1 temat zmieniono na: %1 @@ -789,17 +802,17 @@ Example: https://server.my:8787 usunięto temat - + %1 changed the room avatar - + %1 created and configured room: %2 %1 utworzył i skonfigurował pokój: %2 - + %1 placed a voice call. %1 rozpoczął(-ęła) połączenie głosowe. @@ -814,17 +827,17 @@ Example: https://server.my:8787 %1 rozpoczął(-ęła) połączenie. - + %1 answered the call. %1 odebrał(a) połączenie. - + %1 ended the call. %1 zakończył(a) połączenie. - + Negotiating call... Negocjowanie połączenia… @@ -832,7 +845,7 @@ Example: https://server.my:8787 MessageInput - + Hang up @@ -853,6 +866,11 @@ Example: https://server.my:8787 + Stickers + + + + Emoji Emoji @@ -870,17 +888,17 @@ Example: https://server.my:8787 MessageView - + Edit - + React - + Reply @@ -890,7 +908,7 @@ Example: https://server.my:8787 - + &Copy @@ -1098,7 +1116,7 @@ Example: https://server.my:8787 Placeholder - + unimplemented event: Niezaimplementowane wydarzenie: @@ -1218,7 +1236,7 @@ Example: https://server.my:8787 ReplyPopup - + Close Zamknij @@ -1231,7 +1249,7 @@ Example: https://server.my:8787 RoomInfo - + no version stored @@ -1239,7 +1257,7 @@ Example: https://server.my:8787 RoomList - + New tag @@ -1279,17 +1297,7 @@ Example: https://server.my:8787 - - Accept - Akceptuj - - - - Decline - Odrzuć - - - + Status Message @@ -1339,20 +1347,43 @@ Example: https://server.my:8787 Ustawienia użytkownika + + RoomMembers + + + Members of %1 + + + + + %n people in %1 + Summary above list of members + + + + + + + + + Invite more people + + + RoomSettings - + Room Settings - + %1 member(s) - + SETTINGS @@ -1434,11 +1465,6 @@ Example: https://server.my:8787 Room Version - - - OK - OK - Failed to enable encryption: %1 @@ -1471,6 +1497,24 @@ Example: https://server.my:8787 Nie udało się wysłać obrazu: %s + + RoomlistModel + + + Pending invite. + + + + + Previewing this room + + + + + No preview available + + + ScreenShare @@ -1528,7 +1572,7 @@ Example: https://server.my:8787 StatusIndicator - + Failed @@ -1548,6 +1592,14 @@ Example: https://server.my:8787 + + StickerPicker + + + Search + Szukaj + + Success @@ -1569,7 +1621,7 @@ Example: https://server.my:8787 TimelineModel - + Message redaction failed: %1 Redagowanie wiadomości nie powiodło się: %1 @@ -1580,7 +1632,7 @@ Example: https://server.my:8787 - + Save image Zapisz obraz @@ -1715,12 +1767,12 @@ Example: https://server.my:8787 - + You joined this room. Dołączyłeś(-łaś) do tego pokoju. - + %1 has changed their avatar and changed their display name to %2. @@ -1749,7 +1801,7 @@ Example: https://server.my:8787 TimelineRow - + Edited @@ -1757,17 +1809,32 @@ Example: https://server.my:8787 TimelineView - + No room open - + %1 member(s) - + + join the conversation + + + + + accept invite + + + + + decline invite + + + + Back to room list @@ -1775,7 +1842,7 @@ Example: https://server.my:8787 TimelineViewManager - + No encrypted private chat found with this user. Create an encrypted private chat with this user and try again. @@ -1783,18 +1850,17 @@ Example: https://server.my:8787 TopBar - + Back to room list - - + No room selected - + Room options Ustawienia pokoju @@ -1835,7 +1901,7 @@ Example: https://server.my:8787 UserProfile - + Global User Profile @@ -1845,7 +1911,7 @@ Example: https://server.my:8787 - + Verify @@ -1894,7 +1960,7 @@ Example: https://server.my:8787 UserSettings - + Default @@ -1903,7 +1969,7 @@ Example: https://server.my:8787 UserSettingsPage - + Minimize to tray Zminimalizuj do paska zadań @@ -2349,7 +2415,7 @@ This usually causes the application icon in the task bar to animate in some fash Waiting - + Waiting for other party… @@ -2400,7 +2466,7 @@ This usually causes the application icon in the task bar to animate in some fash descriptiveTime - + Yesterday @@ -2471,19 +2537,6 @@ This usually causes the application icon in the task bar to animate in some fash - - dialogs::InviteUsers - - - Cancel - Anuluj - - - - User ID to invite - ID użytkownika do zaproszenia - - dialogs::JoinRoom diff --git a/resources/langs/nheko_pt_BR.ts b/resources/langs/nheko_pt_BR.ts index fa0ea193..7774ba58 100644 --- a/resources/langs/nheko_pt_BR.ts +++ b/resources/langs/nheko_pt_BR.ts @@ -38,7 +38,7 @@ AwaitingVerificationConfirmation - + Awaiting Confirmation @@ -48,7 +48,7 @@ Esperando o outro lado completar a verificação. - + Cancel Cancelar @@ -125,7 +125,7 @@ ChatPage - + Failed to invite user: %1 Falha ao convidar usuário: %1 @@ -157,12 +157,12 @@ - + Confirm invite Confirmar convite - + Do you really want to invite %1 (%2)? @@ -227,12 +227,12 @@ Usuário desbanido: %1 - + Do you really want to start a private chat with %1? - + Cache migration failed! Migração do cache falhou! @@ -352,7 +352,7 @@ CrossSigningSecrets - + Decrypt secrets @@ -426,12 +426,12 @@ EmojiPicker - + Search - + People @@ -607,7 +607,7 @@ InputBar - + Select a file @@ -617,18 +617,44 @@ - + Failed to upload media. Please try again. - InviteeItem + InviteDialog - - Remove + + Invite users to %1 + + + User ID to invite + + + + + @joe:matrix.org + Example user id. The name 'joe' can be localized however you want. + + + + + Add + + + + + Invite + + + + + Cancel + Cancelar + LoginPage @@ -740,28 +766,15 @@ Example: https://server.my:8787 - - MemberList - - - Room members - - - - - OK - - - MessageDelegate - + Encryption enabled - + room name changed to: %1 @@ -771,7 +784,7 @@ Example: https://server.my:8787 - + topic changed to: %1 @@ -781,17 +794,17 @@ Example: https://server.my:8787 - + %1 changed the room avatar - + %1 created and configured room: %2 - + %1 placed a voice call. @@ -806,23 +819,23 @@ Example: https://server.my:8787 - + Negotiating call... - + %1 answered the call. - + removed - + %1 ended the call. @@ -830,7 +843,7 @@ Example: https://server.my:8787 MessageInput - + Hang up @@ -851,6 +864,11 @@ Example: https://server.my:8787 + Stickers + + + + Emoji @@ -868,17 +886,17 @@ Example: https://server.my:8787 MessageView - + Edit - + React - + Reply @@ -888,7 +906,7 @@ Example: https://server.my:8787 - + &Copy @@ -1096,7 +1114,7 @@ Example: https://server.my:8787 Placeholder - + unimplemented event: @@ -1216,7 +1234,7 @@ Example: https://server.my:8787 ReplyPopup - + Close @@ -1229,7 +1247,7 @@ Example: https://server.my:8787 RoomInfo - + no version stored @@ -1237,7 +1255,7 @@ Example: https://server.my:8787 RoomList - + New tag @@ -1277,17 +1295,7 @@ Example: https://server.my:8787 - - Accept - Aceitar - - - - Decline - Rejeitar - - - + Status Message @@ -1337,20 +1345,42 @@ Example: https://server.my:8787 + + RoomMembers + + + Members of %1 + + + + + %n people in %1 + Summary above list of members + + + + + + + + Invite more people + + + RoomSettings - + Room Settings - + %1 member(s) - + SETTINGS @@ -1432,11 +1462,6 @@ Example: https://server.my:8787 Room Version - - - OK - - Failed to enable encryption: %1 @@ -1469,6 +1494,24 @@ Example: https://server.my:8787 + + RoomlistModel + + + Pending invite. + + + + + Previewing this room + + + + + No preview available + + + ScreenShare @@ -1526,7 +1569,7 @@ Example: https://server.my:8787 StatusIndicator - + Failed @@ -1546,6 +1589,14 @@ Example: https://server.my:8787 + + StickerPicker + + + Search + + + Success @@ -1567,7 +1618,7 @@ Example: https://server.my:8787 TimelineModel - + Message redaction failed: %1 @@ -1578,7 +1629,7 @@ Example: https://server.my:8787 - + Save image @@ -1712,12 +1763,12 @@ Example: https://server.my:8787 - + You joined this room. Você entrou nessa sala. - + %1 has changed their avatar and changed their display name to %2. @@ -1746,7 +1797,7 @@ Example: https://server.my:8787 TimelineRow - + Edited @@ -1754,17 +1805,32 @@ Example: https://server.my:8787 TimelineView - + No room open - + %1 member(s) - + + join the conversation + + + + + accept invite + + + + + decline invite + + + + Back to room list @@ -1772,7 +1838,7 @@ Example: https://server.my:8787 TimelineViewManager - + No encrypted private chat found with this user. Create an encrypted private chat with this user and try again. @@ -1780,18 +1846,17 @@ Example: https://server.my:8787 TopBar - + Back to room list - - + No room selected - + Room options @@ -1832,7 +1897,7 @@ Example: https://server.my:8787 UserProfile - + Global User Profile @@ -1842,7 +1907,7 @@ Example: https://server.my:8787 - + Verify @@ -1891,7 +1956,7 @@ Example: https://server.my:8787 UserSettings - + Default @@ -1900,7 +1965,7 @@ Example: https://server.my:8787 UserSettingsPage - + Minimize to tray @@ -2346,7 +2411,7 @@ This usually causes the application icon in the task bar to animate in some fash Waiting - + Waiting for other party… @@ -2397,7 +2462,7 @@ This usually causes the application icon in the task bar to animate in some fash descriptiveTime - + Yesterday @@ -2468,19 +2533,6 @@ This usually causes the application icon in the task bar to animate in some fash - - dialogs::InviteUsers - - - Cancel - Cancelar - - - - User ID to invite - - - dialogs::JoinRoom diff --git a/resources/langs/nheko_pt_PT.ts b/resources/langs/nheko_pt_PT.ts index 81343f97..cafbcdd6 100644 --- a/resources/langs/nheko_pt_PT.ts +++ b/resources/langs/nheko_pt_PT.ts @@ -38,7 +38,7 @@ AwaitingVerificationConfirmation - + Awaiting Confirmation @@ -48,7 +48,7 @@ - + Cancel @@ -125,7 +125,7 @@ ChatPage - + Failed to invite user: %1 @@ -157,12 +157,12 @@ - + Confirm invite - + Do you really want to invite %1 (%2)? @@ -227,12 +227,12 @@ - + Do you really want to start a private chat with %1? - + Cache migration failed! @@ -352,7 +352,7 @@ CrossSigningSecrets - + Decrypt secrets @@ -426,12 +426,12 @@ EmojiPicker - + Search - + People @@ -607,7 +607,7 @@ InputBar - + Select a file @@ -617,16 +617,42 @@ - + Failed to upload media. Please try again. - InviteeItem + InviteDialog - - Remove + + Invite users to %1 + + + + + User ID to invite + + + + + @joe:matrix.org + Example user id. The name 'joe' can be localized however you want. + + + + + Add + + + + + Invite + + + + + Cancel @@ -740,28 +766,15 @@ Example: https://server.my:8787 - - MemberList - - - Room members - - - - - OK - - - MessageDelegate - + Encryption enabled - + room name changed to: %1 @@ -771,7 +784,7 @@ Example: https://server.my:8787 - + topic changed to: %1 @@ -781,17 +794,17 @@ Example: https://server.my:8787 - + %1 changed the room avatar - + %1 created and configured room: %2 - + %1 placed a voice call. @@ -806,23 +819,23 @@ Example: https://server.my:8787 - + Negotiating call... - + %1 answered the call. - + removed - + %1 ended the call. @@ -830,7 +843,7 @@ Example: https://server.my:8787 MessageInput - + Hang up @@ -851,6 +864,11 @@ Example: https://server.my:8787 + Stickers + + + + Emoji @@ -868,17 +886,17 @@ Example: https://server.my:8787 MessageView - + Edit - + React - + Reply @@ -888,7 +906,7 @@ Example: https://server.my:8787 - + &Copy @@ -1096,7 +1114,7 @@ Example: https://server.my:8787 Placeholder - + unimplemented event: @@ -1216,7 +1234,7 @@ Example: https://server.my:8787 ReplyPopup - + Close @@ -1229,7 +1247,7 @@ Example: https://server.my:8787 RoomInfo - + no version stored @@ -1237,7 +1255,7 @@ Example: https://server.my:8787 RoomList - + New tag @@ -1277,17 +1295,7 @@ Example: https://server.my:8787 - - Accept - - - - - Decline - - - - + Status Message @@ -1337,20 +1345,42 @@ Example: https://server.my:8787 + + RoomMembers + + + Members of %1 + + + + + %n people in %1 + Summary above list of members + + + + + + + + Invite more people + + + RoomSettings - + Room Settings - + %1 member(s) - + SETTINGS @@ -1432,11 +1462,6 @@ Example: https://server.my:8787 Room Version - - - OK - - Failed to enable encryption: %1 @@ -1469,6 +1494,24 @@ Example: https://server.my:8787 + + RoomlistModel + + + Pending invite. + + + + + Previewing this room + + + + + No preview available + + + ScreenShare @@ -1526,7 +1569,7 @@ Example: https://server.my:8787 StatusIndicator - + Failed @@ -1546,6 +1589,14 @@ Example: https://server.my:8787 + + StickerPicker + + + Search + + + Success @@ -1567,7 +1618,7 @@ Example: https://server.my:8787 TimelineModel - + Message redaction failed: %1 @@ -1578,7 +1629,7 @@ Example: https://server.my:8787 - + Save image @@ -1712,12 +1763,12 @@ Example: https://server.my:8787 - + You joined this room. - + %1 has changed their avatar and changed their display name to %2. @@ -1746,7 +1797,7 @@ Example: https://server.my:8787 TimelineRow - + Edited @@ -1754,17 +1805,32 @@ Example: https://server.my:8787 TimelineView - + No room open - + %1 member(s) - + + join the conversation + + + + + accept invite + + + + + decline invite + + + + Back to room list @@ -1772,7 +1838,7 @@ Example: https://server.my:8787 TimelineViewManager - + No encrypted private chat found with this user. Create an encrypted private chat with this user and try again. @@ -1780,18 +1846,17 @@ Example: https://server.my:8787 TopBar - + Back to room list - - + No room selected - + Room options @@ -1832,7 +1897,7 @@ Example: https://server.my:8787 UserProfile - + Global User Profile @@ -1842,7 +1907,7 @@ Example: https://server.my:8787 - + Verify @@ -1891,7 +1956,7 @@ Example: https://server.my:8787 UserSettings - + Default @@ -1900,7 +1965,7 @@ Example: https://server.my:8787 UserSettingsPage - + Minimize to tray @@ -2346,7 +2411,7 @@ This usually causes the application icon in the task bar to animate in some fash Waiting - + Waiting for other party… @@ -2397,7 +2462,7 @@ This usually causes the application icon in the task bar to animate in some fash descriptiveTime - + Yesterday @@ -2468,19 +2533,6 @@ This usually causes the application icon in the task bar to animate in some fash - - dialogs::InviteUsers - - - Cancel - - - - - User ID to invite - - - dialogs::JoinRoom diff --git a/resources/langs/nheko_ro.ts b/resources/langs/nheko_ro.ts index c21bb069..8ff28d24 100644 --- a/resources/langs/nheko_ro.ts +++ b/resources/langs/nheko_ro.ts @@ -38,7 +38,7 @@ AwaitingVerificationConfirmation - + Awaiting Confirmation @@ -48,7 +48,7 @@ - + Cancel @@ -125,7 +125,7 @@ ChatPage - + Failed to invite user: %1 Nu s-a putut invita utilizatorul: %1 @@ -157,12 +157,12 @@ - + Confirm invite - + Do you really want to invite %1 (%2)? @@ -227,12 +227,12 @@ Utilizator dezinterzis: %1 - + Do you really want to start a private chat with %1? - + Cache migration failed! Nu s-a putut migra cache-ul! @@ -352,7 +352,7 @@ CrossSigningSecrets - + Decrypt secrets @@ -426,12 +426,12 @@ EmojiPicker - + Search - + People @@ -607,7 +607,7 @@ InputBar - + Select a file @@ -617,17 +617,43 @@ Toate fișierele (*) - + Failed to upload media. Please try again. - InviteeItem + InviteDialog - - Remove - Ștergere + + Invite users to %1 + + + + + User ID to invite + IDul utilizatorului de invitat + + + + @joe:matrix.org + Example user id. The name 'joe' can be localized however you want. + + + + + Add + + + + + Invite + + + + + Cancel + @@ -744,23 +770,10 @@ Exemplu: https://serverul.meu:8787 Conectarea SSO a eșuat - - MemberList - - - Room members - Membrii camerei - - - - OK - OK - - MessageDelegate - + removed @@ -771,7 +784,7 @@ Exemplu: https://serverul.meu:8787 Criptare activată - + room name changed to: %1 numele camerei schimbat la: %1 @@ -781,7 +794,7 @@ Exemplu: https://serverul.meu:8787 numele camerei șters - + topic changed to: %1 subiect schimbat la: %1 @@ -791,17 +804,17 @@ Exemplu: https://serverul.meu:8787 subiect șters - + %1 changed the room avatar - + %1 created and configured room: %2 %1 a creat și configurat camera: %2 - + %1 placed a voice call. @@ -816,17 +829,17 @@ Exemplu: https://serverul.meu:8787 - + %1 answered the call. %1 a răspuns apelului. - + %1 ended the call. %1 a închis apelul. - + Negotiating call... @@ -834,7 +847,7 @@ Exemplu: https://serverul.meu:8787 MessageInput - + Hang up @@ -855,6 +868,11 @@ Exemplu: https://serverul.meu:8787 + Stickers + + + + Emoji @@ -872,17 +890,17 @@ Exemplu: https://serverul.meu:8787 MessageView - + Edit - + React - + Reply Răspuns @@ -892,7 +910,7 @@ Exemplu: https://serverul.meu:8787 Opțiuni - + &Copy @@ -1100,7 +1118,7 @@ Exemplu: https://serverul.meu:8787 Placeholder - + unimplemented event: eveniment neimplementat: @@ -1220,7 +1238,7 @@ Exemplu: https://serverul.meu:8787 ReplyPopup - + Close Închide @@ -1233,7 +1251,7 @@ Exemplu: https://serverul.meu:8787 RoomInfo - + no version stored nicio versiune stocată @@ -1241,7 +1259,7 @@ Exemplu: https://serverul.meu:8787 RoomList - + New tag @@ -1281,17 +1299,7 @@ Exemplu: https://serverul.meu:8787 - - Accept - Acceptare - - - - Decline - Refuzare - - - + Status Message @@ -1341,20 +1349,43 @@ Exemplu: https://serverul.meu:8787 Setări utilizator + + RoomMembers + + + Members of %1 + + + + + %n people in %1 + Summary above list of members + + + + + + + + + Invite more people + + + RoomSettings - + Room Settings - + %1 member(s) - + SETTINGS @@ -1436,11 +1467,6 @@ Exemplu: https://serverul.meu:8787 Room Version - - - OK - OK - Failed to enable encryption: %1 @@ -1473,6 +1499,24 @@ Exemplu: https://serverul.meu:8787 Nu s-a putut încărca imaginea: %s + + RoomlistModel + + + Pending invite. + + + + + Previewing this room + + + + + No preview available + + + ScreenShare @@ -1530,7 +1574,7 @@ Exemplu: https://serverul.meu:8787 StatusIndicator - + Failed Eșuat @@ -1550,6 +1594,14 @@ Exemplu: https://serverul.meu:8787 Citit + + StickerPicker + + + Search + + + Success @@ -1571,7 +1623,7 @@ Exemplu: https://serverul.meu:8787 TimelineModel - + Message redaction failed: %1 Redactare mesaj eșuată: %1 @@ -1582,7 +1634,7 @@ Exemplu: https://serverul.meu:8787 - + Save image Salvați imaginea @@ -1717,12 +1769,12 @@ Exemplu: https://serverul.meu:8787 %1 și-a redactat ciocănitul. - + You joined this room. Te-ai alăturat camerei. - + %1 has changed their avatar and changed their display name to %2. @@ -1751,7 +1803,7 @@ Exemplu: https://serverul.meu:8787 TimelineRow - + Edited @@ -1759,17 +1811,32 @@ Exemplu: https://serverul.meu:8787 TimelineView - + No room open Nicio cameră deschisă - + %1 member(s) - + + join the conversation + + + + + accept invite + + + + + decline invite + + + + Back to room list @@ -1777,7 +1844,7 @@ Exemplu: https://serverul.meu:8787 TimelineViewManager - + No encrypted private chat found with this user. Create an encrypted private chat with this user and try again. @@ -1785,18 +1852,17 @@ Exemplu: https://serverul.meu:8787 TopBar - + Back to room list - - + No room selected - + Room options @@ -1837,7 +1903,7 @@ Exemplu: https://serverul.meu:8787 UserProfile - + Global User Profile @@ -1847,7 +1913,7 @@ Exemplu: https://serverul.meu:8787 - + Verify @@ -1896,7 +1962,7 @@ Exemplu: https://serverul.meu:8787 UserSettings - + Default @@ -1905,7 +1971,7 @@ Exemplu: https://serverul.meu:8787 UserSettingsPage - + Minimize to tray Minimizează în bara de notificări @@ -2351,7 +2417,7 @@ This usually causes the application icon in the task bar to animate in some fash Waiting - + Waiting for other party… @@ -2402,7 +2468,7 @@ This usually causes the application icon in the task bar to animate in some fash descriptiveTime - + Yesterday Ieri @@ -2473,19 +2539,6 @@ This usually causes the application icon in the task bar to animate in some fash Deschideți fallback, urmăriți pașii și confirmați după ce i-ați completat. - - dialogs::InviteUsers - - - Cancel - Anulare - - - - User ID to invite - IDul utilizatorului de invitat - - dialogs::JoinRoom diff --git a/resources/langs/nheko_ru.ts b/resources/langs/nheko_ru.ts index 6f2b19af..be97413f 100644 --- a/resources/langs/nheko_ru.ts +++ b/resources/langs/nheko_ru.ts @@ -38,7 +38,7 @@ AwaitingVerificationConfirmation - + Awaiting Confirmation Ожидание Подтверждения @@ -48,7 +48,7 @@ Ожидание подтверждения у собеседника. - + Cancel Отмена @@ -125,7 +125,7 @@ ChatPage - + Failed to invite user: %1 Не удалось пригласить пользователя: %1 @@ -157,12 +157,12 @@ - + Confirm invite Подтвердите приглашение - + Do you really want to invite %1 (%2)? Вы точно хотите пригласить %1 (%2)? @@ -227,12 +227,12 @@ Разблокированный пользователь: %1 - + Do you really want to start a private chat with %1? Вы действительно хотите начать личную переписку с %1? - + Cache migration failed! Миграция кэша не удалась! @@ -352,7 +352,7 @@ CrossSigningSecrets - + Decrypt secrets Расшифровать секреты @@ -426,12 +426,12 @@ EmojiPicker - + Search Поиск - + People Люди @@ -607,7 +607,7 @@ InputBar - + Select a file Выберите файл @@ -617,17 +617,43 @@ Все файлы (*) - + Failed to upload media. Please try again. Не удалось загрузить медиа. Пожалуйста попробуйте ещё раз - InviteeItem + InviteDialog - - Remove - Удалить + + Invite users to %1 + + + + + User ID to invite + Идентификатор пользователя + + + + @joe:matrix.org + Example user id. The name 'joe' can be localized however you want. + + + + + Add + + + + + Invite + + + + + Cancel + @@ -744,23 +770,10 @@ Example: https://server.my:8787 SSO вход не удался - - MemberList - - - Room members - Участники комнаты - - - - OK - ОК - - MessageDelegate - + removed убрано @@ -771,7 +784,7 @@ Example: https://server.my:8787 Шифрование включено - + room name changed to: %1 имя комнаты изменено на: %1 @@ -781,7 +794,7 @@ Example: https://server.my:8787 название комнаты убрано - + topic changed to: %1 тема изменена на: %1 @@ -791,17 +804,17 @@ Example: https://server.my:8787 тема убрана - + %1 changed the room avatar - + %1 created and configured room: %2 %1 создал и настроил комнату: %2 - + %1 placed a voice call. %1 начал голосовой звонок. @@ -816,17 +829,17 @@ Example: https://server.my:8787 %1 начал вызов. - + %1 answered the call. %1 ответил на звонок. - + %1 ended the call. %1 завершил вызов. - + Negotiating call... Совершение звонка... @@ -834,7 +847,7 @@ Example: https://server.my:8787 MessageInput - + Hang up Завершить звонок @@ -855,6 +868,11 @@ Example: https://server.my:8787 + Stickers + + + + Emoji Эмоджи @@ -872,17 +890,17 @@ Example: https://server.my:8787 MessageView - + Edit Редактировать - + React Реакция - + Reply Ответить @@ -892,7 +910,7 @@ Example: https://server.my:8787 Опции - + &Copy @@ -1100,7 +1118,7 @@ Example: https://server.my:8787 Placeholder - + unimplemented event: не реализованное событие @@ -1220,7 +1238,7 @@ Example: https://server.my:8787 ReplyPopup - + Close Закрыть @@ -1233,7 +1251,7 @@ Example: https://server.my:8787 RoomInfo - + no version stored нет сохраненной версии @@ -1241,7 +1259,7 @@ Example: https://server.my:8787 RoomList - + New tag @@ -1281,17 +1299,7 @@ Example: https://server.my:8787 - - Accept - Принять - - - - Decline - Отказаться - - - + Status Message @@ -1341,20 +1349,43 @@ Example: https://server.my:8787 Пользовательские настройки + + RoomMembers + + + Members of %1 + + + + + %n people in %1 + Summary above list of members + + + + + + + + + Invite more people + + + RoomSettings - + Room Settings Настройки комнаты - + %1 member(s) %1 участник(ов) - + SETTINGS НАЙСТРОЙКИ @@ -1436,11 +1467,6 @@ Example: https://server.my:8787 Room Version Версия Комнаты - - - OK - ОК - Failed to enable encryption: %1 @@ -1473,6 +1499,24 @@ Example: https://server.my:8787 Не удалось загрузить изображение: %s + + RoomlistModel + + + Pending invite. + + + + + Previewing this room + + + + + No preview available + + + ScreenShare @@ -1530,7 +1574,7 @@ Example: https://server.my:8787 StatusIndicator - + Failed Не удалоcь @@ -1550,6 +1594,14 @@ Example: https://server.my:8787 Прочитано + + StickerPicker + + + Search + Поиск + + Success @@ -1571,7 +1623,7 @@ Example: https://server.my:8787 TimelineModel - + Message redaction failed: %1 Ошибка редактирования сообщения: %1 @@ -1582,7 +1634,7 @@ Example: https://server.my:8787 Не удалось зашифровать сообщение, отправка отменена! - + Save image Сохранить изображение @@ -1717,12 +1769,12 @@ Example: https://server.my:8787 %1 отредактировал его "стук". - + You joined this room. Вы присоединились к этой комнате. - + %1 has changed their avatar and changed their display name to %2. @@ -1751,7 +1803,7 @@ Example: https://server.my:8787 TimelineRow - + Edited Изменено @@ -1759,17 +1811,32 @@ Example: https://server.my:8787 TimelineView - + No room open Комната не выбрана - + %1 member(s) %1 участник(ов) - + + join the conversation + + + + + accept invite + + + + + decline invite + + + + Back to room list Вернуться к списку комнат @@ -1777,7 +1844,7 @@ Example: https://server.my:8787 TimelineViewManager - + No encrypted private chat found with this user. Create an encrypted private chat with this user and try again. Не найдено личного чата с этим пользователем. Создайте зашифрованный личный чат с этим пользователем и попытайтесь еще раз. @@ -1785,18 +1852,17 @@ Example: https://server.my:8787 TopBar - + Back to room list Вернуться к списку комнат - - + No room selected Комнаты не выбраны - + Room options Настройки комнаты @@ -1837,7 +1903,7 @@ Example: https://server.my:8787 UserProfile - + Global User Profile Глобальный Пользовательский Профиль @@ -1847,7 +1913,7 @@ Example: https://server.my:8787 Поользовательский Профиль в Комнате - + Verify Верифицировать @@ -1896,7 +1962,7 @@ Example: https://server.my:8787 UserSettings - + Default По умолчанию @@ -1905,7 +1971,7 @@ Example: https://server.my:8787 UserSettingsPage - + Minimize to tray Сворачивать в системную панель @@ -2357,7 +2423,7 @@ This usually causes the application icon in the task bar to animate in some fash Waiting - + Waiting for other party… @@ -2408,7 +2474,7 @@ This usually causes the application icon in the task bar to animate in some fash descriptiveTime - + Yesterday Вчера @@ -2479,19 +2545,6 @@ This usually causes the application icon in the task bar to animate in some fash Запустите резервный вариант, пройдите его шаги и подтвердите завершение. - - dialogs::InviteUsers - - - Cancel - Отмена - - - - User ID to invite - Идентификатор пользователя - - dialogs::JoinRoom diff --git a/resources/langs/nheko_si.ts b/resources/langs/nheko_si.ts index a80adb1b..67f09710 100644 --- a/resources/langs/nheko_si.ts +++ b/resources/langs/nheko_si.ts @@ -38,7 +38,7 @@ AwaitingVerificationConfirmation - + Awaiting Confirmation @@ -48,7 +48,7 @@ - + Cancel @@ -125,7 +125,7 @@ ChatPage - + Failed to invite user: %1 @@ -157,12 +157,12 @@ - + Confirm invite - + Do you really want to invite %1 (%2)? @@ -227,12 +227,12 @@ - + Do you really want to start a private chat with %1? - + Cache migration failed! @@ -352,7 +352,7 @@ CrossSigningSecrets - + Decrypt secrets @@ -426,12 +426,12 @@ EmojiPicker - + Search - + People @@ -607,7 +607,7 @@ InputBar - + Select a file @@ -617,16 +617,42 @@ - + Failed to upload media. Please try again. - InviteeItem + InviteDialog - - Remove + + Invite users to %1 + + + + + User ID to invite + + + + + @joe:matrix.org + Example user id. The name 'joe' can be localized however you want. + + + + + Add + + + + + Invite + + + + + Cancel @@ -740,23 +766,10 @@ Example: https://server.my:8787 - - MemberList - - - Room members - - - - - OK - - - MessageDelegate - + removed @@ -767,7 +780,7 @@ Example: https://server.my:8787 - + room name changed to: %1 @@ -777,7 +790,7 @@ Example: https://server.my:8787 - + topic changed to: %1 @@ -787,17 +800,17 @@ Example: https://server.my:8787 - + %1 changed the room avatar - + %1 created and configured room: %2 - + %1 placed a voice call. @@ -812,17 +825,17 @@ Example: https://server.my:8787 - + %1 answered the call. - + %1 ended the call. - + Negotiating call... @@ -830,7 +843,7 @@ Example: https://server.my:8787 MessageInput - + Hang up @@ -851,6 +864,11 @@ Example: https://server.my:8787 + Stickers + + + + Emoji @@ -868,17 +886,17 @@ Example: https://server.my:8787 MessageView - + Edit - + React - + Reply @@ -888,7 +906,7 @@ Example: https://server.my:8787 - + &Copy @@ -1096,7 +1114,7 @@ Example: https://server.my:8787 Placeholder - + unimplemented event: @@ -1216,7 +1234,7 @@ Example: https://server.my:8787 ReplyPopup - + Close @@ -1229,7 +1247,7 @@ Example: https://server.my:8787 RoomInfo - + no version stored @@ -1237,7 +1255,7 @@ Example: https://server.my:8787 RoomList - + New tag @@ -1277,17 +1295,7 @@ Example: https://server.my:8787 - - Accept - - - - - Decline - - - - + Status Message @@ -1337,20 +1345,42 @@ Example: https://server.my:8787 + + RoomMembers + + + Members of %1 + + + + + %n people in %1 + Summary above list of members + + + + + + + + Invite more people + + + RoomSettings - + Room Settings - + %1 member(s) - + SETTINGS @@ -1432,11 +1462,6 @@ Example: https://server.my:8787 Room Version - - - OK - - Failed to enable encryption: %1 @@ -1469,6 +1494,24 @@ Example: https://server.my:8787 + + RoomlistModel + + + Pending invite. + + + + + Previewing this room + + + + + No preview available + + + ScreenShare @@ -1526,7 +1569,7 @@ Example: https://server.my:8787 StatusIndicator - + Failed @@ -1546,6 +1589,14 @@ Example: https://server.my:8787 + + StickerPicker + + + Search + + + Success @@ -1567,7 +1618,7 @@ Example: https://server.my:8787 TimelineModel - + Message redaction failed: %1 @@ -1578,7 +1629,7 @@ Example: https://server.my:8787 - + Save image @@ -1712,12 +1763,12 @@ Example: https://server.my:8787 - + You joined this room. - + %1 has changed their avatar and changed their display name to %2. @@ -1746,7 +1797,7 @@ Example: https://server.my:8787 TimelineRow - + Edited @@ -1754,17 +1805,32 @@ Example: https://server.my:8787 TimelineView - + No room open - + %1 member(s) - + + join the conversation + + + + + accept invite + + + + + decline invite + + + + Back to room list @@ -1772,7 +1838,7 @@ Example: https://server.my:8787 TimelineViewManager - + No encrypted private chat found with this user. Create an encrypted private chat with this user and try again. @@ -1780,18 +1846,17 @@ Example: https://server.my:8787 TopBar - + Back to room list - - + No room selected - + Room options @@ -1832,7 +1897,7 @@ Example: https://server.my:8787 UserProfile - + Global User Profile @@ -1842,7 +1907,7 @@ Example: https://server.my:8787 - + Verify @@ -1891,7 +1956,7 @@ Example: https://server.my:8787 UserSettings - + Default @@ -1900,7 +1965,7 @@ Example: https://server.my:8787 UserSettingsPage - + Minimize to tray @@ -2346,7 +2411,7 @@ This usually causes the application icon in the task bar to animate in some fash Waiting - + Waiting for other party… @@ -2397,7 +2462,7 @@ This usually causes the application icon in the task bar to animate in some fash descriptiveTime - + Yesterday @@ -2468,19 +2533,6 @@ This usually causes the application icon in the task bar to animate in some fash - - dialogs::InviteUsers - - - Cancel - - - - - User ID to invite - - - dialogs::JoinRoom diff --git a/resources/langs/nheko_sv.ts b/resources/langs/nheko_sv.ts index 8069dcea..fb662292 100644 --- a/resources/langs/nheko_sv.ts +++ b/resources/langs/nheko_sv.ts @@ -38,7 +38,7 @@ AwaitingVerificationConfirmation - + Awaiting Confirmation Inväntar Bekräftelse @@ -48,7 +48,7 @@ Väntar på att motparten ska slutföra verifikationen. - + Cancel Avbryt @@ -125,7 +125,7 @@ ChatPage - + Failed to invite user: %1 Kunde inte bjuda in användare: %1 @@ -157,12 +157,12 @@ - + Confirm invite Bekräfta inbjudan - + Do you really want to invite %1 (%2)? Är du säker på att du vill bjuda in %1 (%2)? @@ -227,12 +227,12 @@ Hävde bannlysningen av användare: %1 - + Do you really want to start a private chat with %1? - + Cache migration failed! Cache-migration misslyckades! @@ -352,7 +352,7 @@ CrossSigningSecrets - + Decrypt secrets Dekryptera hemliga nycklar @@ -426,12 +426,12 @@ EmojiPicker - + Search Sök - + People Personer @@ -607,7 +607,7 @@ InputBar - + Select a file Välj en fil @@ -617,17 +617,43 @@ Alla Filer (*) - + Failed to upload media. Please try again. Kunde inte ladda upp media. Vänligen försök igen. - InviteeItem + InviteDialog - - Remove - Ta bort + + Invite users to %1 + + + + + User ID to invite + Användar-ID att bjuda in + + + + @joe:matrix.org + Example user id. The name 'joe' can be localized however you want. + + + + + Add + + + + + Invite + + + + + Cancel + Avbryt @@ -744,28 +770,15 @@ Exempel: https://server.my:8787 SSO-inloggning misslyckades - - MemberList - - - Room members - Rumsmedlemmar - - - - OK - OK - - MessageDelegate - + Encryption enabled Kryptering aktiverad - + room name changed to: %1 rummets namn ändrat till: %1 @@ -775,7 +788,7 @@ Exempel: https://server.my:8787 tog bort rummets namn - + topic changed to: %1 ämne ändrat till: %1 @@ -785,17 +798,17 @@ Exempel: https://server.my:8787 tog bort ämne - + %1 changed the room avatar - + %1 created and configured room: %2 %1 skapade och konfigurerade rum: %2 - + %1 placed a voice call. %1 påbörjade ett röstsamtal. @@ -810,23 +823,23 @@ Exempel: https://server.my:8787 %1 påbörjade ett samtal. - + Negotiating call... Förhandlar samtal… - + %1 answered the call. %1 besvarade samtalet. - + removed borttagen - + %1 ended the call. %1 avslutade samtalet. @@ -834,7 +847,7 @@ Exempel: https://server.my:8787 MessageInput - + Hang up Lägg på @@ -855,6 +868,11 @@ Exempel: https://server.my:8787 + Stickers + + + + Emoji Emoji @@ -872,17 +890,17 @@ Exempel: https://server.my:8787 MessageView - + Edit - + React Reagera - + Reply Svara @@ -892,7 +910,7 @@ Exempel: https://server.my:8787 Alternativ - + &Copy @@ -1100,7 +1118,7 @@ Exempel: https://server.my:8787 Placeholder - + unimplemented event: ej implementerat event: @@ -1220,7 +1238,7 @@ Exempel: https://server.my:8787 ReplyPopup - + Close Stäng @@ -1233,7 +1251,7 @@ Exempel: https://server.my:8787 RoomInfo - + no version stored ingen version lagrad @@ -1241,7 +1259,7 @@ Exempel: https://server.my:8787 RoomList - + New tag @@ -1281,17 +1299,7 @@ Exempel: https://server.my:8787 - - Accept - Godkänn - - - - Decline - - - - + Status Message @@ -1341,20 +1349,42 @@ Exempel: https://server.my:8787 Användarinställningar + + RoomMembers + + + Members of %1 + + + + + %n people in %1 + Summary above list of members + + + + + + + + Invite more people + + + RoomSettings - + Room Settings - + %1 member(s) - + SETTINGS @@ -1436,11 +1466,6 @@ Exempel: https://server.my:8787 Room Version - - - OK - OK - Failed to enable encryption: %1 @@ -1473,6 +1498,24 @@ Exempel: https://server.my:8787 Kunde inte ladda upp bilden: %s + + RoomlistModel + + + Pending invite. + + + + + Previewing this room + + + + + No preview available + + + ScreenShare @@ -1530,7 +1573,7 @@ Exempel: https://server.my:8787 StatusIndicator - + Failed Misslyckat @@ -1550,6 +1593,14 @@ Exempel: https://server.my:8787 Läst + + StickerPicker + + + Search + Sök + + Success @@ -1571,7 +1622,7 @@ Exempel: https://server.my:8787 TimelineModel - + Message redaction failed: %1 Kunde inte maskera meddelande: %1 @@ -1582,7 +1633,7 @@ Exempel: https://server.my:8787 Kunde inte kryptera event, sändning avbruten! - + Save image Spara bild @@ -1716,12 +1767,12 @@ Exempel: https://server.my:8787 %1 maskerade sin knackning. - + You joined this room. Du gick med i detta rum. - + %1 has changed their avatar and changed their display name to %2. @@ -1750,7 +1801,7 @@ Exempel: https://server.my:8787 TimelineRow - + Edited @@ -1758,17 +1809,32 @@ Exempel: https://server.my:8787 TimelineView - + No room open Inget rum öppet - + %1 member(s) - + + join the conversation + + + + + accept invite + + + + + decline invite + + + + Back to room list Tillbaka till rumlista @@ -1776,7 +1842,7 @@ Exempel: https://server.my:8787 TimelineViewManager - + No encrypted private chat found with this user. Create an encrypted private chat with this user and try again. Ingen krypterad privat chatt med denna användare kunde hittas. Skapa en krypterad privat chatt med användaren och försök igen. @@ -1784,18 +1850,17 @@ Exempel: https://server.my:8787 TopBar - + Back to room list Tillbaka till rumlista - - + No room selected Inget rum markerat - + Room options Alternativ för rum @@ -1836,7 +1901,7 @@ Exempel: https://server.my:8787 UserProfile - + Global User Profile @@ -1846,7 +1911,7 @@ Exempel: https://server.my:8787 - + Verify Bekräfta @@ -1895,7 +1960,7 @@ Exempel: https://server.my:8787 UserSettings - + Default @@ -1904,7 +1969,7 @@ Exempel: https://server.my:8787 UserSettingsPage - + Minimize to tray Minimera till systemtråg @@ -2358,7 +2423,7 @@ Detta gör vanligtvis att ikonen i aktivitetsfältet animeras på något sätt.< Waiting - + Waiting for other party… Väntar på motparten… @@ -2409,7 +2474,7 @@ Detta gör vanligtvis att ikonen i aktivitetsfältet animeras på något sätt.< descriptiveTime - + Yesterday Igår @@ -2480,19 +2545,6 @@ Detta gör vanligtvis att ikonen i aktivitetsfältet animeras på något sätt.< Öppna reserven, följ stegen och bekräfta när du slutfört dem. - - dialogs::InviteUsers - - - Cancel - Avbryt - - - - User ID to invite - Användar-ID att bjuda in - - dialogs::JoinRoom diff --git a/resources/langs/nheko_zh_CN.ts b/resources/langs/nheko_zh_CN.ts index d468dfaa..c84e3c82 100644 --- a/resources/langs/nheko_zh_CN.ts +++ b/resources/langs/nheko_zh_CN.ts @@ -38,7 +38,7 @@ AwaitingVerificationConfirmation - + Awaiting Confirmation @@ -48,7 +48,7 @@ - + Cancel 取消 @@ -125,7 +125,7 @@ ChatPage - + Failed to invite user: %1 邀请用户失败: %1 @@ -157,12 +157,12 @@ - + Confirm invite - + Do you really want to invite %1 (%2)? @@ -227,12 +227,12 @@ 解禁用户: %1 - + Do you really want to start a private chat with %1? - + Cache migration failed! 缓存迁移失败! @@ -352,7 +352,7 @@ CrossSigningSecrets - + Decrypt secrets @@ -426,12 +426,12 @@ EmojiPicker - + Search - + People @@ -607,7 +607,7 @@ InputBar - + Select a file 选择一个文件 @@ -617,17 +617,43 @@ 所有文件(*) - + Failed to upload media. Please try again. - InviteeItem + InviteDialog - - Remove - 移除 + + Invite users to %1 + + + + + User ID to invite + 要邀请的用户 ID + + + + @joe:matrix.org + Example user id. The name 'joe' can be localized however you want. + + + + + Add + + + + + Invite + + + + + Cancel + 取消 @@ -740,23 +766,10 @@ Example: https://server.my:8787 - - MemberList - - - Room members - 聊天室成员 - - - - OK - - - MessageDelegate - + removed @@ -767,7 +780,7 @@ Example: https://server.my:8787 - + room name changed to: %1 @@ -777,7 +790,7 @@ Example: https://server.my:8787 - + topic changed to: %1 @@ -787,17 +800,17 @@ Example: https://server.my:8787 - + %1 changed the room avatar - + %1 created and configured room: %2 - + %1 placed a voice call. @@ -812,17 +825,17 @@ Example: https://server.my:8787 - + %1 answered the call. - + %1 ended the call. - + Negotiating call... @@ -830,7 +843,7 @@ Example: https://server.my:8787 MessageInput - + Hang up @@ -851,6 +864,11 @@ Example: https://server.my:8787 + Stickers + + + + Emoji @@ -868,17 +886,17 @@ Example: https://server.my:8787 MessageView - + Edit - + React - + Reply @@ -888,7 +906,7 @@ Example: https://server.my:8787 - + &Copy @@ -1096,7 +1114,7 @@ Example: https://server.my:8787 Placeholder - + unimplemented event: @@ -1216,7 +1234,7 @@ Example: https://server.my:8787 ReplyPopup - + Close @@ -1229,7 +1247,7 @@ Example: https://server.my:8787 RoomInfo - + no version stored @@ -1237,7 +1255,7 @@ Example: https://server.my:8787 RoomList - + New tag @@ -1277,17 +1295,7 @@ Example: https://server.my:8787 - - Accept - 接受 - - - - Decline - 拒绝 - - - + Status Message @@ -1337,20 +1345,41 @@ Example: https://server.my:8787 用户设置 + + RoomMembers + + + Members of %1 + + + + + %n people in %1 + Summary above list of members + + + + + + + Invite more people + + + RoomSettings - + Room Settings - + %1 member(s) - + SETTINGS @@ -1432,11 +1461,6 @@ Example: https://server.my:8787 Room Version - - - OK - - Failed to enable encryption: %1 @@ -1469,6 +1493,24 @@ Example: https://server.my:8787 上传图像失败:%s + + RoomlistModel + + + Pending invite. + + + + + Previewing this room + + + + + No preview available + + + ScreenShare @@ -1526,7 +1568,7 @@ Example: https://server.my:8787 StatusIndicator - + Failed @@ -1546,6 +1588,14 @@ Example: https://server.my:8787 + + StickerPicker + + + Search + + + Success @@ -1567,7 +1617,7 @@ Example: https://server.my:8787 TimelineModel - + Message redaction failed: %1 删除消息失败:%1 @@ -1578,7 +1628,7 @@ Example: https://server.my:8787 - + Save image 保存图像 @@ -1711,12 +1761,12 @@ Example: https://server.my:8787 - + You joined this room. 您已加入此房间 - + %1 has changed their avatar and changed their display name to %2. @@ -1745,7 +1795,7 @@ Example: https://server.my:8787 TimelineRow - + Edited @@ -1753,17 +1803,32 @@ Example: https://server.my:8787 TimelineView - + No room open - + %1 member(s) - + + join the conversation + + + + + accept invite + + + + + decline invite + + + + Back to room list @@ -1771,7 +1836,7 @@ Example: https://server.my:8787 TimelineViewManager - + No encrypted private chat found with this user. Create an encrypted private chat with this user and try again. @@ -1779,18 +1844,17 @@ Example: https://server.my:8787 TopBar - + Back to room list - - + No room selected - + Room options 聊天室选项 @@ -1831,7 +1895,7 @@ Example: https://server.my:8787 UserProfile - + Global User Profile @@ -1841,7 +1905,7 @@ Example: https://server.my:8787 - + Verify @@ -1890,7 +1954,7 @@ Example: https://server.my:8787 UserSettings - + Default @@ -1899,7 +1963,7 @@ Example: https://server.my:8787 UserSettingsPage - + Minimize to tray 最小化至托盘 @@ -2345,7 +2409,7 @@ This usually causes the application icon in the task bar to animate in some fash Waiting - + Waiting for other party… @@ -2396,7 +2460,7 @@ This usually causes the application icon in the task bar to animate in some fash descriptiveTime - + Yesterday @@ -2467,19 +2531,6 @@ This usually causes the application icon in the task bar to animate in some fash - - dialogs::InviteUsers - - - Cancel - 取消 - - - - User ID to invite - 要邀请的用户 ID - - dialogs::JoinRoom diff --git a/resources/qml/Completer.qml b/resources/qml/Completer.qml index 333fb11d..00fc3216 100644 --- a/resources/qml/Completer.qml +++ b/resources/qml/Completer.qml @@ -70,7 +70,7 @@ Popup { onCompleterNameChanged: { if (completerName) { if (completerName == "user") - completer = TimelineManager.completerFor(completerName, room.roomId()); + completer = TimelineManager.completerFor(completerName, room.roomId); else completer = TimelineManager.completerFor(completerName); completer.setSearchString(""); diff --git a/resources/qml/InviteDialog.qml b/resources/qml/InviteDialog.qml new file mode 100644 index 00000000..50287ad5 --- /dev/null +++ b/resources/qml/InviteDialog.qml @@ -0,0 +1,159 @@ +// SPDX-FileCopyrightText: 2021 Nheko Contributors +// +// SPDX-License-Identifier: GPL-3.0-or-later + +import QtQuick 2.12 +import QtQuick.Controls 2.12 +import QtQuick.Layouts 1.12 +import im.nheko 1.0 + +ApplicationWindow { + id: inviteDialogRoot + + property string roomId + property string plainRoomName + property InviteesModel invitees + + function addInvite() { + if (inviteeEntry.isValidMxid) { + invitees.addUser(inviteeEntry.text); + inviteeEntry.clear(); + } + } + + function cleanUpAndClose() { + if (inviteeEntry.isValidMxid) + addInvite(); + + invitees.accept(); + close(); + } + + title: qsTr("Invite users to %1").arg(plainRoomName) + x: MainWindow.x + (MainWindow.width / 2) - (width / 2) + y: MainWindow.y + (MainWindow.height / 2) - (height / 2) + height: 380 + width: 340 + palette: Nheko.colors + color: Nheko.colors.window + + Shortcut { + sequence: "Ctrl+Enter" + onActivated: cleanUpAndClose() + } + + Shortcut { + sequence: StandardKey.Cancel + onActivated: inviteDialogRoot.close() + } + + ColumnLayout { + anchors.fill: parent + anchors.margins: Nheko.paddingMedium + spacing: Nheko.paddingMedium + + Label { + text: qsTr("User ID to invite") + Layout.fillWidth: true + color: Nheko.colors.text + } + + RowLayout { + spacing: Nheko.paddingMedium + + MatrixTextField { + id: inviteeEntry + + property bool isValidMxid: text.match("@.+?:.{3,}") + + backgroundColor: Nheko.colors.window + placeholderText: qsTr("@joe:matrix.org", "Example user id. The name 'joe' can be localized however you want.") + Layout.fillWidth: true + onAccepted: { + if (isValidMxid) + addInvite(); + + } + Component.onCompleted: forceActiveFocus() + Keys.onShortcutOverride: event.accepted = ((event.key === Qt.Key_Return || event.key === Qt.Key_Enter) && (event.modifiers & Qt.ControlModifier)) + Keys.onPressed: { + if ((event.key === Qt.Key_Return || event.key === Qt.Key_Enter) && (event.modifiers === Qt.ControlModifier)) + cleanUpAndClose(); + + } + } + + Button { + text: qsTr("Add") + enabled: inviteeEntry.isValidMxid + onClicked: addInvite() + } + + } + + ListView { + id: inviteesList + + Layout.fillWidth: true + Layout.fillHeight: true + model: invitees + + delegate: RowLayout { + spacing: Nheko.paddingMedium + + Avatar { + width: Nheko.avatarSize + height: Nheko.avatarSize + userid: model.mxid + url: model.avatarUrl.replace("mxc://", "image://MxcImage/") + displayName: model.displayName + onClicked: TimelineManager.openGlobalUserProfile(model.mxid) + } + + ColumnLayout { + spacing: Nheko.paddingSmall + + Label { + text: model.displayName + color: TimelineManager.userColor(model ? model.mxid : "", Nheko.colors.window) + font.pointSize: fontMetrics.font.pointSize + } + + Label { + text: model.mxid + color: Nheko.colors.buttonText + font.pointSize: fontMetrics.font.pointSize * 0.9 + } + + Item { + Layout.fillHeight: true + Layout.fillWidth: true + } + + } + + } + + } + + } + + footer: DialogButtonBox { + id: buttons + + Button { + text: qsTr("Invite") + DialogButtonBox.buttonRole: DialogButtonBox.AcceptRole + enabled: invitees.count > 0 + onClicked: cleanUpAndClose() + } + + Button { + text: qsTr("Cancel") + DialogButtonBox.buttonRole: DialogButtonBox.DestructiveRole + onClicked: inviteDialogRoot.close() + } + + } + +} diff --git a/resources/qml/MatrixText.qml b/resources/qml/MatrixText.qml index 9129b154..35e5f7e7 100644 --- a/resources/qml/MatrixText.qml +++ b/resources/qml/MatrixText.qml @@ -8,6 +8,7 @@ import im.nheko 1.0 TextEdit { id: r + textFormat: TextEdit.RichText readOnly: true focus: false @@ -19,14 +20,13 @@ TextEdit { onLinkActivated: Nheko.openLink(link) ToolTip.visible: hoveredLink ToolTip.text: hoveredLink + Component.onCompleted: { + TimelineManager.fixImageRendering(r.textDocument, r); + } CursorShape { anchors.fill: parent cursorShape: hoveredLink ? Qt.PointingHandCursor : Qt.ArrowCursor } - Component.onCompleted: { - TimelineManager.fixImageRendering(r.textDocument, r) - } - } diff --git a/resources/qml/MatrixTextField.qml b/resources/qml/MatrixTextField.qml index 3c660bac..80732b27 100644 --- a/resources/qml/MatrixTextField.qml +++ b/resources/qml/MatrixTextField.qml @@ -10,6 +10,8 @@ import im.nheko 1.0 TextField { id: input + property alias backgroundColor: backgroundRect.color + palette: Nheko.colors color: Nheko.colors.text @@ -62,6 +64,8 @@ TextField { } background: Rectangle { + id: backgroundRect + color: Nheko.colors.base } diff --git a/resources/qml/MessageInput.qml b/resources/qml/MessageInput.qml index 24f9b0e8..58d71a4e 100644 --- a/resources/qml/MessageInput.qml +++ b/resources/qml/MessageInput.qml @@ -2,6 +2,7 @@ // // SPDX-License-Identifier: GPL-3.0-or-later +import "./emoji" import "./voip" import QtQuick 2.12 import QtQuick.Controls 2.3 @@ -87,7 +88,7 @@ Rectangle { Layout.alignment: Qt.AlignBottom // | Qt.AlignHCenter Layout.maximumHeight: Window.height / 4 Layout.minimumHeight: Settings.fontSize - implicitWidth: inputBar.width - 4 * (22 + 16) - 24 + implicitWidth: inputBar.width - 5 * (22 + 16) - 24 TextArea { id: messageInput @@ -319,6 +320,30 @@ Rectangle { } + ImageButton { + id: stickerButton + + Layout.alignment: Qt.AlignRight | Qt.AlignBottom + Layout.margins: 8 + hoverEnabled: true + width: 22 + height: 22 + image: ":/icons/icons/ui/sticky-note-solid.svg" + ToolTip.visible: hovered + ToolTip.text: qsTr("Stickers") + onClicked: stickerPopup.visible ? stickerPopup.close() : stickerPopup.show(stickerButton, room.roomId, function(row) { + room.input.sticker(stickerPopup.model.sourceModel, row); + TimelineManager.focusMessageInput(); + }) + + StickerPicker { + id: stickerPopup + + colors: Nheko.colors + } + + } + ImageButton { id: emojiButton diff --git a/resources/qml/MessageView.qml b/resources/qml/MessageView.qml index 33dff122..50cbd371 100644 --- a/resources/qml/MessageView.qml +++ b/resources/qml/MessageView.qml @@ -92,16 +92,20 @@ ScrollView { } } - EmojiButton { + ImageButton { id: reactButton visible: chat.model ? chat.model.permissions.canSend(MtxEvent.Reaction) : false width: 16 hoverEnabled: true + image: ":/icons/icons/ui/smile.png" ToolTip.visible: hovered ToolTip.text: qsTr("React") - emojiPicker: emojiPopup - event_id: row.model ? row.model.eventId : "" + onClicked: emojiPopup.visible ? emojiPopup.close() : emojiPopup.show(reactButton, function(emoji) { + var event_id = row.model ? row.model.eventId : ""; + room.input.reaction(event_id, emoji); + TimelineManager.focusMessageInput(); + }) } ImageButton { @@ -337,6 +341,7 @@ ScrollView { required property var timestamp required property int status required property int index + required property int relatedEventCacheBuster required property string previousMessageUserId required property string day required property string previousMessageDay @@ -442,6 +447,7 @@ ScrollView { trustlevel: wrapper.trustlevel timestamp: wrapper.timestamp status: wrapper.status + relatedEventCacheBuster: wrapper.relatedEventCacheBuster y: section.visible && section.active ? section.y + section.height : 0 HoverHandler { @@ -471,12 +477,23 @@ ScrollView { } - footer: Spinner { + footer: Item { anchors.horizontalCenter: parent.horizontalCenter - running: chat.model && chat.model.paginationInProgress - foreground: Nheko.colors.mid + anchors.margins: Nheko.paddingLarge visible: chat.model && chat.model.paginationInProgress - z: 3 + // hacky, but works + height: loadingSpinner.height + 2 * Nheko.paddingLarge + + Spinner { + id: loadingSpinner + + anchors.centerIn: parent + anchors.margins: Nheko.paddingLarge + running: chat.model && chat.model.paginationInProgress + foreground: Nheko.colors.mid + z: 3 + } + } } diff --git a/resources/qml/RoomList.qml b/resources/qml/RoomList.qml index a1ce8d7e..2be5fe92 100644 --- a/resources/qml/RoomList.qml +++ b/resources/qml/RoomList.qml @@ -33,8 +33,8 @@ Page { Connections { onActiveTimelineChanged: { - roomlist.positionViewAtIndex(Rooms.roomidToIndex(Rooms.currentRoom.roomId()), ListView.Contain); - console.log("Test" + Rooms.currentRoom.roomId() + " " + Rooms.roomidToIndex(Rooms.currentRoom.roomId())); + roomlist.positionViewAtIndex(Rooms.roomidToIndex(Rooms.currentRoom.roomId), ListView.Contain); + console.log("Test" + Rooms.currentRoom.roomId + " " + Rooms.roomidToIndex(Rooms.currentRoom.roomId)); } target: TimelineManager } @@ -61,9 +61,19 @@ Page { } } + Platform.MessageDialog { + id: leaveRoomDialog + + title: qsTr("Leave Room") + text: qsTr("Are you sure you want to leave this room?") + modality: Qt.Modal + onAccepted: Rooms.leave(roomContextMenu.roomid) + buttons: Dialog.Ok | Dialog.Cancel + } + Platform.MenuItem { text: qsTr("Leave room") - onTriggered: Rooms.leave(roomContextMenu.roomid) + onTriggered: leaveRoomDialog.open() } Platform.MenuSeparator { @@ -133,7 +143,7 @@ Page { states: [ State { name: "highlight" - when: hovered.hovered && !((Rooms.currentRoom && roomId == Rooms.currentRoom.roomId()) || Rooms.currentRoomPreview.roomid == roomId) + when: hovered.hovered && !((Rooms.currentRoom && roomId == Rooms.currentRoom.roomId) || Rooms.currentRoomPreview.roomid == roomId) PropertyChanges { target: roomItem @@ -147,7 +157,7 @@ Page { }, State { name: "selected" - when: (Rooms.currentRoom && roomId == Rooms.currentRoom.roomId()) || Rooms.currentRoomPreview.roomid == roomId + when: (Rooms.currentRoom && roomId == Rooms.currentRoom.roomId) || Rooms.currentRoomPreview.roomid == roomId PropertyChanges { target: roomItem diff --git a/resources/qml/RoomMembers.qml b/resources/qml/RoomMembers.qml new file mode 100644 index 00000000..641a08be --- /dev/null +++ b/resources/qml/RoomMembers.qml @@ -0,0 +1,148 @@ +// SPDX-FileCopyrightText: 2021 Nheko Contributors +// +// SPDX-License-Identifier: GPL-3.0-or-later + +import "./ui" +import QtQuick 2.12 +import QtQuick.Controls 2.12 +import QtQuick.Layouts 1.12 +import QtQuick.Window 2.12 +import im.nheko 1.0 + +ApplicationWindow { + id: roomMembersRoot + + property MemberList members + + title: qsTr("Members of %1").arg(members.roomName) + x: MainWindow.x + (MainWindow.width / 2) - (width / 2) + y: MainWindow.y + (MainWindow.height / 2) - (height / 2) + height: 650 + width: 420 + minimumHeight: 420 + palette: Nheko.colors + color: Nheko.colors.window + + Shortcut { + sequence: StandardKey.Cancel + onActivated: roomMembersRoot.close() + } + + ColumnLayout { + anchors.fill: parent + anchors.margins: Nheko.paddingMedium + spacing: Nheko.paddingMedium + + Avatar { + id: roomAvatar + + width: 130 + height: width + displayName: members.roomName + Layout.alignment: Qt.AlignHCenter + url: members.avatarUrl.replace("mxc://", "image://MxcImage/") + onClicked: TimelineManager.openRoomSettings(members.roomId) + } + + ElidedLabel { + font.pixelSize: fontMetrics.font.pixelSize * 2 + fullText: qsTr("%n people in %1", "Summary above list of members", members.memberCount).arg(members.roomName) + Layout.alignment: Qt.AlignHCenter + elideWidth: parent.width - Nheko.paddingMedium + } + + ImageButton { + Layout.alignment: Qt.AlignHCenter + image: ":/icons/icons/ui/add-square-button.png" + hoverEnabled: true + ToolTip.visible: hovered + ToolTip.text: qsTr("Invite more people") + onClicked: TimelineManager.openInviteUsers(members.roomId) + } + + ScrollView { + palette: Nheko.colors + padding: Nheko.paddingMedium + ScrollBar.horizontal.visible: false + Layout.fillHeight: true + Layout.minimumHeight: 200 + Layout.fillWidth: true + + ListView { + id: memberList + + clip: true + spacing: Nheko.paddingMedium + boundsBehavior: Flickable.StopAtBounds + model: members + + ScrollHelper { + flickable: parent + anchors.fill: parent + enabled: !Settings.mobileMode + } + + delegate: RowLayout { + spacing: Nheko.paddingMedium + + Avatar { + width: Nheko.avatarSize + height: Nheko.avatarSize + userid: model.mxid + url: model.avatarUrl.replace("mxc://", "image://MxcImage/") + displayName: model.displayName + onClicked: Rooms.currentRoom.openUserProfile(model.mxid) + } + + ColumnLayout { + spacing: Nheko.paddingSmall + + Label { + text: model.displayName + color: TimelineManager.userColor(model ? model.mxid : "", Nheko.colors.window) + font.pointSize: fontMetrics.font.pointSize + } + + Label { + text: model.mxid + color: Nheko.colors.buttonText + font.pointSize: fontMetrics.font.pointSize * 0.9 + } + + Item { + Layout.fillHeight: true + Layout.fillWidth: true + } + + } + + } + + footer: Item { + width: parent.width + visible: (members.numUsersLoaded < members.memberCount) && members.loadingMoreMembers + // use the default height if it's visible, otherwise no height at all + height: membersLoadingSpinner.height + anchors.margins: Nheko.paddingMedium + + Spinner { + id: membersLoadingSpinner + + anchors.centerIn: parent + height: visible ? 35 : 0 + } + + } + + } + + } + + } + + footer: DialogButtonBox { + standardButtons: DialogButtonBox.Ok + onAccepted: roomMembersRoot.close() + } + +} diff --git a/resources/qml/RoomSettings.qml b/resources/qml/RoomSettings.qml index 4b06401a..b8e527a5 100644 --- a/resources/qml/RoomSettings.qml +++ b/resources/qml/RoomSettings.qml @@ -4,7 +4,7 @@ import "./ui" import Qt.labs.platform 1.1 as Platform -import QtQuick 2.9 +import QtQuick 2.15 import QtQuick.Controls 2.3 import QtQuick.Layouts 1.2 import QtQuick.Window 2.3 @@ -98,13 +98,23 @@ ApplicationWindow { MatrixText { text: roomSettings.roomName - font.pixelSize: 24 + font.pixelSize: fontMetrics.font.pixelSize * 2 Layout.alignment: Qt.AlignHCenter } MatrixText { text: qsTr("%1 member(s)").arg(roomSettings.memberCount) Layout.alignment: Qt.AlignHCenter + + TapHandler { + onTapped: TimelineManager.openRoomMembers(roomSettings.roomId) + } + + CursorShape { + cursorShape: Qt.PointingHandCursor + anchors.fill: parent + } + } } @@ -209,7 +219,7 @@ ApplicationWindow { title: qsTr("End-to-End Encryption") text: qsTr("Encryption is currently experimental and things might break unexpectedly.
Please take note that it can't be disabled afterwards.") - modality: Qt.NonModal + modality: Qt.Modal onAccepted: { if (roomSettings.isEncryptionEnabled) return ; @@ -222,6 +232,17 @@ ApplicationWindow { buttons: Dialog.Ok | Dialog.Cancel } + MatrixText { + text: qsTr("Sticker & Emote Settings") + } + + Button { + text: qsTr("Change") + ToolTip.text: qsTr("Change what packs are enabled, remove packs or create new ones") + onClicked: TimelineManager.openImagePackSettings(roomSettings.roomId) + Layout.alignment: Qt.AlignRight + } + Item { // for adding extra space between sections Layout.fillWidth: true @@ -247,7 +268,7 @@ ApplicationWindow { MatrixText { text: roomSettings.roomId - font.pixelSize: 14 + font.pixelSize: fontMetrics.font.pixelSize * 1.2 Layout.alignment: Qt.AlignRight } @@ -257,16 +278,16 @@ ApplicationWindow { MatrixText { text: roomSettings.roomVersion - font.pixelSize: 14 + font.pixelSize: fontMetrics.font.pixelSize * 1.2 Layout.alignment: Qt.AlignRight } } - Button { - Layout.alignment: Qt.AlignRight - text: qsTr("OK") - onClicked: close() + DialogButtonBox { + Layout.fillWidth: true + standardButtons: DialogButtonBox.Ok + onAccepted: close() } } diff --git a/resources/qml/Root.qml b/resources/qml/Root.qml index 5316e20d..1793d9bc 100644 --- a/resources/qml/Root.qml +++ b/resources/qml/Root.qml @@ -4,6 +4,7 @@ import "./delegates" import "./device-verification" +import "./dialogs" import "./emoji" import "./voip" import Qt.labs.platform 1.1 as Platform @@ -47,6 +48,14 @@ Page { } + Component { + id: roomMembersComponent + + RoomMembers { + } + + } + Component { id: mobileCallInviteDialog @@ -63,6 +72,30 @@ Page { } + Component { + id: deviceVerificationDialog + + DeviceVerification { + } + + } + + Component { + id: inviteDialog + + InviteDialog { + } + + } + + Component { + id: packSettingsComponent + + ImagePackSettingsDialog { + } + + } + Shortcut { sequence: "Ctrl+K" onActivated: { @@ -82,14 +115,6 @@ Page { onActivated: Rooms.previousRoom() } - Component { - id: deviceVerificationDialog - - DeviceVerification { - } - - } - Connections { target: TimelineManager onNewDeviceVerificationRequest: { @@ -104,6 +129,12 @@ Page { }); userProfile.show(); } + onShowImagePackSettings: { + var packSet = packSettingsComponent.createObject(timelineRoot, { + "packlist": packlist + }); + packSet.show(); + } } Connections { @@ -116,6 +147,31 @@ Page { } } + Connections { + target: TimelineManager + onOpenRoomMembersDialog: { + var membersDialog = roomMembersComponent.createObject(timelineRoot, { + "members": members, + "roomName": Rooms.currentRoom.roomName + }); + membersDialog.show(); + } + onOpenRoomSettingsDialog: { + var roomSettings = roomSettingsComponent.createObject(timelineRoot, { + "roomSettings": settings + }); + roomSettings.show(); + } + onOpenInviteUsersDialog: { + var dialog = inviteDialog.createObject(timelineRoot, { + "roomId": Rooms.currentRoom.roomId, + "plainRoomName": Rooms.currentRoom.plainRoomName, + "invitees": invitees + }); + dialog.show(); + } + } + ChatPage { anchors.fill: parent } diff --git a/resources/qml/TimelineRow.qml b/resources/qml/TimelineRow.qml index 58e367a0..755ab503 100644 --- a/resources/qml/TimelineRow.qml +++ b/resources/qml/TimelineRow.qml @@ -40,6 +40,7 @@ Item { required property int trustlevel required property var timestamp required property int status + required property int relatedEventCacheBuster anchors.left: parent.left anchors.right: parent.right @@ -86,29 +87,30 @@ Item { // fancy reply, if this is a reply Reply { function fromModel(role) { - return replyTo != "" ? room.dataById(replyTo, role) : null; + return replyTo != "" ? room.dataById(replyTo, role, r.eventId) : null; } visible: replyTo - userColor: TimelineManager.userColor(userId, Nheko.colors.base) - blurhash: fromModel(Room.Blurhash) ?? "" - body: fromModel(Room.Body) ?? "" - formattedBody: fromModel(Room.FormattedBody) ?? "" + userColor: r.relatedEventCacheBuster, TimelineManager.userColor(userId, Nheko.colors.base) + blurhash: r.relatedEventCacheBuster, fromModel(Room.Blurhash) ?? "" + body: r.relatedEventCacheBuster, fromModel(Room.Body) ?? "" + formattedBody: r.relatedEventCacheBuster, fromModel(Room.FormattedBody) ?? "" eventId: fromModel(Room.EventId) ?? "" - filename: fromModel(Room.Filename) ?? "" - filesize: fromModel(Room.Filesize) ?? "" - proportionalHeight: fromModel(Room.ProportionalHeight) ?? 1 - type: fromModel(Room.Type) ?? MtxEvent.UnknownMessage - typeString: fromModel(Room.TypeString) ?? "" - url: fromModel(Room.Url) ?? "" - originalWidth: fromModel(Room.OriginalWidth) ?? 0 - isOnlyEmoji: fromModel(Room.IsOnlyEmoji) ?? false - userId: fromModel(Room.UserId) ?? "" - userName: fromModel(Room.UserName) ?? "" - thumbnailUrl: fromModel(Room.ThumbnailUrl) ?? "" - roomTopic: fromModel(Room.RoomTopic) ?? "" - roomName: fromModel(Room.RoomName) ?? "" - callType: fromModel(Room.CallType) ?? "" + filename: r.relatedEventCacheBuster, fromModel(Room.Filename) ?? "" + filesize: r.relatedEventCacheBuster, fromModel(Room.Filesize) ?? "" + proportionalHeight: r.relatedEventCacheBuster, fromModel(Room.ProportionalHeight) ?? 1 + type: r.relatedEventCacheBuster, fromModel(Room.Type) ?? MtxEvent.UnknownMessage + typeString: r.relatedEventCacheBuster, fromModel(Room.TypeString) ?? "" + url: r.relatedEventCacheBuster, fromModel(Room.Url) ?? "" + originalWidth: r.relatedEventCacheBuster, fromModel(Room.OriginalWidth) ?? 0 + isOnlyEmoji: r.relatedEventCacheBuster, fromModel(Room.IsOnlyEmoji) ?? false + userId: r.relatedEventCacheBuster, fromModel(Room.UserId) ?? "" + userName: r.relatedEventCacheBuster, fromModel(Room.UserName) ?? "" + thumbnailUrl: r.relatedEventCacheBuster, fromModel(Room.ThumbnailUrl) ?? "" + roomTopic: r.relatedEventCacheBuster, fromModel(Room.RoomTopic) ?? "" + roomName: r.relatedEventCacheBuster, fromModel(Room.RoomName) ?? "" + callType: r.relatedEventCacheBuster, fromModel(Room.CallType) ?? "" + relatedEventCacheBuster: r.relatedEventCacheBuster, fromModel(Room.RelatedEventCacheBuster) ?? 0 } // actual message content @@ -134,6 +136,7 @@ Item { roomTopic: r.roomTopic roomName: r.roomName callType: r.callType + relatedEventCacheBuster: r.relatedEventCacheBuster isReply: false } diff --git a/resources/qml/TimelineView.qml b/resources/qml/TimelineView.qml index 148a5817..c5cc69a6 100644 --- a/resources/qml/TimelineView.qml +++ b/resources/qml/TimelineView.qml @@ -246,17 +246,7 @@ Item { NhekoDropArea { anchors.fill: parent - roomid: room ? room.roomId() : "" - } - - Connections { - target: room - onOpenRoomSettingsDialog: { - var roomSettings = roomSettingsComponent.createObject(timelineRoot, { - "roomSettings": settings - }); - roomSettings.show(); - } + roomid: room ? room.roomId : "" } } diff --git a/resources/qml/TopBar.qml b/resources/qml/TopBar.qml index 58aba0c7..8543d02a 100644 --- a/resources/qml/TopBar.qml +++ b/resources/qml/TopBar.qml @@ -24,7 +24,7 @@ Rectangle { TapHandler { onSingleTapped: { if (room) - room.openRoomSettings(); + TimelineManager.openRoomSettings(room.roomId); eventPoint.accepted = true; } @@ -66,7 +66,7 @@ Rectangle { displayName: roomName onClicked: { if (room) - room.openRoomSettings(); + TimelineManager.openRoomSettings(room.roomId); } } @@ -111,22 +111,22 @@ Rectangle { Platform.MenuItem { visible: room ? room.permissions.canInvite() : false text: qsTr("Invite users") - onTriggered: TimelineManager.openInviteUsersDialog() + onTriggered: TimelineManager.openInviteUsers(room.roomId) } Platform.MenuItem { text: qsTr("Members") - onTriggered: TimelineManager.openMemberListDialog(room.roomId()) + onTriggered: TimelineManager.openRoomMembers(room.roomId) } Platform.MenuItem { text: qsTr("Leave room") - onTriggered: TimelineManager.openLeaveRoomDialog(room.roomId()) + onTriggered: TimelineManager.openLeaveRoomDialog(room.roomId) } Platform.MenuItem { text: qsTr("Settings") - onTriggered: room.openRoomSettings() + onTriggered: TimelineManager.openRoomSettings(room.roomId) } } diff --git a/resources/qml/delegates/MessageDelegate.qml b/resources/qml/delegates/MessageDelegate.qml index 0b060629..a98c2a8b 100644 --- a/resources/qml/delegates/MessageDelegate.qml +++ b/resources/qml/delegates/MessageDelegate.qml @@ -29,6 +29,7 @@ Item { required property string roomTopic required property string roomName required property string callType + required property int relatedEventCacheBuster height: chooser.childrenRect.height @@ -231,7 +232,7 @@ Item { body: formatted isOnlyEmoji: false isReply: d.isReply - formatted: qsTr("%1 created and configured room: %2").arg(d.userName).arg(room.roomId()) + formatted: qsTr("%1 created and configured room: %2").arg(d.userName).arg(room.roomId) } } @@ -301,7 +302,7 @@ Item { body: formatted isOnlyEmoji: false isReply: d.isReply - formatted: room.formatPowerLevelEvent(d.eventId) + formatted: d.relatedEventCacheBuster, room.formatPowerLevelEvent(d.eventId) } } @@ -313,7 +314,7 @@ Item { body: formatted isOnlyEmoji: false isReply: d.isReply - formatted: room.formatJoinRuleEvent(d.eventId) + formatted: d.relatedEventCacheBuster, room.formatJoinRuleEvent(d.eventId) } } @@ -325,7 +326,7 @@ Item { body: formatted isOnlyEmoji: false isReply: d.isReply - formatted: room.formatHistoryVisibilityEvent(d.eventId) + formatted: d.relatedEventCacheBuster, room.formatHistoryVisibilityEvent(d.eventId) } } @@ -337,7 +338,7 @@ Item { body: formatted isOnlyEmoji: false isReply: d.isReply - formatted: room.formatGuestAccessEvent(d.eventId) + formatted: d.relatedEventCacheBuster, room.formatGuestAccessEvent(d.eventId) } } @@ -349,7 +350,7 @@ Item { body: formatted isOnlyEmoji: false isReply: d.isReply - formatted: room.formatMemberEvent(d.eventId) + formatted: d.relatedEventCacheBuster, room.formatMemberEvent(d.eventId) } } diff --git a/resources/qml/delegates/Reply.qml b/resources/qml/delegates/Reply.qml index 3a188d78..75e3d617 100644 --- a/resources/qml/delegates/Reply.qml +++ b/resources/qml/delegates/Reply.qml @@ -30,6 +30,7 @@ Item { property string roomTopic property string roomName property string callType + property int relatedEventCacheBuster width: parent.width height: replyContainer.height @@ -95,6 +96,7 @@ Item { roomTopic: r.roomTopic roomName: r.roomName callType: r.callType + relatedEventCacheBuster: r.relatedEventCacheBuster enabled: false width: parent.width isReply: true diff --git a/resources/qml/dialogs/ImagePackSettingsDialog.qml b/resources/qml/dialogs/ImagePackSettingsDialog.qml new file mode 100644 index 00000000..c4b4a885 --- /dev/null +++ b/resources/qml/dialogs/ImagePackSettingsDialog.qml @@ -0,0 +1,309 @@ +// SPDX-FileCopyrightText: 2021 Nheko Contributors +// +// SPDX-License-Identifier: GPL-3.0-or-later + +import ".." +import "../components" +import QtQuick 2.12 +import QtQuick.Controls 2.12 +import QtQuick.Layouts 1.12 +import im.nheko 1.0 + +ApplicationWindow { + id: win + + property ImagePackListModel packlist + property int avatarSize: Math.ceil(fontMetrics.lineSpacing * 2.3) + property SingleImagePackModel currentPack: packlist.packAt(currentPackIndex) + property int currentPackIndex: 0 + readonly property int stickerDim: 128 + readonly property int stickerDimPad: 128 + Nheko.paddingSmall + + title: qsTr("Image pack settings") + x: MainWindow.x + (MainWindow.width / 2) - (width / 2) + y: MainWindow.y + (MainWindow.height / 2) - (height / 2) + height: 400 + width: 600 + palette: Nheko.colors + color: Nheko.colors.base + modality: Qt.NonModal + flags: Qt.Dialog + + AdaptiveLayout { + id: adaptiveView + + anchors.fill: parent + singlePageMode: false + pageIndex: 0 + + AdaptiveLayoutElement { + id: packlistC + + visible: Settings.groupView + minimumWidth: 200 + collapsedWidth: 200 + preferredWidth: 300 + maximumWidth: 300 + + ListView { + model: packlist + clip: true + + ScrollHelper { + flickable: parent + anchors.fill: parent + enabled: !Settings.mobileMode + } + + delegate: Rectangle { + id: packItem + + property color background: Nheko.colors.window + property color importantText: Nheko.colors.text + property color unimportantText: Nheko.colors.buttonText + property color bubbleBackground: Nheko.colors.highlight + property color bubbleText: Nheko.colors.highlightedText + required property string displayName + required property string avatarUrl + required property bool fromAccountData + required property bool fromCurrentRoom + required property int index + + color: background + height: avatarSize + 2 * Nheko.paddingMedium + width: ListView.view.width + state: "normal" + states: [ + State { + name: "highlight" + when: hovered.hovered && !(index == currentPackIndex) + + PropertyChanges { + target: packItem + background: Nheko.colors.dark + importantText: Nheko.colors.brightText + unimportantText: Nheko.colors.brightText + bubbleBackground: Nheko.colors.highlight + bubbleText: Nheko.colors.highlightedText + } + + }, + State { + name: "selected" + when: index == currentPackIndex + + PropertyChanges { + target: packItem + background: Nheko.colors.highlight + importantText: Nheko.colors.highlightedText + unimportantText: Nheko.colors.highlightedText + bubbleBackground: Nheko.colors.highlightedText + bubbleText: Nheko.colors.highlight + } + + } + ] + + TapHandler { + margin: -Nheko.paddingSmall + onSingleTapped: currentPackIndex = index + } + + HoverHandler { + id: hovered + } + + RowLayout { + spacing: Nheko.paddingMedium + anchors.fill: parent + anchors.margins: Nheko.paddingMedium + + Avatar { + // In the future we could show an online indicator by setting the userid for the avatar + //userid: Nheko.currentUser.userid + + id: avatar + + enabled: false + Layout.alignment: Qt.AlignVCenter + height: avatarSize + width: avatarSize + url: avatarUrl.replace("mxc://", "image://MxcImage/") + displayName: packItem.displayName + } + + ColumnLayout { + id: textContent + + Layout.alignment: Qt.AlignLeft + Layout.fillWidth: true + Layout.minimumWidth: 100 + width: parent.width - avatar.width + Layout.preferredWidth: parent.width - avatar.width + spacing: Nheko.paddingSmall + + RowLayout { + Layout.fillWidth: true + spacing: 0 + + ElidedLabel { + Layout.alignment: Qt.AlignBottom + color: packItem.importantText + elideWidth: textContent.width - Nheko.paddingMedium + fullText: displayName + textFormat: Text.PlainText + } + + Item { + Layout.fillWidth: true + } + + } + + RowLayout { + Layout.fillWidth: true + spacing: 0 + + ElidedLabel { + color: packItem.unimportantText + font.pixelSize: fontMetrics.font.pixelSize * 0.9 + elideWidth: textContent.width - Nheko.paddingSmall + fullText: { + if (fromAccountData) + return qsTr("Private pack"); + else if (fromCurrentRoom) + return qsTr("Pack from this room"); + else + return qsTr("Globally enabled pack"); + } + textFormat: Text.PlainText + } + + Item { + Layout.fillWidth: true + } + + } + + } + + } + + } + + } + + } + + AdaptiveLayoutElement { + id: packinfoC + + Rectangle { + color: Nheko.colors.window + + ColumnLayout { + id: packinfo + + property string packName: currentPack ? currentPack.packname : "" + property string avatarUrl: currentPack ? currentPack.avatarUrl : "" + + anchors.fill: parent + anchors.margins: Nheko.paddingLarge + spacing: Nheko.paddingLarge + + Avatar { + url: packinfo.avatarUrl.replace("mxc://", "image://MxcImage/") + displayName: packinfo.packName + height: 100 + width: 100 + Layout.alignment: Qt.AlignHCenter + enabled: false + } + + MatrixText { + text: packinfo.packName + font.pixelSize: 24 + Layout.alignment: Qt.AlignHCenter + } + + GridLayout { + Layout.alignment: Qt.AlignHCenter + visible: currentPack && currentPack.roomid != "" + columns: 2 + rowSpacing: Nheko.paddingMedium + + MatrixText { + text: qsTr("Enable globally") + } + + ToggleButton { + ToolTip.text: qsTr("Enables this pack to be used in all rooms") + checked: currentPack ? currentPack.isGloballyEnabled : false + onClicked: currentPack.isGloballyEnabled = !currentPack.isGloballyEnabled + Layout.alignment: Qt.AlignRight + } + + } + + GridView { + Layout.fillHeight: true + Layout.fillWidth: true + model: currentPack + cellWidth: stickerDimPad + cellHeight: stickerDimPad + boundsBehavior: Flickable.StopAtBounds + clip: true + currentIndex: -1 // prevent sorting from stealing focus + cacheBuffer: 500 + + ScrollHelper { + flickable: parent + anchors.fill: parent + enabled: !Settings.mobileMode + } + + // Individual emoji + delegate: AbstractButton { + width: stickerDim + height: stickerDim + hoverEnabled: true + ToolTip.text: ":" + model.shortcode + ": - " + model.body + ToolTip.visible: hovered + + contentItem: Image { + height: stickerDim + width: stickerDim + source: model.url.replace("mxc://", "image://MxcImage/") + fillMode: Image.PreserveAspectFit + } + + background: Rectangle { + anchors.fill: parent + color: hovered ? Nheko.colors.highlight : 'transparent' + radius: 5 + } + + } + + } + + } + + } + + } + + } + + footer: DialogButtonBox { + id: buttons + + Button { + text: qsTr("Close") + DialogButtonBox.buttonRole: DialogButtonBox.AcceptRole + onClicked: win.close() + } + + } + +} diff --git a/resources/qml/emoji/EmojiButton.qml b/resources/qml/emoji/EmojiButton.qml deleted file mode 100644 index 5f4d23d3..00000000 --- a/resources/qml/emoji/EmojiButton.qml +++ /dev/null @@ -1,23 +0,0 @@ -// SPDX-FileCopyrightText: 2021 Nheko Contributors -// -// SPDX-License-Identifier: GPL-3.0-or-later - -import "../" -import QtQuick 2.10 -import QtQuick.Controls 2.1 -import im.nheko 1.0 -import im.nheko.EmojiModel 1.0 - -ImageButton { - id: emojiButton - - property var colors: currentActivePalette - property var emojiPicker - property string event_id - - image: ":/icons/icons/ui/smile.png" - onClicked: emojiPicker.visible ? emojiPicker.close() : emojiPicker.show(emojiButton, function(emoji) { - room.input.reaction(event_id, emoji); - TimelineManager.focusMessageInput(); - }) -} diff --git a/resources/qml/emoji/EmojiPicker.qml b/resources/qml/emoji/EmojiPicker.qml index 6f10a230..354e340c 100644 --- a/resources/qml/emoji/EmojiPicker.qml +++ b/resources/qml/emoji/EmojiPicker.qml @@ -130,6 +130,7 @@ Menu { boundsBehavior: Flickable.StopAtBounds clip: true currentIndex: -1 // prevent sorting from stealing focus + cacheBuffer: 500 // Individual emoji delegate: AbstractButton { diff --git a/resources/qml/emoji/StickerPicker.qml b/resources/qml/emoji/StickerPicker.qml new file mode 100644 index 00000000..3731a948 --- /dev/null +++ b/resources/qml/emoji/StickerPicker.qml @@ -0,0 +1,180 @@ +// SPDX-FileCopyrightText: 2021 Nheko Contributors +// +// SPDX-License-Identifier: GPL-3.0-or-later + +import "../" +import QtGraphicalEffects 1.0 +import QtQuick 2.9 +import QtQuick.Controls 2.3 +import QtQuick.Layouts 1.3 +import im.nheko 1.0 +import im.nheko.EmojiModel 1.0 + +Menu { + id: stickerPopup + + property var callback + property var colors + property string roomid + property alias model: gridView.model + property var textArea + property real highlightHue: Nheko.colors.highlight.hslHue + property real highlightSat: Nheko.colors.highlight.hslSaturation + property real highlightLight: Nheko.colors.highlight.hslLightness + readonly property int stickerDim: 128 + readonly property int stickerDimPad: 128 + Nheko.paddingSmall + readonly property int stickersPerRow: 3 + + function show(showAt, roomid_, callback) { + console.debug("Showing sticker picker"); + roomid = roomid_; + stickerPopup.callback = callback; + popup(showAt ? showAt : null); + } + + margins: 0 + bottomPadding: 1 + leftPadding: 1 + rightPadding: 1 + modal: true + focus: true + closePolicy: Popup.CloseOnEscape | Popup.CloseOnPressOutside + width: stickersPerRow * stickerDimPad + 20 + + Rectangle { + color: Nheko.colors.window + height: columnView.implicitHeight + 4 + width: stickersPerRow * stickerDimPad + 20 + + ColumnLayout { + id: columnView + + spacing: 0 + anchors.leftMargin: 3 + anchors.rightMargin: 3 + anchors.bottom: parent.bottom + anchors.left: parent.left + anchors.right: parent.right + anchors.topMargin: 2 + + // Search field + TextField { + id: emojiSearch + + Layout.topMargin: 3 + Layout.preferredWidth: stickersPerRow * stickerDimPad + 20 - 6 + palette: Nheko.colors + background: null + placeholderTextColor: Nheko.colors.buttonText + color: Nheko.colors.text + placeholderText: qsTr("Search") + selectByMouse: true + rightPadding: clearSearch.width + onTextChanged: searchTimer.restart() + onVisibleChanged: { + if (visible) + forceActiveFocus(); + + } + + Timer { + id: searchTimer + + interval: 350 // tweak as needed? + onTriggered: stickerPopup.model.searchString = emojiSearch.text + } + + ToolButton { + id: clearSearch + + visible: emojiSearch.text !== '' + icon.source: "image://colorimage/:/icons/icons/ui/round-remove-button.png?" + (clearSearch.hovered ? Nheko.colors.highlight : Nheko.colors.buttonText) + focusPolicy: Qt.NoFocus + onClicked: emojiSearch.clear() + hoverEnabled: true + background: null + + anchors { + verticalCenter: parent.verticalCenter + right: parent.right + } + // clear the default hover effects. + + Image { + height: parent.height - 2 * Nheko.paddingSmall + width: height + source: "image://colorimage/:/icons/icons/ui/round-remove-button.png?" + (clearSearch.hovered ? Nheko.colors.highlight : Nheko.colors.buttonText) + + anchors { + verticalCenter: parent.verticalCenter + right: parent.right + margins: Nheko.paddingSmall + } + + } + + } + + } + + // emoji grid + GridView { + id: gridView + + model: roomid ? TimelineManager.completerFor("stickers", roomid) : null + Layout.preferredHeight: cellHeight * 3.5 + Layout.preferredWidth: stickersPerRow * stickerDimPad + 20 + Layout.leftMargin: 4 + cellWidth: stickerDimPad + cellHeight: stickerDimPad + boundsBehavior: Flickable.StopAtBounds + clip: true + currentIndex: -1 // prevent sorting from stealing focus + cacheBuffer: 500 + + ScrollHelper { + flickable: parent + anchors.fill: parent + enabled: !Settings.mobileMode + } + + // Individual emoji + delegate: AbstractButton { + width: stickerDim + height: stickerDim + hoverEnabled: true + ToolTip.text: ":" + model.shortcode + ": - " + model.body + ToolTip.visible: hovered + // TODO: maybe add favorites at some point? + onClicked: { + console.debug("Picked " + model.shortcode); + stickerPopup.close(); + callback(model.originalRow); + } + + contentItem: Image { + height: stickerDim + width: stickerDim + source: model.url.replace("mxc://", "image://MxcImage/") + fillMode: Image.PreserveAspectFit + } + + background: Rectangle { + anchors.fill: parent + color: hovered ? Nheko.colors.highlight : 'transparent' + radius: 5 + } + + } + + ScrollBar.vertical: ScrollBar { + id: emojiScroll + } + + } + + } + + } + +} diff --git a/resources/qml/voip/PlaceCall.qml b/resources/qml/voip/PlaceCall.qml index 5f564853..97932cc9 100644 --- a/resources/qml/voip/PlaceCall.qml +++ b/resources/qml/voip/PlaceCall.qml @@ -88,7 +88,7 @@ Popup { onClicked: { if (buttonLayout.validateMic()) { Settings.microphone = micCombo.currentText; - CallManager.sendInvite(room.roomId(), CallType.VOICE); + CallManager.sendInvite(room.roomId, CallType.VOICE); close(); } } @@ -102,7 +102,7 @@ Popup { if (buttonLayout.validateMic()) { Settings.microphone = micCombo.currentText; Settings.camera = cameraCombo.currentText; - CallManager.sendInvite(room.roomId(), CallType.VIDEO); + CallManager.sendInvite(room.roomId, CallType.VIDEO); close(); } } diff --git a/resources/qml/voip/ScreenShare.qml b/resources/qml/voip/ScreenShare.qml index a10057b2..8cd43b1c 100644 --- a/resources/qml/voip/ScreenShare.qml +++ b/resources/qml/voip/ScreenShare.qml @@ -136,7 +136,7 @@ Popup { Settings.screenSharePiP = pipCheckBox.checked; Settings.screenShareRemoteVideo = remoteVideoCheckBox.checked; Settings.screenShareHideCursor = hideCursorCheckBox.checked; - CallManager.sendInvite(room.roomId(), CallType.SCREEN, windowCombo.currentIndex); + CallManager.sendInvite(room.roomId, CallType.SCREEN, windowCombo.currentIndex); close(); } } diff --git a/resources/res.qrc b/resources/res.qrc index f41835f9..5d37c397 100644 --- a/resources/res.qrc +++ b/resources/res.qrc @@ -9,7 +9,6 @@ icons/ui/do-not-disturb-rounded-sign@2x.png icons/ui/round-remove-button.png icons/ui/round-remove-button@2x.png - icons/ui/double-tick-indicator.png icons/ui/double-tick-indicator@2x.png icons/ui/lock.png @@ -26,6 +25,7 @@ icons/ui/search@2x.png icons/ui/settings.png icons/ui/settings@2x.png + icons/ui/sticky-note-solid.svg icons/ui/smile.png icons/ui/smile@2x.png icons/ui/speech-bubbles-comment-option.png @@ -54,22 +54,17 @@ icons/ui/pause-symbol@2x.png icons/ui/remove-symbol.png icons/ui/remove-symbol@2x.png - icons/ui/world.png icons/ui/world@2x.png - icons/ui/tag.png icons/ui/tag@2x.png icons/ui/star.png icons/ui/star@2x.png icons/ui/lowprio.png icons/ui/lowprio@2x.png - icons/ui/edit.png icons/ui/edit@2x.png - icons/ui/mail-reply.png - icons/ui/place-call.png icons/ui/end-call.png icons/ui/microphone-mute.png @@ -77,7 +72,6 @@ icons/ui/screen-share.png icons/ui/toggle-camera-view.png icons/ui/video-call.png - icons/emoji-categories/people.png icons/emoji-categories/people@2x.png icons/emoji-categories/nature.png @@ -98,16 +92,12 @@ nheko.png nheko.svg - splash.png splash@2x.png - register.png register@2x.png - login.png login@2x.png - nheko-512.png nheko-256.png nheko-128.png @@ -150,8 +140,8 @@ qml/ForwardCompleter.qml qml/TypingIndicator.qml qml/RoomSettings.qml - qml/emoji/EmojiButton.qml qml/emoji/EmojiPicker.qml + qml/emoji/StickerPicker.qml qml/UserProfile.qml qml/delegates/MessageDelegate.qml qml/delegates/TextMessage.qml @@ -170,6 +160,7 @@ qml/device-verification/Failed.qml qml/device-verification/Success.qml qml/dialogs/InputDialog.qml + qml/dialogs/ImagePackSettingsDialog.qml qml/ui/Ripple.qml qml/ui/Spinner.qml qml/ui/animations/BlinkAnimation.qml @@ -184,6 +175,8 @@ qml/components/AdaptiveLayout.qml qml/components/AdaptiveLayoutElement.qml qml/components/FlatButton.qml + qml/RoomMembers.qml + qml/InviteDialog.qml media/ring.ogg diff --git a/src/Cache.cpp b/src/Cache.cpp index 9304db0e..d651b182 100644 --- a/src/Cache.cpp +++ b/src/Cache.cpp @@ -78,6 +78,8 @@ constexpr auto ENCRYPTED_ROOMS_DB("encrypted_rooms"); constexpr auto INBOUND_MEGOLM_SESSIONS_DB("inbound_megolm_sessions"); //! MegolmSessionIndex -> pickled OlmOutboundGroupSession constexpr auto OUTBOUND_MEGOLM_SESSIONS_DB("outbound_megolm_sessions"); +//! MegolmSessionIndex -> session data about which devices have access to this +constexpr auto MEGOLM_SESSIONS_DATA_DB("megolm_sessions_data_db"); using CachedReceipts = std::multimap>; using Receipts = std::map>; @@ -284,6 +286,7 @@ Cache::setup() // Session management inboundMegolmSessionDb_ = lmdb::dbi::open(txn, INBOUND_MEGOLM_SESSIONS_DB, MDB_CREATE); outboundMegolmSessionDb_ = lmdb::dbi::open(txn, OUTBOUND_MEGOLM_SESSIONS_DB, MDB_CREATE); + megolmSessionDataDb_ = lmdb::dbi::open(txn, MEGOLM_SESSIONS_DATA_DB, MDB_CREATE); txn.commit(); @@ -387,9 +390,14 @@ Cache::importSessionKeys(const mtx::crypto::ExportedSessionKeys &keys) index.session_id = s.session_id; index.sender_key = s.sender_key; + GroupSessionData data{}; + data.forwarding_curve25519_key_chain = s.forwarding_curve25519_key_chain; + if (s.sender_claimed_keys.count("ed25519")) + data.sender_claimed_ed25519_key = s.sender_claimed_keys.at("ed25519"); + auto exported_session = mtx::crypto::import_session(s.session_key); - saveInboundMegolmSession(index, std::move(exported_session)); + saveInboundMegolmSession(index, std::move(exported_session), data); ChatPage::instance()->receivedSessionKey(index.room_id, index.session_id); } } @@ -400,7 +408,8 @@ Cache::importSessionKeys(const mtx::crypto::ExportedSessionKeys &keys) void Cache::saveInboundMegolmSession(const MegolmSessionIndex &index, - mtx::crypto::InboundGroupSessionPtr session) + mtx::crypto::InboundGroupSessionPtr session, + const GroupSessionData &data) { using namespace mtx::crypto; const auto key = json(index).dump(); @@ -420,6 +429,7 @@ Cache::saveInboundMegolmSession(const MegolmSessionIndex &index, } inboundMegolmSessionDb_.put(txn, key, pickled); + megolmSessionDataDb_.put(txn, key, json(data).dump()); txn.commit(); } @@ -464,7 +474,7 @@ Cache::inboundMegolmSessionExists(const MegolmSessionIndex &index) void Cache::updateOutboundMegolmSession(const std::string &room_id, - const OutboundGroupSessionData &data_, + const GroupSessionData &data_, mtx::crypto::OutboundGroupSessionPtr &ptr) { using namespace mtx::crypto; @@ -472,18 +482,20 @@ Cache::updateOutboundMegolmSession(const std::string &room_id, if (!outboundMegolmSessionExists(room_id)) return; - OutboundGroupSessionData data = data_; - data.message_index = olm_outbound_group_session_message_index(ptr.get()); - data.session_id = mtx::crypto::session_id(ptr.get()); - data.session_key = mtx::crypto::session_key(ptr.get()); + GroupSessionData data = data_; + data.message_index = olm_outbound_group_session_message_index(ptr.get()); + MegolmSessionIndex index; + index.room_id = room_id; + index.sender_key = olm::client()->identity_keys().ed25519; + index.session_id = mtx::crypto::session_id(ptr.get()); // Save the updated pickled data for the session. json j; - j["data"] = data; j["session"] = pickle(ptr.get(), SECRET); auto txn = lmdb::txn::begin(env_); outboundMegolmSessionDb_.put(txn, room_id, j.dump()); + megolmSessionDataDb_.put(txn, json(index).dump(), json(data).dump()); txn.commit(); } @@ -498,24 +510,32 @@ Cache::dropOutboundMegolmSession(const std::string &room_id) { auto txn = lmdb::txn::begin(env_); outboundMegolmSessionDb_.del(txn, room_id); + // don't delete session data, so that we can still share the session. txn.commit(); } } void Cache::saveOutboundMegolmSession(const std::string &room_id, - const OutboundGroupSessionData &data, + const GroupSessionData &data_, mtx::crypto::OutboundGroupSessionPtr &session) { using namespace mtx::crypto; const auto pickled = pickle(session.get(), SECRET); + GroupSessionData data = data_; + data.message_index = olm_outbound_group_session_message_index(session.get()); + MegolmSessionIndex index; + index.room_id = room_id; + index.sender_key = olm::client()->identity_keys().ed25519; + index.session_id = mtx::crypto::session_id(session.get()); + json j; - j["data"] = data; j["session"] = pickled; auto txn = lmdb::txn::begin(env_); outboundMegolmSessionDb_.put(txn, room_id, j.dump()); + megolmSessionDataDb_.put(txn, json(index).dump(), json(data).dump()); txn.commit(); } @@ -544,8 +564,17 @@ Cache::getOutboundMegolmSession(const std::string &room_id) auto obj = json::parse(value); OutboundGroupSessionDataRef ref{}; - ref.data = obj.at("data").get(); ref.session = unpickle(obj.at("session"), SECRET); + + MegolmSessionIndex index; + index.room_id = room_id; + index.sender_key = olm::client()->identity_keys().ed25519; + index.session_id = mtx::crypto::session_id(ref.session.get()); + + if (megolmSessionDataDb_.get(txn, json(index).dump(), value)) { + ref.data = nlohmann::json::parse(value).get(); + } + return ref; } catch (std::exception &e) { nhlog::db()->error("Failed to retrieve outbound Megolm Session: {}", e.what()); @@ -553,6 +582,25 @@ Cache::getOutboundMegolmSession(const std::string &room_id) } } +std::optional +Cache::getMegolmSessionData(const MegolmSessionIndex &index) +{ + try { + using namespace mtx::crypto; + + auto txn = ro_txn(env_); + + std::string_view value; + if (megolmSessionDataDb_.get(txn, json(index).dump(), value)) { + return nlohmann::json::parse(value).get(); + } + + return std::nullopt; + } catch (std::exception &e) { + nhlog::db()->error("Failed to retrieve Megolm Session Data: {}", e.what()); + return std::nullopt; + } +} // // OLM sessions. // @@ -829,6 +877,7 @@ Cache::deleteData() lmdb::dbi_close(env_, inboundMegolmSessionDb_); lmdb::dbi_close(env_, outboundMegolmSessionDb_); + lmdb::dbi_close(env_, megolmSessionDataDb_); env_.close(); @@ -3333,6 +3382,86 @@ Cache::getChildRoomIds(const std::string &room_id) return roomids; } +std::vector +Cache::getImagePacks(const std::string &room_id, std::optional stickers) +{ + auto txn = ro_txn(env_); + std::vector infos; + + auto addPack = [&infos, stickers](const mtx::events::msc2545::ImagePack &pack, + const std::string &source_room, + const std::string &state_key) { + if (!pack.pack || !stickers.has_value() || + (stickers.value() ? pack.pack->is_sticker() : pack.pack->is_emoji())) { + ImagePackInfo info; + info.source_room = source_room; + info.state_key = state_key; + info.pack.pack = pack.pack; + + for (const auto &img : pack.images) { + if (img.second.overrides_usage() && + (stickers ? !img.second.is_sticker() : !img.second.is_emoji())) + continue; + + info.pack.images.insert(img); + } + + if (!info.pack.images.empty()) + infos.push_back(std::move(info)); + } + }; + + // packs from account data + if (auto accountpack = + getAccountData(txn, mtx::events::EventType::ImagePackInAccountData, "")) { + auto tmp = + std::get_if>( + &*accountpack); + if (tmp) + addPack(tmp->content, "", ""); + } + + // packs from rooms, that were enabled globally + if (auto roomPacks = getAccountData(txn, mtx::events::EventType::ImagePackRooms, "")) { + auto tmp = + std::get_if>( + &*roomPacks); + if (tmp) { + for (const auto &[room_id2, state_to_d] : tmp->content.rooms) { + // don't add stickers from this room twice + if (room_id2 == room_id) + continue; + + for (const auto &[state_id, d] : state_to_d) { + (void)d; + if (auto pack = + getStateEvent( + txn, room_id2, state_id)) + addPack(pack->content, room_id2, state_id); + } + } + } + } + + // packs from current room + if (auto pack = getStateEvent(txn, room_id)) { + addPack(pack->content, room_id, ""); + } + for (const auto &pack : + getStateEventsWithType(txn, room_id)) { + addPack(pack.content, room_id, pack.state_key); + } + + return infos; +} + +std::optional +Cache::getAccountData(mtx::events::EventType type, const std::string &room_id) +{ + auto txn = ro_txn(env_); + return getAccountData(txn, type, room_id); +} + std::optional Cache::getAccountData(lmdb::txn &txn, mtx::events::EventType type, const std::string &room_id) { @@ -3525,6 +3654,7 @@ to_json(json &j, const UserKeyCache &info) { j["device_keys"] = info.device_keys; j["seen_device_keys"] = info.seen_device_keys; + j["seen_device_ids"] = info.seen_device_ids; j["master_keys"] = info.master_keys; j["master_key_changed"] = info.master_key_changed; j["user_signing_keys"] = info.user_signing_keys; @@ -3538,6 +3668,7 @@ from_json(const json &j, UserKeyCache &info) { info.device_keys = j.value("device_keys", std::map{}); info.seen_device_keys = j.value("seen_device_keys", std::set{}); + info.seen_device_ids = j.value("seen_device_ids", std::set{}); info.master_keys = j.value("master_keys", mtx::crypto::CrossSigningKeys{}); info.master_key_changed = j.value("master_key_changed", false); info.user_signing_keys = j.value("user_signing_keys", mtx::crypto::CrossSigningKeys{}); @@ -3634,6 +3765,15 @@ Cache::updateUserKeys(const std::string &sync_token, const mtx::responses::Query keyReused = true; break; } + if (updateToWrite.seen_device_ids.count( + device_id)) { + nhlog::crypto()->warn( + "device_id '{}' reused by ({})", + device_id, + user); + keyReused = true; + break; + } } if (!keyReused && !oldDeviceKeys.count(device_id)) @@ -3644,6 +3784,7 @@ Cache::updateUserKeys(const std::string &sync_token, const mtx::responses::Query (void)key_id; updateToWrite.seen_device_keys.insert(key); } + updateToWrite.seen_device_ids.insert(device_id); } } db.put(txn, user, json(updateToWrite).dump()); @@ -4077,17 +4218,15 @@ from_json(const json &j, MemberInfo &info) } void -to_json(nlohmann::json &obj, const DeviceAndMasterKeys &msg) +to_json(nlohmann::json &obj, const DeviceKeysToMsgIndex &msg) { - obj["devices"] = msg.devices; - obj["master_keys"] = msg.master_keys; + obj["deviceids"] = msg.deviceids; } void -from_json(const nlohmann::json &obj, DeviceAndMasterKeys &msg) +from_json(const nlohmann::json &obj, DeviceKeysToMsgIndex &msg) { - msg.devices = obj.at("devices").get(); - msg.master_keys = obj.at("master_keys").get(); + msg.deviceids = obj.at("deviceids").get(); } void @@ -4099,30 +4238,31 @@ to_json(nlohmann::json &obj, const SharedWithUsers &msg) void from_json(const nlohmann::json &obj, SharedWithUsers &msg) { - msg.keys = obj.at("keys").get>(); + msg.keys = obj.at("keys").get>(); } void -to_json(nlohmann::json &obj, const OutboundGroupSessionData &msg) +to_json(nlohmann::json &obj, const GroupSessionData &msg) { - obj["session_id"] = msg.session_id; - obj["session_key"] = msg.session_key; obj["message_index"] = msg.message_index; obj["ts"] = msg.timestamp; - obj["initially"] = msg.initially; + obj["sender_claimed_ed25519_key"] = msg.sender_claimed_ed25519_key; + obj["forwarding_curve25519_key_chain"] = msg.forwarding_curve25519_key_chain; + obj["currently"] = msg.currently; } void -from_json(const nlohmann::json &obj, OutboundGroupSessionData &msg) +from_json(const nlohmann::json &obj, GroupSessionData &msg) { - msg.session_id = obj.at("session_id"); - msg.session_key = obj.at("session_key"); msg.message_index = obj.at("message_index"); msg.timestamp = obj.value("ts", 0ULL); - msg.initially = obj.value("initially", SharedWithUsers{}); + msg.sender_claimed_ed25519_key = obj.value("sender_claimed_ed25519_key", ""); + msg.forwarding_curve25519_key_chain = + obj.value("forwarding_curve25519_key_chain", std::vector{}); + msg.currently = obj.value("currently", SharedWithUsers{}); } @@ -4522,7 +4662,7 @@ isRoomMember(const std::string &user_id, const std::string &room_id) // void saveOutboundMegolmSession(const std::string &room_id, - const OutboundGroupSessionData &data, + const GroupSessionData &data, mtx::crypto::OutboundGroupSessionPtr &session) { instance_->saveOutboundMegolmSession(room_id, data, session); @@ -4539,7 +4679,7 @@ outboundMegolmSessionExists(const std::string &room_id) noexcept } void updateOutboundMegolmSession(const std::string &room_id, - const OutboundGroupSessionData &data, + const GroupSessionData &data, mtx::crypto::OutboundGroupSessionPtr &session) { instance_->updateOutboundMegolmSession(room_id, data, session); @@ -4566,9 +4706,10 @@ exportSessionKeys() // void saveInboundMegolmSession(const MegolmSessionIndex &index, - mtx::crypto::InboundGroupSessionPtr session) + mtx::crypto::InboundGroupSessionPtr session, + const GroupSessionData &data) { - instance_->saveInboundMegolmSession(index, std::move(session)); + instance_->saveInboundMegolmSession(index, std::move(session), data); } mtx::crypto::InboundGroupSessionPtr getInboundMegolmSession(const MegolmSessionIndex &index) @@ -4580,6 +4721,11 @@ inboundMegolmSessionExists(const MegolmSessionIndex &index) { return instance_->inboundMegolmSessionExists(index); } +std::optional +getMegolmSessionData(const MegolmSessionIndex &index) +{ + return instance_->getMegolmSessionData(index); +} // // Olm Sessions diff --git a/src/Cache.h b/src/Cache.h index b0520f6b..57a36d73 100644 --- a/src/Cache.h +++ b/src/Cache.h @@ -200,7 +200,7 @@ isRoomMember(const std::string &user_id, const std::string &room_id); // void saveOutboundMegolmSession(const std::string &room_id, - const OutboundGroupSessionData &data, + const GroupSessionData &data, mtx::crypto::OutboundGroupSessionPtr &session); OutboundGroupSessionDataRef getOutboundMegolmSession(const std::string &room_id); @@ -208,7 +208,7 @@ bool outboundMegolmSessionExists(const std::string &room_id) noexcept; void updateOutboundMegolmSession(const std::string &room_id, - const OutboundGroupSessionData &data, + const GroupSessionData &data, mtx::crypto::OutboundGroupSessionPtr &session); void dropOutboundMegolmSession(const std::string &room_id); @@ -223,11 +223,14 @@ exportSessionKeys(); // void saveInboundMegolmSession(const MegolmSessionIndex &index, - mtx::crypto::InboundGroupSessionPtr session); + mtx::crypto::InboundGroupSessionPtr session, + const GroupSessionData &data); mtx::crypto::InboundGroupSessionPtr getInboundMegolmSession(const MegolmSessionIndex &index); bool inboundMegolmSessionExists(const MegolmSessionIndex &index); +std::optional +getMegolmSessionData(const MegolmSessionIndex &index); // // Olm Sessions diff --git a/src/CacheCryptoStructs.h b/src/CacheCryptoStructs.h index 07ca274e..409c9d67 100644 --- a/src/CacheCryptoStructs.h +++ b/src/CacheCryptoStructs.h @@ -27,40 +27,43 @@ enum Trust Q_ENUM_NS(Trust) } -struct DeviceAndMasterKeys +struct DeviceKeysToMsgIndex { - // map from device id or master key id to message_index - std::map devices, master_keys; + // map from device key to message_index + // Using the device id is safe because we check for reuse on device list updates + // Using the device id makes our logic much easier to read. + std::map deviceids; }; struct SharedWithUsers { // userid to keys - std::map keys; + std::map keys; }; // Extra information associated with an outbound megolm session. -struct OutboundGroupSessionData +struct GroupSessionData { - std::string session_id; - std::string session_key; uint64_t message_index = 0; uint64_t timestamp = 0; + std::string sender_claimed_ed25519_key; + std::vector forwarding_curve25519_key_chain; + // who has access to this session. // Rotate, when a user leaves the room and share, when a user gets added. - SharedWithUsers initially, currently; + SharedWithUsers currently; }; void -to_json(nlohmann::json &obj, const OutboundGroupSessionData &msg); +to_json(nlohmann::json &obj, const GroupSessionData &msg); void -from_json(const nlohmann::json &obj, OutboundGroupSessionData &msg); +from_json(const nlohmann::json &obj, GroupSessionData &msg); struct OutboundGroupSessionDataRef { mtx::crypto::OutboundGroupSessionPtr session; - OutboundGroupSessionData data; + GroupSessionData data; }; struct DevicePublicKeys @@ -134,6 +137,8 @@ struct UserKeyCache bool master_key_changed = false; //! Device keys that were already used at least once std::set seen_device_keys; + //! Device ids that were already used at least once + std::set seen_device_ids; }; void diff --git a/src/CacheStructs.h b/src/CacheStructs.h index 28c70055..4a5c5c76 100644 --- a/src/CacheStructs.h +++ b/src/CacheStructs.h @@ -11,6 +11,7 @@ #include #include +#include namespace cache { enum class CacheVersion : int @@ -109,3 +110,10 @@ struct RoomSearchResult std::string room_id; RoomInfo info; }; + +struct ImagePackInfo +{ + mtx::events::msc2545::ImagePack pack; + std::string source_room; + std::string state_key; +}; diff --git a/src/Cache_p.h b/src/Cache_p.h index c76cc717..c9d42202 100644 --- a/src/Cache_p.h +++ b/src/Cache_p.h @@ -97,6 +97,12 @@ public: return getStateEvent(txn, room_id, state_key); } + //! retrieve a specific event from account data + //! pass empty room_id for global account data + std::optional getAccountData( + mtx::events::EventType type, + const std::string &room_id = ""); + //! Retrieve member info from a room. std::vector getMembers(const std::string &room_id, std::size_t startIndex = 0, @@ -225,6 +231,9 @@ public: std::vector getParentRoomIds(const std::string &room_id); std::vector getChildRoomIds(const std::string &room_id); + std::vector getImagePacks(const std::string &room_id, + std::optional stickers); + //! Mark a room that uses e2e encryption. void setEncryptedRoom(lmdb::txn &txn, const std::string &room_id); bool isRoomEncrypted(const std::string &room_id); @@ -238,12 +247,12 @@ public: // Outbound Megolm Sessions // void saveOutboundMegolmSession(const std::string &room_id, - const OutboundGroupSessionData &data, + const GroupSessionData &data, mtx::crypto::OutboundGroupSessionPtr &session); OutboundGroupSessionDataRef getOutboundMegolmSession(const std::string &room_id); bool outboundMegolmSessionExists(const std::string &room_id) noexcept; void updateOutboundMegolmSession(const std::string &room_id, - const OutboundGroupSessionData &data, + const GroupSessionData &data, mtx::crypto::OutboundGroupSessionPtr &session); void dropOutboundMegolmSession(const std::string &room_id); @@ -254,10 +263,12 @@ public: // Inbound Megolm Sessions // void saveInboundMegolmSession(const MegolmSessionIndex &index, - mtx::crypto::InboundGroupSessionPtr session); + mtx::crypto::InboundGroupSessionPtr session, + const GroupSessionData &data); mtx::crypto::InboundGroupSessionPtr getInboundMegolmSession( const MegolmSessionIndex &index); bool inboundMegolmSessionExists(const MegolmSessionIndex &index); + std::optional getMegolmSessionData(const MegolmSessionIndex &index); // // Olm Sessions @@ -676,6 +687,7 @@ private: lmdb::dbi inboundMegolmSessionDb_; lmdb::dbi outboundMegolmSessionDb_; + lmdb::dbi megolmSessionDataDb_; QString localUserId_; QString cacheDirectory_; diff --git a/src/ChatPage.cpp b/src/ChatPage.cpp index 6003eb85..615a15b3 100644 --- a/src/ChatPage.cpp +++ b/src/ChatPage.cpp @@ -116,29 +116,31 @@ ChatPage::ChatPage(QSharedPointer userSettings, QWidget *parent) connect(this, &ChatPage::loggedOut, this, &ChatPage::logout); - connect(view_manager_, &TimelineViewManager::inviteUsers, this, [this](QStringList users) { - const auto room_id = currentRoom().toStdString(); + connect( + view_manager_, + &TimelineViewManager::inviteUsers, + this, + [this](QString roomId, QStringList users) { + for (int ii = 0; ii < users.size(); ++ii) { + QTimer::singleShot(ii * 500, this, [this, roomId, ii, users]() { + const auto user = users.at(ii); - for (int ii = 0; ii < users.size(); ++ii) { - QTimer::singleShot(ii * 500, this, [this, room_id, ii, users]() { - const auto user = users.at(ii); + http::client()->invite_user( + roomId.toStdString(), + user.toStdString(), + [this, user](const mtx::responses::RoomInvite &, + mtx::http::RequestErr err) { + if (err) { + emit showNotification( + tr("Failed to invite user: %1").arg(user)); + return; + } - http::client()->invite_user( - room_id, - user.toStdString(), - [this, user](const mtx::responses::RoomInvite &, - mtx::http::RequestErr err) { - if (err) { - emit showNotification( - tr("Failed to invite user: %1").arg(user)); - return; - } - - emit showNotification(tr("Invited user: %1").arg(user)); - }); - }); - } - }); + emit showNotification(tr("Invited user: %1").arg(user)); + }); + }); + } + }); connect(this, &ChatPage::leftRoom, this, &ChatPage::removeRoom); connect(this, &ChatPage::newRoom, this, &ChatPage::changeRoom, Qt::QueuedConnection); @@ -927,27 +929,33 @@ ChatPage::currentPresence() const void ChatPage::ensureOneTimeKeyCount(const std::map &counts) { - for (const auto &entry : counts) { - if (entry.second < MAX_ONETIME_KEYS) { - const int nkeys = MAX_ONETIME_KEYS - entry.second; + uint16_t count = 0; + if (auto c = counts.find(mtx::crypto::SIGNED_CURVE25519); c != counts.end()) + count = c->second; - nhlog::crypto()->info("uploading {} {} keys", nkeys, entry.first); - olm::client()->generate_one_time_keys(nkeys); + if (count < MAX_ONETIME_KEYS) { + const int nkeys = MAX_ONETIME_KEYS - count; - http::client()->upload_keys( - olm::client()->create_upload_keys_request(), - [](const mtx::responses::UploadKeys &, mtx::http::RequestErr err) { - if (err) { - nhlog::crypto()->warn( - "failed to update one-time keys: {} {}", - err->matrix_error.error, - static_cast(err->status_code)); + nhlog::crypto()->info( + "uploading {} {} keys", nkeys, mtx::crypto::SIGNED_CURVE25519); + olm::client()->generate_one_time_keys(nkeys); + + http::client()->upload_keys( + olm::client()->create_upload_keys_request(), + [](const mtx::responses::UploadKeys &, mtx::http::RequestErr err) { + if (err) { + nhlog::crypto()->warn("failed to update one-time keys: {} {} {}", + err->matrix_error.error, + static_cast(err->status_code), + static_cast(err->error_code)); + + if (err->status_code < 400 || err->status_code >= 500) return; - } + } - olm::mark_keys_as_published(); - }); - } + // mark as published anyway, otherwise we may end up in a loop. + olm::mark_keys_as_published(); + }); } } diff --git a/src/CombinedImagePackModel.cpp b/src/CombinedImagePackModel.cpp new file mode 100644 index 00000000..341a34ec --- /dev/null +++ b/src/CombinedImagePackModel.cpp @@ -0,0 +1,77 @@ +// SPDX-FileCopyrightText: 2021 Nheko Contributors +// +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "CombinedImagePackModel.h" + +#include "Cache_p.h" +#include "CompletionModelRoles.h" + +CombinedImagePackModel::CombinedImagePackModel(const std::string &roomId, + bool stickers, + QObject *parent) + : QAbstractListModel(parent) + , room_id(roomId) +{ + auto packs = cache::client()->getImagePacks(room_id, stickers); + + for (const auto &pack : packs) { + QString packname = + pack.pack.pack ? QString::fromStdString(pack.pack.pack->display_name) : ""; + + for (const auto &img : pack.pack.images) { + ImageDesc i{}; + i.shortcode = QString::fromStdString(img.first); + i.packname = packname; + i.image = img.second; + images.push_back(std::move(i)); + } + } +} + +int +CombinedImagePackModel::rowCount(const QModelIndex &) const +{ + return (int)images.size(); +} + +QHash +CombinedImagePackModel::roleNames() const +{ + return { + {CompletionModel::CompletionRole, "completionRole"}, + {CompletionModel::SearchRole, "searchRole"}, + {CompletionModel::SearchRole2, "searchRole2"}, + {Roles::Url, "url"}, + {Roles::ShortCode, "shortcode"}, + {Roles::Body, "body"}, + {Roles::PackName, "packname"}, + {Roles::OriginalRow, "originalRow"}, + }; +} + +QVariant +CombinedImagePackModel::data(const QModelIndex &index, int role) const +{ + if (hasIndex(index.row(), index.column(), index.parent())) { + switch (role) { + case CompletionModel::CompletionRole: + return QString::fromStdString(images[index.row()].image.url); + case Roles::Url: + return QString::fromStdString(images[index.row()].image.url); + case CompletionModel::SearchRole: + case Roles::ShortCode: + return images[index.row()].shortcode; + case CompletionModel::SearchRole2: + case Roles::Body: + return QString::fromStdString(images[index.row()].image.body); + case Roles::PackName: + return images[index.row()].packname; + case Roles::OriginalRow: + return index.row(); + default: + return {}; + } + } + return {}; +} diff --git a/src/CombinedImagePackModel.h b/src/CombinedImagePackModel.h new file mode 100644 index 00000000..f0f69799 --- /dev/null +++ b/src/CombinedImagePackModel.h @@ -0,0 +1,48 @@ +// SPDX-FileCopyrightText: 2021 Nheko Contributors +// +// SPDX-License-Identifier: GPL-3.0-or-later + +#pragma once + +#include + +#include + +class CombinedImagePackModel : public QAbstractListModel +{ + Q_OBJECT +public: + enum Roles + { + Url = Qt::UserRole, + ShortCode, + Body, + PackName, + OriginalRow, + }; + + CombinedImagePackModel(const std::string &roomId, bool stickers, QObject *parent = nullptr); + QHash roleNames() const override; + int rowCount(const QModelIndex &parent = QModelIndex()) const override; + QVariant data(const QModelIndex &index, int role) const override; + + mtx::events::msc2545::PackImage imageAt(int row) + { + if (row < 0 || static_cast(row) >= images.size()) + return {}; + return images.at(static_cast(row)).image; + } + +private: + std::string room_id; + + struct ImageDesc + { + QString shortcode; + QString packname; + + mtx::events::msc2545::PackImage image; + }; + + std::vector images; +}; diff --git a/src/ImagePackListModel.cpp b/src/ImagePackListModel.cpp new file mode 100644 index 00000000..89f1f68e --- /dev/null +++ b/src/ImagePackListModel.cpp @@ -0,0 +1,76 @@ +// SPDX-FileCopyrightText: 2021 Nheko Contributors +// +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "ImagePackListModel.h" + +#include + +#include "Cache_p.h" +#include "SingleImagePackModel.h" + +ImagePackListModel::ImagePackListModel(const std::string &roomId, QObject *parent) + : QAbstractListModel(parent) + , room_id(roomId) +{ + auto packs_ = cache::client()->getImagePacks(room_id, std::nullopt); + + for (const auto &pack : packs_) { + packs.push_back( + QSharedPointer(new SingleImagePackModel(pack))); + } +} + +int +ImagePackListModel::rowCount(const QModelIndex &) const +{ + return (int)packs.size(); +} + +QHash +ImagePackListModel::roleNames() const +{ + return { + {Roles::DisplayName, "displayName"}, + {Roles::AvatarUrl, "avatarUrl"}, + {Roles::FromAccountData, "fromAccountData"}, + {Roles::FromCurrentRoom, "fromCurrentRoom"}, + {Roles::StateKey, "statekey"}, + {Roles::RoomId, "roomid"}, + }; +} + +QVariant +ImagePackListModel::data(const QModelIndex &index, int role) const +{ + if (hasIndex(index.row(), index.column(), index.parent())) { + const auto &pack = packs.at(index.row()); + switch (role) { + case Roles::DisplayName: + return pack->packname(); + case Roles::AvatarUrl: + return pack->avatarUrl(); + case Roles::FromAccountData: + return pack->roomid().isEmpty(); + case Roles::FromCurrentRoom: + return pack->roomid().toStdString() == this->room_id; + case Roles::StateKey: + return pack->statekey(); + case Roles::RoomId: + return pack->roomid(); + default: + return {}; + } + } + return {}; +} + +SingleImagePackModel * +ImagePackListModel::packAt(int row) +{ + if (row < 0 || static_cast(row) >= packs.size()) + return {}; + auto e = packs.at(row).get(); + QQmlEngine::setObjectOwnership(e, QQmlEngine::CppOwnership); + return e; +} diff --git a/src/ImagePackListModel.h b/src/ImagePackListModel.h new file mode 100644 index 00000000..0a044690 --- /dev/null +++ b/src/ImagePackListModel.h @@ -0,0 +1,37 @@ +// SPDX-FileCopyrightText: 2021 Nheko Contributors +// +// SPDX-License-Identifier: GPL-3.0-or-later + +#pragma once + +#include +#include +#include + +class SingleImagePackModel; +class ImagePackListModel : public QAbstractListModel +{ + Q_OBJECT +public: + enum Roles + { + DisplayName = Qt::UserRole, + AvatarUrl, + FromAccountData, + FromCurrentRoom, + StateKey, + RoomId, + }; + + ImagePackListModel(const std::string &roomId, QObject *parent = nullptr); + QHash roleNames() const override; + int rowCount(const QModelIndex &parent = QModelIndex()) const override; + QVariant data(const QModelIndex &index, int role) const override; + + Q_INVOKABLE SingleImagePackModel *packAt(int row); + +private: + std::string room_id; + + std::vector> packs; +}; diff --git a/src/InviteeItem.cpp b/src/InviteeItem.cpp deleted file mode 100644 index 27f02560..00000000 --- a/src/InviteeItem.cpp +++ /dev/null @@ -1,28 +0,0 @@ -// SPDX-FileCopyrightText: 2021 Nheko Contributors -// -// SPDX-License-Identifier: GPL-3.0-or-later - -#include -#include -#include - -#include "InviteeItem.h" - -constexpr int SidePadding = 10; - -InviteeItem::InviteeItem(mtx::identifiers::User user, QWidget *parent) - : QWidget{parent} - , user_{QString::fromStdString(user.to_string())} -{ - auto topLayout_ = new QHBoxLayout(this); - topLayout_->setSpacing(0); - topLayout_->setContentsMargins(SidePadding, 0, 3 * SidePadding, 0); - - name_ = new QLabel(user_, this); - removeUserBtn_ = new QPushButton(tr("Remove"), this); - - topLayout_->addWidget(name_); - topLayout_->addWidget(removeUserBtn_, 0, Qt::AlignRight); - - connect(removeUserBtn_, &QPushButton::clicked, this, &InviteeItem::removeItem); -} diff --git a/src/InviteeItem.h b/src/InviteeItem.h deleted file mode 100644 index 014541ea..00000000 --- a/src/InviteeItem.h +++ /dev/null @@ -1,31 +0,0 @@ -// SPDX-FileCopyrightText: 2021 Nheko Contributors -// -// SPDX-License-Identifier: GPL-3.0-or-later - -#pragma once - -#include - -#include - -class QPushButton; -class QLabel; - -class InviteeItem : public QWidget -{ - Q_OBJECT - -public: - InviteeItem(mtx::identifiers::User user, QWidget *parent = nullptr); - - QString userID() { return user_; } - -signals: - void removeItem(); - -private: - QString user_; - - QLabel *name_; - QPushButton *removeUserBtn_; -}; diff --git a/src/InviteesModel.cpp b/src/InviteesModel.cpp new file mode 100644 index 00000000..27b2116f --- /dev/null +++ b/src/InviteesModel.cpp @@ -0,0 +1,84 @@ +// SPDX-FileCopyrightText: 2021 Nheko Contributors +// +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "InviteesModel.h" + +#include "Cache.h" +#include "Logging.h" +#include "MatrixClient.h" +#include "mtx/responses/profile.hpp" + +InviteesModel::InviteesModel(QObject *parent) + : QAbstractListModel{parent} +{} + +void +InviteesModel::addUser(QString mxid) +{ + beginInsertRows(QModelIndex(), invitees_.count(), invitees_.count()); + + auto invitee = new Invitee{mxid, this}; + auto indexOfInvitee = invitees_.count(); + connect(invitee, &Invitee::userInfoLoaded, this, [this, indexOfInvitee]() { + emit dataChanged(index(indexOfInvitee), index(indexOfInvitee)); + }); + + invitees_.push_back(invitee); + + endInsertRows(); + emit countChanged(); +} + +QHash +InviteesModel::roleNames() const +{ + return {{Mxid, "mxid"}, {DisplayName, "displayName"}, {AvatarUrl, "avatarUrl"}}; +} + +QVariant +InviteesModel::data(const QModelIndex &index, int role) const +{ + if (!index.isValid() || index.row() >= (int)invitees_.size() || index.row() < 0) + return {}; + + switch (role) { + case Mxid: + return invitees_[index.row()]->mxid_; + case DisplayName: + return invitees_[index.row()]->displayName_; + case AvatarUrl: + return invitees_[index.row()]->avatarUrl_; + default: + return {}; + } +} + +QStringList +InviteesModel::mxids() +{ + QStringList mxidList; + for (int i = 0; i < invitees_.length(); ++i) + mxidList.push_back(invitees_[i]->mxid_); + return mxidList; +} + +Invitee::Invitee(const QString &mxid, QObject *parent) + : QObject{parent} + , mxid_{mxid} +{ + http::client()->get_profile( + mxid_.toStdString(), + [this](const mtx::responses::Profile &res, mtx::http::RequestErr err) { + if (err) { + nhlog::net()->warn("failed to retrieve profile info"); + emit userInfoLoaded(); + return; + } + + displayName_ = QString::fromStdString(res.display_name); + avatarUrl_ = QString::fromStdString(res.avatar_url); + + emit userInfoLoaded(); + }); +} diff --git a/src/InviteesModel.h b/src/InviteesModel.h new file mode 100644 index 00000000..a4e19ebb --- /dev/null +++ b/src/InviteesModel.h @@ -0,0 +1,63 @@ +// SPDX-FileCopyrightText: 2021 Nheko Contributors +// +// SPDX-License-Identifier: GPL-3.0-or-later + +#ifndef INVITEESMODEL_H +#define INVITEESMODEL_H + +#include +#include + +class Invitee : public QObject +{ + Q_OBJECT + +public: + Invitee(const QString &mxid, QObject *parent = nullptr); + +signals: + void userInfoLoaded(); + +private: + const QString mxid_; + QString displayName_; + QString avatarUrl_; + + friend class InviteesModel; +}; + +class InviteesModel : public QAbstractListModel +{ + Q_OBJECT + + Q_PROPERTY(int count READ rowCount NOTIFY countChanged) + +public: + enum Roles + { + Mxid, + DisplayName, + AvatarUrl, + }; + + InviteesModel(QObject *parent = nullptr); + + Q_INVOKABLE void addUser(QString mxid); + + QHash roleNames() const override; + int rowCount(const QModelIndex & = QModelIndex()) const override + { + return (int)invitees_.size(); + } + QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; + QStringList mxids(); + +signals: + void accept(); + void countChanged(); + +private: + QVector invitees_; +}; + +#endif // INVITEESMODEL_H diff --git a/src/MainWindow.cpp b/src/MainWindow.cpp index ed337ca4..c0486d01 100644 --- a/src/MainWindow.cpp +++ b/src/MainWindow.cpp @@ -21,6 +21,7 @@ #include "LoginPage.h" #include "MainWindow.h" #include "MatrixClient.h" +#include "MemberList.h" #include "RegisterPage.h" #include "TrayIcon.h" #include "UserSettingsPage.h" @@ -32,11 +33,9 @@ #include "ui/SnackBar.h" #include "dialogs/CreateRoom.h" -#include "dialogs/InviteUsers.h" #include "dialogs/JoinRoom.h" #include "dialogs/LeaveRoom.h" #include "dialogs/Logout.h" -#include "dialogs/MemberList.h" #include "dialogs/ReadReceipts.h" MainWindow *MainWindow::instance_ = nullptr; @@ -310,14 +309,6 @@ MainWindow::hasActiveUser() settings.contains(prefix + "auth/user_id"); } -void -MainWindow::openMemberListDialog(const QString &room_id) -{ - auto dialog = new dialogs::MemberList(room_id, this); - - showDialog(dialog); -} - void MainWindow::openLeaveRoomDialog(const QString &room_id) { @@ -341,18 +332,6 @@ MainWindow::showOverlayProgressBar() showSolidOverlayModal(spinner_); } -void -MainWindow::openInviteUsersDialog(std::function callback) -{ - auto dialog = new dialogs::InviteUsers(this); - connect(dialog, &dialogs::InviteUsers::sendInvites, this, [callback](QStringList invitees) { - if (!invitees.isEmpty()) - callback(invitees); - }); - - showDialog(dialog); -} - void MainWindow::openJoinRoomDialog(std::function callback) { diff --git a/src/MainWindow.h b/src/MainWindow.h index 3571f079..6d62545c 100644 --- a/src/MainWindow.h +++ b/src/MainWindow.h @@ -65,7 +65,6 @@ public: std::function callback); void openJoinRoomDialog(std::function callback); void openLogoutDialog(); - void openMemberListDialog(const QString &room_id); void openReadReceiptsDialog(const QString &event_id); void hideOverlay(); diff --git a/src/MemberList.cpp b/src/MemberList.cpp new file mode 100644 index 00000000..0ef3b696 --- /dev/null +++ b/src/MemberList.cpp @@ -0,0 +1,111 @@ +// SPDX-FileCopyrightText: 2021 Nheko Contributors +// +// SPDX-License-Identifier: GPL-3.0-or-later + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "MemberList.h" + +#include "Cache.h" +#include "ChatPage.h" +#include "Config.h" +#include "Logging.h" +#include "Utils.h" +#include "timeline/TimelineViewManager.h" +#include "ui/Avatar.h" + +MemberList::MemberList(const QString &room_id, QObject *parent) + : QAbstractListModel{parent} + , room_id_{room_id} +{ + try { + info_ = cache::singleRoomInfo(room_id_.toStdString()); + } catch (const lmdb::error &) { + nhlog::db()->warn("failed to retrieve room info from cache: {}", + room_id_.toStdString()); + } + + try { + auto members = cache::getMembers(room_id_.toStdString()); + addUsers(members); + numUsersLoaded_ = members.size(); + } catch (const lmdb::error &e) { + nhlog::db()->critical("Failed to retrieve members from cache: {}", e.what()); + } +} + +void +MemberList::addUsers(const std::vector &members) +{ + beginInsertRows( + QModelIndex{}, m_memberList.count(), m_memberList.count() + members.size() - 1); + + for (const auto &member : members) + m_memberList.push_back( + {member, + ChatPage::instance()->timelineManager()->rooms()->currentRoom()->avatarUrl( + member.user_id)}); + + endInsertRows(); +} + +QHash +MemberList::roleNames() const +{ + return { + {Mxid, "mxid"}, + {DisplayName, "displayName"}, + {AvatarUrl, "avatarUrl"}, + }; +} + +QVariant +MemberList::data(const QModelIndex &index, int role) const +{ + if (!index.isValid() || index.row() >= (int)m_memberList.size() || index.row() < 0) + return {}; + + switch (role) { + case Mxid: + return m_memberList[index.row()].first.user_id; + case DisplayName: + return m_memberList[index.row()].first.display_name; + case AvatarUrl: + return m_memberList[index.row()].second; + default: + return {}; + } +} + +bool +MemberList::canFetchMore(const QModelIndex &) const +{ + const size_t numMembers = rowCount(); + if (numMembers > 1 && numMembers < info_.member_count) + return true; + else + return false; +} + +void +MemberList::fetchMore(const QModelIndex &) +{ + loadingMoreMembers_ = true; + emit loadingMoreMembersChanged(); + + auto members = cache::getMembers(room_id_.toStdString(), rowCount()); + addUsers(members); + numUsersLoaded_ += members.size(); + emit numUsersLoadedChanged(); + + loadingMoreMembers_ = false; + emit loadingMoreMembersChanged(); +} diff --git a/src/MemberList.h b/src/MemberList.h new file mode 100644 index 00000000..9932f6a4 --- /dev/null +++ b/src/MemberList.h @@ -0,0 +1,66 @@ +// SPDX-FileCopyrightText: 2021 Nheko Contributors +// +// SPDX-License-Identifier: GPL-3.0-or-later + +#pragma once + +#include "CacheStructs.h" +#include + +class MemberList : public QAbstractListModel +{ + Q_OBJECT + + Q_PROPERTY(QString roomName READ roomName NOTIFY roomNameChanged) + Q_PROPERTY(int memberCount READ memberCount NOTIFY memberCountChanged) + Q_PROPERTY(QString avatarUrl READ avatarUrl NOTIFY avatarUrlChanged) + Q_PROPERTY(QString roomId READ roomId NOTIFY roomIdChanged) + Q_PROPERTY(int numUsersLoaded READ numUsersLoaded NOTIFY numUsersLoadedChanged) + Q_PROPERTY(bool loadingMoreMembers READ loadingMoreMembers NOTIFY loadingMoreMembersChanged) + +public: + enum Roles + { + Mxid, + DisplayName, + AvatarUrl, + }; + MemberList(const QString &room_id, QObject *parent = nullptr); + + QHash roleNames() const override; + int rowCount(const QModelIndex &parent = QModelIndex()) const override + { + Q_UNUSED(parent) + return static_cast(m_memberList.size()); + } + QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; + + QString roomName() const { return QString::fromStdString(info_.name); } + int memberCount() const { return info_.member_count; } + QString avatarUrl() const { return QString::fromStdString(info_.avatar_url); } + QString roomId() const { return room_id_; } + int numUsersLoaded() const { return numUsersLoaded_; } + bool loadingMoreMembers() const { return loadingMoreMembers_; } + +signals: + void roomNameChanged(); + void memberCountChanged(); + void avatarUrlChanged(); + void roomIdChanged(); + void numUsersLoadedChanged(); + void loadingMoreMembersChanged(); + +public slots: + void addUsers(const std::vector &users); + +protected: + bool canFetchMore(const QModelIndex &) const override; + void fetchMore(const QModelIndex &) override; + +private: + QVector> m_memberList; + QString room_id_; + RoomInfo info_; + int numUsersLoaded_{0}; + bool loadingMoreMembers_{false}; +}; diff --git a/src/MxcImageProvider.cpp b/src/MxcImageProvider.cpp index ab6540a4..ab0f8152 100644 --- a/src/MxcImageProvider.cpp +++ b/src/MxcImageProvider.cpp @@ -196,7 +196,6 @@ MxcImageProvider::download(const QString &id, image.setText("original filename", QString::fromStdString(originalFilename)); image.setText("mxc url", "mxc://" + id); - image.save(fileInfo.absoluteFilePath()); then(id, requestedSize, image, fileInfo.absoluteFilePath()); }); } catch (std::exception &e) { diff --git a/src/Olm.cpp b/src/Olm.cpp index 4a403cd0..69503e6e 100644 --- a/src/Olm.cpp +++ b/src/Olm.cpp @@ -123,7 +123,17 @@ handle_to_device_messages(const std::vectorquery_keys( + olm_msg.sender, + [olm_msg](const UserKeyCache &userKeys, mtx::http::RequestErr e) { + if (e) { + nhlog::crypto()->error( + "Failed to query user keys, dropping olm " + "message"); + return; + } + handle_olm_message(std::move(olm_msg), userKeys); + }); } catch (const nlohmann::json::exception &e) { nhlog::crypto()->warn( "parsing error for olm message: {} {}", e.what(), j_msg.dump(2)); @@ -197,13 +207,15 @@ handle_to_device_messages(const std::vectorinfo("sender : {}", msg.sender); nhlog::crypto()->info("sender_key: {}", msg.sender_key); const auto my_key = olm::client()->identity_keys().curve25519; + bool failed_decryption = false; + for (const auto &cipher : msg.ciphertext) { // We skip messages not meant for the current device. if (cipher.first != my_key) { @@ -224,6 +236,7 @@ handle_olm_message(const OlmMessage &msg) msg.sender, msg.sender_key, cipher.second); } else { nhlog::crypto()->error("Undecryptable olm message!"); + failed_decryption = true; continue; } } @@ -231,6 +244,57 @@ handle_olm_message(const OlmMessage &msg) if (!payload.is_null()) { mtx::events::collections::DeviceEvents device_event; + // Other properties are included in order to prevent an attacker from + // publishing someone else's curve25519 keys as their own and subsequently + // claiming to have sent messages which they didn't. sender must correspond + // to the user who sent the event, recipient to the local user, and + // recipient_keys to the local ed25519 key. + std::string receiver_ed25519 = payload["recipient_keys"]["ed25519"]; + if (receiver_ed25519.empty() || + receiver_ed25519 != olm::client()->identity_keys().ed25519) { + nhlog::crypto()->warn( + "Decrypted event doesn't include our ed25519: {}", + payload.dump()); + return; + } + std::string receiver = payload["recipient"]; + if (receiver.empty() || receiver != http::client()->user_id().to_string()) { + nhlog::crypto()->warn( + "Decrypted event doesn't include our user_id: {}", + payload.dump()); + return; + } + + // Clients must confirm that the sender_key and the ed25519 field value + // under the keys property match the keys returned by /keys/query for the + // given user, and must also verify the signature of the payload. Without + // this check, a client cannot be sure that the sender device owns the + // private part of the ed25519 key it claims to have in the Olm payload. + // This is crucial when the ed25519 key corresponds to a verified device. + std::string sender_ed25519 = payload["keys"]["ed25519"]; + if (sender_ed25519.empty()) { + nhlog::crypto()->warn( + "Decrypted event doesn't include sender ed25519: {}", + payload.dump()); + return; + } + + bool from_their_device = false; + for (auto [device_id, key] : otherUserDeviceKeys.device_keys) { + if (key.keys.at("curve25519:" + device_id) == msg.sender_key) { + if (key.keys.at("ed25519:" + device_id) == sender_ed25519) { + from_their_device = true; + break; + } + } + } + if (!from_their_device) { + nhlog::crypto()->warn("Decrypted event isn't sent from a device " + "listed by that user! {}", + payload.dump()); + return; + } + { std::string msg_type = payload["type"]; json event_array = json::array(); @@ -242,7 +306,7 @@ handle_olm_message(const OlmMessage &msg) if (temp_events.empty()) { nhlog::crypto()->warn("Decrypted unknown event: {}", payload.dump()); - continue; + return; } device_event = temp_events.at(0); } @@ -276,17 +340,20 @@ handle_olm_message(const OlmMessage &msg) ChatPage::instance()->receivedDeviceVerificationDone(e8->content); } else if (auto roomKey = std::get_if>(&device_event)) { - create_inbound_megolm_session(*roomKey, msg.sender_key); + create_inbound_megolm_session( + *roomKey, msg.sender_key, sender_ed25519); } else if (auto forwardedRoomKey = std::get_if>( &device_event)) { + forwardedRoomKey->content.forwarding_curve25519_key_chain.push_back( + msg.sender_key); import_inbound_megolm_session(*forwardedRoomKey); } else if (auto e = std::get_if>(&device_event)) { auto local_user = http::client()->user_id(); if (msg.sender != local_user.to_string()) - continue; + return; auto secret_name = request_id_to_secret_name.find(e->content.request_id); @@ -306,7 +373,7 @@ handle_olm_message(const OlmMessage &msg) cache::verificationStatus(local_user.to_string()); if (!verificationStatus) - continue; + return; auto deviceKeys = cache::userKeys(local_user.to_string()); std::string sender_device_id; @@ -344,7 +411,6 @@ handle_olm_message(const OlmMessage &msg) "for secrect " "'{}'", name); - return; } }); @@ -360,27 +426,28 @@ handle_olm_message(const OlmMessage &msg) } return; + } else { + failed_decryption = true; } } - try { - auto otherUserDeviceKeys = cache::userKeys(msg.sender); + if (failed_decryption) { + try { + std::map> targets; + for (auto [device_id, key] : otherUserDeviceKeys.device_keys) { + if (key.keys.at("curve25519:" + device_id) == msg.sender_key) + targets[msg.sender].push_back(device_id); + } - if (!otherUserDeviceKeys) - return; - - std::map> targets; - for (auto [device_id, key] : otherUserDeviceKeys->device_keys) { - if (key.keys.at("curve25519:" + device_id) == msg.sender_key) - targets[msg.sender].push_back(device_id); + send_encrypted_to_device_messages( + targets, mtx::events::DeviceEvent{}, true); + nhlog::crypto()->info("Recovering from broken olm channel with {}:{}", + msg.sender, + msg.sender_key); + } catch (std::exception &e) { + nhlog::crypto()->error("Failed to recover from broken olm sessions: {}", + e.what()); } - - send_encrypted_to_device_messages( - targets, mtx::events::DeviceEvent{}, true); - nhlog::crypto()->info( - "Recovering from broken olm channel with {}:{}", msg.sender, msg.sender_key); - } catch (std::exception &e) { - nhlog::crypto()->error("Failed to recover from broken olm sessions: {}", e.what()); } } @@ -450,7 +517,7 @@ encrypt_group_message(const std::string &room_id, const std::string &device_id, std::map> sendSessionTo; mtx::crypto::OutboundGroupSessionPtr session = nullptr; - OutboundGroupSessionData group_session_data; + GroupSessionData group_session_data; if (cache::outboundMegolmSessionExists(room_id)) { auto res = cache::getOutboundMegolmSession(room_id); @@ -519,7 +586,8 @@ encrypt_group_message(const std::string &room_id, const std::string &device_id, } else { // compare devices bool device_removed = false; - for (const auto &dev : session_member_it->second.devices) { + for (const auto &dev : + session_member_it->second.deviceids) { if (!member_it->second || !member_it->second->device_keys.count( dev.first)) { @@ -541,7 +609,7 @@ encrypt_group_message(const std::string &room_id, const std::string &device_id, if (member_it->second) for (const auto &dev : member_it->second->device_keys) - if (!session_member_it->second.devices + if (!session_member_it->second.deviceids .count(dev.first) && (member_it->first != own_user_id || dev.first != device_id)) @@ -571,32 +639,28 @@ encrypt_group_message(const std::string &room_id, const std::string &device_id, const auto session_key = mtx::crypto::session_key(session.get()); // Saving the new megolm session. - OutboundGroupSessionData session_data{}; - session_data.session_id = mtx::crypto::session_id(session.get()); - session_data.session_key = mtx::crypto::session_key(session.get()); - session_data.message_index = 0; - session_data.timestamp = QDateTime::currentMSecsSinceEpoch(); + GroupSessionData session_data{}; + session_data.message_index = 0; + session_data.timestamp = QDateTime::currentMSecsSinceEpoch(); + session_data.sender_claimed_ed25519_key = olm::client()->identity_keys().ed25519; sendSessionTo.clear(); for (const auto &[user, devices] : members) { sendSessionTo[user] = {}; - session_data.initially.keys[user] = {}; + session_data.currently.keys[user] = {}; if (devices) { for (const auto &[device_id_, key] : devices->device_keys) { (void)key; if (device_id != device_id_ || user != own_user_id) { sendSessionTo[user].push_back(device_id_); - session_data.initially.keys[user] - .devices[device_id_] = 0; + session_data.currently.keys[user] + .deviceids[device_id_] = 0; } } } } - cache::saveOutboundMegolmSession(room_id, session_data, session); - group_session_data = std::move(session_data); - { MegolmSessionIndex index; index.room_id = room_id; @@ -604,8 +668,12 @@ encrypt_group_message(const std::string &room_id, const std::string &device_id, index.sender_key = olm::client()->identity_keys().curve25519; auto megolm_session = olm::client()->init_inbound_group_session(session_key); - cache::saveInboundMegolmSession(index, std::move(megolm_session)); + cache::saveInboundMegolmSession( + index, std::move(megolm_session), session_data); } + + cache::saveOutboundMegolmSession(room_id, session_data, session); + group_session_data = std::move(session_data); } mtx::events::DeviceEvent megolm_payload{}; @@ -641,8 +709,8 @@ encrypt_group_message(const std::string &room_id, const std::string &device_id, group_session_data.currently.keys[user] = {}; for (const auto &device_id_ : devices) { - if (!group_session_data.currently.keys[user].devices.count(device_id_)) - group_session_data.currently.keys[user].devices[device_id_] = + if (!group_session_data.currently.keys[user].deviceids.count(device_id_)) + group_session_data.currently.keys[user].deviceids[device_id_] = group_session_data.message_index; } } @@ -704,7 +772,8 @@ try_olm_decryption(const std::string &sender_key, const mtx::events::msg::OlmCip void create_inbound_megolm_session(const mtx::events::DeviceEvent &roomKey, - const std::string &sender_key) + const std::string &sender_key, + const std::string &sender_ed25519) { MegolmSessionIndex index; index.room_id = roomKey.content.room_id; @@ -712,9 +781,13 @@ create_inbound_megolm_session(const mtx::events::DeviceEventinit_inbound_group_session(roomKey.content.session_key); - cache::saveInboundMegolmSession(index, std::move(megolm_session)); + cache::saveInboundMegolmSession(index, std::move(megolm_session), data); } catch (const lmdb::error &e) { nhlog::crypto()->critical("failed to save inbound megolm session: {}", e.what()); return; @@ -741,7 +814,13 @@ import_inbound_megolm_session( try { auto megolm_session = olm::client()->import_inbound_group_session(roomKey.content.session_key); - cache::saveInboundMegolmSession(index, std::move(megolm_session)); + + GroupSessionData data{}; + data.forwarding_curve25519_key_chain = + roomKey.content.forwarding_curve25519_key_chain; + data.sender_claimed_ed25519_key = roomKey.content.sender_claimed_ed25519_key; + + cache::saveInboundMegolmSession(index, std::move(megolm_session), data); } catch (const lmdb::error &e) { nhlog::crypto()->critical("failed to save inbound megolm session: {}", e.what()); return; @@ -817,21 +896,16 @@ handle_key_request_message(const mtx::events::DeviceEventidentity_keys().curve25519) { - nhlog::crypto()->debug("ignoring key request {} because we were not the sender: " - "\nrequested({}) ours({})", - req.content.request_id, - req.content.sender_key, - olm::client()->identity_keys().curve25519); - return; - } - - // Check if we have the keys for the requested session. - auto outboundSession = cache::getOutboundMegolmSession(req.content.room_id); - if (!outboundSession.session) { - nhlog::crypto()->warn("requested session not found in room: {}", - req.content.room_id); + // Check if we were the sender of the session being requested (unless it is actually us + // requesting the session). + if (req.sender != http::client()->user_id().to_string() && + req.content.sender_key != olm::client()->identity_keys().curve25519) { + nhlog::crypto()->debug( + "ignoring key request {} because we did not create the requested session: " + "\nrequested({}) ours({})", + req.content.request_id, + req.content.sender_key, + olm::client()->identity_keys().curve25519); return; } @@ -839,7 +913,15 @@ handle_key_request_message(const mtx::events::DeviceEventidentity_keys().curve25519; + index.sender_key = req.content.sender_key; + + // Check if we have the keys for the requested session. + auto sessionData = cache::getMegolmSessionData(index); + if (!sessionData) { + nhlog::crypto()->warn("requested session not found in room: {}", + req.content.room_id); + return; + } const auto session = cache::getInboundMegolmSession(index); if (!session) { @@ -873,12 +955,12 @@ handle_key_request_message(const mtx::events::DeviceEventcurrently.keys.count(req.sender)) { + if (sessionData->currently.keys.at(req.sender) + .deviceids.count(req.content.requesting_device_id)) { shouldSeeKeys = true; - minimumIndex = outboundSession.data.currently.keys.at(req.sender) - .devices.at(req.content.requesting_device_id); + minimumIndex = sessionData->currently.keys.at(req.sender) + .deviceids.at(req.content.requesting_device_id); } } @@ -906,8 +988,9 @@ handle_key_request_message(const mtx::events::DeviceEventidentity_keys().ed25519; - forward_key.forwarding_curve25519_key_chain = {}; + forward_key.sender_claimed_ed25519_key = sessionData->sender_claimed_ed25519_key; + forward_key.forwarding_curve25519_key_chain = + sessionData->forwarding_curve25519_key_chain; send_megolm_key_to_device( req.sender, req.content.requesting_device_id, forward_key); @@ -928,6 +1011,7 @@ send_megolm_key_to_device(const std::string &user_id, std::map> targets; targets[user_id] = {device_id}; send_encrypted_to_device_messages(targets, room_key); + nhlog::crypto()->debug("Forwarded key to {}:{}", user_id, device_id); } DecryptionResult diff --git a/src/Olm.h b/src/Olm.h index 8479f4f2..a18cbbfb 100644 --- a/src/Olm.h +++ b/src/Olm.h @@ -59,12 +59,13 @@ try_olm_decryption(const std::string &sender_key, const mtx::events::msg::OlmCipherContent &content); void -handle_olm_message(const OlmMessage &msg); +handle_olm_message(const OlmMessage &msg, const UserKeyCache &otherUserDeviceKeys); //! Establish a new inbound megolm session with the decrypted payload from olm. void create_inbound_megolm_session(const mtx::events::DeviceEvent &roomKey, - const std::string &sender_key); + const std::string &sender_key, + const std::string &sender_ed25519); void import_inbound_megolm_session( const mtx::events::DeviceEvent &roomKey); diff --git a/src/SingleImagePackModel.cpp b/src/SingleImagePackModel.cpp new file mode 100644 index 00000000..6c508da0 --- /dev/null +++ b/src/SingleImagePackModel.cpp @@ -0,0 +1,100 @@ +// SPDX-FileCopyrightText: 2021 Nheko Contributors +// +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "SingleImagePackModel.h" + +#include "Cache_p.h" +#include "MatrixClient.h" + +SingleImagePackModel::SingleImagePackModel(ImagePackInfo pack_, QObject *parent) + : QAbstractListModel(parent) + , roomid_(std::move(pack_.source_room)) + , statekey_(std::move(pack_.state_key)) + , pack(std::move(pack_.pack)) +{ + if (!pack.pack) + pack.pack = mtx::events::msc2545::ImagePack::PackDescription{}; + + for (const auto &e : pack.images) + shortcodes.push_back(e.first); +} + +int +SingleImagePackModel::rowCount(const QModelIndex &) const +{ + return (int)shortcodes.size(); +} + +QHash +SingleImagePackModel::roleNames() const +{ + return { + {Roles::Url, "url"}, + {Roles::ShortCode, "shortCode"}, + {Roles::Body, "body"}, + {Roles::IsEmote, "isEmote"}, + {Roles::IsSticker, "isSticker"}, + }; +} + +QVariant +SingleImagePackModel::data(const QModelIndex &index, int role) const +{ + if (hasIndex(index.row(), index.column(), index.parent())) { + const auto &img = pack.images.at(shortcodes.at(index.row())); + switch (role) { + case Url: + return QString::fromStdString(img.url); + case ShortCode: + return QString::fromStdString(shortcodes.at(index.row())); + case Body: + return QString::fromStdString(img.body); + case IsEmote: + return img.overrides_usage() ? img.is_emoji() : pack.pack->is_emoji(); + case IsSticker: + return img.overrides_usage() ? img.is_sticker() : pack.pack->is_sticker(); + default: + return {}; + } + } + return {}; +} + +bool +SingleImagePackModel::isGloballyEnabled() const +{ + if (auto roomPacks = + cache::client()->getAccountData(mtx::events::EventType::ImagePackRooms)) { + if (auto tmp = std::get_if< + mtx::events::EphemeralEvent>( + &*roomPacks)) { + if (tmp->content.rooms.count(roomid_) && + tmp->content.rooms.at(roomid_).count(statekey_)) + return true; + } + } + return false; +} +void +SingleImagePackModel::setGloballyEnabled(bool enabled) +{ + mtx::events::msc2545::ImagePackRooms content{}; + if (auto roomPacks = + cache::client()->getAccountData(mtx::events::EventType::ImagePackRooms)) { + if (auto tmp = std::get_if< + mtx::events::EphemeralEvent>( + &*roomPacks)) { + content = tmp->content; + } + } + + if (enabled) + content.rooms[roomid_][statekey_] = {}; + else + content.rooms[roomid_].erase(statekey_); + + http::client()->put_account_data(content, [](mtx::http::RequestErr) { + // emit this->globallyEnabledChanged(); + }); +} diff --git a/src/SingleImagePackModel.h b/src/SingleImagePackModel.h new file mode 100644 index 00000000..e0c791ba --- /dev/null +++ b/src/SingleImagePackModel.h @@ -0,0 +1,61 @@ +// SPDX-FileCopyrightText: 2021 Nheko Contributors +// +// SPDX-License-Identifier: GPL-3.0-or-later + +#pragma once + +#include + +#include + +#include "CacheStructs.h" + +class SingleImagePackModel : public QAbstractListModel +{ + Q_OBJECT + + Q_PROPERTY(QString roomid READ roomid CONSTANT) + Q_PROPERTY(QString statekey READ statekey CONSTANT) + Q_PROPERTY(QString attribution READ statekey CONSTANT) + Q_PROPERTY(QString packname READ packname CONSTANT) + Q_PROPERTY(QString avatarUrl READ avatarUrl CONSTANT) + Q_PROPERTY(bool isStickerPack READ isStickerPack CONSTANT) + Q_PROPERTY(bool isEmotePack READ isEmotePack CONSTANT) + Q_PROPERTY(bool isGloballyEnabled READ isGloballyEnabled WRITE setGloballyEnabled NOTIFY + globallyEnabledChanged) +public: + enum Roles + { + Url = Qt::UserRole, + ShortCode, + Body, + IsEmote, + IsSticker, + }; + + SingleImagePackModel(ImagePackInfo pack_, QObject *parent = nullptr); + QHash roleNames() const override; + int rowCount(const QModelIndex &parent = QModelIndex()) const override; + QVariant data(const QModelIndex &index, int role) const override; + + QString roomid() const { return QString::fromStdString(roomid_); } + QString statekey() const { return QString::fromStdString(statekey_); } + QString packname() const { return QString::fromStdString(pack.pack->display_name); } + QString attribution() const { return QString::fromStdString(pack.pack->attribution); } + QString avatarUrl() const { return QString::fromStdString(pack.pack->avatar_url); } + bool isStickerPack() const { return pack.pack->is_sticker(); } + bool isEmotePack() const { return pack.pack->is_emoji(); } + + bool isGloballyEnabled() const; + void setGloballyEnabled(bool enabled); + +signals: + void globallyEnabledChanged(); + +private: + std::string roomid_; + std::string statekey_; + + mtx::events::msc2545::ImagePack pack; + std::vector shortcodes; +}; diff --git a/src/UserSettingsPage.cpp b/src/UserSettingsPage.cpp index 740b8979..a062780a 100644 --- a/src/UserSettingsPage.cpp +++ b/src/UserSettingsPage.cpp @@ -91,7 +91,7 @@ UserSettings::load(std::optional profile) privacyScreen_ = settings.value("user/privacy_screen", false).toBool(); privacyScreenTimeout_ = settings.value("user/privacy_screen_timeout", 0).toInt(); shareKeysWithTrustedUsers_ = - settings.value("user/share_keys_with_trusted_users", true).toBool(); + settings.value("user/automatically_share_keys_with_trusted_users", false).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(); @@ -610,7 +610,8 @@ UserSettings::save() settings.setValue("decrypt_sidebar", decryptSidebar_); settings.setValue("privacy_screen", privacyScreen_); settings.setValue("privacy_screen_timeout", privacyScreenTimeout_); - settings.setValue("share_keys_with_trusted_users", shareKeysWithTrustedUsers_); + settings.setValue("automatically_share_keys_with_trusted_users", + shareKeysWithTrustedUsers_); settings.setValue("mobile_mode", mobileMode_); settings.setValue("font_size", baseFontSize_); settings.setValue("typing_notifications", typingNotifications_); @@ -1398,7 +1399,7 @@ UserSettingsPage::exportSessionKeys() QString suffix("-----END MEGOLM SESSION DATA-----"); QString newline("\n"); QTextStream out(&file); - out << prefix << newline << b64 << newline << suffix; + out << prefix << newline << b64 << newline << suffix << newline; file.close(); } catch (const std::exception &e) { QMessageBox::warning(this, tr("Error"), e.what()); diff --git a/src/Utils.cpp b/src/Utils.cpp index eabf50d9..a99831c4 100644 --- a/src/Utils.cpp +++ b/src/Utils.cpp @@ -540,7 +540,7 @@ utils::markdownToHtml(const QString &text, bool rainbowify) // Use colors as described here: // https://shark.comfsm.fm/~dleeling/cis/hsl_rainbow.html auto color = - QColor::fromHslF((charIdx - 1.0) / textLen * (5. / 6.), 1.0, 0.5); + QColor::fromHslF((charIdx - 1.0) / textLen * (5. / 6.), 0.9, 0.5); // format color for HTML auto colorString = color.name(QColor::NameFormat::HexRgb); // create HTML element for current char diff --git a/src/dialogs/InviteUsers.cpp b/src/dialogs/InviteUsers.cpp deleted file mode 100644 index 9dd6085f..00000000 --- a/src/dialogs/InviteUsers.cpp +++ /dev/null @@ -1,158 +0,0 @@ -// SPDX-FileCopyrightText: 2021 Nheko Contributors -// -// SPDX-License-Identifier: GPL-3.0-or-later - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "dialogs/InviteUsers.h" - -#include "Config.h" -#include "InviteeItem.h" -#include "ui/TextField.h" - -#include - -using namespace dialogs; - -InviteUsers::InviteUsers(QWidget *parent) - : QFrame(parent) -{ - setAutoFillBackground(true); - setWindowFlags(Qt::Tool | Qt::WindowStaysOnTopHint); - setWindowModality(Qt::WindowModal); - setAttribute(Qt::WA_DeleteOnClose, true); - - setMinimumWidth(conf::window::minModalWidth); - setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Maximum); - - auto layout = new QVBoxLayout(this); - layout->setSpacing(conf::modals::WIDGET_SPACING); - layout->setMargin(conf::modals::WIDGET_MARGIN); - - auto buttonLayout = new QHBoxLayout(); - buttonLayout->setSpacing(0); - buttonLayout->setMargin(0); - - confirmBtn_ = new QPushButton("Invite", this); - confirmBtn_->setDefault(true); - cancelBtn_ = new QPushButton(tr("Cancel"), this); - - buttonLayout->addStretch(1); - buttonLayout->setSpacing(15); - buttonLayout->addWidget(cancelBtn_); - buttonLayout->addWidget(confirmBtn_); - - inviteeInput_ = new TextField(this); - inviteeInput_->setLabel(tr("User ID to invite")); - - inviteeList_ = new QListWidget; - inviteeList_->setFrameStyle(QFrame::NoFrame); - inviteeList_->setSelectionMode(QAbstractItemView::NoSelection); - inviteeList_->setAttribute(Qt::WA_MacShowFocusRect, 0); - inviteeList_->setSpacing(5); - - errorLabel_ = new QLabel(this); - errorLabel_->setAlignment(Qt::AlignCenter); - - layout->addWidget(inviteeInput_); - layout->addWidget(errorLabel_); - layout->addWidget(inviteeList_); - layout->addLayout(buttonLayout); - - connect(inviteeInput_, &TextField::returnPressed, this, &InviteUsers::addUser); - connect(confirmBtn_, &QPushButton::clicked, [this]() { - if (!inviteeInput_->text().trimmed().isEmpty()) { - addUser(); - } - - emit sendInvites(invitedUsers()); - - inviteeInput_->clear(); - inviteeList_->clear(); - errorLabel_->hide(); - - emit close(); - }); - - connect(cancelBtn_, &QPushButton::clicked, [this]() { - inviteeInput_->clear(); - inviteeList_->clear(); - errorLabel_->hide(); - - emit close(); - }); -} - -void -InviteUsers::addUser() -{ - auto user_id = inviteeInput_->text(); - - try { - namespace ids = mtx::identifiers; - auto user = ids::parse(user_id.toStdString()); - - auto item = new QListWidgetItem(inviteeList_); - auto invitee = new InviteeItem(user, this); - - item->setSizeHint(invitee->minimumSizeHint()); - item->setFlags(Qt::NoItemFlags); - item->setTextAlignment(Qt::AlignCenter); - - inviteeList_->setItemWidget(item, invitee); - - connect(invitee, &InviteeItem::removeItem, this, [this, item]() { - emit removeInvitee(item); - }); - - errorLabel_->hide(); - inviteeInput_->clear(); - } catch (std::exception &e) { - errorLabel_->setText(e.what()); - errorLabel_->show(); - } -} - -void -InviteUsers::removeInvitee(QListWidgetItem *item) -{ - int row = inviteeList_->row(item); - auto widget = inviteeList_->takeItem(row); - - inviteeList_->removeItemWidget(widget); -} - -QStringList -InviteUsers::invitedUsers() const -{ - QStringList users; - - for (int ii = 0; ii < inviteeList_->count(); ++ii) { - auto item = inviteeList_->item(ii); - auto widget = inviteeList_->itemWidget(item); - auto invitee = qobject_cast(widget); - - if (invitee) - users << invitee->userID(); - else - qDebug() << "Cast InviteeItem failed"; - } - - return users; -} - -void -InviteUsers::showEvent(QShowEvent *event) -{ - inviteeInput_->setFocus(); - - QFrame::showEvent(event); -} diff --git a/src/dialogs/InviteUsers.h b/src/dialogs/InviteUsers.h deleted file mode 100644 index e40183c1..00000000 --- a/src/dialogs/InviteUsers.h +++ /dev/null @@ -1,45 +0,0 @@ -// SPDX-FileCopyrightText: 2021 Nheko Contributors -// -// SPDX-License-Identifier: GPL-3.0-or-later - -#pragma once - -#include -#include - -class QPushButton; -class QLabel; -class TextField; -class QListWidget; -class QListWidgetItem; - -namespace dialogs { - -class InviteUsers : public QFrame -{ - Q_OBJECT -public: - explicit InviteUsers(QWidget *parent = nullptr); - -protected: - void showEvent(QShowEvent *event) override; - -signals: - void sendInvites(QStringList invitees); - -private slots: - void removeInvitee(QListWidgetItem *item); - -private: - void addUser(); - QStringList invitedUsers() const; - - QPushButton *confirmBtn_; - QPushButton *cancelBtn_; - - TextField *inviteeInput_; - QLabel *errorLabel_; - - QListWidget *inviteeList_; -}; -} // dialogs diff --git a/src/dialogs/MemberList.cpp b/src/dialogs/MemberList.cpp deleted file mode 100644 index 21eb72b0..00000000 --- a/src/dialogs/MemberList.cpp +++ /dev/null @@ -1,146 +0,0 @@ -// SPDX-FileCopyrightText: 2021 Nheko Contributors -// -// SPDX-License-Identifier: GPL-3.0-or-later - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "dialogs/MemberList.h" - -#include "Cache.h" -#include "ChatPage.h" -#include "Config.h" -#include "Logging.h" -#include "Utils.h" -#include "ui/Avatar.h" - -using namespace dialogs; - -MemberItem::MemberItem(const RoomMember &member, QWidget *parent) - : QWidget(parent) -{ - topLayout_ = new QHBoxLayout(this); - topLayout_->setMargin(0); - - textLayout_ = new QVBoxLayout; - textLayout_->setMargin(0); - textLayout_->setSpacing(0); - - avatar_ = new Avatar(this, 44); - avatar_->setLetter(utils::firstChar(member.display_name)); - - avatar_->setImage(ChatPage::instance()->currentRoom(), member.user_id); - - QFont nameFont; - nameFont.setPointSizeF(nameFont.pointSizeF() * 1.1); - - userId_ = new QLabel(member.user_id, this); - userName_ = new QLabel(member.display_name, this); - userName_->setFont(nameFont); - - textLayout_->addWidget(userName_); - textLayout_->addWidget(userId_); - - topLayout_->addWidget(avatar_); - topLayout_->addLayout(textLayout_, 1); -} - -void -MemberItem::paintEvent(QPaintEvent *) -{ - QStyleOption opt; - opt.init(this); - QPainter p(this); - style()->drawPrimitive(QStyle::PE_Widget, &opt, &p, this); -} - -MemberList::MemberList(const QString &room_id, QWidget *parent) - : QFrame(parent) - , room_id_{room_id} -{ - setAutoFillBackground(true); - setWindowFlags(Qt::Tool | Qt::WindowStaysOnTopHint); - setWindowModality(Qt::WindowModal); - setAttribute(Qt::WA_DeleteOnClose, true); - - auto layout = new QVBoxLayout(this); - layout->setSpacing(conf::modals::WIDGET_SPACING); - layout->setMargin(conf::modals::WIDGET_MARGIN); - - list_ = new QListWidget; - list_->setFrameStyle(QFrame::NoFrame); - list_->setSelectionMode(QAbstractItemView::NoSelection); - list_->setSpacing(5); - - QFont largeFont; - largeFont.setPointSizeF(largeFont.pointSizeF() * 1.5); - - setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Maximum); - setMinimumHeight(list_->sizeHint().height() * 2); - setMinimumWidth(std::max(list_->sizeHint().width() + 4 * conf::modals::WIDGET_MARGIN, - QFontMetrics(largeFont).averageCharWidth() * 30 - - 2 * conf::modals::WIDGET_MARGIN)); - - QFont font; - font.setPointSizeF(font.pointSizeF() * conf::modals::LABEL_MEDIUM_SIZE_RATIO); - - topLabel_ = new QLabel(tr("Room members"), this); - topLabel_->setAlignment(Qt::AlignCenter); - topLabel_->setFont(font); - - auto okBtn = new QPushButton(tr("OK"), this); - - auto buttonLayout = new QHBoxLayout(); - buttonLayout->setSpacing(15); - buttonLayout->addStretch(1); - buttonLayout->addWidget(okBtn); - - layout->addWidget(topLabel_); - layout->addWidget(list_); - layout->addLayout(buttonLayout); - - list_->clear(); - - connect(list_->verticalScrollBar(), &QAbstractSlider::valueChanged, this, [this](int pos) { - if (pos != list_->verticalScrollBar()->maximum()) - return; - - const size_t numMembers = list_->count() - 1; - - if (numMembers > 0) - addUsers(cache::getMembers(room_id_.toStdString(), numMembers)); - }); - - try { - addUsers(cache::getMembers(room_id_.toStdString())); - } catch (const lmdb::error &e) { - nhlog::db()->critical("Failed to retrieve members from cache: {}", e.what()); - } - - auto closeShortcut = new QShortcut(QKeySequence(QKeySequence::Cancel), this); - connect(closeShortcut, &QShortcut::activated, this, &MemberList::close); - connect(okBtn, &QPushButton::clicked, this, &MemberList::close); -} - -void -MemberList::addUsers(const std::vector &members) -{ - for (const auto &member : members) { - auto user = new MemberItem(member, this); - auto item = new QListWidgetItem; - - item->setSizeHint(user->minimumSizeHint()); - item->setFlags(Qt::NoItemFlags); - item->setTextAlignment(Qt::AlignCenter); - - list_->insertItem(list_->count() - 1, item); - list_->setItemWidget(item, user); - } -} diff --git a/src/dialogs/MemberList.h b/src/dialogs/MemberList.h deleted file mode 100644 index b822eec8..00000000 --- a/src/dialogs/MemberList.h +++ /dev/null @@ -1,57 +0,0 @@ -// SPDX-FileCopyrightText: 2021 Nheko Contributors -// -// SPDX-License-Identifier: GPL-3.0-or-later - -#pragma once - -#include -#include - -class Avatar; -class QPushButton; -class QHBoxLayout; -class QLabel; -class QVBoxLayout; - -struct RoomMember; - -template -class QSharedPointer; - -namespace dialogs { - -class MemberItem : public QWidget -{ - Q_OBJECT - -public: - MemberItem(const RoomMember &member, QWidget *parent); - -protected: - void paintEvent(QPaintEvent *) override; - -private: - QHBoxLayout *topLayout_; - QVBoxLayout *textLayout_; - - Avatar *avatar_; - - QLabel *userName_; - QLabel *userId_; -}; - -class MemberList : public QFrame -{ - Q_OBJECT -public: - MemberList(const QString &room_id, QWidget *parent = nullptr); - -public slots: - void addUsers(const std::vector &users); - -private: - QString room_id_; - QLabel *topLabel_; - QListWidget *list_; -}; -} // dialogs diff --git a/src/timeline/InputBar.cpp b/src/timeline/InputBar.cpp index b0747a7c..f17081e5 100644 --- a/src/timeline/InputBar.cpp +++ b/src/timeline/InputBar.cpp @@ -19,6 +19,7 @@ #include "Cache.h" #include "ChatPage.h" +#include "CombinedImagePackModel.h" #include "CompletionProxyModel.h" #include "Config.h" #include "Logging.h" @@ -501,6 +502,31 @@ InputBar::video(const QString &filename, room->sendMessageEvent(video, mtx::events::EventType::RoomMessage); } +void +InputBar::sticker(CombinedImagePackModel *model, int row) +{ + if (!model || row < 0) + return; + + auto img = model->imageAt(row); + + mtx::events::msg::StickerImage sticker{}; + sticker.info = img.info.value_or(mtx::common::ImageInfo{}); + sticker.url = img.url; + sticker.body = img.body; + + if (!room->reply().isEmpty()) { + sticker.relations.relations.push_back( + {mtx::common::RelationType::InReplyTo, room->reply().toStdString()}); + } + if (!room->edit().isEmpty()) { + sticker.relations.relations.push_back( + {mtx::common::RelationType::Replace, room->edit().toStdString()}); + } + + room->sendMessageEvent(sticker, mtx::events::EventType::Sticker); +} + void InputBar::command(QString command, QString args) { diff --git a/src/timeline/InputBar.h b/src/timeline/InputBar.h index c9728379..2e6fb5c0 100644 --- a/src/timeline/InputBar.h +++ b/src/timeline/InputBar.h @@ -12,6 +12,7 @@ #include class TimelineModel; +class CombinedImagePackModel; class QMimeData; class QDropEvent; class QStringList; @@ -57,6 +58,7 @@ public slots: MarkdownOverride useMarkdown = MarkdownOverride::NOT_SPECIFIED, bool rainbowify = false); void reaction(const QString &reactedEvent, const QString &reactionKey); + void sticker(CombinedImagePackModel *model, int row); private slots: void startTyping(); diff --git a/src/timeline/Permissions.cpp b/src/timeline/Permissions.cpp index 1eaab468..e4957045 100644 --- a/src/timeline/Permissions.cpp +++ b/src/timeline/Permissions.cpp @@ -8,9 +8,9 @@ #include "MatrixClient.h" #include "TimelineModel.h" -Permissions::Permissions(TimelineModel *parent) +Permissions::Permissions(QString roomId, QObject *parent) : QObject(parent) - , room(parent) + , roomId_(roomId) { invalidate(); } @@ -19,7 +19,7 @@ void Permissions::invalidate() { pl = cache::client() - ->getStateEvent(room->roomId().toStdString()) + ->getStateEvent(roomId_.toStdString()) .value_or(mtx::events::StateEvent{}) .content; } diff --git a/src/timeline/Permissions.h b/src/timeline/Permissions.h index f7e6f389..7aab1ddb 100644 --- a/src/timeline/Permissions.h +++ b/src/timeline/Permissions.h @@ -15,7 +15,7 @@ class Permissions : public QObject Q_OBJECT public: - Permissions(TimelineModel *parent); + Permissions(QString roomId, QObject *parent = nullptr); Q_INVOKABLE bool canInvite(); Q_INVOKABLE bool canBan(); @@ -28,6 +28,6 @@ public: void invalidate(); private: - TimelineModel *room; + QString roomId_; mtx::events::state::PowerLevels pl; }; diff --git a/src/timeline/TimelineModel.cpp b/src/timeline/TimelineModel.cpp index ab11f99b..ee5564a5 100644 --- a/src/timeline/TimelineModel.cpp +++ b/src/timeline/TimelineModel.cpp @@ -25,6 +25,7 @@ #include "Logging.h" #include "MainWindow.h" #include "MatrixClient.h" +#include "MemberList.h" #include "MxcImageProvider.h" #include "Olm.h" #include "TimelineViewManager.h" @@ -317,6 +318,7 @@ TimelineModel::TimelineModel(TimelineViewManager *manager, QString room_id, QObj , events(room_id.toStdString(), this) , room_id_(room_id) , manager_(manager) + , permissions_{room_id} { lastMessage_.timestamp = 0; @@ -325,6 +327,10 @@ TimelineModel::TimelineModel(TimelineViewManager *manager, QString room_id, QObj this->isSpace_ = create->content.type == mtx::events::state::room_type::space; this->isEncrypted_ = cache::isRoomEncrypted(room_id_.toStdString()); + // this connection will simplify adding the plainRoomNameChanged() signal everywhere that it + // needs to be + connect(this, &TimelineModel::roomNameChanged, this, &TimelineModel::plainRoomNameChanged); + connect( this, &TimelineModel::redactionFailed, @@ -344,6 +350,7 @@ TimelineModel::TimelineModel(TimelineViewManager *manager, QString room_id, QObj &EventStore::dataChanged, this, [this](int from, int to) { + relatedEventCacheBuster++; nhlog::ui()->debug( "data changed {} to {}", events.size() - to - 1, events.size() - from - 1); emit dataChanged(index(events.size() - to - 1, 0), @@ -443,6 +450,7 @@ TimelineModel::roleNames() const {RoomTopic, "roomTopic"}, {CallType, "callType"}, {Dump, "dump"}, + {RelatedEventCacheBuster, "relatedEventCacheBuster"}, }; } int @@ -676,6 +684,8 @@ TimelineModel::data(const mtx::events::collections::TimelineEvents &event, int r return QVariant(m); } + case RelatedEventCacheBuster: + return relatedEventCacheBuster; default: return QVariant(); } @@ -710,6 +720,14 @@ TimelineModel::data(const QModelIndex &index, int role) const return data(*event, role); } +QVariant +TimelineModel::dataById(QString id, int role, QString relatedTo) +{ + if (auto event = events.get(id.toStdString(), relatedTo.toStdString())) + return data(*event, role); + return QVariant(); +} + bool TimelineModel::canFetchMore(const QModelIndex &) const { @@ -1048,14 +1066,6 @@ TimelineModel::openUserProfile(QString userid) emit manager_->openProfile(userProfile); } -void -TimelineModel::openRoomSettings() -{ - RoomSettings *settings = new RoomSettings(roomId(), this); - connect(this, &TimelineModel::roomAvatarUrlChanged, settings, &RoomSettings::avatarChanged); - openRoomSettingsDialog(settings); -} - void TimelineModel::replyAction(QString id) { @@ -1292,6 +1302,14 @@ struct SendMessageVisitor sendRoomEvent(msg); } + void operator()(mtx::events::Sticker msg) + { + msg.type = mtx::events::EventType::Sticker; + if (cache::isRoomEncrypted(model_->room_id_.toStdString())) { + model_->sendEncryptedMessage(msg, mtx::events::EventType::Sticker); + } else + emit model_->addPendingMessageToStore(msg); + } TimelineModel *model_; }; @@ -1301,6 +1319,7 @@ TimelineModel::addPendingMessage(mtx::events::collections::TimelineEvents event) { std::visit( [](auto &msg) { + // gets overwritten for reactions and stickers in SendMessageVisitor msg.type = mtx::events::EventType::RoomMessage; msg.event_id = "m" + http::client()->generate_txn_id(); msg.sender = http::client()->user_id().to_string(); diff --git a/src/timeline/TimelineModel.h b/src/timeline/TimelineModel.h index a3c973d6..0e2ce153 100644 --- a/src/timeline/TimelineModel.h +++ b/src/timeline/TimelineModel.h @@ -17,6 +17,8 @@ #include "CacheStructs.h" #include "EventStore.h" #include "InputBar.h" +#include "InviteesModel.h" +#include "MemberList.h" #include "Permissions.h" #include "ui/RoomSettings.h" #include "ui/UserProfile.h" @@ -158,7 +160,9 @@ class TimelineModel : public QAbstractListModel Q_PROPERTY(QString edit READ edit WRITE setEdit NOTIFY editChanged RESET resetEdit) Q_PROPERTY( bool paginationInProgress READ paginationInProgress NOTIFY paginationInProgressChanged) + Q_PROPERTY(QString roomId READ roomId CONSTANT) Q_PROPERTY(QString roomName READ roomName NOTIFY roomNameChanged) + Q_PROPERTY(QString plainRoomName READ plainRoomName NOTIFY plainRoomNameChanged) Q_PROPERTY(QString roomAvatarUrl READ roomAvatarUrl NOTIFY roomAvatarUrlChanged) Q_PROPERTY(QString roomTopic READ roomTopic NOTIFY roomTopicChanged) Q_PROPERTY(int roomMemberCount READ roomMemberCount NOTIFY roomMemberCountChanged) @@ -208,6 +212,7 @@ public: RoomTopic, CallType, Dump, + RelatedEventCacheBuster, }; Q_ENUM(Roles); @@ -215,10 +220,7 @@ public: int rowCount(const QModelIndex &parent = QModelIndex()) const override; QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; QVariant data(const mtx::events::collections::TimelineEvents &event, int role) const; - Q_INVOKABLE QVariant dataById(QString id, int role) - { - return data(index(idToIndex(id)), role); - } + Q_INVOKABLE QVariant dataById(QString id, int role, QString relatedTo); bool canFetchMore(const QModelIndex &) const override; void fetchMore(const QModelIndex &) override; @@ -237,7 +239,6 @@ public: Q_INVOKABLE void forwardMessage(QString eventId, QString roomId); Q_INVOKABLE void viewDecryptedRawMessage(QString id) const; Q_INVOKABLE void openUserProfile(QString userid); - Q_INVOKABLE void openRoomSettings(); Q_INVOKABLE void editAction(QString id); Q_INVOKABLE void replyAction(QString id); Q_INVOKABLE void readReceiptsAction(QString id) const; @@ -354,14 +355,13 @@ signals: void lastMessageChanged(); void notificationsChanged(); - void openRoomSettingsDialog(RoomSettings *settings); - void newMessageToSend(mtx::events::collections::TimelineEvents event); void addPendingMessageToStore(mtx::events::collections::TimelineEvents event); void updateFlowEventId(std::string event_id); void encryptionChanged(); void roomNameChanged(); + void plainRoomNameChanged(); void roomTopicChanged(); void roomAvatarUrlChanged(); void roomMemberCountChanged(); @@ -391,7 +391,7 @@ private: TimelineViewManager *manager_; InputBar input_{this}; - Permissions permissions_{this}; + Permissions permissions_; QTimer showEventTimer{this}; QString eventIdToShow; @@ -403,6 +403,8 @@ private: int notification_count = 0, highlight_count = 0; + unsigned int relatedEventCacheBuster = 0; + bool decryptDescription = true; bool m_paginationInProgress = false; bool isSpace_ = false; @@ -413,10 +415,17 @@ template void TimelineModel::sendMessageEvent(const T &content, mtx::events::EventType eventType) { - mtx::events::RoomEvent msgCopy = {}; - msgCopy.content = content; - msgCopy.type = eventType; - emit newMessageToSend(msgCopy); + if constexpr (std::is_same_v) { + mtx::events::Sticker msgCopy = {}; + msgCopy.content = content; + msgCopy.type = eventType; + emit newMessageToSend(msgCopy); + } else { + mtx::events::RoomEvent msgCopy = {}; + msgCopy.content = content; + msgCopy.type = eventType; + emit newMessageToSend(msgCopy); + } resetReply(); resetEdit(); } diff --git a/src/timeline/TimelineViewManager.cpp b/src/timeline/TimelineViewManager.cpp index b39ef615..a6922be7 100644 --- a/src/timeline/TimelineViewManager.cpp +++ b/src/timeline/TimelineViewManager.cpp @@ -15,15 +15,19 @@ #include "ChatPage.h" #include "Clipboard.h" #include "ColorImageProvider.h" +#include "CombinedImagePackModel.h" #include "CompletionProxyModel.h" #include "DelegateChooser.h" #include "DeviceVerificationFlow.h" #include "EventAccessors.h" +#include "ImagePackListModel.h" +#include "InviteesModel.h" #include "Logging.h" #include "MainWindow.h" #include "MatrixClient.h" #include "MxcImageProvider.h" #include "RoomsModel.h" +#include "SingleImagePackModel.h" #include "UserSettingsPage.h" #include "UsersModel.h" #include "dialogs/ImageOverlay.h" @@ -144,6 +148,7 @@ TimelineViewManager::TimelineViewManager(CallManager *callManager, ChatPage *par qRegisterMetaType(); qRegisterMetaType(); qRegisterMetaType(); + qRegisterMetaType(); qmlRegisterUncreatableMetaObject(qml_mtx_events::staticMetaObject, "im.nheko", @@ -172,6 +177,8 @@ TimelineViewManager::TimelineViewManager(CallManager *callManager, ChatPage *par 0, "UserProfileModel", "UserProfile needs to be instantiated on the C++ side"); + qmlRegisterUncreatableType( + "im.nheko", 1, 0, "MemberList", "MemberList needs to be instantiated on the C++ side"); qmlRegisterUncreatableType( "im.nheko", 1, @@ -180,6 +187,24 @@ TimelineViewManager::TimelineViewManager(CallManager *callManager, ChatPage *par "Room Settings needs to be instantiated on the C++ side"); qmlRegisterUncreatableType( "im.nheko", 1, 0, "Room", "Room needs to be instantiated on the C++ side"); + qmlRegisterUncreatableType( + "im.nheko", + 1, + 0, + "ImagePackListModel", + "ImagePackListModel needs to be instantiated on the C++ side"); + qmlRegisterUncreatableType( + "im.nheko", + 1, + 0, + "SingleImagePackModel", + "SingleImagePackModel needs to be instantiated on the C++ side"); + qmlRegisterUncreatableType( + "im.nheko", + 1, + 0, + "InviteesModel", + "InviteesModel needs to be instantiated on the C++ side"); static auto self = this; qmlRegisterSingletonType( @@ -340,6 +365,41 @@ TimelineViewManager::TimelineViewManager(CallManager *callManager, ChatPage *par &TimelineViewManager::openImageOverlayInternal); } +void +TimelineViewManager::openRoomMembers(QString room_id) +{ + MemberList *memberList = new MemberList(room_id, this); + emit openRoomMembersDialog(memberList); +} + +void +TimelineViewManager::openRoomSettings(QString room_id) +{ + RoomSettings *settings = new RoomSettings(room_id, this); + connect(rooms_->getRoomById(room_id).data(), + &TimelineModel::roomAvatarUrlChanged, + settings, + &RoomSettings::avatarChanged); + emit openRoomSettingsDialog(settings); +} + +void +TimelineViewManager::openInviteUsers(QString roomId) +{ + InviteesModel *model = new InviteesModel{this}; + connect(model, &InviteesModel::accept, this, [this, model, roomId]() { + emit inviteUsers(roomId, model->mxids()); + }); + emit openInviteUsersDialog(model); +} + +void +TimelineViewManager::openGlobalUserProfile(QString userId) +{ + UserProfile *profile = new UserProfile{QString{}, userId, this}; + emit openProfile(profile); +} + void TimelineViewManager::setVideoCallItem() { @@ -397,6 +457,12 @@ TimelineViewManager::openImageOverlay(QString mxcUrl, QString eventId) }); } +void +TimelineViewManager::openImagePackSettings(QString roomid) +{ + emit showImagePackSettings(new ImagePackListModel(roomid.toStdString(), this)); +} + void TimelineViewManager::openImageOverlayInternal(QString eventId, QImage img) { @@ -419,17 +485,6 @@ TimelineViewManager::openImageOverlayInternal(QString eventId, QImage img) }); } -void -TimelineViewManager::openInviteUsersDialog() -{ - MainWindow::instance()->openInviteUsersDialog( - [this](const QStringList &invitees) { emit inviteUsers(invitees); }); -} -void -TimelineViewManager::openMemberListDialog(QString roomid) const -{ - MainWindow::instance()->openMemberListDialog(roomid); -} void TimelineViewManager::openLeaveRoomDialog(QString roomid) const { @@ -593,6 +648,11 @@ TimelineViewManager::completerFor(QString completerName, QString roomId) auto proxy = new CompletionProxyModel(roomModel); roomModel->setParent(proxy); return proxy; + } else if (completerName == "stickers") { + auto stickerModel = new CombinedImagePackModel(roomId.toStdString(), true); + auto proxy = new CompletionProxyModel(stickerModel, 1, static_cast(-1) / 4); + stickerModel->setParent(proxy); + return proxy; } return nullptr; } diff --git a/src/timeline/TimelineViewManager.h b/src/timeline/TimelineViewManager.h index 15b4f523..54e3a935 100644 --- a/src/timeline/TimelineViewManager.h +++ b/src/timeline/TimelineViewManager.h @@ -33,6 +33,7 @@ class ColorImageProvider; class UserSettings; class ChatPage; class DeviceVerificationFlow; +class ImagePackListModel; class TimelineViewManager : public QObject { @@ -57,6 +58,7 @@ public: Q_INVOKABLE bool isInitialSync() const { return isInitialSync_; } bool isWindowFocused() const { return isWindowFocused_; } Q_INVOKABLE void openImageOverlay(QString mxcUrl, QString eventId); + Q_INVOKABLE void openImagePackSettings(QString roomid); Q_INVOKABLE QColor userColor(QString id, QColor background); Q_INVOKABLE QString escapeEmoji(QString str) const; Q_INVOKABLE QString htmlEscape(QString str) const { return str.toHtmlEscaped(); } @@ -64,9 +66,12 @@ public: Q_INVOKABLE QString userPresence(QString id) const; Q_INVOKABLE QString userStatus(QString id) const; + Q_INVOKABLE void openRoomMembers(QString room_id); + Q_INVOKABLE void openRoomSettings(QString room_id); + Q_INVOKABLE void openInviteUsers(QString roomId); + Q_INVOKABLE void openGlobalUserProfile(QString userId); + Q_INVOKABLE void focusMessageInput(); - Q_INVOKABLE void openInviteUsersDialog(); - Q_INVOKABLE void openMemberListDialog(QString roomid) const; Q_INVOKABLE void openLeaveRoomDialog(QString roomid) const; Q_INVOKABLE void removeVerificationFlow(DeviceVerificationFlow *flow); @@ -81,11 +86,17 @@ signals: void replyingEventChanged(QString replyingEvent); void replyClosed(); void newDeviceVerificationRequest(DeviceVerificationFlow *flow); - void inviteUsers(QStringList users); + void inviteUsers(QString roomId, QStringList users); + void showRoomList(); + void narrowViewChanged(); void focusChanged(); void focusInput(); void openImageOverlayInternalCb(QString eventId, QImage img); + void openRoomMembersDialog(MemberList *members); + void openRoomSettingsDialog(RoomSettings *settings); + void openInviteUsersDialog(InviteesModel *invitees); void openProfile(UserProfile *profile); + void showImagePackSettings(ImagePackListModel *packlist); public slots: void updateReadReceipts(const QString &room_id, const std::vector &event_ids); diff --git a/src/ui/RoomSettings.h b/src/ui/RoomSettings.h index 2a68a182..2deaa5e3 100644 --- a/src/ui/RoomSettings.h +++ b/src/ui/RoomSettings.h @@ -134,4 +134,4 @@ private: RoomInfo info_; int notifications_ = 0; int accessRules_ = 0; -}; \ No newline at end of file +};