diff --git a/CMakeLists.txt b/CMakeLists.txt
index 7295cc54..fe686ddf 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -304,7 +304,6 @@ set(SRC_FILES
src/SideBarActions.cpp
src/Splitter.cpp
src/TextInputWidget.cpp
- src/TopRoomBar.cpp
src/TrayIcon.cpp
src/UserInfoWidget.cpp
src/UserSettingsPage.cpp
@@ -341,7 +340,7 @@ if(USE_BUNDLED_MTXCLIENT)
FetchContent_Declare(
MatrixClient
GIT_REPOSITORY https://github.com/Nheko-Reborn/mtxclient.git
- GIT_TAG d8666a3f1a5b709b78ccea2b545d540a8cb502ca
+ GIT_TAG ac680e971d437eb2135ef994dcb58c0bbb5bdf61
)
FetchContent_MakeAvailable(MatrixClient)
else()
@@ -512,7 +511,6 @@ qt5_wrap_cpp(MOC_HEADERS
src/SideBarActions.h
src/Splitter.h
src/TextInputWidget.h
- src/TopRoomBar.h
src/TrayIcon.h
src/UserInfoWidget.h
src/UserSettingsPage.h
diff --git a/README.md b/README.md
index fb0167c8..2d24165c 100644
--- a/README.md
+++ b/README.md
@@ -132,6 +132,7 @@ Nheko can use bundled version for most of those libraries automatically, if the
To use them, you can enable the hunter integration by passing `-DHUNTER_ENABLED=ON`.
It is probably wise to link those dependencies statically by passing `-DBUILD_SHARED_LIBS=OFF`
You can select which bundled dependencies you want to use py passing various `-DUSE_BUNDLED_*` flags. By default all dependencies are bundled *if* you enable hunter.
+If you experience build issues and you are trying to link `mtxclient` library without hunter, make sure the library version(commit) as mentioned in the `CMakeList.txt` is used. Sometimes we have to make breaking changes in `mtxclient` and for that period the master branch of both repos may not be compatible.
The bundle flags are currently:
diff --git a/io.github.NhekoReborn.Nheko.json b/io.github.NhekoReborn.Nheko.json
index b11e587c..8ff82636 100644
--- a/io.github.NhekoReborn.Nheko.json
+++ b/io.github.NhekoReborn.Nheko.json
@@ -146,7 +146,7 @@
"name": "mtxclient",
"sources": [
{
- "commit": "d8666a3f1a5b709b78ccea2b545d540a8cb502ca",
+ "commit": "ac680e971d437eb2135ef994dcb58c0bbb5bdf61",
"type": "git",
"url": "https://github.com/Nheko-Reborn/mtxclient.git"
}
diff --git a/resources/langs/nheko_cs.ts b/resources/langs/nheko_cs.ts
new file mode 100644
index 00000000..ab0b22df
--- /dev/null
+++ b/resources/langs/nheko_cs.ts
@@ -0,0 +1,1860 @@
+
+
+
+
+ Cache
+
+
+
+
+
+
+
+ ChatPage
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ CommunitiesListItem
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Tag translation for m.server_notice
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ EditModal
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ EmojiPicker
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ EncryptionIndicator
+
+
+
+
+
+
+
+
+
+
+
+
+ InviteeItem
+
+
+
+
+
+
+
+ LoginPage
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ MemberList
+
+
+
+
+
+
+
+
+
+
+
+
+ MessageDelegate
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Placeholder
+
+
+
+
+
+
+
+ QuickSwitcher
+
+
+
+
+
+
+
+ RegisterPage
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ RoomInfo
+
+
+
+
+
+
+
+ RoomInfoListItem
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Standard matrix tag for favourites
+
+
+
+
+
+ Standard matrix tag for low priority rooms
+
+
+
+
+
+ Standard matrix tag for server notices
+
+
+
+
+
+ WhatsThis hint for tag menu actions
+
+
+
+
+
+ Add a new tag to the room
+
+
+
+
+
+ Tag name prompt title
+
+
+
+
+
+ Tag name prompt
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ SideBarActions
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ StatusIndicator
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ TextInputWidget
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ TimelineModel
+
+
+
+ Placeholder, when the message can't be decrypted, because the DB access failed when trying to lookup the session.
+
+
+
+
+
+ Placeholder, when the message can't be decrypted, because the DB access failed.
+
+
+
+
+
+ Placeholder, when the message can't be decrypted. In this case, the Olm decrytion returned an error, which is passed ad %1.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Placeholder, when the message was not decrypted yet or can't be decrypted.
+
+
+
+
+
+ Placeholder, when the message was decrypted, but we couldn't parse it, because Nheko/mtxclient don't support that event type yet.
+
+
+
+
+
+ Multiple users are typing. First argument is a comma separated list of potentially multiple users. Second argument is the last user of that list. (If only one user is typing, %1 is empty. You should still use it in your string though to silence Qt warnings.)
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ This is a leave event after the user already left and shouldn't happen apart from state resets
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ TimelineRow
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ TimelineView
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ TopRoomBar
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ TrayIcon
+
+
+
+
+
+
+
+
+
+
+
+
+ UserInfoWidget
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ UserSettingsPage
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ WelcomePage
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ descriptiveTime
+
+
+
+
+
+
+
+ dialogs::CreateRoom
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ dialogs::FallbackAuth
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ dialogs::InviteUsers
+
+
+
+
+
+
+
+
+
+
+
+
+ dialogs::JoinRoom
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ dialogs::LeaveRoom
+
+
+
+
+
+
+
+
+
+
+
+
+ dialogs::Logout
+
+
+
+
+
+
+
+
+
+
+
+
+ dialogs::PreviewUploadOverlay
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ dialogs::ReCaptcha
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ dialogs::ReadReceipts
+
+
+
+
+
+
+
+
+
+
+
+
+ dialogs::ReceiptItem
+
+
+
+
+
+
+
+
+
+
+
+
+ dialogs::RoomSettings
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ dialogs::UserProfile
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ emoji::Panel
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ message-description sent:
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ popups::UserMentions
+
+
+
+
+
+
+
+
+
+
+
+
+ utils
+
+
+
+
+
+
+
diff --git a/resources/langs/nheko_et.ts b/resources/langs/nheko_et.ts
new file mode 100644
index 00000000..54ab523a
--- /dev/null
+++ b/resources/langs/nheko_et.ts
@@ -0,0 +1,1858 @@
+
+
+
+
+ Cache
+
+
+
+ Sa liitusid selle jututoaga.
+
+
+
+ ChatPage
+
+
+
+ Kutse saatmine kasutajale ei õnnestunud: %1
+
+
+
+
+
+ Kutsutud kasutaja: %1
+
+
+
+
+
+
+
+
+
+ %1 jututuba on loodud.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Väljamüksatud kasutaja: %1
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Mitteühilduv puhvri versioon
+
+
+
+
+ Sinu andmekandjale salvestatud puhvri versioon on uuem, kui käesolev Nheko versioon kasutada oskab. Palun tee Nheko uuendus või kustuta puhverdatud andmed.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ CommunitiesListItem
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Tag translation for m.server_notice
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ EditModal
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ EmojiPicker
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ EncryptionIndicator
+
+
+
+
+
+
+
+
+
+
+
+
+ InviteeItem
+
+
+
+
+
+
+
+ LoginPage
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ MemberList
+
+
+
+
+
+
+
+
+
+
+
+
+ MessageDelegate
+
+
+
+ muudetud
+
+
+
+
+ Krüptimine on kasutusel
+
+
+
+
+ jututoa uus nimi on: %1
+
+
+
+
+ eemaldas jututoa nime
+
+
+
+
+ jututoa uus teema on: %1
+
+
+
+
+ teema on eemaldatud
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Placeholder
+
+
+
+
+
+
+
+ QuickSwitcher
+
+
+
+
+
+
+
+ RegisterPage
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ RoomInfo
+
+
+
+
+
+
+
+ RoomInfoListItem
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Standard matrix tag for favourites
+
+
+
+
+
+ Standard matrix tag for low priority rooms
+
+
+
+
+
+ Standard matrix tag for server notices
+
+
+
+
+
+ WhatsThis hint for tag menu actions
+
+
+
+
+
+ Add a new tag to the room
+
+
+
+
+
+ Tag name prompt title
+
+
+
+
+
+ Tag name prompt
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ SideBarActions
+
+
+
+
+
+
+
+
+ Loo uus jututuba
+
+
+
+
+ Liitu jututoaga
+
+
+
+
+ Alusta uut vestlust
+
+
+
+
+ Jututubade loend
+
+
+
+ StatusIndicator
+
+
+
+ Ebaõnnestus
+
+
+
+
+ Saadetud
+
+
+
+
+ Vastuvõetud
+
+
+
+
+ Loetud
+
+
+
+ TextInputWidget
+
+
+
+ Saada fail
+
+
+
+
+
+ Kirjuta sõnum…
+
+
+
+
+ Saada sõnum
+
+
+
+
+ Emoji
+
+
+
+
+ Vali fail
+
+
+
+
+ Kõik failid (*)
+
+
+
+
+ Ühendus serveriga on katkenud. Nheko proovib uuesti ühendust luua…
+
+
+
+ TimelineModel
+
+
+
+ Placeholder, when the message can't be decrypted, because the DB access failed when trying to lookup the session.
+ -- Dekrüptimise viga (ei õnnestu suhelda andmebaasiga) --
+
+
+
+
+ Placeholder, when the message can't be decrypted, because the DB access failed.
+ -- Dekrüptimise viga (megolm'i võtmete laadimine andmebaasist ei õnnestunud) --
+
+
+
+
+ Placeholder, when the message can't be decrypted. In this case, the Olm decrytion returned an error, which is passed ad %1.
+ -- Dekrüptimise viga (%1) --
+
+
+
+
+ Sõnumi ümbersõnastamine ebaõnnestus: %1
+
+
+
+
+ Salvesta pilt
+
+
+
+
+ Salvesta video
+
+
+
+
+ Salvesta helifail
+
+
+
+
+ Salvesta fail
+
+
+
+
+ Placeholder, when the message was not decrypted yet or can't be decrypted.
+ -- Krüptitud sündmus (Dekrüptimisvõtmeid ei leidunud) --
+
+
+
+
+ Placeholder, when the message was decrypted, but we couldn't parse it, because Nheko/mtxclient don't support that event type yet.
+ -- Krüptitud sündmus (Tundmatu sündmuse tüüp) --
+
+
+
+
+ Multiple users are typing. First argument is a comma separated list of potentially multiple users. Second argument is the last user of that list. (If only one user is typing, %1 is empty. You should still use it in your string though to silence Qt warnings.)
+
+ %1%2 kirjutab.
+ %1 and %2 kirjutavad.
+
+
+
+
+
+ %1 tegi jututoa avalikuks.
+
+
+
+
+ %1 seadistas, et selle jututoaga liitumine eeldab kutset.
+
+
+
+
+ %1 muutis selle jututoa külalistele ligipääsetavaks.
+
+
+
+
+ %1 eemaldas sellest jututoast külaliste ligipääsu.
+
+
+
+
+ %1 muutis, et kogu maailm saab selle jututoa ajalugu lugeda. Kõiki sündmusi saavad lugeda ka need, kes ei ole liitunud jututoaga.
+
+
+
+
+ %1 muutis, et selle jututoa ajalugu saavad lugeda kõik liikmed alates praegusest ajahetkest.
+
+
+
+
+ %1 muutis, et selle jututoa ajalugu saavad lugeda kõik liikmed alates oma kutse saatmisest.
+
+
+
+
+ %1 muutis, et selle jututoa ajalugu saavad lugeda kõik liikmed alates jututoaga liitumise hetkest.
+
+
+
+
+ %1 muutis selle jututoa õigusi.
+
+
+
+
+ %1 sai kutse.
+
+
+
+
+ %1 muutis oma kuvatavat nime ja tunnuspilti.
+
+
+
+
+ %1 muutis oma kuvatavat nime.
+
+
+
+
+ %1 muutis oma tunnuspilti.
+
+
+
+
+
+
+
+
+
+ %1 liitus jututoaga.
+
+
+
+
+ %1 lükkas liitumiskutse tagasi.
+
+
+
+
+ Tühistas %1 kutse.
+
+
+
+
+ %1 lahkus jututoast.
+
+
+
+
+ Müksas kasutaja %1 välja.
+
+
+
+
+ Eemaldas kasutaja %1 suhtluskeelu.
+
+
+
+
+ Kasutaja %1 sai suhtluskeelu.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ This is a leave event after the user already left and shouldn't happen apart from state resets
+
+
+
+
+
+ Põhjus: %1
+
+
+
+
+ %1 müksati välja.
+
+
+
+ TimelineRow
+
+
+
+
+
+
+
+
+ Vasta
+
+
+
+
+ Valikud
+
+
+
+ TimelineView
+
+
+
+
+
+
+
+
+ Vasta
+
+
+
+
+ Lugemisteatised
+
+
+
+
+ Märgi loetuks
+
+
+
+
+ Näita sõnumi lähtekoodi
+
+
+
+
+ Näita sõnumi dekrüptitud lähtekoodi
+
+
+
+
+ Muuda sõnumit
+
+
+
+
+ Salvesta kui
+
+
+
+
+ Ãœhtegi jututuba pole avatud
+
+
+
+
+ Sulge
+
+
+
+ TopRoomBar
+
+
+
+ Jututoa valikud
+
+
+
+
+ Mainimised
+
+
+
+
+ Kutsu kasutajaid
+
+
+
+
+ Liikmed
+
+
+
+
+ Lahku jututoast
+
+
+
+
+ Seadistused
+
+
+
+ TrayIcon
+
+
+
+ Näita
+
+
+
+
+ Lõpeta töö
+
+
+
+ UserInfoWidget
+
+
+
+ Logi välja
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ UserSettingsPage
+
+
+
+ Vähenda tegumiribale
+
+
+
+
+ Käivita tegumiribalt
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ WelcomePage
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ descriptiveTime
+
+
+
+
+
+
+
+ dialogs::CreateRoom
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ dialogs::FallbackAuth
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ dialogs::InviteUsers
+
+
+
+
+
+
+
+
+
+
+
+
+ dialogs::JoinRoom
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ dialogs::LeaveRoom
+
+
+
+
+
+
+
+
+
+
+
+
+ dialogs::Logout
+
+
+
+
+
+
+
+
+
+
+
+
+ dialogs::PreviewUploadOverlay
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ dialogs::ReCaptcha
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ dialogs::ReadReceipts
+
+
+
+
+
+
+
+
+
+
+
+
+ dialogs::ReceiptItem
+
+
+
+
+
+
+
+
+
+
+
+
+ dialogs::RoomSettings
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ dialogs::UserProfile
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ emoji::Panel
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Tegevused
+
+
+
+
+
+
+
+
+
+ Esemed
+
+
+
+
+ Sümbolid
+
+
+
+
+ Lipud
+
+
+
+ message-description sent:
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ popups::UserMentions
+
+
+
+
+
+
+
+
+
+
+
+
+ utils
+
+
+
+
+
+
+
diff --git a/resources/langs/nheko_si.ts b/resources/langs/nheko_si.ts
index 2f405ca2..f2b58d33 100644
--- a/resources/langs/nheko_si.ts
+++ b/resources/langs/nheko_si.ts
@@ -237,11 +237,6 @@ If Nheko fails to discover your homeserver, it will show you a field to enter th
-
-
-
-
-
Tag name prompt title
-
-
-
-
-
diff --git a/resources/qml/Avatar.qml b/resources/qml/Avatar.qml
index f934e2f6..a3943806 100644
--- a/resources/qml/Avatar.qml
+++ b/resources/qml/Avatar.qml
@@ -14,7 +14,7 @@ Rectangle {
Label {
anchors.fill: parent
- text: chat.model.escapeEmoji(String.fromCodePoint(displayName.codePointAt(0)))
+ text: timelineManager.escapeEmoji(String.fromCodePoint(displayName.codePointAt(0)))
textFormat: Text.RichText
font.pixelSize: avatar.height/2
verticalAlignment: Text.AlignVCenter
@@ -50,6 +50,8 @@ Rectangle {
anchors.bottom: avatar.bottom
anchors.right: avatar.right
+ visible: !!userid
+
height: avatar.height / 6
width: height
radius: settings.avatarCircles ? height / 2 : height / 4
diff --git a/resources/qml/TimelineView.qml b/resources/qml/TimelineView.qml
index 1c16ddc6..71a287bb 100644
--- a/resources/qml/TimelineView.qml
+++ b/resources/qml/TimelineView.qml
@@ -115,6 +115,130 @@ Page {
z: 3
}
+ ColumnLayout {
+ anchors.fill: parent
+ Rectangle {
+ id: topBar
+
+ Layout.fillWidth: true
+ implicitHeight: topLayout.height + 16
+ z: 3
+
+ color: colors.base
+
+ MouseArea {
+ anchors.fill: parent
+ onClicked: timelineManager.openRoomSettings();
+ }
+
+ GridLayout {
+ id: topLayout
+
+ anchors.left: parent.left
+ anchors.right: parent.right
+ anchors.margins: 8
+ anchors.verticalCenter: parent.verticalCenter
+
+ //Layout.margins: 8
+
+ ImageButton {
+ id: backToRoomsButton
+
+ Layout.column: 0
+ Layout.row: 0
+ Layout.rowSpan: 2
+ Layout.alignment: Qt.AlignVCenter
+
+ visible: timelineManager.isNarrowView
+
+ image: ":/icons/icons/ui/angle-pointing-to-left.png"
+
+ ToolTip.visible: hovered
+ ToolTip.text: qsTr("Back to room list")
+
+ onClicked: timelineManager.backToRooms()
+ }
+
+ Avatar {
+ Layout.column: 1
+ Layout.row: 0
+ Layout.rowSpan: 2
+ Layout.alignment: Qt.AlignVCenter
+
+ width: avatarSize
+ height: avatarSize
+
+ url: chat.model ? chat.model.roomAvatarUrl.replace("mxc://", "image://MxcImage/") : ""
+ displayName: chat.model ? chat.model.roomName : qsTr("No room selected")
+
+ MouseArea {
+ anchors.fill: parent
+ onClicked: timelineManager.openRoomSettings();
+ }
+ }
+
+ Label {
+ Layout.fillWidth: true
+ Layout.column: 2
+ Layout.row: 0
+
+ font.pointSize: fontMetrics.font.pointSize * 1.1
+
+ text: chat.model ? chat.model.roomName : qsTr("No room selected")
+
+ MouseArea {
+ anchors.fill: parent
+ onClicked: timelineManager.openRoomSettings();
+ }
+ }
+ MatrixText {
+ Layout.fillWidth: true
+ Layout.column: 2
+ Layout.row: 1
+ Layout.maximumHeight: fontMetrics.lineSpacing * 2 // show 2 lines
+ clip: true
+
+ text: chat.model ? chat.model.roomTopic : ""
+ }
+
+ ImageButton {
+ id: roomOptionsButton
+
+ Layout.column: 3
+ Layout.row: 0
+ Layout.rowSpan: 2
+ Layout.alignment: Qt.AlignVCenter
+
+ image: ":/icons/icons/ui/vertical-ellipsis.png"
+
+ ToolTip.visible: hovered
+ ToolTip.text: qsTr("Room options")
+
+ onClicked: roomOptionsMenu.popup(roomOptionsButton)
+
+ Menu {
+ id: roomOptionsMenu
+ MenuItem {
+ text: qsTr("Invite users")
+ onTriggered: timelineManager.openInviteUsersDialog();
+ }
+ MenuItem {
+ text: qsTr("Members")
+ onTriggered: timelineManager.openMemberListDialog();
+ }
+ MenuItem {
+ text: qsTr("Leave room")
+ onTriggered: timelineManager.openLeaveRoomDialog();
+ }
+ MenuItem {
+ text: qsTr("Settings")
+ onTriggered: timelineManager.openRoomSettings();
+ }
+ }
+ }
+ }
+ }
+
ListView {
id: chat
@@ -122,13 +246,8 @@ Page {
cacheBuffer: 400
- anchors.horizontalCenter: parent.horizontalCenter
- anchors.top: parent.top
- anchors.bottom: chatFooter.top
- width: parent.width
-
- anchors.leftMargin: 4
- anchors.rightMargin: scrollbar.width
+ Layout.fillWidth: true
+ Layout.fillHeight: true
model: timelineManager.timeline
@@ -167,10 +286,6 @@ Page {
ScrollBar.vertical: ScrollBar {
id: scrollbar
- parent: chat.parent
- anchors.top: chat.top
- anchors.right: chat.right
- anchors.bottom: chat.bottom
}
spacing: 4
@@ -178,9 +293,9 @@ Page {
onCountChanged: if (atYEnd) model.currentIndex = 0 // Mark last event as read, since we are at the bottom
- property int delegateMaxWidth: (settings.timelineMaxWidth > 100 && (parent.width - settings.timelineMaxWidth) > 32) ? settings.timelineMaxWidth : (parent.width - 32)
+ property int delegateMaxWidth: (settings.timelineMaxWidth > 100 && (parent.width - settings.timelineMaxWidth) > scrollbar.width*2) ? settings.timelineMaxWidth : (parent.width - scrollbar.width*2)
- delegate: Rectangle {
+ delegate: Item {
// This would normally be previousSection, but our model's order is inverted.
property bool sectionBoundary: (ListView.nextSection != "" && ListView.nextSection !== ListView.section) || model.index === chat.count - 1
@@ -189,7 +304,6 @@ Page {
anchors.horizontalCenter: parent ? parent.horizontalCenter : undefined
width: chat.delegateMaxWidth
height: section ? section.height + timelinerow.height : timelinerow.height
- color: "transparent"
TimelineRow {
id: timelinerow
@@ -276,7 +390,7 @@ Page {
Label {
id: userName
- text: chat.model.escapeEmoji(modelData.userName)
+ text: timelineManager.escapeEmoji(modelData.userName)
color: timelineManager.userColor(modelData.userId, colors.window)
textFormat: Text.RichText
@@ -309,17 +423,13 @@ Page {
}
}
- Rectangle {
+ Item {
id: chatFooter
- height: Math.max(fontMetrics.height * 1.2, footerContent.height)
- anchors.left: parent.left
- anchors.right: parent.right
- anchors.bottom: parent.bottom
+ implicitHeight: Math.max(fontMetrics.height * 1.2, footerContent.height)
+ Layout.fillWidth: true
z: 3
- color: "transparent"
-
Column {
id: footerContent
anchors.left: parent.left
@@ -397,4 +507,5 @@ Page {
}
}
}
+ }
}
diff --git a/resources/qml/delegates/MessageDelegate.qml b/resources/qml/delegates/MessageDelegate.qml
index 56b8040e..90e52442 100644
--- a/resources/qml/delegates/MessageDelegate.qml
+++ b/resources/qml/delegates/MessageDelegate.qml
@@ -36,7 +36,7 @@ Item {
DelegateChoice {
roleValue: MtxEvent.EmoteMessage
NoticeMessage {
- formatted: chat.model.escapeEmoji(modelData.userName) + " " + model.data.formattedBody
+ formatted: timelineManager.escapeEmoji(modelData.userName) + " " + model.data.formattedBody
color: timelineManager.userColor(modelData.userId, colors.window)
}
}
diff --git a/resources/qml/delegates/Reply.qml b/resources/qml/delegates/Reply.qml
index f9fd3f11..36a6d373 100644
--- a/resources/qml/delegates/Reply.qml
+++ b/resources/qml/delegates/Reply.qml
@@ -37,7 +37,7 @@ Item {
Text {
id: userName
- text: chat.model ? chat.model.escapeEmoji(reply.modelData.userName) : ""
+ text: timelineManager.escapeEmoji(reply.modelData.userName)
color: replyComponent.userColor
textFormat: Text.RichText
diff --git a/src/AvatarProvider.cpp b/src/AvatarProvider.cpp
index 603bb71a..b1751c33 100644
--- a/src/AvatarProvider.cpp
+++ b/src/AvatarProvider.cpp
@@ -34,10 +34,12 @@ resolve(const QString &avatarUrl, int size, QObject *receiver, AvatarCallback ca
{
const auto cacheKey = QString("%1_size_%2").arg(avatarUrl).arg(size);
- if (avatarUrl.isEmpty())
- return;
-
QPixmap pixmap;
+ if (avatarUrl.isEmpty()) {
+ callback(pixmap);
+ return;
+ }
+
if (avatar_cache.find(cacheKey, &pixmap)) {
callback(pixmap);
return;
@@ -75,11 +77,10 @@ resolve(const QString &avatarUrl, int size, QObject *receiver, AvatarCallback ca
opts.mxc_url,
mtx::errors::to_string(err->matrix_error.errcode),
err->matrix_error.error);
- return;
+ } else {
+ cache::saveImage(cacheKey.toStdString(), res);
}
- cache::saveImage(cacheKey.toStdString(), res);
-
emit proxy->avatarDownloaded(QByteArray(res.data(), res.size()));
});
}
diff --git a/src/Cache.cpp b/src/Cache.cpp
index 91cde9e7..98fe64c0 100644
--- a/src/Cache.cpp
+++ b/src/Cache.cpp
@@ -94,8 +94,10 @@ namespace {
std::unique_ptr instance_ = nullptr;
}
-static bool
-isHiddenEvent(mtx::events::collections::TimelineEvents e, const std::string &room_id)
+bool
+Cache::isHiddenEvent(lmdb::txn &txn,
+ mtx::events::collections::TimelineEvents e,
+ const std::string &room_id)
{
using namespace mtx::events;
if (auto encryptedEvent = std::get_if>(&e)) {
@@ -109,13 +111,27 @@ isHiddenEvent(mtx::events::collections::TimelineEvents e, const std::string &roo
e = result.event.value();
}
- static constexpr std::initializer_list hiddenEvents = {
+ mtx::events::account_data::nheko_extensions::HiddenEvents hiddenEvents;
+ hiddenEvents.hidden_event_types = {
EventType::Reaction, EventType::CallCandidates, EventType::Unsupported};
+ if (auto temp = getAccountData(txn, mtx::events::EventType::NhekoHiddenEvents, ""))
+ hiddenEvents = std::move(
+ std::get<
+ mtx::events::Event>(
+ *temp)
+ .content);
+ if (auto temp = getAccountData(txn, mtx::events::EventType::NhekoHiddenEvents, room_id))
+ hiddenEvents = std::move(
+ std::get<
+ mtx::events::Event>(
+ *temp)
+ .content);
+
return std::visit(
- [](const auto &ev) {
- return std::any_of(hiddenEvents.begin(),
- hiddenEvents.end(),
+ [hiddenEvents](const auto &ev) {
+ return std::any_of(hiddenEvents.hidden_event_types.begin(),
+ hiddenEvents.hidden_event_types.end(),
[ev](EventType type) { return type == ev.type; });
},
e);
@@ -624,6 +640,7 @@ Cache::removeRoom(lmdb::txn &txn, const std::string &roomid)
{
lmdb::dbi_del(txn, roomsDb_, lmdb::val(roomid), nullptr);
lmdb::dbi_drop(txn, getStatesDb(txn, roomid), true);
+ lmdb::dbi_drop(txn, getAccountDataDb(txn, roomid), true);
lmdb::dbi_drop(txn, getMembersDb(txn, roomid), true);
}
@@ -982,6 +999,19 @@ Cache::saveState(const mtx::responses::Sync &res)
setNextBatchToken(txn, res.next_batch);
+ if (!res.account_data.events.empty()) {
+ auto accountDataDb = getAccountDataDb(txn, "");
+ for (const auto &ev : res.account_data.events)
+ std::visit(
+ [&txn, &accountDataDb](const auto &event) {
+ lmdb::dbi_put(txn,
+ accountDataDb,
+ lmdb::val(to_string(event.type)),
+ lmdb::val(json(event).dump()));
+ },
+ ev);
+ }
+
// Save joined rooms
for (const auto &room : res.rooms.join) {
auto statesdb = getStatesDb(txn, room.first);
@@ -1001,30 +1031,43 @@ Cache::saveState(const mtx::responses::Sync &res)
updatedInfo.version = getRoomVersion(txn, statesdb).toStdString();
// Process the account_data associated with this room
- bool has_new_tags = false;
- for (const auto &evt : room.second.account_data.events) {
- // for now only fetch tag events
- if (std::holds_alternative>(evt)) {
- auto tags_evt = std::get>(evt);
- has_new_tags = true;
- for (const auto &tag : tags_evt.content.tags) {
- updatedInfo.tags.push_back(tag.first);
+ if (!room.second.account_data.events.empty()) {
+ auto accountDataDb = getAccountDataDb(txn, room.first);
+
+ bool has_new_tags = false;
+ for (const auto &evt : room.second.account_data.events) {
+ std::visit(
+ [&txn, &accountDataDb](const auto &event) {
+ lmdb::dbi_put(txn,
+ accountDataDb,
+ lmdb::val(to_string(event.type)),
+ lmdb::val(json(event).dump()));
+ },
+ evt);
+
+ // for tag events
+ if (std::holds_alternative>(evt)) {
+ auto tags_evt = std::get>(evt);
+ has_new_tags = true;
+ for (const auto &tag : tags_evt.content.tags) {
+ updatedInfo.tags.push_back(tag.first);
+ }
}
}
- }
- if (!has_new_tags) {
- // retrieve the old tags, they haven't changed
- lmdb::val data;
- if (lmdb::dbi_get(txn, roomsDb_, lmdb::val(room.first), data)) {
- try {
- RoomInfo tmp =
- json::parse(std::string_view(data.data(), data.size()));
- updatedInfo.tags = tmp.tags;
- } catch (const json::exception &e) {
- nhlog::db()->warn(
- "failed to parse room info: room_id ({}), {}",
- room.first,
- std::string(data.data(), data.size()));
+ if (!has_new_tags) {
+ // retrieve the old tags, they haven't changed
+ lmdb::val data;
+ if (lmdb::dbi_get(txn, roomsDb_, lmdb::val(room.first), data)) {
+ try {
+ RoomInfo tmp = json::parse(
+ std::string_view(data.data(), data.size()));
+ updatedInfo.tags = tmp.tags;
+ } catch (const json::exception &e) {
+ nhlog::db()->warn(
+ "failed to parse room info: room_id ({}), {}",
+ room.first,
+ std::string(data.data(), data.size()));
+ }
}
}
}
@@ -2439,7 +2482,7 @@ Cache::saveTimelineMessages(lmdb::txn &txn,
lmdb::dbi_put(txn, evToOrderDb, event_id, lmdb::val(&index, sizeof(index)));
// TODO(Nico): Allow blacklisting more event types in UI
- if (!isHiddenEvent(e, room_id)) {
+ if (!isHiddenEvent(txn, e, room_id)) {
++msgIndex;
lmdb::cursor_put(msgCursor.handle(),
lmdb::val(&msgIndex, sizeof(msgIndex)),
@@ -2522,7 +2565,7 @@ Cache::saveOldMessages(const std::string &room_id, const mtx::responses::Message
lmdb::dbi_put(txn, evToOrderDb, event_id, lmdb::val(&index, sizeof(index)));
// TODO(Nico): Allow blacklisting more event types in UI
- if (!isHiddenEvent(e, room_id)) {
+ if (!isHiddenEvent(txn, e, room_id)) {
--msgIndex;
lmdb::dbi_put(
txn, order2msgDb, lmdb::val(&msgIndex, sizeof(msgIndex)), event_id);
@@ -2840,6 +2883,24 @@ Cache::deleteOldData() noexcept
}
}
+std::optional
+Cache::getAccountData(lmdb::txn &txn, mtx::events::EventType type, const std::string &room_id)
+{
+ try {
+ auto db = getAccountDataDb(txn, room_id);
+
+ lmdb::val data;
+ if (lmdb::dbi_get(txn, db, lmdb::val(to_string(type)), data)) {
+ mtx::responses::utils::RoomAccountDataEvents events;
+ mtx::responses::utils::parse_room_account_data_events(
+ std::string_view(data.data(), data.size()), events);
+ return events.front();
+ }
+ } catch (...) {
+ }
+ return std::nullopt;
+}
+
bool
Cache::hasEnoughPowerLevel(const std::vector &eventTypes,
const std::string &room_id,
diff --git a/src/Cache_p.h b/src/Cache_p.h
index d3ec6ee0..c57995a2 100644
--- a/src/Cache_p.h
+++ b/src/Cache_p.h
@@ -289,6 +289,14 @@ private:
const std::string &room_id,
const mtx::responses::Timeline &res);
+ //! retrieve a specific event from account data
+ //! pass empty room_id for global account data
+ std::optional
+ getAccountData(lmdb::txn &txn, mtx::events::EventType type, const std::string &room_id);
+ bool isHiddenEvent(lmdb::txn &txn,
+ mtx::events::collections::TimelineEvents e,
+ const std::string &room_id);
+
//! Remove a room from the cache.
// void removeLeftRoom(lmdb::txn &txn, const std::string &room_id);
template
@@ -498,6 +506,12 @@ private:
return lmdb::dbi::open(txn, std::string(room_id + "/state").c_str(), MDB_CREATE);
}
+ lmdb::dbi getAccountDataDb(lmdb::txn &txn, const std::string &room_id)
+ {
+ return lmdb::dbi::open(
+ txn, std::string(room_id + "/account_data").c_str(), MDB_CREATE);
+ }
+
lmdb::dbi getMembersDb(lmdb::txn &txn, const std::string &room_id)
{
return lmdb::dbi::open(txn, std::string(room_id + "/members").c_str(), MDB_CREATE);
diff --git a/src/ChatPage.cpp b/src/ChatPage.cpp
index e55b3eca..6008846a 100644
--- a/src/ChatPage.cpp
+++ b/src/ChatPage.cpp
@@ -37,7 +37,6 @@
#include "SideBarActions.h"
#include "Splitter.h"
#include "TextInputWidget.h"
-#include "TopRoomBar.h"
#include "UserInfoWidget.h"
#include "UserSettingsPage.h"
#include "Utils.h"
@@ -126,10 +125,8 @@ ChatPage::ChatPage(QSharedPointer userSettings, QWidget *parent)
contentLayout_->setSpacing(0);
contentLayout_->setMargin(0);
- top_bar_ = new TopRoomBar(this);
view_manager_ = new TimelineViewManager(userSettings_, &callManager_, this);
- contentLayout_->addWidget(top_bar_);
contentLayout_->addWidget(view_manager_->getWidget());
activeCallBar_ = new ActiveCallBar(this);
@@ -181,30 +178,6 @@ ChatPage::ChatPage(QSharedPointer userSettings, QWidget *parent)
room_list_->previousRoom();
});
- connect(top_bar_, &TopRoomBar::mentionsClicked, this, [this](const QPoint &mentionsPos) {
- if (user_mentions_popup_->isVisible()) {
- user_mentions_popup_->hide();
- } else {
- showNotificationsDialog(mentionsPos);
- http::client()->notifications(
- 1000,
- "",
- "highlight",
- [this, mentionsPos](const mtx::responses::Notifications &res,
- mtx::http::RequestErr err) {
- if (err) {
- nhlog::net()->warn(
- "failed to retrieve notifications: {} ({})",
- err->matrix_error.error,
- static_cast(err->status_code));
- return;
- }
-
- emit highlightedNotifsRetrieved(std::move(res), mentionsPos);
- });
- }
- });
-
connectivityTimer_.setInterval(CHECK_CONNECTIVITY_INTERVAL);
connect(&connectivityTimer_, &QTimer::timeout, this, [=]() {
if (http::client()->access_token().empty()) {
@@ -226,8 +199,9 @@ ChatPage::ChatPage(QSharedPointer userSettings, QWidget *parent)
connect(this, &ChatPage::loggedOut, this, &ChatPage::logout);
- connect(top_bar_, &TopRoomBar::showRoomList, splitter, &Splitter::showFullRoomList);
- connect(top_bar_, &TopRoomBar::inviteUsers, this, [this](QStringList users) {
+ connect(
+ view_manager_, &TimelineViewManager::showRoomList, splitter, &Splitter::showFullRoomList);
+ connect(view_manager_, &TimelineViewManager::inviteUsers, this, [this](QStringList users) {
const auto room_id = current_room_.toStdString();
for (int ii = 0; ii < users.size(); ++ii) {
@@ -251,8 +225,10 @@ ChatPage::ChatPage(QSharedPointer userSettings, QWidget *parent)
}
});
+ connect(room_list_, &RoomList::roomChanged, this, [this](QString room_id) {
+ this->current_room_ = room_id;
+ });
connect(room_list_, &RoomList::roomChanged, text_input_, &TextInputWidget::stopTyping);
- connect(room_list_, &RoomList::roomChanged, this, &ChatPage::changeTopRoomInfo);
connect(room_list_, &RoomList::roomChanged, splitter, &Splitter::showChatView);
connect(room_list_, &RoomList::roomChanged, text_input_, &TextInputWidget::focusLineEdit);
connect(
@@ -487,8 +463,6 @@ ChatPage::ChatPage(QSharedPointer userSettings, QWidget *parent)
}
});
- connect(room_list_, &RoomList::roomAvatarChanged, this, &ChatPage::updateTopBarAvatar);
-
connect(
this, &ChatPage::updateGroupsInfo, communitiesList_, &CommunitiesList::setCommunities);
@@ -588,11 +562,6 @@ ChatPage::ChatPage(QSharedPointer userSettings, QWidget *parent)
});
connect(this, &ChatPage::syncRoomlist, room_list_, &RoomList::sync);
connect(this, &ChatPage::syncTags, communitiesList_, &CommunitiesList::syncTags);
- connect(
- this, &ChatPage::syncTopBar, this, [this](const std::map &updates) {
- if (updates.find(currentRoom()) != updates.end())
- changeTopRoomInfo(currentRoom());
- });
// Callbacks to update the user info (top left corner of the page).
connect(this, &ChatPage::setUserAvatar, user_info_widget_, &UserInfoWidget::setAvatar);
@@ -657,7 +626,6 @@ void
ChatPage::resetUI()
{
room_list_->clear();
- top_bar_->reset();
user_info_widget_->reset();
view_manager_->clearAll();
@@ -786,46 +754,6 @@ ChatPage::bootstrap(QString userid, QString homeserver, QString token)
tryInitialSync();
}
-void
-ChatPage::updateTopBarAvatar(const QString &roomid, const QString &img)
-{
- if (current_room_ != roomid)
- return;
-
- top_bar_->updateRoomAvatar(img);
-}
-
-void
-ChatPage::changeTopRoomInfo(const QString &room_id)
-{
- if (room_id.isEmpty()) {
- nhlog::ui()->warn("cannot switch to empty room_id");
- return;
- }
-
- try {
- auto room_info = cache::getRoomInfo({room_id.toStdString()});
-
- if (room_info.find(room_id) == room_info.end())
- return;
-
- const auto name = QString::fromStdString(room_info[room_id].name);
- const auto avatar_url = QString::fromStdString(room_info[room_id].avatar_url);
-
- top_bar_->updateRoomName(name);
- top_bar_->updateRoomTopic(QString::fromStdString(room_info[room_id].topic));
-
- top_bar_->updateRoomAvatarFromName(name);
- if (!avatar_url.isEmpty())
- top_bar_->updateRoomAvatar(avatar_url);
-
- } catch (const lmdb::error &e) {
- nhlog::ui()->error("failed to change top bar room info: {}", e.what());
- }
-
- current_room_ = room_id;
-}
-
void
ChatPage::showUnreadMessageNotification(int count)
{
@@ -967,14 +895,22 @@ ChatPage::sendNotifications(const mtx::responses::Notifications &res)
}
if (userSettings_->hasDesktopNotifications()) {
- notificationsManager.postNotification(
- room_id,
- QString::fromStdString(event_id),
- QString::fromStdString(
- cache::singleRoomInfo(item.room_id).name),
- cache::displayName(room_id, user_id),
- utils::event_body(item.event),
- cache::getRoomAvatar(room_id));
+ auto info = cache::singleRoomInfo(item.room_id);
+
+ AvatarProvider::resolve(
+ QString::fromStdString(info.avatar_url),
+ 96,
+ this,
+ [this, room_id, event_id, item, user_id, info](
+ QPixmap image) {
+ notificationsManager.postNotification(
+ room_id,
+ QString::fromStdString(event_id),
+ QString::fromStdString(info.name),
+ cache::displayName(room_id, user_id),
+ utils::event_body(item.event),
+ image.toImage());
+ });
}
}
} catch (const lmdb::error &e) {
@@ -1070,7 +1006,6 @@ ChatPage::handleSyncResponse(mtx::responses::Sync res)
auto updates = cache::roomUpdates(res);
- emit syncTopBar(updates);
emit syncRoomlist(updates);
emit syncUI(res.rooms);
@@ -1481,9 +1416,12 @@ ChatPage::getProfileInfo()
void
ChatPage::hideSideBars()
{
- communitiesList_->hide();
- sideBar_->hide();
- top_bar_->enableBackButton();
+ // Don't hide side bar, if we are currently only showing the side bar!
+ if (view_manager_->getWidget()->isVisible()) {
+ communitiesList_->hide();
+ sideBar_->hide();
+ }
+ view_manager_->enableBackButton();
}
void
@@ -1493,23 +1431,19 @@ ChatPage::showSideBars()
communitiesList_->show();
sideBar_->show();
- top_bar_->disableBackButton();
+ view_manager_->disableBackButton();
+ content_->show();
}
uint64_t
ChatPage::timelineWidth()
{
- int sidebarWidth = sideBar_->size().width();
- sidebarWidth += communitiesList_->size().width();
+ int sidebarWidth = sideBar_->minimumSize().width();
+ sidebarWidth += communitiesList_->minimumSize().width();
+ nhlog::ui()->info("timelineWidth: {}", size().width() - sidebarWidth);
return size().width() - sidebarWidth;
}
-bool
-ChatPage::isSideBarExpanded()
-{
- const auto sz = splitter::calculateSidebarSizes(QFont{});
- return sideBar_->size().width() > sz.normal;
-}
void
ChatPage::initiateLogout()
diff --git a/src/ChatPage.h b/src/ChatPage.h
index ba1c56d1..a139b5fd 100644
--- a/src/ChatPage.h
+++ b/src/ChatPage.h
@@ -49,7 +49,6 @@ class SideBarActions;
class Splitter;
class TextInputWidget;
class TimelineViewManager;
-class TopRoomBar;
class UserInfoWidget;
class UserSettings;
@@ -82,7 +81,6 @@ public:
//! Calculate the width of the message timeline.
uint64_t timelineWidth();
- bool isSideBarExpanded();
//! Hide the room & group list (if it was visible).
void hideSideBars();
//! Show the room/group list (if it was visible).
@@ -150,7 +148,6 @@ signals:
void syncUI(const mtx::responses::Rooms &rooms);
void syncRoomlist(const std::map &updates);
void syncTags(const std::map &updates);
- void syncTopBar(const std::map &updates);
void dropToLoginPageCb(const QString &msg);
void notifyMessage(const QString &roomid,
@@ -167,8 +164,6 @@ signals:
private slots:
void showUnreadMessageNotification(int count);
- void updateTopBarAvatar(const QString &roomid, const QString &img);
- void changeTopRoomInfo(const QString &room_id);
void logout();
void removeRoom(const QString &room_id);
void dropToLoginPage(const QString &msg);
@@ -239,7 +234,6 @@ private:
TimelineViewManager *view_manager_;
SideBarActions *sidebarActions_;
- TopRoomBar *top_bar_;
TextInputWidget *text_input_;
ActiveCallBar *activeCallBar_;
diff --git a/src/MainWindow.cpp b/src/MainWindow.cpp
index 4dab3d26..29abed86 100644
--- a/src/MainWindow.cpp
+++ b/src/MainWindow.cpp
@@ -200,7 +200,8 @@ MainWindow::adjustSideBars()
const uint64_t timelineWidth = chat_page_->timelineWidth();
const uint64_t minAvailableWidth = sz.collapsePoint + sz.groups;
- if (timelineWidth < minAvailableWidth && !chat_page_->isSideBarExpanded()) {
+ nhlog::ui()->info("timelineWidth: {}, min {}", timelineWidth, minAvailableWidth);
+ if (timelineWidth < minAvailableWidth) {
chat_page_->hideSideBars();
} else {
chat_page_->showSideBars();
@@ -339,9 +340,7 @@ MainWindow::openUserProfile(const QString &user_id, const QString &room_id)
void
MainWindow::openRoomSettings(const QString &room_id)
{
- const auto roomToSearch = room_id.isEmpty() ? chat_page_->currentRoom() : "";
-
- auto dialog = new dialogs::RoomSettings(roomToSearch, this);
+ auto dialog = new dialogs::RoomSettings(room_id, this);
showDialog(dialog);
}
@@ -349,8 +348,7 @@ MainWindow::openRoomSettings(const QString &room_id)
void
MainWindow::openMemberListDialog(const QString &room_id)
{
- const auto roomToSearch = room_id.isEmpty() ? chat_page_->currentRoom() : "";
- auto dialog = new dialogs::MemberList(roomToSearch, this);
+ auto dialog = new dialogs::MemberList(room_id, this);
showDialog(dialog);
}
@@ -358,11 +356,9 @@ MainWindow::openMemberListDialog(const QString &room_id)
void
MainWindow::openLeaveRoomDialog(const QString &room_id)
{
- auto roomToLeave = room_id.isEmpty() ? chat_page_->currentRoom() : room_id;
-
auto dialog = new dialogs::LeaveRoom(this);
- connect(dialog, &dialogs::LeaveRoom::leaving, this, [this, roomToLeave]() {
- chat_page_->leaveRoom(roomToLeave);
+ connect(dialog, &dialogs::LeaveRoom::leaving, this, [this, room_id]() {
+ chat_page_->leaveRoom(room_id);
});
showDialog(dialog);
diff --git a/src/MainWindow.h b/src/MainWindow.h
index e3e04698..4f54a195 100644
--- a/src/MainWindow.h
+++ b/src/MainWindow.h
@@ -68,14 +68,14 @@ public:
static MainWindow *instance() { return instance_; };
void saveCurrentWindowSize();
- void openLeaveRoomDialog(const QString &room_id = "");
+ void openLeaveRoomDialog(const QString &room_id);
void openInviteUsersDialog(std::function callback);
void openCreateRoomDialog(
std::function callback);
void openJoinRoomDialog(std::function callback);
void openLogoutDialog();
- void openRoomSettings(const QString &room_id = "");
- void openMemberListDialog(const QString &room_id = "");
+ void openRoomSettings(const QString &room_id);
+ void openMemberListDialog(const QString &room_id);
void openUserProfile(const QString &user_id, const QString &room_id);
void openReadReceiptsDialog(const QString &event_id);
diff --git a/src/MxcImageProvider.cpp b/src/MxcImageProvider.cpp
index a197e4aa..b59fdff8 100644
--- a/src/MxcImageProvider.cpp
+++ b/src/MxcImageProvider.cpp
@@ -17,13 +17,16 @@ MxcImageResponse::run()
auto data = cache::image(fileName);
if (!data.isNull()) {
m_image = utils::readImage(&data);
- m_image = m_image.scaled(
- m_requestedSize, Qt::KeepAspectRatio, Qt::SmoothTransformation);
- m_image.setText("mxc url", "mxc://" + m_id);
if (!m_image.isNull()) {
- emit finished();
- return;
+ m_image = m_image.scaled(
+ m_requestedSize, Qt::KeepAspectRatio, Qt::SmoothTransformation);
+ m_image.setText("mxc url", "mxc://" + m_id);
+
+ if (!m_image.isNull()) {
+ emit finished();
+ return;
+ }
}
}
@@ -34,7 +37,7 @@ MxcImageResponse::run()
opts.method = "crop";
http::client()->get_thumbnail(
opts, [this, fileName](const std::string &res, mtx::http::RequestErr err) {
- if (err) {
+ if (err || res.empty()) {
nhlog::net()->error("Failed to download image {}",
m_id.toStdString());
m_error = "Failed download";
@@ -46,6 +49,10 @@ MxcImageResponse::run()
auto data = QByteArray(res.data(), res.size());
cache::saveImage(fileName, data);
m_image = utils::readImage(&data);
+ if (!m_image.isNull()) {
+ m_image = m_image.scaled(
+ m_requestedSize, Qt::KeepAspectRatio, Qt::SmoothTransformation);
+ }
m_image.setText("mxc url", "mxc://" + m_id);
emit finished();
diff --git a/src/TopRoomBar.cpp b/src/TopRoomBar.cpp
deleted file mode 100644
index a45a751e..00000000
--- a/src/TopRoomBar.cpp
+++ /dev/null
@@ -1,229 +0,0 @@
-/*
- * nheko Copyright (C) 2017 Konstantinos Sideris
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see .
- */
-
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-
-#include "Config.h"
-#include "MainWindow.h"
-#include "TopRoomBar.h"
-#include "Utils.h"
-#include "ui/Avatar.h"
-#include "ui/FlatButton.h"
-#include "ui/Menu.h"
-#include "ui/OverlayModal.h"
-#include "ui/TextLabel.h"
-
-TopRoomBar::TopRoomBar(QWidget *parent)
- : QWidget(parent)
- , buttonSize_{32}
-{
- QFont f;
- f.setPointSizeF(f.pointSizeF());
-
- const int fontHeight = QFontMetrics(f).height();
- const int widgetMargin = fontHeight / 3;
- const int contentHeight = fontHeight * 3;
-
- setFixedHeight(contentHeight + widgetMargin);
-
- topLayout_ = new QHBoxLayout(this);
- topLayout_->setSpacing(widgetMargin);
- topLayout_->setContentsMargins(
- 2 * widgetMargin, widgetMargin, 2 * widgetMargin, widgetMargin);
-
- avatar_ = new Avatar(this, fontHeight * 2);
- avatar_->setLetter("");
-
- textLayout_ = new QVBoxLayout();
- textLayout_->setSpacing(0);
- textLayout_->setMargin(0);
-
- QFont roomFont;
- roomFont.setPointSizeF(roomFont.pointSizeF() * 1.1);
- roomFont.setWeight(QFont::Medium);
-
- nameLabel_ = new QLabel(this);
- nameLabel_->setFont(roomFont);
- nameLabel_->setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Fixed);
-
- QFont descriptionFont;
-
- topicLabel_ = new TextLabel(this);
- topicLabel_->setLineWrapMode(QTextEdit::NoWrap);
- topicLabel_->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
- topicLabel_->setFont(descriptionFont);
- topicLabel_->setTextInteractionFlags(Qt::TextBrowserInteraction);
- topicLabel_->setOpenExternalLinks(true);
- topicLabel_->setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Fixed);
-
- textLayout_->addWidget(nameLabel_);
- textLayout_->addWidget(topicLabel_);
-
- settingsBtn_ = new FlatButton(this);
- settingsBtn_->setToolTip(tr("Room options"));
- settingsBtn_->setFixedSize(buttonSize_, buttonSize_);
- settingsBtn_->setCornerRadius(buttonSize_ / 2);
-
- mentionsBtn_ = new FlatButton(this);
- mentionsBtn_->setToolTip(tr("Mentions"));
- mentionsBtn_->setFixedSize(buttonSize_, buttonSize_);
- mentionsBtn_->setCornerRadius(buttonSize_ / 2);
-
- QIcon settings_icon;
- settings_icon.addFile(":/icons/icons/ui/vertical-ellipsis.png");
- settingsBtn_->setIcon(settings_icon);
- settingsBtn_->setIconSize(QSize(buttonSize_ / 2, buttonSize_ / 2));
-
- QIcon mentions_icon;
- mentions_icon.addFile(":/icons/icons/ui/at-solid.svg");
- mentionsBtn_->setIcon(mentions_icon);
- mentionsBtn_->setIconSize(QSize(buttonSize_ / 2, buttonSize_ / 2));
-
- backBtn_ = new FlatButton(this);
- backBtn_->setFixedSize(buttonSize_, buttonSize_);
- backBtn_->setCornerRadius(buttonSize_ / 2);
-
- QIcon backIcon;
- backIcon.addFile(":/icons/icons/ui/angle-pointing-to-left.png");
- backBtn_->setIcon(backIcon);
- backBtn_->setIconSize(QSize(buttonSize_ / 2, buttonSize_ / 2));
- backBtn_->hide();
-
- connect(backBtn_, &QPushButton::clicked, this, &TopRoomBar::showRoomList);
-
- topLayout_->addWidget(avatar_);
- topLayout_->addWidget(backBtn_);
- topLayout_->addLayout(textLayout_, 1);
- topLayout_->addWidget(mentionsBtn_, 0, Qt::AlignRight);
- topLayout_->addWidget(settingsBtn_, 0, Qt::AlignRight);
-
- menu_ = new Menu(this);
-
- inviteUsers_ = new QAction(tr("Invite users"), this);
- connect(inviteUsers_, &QAction::triggered, this, [this]() {
- MainWindow::instance()->openInviteUsersDialog(
- [this](const QStringList &invitees) { emit inviteUsers(invitees); });
- });
-
- roomMembers_ = new QAction(tr("Members"), this);
- connect(roomMembers_, &QAction::triggered, this, []() {
- MainWindow::instance()->openMemberListDialog();
- });
-
- leaveRoom_ = new QAction(tr("Leave room"), this);
- connect(leaveRoom_, &QAction::triggered, this, []() {
- MainWindow::instance()->openLeaveRoomDialog();
- });
-
- roomSettings_ = new QAction(tr("Settings"), this);
- connect(roomSettings_, &QAction::triggered, this, []() {
- MainWindow::instance()->openRoomSettings();
- });
-
- menu_->addAction(inviteUsers_);
- menu_->addAction(roomMembers_);
- menu_->addAction(leaveRoom_);
- menu_->addAction(roomSettings_);
-
- connect(settingsBtn_, &QPushButton::clicked, this, [this]() {
- auto pos = mapToGlobal(settingsBtn_->pos());
- menu_->popup(
- QPoint(pos.x() + buttonSize_ - menu_->sizeHint().width(), pos.y() + buttonSize_));
- });
-
- connect(mentionsBtn_, &QPushButton::clicked, this, [this]() {
- auto pos = mapToGlobal(mentionsBtn_->pos());
- emit mentionsClicked(pos);
- });
-}
-
-void
-TopRoomBar::enableBackButton()
-{
- avatar_->hide();
- backBtn_->show();
-}
-
-void
-TopRoomBar::disableBackButton()
-{
- avatar_->show();
- backBtn_->hide();
-}
-
-void
-TopRoomBar::updateRoomAvatarFromName(const QString &name)
-{
- avatar_->setLetter(utils::firstChar(name));
- update();
-}
-
-void
-TopRoomBar::reset()
-{
- nameLabel_->setText("");
- topicLabel_->setText("");
- avatar_->setLetter("");
-}
-
-void
-TopRoomBar::updateRoomAvatar(const QString &avatar_image)
-{
- avatar_->setImage(avatar_image);
- update();
-}
-
-void
-TopRoomBar::updateRoomName(const QString &name)
-{
- nameLabel_->setText(name);
- update();
-}
-
-void
-TopRoomBar::updateRoomTopic(QString topic)
-{
- topic.replace(conf::strings::url_regex, conf::strings::url_html);
- topicLabel_->clearLinks();
- topicLabel_->setHtml(topic);
- update();
-}
-
-void
-TopRoomBar::mousePressEvent(QMouseEvent *)
-{
- if (roomSettings_ != nullptr)
- roomSettings_->trigger();
-}
-
-void
-TopRoomBar::paintEvent(QPaintEvent *)
-{
- QStyleOption opt;
- opt.init(this);
- QPainter p(this);
- style()->drawPrimitive(QStyle::PE_Widget, &opt, &p, this);
-}
diff --git a/src/TopRoomBar.h b/src/TopRoomBar.h
deleted file mode 100644
index 0c33c1e0..00000000
--- a/src/TopRoomBar.h
+++ /dev/null
@@ -1,90 +0,0 @@
-/*
- * nheko Copyright (C) 2017 Konstantinos Sideris
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see .
- */
-
-#pragma once
-
-#include
-#include
-#include
-
-class Avatar;
-class FlatButton;
-class Menu;
-class TextLabel;
-class OverlayModal;
-
-class QLabel;
-class QHBoxLayout;
-class QVBoxLayout;
-
-class TopRoomBar : public QWidget
-{
- Q_OBJECT
-
- Q_PROPERTY(QColor borderColor READ borderColor WRITE setBorderColor)
-
-public:
- TopRoomBar(QWidget *parent = nullptr);
-
- void updateRoomAvatar(const QString &avatar_image);
- void updateRoomName(const QString &name);
- void updateRoomTopic(QString topic);
- void updateRoomAvatarFromName(const QString &name);
-
- void reset();
-
- QColor borderColor() const { return borderColor_; }
- void setBorderColor(QColor &color) { borderColor_ = color; }
-
-public slots:
- //! Add a "back-arrow" button that can switch to roomlist only view.
- void enableBackButton();
- //! Replace the "back-arrow" button with the avatar of the room.
- void disableBackButton();
-
-signals:
- void inviteUsers(QStringList users);
- void showRoomList();
- void mentionsClicked(const QPoint &pos);
-
-protected:
- void mousePressEvent(QMouseEvent *) override;
- void paintEvent(QPaintEvent *) override;
-
-private:
- QHBoxLayout *topLayout_ = nullptr;
- QVBoxLayout *textLayout_ = nullptr;
-
- QLabel *nameLabel_ = nullptr;
- TextLabel *topicLabel_ = nullptr;
-
- Menu *menu_;
- QAction *leaveRoom_ = nullptr;
- QAction *roomMembers_ = nullptr;
- QAction *roomSettings_ = nullptr;
- QAction *inviteUsers_ = nullptr;
-
- FlatButton *settingsBtn_;
- FlatButton *mentionsBtn_;
- FlatButton *backBtn_;
-
- Avatar *avatar_;
-
- int buttonSize_;
-
- QColor borderColor_;
-};
diff --git a/src/UserSettingsPage.cpp b/src/UserSettingsPage.cpp
index ab5658a4..f1542ec5 100644
--- a/src/UserSettingsPage.cpp
+++ b/src/UserSettingsPage.cpp
@@ -513,9 +513,6 @@ UserSettingsPage::UserSettingsPage(QSharedPointer settings, QWidge
callsLabel->setFont(font);
useStunServer_ = new Toggle{this};
- defaultAudioSourceValue_ = new QLabel(this);
- defaultAudioSourceValue_->setFont(font);
-
auto encryptionLabel_ = new QLabel{tr("ENCRYPTION"), this};
encryptionLabel_->setFixedHeight(encryptionLabel_->minimumHeight() + LayoutTopMargin);
encryptionLabel_->setAlignment(Qt::AlignBottom);
@@ -652,7 +649,6 @@ UserSettingsPage::UserSettingsPage(QSharedPointer settings, QWidge
boxWrap(tr("Allow fallback call assist server"),
useStunServer_,
tr("Will use turn.matrix.org as assist when your home server does not offer one."));
- boxWrap(tr("Default audio source device"), defaultAudioSourceValue_);
formLayout_->addRow(encryptionLabel_);
formLayout_->addRow(new HorizontalLine{this});
@@ -813,7 +809,6 @@ UserSettingsPage::showEvent(QShowEvent *)
deviceIdValue_->setText(QString::fromStdString(http::client()->device_id()));
timelineMaxWidthSpin_->setValue(settings_->timelineMaxWidth());
useStunServer_->setState(!settings_->useStunServer());
- defaultAudioSourceValue_->setText(settings_->defaultAudioSource());
deviceFingerprintValue_->setText(
utils::humanReadableFingerprint(olm::client()->identity_keys().ed25519));
diff --git a/src/UserSettingsPage.h b/src/UserSettingsPage.h
index 52ff9466..e947bfae 100644
--- a/src/UserSettingsPage.h
+++ b/src/UserSettingsPage.h
@@ -250,7 +250,6 @@ private:
Toggle *decryptSidebar_;
QLabel *deviceFingerprintValue_;
QLabel *deviceIdValue_;
- QLabel *defaultAudioSourceValue_;
QComboBox *themeCombo_;
QComboBox *scaleFactorCombo_;
diff --git a/src/WebRTCSession.cpp b/src/WebRTCSession.cpp
index 1c1d008d..a3900b48 100644
--- a/src/WebRTCSession.cpp
+++ b/src/WebRTCSession.cpp
@@ -426,8 +426,12 @@ WebRTCSession::acceptICECandidates(
for (const auto &c : candidates) {
nhlog::ui()->debug(
"WebRTC: remote candidate: (m-line:{}):{}", c.sdpMLineIndex, c.candidate);
- g_signal_emit_by_name(
- webrtc_, "add-ice-candidate", c.sdpMLineIndex, c.candidate.c_str());
+ if (!c.candidate.empty()) {
+ g_signal_emit_by_name(webrtc_,
+ "add-ice-candidate",
+ c.sdpMLineIndex,
+ c.candidate.c_str());
+ }
}
}
}
@@ -491,7 +495,7 @@ WebRTCSession::startPipeline(int opusPayloadType)
}
GstBus *bus = gst_pipeline_get_bus(GST_PIPELINE(pipe_));
- gst_bus_add_watch(bus, newBusMessage, this);
+ busWatchId_ = gst_bus_add_watch(bus, newBusMessage, this);
gst_object_unref(bus);
emit stateChanged(State::INITIATED);
return true;
@@ -597,6 +601,8 @@ WebRTCSession::end()
gst_element_set_state(pipe_, GST_STATE_NULL);
gst_object_unref(pipe_);
pipe_ = nullptr;
+ g_source_remove(busWatchId_);
+ busWatchId_ = 0;
}
webrtc_ = nullptr;
if (state_ != State::DISCONNECTED)
diff --git a/src/WebRTCSession.h b/src/WebRTCSession.h
index 56d76fa8..8e78812f 100644
--- a/src/WebRTCSession.h
+++ b/src/WebRTCSession.h
@@ -64,10 +64,11 @@ private slots:
private:
WebRTCSession();
- bool initialised_ = false;
- State state_ = State::DISCONNECTED;
- GstElement *pipe_ = nullptr;
- GstElement *webrtc_ = nullptr;
+ bool initialised_ = false;
+ State state_ = State::DISCONNECTED;
+ GstElement *pipe_ = nullptr;
+ GstElement *webrtc_ = nullptr;
+ unsigned int busWatchId_ = 0;
std::string stunServer_;
std::vector turnServers_;
GList *audioSources_ = nullptr;
diff --git a/src/timeline/TimelineModel.cpp b/src/timeline/TimelineModel.cpp
index b6c2d4bb..32e9f92c 100644
--- a/src/timeline/TimelineModel.cpp
+++ b/src/timeline/TimelineModel.cpp
@@ -517,6 +517,25 @@ TimelineModel::fetchMore(const QModelIndex &)
events.fetchMore();
}
+void
+TimelineModel::syncState(const mtx::responses::State &s)
+{
+ using namespace mtx::events;
+
+ for (const auto &e : s.events) {
+ if (std::holds_alternative>(e))
+ emit roomAvatarUrlChanged();
+ else if (std::holds_alternative>(e))
+ emit roomNameChanged();
+ else if (std::holds_alternative>(e))
+ emit roomTopicChanged();
+ else if (std::holds_alternative>(e)) {
+ emit roomAvatarUrlChanged();
+ emit roomNameChanged();
+ }
+ }
+}
+
void
TimelineModel::addEvents(const mtx::responses::Timeline &timeline)
{
@@ -526,6 +545,7 @@ TimelineModel::addEvents(const mtx::responses::Timeline &timeline)
events.handleSync(timeline);
using namespace mtx::events;
+
for (auto e : timeline.events) {
if (auto encryptedEvent = std::get_if>(&e)) {
MegolmSessionIndex index;
@@ -549,6 +569,16 @@ TimelineModel::addEvents(const mtx::responses::Timeline &timeline)
emit newCallEvent(event);
},
e);
+ else if (std::holds_alternative>(e))
+ emit roomAvatarUrlChanged();
+ else if (std::holds_alternative>(e))
+ emit roomNameChanged();
+ else if (std::holds_alternative>(e))
+ emit roomTopicChanged();
+ else if (std::holds_alternative>(e)) {
+ emit roomAvatarUrlChanged();
+ emit roomNameChanged();
+ }
}
updateLastMessage();
}
@@ -689,12 +719,6 @@ TimelineModel::formatDateSeparator(QDate date) const
return date.toString(fmt);
}
-QString
-TimelineModel::escapeEmoji(QString str) const
-{
- return utils::replaceEmoji(str);
-}
-
void
TimelineModel::viewRawMessage(QString id) const
{
@@ -1359,7 +1383,7 @@ TimelineModel::formatTypingUsers(const std::vector &users, QColor bg)
QStringList uidWithoutLast;
auto formatUser = [this, bg](const QString &user_id) -> QString {
- auto uncoloredUsername = escapeEmoji(displayName(user_id));
+ auto uncoloredUsername = utils::replaceEmoji(displayName(user_id));
QString prefix =
QString("").arg(manager_->userColor(user_id, bg).name());
@@ -1409,7 +1433,7 @@ TimelineModel::formatJoinRuleEvent(QString id)
return "";
QString user = QString::fromStdString(event->sender);
- QString name = escapeEmoji(displayName(user));
+ QString name = utils::replaceEmoji(displayName(user));
switch (event->content.join_rule) {
case mtx::events::state::JoinRule::Public:
@@ -1434,7 +1458,7 @@ TimelineModel::formatGuestAccessEvent(QString id)
return "";
QString user = QString::fromStdString(event->sender);
- QString name = escapeEmoji(displayName(user));
+ QString name = utils::replaceEmoji(displayName(user));
switch (event->content.guest_access) {
case mtx::events::state::AccessState::CanJoin:
@@ -1459,7 +1483,7 @@ TimelineModel::formatHistoryVisibilityEvent(QString id)
return "";
QString user = QString::fromStdString(event->sender);
- QString name = escapeEmoji(displayName(user));
+ QString name = utils::replaceEmoji(displayName(user));
switch (event->content.history_visibility) {
case mtx::events::state::Visibility::WorldReadable:
@@ -1492,7 +1516,7 @@ TimelineModel::formatPowerLevelEvent(QString id)
return "";
QString user = QString::fromStdString(event->sender);
- QString name = escapeEmoji(displayName(user));
+ QString name = utils::replaceEmoji(displayName(user));
// TODO: power levels rendering is actually a bit complex. work on this later.
return tr("%1 has changed the room's permissions.").arg(name);
@@ -1521,7 +1545,7 @@ TimelineModel::formatMemberEvent(QString id)
}
QString user = QString::fromStdString(event->state_key);
- QString name = escapeEmoji(displayName(user));
+ QString name = utils::replaceEmoji(displayName(user));
QString rendered;
// see table https://matrix.org/docs/spec/client_server/latest#m-room-member
@@ -1594,3 +1618,37 @@ TimelineModel::formatMemberEvent(QString id)
return rendered;
}
+
+QString
+TimelineModel::roomName() const
+{
+ auto info = cache::getRoomInfo({room_id_.toStdString()});
+
+ if (!info.count(room_id_))
+ return "";
+ else
+ return QString::fromStdString(info[room_id_].name);
+}
+
+QString
+TimelineModel::roomAvatarUrl() const
+{
+ auto info = cache::getRoomInfo({room_id_.toStdString()});
+
+ if (!info.count(room_id_))
+ return "";
+ else
+ return QString::fromStdString(info[room_id_].avatar_url);
+}
+
+QString
+TimelineModel::roomTopic() const
+{
+ auto info = cache::getRoomInfo({room_id_.toStdString()});
+
+ if (!info.count(room_id_))
+ return "";
+ else
+ return utils::replaceEmoji(utils::linkifyMessage(
+ utils::escapeBlacklistedHtml(QString::fromStdString(info[room_id_].topic))));
+}
diff --git a/src/timeline/TimelineModel.h b/src/timeline/TimelineModel.h
index 156606e6..6daaac1b 100644
--- a/src/timeline/TimelineModel.h
+++ b/src/timeline/TimelineModel.h
@@ -137,6 +137,9 @@ class TimelineModel : public QAbstractListModel
Q_PROPERTY(QString reply READ reply WRITE setReply NOTIFY replyChanged RESET resetReply)
Q_PROPERTY(
bool paginationInProgress READ paginationInProgress NOTIFY paginationInProgressChanged)
+ Q_PROPERTY(QString roomName READ roomName NOTIFY roomNameChanged)
+ Q_PROPERTY(QString roomAvatarUrl READ roomAvatarUrl NOTIFY roomAvatarUrlChanged)
+ Q_PROPERTY(QString roomTopic READ roomTopic NOTIFY roomTopicChanged)
public:
explicit TimelineModel(TimelineViewManager *manager,
@@ -194,7 +197,6 @@ public:
Q_INVOKABLE QString formatGuestAccessEvent(QString id);
Q_INVOKABLE QString formatPowerLevelEvent(QString id);
- Q_INVOKABLE QString escapeEmoji(QString str) const;
Q_INVOKABLE void viewRawMessage(QString id) const;
Q_INVOKABLE void viewDecryptedRawMessage(QString id) const;
Q_INVOKABLE void openUserProfile(QString userid) const;
@@ -217,6 +219,7 @@ public:
void updateLastMessage();
void addEvents(const mtx::responses::Timeline &events);
+ void syncState(const mtx::responses::State &state);
template
void sendMessageEvent(const T &content, mtx::events::EventType eventType);
RelatedInfo relatedInfo(QString id);
@@ -253,6 +256,11 @@ public slots:
void setDecryptDescription(bool decrypt) { decryptDescription = decrypt; }
void clearTimeline() { events.clearTimeline(); }
+ QString roomName() const;
+ QString roomTopic() const;
+ QString roomAvatarUrl() const;
+ QString roomId() const { return room_id_; }
+
private slots:
void addPendingMessage(mtx::events::collections::TimelineEvents event);
@@ -270,6 +278,10 @@ signals:
void newMessageToSend(mtx::events::collections::TimelineEvents event);
void addPendingMessageToStore(mtx::events::collections::TimelineEvents event);
+ void roomNameChanged();
+ void roomTopicChanged();
+ void roomAvatarUrlChanged();
+
private:
void sendEncryptedMessageEvent(const std::string &txn_id,
nlohmann::json content,
diff --git a/src/timeline/TimelineViewManager.cpp b/src/timeline/TimelineViewManager.cpp
index 466c3cee..abb807b3 100644
--- a/src/timeline/TimelineViewManager.cpp
+++ b/src/timeline/TimelineViewManager.cpp
@@ -12,6 +12,7 @@
#include "ColorImageProvider.h"
#include "DelegateChooser.h"
#include "Logging.h"
+#include "MainWindow.h"
#include "MatrixClient.h"
#include "MxcImageProvider.h"
#include "UserSettingsPage.h"
@@ -76,7 +77,7 @@ TimelineViewManager::userStatus(QString id) const
TimelineViewManager::TimelineViewManager(QSharedPointer userSettings,
CallManager *callManager,
- QWidget *parent)
+ ChatPage *parent)
: imgProvider(new MxcImageProvider())
, colorImgProvider(new ColorImageProvider())
, blurhashProvider(new BlurhashProvider())
@@ -131,15 +132,12 @@ TimelineViewManager::TimelineViewManager(QSharedPointer userSettin
view->engine()->addImageProvider("blurhash", blurhashProvider);
view->setSource(QUrl("qrc:///qml/TimelineView.qml"));
- connect(dynamic_cast(parent),
- &ChatPage::themeChanged,
- this,
- &TimelineViewManager::updateColorPalette);
- connect(dynamic_cast(parent),
+ connect(parent, &ChatPage::themeChanged, this, &TimelineViewManager::updateColorPalette);
+ connect(parent,
&ChatPage::decryptSidebarChanged,
this,
&TimelineViewManager::updateEncryptedDescriptions);
- connect(dynamic_cast(parent), &ChatPage::loggedOut, this, [this]() {
+ connect(parent, &ChatPage::loggedOut, this, [this]() {
isInitialSync_ = true;
emit initialSyncChanged(true);
});
@@ -157,6 +155,7 @@ TimelineViewManager::sync(const mtx::responses::Rooms &rooms)
&TimelineModel::newCallEvent,
callManager_,
&CallManager::syncEvent);
+ room_model->syncState(room.state);
room_model->addEvents(room.timeline);
if (!isInitialSync_)
disconnect(room_model.data(),
@@ -207,6 +206,12 @@ TimelineViewManager::setHistoryView(const QString &room_id)
}
}
+QString
+TimelineViewManager::escapeEmoji(QString str) const
+{
+ return utils::replaceEmoji(str);
+}
+
void
TimelineViewManager::openImageOverlay(QString mxcUrl, QString eventId) const
{
@@ -245,6 +250,28 @@ TimelineViewManager::openLink(QString link) const
QDesktopServices::openUrl(link);
}
+void
+TimelineViewManager::openInviteUsersDialog()
+{
+ MainWindow::instance()->openInviteUsersDialog(
+ [this](const QStringList &invitees) { emit inviteUsers(invitees); });
+}
+void
+TimelineViewManager::openMemberListDialog() const
+{
+ MainWindow::instance()->openMemberListDialog(timeline_->roomId());
+}
+void
+TimelineViewManager::openLeaveRoomDialog() const
+{
+ MainWindow::instance()->openLeaveRoomDialog(timeline_->roomId());
+}
+void
+TimelineViewManager::openRoomSettings() const
+{
+ MainWindow::instance()->openRoomSettings(timeline_->roomId());
+}
+
void
TimelineViewManager::updateReadReceipts(const QString &room_id,
const std::vector &event_ids)
diff --git a/src/timeline/TimelineViewManager.h b/src/timeline/TimelineViewManager.h
index ea6d1743..1a98f64d 100644
--- a/src/timeline/TimelineViewManager.h
+++ b/src/timeline/TimelineViewManager.h
@@ -21,6 +21,7 @@ class BlurhashProvider;
class CallManager;
class ColorImageProvider;
class UserSettings;
+class ChatPage;
class TimelineViewManager : public QObject
{
@@ -30,11 +31,13 @@ class TimelineViewManager : public QObject
TimelineModel *timeline MEMBER timeline_ READ activeTimeline NOTIFY activeTimelineChanged)
Q_PROPERTY(
bool isInitialSync MEMBER isInitialSync_ READ isInitialSync NOTIFY initialSyncChanged)
+ Q_PROPERTY(
+ bool isNarrowView MEMBER isNarrowView_ READ isNarrowView NOTIFY narrowViewChanged)
public:
TimelineViewManager(QSharedPointer userSettings,
CallManager *callManager,
- QWidget *parent = nullptr);
+ ChatPage *parent = nullptr);
QWidget *getWidget() const { return container; }
void sync(const mtx::responses::Rooms &rooms);
@@ -44,14 +47,21 @@ public:
Q_INVOKABLE TimelineModel *activeTimeline() const { return timeline_; }
Q_INVOKABLE bool isInitialSync() const { return isInitialSync_; }
+ bool isNarrowView() const { return isNarrowView_; }
Q_INVOKABLE void openImageOverlay(QString mxcUrl, QString eventId) const;
Q_INVOKABLE QColor userColor(QString id, QColor background);
+ Q_INVOKABLE QString escapeEmoji(QString str) const;
Q_INVOKABLE QString userPresence(QString id) const;
Q_INVOKABLE QString userStatus(QString id) const;
Q_INVOKABLE void openLink(QString link) const;
+ Q_INVOKABLE void openInviteUsersDialog();
+ Q_INVOKABLE void openMemberListDialog() const;
+ Q_INVOKABLE void openLeaveRoomDialog() const;
+ Q_INVOKABLE void openRoomSettings() const;
+
signals:
void clearRoomMessageCount(QString roomid);
void updateRoomsLastMessage(QString roomid, const DescInfo &info);
@@ -59,6 +69,9 @@ signals:
void initialSyncChanged(bool isInitialSync);
void replyingEventChanged(QString replyingEvent);
void replyClosed();
+ void inviteUsers(QStringList users);
+ void showRoomList();
+ void narrowViewChanged();
public slots:
void updateReadReceipts(const QString &room_id, const std::vector &event_ids);
@@ -108,6 +121,23 @@ public slots:
timeline_->clearTimeline();
}
+ void enableBackButton()
+ {
+ if (isNarrowView_)
+ return;
+ isNarrowView_ = true;
+ emit narrowViewChanged();
+ }
+ void disableBackButton()
+ {
+ if (!isNarrowView_)
+ return;
+ isNarrowView_ = false;
+ emit narrowViewChanged();
+ }
+
+ void backToRooms() { emit showRoomList(); }
+
private:
#ifdef USE_QUICK_VIEW
QQuickView *view;
@@ -125,6 +155,7 @@ private:
CallManager *callManager_ = nullptr;
bool isInitialSync_ = true;
+ bool isNarrowView_ = false;
QSharedPointer settings;
QHash userColors;
diff --git a/third_party/blurhash/blurhash.cpp b/third_party/blurhash/blurhash.cpp
index cd0a18a4..a4adf89f 100644
--- a/third_party/blurhash/blurhash.cpp
+++ b/third_party/blurhash/blurhash.cpp
@@ -260,6 +260,7 @@ decode(std::string_view blurhash, size_t width, size_t height, size_t bytesPerPi
Components components{};
std::vector values;
+ values.reserve(blurhash.size() / 2);
try {
components = unpackComponents(decode83(blurhash.substr(0, 1)));
@@ -277,7 +278,7 @@ decode(std::string_view blurhash, size_t width, size_t height, size_t bytesPerPi
return {};
}
- i.image.reserve(height * width * 3);
+ i.image.reserve(height * width * bytesPerPixel);
for (size_t y = 0; y < height; y++) {
for (size_t x = 0; x < width; x++) {
@@ -344,7 +345,7 @@ encode(unsigned char *image, size_t width, size_t height, int components_x, int
}
int quantisedMaximumValue = encodeMaxAC(actualMaximumValue);
- maximumValue = ((float)quantisedMaximumValue + 1) / 166;
+ maximumValue = ((float)quantisedMaximumValue + 1) / 166;
h += leftPad(encode83(quantisedMaximumValue), 1);
} else {
maximumValue = 1;
@@ -406,7 +407,7 @@ TEST_CASE("AC")
{
auto h = "00%#MwS|WCWEM{R*bbWBbH"sv;
for (size_t i = 0; i < h.size(); i += 2) {
- auto s = h.substr(i, 2);
+ auto s = h.substr(i, 2);
const auto maxAC = 0.289157f;
CHECK(leftPad(encode83(encodeAC(decodeAC(decode83(s), maxAC), maxAC)), 2) == s);
}