diff --git a/CMakeLists.txt b/CMakeLists.txt
index 9280f7aa..4348e819 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -279,7 +279,6 @@ set(SRC_FILES
src/ui/ThemeManager.cpp
src/ui/UserProfile.cpp
- src/ActiveCallBar.cpp
src/AvatarProvider.cpp
src/BlurhashProvider.cpp
src/Cache.cpp
@@ -492,7 +491,6 @@ qt5_wrap_cpp(MOC_HEADERS
src/notifications/Manager.h
- src/ActiveCallBar.h
src/AvatarProvider.h
src/BlurhashProvider.h
src/Cache_p.h
diff --git a/resources/langs/nheko_pt_PT.ts b/resources/langs/nheko_pt_PT.ts
new file mode 100644
index 00000000..66e07bb4
--- /dev/null
+++ b/resources/langs/nheko_pt_PT.ts
@@ -0,0 +1,1993 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!DOCTYPE TS>
+<TS version="2.1" language="pt_PT">
+<context>
+ <name>Cache</name>
+ <message>
+ <location filename="../../src/Cache.cpp" line="+1658"/>
+ <source>You joined this room.</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
+ <name>ChatPage</name>
+ <message>
+ <location filename="../../src/ChatPage.cpp" line="+218"/>
+ <source>Failed to invite user: %1</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+4"/>
+ <location line="+947"/>
+ <source>Invited user: %1</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="-458"/>
+ <source>Migrating the cache to the current version failed. This can have different reasons. Please open an issue and try to use an older version in the mean time. Alternatively you can try deleting the cache manually.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+415"/>
+ <source>Room %1 created.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+26"/>
+ <source>Confirm invite</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+1"/>
+ <source>Do you really want to invite %1 (%2)?</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+11"/>
+ <source>Failed to invite %1 to %2: %3</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+15"/>
+ <source>Confirm kick</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+1"/>
+ <source>Do you really want to kick %1 (%2)?</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+11"/>
+ <source>Failed to kick %1 to %2: %3</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+5"/>
+ <source>Kicked user: %1</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+10"/>
+ <source>Confirm ban</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+1"/>
+ <source>Do you really want to ban %1 (%2)?</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+11"/>
+ <source>Failed to ban %1 in %2: %3</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+5"/>
+ <source>Banned user: %1</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+10"/>
+ <source>Confirm unban</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+1"/>
+ <source>Do you really want to unban %1 (%2)?</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+11"/>
+ <source>Failed to unban %1 in %2: %3</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+5"/>
+ <source>Unbanned user: %1</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="-874"/>
+ <source>Failed to upload media. Please try again.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+334"/>
+ <source>Cache migration failed!</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+13"/>
+ <source>Incompatible cache version</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+1"/>
+ <source>The cache on your disk is newer than this version of Nheko supports. Please update or clear your cache.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+67"/>
+ <source>Failed to restore OLM account. Please login again.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+4"/>
+ <source>Failed to restore save data. Please login again.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+165"/>
+ <source>Failed to setup encryption keys. Server response: %1 %2. Please try again later.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+99"/>
+ <location line="+251"/>
+ <source>Please try to login again: %1</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="-219"/>
+ <source>Failed to join room: %1</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+5"/>
+ <source>You joined the room</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+6"/>
+ <source>Failed to remove invite: %1</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+19"/>
+ <source>Room creation failed: %1</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+16"/>
+ <source>Failed to leave room: %1</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
+ <name>CommunitiesListItem</name>
+ <message>
+ <location filename="../../src/CommunitiesListItem.cpp" line="+133"/>
+ <source>All rooms</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+4"/>
+ <source>Favourite rooms</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+2"/>
+ <source>Low priority rooms</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+2"/>
+ <source>Server Notices</source>
+ <comment>Tag translation for m.server_notice</comment>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+2"/>
+ <location line="+2"/>
+ <source> (tag)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+3"/>
+ <source> (community)</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
+ <name>EditModal</name>
+ <message>
+ <location filename="../../src/dialogs/RoomSettings.cpp" line="+72"/>
+ <source>Apply</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+1"/>
+ <source>Cancel</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+10"/>
+ <source>Name</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+2"/>
+ <source>Topic</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
+ <name>EmojiPicker</name>
+ <message>
+ <location filename="../qml/emoji/EmojiPicker.qml" line="+117"/>
+ <location line="+139"/>
+ <source>Search</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="-42"/>
+ <source>People</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+2"/>
+ <source>Nature</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+2"/>
+ <source>Food</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+2"/>
+ <source>Activity</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+2"/>
+ <source>Travel</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+2"/>
+ <source>Objects</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+2"/>
+ <source>Symbols</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+2"/>
+ <source>Flags</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
+ <name>EncryptionIndicator</name>
+ <message>
+ <location filename="../qml/EncryptionIndicator.qml" line="+36"/>
+ <source>Encrypted</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+2"/>
+ <source>This message is not encrypted!</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
+ <name>EventStore</name>
+ <message>
+ <location filename="../../src/timeline/EventStore.cpp" line="+418"/>
+ <source>-- Encrypted Event (No keys found for decryption) --</source>
+ <comment>Placeholder, when the message was not decrypted yet or can't be decrypted.</comment>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+19"/>
+ <source>-- Decryption Error (failed to retrieve megolm keys from db) --</source>
+ <comment>Placeholder, when the message can't be decrypted, because the DB access failed.</comment>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+14"/>
+ <source>-- Decryption Error (%1) --</source>
+ <comment>Placeholder, when the message can't be decrypted. In this case, the Olm decrytion returned an error, which is passed as %1.</comment>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+10"/>
+ <source>-- Encrypted Event (Unknown event type) --</source>
+ <comment>Placeholder, when the message was decrypted, but we couldn't parse it, because Nheko/mtxclient don't support that event type yet.</comment>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+13"/>
+ <source>-- Replay attack! This message index was reused! --</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+7"/>
+ <source>-- Message by unverified device! --</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
+ <name>InviteeItem</name>
+ <message>
+ <location filename="../../src/InviteeItem.cpp" line="+18"/>
+ <source>Remove</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
+ <name>LoginPage</name>
+ <message>
+ <location filename="../../src/LoginPage.cpp" line="+90"/>
+ <source>Matrix ID</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+1"/>
+ <source>e.g @joe:matrix.org</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+2"/>
+ <source>Your login name. A mxid should start with @ followed by the user id. After the user id you need to include your server name after a :.
+You can also put your homeserver address there, if your server doesn't support .well-known lookup.
+Example: @user:server.my
+If Nheko fails to discover your homeserver, it will show you a field to enter the server manually.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+21"/>
+ <source>Password</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+5"/>
+ <source>Device name</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+2"/>
+ <source>A name for this device, which will be shown to others, when verifying your devices. If none is provided a default is used.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+6"/>
+ <source>The address that can be used to contact you homeservers client API.
+Example: https://server.my:8787</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+16"/>
+ <location line="+191"/>
+ <source>LOGIN</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="-100"/>
+ <source>Autodiscovery failed. Received malformed response.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+6"/>
+ <source>Autodiscovery failed. Unknown error when requesting .well-known.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+25"/>
+ <source>The required endpoints were not found. Possibly not a Matrix server.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+6"/>
+ <source>Received malformed response. Make sure the homeserver domain is valid.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+5"/>
+ <source>An unknown error occured. Make sure the homeserver domain is valid.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+55"/>
+ <source>SSO LOGIN</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+25"/>
+ <source>Empty password</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+52"/>
+ <source>SSO login failed</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
+ <name>MemberList</name>
+ <message>
+ <location filename="../../src/dialogs/MemberList.cpp" line="+90"/>
+ <source>Room members</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+4"/>
+ <source>OK</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
+ <name>MessageDelegate</name>
+ <message>
+ <location filename="../qml/delegates/MessageDelegate.qml" line="+66"/>
+ <location line="+6"/>
+ <source>redacted</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+6"/>
+ <source>Encryption enabled</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+6"/>
+ <source>room name changed to: %1</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+0"/>
+ <source>removed room name</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+6"/>
+ <source>topic changed to: %1</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+0"/>
+ <source>removed topic</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+6"/>
+ <source>%1 created and configured room: %2</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+7"/>
+ <source>%1 placed a voice call.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+1"/>
+ <source>%1 placed a video call.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+1"/>
+ <source>%1 placed a call.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+19"/>
+ <source>Negotiating call...</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="-12"/>
+ <source>%1 answered the call.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+6"/>
+ <source>%1 ended the call.</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
+ <name>Placeholder</name>
+ <message>
+ <location filename="../qml/delegates/Placeholder.qml" line="+4"/>
+ <source>unimplemented event: </source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
+ <name>QuickSwitcher</name>
+ <message>
+ <location filename="../../src/QuickSwitcher.cpp" line="+74"/>
+ <source>Search for a room...</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
+ <name>RegisterPage</name>
+ <message>
+ <location filename="../../src/RegisterPage.cpp" line="+88"/>
+ <source>Username</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+3"/>
+ <source>The username must not be empty, and must contain only the characters a-z, 0-9, ., _, =, -, and /.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+4"/>
+ <source>Password</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+2"/>
+ <source>Please choose a secure password. The exact requirements for password strength may depend on your server.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+4"/>
+ <source>Password confirmation</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+4"/>
+ <source>Homeserver</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+2"/>
+ <source>A server that allows registration. Since matrix is decentralized, you need to first find a server you can register on or host your own.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+17"/>
+ <source>REGISTER</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+55"/>
+ <source>No supported registration flows!</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+135"/>
+ <source>Invalid username</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+2"/>
+ <source>Password is not long enough (min 8 chars)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+2"/>
+ <source>Passwords don't match</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+2"/>
+ <source>Invalid server name</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
+ <name>RoomInfo</name>
+ <message>
+ <location filename="../../src/Cache.cpp" line="+1443"/>
+ <source>no version stored</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
+ <name>RoomInfoListItem</name>
+ <message>
+ <location filename="../../src/RoomInfoListItem.cpp" line="+102"/>
+ <source>Leave room</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+7"/>
+ <source>Tag room as:</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+18"/>
+ <source>Favourite</source>
+ <comment>Standard matrix tag for favourites</comment>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+3"/>
+ <source>Low Priority</source>
+ <comment>Standard matrix tag for low priority rooms</comment>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+3"/>
+ <source>Server Notice</source>
+ <comment>Standard matrix tag for server notices</comment>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+12"/>
+ <source>Adds or removes the specified tag.</source>
+ <comment>WhatsThis hint for tag menu actions</comment>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+38"/>
+ <source>New tag...</source>
+ <comment>Add a new tag to the room</comment>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+4"/>
+ <source>New Tag</source>
+ <comment>Tag name prompt title</comment>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+1"/>
+ <source>Tag:</source>
+ <comment>Tag name prompt</comment>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+173"/>
+ <source>Accept</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+3"/>
+ <source>Decline</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
+ <name>SideBarActions</name>
+ <message>
+ <location filename="../../src/SideBarActions.cpp" line="+40"/>
+ <source>User settings</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+7"/>
+ <source>Create new room</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+1"/>
+ <source>Join a room</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+16"/>
+ <source>Start a new chat</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+15"/>
+ <source>Room directory</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
+ <name>StatusIndicator</name>
+ <message>
+ <location filename="../qml/StatusIndicator.qml" line="+14"/>
+ <source>Failed</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+1"/>
+ <source>Sent</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+1"/>
+ <source>Received</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+1"/>
+ <source>Read</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
+ <name>TextInputWidget</name>
+ <message>
+ <location filename="../../src/TextInputWidget.cpp" line="+574"/>
+ <source>Send a file</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+13"/>
+ <location filename="../../src/TextInputWidget.h" line="+160"/>
+ <source>Write a message...</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+31"/>
+ <source>Send a message</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+8"/>
+ <source>Emoji</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+94"/>
+ <source>Select a file</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+0"/>
+ <source>All Files (*)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+64"/>
+ <source>Place a call</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+3"/>
+ <source>Hang up</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../../src/TextInputWidget.h" line="-5"/>
+ <source>Connection lost. Nheko is trying to re-connect...</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
+ <name>TimelineModel</name>
+ <message>
+ <location filename="../../src/timeline/TimelineModel.cpp" line="+805"/>
+ <source>Message redaction failed: %1</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+114"/>
+ <location line="+17"/>
+ <location line="+101"/>
+ <location line="+5"/>
+ <source>Failed to encrypt event, sending aborted!</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+195"/>
+ <source>Save image</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+2"/>
+ <source>Save video</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+2"/>
+ <source>Save audio</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+2"/>
+ <source>Save file</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message numerus="yes">
+ <location line="+129"/>
+ <source>%1 and %2 are typing.</source>
+ <comment>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.)</comment>
+ <translation type="unfinished">
+ <numerusform></numerusform>
+ <numerusform></numerusform>
+ </translation>
+ </message>
+ <message>
+ <location line="+68"/>
+ <source>%1 opened the room to the public.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+2"/>
+ <source>%1 made this room require and invitation to join.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+23"/>
+ <source>%1 made the room open to guests.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+2"/>
+ <source>%1 has closed the room to guest access.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+23"/>
+ <source>%1 made the room history world readable. Events may be now read by non-joined people.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+4"/>
+ <source>%1 set the room history visible to members from this point on.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+3"/>
+ <source>%1 set the room history visible to members since they were invited.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+3"/>
+ <source>%1 set the room history visible to members since they joined the room.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+22"/>
+ <source>%1 has changed the room's permissions.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+33"/>
+ <source>%1 was invited.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+11"/>
+ <source>%1 changed their display name and avatar.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+2"/>
+ <source>%1 changed their display name.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+2"/>
+ <source>%1 changed their avatar.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+2"/>
+ <source>%1 changed some profile info.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+4"/>
+ <source>%1 joined.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+9"/>
+ <source>%1 rejected their invite.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+2"/>
+ <source>Revoked the invite to %1.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+3"/>
+ <source>%1 left the room.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+2"/>
+ <source>Kicked %1.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+2"/>
+ <source>Unbanned %1.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+14"/>
+ <source>%1 was banned.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="-11"/>
+ <source>%1 redacted their knock.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="-943"/>
+ <source>You joined this room.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+945"/>
+ <source>Rejected the knock from %1.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+2"/>
+ <source>%1 left after having already left!</source>
+ <comment>This is a leave event after the user already left and shouldn't happen apart from state resets</comment>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+15"/>
+ <source> Reason: %1</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="-5"/>
+ <source>%1 knocked.</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
+ <name>TimelineRow</name>
+ <message>
+ <location filename="../qml/TimelineRow.qml" line="+94"/>
+ <source>React</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+16"/>
+ <source>Reply</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+15"/>
+ <source>Options</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
+ <name>TimelineView</name>
+ <message>
+ <location filename="../qml/TimelineView.qml" line="+61"/>
+ <source>React</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+4"/>
+ <source>Reply</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+4"/>
+ <source>Read receipts</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+4"/>
+ <source>Mark as read</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+3"/>
+ <source>View raw message</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+6"/>
+ <source>View decrypted raw message</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+4"/>
+ <source>Redact message</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+6"/>
+ <source>Save as</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+12"/>
+ <source>No room open</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+53"/>
+ <source>Back to room list</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+15"/>
+ <location line="+15"/>
+ <source>No room selected</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+28"/>
+ <source>Room options</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+7"/>
+ <source>Invite users</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+4"/>
+ <source>Members</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+4"/>
+ <source>Leave room</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+4"/>
+ <source>Settings</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+253"/>
+ <source>Close</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
+ <name>TrayIcon</name>
+ <message>
+ <location filename="../../src/TrayIcon.cpp" line="+122"/>
+ <source>Show</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+1"/>
+ <source>Quit</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
+ <name>UserInfoWidget</name>
+ <message>
+ <location filename="../../src/UserInfoWidget.cpp" line="+95"/>
+ <source>Logout</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+20"/>
+ <source>Set custom status message</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+4"/>
+ <source>Custom status message</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+1"/>
+ <source>Status:</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+9"/>
+ <source>Set presence automatically</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+6"/>
+ <source>Online</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+5"/>
+ <source>Unavailable</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+6"/>
+ <source>Offline</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
+ <name>UserSettingsPage</name>
+ <message>
+ <location filename="../../src/UserSettingsPage.cpp" line="+566"/>
+ <source>Minimize to tray</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+3"/>
+ <source>Start in tray</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+7"/>
+ <source>Group's sidebar</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="-3"/>
+ <source>Circular Avatars</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="-63"/>
+ <source>CALLS</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+58"/>
+ <source>Keep the application running in the background after closing the client window.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+3"/>
+ <source>Start the application in the background without showing the client window.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+4"/>
+ <source>Change the appearance of user avatars in chats.
+OFF - square, ON - Circle.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+3"/>
+ <source>Show a column containing groups and tags next to the room list.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+1"/>
+ <source>Decrypt messages in sidebar</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+2"/>
+ <source>Decrypt the messages shown in the sidebar.
+Only affects messages in encrypted chats.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+2"/>
+ <source>Show buttons in timeline</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+2"/>
+ <source>Show buttons to quickly reply, react or access additional options next to each message.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+2"/>
+ <source>Limit width of timeline</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+2"/>
+ <source>Set the max width of messages in the timeline (in pixels). This can help readability on wide screen, when Nheko is maximised</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+2"/>
+ <source>Typing notifications</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+2"/>
+ <source>Show who is typing in a room.
+This will also enable or disable sending typing notifications to others.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+3"/>
+ <source>Sort rooms by unreads</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+2"/>
+ <source>Display rooms with new messages first.
+If this is off, the list of rooms will only be sorted by the timestamp of the last message in a room.
+If this is on, rooms which have active notifications (the small circle with a number in it) will be sorted on top. Rooms, that you have muted, will still be sorted by timestamp, since you don't seem to consider them as important as the other rooms.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+7"/>
+ <source>Read receipts</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+2"/>
+ <source>Show if your message was read.
+Status is displayed next to timestamps.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+2"/>
+ <source>Send messages as Markdown</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+2"/>
+ <source>Allow using markdown in messages.
+When disabled, all messages are sent as a plain text.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+2"/>
+ <source>Desktop notifications</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+2"/>
+ <source>Notify about received message when the client is not currently focused.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+1"/>
+ <source>Alert on notification</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+2"/>
+ <source>Show an alert when a message is received.
+This usually causes the application icon in the task bar to animate in some fashion.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+2"/>
+ <source>Highlight message on hover</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+2"/>
+ <source>Change the background color of messages when you hover over them.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+1"/>
+ <source>Large Emoji in timeline</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+2"/>
+ <source>Make font size larger if messages with only a few emojis are displayed.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+5"/>
+ <source>Scale factor</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+2"/>
+ <source>Change the scale factor of the whole user interface.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+4"/>
+ <source>Font size</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+1"/>
+ <source>Font Family</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+8"/>
+ <source>Theme</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+4"/>
+ <source>Allow fallback call assist server</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+2"/>
+ <source>Will use turn.matrix.org as assist when your home server does not offer one.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+4"/>
+ <source>Device ID</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+1"/>
+ <source>Device Fingerprint</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="-120"/>
+ <source>Session Keys</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+4"/>
+ <source>IMPORT</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+1"/>
+ <source>EXPORT</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="-25"/>
+ <source>ENCRYPTION</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="-78"/>
+ <source>GENERAL</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+30"/>
+ <source>INTERFACE</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+172"/>
+ <source>Emoji Font Family</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+191"/>
+ <source>Open Sessions File</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+4"/>
+ <location line="+18"/>
+ <location line="+9"/>
+ <location line="+2"/>
+ <location line="+2"/>
+ <location line="+19"/>
+ <location line="+11"/>
+ <location line="+18"/>
+ <location line="+2"/>
+ <location line="+2"/>
+ <source>Error</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="-74"/>
+ <location line="+32"/>
+ <source>File Password</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="-31"/>
+ <source>Enter the passphrase to decrypt the file:</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+8"/>
+ <location line="+32"/>
+ <source>The password cannot be empty</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="-8"/>
+ <source>Enter passphrase to encrypt your session keys:</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+15"/>
+ <source>File to save the exported session keys</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
+ <name>WelcomePage</name>
+ <message>
+ <location filename="../../src/WelcomePage.cpp" line="+47"/>
+ <source>Welcome to nheko! The desktop client for the Matrix protocol.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+1"/>
+ <source>Enjoy your stay!</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+23"/>
+ <source>REGISTER</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+5"/>
+ <source>LOGIN</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
+ <name>descriptiveTime</name>
+ <message>
+ <location filename="../../src/Utils.cpp" line="+146"/>
+ <source>Yesterday</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
+ <name>dialogs::AcceptCall</name>
+ <message>
+ <location filename="../../src/dialogs/AcceptCall.cpp" line="+89"/>
+ <source>Accept</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+5"/>
+ <source>Reject</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
+ <name>dialogs::CreateRoom</name>
+ <message>
+ <location filename="../../src/dialogs/CreateRoom.cpp" line="+36"/>
+ <source>Create room</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+2"/>
+ <source>Cancel</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+10"/>
+ <source>Name</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+3"/>
+ <source>Topic</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+3"/>
+ <source>Alias</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+8"/>
+ <source>Room Visibility</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+8"/>
+ <source>Room Preset</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+9"/>
+ <source>Direct Chat</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
+ <name>dialogs::FallbackAuth</name>
+ <message>
+ <location filename="../../src/dialogs/FallbackAuth.cpp" line="+30"/>
+ <source>Open Fallback in Browser</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+1"/>
+ <source>Cancel</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+1"/>
+ <source>Confirm</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+12"/>
+ <source>Open the fallback, follow the steps and confirm after completing them.</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
+ <name>dialogs::InviteUsers</name>
+ <message>
+ <location filename="../../src/dialogs/InviteUsers.cpp" line="+42"/>
+ <source>Cancel</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+8"/>
+ <source>User ID to invite</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
+ <name>dialogs::JoinRoom</name>
+ <message>
+ <location filename="../../src/dialogs/JoinRoom.cpp" line="+30"/>
+ <source>Join</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+2"/>
+ <source>Cancel</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+7"/>
+ <source>Room ID or alias</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
+ <name>dialogs::LeaveRoom</name>
+ <message>
+ <location filename="../../src/dialogs/LeaveRoom.cpp" line="+31"/>
+ <source>Cancel</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+8"/>
+ <source>Are you sure you want to leave?</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
+ <name>dialogs::Logout</name>
+ <message>
+ <location filename="../../src/dialogs/Logout.cpp" line="+47"/>
+ <source>Cancel</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+8"/>
+ <source>Logout. Are you sure?</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
+ <name>dialogs::PlaceCall</name>
+ <message>
+ <location filename="../../src/dialogs/PlaceCall.cpp" line="+60"/>
+ <source>Voice</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+4"/>
+ <source>Cancel</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
+ <name>dialogs::PreviewUploadOverlay</name>
+ <message>
+ <location filename="../../src/dialogs/PreviewUploadOverlay.cpp" line="+41"/>
+ <source>Upload</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+1"/>
+ <source>Cancel</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+84"/>
+ <source>Media type: %1
+Media size: %2
+</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
+ <name>dialogs::ReCaptcha</name>
+ <message>
+ <location filename="../../src/dialogs/ReCaptcha.cpp" line="+31"/>
+ <source>Cancel</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+1"/>
+ <source>Confirm</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+11"/>
+ <source>Solve the reCAPTCHA and press the confirm button</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
+ <name>dialogs::ReadReceipts</name>
+ <message>
+ <location filename="../../src/dialogs/ReadReceipts.cpp" line="+120"/>
+ <source>Read receipts</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+4"/>
+ <source>Close</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
+ <name>dialogs::ReceiptItem</name>
+ <message>
+ <location line="-46"/>
+ <source>Today %1</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+3"/>
+ <source>Yesterday %1</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
+ <name>dialogs::RoomSettings</name>
+ <message>
+ <location filename="../../src/dialogs/RoomSettings.cpp" line="+135"/>
+ <source>Settings</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+3"/>
+ <source>Info</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+11"/>
+ <source>Internal ID</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+10"/>
+ <source>Room Version</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+4"/>
+ <source>Notifications</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+2"/>
+ <source>Muted</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+2"/>
+ <source>Mentions only</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+1"/>
+ <source>All messages</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+97"/>
+ <source>Room access</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+2"/>
+ <source>Anyone and guests</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+1"/>
+ <source>Anyone</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+1"/>
+ <source>Invited users</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+50"/>
+ <source>Encryption</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+8"/>
+ <source>End-to-End Encryption</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+1"/>
+ <source>Encryption is currently experimental and things might break unexpectedly. <br>Please take note that it can't be disabled afterwards.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+27"/>
+ <source>Respond to key requests</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+3"/>
+ <source>Whether or not the client should respond automatically with the session keys
+ upon request. Use with caution, this is a temporary measure to test the
+ E2E implementation until device verification is completed.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message numerus="yes">
+ <location line="+51"/>
+ <source>%n member(s)</source>
+ <translation type="unfinished">
+ <numerusform></numerusform>
+ <numerusform></numerusform>
+ </translation>
+ </message>
+ <message>
+ <location line="+140"/>
+ <source>Failed to enable encryption: %1</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+147"/>
+ <source>Select an avatar</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+0"/>
+ <source>All Files (*)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+12"/>
+ <source>The selected file is not an image</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+5"/>
+ <source>Error while reading file: %1</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+35"/>
+ <location line="+20"/>
+ <source>Failed to upload image: %s</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
+ <name>dialogs::UserProfile</name>
+ <message>
+ <location filename="../../src/dialogs/UserProfile.cpp" line="+64"/>
+ <source>Ban the user from the room</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+8"/>
+ <source>Ignore messages from this user</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+9"/>
+ <source>Kick the user from the room</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+8"/>
+ <source>Start a conversation</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+14"/>
+ <source>Confirm DM</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+1"/>
+ <source>Do you really want to invite %1 (%2) to a direct chat?</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+54"/>
+ <source>Devices</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
+ <name>emoji::Panel</name>
+ <message>
+ <location filename="../../src/emoji/Panel.cpp" line="+122"/>
+ <source>Smileys & People</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+4"/>
+ <source>Animals & Nature</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+3"/>
+ <source>Food & Drink</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+3"/>
+ <source>Activity</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+4"/>
+ <source>Travel & Places</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+3"/>
+ <source>Objects</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+3"/>
+ <source>Symbols</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+3"/>
+ <source>Flags</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
+ <name>message-description sent:</name>
+ <message>
+ <location filename="../../src/Utils.h" line="+106"/>
+ <source>You sent an audio clip</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+3"/>
+ <source>%1 sent an audio clip</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+5"/>
+ <source>You sent an image</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+3"/>
+ <source>%1 sent an image</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+5"/>
+ <source>You sent a file</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+3"/>
+ <source>%1 sent a file</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+5"/>
+ <source>You sent a video</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+3"/>
+ <source>%1 sent a video</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+5"/>
+ <source>You sent a sticker</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+3"/>
+ <source>%1 sent a sticker</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+5"/>
+ <source>You sent a notification</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+3"/>
+ <source>%1 sent a notification</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+5"/>
+ <source>You: %1</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+3"/>
+ <source>%1: %2</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+7"/>
+ <source>You sent an encrypted message</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+3"/>
+ <source>%1 sent an encrypted message</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+5"/>
+ <source>You placed a call</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+3"/>
+ <source>%1 placed a call</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+5"/>
+ <source>You answered a call</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+3"/>
+ <source>%1 answered a call</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+5"/>
+ <source>You ended a call</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+3"/>
+ <source>%1 ended a call</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
+ <name>popups::UserMentions</name>
+ <message>
+ <location filename="../../src/popups/UserMentions.cpp" line="+64"/>
+ <source>This Room</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+1"/>
+ <source>All Rooms</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
+ <name>utils</name>
+ <message>
+ <location filename="../../src/Utils.h" line="+4"/>
+ <source>Unknown Message Type</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+</TS>
diff --git a/resources/qml/ActiveCallBar.qml b/resources/qml/ActiveCallBar.qml
new file mode 100644
index 00000000..61484625
--- /dev/null
+++ b/resources/qml/ActiveCallBar.qml
@@ -0,0 +1,111 @@
+import QtQuick 2.9
+import QtQuick.Controls 2.3
+import QtQuick.Layouts 1.2
+
+import im.nheko 1.0
+
+Rectangle {
+ id: activeCallBar
+ visible: TimelineManager.callState != WebRTCState.DISCONNECTED
+ color: "#2ECC71"
+ implicitHeight: rowLayout.height + 8
+
+ RowLayout {
+ id: rowLayout
+ anchors.left: parent.left
+ anchors.right: parent.right
+ anchors.verticalCenter: parent.verticalCenter
+ anchors.leftMargin: 8
+
+ Avatar {
+ width: avatarSize
+ height: avatarSize
+
+ url: TimelineManager.callPartyAvatarUrl.replace("mxc://", "image://MxcImage/")
+ displayName: TimelineManager.callPartyName
+ }
+
+ Label {
+ font.pointSize: fontMetrics.font.pointSize * 1.1
+ text: " " + TimelineManager.callPartyName + " "
+ }
+
+ Image {
+ Layout.preferredWidth: 24
+ Layout.preferredHeight: 24
+ source: "qrc:/icons/icons/ui/place-call.png"
+ }
+
+ Label {
+ id: callStateLabel
+ font.pointSize: fontMetrics.font.pointSize * 1.1
+ }
+
+ Connections {
+ target: TimelineManager
+ function onCallStateChanged(state) {
+ switch (state) {
+ case WebRTCState.INITIATING:
+ callStateLabel.text = qsTr("Initiating...")
+ break;
+ case WebRTCState.OFFERSENT:
+ callStateLabel.text = qsTr("Calling...")
+ break;
+ case WebRTCState.CONNECTING:
+ callStateLabel.text = qsTr("Connecting...")
+ break;
+ case WebRTCState.CONNECTED:
+ callStateLabel.text = "00:00"
+ var d = new Date()
+ callTimer.startTime = Math.floor(d.getTime() / 1000)
+ break;
+ case WebRTCState.DISCONNECTED:
+ callStateLabel.text = ""
+ }
+ }
+ }
+
+ Timer {
+ id: callTimer
+ property int startTime
+ interval: 1000
+ running: TimelineManager.callState == WebRTCState.CONNECTED
+ repeat: true
+ onTriggered: {
+ var d = new Date()
+ let seconds = Math.floor(d.getTime() / 1000 - startTime)
+ let s = Math.floor(seconds % 60)
+ let m = Math.floor(seconds / 60) % 60
+ let h = Math.floor(seconds / 3600)
+ callStateLabel.text = (h ? (pad(h) + ":") : "") + pad(m) + ":" + pad(s)
+ }
+
+ function pad(n) {
+ return (n < 10) ? ("0" + n) : n
+ }
+ }
+
+ Item {
+ Layout.fillWidth: true
+ }
+
+ ImageButton {
+ width: 24
+ height: 24
+ buttonTextColor: "#000000"
+ image: TimelineManager.isMicMuted ?
+ ":/icons/icons/ui/microphone-unmute.png" :
+ ":/icons/icons/ui/microphone-mute.png"
+
+ hoverEnabled: true
+ ToolTip.visible: hovered
+ ToolTip.text: TimelineManager.isMicMuted ? qsTr("Unmute Mic") : qsTr("Mute Mic")
+
+ onClicked: TimelineManager.toggleMicMute()
+ }
+
+ Item {
+ implicitWidth: 16
+ }
+ }
+}
diff --git a/resources/qml/Avatar.qml b/resources/qml/Avatar.qml
index 67c3e008..df3dd08e 100644
--- a/resources/qml/Avatar.qml
+++ b/resources/qml/Avatar.qml
@@ -16,7 +16,7 @@ Rectangle {
Label {
anchors.fill: parent
- text: TimelineManager.escapeEmoji(String.fromCodePoint(displayName.codePointAt(0)))
+ text: TimelineManager.escapeEmoji(displayName ? String.fromCodePoint(displayName.codePointAt(0)) : "")
textFormat: Text.RichText
font.pixelSize: avatar.height/2
verticalAlignment: Text.AlignVCenter
diff --git a/resources/qml/ImageButton.qml b/resources/qml/ImageButton.qml
index dd67d597..54399ae7 100644
--- a/resources/qml/ImageButton.qml
+++ b/resources/qml/ImageButton.qml
@@ -3,6 +3,8 @@ import QtQuick.Controls 2.3
AbstractButton {
property string image: undefined
+ property color highlightColor: colors.highlight
+ property color buttonTextColor: colors.buttonText
width: 16
height: 16
id: button
@@ -11,7 +13,7 @@ AbstractButton {
id: buttonImg
// Workaround, can't get icon.source working for now...
anchors.fill: parent
- source: "image://colorimage/" + image + "?" + (button.hovered ? colors.highlight : colors.buttonText)
+ source: "image://colorimage/" + image + "?" + (button.hovered ? highlightColor : buttonTextColor)
}
MouseArea
diff --git a/resources/qml/TimelineView.qml b/resources/qml/TimelineView.qml
index 1dbe7c1a..5c9ca348 100644
--- a/resources/qml/TimelineView.qml
+++ b/resources/qml/TimelineView.qml
@@ -516,6 +516,11 @@ Page {
}
}
}
+
+ ActiveCallBar {
+ Layout.fillWidth: true
+ z: 3
+ }
}
}
}
diff --git a/resources/res.qrc b/resources/res.qrc
index 64e5b084..87216e30 100644
--- a/resources/res.qrc
+++ b/resources/res.qrc
@@ -121,6 +121,7 @@
<file>qtquickcontrols2.conf</file>
<file>qml/TimelineView.qml</file>
+ <file>qml/ActiveCallBar.qml</file>
<file>qml/Avatar.qml</file>
<file>qml/ImageButton.qml</file>
<file>qml/MatrixText.qml</file>
diff --git a/src/ActiveCallBar.cpp b/src/ActiveCallBar.cpp
deleted file mode 100644
index c0d2c13a..00000000
--- a/src/ActiveCallBar.cpp
+++ /dev/null
@@ -1,160 +0,0 @@
-#include <cstdio>
-
-#include <QDateTime>
-#include <QHBoxLayout>
-#include <QIcon>
-#include <QLabel>
-#include <QString>
-#include <QTimer>
-
-#include "ActiveCallBar.h"
-#include "ChatPage.h"
-#include "Utils.h"
-#include "WebRTCSession.h"
-#include "ui/Avatar.h"
-#include "ui/FlatButton.h"
-
-ActiveCallBar::ActiveCallBar(QWidget *parent)
- : QWidget(parent)
-{
- setAutoFillBackground(true);
- auto p = palette();
- p.setColor(backgroundRole(), QColor(46, 204, 113));
- setPalette(p);
-
- 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);
-
- layout_ = new QHBoxLayout(this);
- layout_->setSpacing(widgetMargin);
- layout_->setContentsMargins(2 * widgetMargin, widgetMargin, 2 * widgetMargin, widgetMargin);
-
- QFont labelFont;
- labelFont.setPointSizeF(labelFont.pointSizeF() * 1.1);
- labelFont.setWeight(QFont::Medium);
-
- avatar_ = new Avatar(this, QFontMetrics(f).height() * 2.5);
-
- callPartyLabel_ = new QLabel(this);
- callPartyLabel_->setFont(labelFont);
-
- stateLabel_ = new QLabel(this);
- stateLabel_->setFont(labelFont);
-
- durationLabel_ = new QLabel(this);
- durationLabel_->setFont(labelFont);
- durationLabel_->hide();
-
- muteBtn_ = new FlatButton(this);
- setMuteIcon(false);
- muteBtn_->setFixedSize(buttonSize_, buttonSize_);
- muteBtn_->setCornerRadius(buttonSize_ / 2);
- connect(muteBtn_, &FlatButton::clicked, this, [this]() {
- if (WebRTCSession::instance().toggleMuteAudioSrc(muted_))
- setMuteIcon(muted_);
- });
-
- layout_->addWidget(avatar_, 0, Qt::AlignLeft);
- layout_->addWidget(callPartyLabel_, 0, Qt::AlignLeft);
- layout_->addWidget(stateLabel_, 0, Qt::AlignLeft);
- layout_->addWidget(durationLabel_, 0, Qt::AlignLeft);
- layout_->addStretch();
- layout_->addWidget(muteBtn_, 0, Qt::AlignCenter);
- layout_->addSpacing(18);
-
- timer_ = new QTimer(this);
- connect(timer_, &QTimer::timeout, this, [this]() {
- auto seconds = QDateTime::currentSecsSinceEpoch() - callStartTime_;
- int s = seconds % 60;
- int m = (seconds / 60) % 60;
- int h = seconds / 3600;
- char buf[12];
- if (h)
- snprintf(buf, sizeof(buf), "%.2d:%.2d:%.2d", h, m, s);
- else
- snprintf(buf, sizeof(buf), "%.2d:%.2d", m, s);
- durationLabel_->setText(buf);
- });
-
- connect(
- &WebRTCSession::instance(), &WebRTCSession::stateChanged, this, &ActiveCallBar::update);
-}
-
-void
-ActiveCallBar::setMuteIcon(bool muted)
-{
- QIcon icon;
- if (muted) {
- muteBtn_->setToolTip("Unmute Mic");
- icon.addFile(":/icons/icons/ui/microphone-unmute.png");
- } else {
- muteBtn_->setToolTip("Mute Mic");
- icon.addFile(":/icons/icons/ui/microphone-mute.png");
- }
- muteBtn_->setIcon(icon);
- muteBtn_->setIconSize(QSize(buttonSize_, buttonSize_));
-}
-
-void
-ActiveCallBar::setCallParty(const QString &userid,
- const QString &displayName,
- const QString &roomName,
- const QString &avatarUrl)
-{
- callPartyLabel_->setText(" " + (displayName.isEmpty() ? userid : displayName) + " ");
-
- if (!avatarUrl.isEmpty())
- avatar_->setImage(avatarUrl);
- else
- avatar_->setLetter(utils::firstChar(roomName));
-}
-
-void
-ActiveCallBar::update(WebRTCSession::State state)
-{
- switch (state) {
- case WebRTCSession::State::INITIATING:
- show();
- stateLabel_->setText("Initiating call...");
- break;
- case WebRTCSession::State::INITIATED:
- show();
- stateLabel_->setText("Call initiated...");
- break;
- case WebRTCSession::State::OFFERSENT:
- show();
- stateLabel_->setText("Calling...");
- break;
- case WebRTCSession::State::CONNECTING:
- show();
- stateLabel_->setText("Connecting...");
- break;
- case WebRTCSession::State::CONNECTED:
- show();
- callStartTime_ = QDateTime::currentSecsSinceEpoch();
- timer_->start(1000);
- stateLabel_->setPixmap(
- QIcon(":/icons/icons/ui/place-call.png").pixmap(QSize(buttonSize_, buttonSize_)));
- durationLabel_->setText("00:00");
- durationLabel_->show();
- break;
- case WebRTCSession::State::ICEFAILED:
- case WebRTCSession::State::DISCONNECTED:
- hide();
- timer_->stop();
- callPartyLabel_->setText(QString());
- stateLabel_->setText(QString());
- durationLabel_->setText(QString());
- durationLabel_->hide();
- setMuteIcon(false);
- break;
- default:
- break;
- }
-}
diff --git a/src/ActiveCallBar.h b/src/ActiveCallBar.h
deleted file mode 100644
index 1e940227..00000000
--- a/src/ActiveCallBar.h
+++ /dev/null
@@ -1,40 +0,0 @@
-#pragma once
-
-#include <QWidget>
-
-#include "WebRTCSession.h"
-
-class QHBoxLayout;
-class QLabel;
-class QTimer;
-class Avatar;
-class FlatButton;
-
-class ActiveCallBar : public QWidget
-{
- Q_OBJECT
-
-public:
- ActiveCallBar(QWidget *parent = nullptr);
-
-public slots:
- void update(WebRTCSession::State);
- void setCallParty(const QString &userid,
- const QString &displayName,
- const QString &roomName,
- const QString &avatarUrl);
-
-private:
- QHBoxLayout *layout_ = nullptr;
- Avatar *avatar_ = nullptr;
- QLabel *callPartyLabel_ = nullptr;
- QLabel *stateLabel_ = nullptr;
- QLabel *durationLabel_ = nullptr;
- FlatButton *muteBtn_ = nullptr;
- int buttonSize_ = 22;
- bool muted_ = false;
- qint64 callStartTime_ = 0;
- QTimer *timer_ = nullptr;
-
- void setMuteIcon(bool muted);
-};
diff --git a/src/CallManager.cpp b/src/CallManager.cpp
index 7a8d2ca7..b1d1a75a 100644
--- a/src/CallManager.cpp
+++ b/src/CallManager.cpp
@@ -52,7 +52,7 @@ CallManager::CallManager(QSharedPointer<UserSettings> userSettings)
emit newMessage(roomid_, CallInvite{callid_, sdp, 0, timeoutms_});
emit newMessage(roomid_, CallCandidates{callid_, candidates, 0});
QTimer::singleShot(timeoutms_, this, [this]() {
- if (session_.state() == WebRTCSession::State::OFFERSENT) {
+ if (session_.state() == webrtc::State::OFFERSENT) {
hangUp(CallHangUp::Reason::InviteTimeOut);
emit ChatPage::instance()->showNotification(
"The remote side failed to pick up.");
@@ -99,13 +99,13 @@ CallManager::CallManager(QSharedPointer<UserSettings> userSettings)
turnServerTimer_.setInterval(ttl * 1000 * 0.9);
});
- connect(&session_, &WebRTCSession::stateChanged, this, [this](WebRTCSession::State state) {
+ connect(&session_, &WebRTCSession::stateChanged, this, [this](webrtc::State state) {
switch (state) {
- case WebRTCSession::State::DISCONNECTED:
+ case webrtc::State::DISCONNECTED:
playRingtone("qrc:/media/media/callend.ogg", false);
clear();
break;
- case WebRTCSession::State::ICEFAILED: {
+ case webrtc::State::ICEFAILED: {
QString error("Call connection failed.");
if (turnURIs_.empty())
error += " Your homeserver has no configured TURN server.";
@@ -155,10 +155,9 @@ CallManager::sendInvite(const QString &roomid)
std::vector<RoomMember> members(cache::getMembers(roomid.toStdString()));
const RoomMember &callee =
members.front().user_id == utils::localUser() ? members.back() : members.front();
- emit newCallParty(callee.user_id,
- callee.display_name,
- QString::fromStdString(roomInfo.name),
- QString::fromStdString(roomInfo.avatar_url));
+ callPartyName_ = callee.display_name.isEmpty() ? callee.user_id : callee.display_name;
+ callPartyAvatarUrl_ = QString::fromStdString(roomInfo.avatar_url);
+ emit newCallParty();
playRingtone("qrc:/media/media/ringback.ogg", true);
if (!session_.createOffer()) {
emit ChatPage::instance()->showNotification("Problem setting up call.");
@@ -193,9 +192,9 @@ CallManager::hangUp(CallHangUp::Reason reason)
}
bool
-CallManager::onActiveCall()
+CallManager::onActiveCall() const
{
- return session_.state() != WebRTCSession::State::DISCONNECTED;
+ return session_.state() != webrtc::State::DISCONNECTED;
}
void
@@ -259,11 +258,9 @@ CallManager::handleEvent(const RoomEvent<CallInvite> &callInviteEvent)
std::vector<RoomMember> members(cache::getMembers(callInviteEvent.room_id));
const RoomMember &caller =
members.front().user_id == utils::localUser() ? members.back() : members.front();
- emit newCallParty(caller.user_id,
- caller.display_name,
- QString::fromStdString(roomInfo.name),
- QString::fromStdString(roomInfo.avatar_url));
-
+ callPartyName_ = caller.display_name.isEmpty() ? caller.user_id : caller.display_name;
+ callPartyAvatarUrl_ = QString::fromStdString(roomInfo.avatar_url);
+ emit newCallParty();
auto dialog = new dialogs::AcceptCall(caller.user_id,
caller.display_name,
QString::fromStdString(roomInfo.name),
@@ -376,6 +373,8 @@ void
CallManager::clear()
{
roomid_.clear();
+ callPartyName_.clear();
+ callPartyAvatarUrl_.clear();
callid_.clear();
remoteICECandidates_.clear();
}
diff --git a/src/CallManager.h b/src/CallManager.h
index 3a406438..640230a4 100644
--- a/src/CallManager.h
+++ b/src/CallManager.h
@@ -29,7 +29,9 @@ public:
void sendInvite(const QString &roomid);
void hangUp(
mtx::events::msg::CallHangUp::Reason = mtx::events::msg::CallHangUp::Reason::User);
- bool onActiveCall();
+ bool onActiveCall() const;
+ QString callPartyName() const { return callPartyName_; }
+ QString callPartyAvatarUrl() const { return callPartyAvatarUrl_; }
void refreshTurnServer();
public slots:
@@ -40,11 +42,8 @@ signals:
void newMessage(const QString &roomid, const mtx::events::msg::CallCandidates &);
void newMessage(const QString &roomid, const mtx::events::msg::CallAnswer &);
void newMessage(const QString &roomid, const mtx::events::msg::CallHangUp &);
+ void newCallParty();
void turnServerRetrieved(const mtx::responses::TurnServer &);
- void newCallParty(const QString &userid,
- const QString &displayName,
- const QString &roomName,
- const QString &avatarUrl);
private slots:
void retrieveTurnServer();
@@ -52,6 +51,8 @@ private slots:
private:
WebRTCSession &session_;
QString roomid_;
+ QString callPartyName_;
+ QString callPartyAvatarUrl_;
std::string callid_;
const uint32_t timeoutms_ = 120000;
std::vector<mtx::events::msg::CallCandidates::Candidate> remoteICECandidates_;
diff --git a/src/ChatPage.cpp b/src/ChatPage.cpp
index 87b4c277..8e93c0f4 100644
--- a/src/ChatPage.cpp
+++ b/src/ChatPage.cpp
@@ -22,7 +22,6 @@
#include <QShortcut>
#include <QtConcurrent>
-#include "ActiveCallBar.h"
#include "AvatarProvider.h"
#include "Cache.h"
#include "Cache_p.h"
@@ -41,7 +40,6 @@
#include "UserInfoWidget.h"
#include "UserSettingsPage.h"
#include "Utils.h"
-#include "WebRTCSession.h"
#include "ui/OverlayModal.h"
#include "ui/Theme.h"
@@ -130,12 +128,6 @@ ChatPage::ChatPage(QSharedPointer<UserSettings> userSettings, QWidget *parent)
contentLayout_->addWidget(view_manager_->getWidget());
- activeCallBar_ = new ActiveCallBar(this);
- contentLayout_->addWidget(activeCallBar_);
- activeCallBar_->hide();
- connect(
- &callManager_, &CallManager::newCallParty, activeCallBar_, &ActiveCallBar::setCallParty);
-
// Splitter
splitter->addWidget(sideBar_);
splitter->addWidget(content_);
diff --git a/src/ChatPage.h b/src/ChatPage.h
index c37aa915..f0e12ab5 100644
--- a/src/ChatPage.h
+++ b/src/ChatPage.h
@@ -43,7 +43,6 @@
#include "notifications/Manager.h"
#include "popups/UserMentions.h"
-class ActiveCallBar;
class OverlayModal;
class QuickSwitcher;
class RoomList;
@@ -259,7 +258,6 @@ private:
SideBarActions *sidebarActions_;
TextInputWidget *text_input_;
- ActiveCallBar *activeCallBar_;
QTimer connectivityTimer_;
std::atomic_bool isConnected_;
diff --git a/src/MainWindow.cpp b/src/MainWindow.cpp
index 63722372..b6ad8bbe 100644
--- a/src/MainWindow.cpp
+++ b/src/MainWindow.cpp
@@ -288,7 +288,7 @@ MainWindow::showChatPage()
void
MainWindow::closeEvent(QCloseEvent *event)
{
- if (WebRTCSession::instance().state() != WebRTCSession::State::DISCONNECTED) {
+ if (WebRTCSession::instance().state() != webrtc::State::DISCONNECTED) {
if (QMessageBox::question(this, "nheko", "A call is in progress. Quit?") !=
QMessageBox::Yes) {
event->ignore();
@@ -431,7 +431,7 @@ MainWindow::openLogoutDialog()
{
auto dialog = new dialogs::Logout(this);
connect(dialog, &dialogs::Logout::loggingOut, this, [this]() {
- if (WebRTCSession::instance().state() != WebRTCSession::State::DISCONNECTED) {
+ if (WebRTCSession::instance().state() != webrtc::State::DISCONNECTED) {
if (QMessageBox::question(
this, "nheko", "A call is in progress. Log out?") !=
QMessageBox::Yes) {
diff --git a/src/TextInputWidget.cpp b/src/TextInputWidget.cpp
index d1be7fb4..22e8aafc 100644
--- a/src/TextInputWidget.cpp
+++ b/src/TextInputWidget.cpp
@@ -560,7 +560,7 @@ TextInputWidget::TextInputWidget(QWidget *parent)
#ifdef GSTREAMER_AVAILABLE
callBtn_ = new FlatButton(this);
- changeCallButtonState(WebRTCSession::State::DISCONNECTED);
+ changeCallButtonState(webrtc::State::DISCONNECTED);
connect(&WebRTCSession::instance(),
&WebRTCSession::stateChanged,
this,
@@ -778,11 +778,10 @@ TextInputWidget::paintEvent(QPaintEvent *)
}
void
-TextInputWidget::changeCallButtonState(WebRTCSession::State state)
+TextInputWidget::changeCallButtonState(webrtc::State state)
{
QIcon icon;
- if (state == WebRTCSession::State::ICEFAILED ||
- state == WebRTCSession::State::DISCONNECTED) {
+ if (state == webrtc::State::ICEFAILED || state == webrtc::State::DISCONNECTED) {
callBtn_->setToolTip(tr("Place a call"));
icon.addFile(":/icons/icons/ui/place-call.png");
} else {
diff --git a/src/TextInputWidget.h b/src/TextInputWidget.h
index 092e0ff2..7cc73e98 100644
--- a/src/TextInputWidget.h
+++ b/src/TextInputWidget.h
@@ -164,7 +164,7 @@ public slots:
void openFileSelection();
void hideUploadSpinner();
void focusLineEdit() { input_->setFocus(); }
- void changeCallButtonState(WebRTCSession::State);
+ void changeCallButtonState(webrtc::State);
private slots:
void addSelectedEmoji(const QString &emoji);
diff --git a/src/UserSettingsPage.cpp b/src/UserSettingsPage.cpp
index f1542ec5..7d81e663 100644
--- a/src/UserSettingsPage.cpp
+++ b/src/UserSettingsPage.cpp
@@ -511,7 +511,6 @@ UserSettingsPage::UserSettingsPage(QSharedPointer<UserSettings> settings, QWidge
callsLabel->setFixedHeight(callsLabel->minimumHeight() + LayoutTopMargin);
callsLabel->setAlignment(Qt::AlignBottom);
callsLabel->setFont(font);
- useStunServer_ = new Toggle{this};
auto encryptionLabel_ = new QLabel{tr("ENCRYPTION"), this};
encryptionLabel_->setFixedHeight(encryptionLabel_->minimumHeight() + LayoutTopMargin);
diff --git a/src/WebRTCSession.cpp b/src/WebRTCSession.cpp
index 30a27b60..1c11f750 100644
--- a/src/WebRTCSession.cpp
+++ b/src/WebRTCSession.cpp
@@ -1,3 +1,4 @@
+#include <QQmlEngine>
#include <cctype>
#include "Logging.h"
@@ -14,12 +15,17 @@ extern "C"
}
#endif
-Q_DECLARE_METATYPE(WebRTCSession::State)
+Q_DECLARE_METATYPE(webrtc::State)
+
+using webrtc::State;
WebRTCSession::WebRTCSession()
: QObject()
{
- qRegisterMetaType<WebRTCSession::State>();
+ qRegisterMetaType<webrtc::State>();
+ qmlRegisterUncreatableMetaObject(
+ webrtc::staticMetaObject, "im.nheko", 1, 0, "WebRTCState", "Can't instantiate enum");
+
connect(this, &WebRTCSession::stateChanged, this, &WebRTCSession::setState);
init();
}
@@ -246,12 +252,10 @@ iceGatheringStateChanged(GstElement *webrtc,
nhlog::ui()->debug("WebRTC: GstWebRTCICEGatheringState -> Complete");
if (isoffering_) {
emit WebRTCSession::instance().offerCreated(localsdp_, localcandidates_);
- emit WebRTCSession::instance().stateChanged(
- WebRTCSession::State::OFFERSENT);
+ emit WebRTCSession::instance().stateChanged(State::OFFERSENT);
} else {
emit WebRTCSession::instance().answerCreated(localsdp_, localcandidates_);
- emit WebRTCSession::instance().stateChanged(
- WebRTCSession::State::ANSWERSENT);
+ emit WebRTCSession::instance().stateChanged(State::ANSWERSENT);
}
}
}
@@ -264,10 +268,10 @@ onICEGatheringCompletion(gpointer timerid)
*(guint *)(timerid) = 0;
if (isoffering_) {
emit WebRTCSession::instance().offerCreated(localsdp_, localcandidates_);
- emit WebRTCSession::instance().stateChanged(WebRTCSession::State::OFFERSENT);
+ emit WebRTCSession::instance().stateChanged(State::OFFERSENT);
} else {
emit WebRTCSession::instance().answerCreated(localsdp_, localcandidates_);
- emit WebRTCSession::instance().stateChanged(WebRTCSession::State::ANSWERSENT);
+ emit WebRTCSession::instance().stateChanged(State::ANSWERSENT);
}
return FALSE;
}
@@ -285,7 +289,7 @@ addLocalICECandidate(GstElement *webrtc G_GNUC_UNUSED,
localcandidates_.push_back({"audio", (uint16_t)mlineIndex, candidate});
return;
#else
- if (WebRTCSession::instance().state() >= WebRTCSession::State::OFFERSENT) {
+ if (WebRTCSession::instance().state() >= State::OFFERSENT) {
emit WebRTCSession::instance().newICECandidate(
{"audio", (uint16_t)mlineIndex, candidate});
return;
@@ -314,11 +318,11 @@ iceConnectionStateChanged(GstElement *webrtc,
switch (newState) {
case GST_WEBRTC_ICE_CONNECTION_STATE_CHECKING:
nhlog::ui()->debug("WebRTC: GstWebRTCICEConnectionState -> Checking");
- emit WebRTCSession::instance().stateChanged(WebRTCSession::State::CONNECTING);
+ emit WebRTCSession::instance().stateChanged(State::CONNECTING);
break;
case GST_WEBRTC_ICE_CONNECTION_STATE_FAILED:
nhlog::ui()->error("WebRTC: GstWebRTCICEConnectionState -> Failed");
- emit WebRTCSession::instance().stateChanged(WebRTCSession::State::ICEFAILED);
+ emit WebRTCSession::instance().stateChanged(State::ICEFAILED);
break;
default:
break;
@@ -355,8 +359,7 @@ linkNewPad(GstElement *decodebin G_GNUC_UNUSED, GstPad *newpad, GstElement *pipe
if (GST_PAD_LINK_FAILED(gst_pad_link(newpad, queuepad)))
nhlog::ui()->error("WebRTC: unable to link new pad");
else {
- emit WebRTCSession::instance().stateChanged(
- WebRTCSession::State::CONNECTED);
+ emit WebRTCSession::instance().stateChanged(State::CONNECTED);
}
gst_object_unref(queuepad);
}
@@ -632,21 +635,30 @@ WebRTCSession::createPipeline(int opusPayloadType)
}
bool
-WebRTCSession::toggleMuteAudioSrc(bool &isMuted)
+WebRTCSession::isMicMuted() const
{
if (state_ < State::INITIATED)
return false;
GstElement *srclevel = gst_bin_get_by_name(GST_BIN(pipe_), "srclevel");
- if (!srclevel)
+ gboolean muted;
+ g_object_get(srclevel, "mute", &muted, nullptr);
+ gst_object_unref(srclevel);
+ return muted;
+}
+
+bool
+WebRTCSession::toggleMicMute()
+{
+ if (state_ < State::INITIATED)
return false;
+ GstElement *srclevel = gst_bin_get_by_name(GST_BIN(pipe_), "srclevel");
gboolean muted;
g_object_get(srclevel, "mute", &muted, nullptr);
g_object_set(srclevel, "mute", !muted, nullptr);
gst_object_unref(srclevel);
- isMuted = !muted;
- return true;
+ return !muted;
}
void
@@ -777,7 +789,13 @@ WebRTCSession::createPipeline(int)
}
bool
-WebRTCSession::toggleMuteAudioSrc(bool &)
+WebRTCSession::isMicMuted() const
+{
+ return false;
+}
+
+bool
+WebRTCSession::toggleMicMute()
{
return false;
}
diff --git a/src/WebRTCSession.h b/src/WebRTCSession.h
index 653ec2cf..83cabf5c 100644
--- a/src/WebRTCSession.h
+++ b/src/WebRTCSession.h
@@ -9,23 +9,30 @@
typedef struct _GstElement GstElement;
+namespace webrtc {
+Q_NAMESPACE
+
+enum class State
+{
+ DISCONNECTED,
+ ICEFAILED,
+ INITIATING,
+ INITIATED,
+ OFFERSENT,
+ ANSWERSENT,
+ CONNECTING,
+ CONNECTED
+
+};
+Q_ENUM_NS(State)
+
+}
+
class WebRTCSession : public QObject
{
Q_OBJECT
public:
- enum class State
- {
- DISCONNECTED,
- ICEFAILED,
- INITIATING,
- INITIATED,
- OFFERSENT,
- ANSWERSENT,
- CONNECTING,
- CONNECTED
- };
-
static WebRTCSession &instance()
{
static WebRTCSession instance;
@@ -33,14 +40,15 @@ public:
}
bool init(std::string *errorMessage = nullptr);
- State state() const { return state_; }
+ webrtc::State state() const { return state_; }
bool createOffer();
bool acceptOffer(const std::string &sdp);
bool acceptAnswer(const std::string &sdp);
void acceptICECandidates(const std::vector<mtx::events::msg::CallCandidates::Candidate> &);
- bool toggleMuteAudioSrc(bool &isMuted);
+ bool isMicMuted() const;
+ bool toggleMicMute();
void end();
void setStunServer(const std::string &stunServer) { stunServer_ = stunServer; }
@@ -55,16 +63,16 @@ signals:
void answerCreated(const std::string &sdp,
const std::vector<mtx::events::msg::CallCandidates::Candidate> &);
void newICECandidate(const mtx::events::msg::CallCandidates::Candidate &);
- void stateChanged(WebRTCSession::State); // explicit qualifier necessary for Qt
+ void stateChanged(webrtc::State);
private slots:
- void setState(State state) { state_ = state; }
+ void setState(webrtc::State state) { state_ = state; }
private:
WebRTCSession();
bool initialised_ = false;
- State state_ = State::DISCONNECTED;
+ webrtc::State state_ = webrtc::State::DISCONNECTED;
GstElement *pipe_ = nullptr;
GstElement *webrtc_ = nullptr;
unsigned int busWatchId_ = 0;
diff --git a/src/timeline/TimelineViewManager.cpp b/src/timeline/TimelineViewManager.cpp
index ed720056..18151173 100644
--- a/src/timeline/TimelineViewManager.cpp
+++ b/src/timeline/TimelineViewManager.cpp
@@ -8,7 +8,6 @@
#include <QString>
#include "BlurhashProvider.h"
-#include "CallManager.h"
#include "ChatPage.h"
#include "ColorImageProvider.h"
#include "DelegateChooser.h"
@@ -233,6 +232,12 @@ TimelineViewManager::TimelineViewManager(QSharedPointer<UserSettings> userSettin
isInitialSync_ = true;
emit initialSyncChanged(true);
});
+ connect(&WebRTCSession::instance(),
+ &WebRTCSession::stateChanged,
+ this,
+ &TimelineViewManager::callStateChanged);
+ connect(
+ callManager_, &CallManager::newCallParty, this, &TimelineViewManager::callPartyChanged);
}
void
@@ -305,6 +310,13 @@ TimelineViewManager::escapeEmoji(QString str) const
}
void
+TimelineViewManager::toggleMicMute()
+{
+ WebRTCSession::instance().toggleMicMute();
+ emit micMuteChanged();
+}
+
+void
TimelineViewManager::openImageOverlay(QString mxcUrl, QString eventId) const
{
QQuickImageResponse *imgResponse =
diff --git a/src/timeline/TimelineViewManager.h b/src/timeline/TimelineViewManager.h
index a8bd2e06..9a2a6467 100644
--- a/src/timeline/TimelineViewManager.h
+++ b/src/timeline/TimelineViewManager.h
@@ -10,16 +10,17 @@
#include <mtx/responses.hpp>
#include "Cache.h"
+#include "CallManager.h"
#include "DeviceVerificationFlow.h"
#include "Logging.h"
#include "TimelineModel.h"
#include "Utils.h"
+#include "WebRTCSession.h"
#include "emoji/EmojiModel.h"
#include "emoji/Provider.h"
class MxcImageProvider;
class BlurhashProvider;
-class CallManager;
class ColorImageProvider;
class UserSettings;
class ChatPage;
@@ -34,6 +35,10 @@ class TimelineViewManager : public QObject
bool isInitialSync MEMBER isInitialSync_ READ isInitialSync NOTIFY initialSyncChanged)
Q_PROPERTY(
bool isNarrowView MEMBER isNarrowView_ READ isNarrowView NOTIFY narrowViewChanged)
+ Q_PROPERTY(webrtc::State callState READ callState NOTIFY callStateChanged)
+ Q_PROPERTY(QString callPartyName READ callPartyName NOTIFY callPartyChanged)
+ Q_PROPERTY(QString callPartyAvatarUrl READ callPartyAvatarUrl NOTIFY callPartyChanged)
+ Q_PROPERTY(bool isMicMuted READ isMicMuted NOTIFY micMuteChanged)
public:
TimelineViewManager(QSharedPointer<UserSettings> userSettings,
@@ -49,6 +54,11 @@ public:
Q_INVOKABLE TimelineModel *activeTimeline() const { return timeline_; }
Q_INVOKABLE bool isInitialSync() const { return isInitialSync_; }
bool isNarrowView() const { return isNarrowView_; }
+ webrtc::State callState() const { return WebRTCSession::instance().state(); }
+ QString callPartyName() const { return callManager_->callPartyName(); }
+ QString callPartyAvatarUrl() const { return callManager_->callPartyAvatarUrl(); }
+ bool isMicMuted() const { return WebRTCSession::instance().isMicMuted(); }
+ Q_INVOKABLE void toggleMicMute();
Q_INVOKABLE void openImageOverlay(QString mxcUrl, QString eventId) const;
Q_INVOKABLE QColor userColor(QString id, QColor background);
Q_INVOKABLE QString escapeEmoji(QString str) const;
@@ -78,6 +88,9 @@ signals:
void inviteUsers(QStringList users);
void showRoomList();
void narrowViewChanged();
+ void callStateChanged(webrtc::State);
+ void callPartyChanged();
+ void micMuteChanged();
public slots:
void updateReadReceipts(const QString &room_id, const std::vector<QString> &event_ids);
|