Compare commits

...

589 Commits

Author SHA1 Message Date
Nicolas Werner 1f68bc60b5 Actually forward keys using m.forwared_room_key 4 years ago
Nicolas Werner c25fd947a8 Automatic key sharing with trusted users 4 years ago
Nicolas Werner bd0deb7213 Fix crash on exit 4 years ago
Nicolas Werner bc7494473b Answer key requests for all our sessions 4 years ago
DeepBlueV7.X b23913fa7c
Merge pull request #301 from kamathmanu/issue266-filenameOnHover 4 years ago
kamathmanu e1c4f7d516 Make Text element a sibling of the Rectangle to avoid the text from depending on the rectangle's opacity property. Switch to Nheko theme colors 4 years ago
kamathmanu e3c6656613 Avoid hardcoded font size 4 years ago
Weblate 70b5e1e08e Translated using Weblate (Estonian) 4 years ago
kamathmanu a0c2a174ea Display filename on mouse hover 4 years ago
Nicolas Werner 62d0bdbb77 Fix small typo in robustness logic 4 years ago
Nicolas Werner d075a90024 Fix emoji filter losing focus 4 years ago
Nicolas Werner 716992b761 Update translations 4 years ago
Joseph Donofry 3499abd99a
Merge branch 'master' of ssh://github.com/Nheko-Reborn/nheko 4 years ago
Joseph Donofry 31c6857f19
Add some try...catch logic around read-only databases so nheko won't crash if the db doesn't exist 4 years ago
DeepBlueV7.X 82374791c4
Merge pull request #298 from LorenDB/master 4 years ago
DeepBlueV7.X a9c0684a5a
Merge pull request #299 from Nheko-Reborn/qml-linting 4 years ago
Nicolas Werner 1a029112d9 Lint qml with qml-format 4 years ago
DeepBlueV7.X 517a126a44
Merge pull request #270 from Chethan2k1/device-verification 4 years ago
Nicolas Werner 392d7d5568 Try to fix windows build 4 years ago
Nicolas Werner 57a6c05eab More unused variables in bindings 4 years ago
Nicolas Werner 684cfacfad Bump spdlog version 4 years ago
Nicolas Werner 56ba7de501 Fix unused variable warnings on old compilers 4 years ago
Nicolas Werner 8ec76daeda Send master key in verification flow (if we trust it) 4 years ago
Nicolas Werner e5fb9a25ea Try to please the CI gods 4 years ago
Nicolas Werner 08d5a84cbd Fix issues with old qt and bump to 5.10 4 years ago
Nicolas Werner 99ba1f17d3 Merge remote-tracking branch 'origin/master' into cross-signing 4 years ago
Nicolas Werner cd43147b77 Implement signature upload for own master key 4 years ago
Nicolas Werner 7b6fab3373 Calculate verification status from cross-signing sigs and update dynamically 4 years ago
LorenDB d7c3fa844e Begin Esperanto translations 4 years ago
Nicolas Werner 64d5a193f1 Fix in room verification 4 years ago
Nicolas Werner 8a4d85f801 Show different verification errors 4 years ago
Nicolas Werner bca29a4227 Make steps in verification flow explicit 4 years ago
Nicolas Werner 2a79cd2b6b Don't fail on missing key for a device and /rotate-megolm-session command 4 years ago
Nicolas Werner abff61bb6c Fix nullopt when iterating reactions 4 years ago
Nicolas Werner 51964c4fd7 Clean up verification pages a bit 4 years ago
Nicolas Werner d4dccc8e36 Fix room name 4 years ago
Nicolas Werner 600df6d2ec Fix fetching inexistent batch tokens 4 years ago
Nicolas Werner c9de044e32 Handle forwarded room keys 4 years ago
Nicolas Werner 54e3c2c96b Fix verification start to start with request 4 years ago
DeepBlueV7.X 8110f22222
Merge pull request #294 from trilene/master 4 years ago
Nicolas Werner 94690ebd4c Clean up verification and key cache a bit 4 years ago
trilene 28e9a7ad40 Bump mtxclient 4 years ago
trilene 4caa206483 Bump mtxclient 4 years ago
Weblate 1a97859930 Added translation using Weblate (Portuguese (Portugal)) 4 years ago
DeepBlueV7.X e5cb8c08ba
Merge pull request #290 from trilene/master 4 years ago
trilene 5bfe0cd178 Remove duplicate control from settings page 4 years ago
DeepBlueV7.X 5cce5b9999
Merge pull request #289 from trilene/master 4 years ago
trilene e57199412a Allow button colors override 4 years ago
trilene aab6cb88a5 Fix build for those without GStreamer 4 years ago
trilene 3f73853e4b Move ActiveCallBar Qml to separate file 4 years ago
Nicolas Werner 4802c34009 Merge remote-tracking branch 'origin/master' into cross-signing 4 years ago
Weblate 5e0eb945ae Translated using Weblate (French) 4 years ago
Weblate 0cca0f17ee Translated using Weblate (French) 4 years ago
trilene 44cfc8d22a clang-format 4 years ago
trilene da27670cbe Port ActiveCallBar to Qml 4 years ago
Weblate 9169a26e67 Translated using Weblate (Estonian) 4 years ago
Nicolas Werner 54db9c89ed Simplify outbound session setup 4 years ago
Nicolas Werner 67302b3674 Fix some missing english translations 4 years ago
Nicolas Werner 4179be427f Fix binding loop when trying to reply 4 years ago
Weblate beef474582 Translated using Weblate (Estonian) 4 years ago
Weblate e59bf3564d Translated using Weblate (German) 4 years ago
Nicolas Werner 0afe1299e2 Fix emoji escape in file messages 4 years ago
Nicolas Werner 7b7d29737f Fix cut off english translation. 4 years ago
DeepBlueV7.X fe325f9266
Merge pull request #285 from Lurkki14/master 4 years ago
Weblate 6c63762aa4 Translated using Weblate (Estonian) 4 years ago
Weblate 0a7189b6a1 Translated using Weblate (English) 4 years ago
Weblate b4eb1c6df9 Translated using Weblate (English) 4 years ago
Weblate a253eda5fd Translated using Weblate (English) 4 years ago
Weblate 145e207848 Translated using Weblate (Estonian) 4 years ago
Weblate 78572d9d88 Translated using Weblate (English) 4 years ago
Weblate b2c3639f04 Translated using Weblate (German) 4 years ago
Nicolas Werner 4b36585e47 Fix typo in encryption error message 4 years ago
Weblate 1e0962d97c Translated using Weblate (German) 4 years ago
Nicolas Werner 75d7c2608a Update translations 4 years ago
Weblate f10970cc83 Translated using Weblate (Estonian) 4 years ago
Weblate ea2f7283ca Translated using Weblate (Finnish) 4 years ago
Weblate 8cb45ef834 Translated using Weblate (German) 4 years ago
Weblate 5c5134cea2 Translated using Weblate (Estonian) 4 years ago
Jussi Kuokkanen 13658d536e make typing Rectangle extents cleaner 4 years ago
Jussi Kuokkanen bafe181010 Merge branch 'master' of https://github.com/Lurkki14/nheko 4 years ago
Jussi Kuokkanen 438dcd3c5e Merge branch 'master' of https://github.com/Nheko-Reborn/nheko 4 years ago
Lurkki14 8741e5f36a
Set typing Rectangle color directly instead of Connection 4 years ago
DeepBlueV7.X 791a01487b
Merge pull request #286 from trilene/voip 4 years ago
trilene e065bf2205 Remove GStreamer version test 4 years ago
trilene f7beb1b34e clang-format 4 years ago
trilene 8d7c70d912 Fix preprocessor directive 4 years ago
trilene b527c5a21c Adapt device monitoring for GStreamer 1.18 4 years ago
Jussi Kuokkanen 1f71f7227a make Rectangle to fill the gap between input widget and typing users 4 years ago
Jussi Kuokkanen c463568031 simplify check for no typing users 4 years ago
Jussi Kuokkanen 7d72356318 Merge branch 'master' of https://github.com/Nheko-Reborn/nheko 4 years ago
Jussi Kuokkanen e49691fe3b use Rectangle for typing users so it doesn't overlap with the timeline 4 years ago
DeepBlueV7.X c62db00e6f
Merge pull request #284 from Nheko-Reborn/hidden-events 4 years ago
Nicolas Werner c5f93efcd3 Actually use room account data 4 years ago
Nicolas Werner 8af056faa9 Fix avatars in notifications 4 years ago
Nicolas Werner fef0cc2d71 Fix some images not showing up 4 years ago
Nicolas Werner c2f2e8324c Update blurhash 4 years ago
Nicolas Werner b05c101021 Fix null errors in qml 4 years ago
Nicolas Werner 77e241b9e5 Reenable top bar room settings menus on avatar or title clicks 4 years ago
Nicolas Werner 640b0ee405 Port top bar to Qml 4 years ago
DeepBlueV7.X 66d6307252
Merge pull request #283 from trilene/voip 4 years ago
trilene 2526a5604e Remove bus watch when call ends 4 years ago
Weblate 9b8e696979 Translated using Weblate (Estonian) 4 years ago
Weblate 83f0e2772c Translated using Weblate (Estonian) 4 years ago
Weblate 25b5a21ecd Merge branch 'master' of github.com:Nheko-Reborn/nheko 4 years ago
Weblate be2c4e5021 Added translation using Weblate (Estonian) 4 years ago
Weblate ff8cec1ea3 Update translation files 4 years ago
DeepBlueV7.X d02e899941
Merge pull request #281 from trilene/voip 4 years ago
trilene bd0e66b548 Don't show default audio source in settings 4 years ago
CH Chethan Reddy 8eb74daf76 Split qml part of Device Verification 4 years ago
Weblate b5669310e5 Merge branch 'master' of github.com:Nheko-Reborn/nheko 4 years ago
Weblate a09a37cc3c Add simpified chinese translations 4 years ago
Nicolas Werner 94e1b52ddd Single line click handler 4 years ago
Nicolas Werner b934cf329c Clean up UserProfile a bit 4 years ago
DeepBlueV7.X 75e646968d
Merge pull request #275 from Chethan2k1/master 4 years ago
DeepBlueV7.X a7b979084f
Merge pull request #280 from trilene/voip 4 years ago
trilene 124952a11c Ignore empty remote ICE candidates 4 years ago
DeepBlueV7.X d38a4dcf5d
Merge pull request #279 from Lurkki14/master 4 years ago
Jussi Kuokkanen bfcfa79d53 reset filter string when emoji completer is opened 4 years ago
Chethan2k1 f6a47ce72f Some fixes 4 years ago
DeepBlueV7.X b37534aa53
Merge pull request #278 from trilene/voip 4 years ago
trilene b6563d9ffe GStreamer v1.18.0 released 4 years ago
trilene 7d2844b2b0 Fix earlier commit when GStreamer < v1.17 4 years ago
Chethan2k1 2b5deabbdc Fix breaking while using qmlRegisterSingletonInstance 4 years ago
Chethan2k1 a27662dc08 Making sure Verification Objects are deleted properly 4 years ago
Chethan2k1 e70b4e4268 Fix wrong tran_id issue 4 years ago
Chethan2k1 898be090af Add support non-encrypted room-verification messages 4 years ago
Nicolas Werner c4e4938d35 Save account data and allow hiding events via account data 4 years ago
Chethan 0b03d40bf5
Update README.md 4 years ago
Chethan2k1 db0d10f38e Fix Readme 4 years ago
Chethan2k1 e8eeb480d5 Fix Wrong Emojis Issue in Room Verification 4 years ago
DeepBlueV7.X 657f4073e9
Merge branch 'master' into device-verification 4 years ago
CH Chethan Reddy 5358854de3 Add support for Encrypted to-device verification messages 4 years ago
Nicolas Werner 10f09d4f43 Fix catch by value warning 4 years ago
Nicolas Werner e0981e17a1 Fix reactions 4 years ago
Nicolas Werner 8d14a058c6 Fix endless pagination, when old history is inaccessible 4 years ago
DeepBlueV7.X eb554e8266
Merge pull request #267 from Lurkki14/master 4 years ago
Jussi Kuokkanen 8f872f1961 remove unused includes and use QRegularExpression 4 years ago
Jussi Kuokkanen bb4636885d remove comment 4 years ago
Jussi Kuokkanen beec2607fc get completion string based on trigger position instead of current word 4 years ago
Jussi Kuokkanen 254b7549eb ignore enter keypress when completion is selected 4 years ago
CH Chethan Reddy f03a48eec5 fix 4 years ago
CH Chethan Reddy b174bd9380 Merge remote-tracking branch 'upstream/master' into device-verification 4 years ago
CH Chethan Reddy 3396a7a796 Change the tag for mtxclient 4 years ago
CH Chethan Reddy 9a76db85d5 Change ReactionRealtesTo to RelatesTo 4 years ago
Jussi Kuokkanen aed8d23aca don't select emoji completion by default and add minimum string length before showing completions 4 years ago
CH Chethan Reddy 0d1dd29b19 Small Fixes 4 years ago
Jussi Kuokkanen f40d8d15b5 undo changes to emoji_codegen.py 4 years ago
Jussi Kuokkanen 9ad9c8ddf0 fix build and remove commented code 4 years ago
Jussi Kuokkanen 7acd4b3307 lint 4 years ago
Jussi Kuokkanen 5e344d2685 Merge branch 'master' of https://github.com/Nheko-Reborn/nheko 4 years ago
Jussi Kuokkanen a173d964f7 add emoji completer to text input 4 years ago
DeepBlueV7.X b7b9cee30e
Merge pull request #265 from trilene/voip 4 years ago
trilene 67a6ab401b Link GStreamer elements before syncing state 4 years ago
DeepBlueV7.X b58e370c03
Merge pull request #251 from Nheko-Reborn/new-event-store 4 years ago
Nicolas Werner 3df4bde032 Add some log messages, that migrations are in progress 4 years ago
Nicolas Werner 9f79b85579 Speedup db a bit, but loose some crash resiliency 4 years ago
CH Chethan Reddy 19cfd08a55 Verify signatures and find trusted devices 4 years ago
CH Chethan Reddy 1d299951b6 Cache Fix 4 years ago
DeepBlueV7.X 898297a7b0
Merge pull request #263 from trilene/voip 4 years ago
trilene 473293b6a5 Under GStreamer >= 1.17 gather all candidates before sending offer/answer 4 years ago
DeepBlueV7.X 706ba84d73
Merge pull request #262 from bqv/patch-1 4 years ago
Tony O d61d108a4f
Update README.md 4 years ago
CH Chethan Reddy 8a4bd37fea [WIP] Room Verification Works! 4 years ago
DeepBlueV7.X b5d406ad80
Merge pull request #259 from trilene/voip 4 years ago
Nicolas Werner d6bc05fcd6 Bump mtxclient 4 years ago
trilene 1402732b5f Stop SendFile and Call buttons swapping places on file upload 4 years ago
Nicolas Werner 7f7108161e Hide CallCandidates again in new store 4 years ago
Nicolas Werner de7ec4d2b3 Merge remote-tracking branch 'origin/master' into new-event-store 4 years ago
Mihai Fufezan 7c1ca38d98 Translated using Weblate (English) 4 years ago
Mihai Fufezan ed17b0c33b Added translation using Weblate (Romanian) 4 years ago
DeepBlueV7.X 3fece53eb7
Merge pull request #237 from trilene/voip 4 years ago
Nicolas Werner b6751ab01e Appease the linter 4 years ago
Nicolas Werner e06ff1ac1f Fix SVG color undefined 4 years ago
Nicolas Werner f157602a52 Disable call support, when GStreamer is unavailable 4 years ago
Nicolas Werner 29cb065102 Bump flatpak dependency 4 years ago
Nicolas Werner 14a0aac748 Add /clear-timeline command 4 years ago
CH Chethan Reddy 2e20049b36 [WIP] Room-Verification Messages 4 years ago
Nicolas Werner 1e9efa3072 Try to fix variable timestamp width 4 years ago
Nicolas Werner b972d827cb Try to fix issue of pagination interfering with limited: true 4 years ago
Nicolas Werner 7eb0c4e09c Also request keys from own devices 4 years ago
Nicolas Werner 1f9215a5be Split error messages from event decryption 4 years ago
Nicolas Werner dbaddb0165 Further tweak text element 4 years ago
trilene df65093374 Add audio input device selector 4 years ago
trilene 02dfc8039f Conditionally compile against upcoming GStreamer release 4 years ago
trilene b86711a388 Merge remote-tracking branch 'upstream/master' into voip 4 years ago
trilene 979bba6460 Tweak AcceptCall dialog 4 years ago
trilene e527da052b Reset TLVM initial sync flag on logout 4 years ago
trilene e3e7595bab clang-format 4 years ago
trilene f14d141cb5 Improve TURN server retrieval 4 years ago
trilene c0743f9688 Fix error message 4 years ago
HelaBasa 48877307ca Added translation using Weblate (Sinhala) 4 years ago
CH Chethan Reddy 3635c185e9 Add Room Verification Messages 4 years ago
CH Chethan Reddy 1fcd768f88 Adding Room Key Verification Stuff 4 years ago
CH Chethan Reddy a2979c2df1 Updating keys of outdated encrypted users 4 years ago
CH Chethan Reddy 1103cc15cf Adding icons to UserProfile 4 years ago
Nicolas Werner 08028d5c57 Refactor UserProfile 4 years ago
CH Chethan Reddy ac1fbbb69f Some issue with UserProfile 4 years ago
CH Chethan Reddy 6fae36abc4 [WIP] Add Caching for users 4 years ago
CH Chethan Reddy ffa61095b8 Error Handling and some fixes 4 years ago
CH Chethan Reddy 00e36b6068 Add some Userprofile buttons 4 years ago
CH Chethan Reddy 75efa5d3a2 Fix the Weird auto-confirmation and cancellation 4 years ago
CH Chethan Reddy fd232b1f4a Some more fixes 4 years ago
Nicolas Werner 4862be06be Fix presence indicator 4 years ago
CH Chethan Reddy 1633650303 Some more changes 4 years ago
CH Chethan Reddy d49ab15656 Some Improvements 4 years ago
CH Chethan Reddy ce013e67a6 Add some more slots and mac 4 years ago
CH Chethan Reddy 67367d0004 Shared secret with decimal and emoji works! 4 years ago
CH Chethan Reddy 41b6ef0c32 Add DeviceVerificationList to keep track of all flows and Popup on 4 years ago
Chethan2k1 1eb162cb6f Handle Device Verification related to_device messages 4 years ago
Chethan2k1 cd5dd0e39b Add SAS Method choice and Add send_to_device API call 4 years ago
Chethan2k1 b628f485ff Tweak UI for device verification and Add more slots 4 years ago
CH Chethan Reddy f9c0f4dd54 Add C++ Model for DeviceList 4 years ago
Chethan2k1 a54a973ad6 Adding DeviceList for userprofile 4 years ago
CH Chethan Reddy 64f204d984 Rewrite UserProfile in qml 4 years ago
Nicolas Werner 707248fea3 Add DeviceVerificationFlow dummy and verification test button 4 years ago
Nicolas Werner b1362ca69f Use label in device verification dialogs (for proper theming) 4 years ago
Nicolas Werner fed0463e57 Make emojis a bit smaller 4 years ago
Nicolas Werner 480c4bc8f5 Set proper emoji font for device verification 4 years ago
Nicolas Werner 2088053d26 Add DeviceVerificationFlow dummy and verification test button 4 years ago
Nicolas Werner 488cc5e73b First design iteration of device verification dialogs 4 years ago
Nicolas Werner 7f3d97517f Fix double free by closing cursor at the right time 4 years ago
trilene 97681ccf64 Remove references to video calls 4 years ago
Nicolas Werner 12090c0a06 Add workaround for duplicate syncs 4 years ago
Nicolas Werner 720bb164f7 Fix migration (hopefully) 4 years ago
trilene 43ec0c0624 Handle ICE failure 4 years ago
Nicolas Werner ade905c881 Fix shadowing variable 4 years ago
Nicolas Werner a00b11def7 Rename EventStore::event to get to remove ambiguity with QObject::event 4 years ago
Nicolas Werner 28e7ea40cb Bump mtxclient and use git dependency in flatpak 4 years ago
Nicolas Werner 8bf26917ad Make long press menu actually work 4 years ago
trilene 57d5a3d31f Improve debug messages 4 years ago
Nicolas Werner 6f557c19a1 Optimize scrolling a little bit 4 years ago
Nicolas Werner cbb4356b19 Fix more non integer heights 4 years ago
Nicolas Werner fdcf91f5eb Fix binding loop and non integer text height 4 years ago
Nicolas Werner 4e7bd20e0c Reset fetch in progress when fetch failed 4 years ago
Nicolas Werner b294430fe5 Return to redacted messages instead of just storing the redaction 4 years ago
trilene 6be21beebd Acknowledge source of ringtones 4 years ago
trilene aec24efbe2 Specify call type on timeline 4 years ago
trilene d51e34e66a Cancel sent CallInvite if no response within expiry 4 years ago
trilene a4301048e3 Fix QLayout warning 4 years ago
trilene d508e3abd6 Send ICE candidates gathered after timeout 4 years ago
trilene 7377215d28 Confirm logout/quit if active call in progress 4 years ago
trilene 55783c6fe5 Tweak ActiveCallBar 4 years ago
Nicolas Werner 147ae68c31 Don't send url, if we send an encrypted file 4 years ago
trilene 88cfa3a8fa Polish voice call UI 4 years ago
Nicolas Werner f23d733cff Fix room joins 4 years ago
trilene da9995fc3d Update mtxclient reference 4 years ago
Nicolas Werner 19f27236ea Fix reactions 4 years ago
DeepBlueV7.X 12cb77da9c
Merge pull request #247 from not-chicken/centerDate 4 years ago
Nicolas Werner 6f2bc908ba Fix reaction display 4 years ago
Lorem e86c1cc79f Make dateBubble's text vertically aligned to vertical center 4 years ago
Nicolas Werner d467568a65 Close cursor we don't need and where we overwrite the contents 4 years ago
Nicolas Werner 8261446f83 Fix reply scrolling 4 years ago
Nicolas Werner 5695f004a2 Fix race condition between /messages and /sync 4 years ago
Nicolas Werner 046b3f4da6 Mark own events as read again after sending 4 years ago
Nicolas Werner 36e4405f25 Fix flickering of encrypted messages when sending using new store 4 years ago
Nicolas Werner a5dda86a6c Fix encryption indicator 4 years ago
Nicolas Werner 56ea89aa11 Reenable sending messages 4 years ago
trilene 28a678ca60 Add comment wrt TURN server credentials 4 years ago
trilene f8ef55c133 Case-insensitive sdp search 4 years ago
Nicolas Werner aa34576dfd Warn before kicking,banning,inviting,etc 4 years ago
trilene 8968d51b65 Handle preemptive CallAnswer from fellow device 4 years ago
trilene 16209ce073 Hide incoming CallCandidates in encrypted rooms 4 years ago
trilene 195ba5e5ee Remove comments 4 years ago
trilene 9d6cce9fe3 Handle preemptive CallHangUp 4 years ago
trilene 774d864096 Hide CallCandidates events from the timeline 4 years ago
trilene 09d2d937c5 Centre PlaceCall dialog 4 years ago
trilene e85652e7e7 Fix percent-encoding of TURN server URI 4 years ago
Nicolas Werner 9ae7d0dce3 Readd pagination and fix redactions 4 years ago
Nicolas Werner 9479fcde08 Initialize Profile later 4 years ago
trilene c73cfe1810 Merge remote-tracking branch 'upstream/master' into voip 4 years ago
trilene 7a206441c8 Support voice calls 4 years ago
Nicolas Werner da2f80df60 Fix translation loading 4 years ago
Nicolas Werner 3421728898 Fetch missing events 4 years ago
Nicolas Werner 530c531c4b WIP: Event Store split out 4 years ago
Nicolas Werner 7650e6ced6 Fix autolinking breaking on single quotes in href attribute 4 years ago
Nicolas Werner 8d3ab300b6 Open url using QDesktopServices from Qml 4 years ago
Nicolas Werner fe12e63c7c Fix parent undefined warning 4 years ago
Nicolas Werner 0da1a6d5fc Add relations and order without hidden events to db 4 years ago
Nicolas Werner 82eff09062 Fetch event from db and use string_view where possible 4 years ago
Nicolas Werner c79205c26a Use new timeline cache structure 4 years ago
Nicolas Werner 79a29953dd Persist event order 4 years ago
Nicolas Werner 233b3c06ce Store events in room specific db 4 years ago
Nicolas Werner d72eb5eb2d Update dependencies 4 years ago
Nicolas Werner 21a1f249f9 Fix Qt 5.9 build 4 years ago
Nicolas Werner d177405913 Don't compile qml by default 4 years ago
Nicolas Werner f6fa494666 Make connections across threads queued in any case 4 years ago
Nicolas Werner da975038db Drop libsodium dependency 4 years ago
Nicolas Werner a9e321e38f Don't use formatted body without format 4 years ago
DeepBlueV7.X 09e9cffcae
Merge pull request #228 from felixonmars/patch-1 4 years ago
Felix Yan 751c0526d7
Correct a typo in SnackBar.cpp 4 years ago
Nicolas Werner c973fd759b Fix m.relates_to being sent as 'null' when not set in encrypted messages. 4 years ago
Nicolas Werner cded494cb5 Try to make text rendering in qml sharper 4 years ago
Nicolas Werner dde28c627b Lower qt version used for emoji picker and scroll helper 5 years ago
Nicolas Werner 5c8ee99e9a Remove online indicator for offline users 5 years ago
DeepBlueV7.X 887ad25ab7
Merge pull request #220 from not-chicken/theme 5 years ago
Lorem e1706f4df5 Fix not being able to change theme 5 years ago
Nicolas Werner 24d2a2d821 Update mtxclient version to tag 5 years ago
Joseph Donofry 95d5e63f4b
Version number fixes 5 years ago
Joseph Donofry 3ea5a92b2a
Prepare 0.7.2 release 5 years ago
Nicolas Werner a09039a3be Add presence and upgrade notes to the changelog 5 years ago
Joseph Donofry ef51b5e947
Merge pull request #219 from Nheko-Reborn/changelog 5 years ago
Joseph Donofry 21dfb3c0b9
Merge pull request #216 from Nheko-Reborn/presence 5 years ago
Joseph Donofry a96e6e5ecd
Merge branch origin/master and update translations 5 years ago
Joseph Donofry 7afb164244
Merge remote-tracking branch 'origin/master' into presence 5 years ago
Joseph Donofry 813884ee0c
Merge pull request #217 from Nheko-Reborn/reactions 5 years ago
Joseph Donofry dbaf92734c
Fix formatting 5 years ago
Joseph Donofry 34ed487c08
Update translations 5 years ago
Joseph Donofry b90b718961
Merge remote-tracking branch 'origin/master' into reactions 5 years ago
Joseph Donofry b9626f0c69
Additional code cleanup 5 years ago
Nicolas Werner 866f59f79c Update changelog 5 years ago
Nicolas Werner 150c9b1dbc Fix scale factor setting 5 years ago
Nicolas Werner db93e6b853 Fix warning about not registered Presence type 5 years ago
Nicolas Werner 488924c9b3 Allow deleting a status 5 years ago
Joseph Donofry 12c46e86b4
Fix linting 5 years ago
Joseph Donofry 5e355c36fd
Fix build issue on some versions of clang 5 years ago
DeepBlueV7.X 75bb037bb7
Merge pull request #218 from z33ky/alert-notifications 5 years ago
Alexander 'z33ky' Hirsch 2b9860c3af Add setting to alert on notification 5 years ago
Joseph Donofry 39b240e25a
Remove unused QML file 5 years ago
Joseph Donofry 73f4c26dbe
Update translations 5 years ago
Joseph Donofry 0078c72a37
Remove signals in favor of direct function calls 5 years ago
Joseph Donofry 75cdc1eee2
Remove duplicate search icons 5 years ago
Joseph Donofry 07ffd9e7e9
Fix UserSettings in QML 5 years ago
Joseph Donofry 6bb73f84a3
Merge master and fix conflicts 5 years ago
Joseph Donofry fa34749279
Minor adjustments to emoji picker 5 years ago
Nicolas Werner 0a23615dd7 Fix small formatting error 5 years ago
Nicolas Werner b9631753dd Reenable custom status messages 5 years ago
Nicolas Werner 3baf11b5c4 Fix unused capture 5 years ago
Nicolas Werner 814868024c Disable presence and status_msg menu 5 years ago
Nicolas Werner bf440f9a31 Bump mtxclient 5 years ago
Nicolas Werner f2bfa61e08 Add menu actions to set presence 5 years ago
Nicolas Werner 96f4169be9 Show presence and set custom status messages 5 years ago
Nicolas Werner e5a55ab1b9 Smooth scaling for images 5 years ago
Nicolas Werner 5e684a0a2f Download boost from sourceforge 5 years ago
Nicolas Werner c40429af23 Bump mtxclient version 5 years ago
Nicolas Werner f4b84327e8 Limit jobs on arm 5 years ago
Nicolas Werner 43d2ebc095 Fix Qt5.15 issues 5 years ago
Nicolas Werner f8903f493f Extend timeout 5 years ago
Nicolas Werner 95f29a3d19 Try travis_wait alternative 5 years ago
Nicolas Werner 5ca5b4561e Travis wait can only be used in a top level command? 5 years ago
Nicolas Werner 190b6cb3c7 Add travis_wait to flatpak builds 5 years ago
Nicolas Werner c8ba385cb9 Install dependencies for arm manually to prevent timeout 5 years ago
Nicolas Werner 50d5891493 Fix serialization bug introduced in 9eddcfc42f 5 years ago
Nicolas Werner 4d20839d74 Skip precompiled headers for obc-c code 5 years ago
Nicolas Werner 9eddcfc42f Remove some redundant functions 5 years ago
DeepBlueV7.X fd270dcd55 Translated using Weblate (Italian) 5 years ago
Nicolas Werner 6b60ff7713 Rename settings to be more consistent 5 years ago
Nicolas Werner f452bdf2b0 Make settings update immediately in qml and allow limiting timeline width 5 years ago
Nicolas Werner 4ee9e5c27c Improve compile times a tiny bit 5 years ago
Nicolas Werner 4e5bd53b13 Optionally use precompiled headers 5 years ago
Nicolas Werner fe45c49e56 Bump singleapplication version 5 years ago
Nicolas Werner 247539cb5a Set cxx standard as cache variable for when toolchain file is not used 5 years ago
Nicolas Werner 937b35ca8a Fix some join messages showing as empty 5 years ago
Lorenzo Ancora f7cd0c4137 Added translation using Weblate (Italian) 5 years ago
DeepBlueV7.X fbcc53a632
Merge pull request #205 from lkito/SettingTooltips 5 years ago
lkito 60ad6ce277 Added an option to have descriptions for user settings. 5 years ago
DeepBlueV7.X 33ba81a2e6
Merge pull request #204 from lkito/master 5 years ago
Joseph Donofry e20cddd005
Update translations 5 years ago
Joseph Donofry 5228861b88
Add reaction/redaction for in-line Reactions 5 years ago
lkito d8b89e2ef0 Added an optional feature to show bigger emoji-only messages with 3 or less emoji 5 years ago
Joseph Donofry 1c521d1711
Merge origin/master into reactions 5 years ago
Nicolas Werner bdf1147a80 add a bit of additional logging to image paste 5 years ago
Nicolas Werner 23e4408fa8 Add /roomnick command 5 years ago
Nicolas Werner 7de1fc62e8 Tag rooms via context menu 5 years ago
Nicolas Werner 5abdad308d Fix crash when reacting to messages from /sync 5 years ago
Nicolas Werner f0757a6426 Fix local echo for reactions 5 years ago
Nicolas Werner a75d7f00bc Fix reactions binding loop 5 years ago
Joseph Donofry f4ea0b215d
Attach emoji picker picked to reaction sender 5 years ago
Joseph Donofry a5778bdf40
Merge remote-tracking branch 'origin/master' into reactions 5 years ago
Joseph Donofry fca85bea39
Merge pull request #202 from aaronraimist/brew-libolm 5 years ago
Aaron Raimist 3338ecd39f
Remove MacPorts install instructions 5 years ago
Aaron Raimist 14eaad355e
Update macOS building dependencies now that libolm is available in Homebrew 5 years ago
DeepBlueV7.X 883567b0b9
Merge pull request #201 from lkito/master 5 years ago
lkito 2c21f6e3fa Implemented optional message body highlighting feature. 5 years ago
Nicolas Werner 3db9298e66 Bump mtxclient version 5 years ago
Nicolas Werner 73e0a9f453 Drop explicit ZLIB dependency 5 years ago
Nicolas Werner 576269c9e5 Prevent timeline being black on some platforms 5 years ago
Joseph Donofry ca20e2a98f
Remove accidental compiled translation commit 5 years ago
DeepBlueV7.X ce1c6a7df3
Merge pull request #198 from Chethan2k1/device-verification-dialogs 5 years ago
CH Chethan Reddy 57b8cdbd4d remove #if defined __has_include 5 years ago
CH Chethan Reddy e4a7e85935 Change the commit hash for mtxclient 5 years ago
CH Chethan Reddy 30d61e3114 minor fixes 5 years ago
DeepBlueV7.X 54e2e7ef7f Translated using Weblate (German) 5 years ago
Nicolas Werner 5a5b85cded Wait on maybe long running install script in CI 5 years ago
Nicolas Werner 10f0f7462a Try to fix arm builds hanging breaking CI 5 years ago
Joseph Donofry 6d2789f4d5
Merge master into reactions 5 years ago
Joseph Donofry eb4b02e8b9
Merge branch 'master' of ssh://github.com/Nheko-Reborn/nheko 5 years ago
Joseph Donofry 18f934efad
Add un-encrypted warning icon for messages in encrypted rooms 5 years ago
Nicolas Werner 6ff002b4ed Use standard cmake args instead of old -H 5 years ago
Joseph Donofry ff7468e6d5
Update emoji picker and translations 5 years ago
Nicolas Werner 279bcd1bf2 Show inline images 5 years ago
Nicolas Werner 2c3d09edbb Try to smooth scrolling a bit by increasing cacheBuffer 5 years ago
Nicolas Werner d6981355d3 Align scrolling to pixels manually 5 years ago
Joseph Donofry dfb76c693d
Update translations for new emoji picker 5 years ago
Joseph Donofry ee4dcef90f
Add new QML-based emoji picker (work in progress) 5 years ago
Nicolas Werner 004d10bfee Clip replies by default 5 years ago
Joseph Donofry 8984661187
Initial support for sending reactions 5 years ago
Nicolas Werner 000ab4853a Translation updates 5 years ago
Nicolas Werner 6befadeec8 Remove shadowing loginMethod 5 years ago
Nicolas Werner 9713284435 Fix not being able to log http status on login 5 years ago
Nicolas Werner 15716f5a34 bump mtxclient for SSO 5 years ago
Nicolas Werner 7b1fa60cc6 Add SSO 5 years ago
Nicolas Werner 813790e603 Improve Login and Register page hinting 5 years ago
DeepBlueV7.X 197f702dd0
Merge pull request #195 from jonnius/patch-1 5 years ago
jonnius 5f9ce33562 Update Ubuntu install and build instructions 5 years ago
Joseph Donofry 991fa4ac98
Change redaction color that accidentally got committed 5 years ago
Joseph Donofry 5b9611dcd3
Merge branch 'master' into reactions 5 years ago
Joseph Donofry cc92161339
Fix redaction color change that accidentally got committed 5 years ago
Joseph Donofry a1661f7006
merge master into reactions 5 years ago
DeepBlueV7.X 7beaf868ef
Merge pull request #192 from Nheko-Reborn/reactions 5 years ago
Nicolas Werner a942ae9b93 Fix messages being immediately read again 5 years ago
Nicolas Werner 0b1d3a40f4 Remove unused binding name 5 years ago
Nicolas Werner e48dfd15fe Add shortcuts to select/deselect reply targets 5 years ago
Nicolas Werner e5a5a66716 Fix reaction redaction for real this time 5 years ago
Nicolas Werner ff54ce9334 Fix rooms with a lot of reactions not paginating correctly 5 years ago
Nicolas Werner e045e3eb1c Show displayname on reactions 5 years ago
Nicolas Werner 8348a6c35d Fix reaction count 5 years ago
Nicolas Werner a1951056da Reaction and Button layout fixes 5 years ago
Nicolas Werner e55a09906f Misc color fixes 5 years ago
Nicolas Werner ca5490074a Fix scroll to replied to message (somewhat) 5 years ago
Nicolas Werner 692c6119b4 Fix joined rooms dropping to the bottom at first 5 years ago
Nicolas Werner db24f174b1 Fix jumpy room list, when sort order is ambiguous 5 years ago
Nicolas Werner 846ff33ed8 Position reaction emoji and text on the same baseline 5 years ago
Nicolas Werner 54013e4a00 Basic, broken reaction display 5 years ago
Nicolas Werner a9aed09d35 Add placeholder for reactions 5 years ago
Nicolas Werner a8b22e49c3 Fix self counting as read message 5 years ago
Nicolas Werner 0c73c74574 Speed up initial load a bit 5 years ago
DeepBlueV7.X ccd55c70df
Merge pull request #189 from Nheko-Reborn/db-migrations 5 years ago
Nicolas Werner d6685e8d61 Add delete pending_receipts migration 5 years ago
Nicolas Werner dad2de7ba2 Add support for db migrations 5 years ago
DeepBlueV7.X 38417a374d
Merge pull request #184 from shocklateboy92/features/backlog-progress 5 years ago
Lasath Fernando 166ed0674d Run linter 5 years ago
Nicolas Werner 9750241e73 Old Qt doesn't have .get... 5 years ago
DeepBlueV7.X 00c4d2629a
Merge pull request #188 from Nheko-Reborn/optimize-cpu-usage 5 years ago
Nicolas Werner 641a883bfd Optimize RoomList sorting 5 years ago
Nicolas Werner e6fcccc8bd Don't store pending receipts in cache 5 years ago
Nicolas Werner 2997155f56 Fix spacing of typing notifications 5 years ago
Nicolas Werner 0716bbafff Strip reply fallback from plain text body 5 years ago
Lasath Fernando 21b33363ce Make pagination spinner inline 5 years ago
Lasath Fernando 1f9d3024b1 Add visual indication that data is being fetched 5 years ago
DeepBlueV7.X 19ec8d261d
Merge pull request #183 from shocklateboy92/features/smooth-scroll 5 years ago
Nicolas Werner 3226d1787c Fix some smaller coloring issues 5 years ago
Nicolas Werner bb66254c58 Make notifications a bit easier to read 5 years ago
Nicolas Werner 27350cf51e Fix high CPU usage on high dpi screens 5 years ago
Lasath Fernando c5d373e8f1 Address CR comments 5 years ago
Lasath Fernando 5c57de070e Remove commented out code; fix padding for Pill 5 years ago
Lasath Fernando 84c5ff0bcb Fix binding loops for height 5 years ago
Lasath Fernando 1555dc2296 Shamelessly steal `ScrollHelper.qml` from spectral 5 years ago
Nicolas Werner 17c657a170 Send correct orientation for exif rotated images 5 years ago
Nicolas Werner dbb13dfad6 Update Changelog 5 years ago
Nicolas Werner d6386546b3 Only prune old messages every 500 syncs 5 years ago
Nicolas Werner 28adc9dc9b Respect exif rotation of images 5 years ago
Nicolas Werner d94ac86816 Fix double percent encoding of invites 5 years ago
Joseph Donofry a5d5ea1881
Fix URL in appdata.xml 5 years ago
Joseph Donofry 14ce840649 Update changelog 5 years ago
Joseph Donofry 619d9bc185 Merge remote-tracking branch 'origin/master' into release-prep-0.7.1 5 years ago
Joseph Donofry 4e3b190ac7
Merge pull request #173 from Nheko-Reborn/decrypt-sidebar 5 years ago
Joseph Donofry 5893365439 Change decrypt setting again 5 years ago
Joseph Donofry 18557023d9 Fix CHANGELOG typo 5 years ago
Joseph Donofry 87c06f0fc9 Merge branch 'decrypt-sidebar' of ssh://github.com/Nheko-Reborn/nheko into decrypt-sidebar 5 years ago
Joseph Donofry 994edb836c Change how decrypt value is retrieved from settings 5 years ago
Nicolas Werner ddcab64315 Update versions to 0.7.1 5 years ago
Joseph Donofry 69b0b68f92
Merge branch 'master' into decrypt-sidebar 5 years ago
Nicolas Werner b00e624fb0 Fix own messages not showing as encrypted 5 years ago
Nicolas Werner d5e1475a5c Fix encrypted messages not showing a user in the sidebar 5 years ago
Nicolas Werner 4720d2b562 Update translations 5 years ago
Joseph Donofry b298f01d92
Add signal/slot for decryption changes in last patch 5 years ago
Nicolas Werner 35601b6bef Terminate user color calculation, when no solution can be found 5 years ago
Joseph Donofry a4c280a4f9
Add ability to toggle sidebar messages for encrypted rooms 5 years ago
Nicolas Werner 0e1bb5137b Add menu entry to show decrypted source 5 years ago
Nicolas Werner 69c8476cb7 Update changelog 5 years ago
Nicolas Werner ba3d405513 Fix Binding restoreMode warning spamming logs 5 years ago
Nicolas Werner 6a39b29acc Disable opening images, when they are not loaded 5 years ago
Nicolas Werner e8271acd99 Fix own encrypted images not showing up after sending 5 years ago
DeepBlueV7.X 22ecdfc3ff
Merge pull request #169 from not-chicken/master 5 years ago
Lorem b4622f510e Add repology badge to readme 5 years ago
Joseph Donofry 03a838709b
Update appdata.xml 5 years ago
Joe 0768a70e75 Fix flatpak builds referencing wrong branch 5 years ago
Joe 9d46eb123f Release v0.7.0 5 years ago
Nicolas Werner 6e1c57a702 Prepare 0.7.0 release 5 years ago
Nicolas Werner eff8af6fac Try to fix messages getting stuck by sometimes sending them twice and never failing them 5 years ago
DeepBlueV7.X 076a1c3607
Merge pull request #167 from jevolk/master 5 years ago
Jason Volk 1eede32a8b Add missing qml-module-qtgraphicaleffects and qml-module-qtquick-controls2 to Ubuntu 19.10 in README. 5 years ago
Nicolas Werner 695c3c0814 Fix registration on construct 5 years ago
Nicolas Werner 70b0cbcd0f Pull in parsing fixes in mtxclient 5 years ago
Nicolas Werner a32bf3d4fc Add minimum cmark version to CMakeList 5 years ago
Nicolas Werner dd1dca3751 Add --debug switch 5 years ago
Nicolas Werner a70044e67b Cache decrypted events 5 years ago
Nicolas Werner c739a0422d Fix encrypted event indicator 5 years ago
Nicolas Werner 277c0e9564 Update translations 5 years ago
DeepBlueV7.X 46f3c97879 Translated using Weblate (English) 5 years ago
DeepBlueV7.X 741da4ab32 Translated using Weblate (German) 5 years ago
Nicolas Werner 872507002a Bump mtxclient to v0.3.0-rc1 5 years ago
Nicolas Werner 3ae4e49b5f Remove unused paramters 5 years ago
Nicolas Werner dcddea6fb8 Fix reply not closing 5 years ago
Nicolas Werner 82ec022f9c Fix notification not being cleared, when read event didn't cause a notification 5 years ago
Nicolas Werner 2b24a978e1 Mid color is used for scrollbars it seems 5 years ago
Nicolas Werner 2022775dd0 Clear notifications when event is read 5 years ago
Nicolas Werner 537cc966cc Use Item for transparent rectangles 5 years ago
Nicolas Werner d68b24188f Size reply background by contents 5 years ago
Nicolas Werner d2e495532d Make Filemessage background size by content 5 years ago
adasauce 5f7f564e57
Merge pull request #161 from Nheko-Reborn/palettes 5 years ago
Nicolas Werner 4fa12b52aa Add comment on why we surpress some of those log messages 5 years ago
Nicolas Werner 6794b6472d Add a few missing palette colors 5 years ago
Nicolas Werner 31a5972f2a Fix UserProfile resizing 5 years ago
Nicolas Werner 94f5b25888 Change fallback style to fusion 5 years ago
Nicolas Werner 8bf25f3d80 Set palette globally 5 years ago
Nicolas Werner d35cc3dda4 Use palette for qml code for theming 5 years ago
DeepBlueV7.X 384ef13c76
Merge pull request #160 from cloudrac3r/ubuntu-19.10-dependencies 5 years ago
Cadence Ember fd1825b7c0
Update Ubuntu 19.10 dependencies list 5 years ago
DeepBlueV7.X e389588bc2
Merge pull request #157 from Alch-Emi/no-escape 5 years ago
Emi Simpson a9bbea881e
Removed double-sanitization of ... is typing names 5 years ago
DeepBlueV7.X 0913887546
Merge pull request #156 from abma/patch-1 5 years ago
abma 5e14bbd1cd
fix #155: use correct subsystem on windows 5 years ago
Nicolas Werner 95272ce4ab Try to fix avatars not updating after deletion 5 years ago
Nicolas Werner 7a2b996f18 Fix high CPU usage on 5XX error codes 5 years ago
Nicolas Werner 7df6529310 Fix avatar not being set for some rooms in the top bar 5 years ago
Nicolas Werner b25e481418 Clean up unused avatar functions 5 years ago
Nicolas Werner 31a83c515e Fix broken avatars in quick switcher 5 years ago
DeepBlueV7.X c32a8bc226
Merge pull request #154 from adasauce/image-modal-download 5 years ago
Adasauce d0a1e81f43
Better image overlay handling when downloading 5 years ago
DeepBlueV7.X b41e2e6f18
Merge pull request #146 from Alch-Emi/priority-sort 5 years ago
Emi Simpson 5c308b1caf
Fixed bug caused by me forgetting what types im using (invites appearing at the bottom of the list instead of the top when priority sorting was off) 5 years ago
Emi Simpson 06676cfb33
Updated translations 5 years ago
Emi Simpson d51cbe7e32
Place invites on top of room list even when sorting by importance is off 5 years ago
Emi Simpson 78ac902549
Clean up stray comment 5 years ago
Emi Simpson 12aa94ad9a
Fixed compatibility with Qt < 5.11 5 years ago
Emi Simpson bf5ae884de
Make toggle in settings revert between old behavior and new behavior for sorting by unreads 5 years ago
Emi Simpson abac4c8d34
Sort room list on setting change 5 years ago
Emi Simpson 2320bfea26
Foiled by the linter once more 5 years ago
Emi Simpson 6bbe8ade0f
Made merge compatible with master 5 years ago
Emi Simpson b6bd36ac16
Added toggle in the settings to ignore minor events 5 years ago
Emi Simpson 81c9cb5c79
Switched room importance to an enum 5 years ago
Emi Simpson a5b388db15
Appease the linter 5 years ago
Emi Simpson 389117f1e8
Add a comment explaining room importance 5 years ago
Emi Simpson 0153dc7a39
Automatically move rooms down in the list once they've been read 5 years ago
Emi Simpson b2a6232eb3
Fixed channels appearing out of order when only mentions were present, without any non-mentions 5 years ago
Emi Simpson 08125e8c44
Sort room list by room priority 5 years ago
DeepBlueV7.X 5e991af57f
Merge pull request #145 from PC25/master 5 years ago
Pushpam Choudhary 93bcdbed2d Update minor fixes in translations as requested 5 years ago
Pushpam Choudhary 71ba8cb284 Add periods to terminate non-user messages in the timeline 5 years ago
Nicolas Werner 0f1a352dd7 Make buttons hideable via setting, button contents can still be accessed via context menu 5 years ago
Nicolas Werner 69a50c15c7 Reduce times we read upload data 5 years ago
Nicolas Werner a071f55c7b Fix images without size 5 years ago
DeepBlueV7.X baaa687d33
Merge pull request #142 from Alch-Emi/master 5 years ago
DeepBlueV7.X 50eec6a96b Translated using Weblate (German) 5 years ago
Emi Simpson 3fddf69958
Don't request access to the user's home directory 5 years ago
DeepBlueV7.X fc2f08a186
Merge pull request #137 from Nheko-Reborn/blurhash 5 years ago
Nicolas Werner 31a403a68e Update mtxclient in flatpak for blurhash 5 years ago
Nicolas Werner b894ce4dcd Make blurhash provider async 5 years ago
Nicolas Werner 5ac18f1f5f Speed up blurhash code 5 years ago
adasauce 005ed00d67
Merge pull request #131 from adasauce/shh 5 years ago
Adasauce ee176de1ec
update translations 5 years ago
Adasauce ed4bb1a576
Add support for more room events in timeline 5 years ago
Joseph Donofry ca1d3203f5 Translated using Weblate (English) 5 years ago
Joseph Donofry 1046958099
Update translations 5 years ago
Joe 0f9c9df0a9 Merge remote-tracking branch 'weblate/master' 5 years ago
DeepBlueV7.X 7da5361995
Merge pull request #133 from adasauce/member1-and-too-many-others 5 years ago
Nicolas Werner 0fc98b2692 Experimental blurhash implementation (MXC2448) 5 years ago
Adasauce 309461dcba
total + first member shows as one too many room members 5 years ago
Nicolas Werner 328a3c7ebd Fix avatar layering in room list 5 years ago
DeepBlueV7.X 1a2b8b7a6b
Upload nightlies to #nheko-nightlies:neko.dev (#132) 5 years ago
Joseph Donofry 30cb7c5b02
Merge pull request #130 from Nheko-Reborn/0.7.0-dev 5 years ago
Joseph Donofry 326e327d2c
Merge branch 'master' into 0.7.0-dev 5 years ago
Nicolas Werner 2fd6385403 Make small sidebar sizing consistent 5 years ago
Nicolas Werner b80697b072 Fix encrypted messages being pinned to the bottom after read marker fix 5 years ago
Nicolas Werner 095b59c01f Delete unused variable 5 years ago
Nicolas Werner 9efa001bcf Fix high dpi scaling of avatars 5 years ago
Nicolas Werner 11bffd5d90 Revert change from TextEdit to TextArea 5 years ago
Nicolas Werner b1eff0a37f Improve link theming 5 years ago
Nicolas Werner 4d79f8078e Don't let click on reply steal focus from the replied to elements 5 years ago
Nicolas Werner 505a8b5742 Only show reply button and option button on hover 5 years ago
Nicolas Werner bed8c52891 Fix colour in svg not being actually white 5 years ago
Nicolas Werner 5115339636 Wrap error label on login page 5 years ago
Nicolas Werner 1eb2869fa8 Fix stuck unread messages by storing state events in the db 5 years ago
Nicolas Werner 62b962cb44 Improve read status handling in room list 5 years ago
DeepBlueV7.X f0db1c5f12
Merge pull request #129 from nico202/master 5 years ago
nixo 3c57c322d3 Add Guix build and installation instructions 5 years ago
Nicolas Werner 653ad0be97 Fix placeholder text being visible behind transparent avatars 5 years ago
DeepBlueV7.X 58c5bdd7b1
Delete old ISSUE_TEMPLATE 5 years ago
DeepBlueV7.X 17063c4f39 Update issue templates 5 years ago
Nicolas Werner f3a2fdb7c9 Fix windows build 5 years ago
Nicolas Werner 33f9b9672a Try to use bundled openssl on macOS 5 years ago
Nicolas Werner e96241265e Use third_party singleapplication helper 5 years ago
Nicolas Werner 3ef0d9db3c Fix Registration 5 years ago
DeepBlueV7.X fee00746c8
Merge pull request #128 from adasauce/no-setstylesheet 5 years ago
Adasauce 6e6fe0cad2
remove setStyleSheet calls directly from widget code 5 years ago
DeepBlueV7.X 1de7efd4ea
Merge pull request #98 from protesilaos/master 5 years ago
Protesilaos Stavrou eb61ca8796
Update README with reference to Debian package 5 years ago
Tulir 7277df5591 Added translation using Weblate (Finnish) 5 years ago
  1. 1
      .ci/install.sh
  2. 27
      .ci/script.sh
  3. 21
      .ci/upload-nightly.ps1
  4. 9
      .ci/upload-nightly.sh
  5. 33
      .github/ISSUE_TEMPLATE/bug_report.md
  6. 20
      .github/ISSUE_TEMPLATE/feature_request.md
  7. 4
      .gitignore
  8. 65
      .travis.yml
  9. 117
      CHANGELOG.md
  10. 143
      CMakeLists.txt
  11. 82
      README.md
  12. 52
      appveyor.yml
  13. 2
      cmake/Hunter/config.cmake
  14. 77
      cmake/HunterGate.cmake
  15. 2
      cmake/Translations.cmake
  16. 18
      io.github.NhekoReborn.Nheko.json
  17. BIN
      resources/icons/ui/end-call.png
  18. BIN
      resources/icons/ui/microphone-mute.png
  19. BIN
      resources/icons/ui/microphone-unmute.png
  20. BIN
      resources/icons/ui/place-call.png
  21. BIN
      resources/icons/ui/search.png
  22. BIN
      resources/icons/ui/search@2x.png
  23. BIN
      resources/icons/ui/unlock.png
  24. BIN
      resources/icons/ui/unlock@2x.png
  25. 1
      resources/langs/.gitignore
  26. 2243
      resources/langs/nheko_cs.ts
  27. 1280
      resources/langs/nheko_de.ts
  28. 1594
      resources/langs/nheko_el.ts
  29. 1390
      resources/langs/nheko_en.ts
  30. 2252
      resources/langs/nheko_eo.ts
  31. 2257
      resources/langs/nheko_et.ts
  32. 1658
      resources/langs/nheko_fi.ts
  33. 1686
      resources/langs/nheko_fr.ts
  34. 2249
      resources/langs/nheko_it.ts
  35. 1207
      resources/langs/nheko_ja.ts
  36. 1602
      resources/langs/nheko_nl.ts
  37. 1632
      resources/langs/nheko_pl.ts
  38. 2241
      resources/langs/nheko_pt_PT.ts
  39. 2243
      resources/langs/nheko_ro.ts
  40. 1634
      resources/langs/nheko_ru.ts
  41. 2241
      resources/langs/nheko_si.ts
  42. 1628
      resources/langs/nheko_zh_CN.ts
  43. 5
      resources/media/README.txt
  44. BIN
      resources/media/callend.ogg
  45. BIN
      resources/media/ring.ogg
  46. BIN
      resources/media/ringback.ogg
  47. 115
      resources/nheko.appdata.xml
  48. 4
      resources/nheko.svg
  49. 113
      resources/qml/ActiveCallBar.qml
  50. 119
      resources/qml/Avatar.qml
  51. 58
      resources/qml/EncryptionIndicator.qml
  52. 51
      resources/qml/ImageButton.qml
  53. 54
      resources/qml/MatrixText.qml
  54. 95
      resources/qml/Reactions.qml
  55. 108
      resources/qml/ScrollHelper.qml
  56. 84
      resources/qml/StatusIndicator.qml
  57. 230
      resources/qml/TimelineRow.qml
  58. 875
      resources/qml/TimelineView.qml
  59. 175
      resources/qml/UserProfile.qml
  60. 124
      resources/qml/delegates/FileMessage.qml
  61. 78
      resources/qml/delegates/ImageMessage.qml
  62. 419
      resources/qml/delegates/MessageDelegate.qml
  63. 6
      resources/qml/delegates/NoticeMessage.qml
  64. 17
      resources/qml/delegates/Pill.qml
  65. 6
      resources/qml/delegates/Placeholder.qml
  66. 371
      resources/qml/delegates/PlayableMediaMessage.qml
  67. 117
      resources/qml/delegates/Reply.qml
  68. 11
      resources/qml/delegates/TextMessage.qml
  69. 46
      resources/qml/device-verification/AwaitingVerificationConfirmation.qml
  70. 144
      resources/qml/device-verification/DeviceVerification.qml
  71. 69
      resources/qml/device-verification/DigitVerification.qml
  72. 33
      resources/qml/device-verification/EmojiElement.qml
  73. 414
      resources/qml/device-verification/EmojiVerification.qml
  74. 56
      resources/qml/device-verification/Failed.qml
  75. 46
      resources/qml/device-verification/NewVerificationRequest.qml
  76. 38
      resources/qml/device-verification/Success.qml
  77. 56
      resources/qml/device-verification/Waiting.qml
  78. 66
      resources/qml/device-verification/sas-emoji.json
  79. 16
      resources/qml/emoji/EmojiButton.qml
  80. 332
      resources/qml/emoji/EmojiPicker.qml
  81. 2
      resources/qtquickcontrols2.conf
  82. 29
      resources/res.qrc
  83. 50
      resources/styles/nheko-dark.qss
  84. 42
      resources/styles/nheko.qss
  85. 37
      resources/styles/system.qss
  86. 17
      scripts/emoji_codegen.py
  87. 38
      scripts/generate_icns.sh
  88. 19
      src/AvatarProvider.cpp
  89. 38
      src/BlurhashProvider.cpp
  90. 51
      src/BlurhashProvider.h
  91. 1871
      src/Cache.cpp
  92. 46
      src/Cache.h
  93. 50
      src/CacheCryptoStructs.h
  94. 12
      src/CacheStructs.h
  95. 166
      src/Cache_p.h
  96. 457
      src/CallManager.cpp
  97. 76
      src/CallManager.h
  98. 741
      src/ChatPage.cpp
  99. 52
      src/ChatPage.h
  100. 12
      src/CommunitiesList.cpp
  101. Some files were not shown because too many files have changed in this diff Show More

@ -3,6 +3,7 @@
set -ex
if [ "$FLATPAK" ]; then
sudo apt-get -y install flatpak flatpak-builder elfutils
flatpak remote-add --user --if-not-exists flathub https://flathub.org/repo/flathub.flatpakrepo
flatpak --noninteractive install --user flathub org.kde.Platform//5.14
flatpak --noninteractive install --user flathub org.kde.Sdk//5.14

@ -6,8 +6,27 @@ if [ "$FLATPAK" ]; then
mkdir -p build-flatpak
cd build-flatpak
flatpak-builder --ccache --repo=repo --subject="Build of Nheko ${VERSION} `date`" app ../io.github.NhekoReborn.Nheko.json
flatpak build-bundle repo nheko-${VERSION}-${ARCH}.flatpak io.github.NhekoReborn.Nheko 0.7.0-dev
jobsarg=""
if [ "$ARCH" = "arm64" ]; then
jobsarg="--jobs=2"
fi
flatpak-builder --ccache --repo=repo --subject="Build of Nheko ${VERSION} $jobsarg `date`" app ../io.github.NhekoReborn.Nheko.json &
# to prevent flatpak builder from timing out on arm, run it in the background and print something every minute for up to 30 minutes.
minutes=0
limit=40
while kill -0 $! >/dev/null 2>&1; do
if [ $minutes == $limit ]; then
break;
fi
minutes=$((minutes+1))
sleep 60
done
flatpak build-bundle repo nheko-${VERSION}-${ARCH}.flatpak io.github.NhekoReborn.Nheko master
mkdir ../artifacts
mv nheko-*.flatpak ../artifacts
@ -43,9 +62,6 @@ cmake -GNinja -H. -Bbuild \
-DHUNTER_ROOT=".hunter" \
-DHUNTER_ENABLED=ON -DBUILD_SHARED_LIBS=OFF \
-DCMAKE_BUILD_TYPE=RelWithDebInfo -DHUNTER_CONFIGURATION_TYPES=RelWithDebInfo \
-DUSE_BUNDLED_OPENSSL=OFF \
-DOPENSSL_ROOT_DIR=/usr/local/opt/openssl \
-DOPENSSL_INCLUDE_DIR=/usr/local/opt/openssl/include \
-DCMAKE_PREFIX_PATH=/usr/local/opt/qt5 \
-DCI_BUILD=ON
else
@ -55,7 +71,6 @@ cmake -GNinja -H. -Bbuild \
-DHUNTER_ROOT=".hunter" \
-DHUNTER_ENABLED=ON -DBUILD_SHARED_LIBS=OFF \
-DCMAKE_BUILD_TYPE=RelWithDebInfo -DHUNTER_CONFIGURATION_TYPES=RelWithDebInfo \
-DUSE_BUNDLED_OPENSSL=OFF \
-DCI_BUILD=ON
fi
cmake --build build

@ -0,0 +1,21 @@
$file = "nheko_win_64.zip"
$fileName = "nheko-${env:APPVEYOR_REPO_BRANCH}-${env:APPVEYOR_REPO_COMMIT}-win64.zip"
$response = Invoke-RestMethod -uri "https://matrix.neko.dev/_matrix/media/r0/upload?filename=$fileName" -Method Post -Infile "$file" -ContentType 'application/x-compressed' -Headers @{"Authorization"="Bearer ${env:MATRIX_ACCESS_TOKEN}"}
$txId = [DateTimeOffset]::Now.ToUnixTimeSeconds()
$fileSize = (Get-Item $file).Length
$body = @{
"body" = "${fileName}"
"filename"= "${fileName}"
"info" = @{
"mimetype" = "application/x-compressed"
"size" = ${fileSize}
}
"msgtype" = "m.file"
"url" = ${response}.content_uri
} | ConvertTo-Json
$room = "!TshDrgpBNBDmfDeEGN:neko.dev"
Invoke-RestMethod -uri "https://matrix.neko.dev/_matrix/client/r0/rooms/${room}/send/m.room.message/${txid}" -Method Put -Body "$body" -ContentType 'application/json' -Headers @{"Authorization"="Bearer ${env:MATRIX_ACCESS_TOKEN}"}

@ -0,0 +1,9 @@
#!/bin/sh
file=$(find artifacts/ -type f -exec basename {} \;)
fileName="nheko-${TRAVIS_BRANCH}-${file#nheko-}"
uri=$(curl -H "Authorization: Bearer ${MATRIX_ACCESS_TOKEN}" -H "Content-Type: application/x-compressed" -X POST --data-binary "@artifacts/${file}" "https://matrix.neko.dev/_matrix/media/r0/upload?filename=${fileName}" --http1.1 | python -c "import sys, json; print(json.load(sys.stdin)['content_uri'])")
echo "Uploaded to ${uri}"
curl -H "Authorization: Bearer ${MATRIX_ACCESS_TOKEN}" -H "Content-Type: application/json" -X PUT -d "{ \"body\": \"${fileName}\", \"filename\": \"${fileName}\", \"info\": { \"mimetype\": \"application/x-compressed\", \"size\": $(wc -c < artifacts/${file}) }, \"msgtype\": \"m.file\", \"url\": \"${uri}\" }" "https://matrix.neko.dev/_matrix/client/r0/rooms/${ROOM}/send/m.room.message/$(date +%s)"

@ -1,6 +1,27 @@
<!--
If you want to request a feature or ask a question, feel free to remove all the irrelevant text.
-->
---
name: Bug report
about: Create a report to help us improve
title: ''
labels: bug
assignees: ''
---
### Describe the bug
A clear and concise description of what the bug is.
### To Reproduce
Steps to reproduce the behavior:
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See error
### Expected behavior
A clear and concise description of what you expected to happen.
### Screenshots
If applicable, add screenshots to help explain your problem.
### System:
@ -11,12 +32,6 @@ If you want to request a feature or ask a question, feel free to remove all the
- C++ compiler: <!-- if you compiled it yourself -->
- Desktop Environment: <!-- for Linux -->
### Actual behavior
### Expected behavior
### Steps to reproduce
### Logs
<!-- If applicable -->

@ -0,0 +1,20 @@
---
name: Feature request
about: Suggest an idea for this project
title: ''
labels: enhancement
assignees: ''
---
**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
**Describe the solution you'd like**
A clear and concise description of what you want to happen.
**Describe alternatives you've considered**
A clear and concise description of any alternative solutions or features you've considered.
**Additional context**
Add any other context or screenshots about the feature request here.

4
.gitignore vendored

@ -6,6 +6,7 @@ cscope*
/.ccls-cache
/.exrc
.gdb_history
.hunter
# GTAGS
GTAGS
@ -70,7 +71,8 @@ install_manifest.txt
.LSOverride
# Icon must end with two \r
Icon
Icon
# Thumbnails
._*

@ -33,6 +33,8 @@ matrix:
- ninja
- openssl
- qt5
- python3 # for uploads
update: true # workaround for broken travis homebrew
- os: linux
compiler: gcc-7
env:
@ -61,21 +63,21 @@ matrix:
env:
- CXX=g++-8
- CC=gcc-8
- QT_PKG=59
- QT_PKG=510
addons:
apt:
sources:
- ubuntu-toolchain-r-test
- sourceline: 'ppa:beineri/opt-qt597-xenial'
- sourceline: 'ppa:beineri/opt-qt-5.10.1-xenial'
packages:
- g++-8
- ninja-build
- qt59base
- qt59tools
- qt59svg
- qt59multimedia
- qt59quickcontrols2
- qt59graphicaleffects
- qt510base
- qt510tools
- qt510svg
- qt510multimedia
- qt510quickcontrols2
- qt510graphicaleffects
- liblmdb-dev
- libgl1-mesa-dev # needed for missing gl.h
- os: linux
@ -83,23 +85,23 @@ matrix:
env:
- CXX=clang++-6.0
- CC=clang-6.0
- QT_PKG=59
- QT_PKG=510
addons:
apt:
sources:
- ubuntu-toolchain-r-test
- llvm-toolchain-xenial-6.0
- sourceline: 'ppa:beineri/opt-qt597-xenial'
- sourceline: 'ppa:beineri/opt-qt-5.10.1-xenial'
packages:
- clang++-6.0
- g++-7
- ninja-build
- qt59base
- qt59tools
- qt59svg
- qt59multimedia
- qt59quickcontrols2
- qt59graphicaleffects
- qt510base
- qt510tools
- qt510svg
- qt510multimedia
- qt510quickcontrols2
- qt510graphicaleffects
- liblmdb-dev
- libgl1-mesa-dev # needed for missing gl.h
- os: linux
@ -111,10 +113,6 @@ matrix:
apt:
sources:
- sourceline: 'ppa:alexlarsson/flatpak'
packages:
- flatpak
- flatpak-builder
- elfutils
- os: linux
arch: arm64
env:
@ -126,9 +124,6 @@ matrix:
sources:
- sourceline: 'ppa:alexlarsson/flatpak'
packages:
- flatpak
- flatpak-builder
- elfutils
- librsvg2-bin
before_install:
@ -143,34 +138,14 @@ script:
- sed -i -e "s/VERSION_NAME_VALUE/${VERSION}/g" ./.ci/bintray-release.json || true
- cp ./.ci/bintray-release.json .
deploy:
- provider: s3
access_key_id: $ARTIFACTS_KEY
secret_access_key: $ARTIFACTS_SECRET
bucket: $ARTIFACTS_BUCKET
region: $AWS_DEFAULT_REGION
detect_encoding: true
cache_control: "max-age=31536000"
skip_cleanup: true
acl: public_read
local_dir: artifacts
on:
condition: "$DEPLOYMENT == 1"
repo: Nheko-Reborn/nheko
tags: false
all_branches: true
- provider: bintray
user: "redsky17"
key:
secure: "2C+ESOClZdNCDzfUfE8Rcdn9+BM3/5x1lebGS0qRxICMAuylx50C8RAxlqKIFNI1J+IMnj5xhK+1oWsLg6wUdo8B3JocvM6P1NbC/WwfY5UJVwtSmzcqvEC+IjM90Bng8OoM5f6ED3IAUyYPBgo8J14+2l5Oruvr99t0mrpniLXDf66TJrI9u/+05JYa2pm0EWlQkDVaziI+96zAt1pMIX9Z/ToTmUbq2OAlHaN8H4hR5aBRmYuF11FQVyARK64eqRsBV4RXKJ9DjW7BR+pQV008hQUoXrCxK8xIf/Qph9h1fz63961NJxfru62LeuZ4Z8MUcV9L+p0HYZzkhNdn5Z3QFf3SXbrVCYs6LK6+uKHpmYS7vB5XwjDx9SWc2szsd5OVKJg3YEdBcC4KUSL3VEbBHDSQ3GFsVKoBsZvopwHkyCmPotY47HtEiPBLdEcsOvcVyeU5Gfq9EOp9dB+nJbNrujBgav1l13CorkBG2rgD+NRivV1otclXnPsIj85ySrc6xk0VDN3zogLAdLw0f+fNOaK17N8GmhPbCu/iX2Vjem/fdjh4NPC4pSNDei2MoWUfqGBmoTxuQEsZeywigwV2c51W9RZuwo8X810N3ehVcBmmNyLjbKmYT+8Kp+dJbaf/ErFnvdZl06mX2bgyxFakv5iQY3dwX4WYHvl77c4="
- provider: script
script: ./.ci/upload-nightly.sh
skip_cleanup: true
overwrite: true
file: bintray-release.json
on:
condition: "$DEPLOYMENT == 1"
repo: Nheko-Reborn/nheko
tags: false
all_branches: true
deploy:
- skip_cleanup: true
overwrite: true
provider: releases

@ -1,25 +1,126 @@
# Changelog
## [Unreleased]
## [0.7.2] -- 2020-06-12
### Highlights
- Reactions
- React to a message with an emoji! 🎉
- Reactions are shown below a message in a small bubble with a counter.
- By clicking on that, others can add to the reaction count.
- It may help you celebrating a new Nheko Release or react with a 👎 to a failed build to express your frustration.
- This uses a new emoji picker. The picker will be improved in the near future (better scrolling, sections, favorites, recently used or similar) and then probably replace the current picker.
- Support for tagging rooms `[tag]`
- Assign custom tags to rooms from the context menu in the room list.
- This allows filtering rooms via the group list. This puts you in a focus mode showing only the selected tags.
- You can assign multiple tags to group rooms however you like.
- SSO Login
- With this you can now login on servers, that only provide SSO.
- Just enter any mxid on the server. Nheko will figure out that you need to use SSO and redirect your browser to the login page.
- Complete the login in your browser and Nheko should automatically log you in.
- Presence
- Shows online status of the people you are talking to.
- You can define a custom status message to tell others what you are currently up to.
- The status message appears next to the usernames in the timeline.
- Your server needs to have presence enabled for this to work.
### [0.7.0] -- Unreleased
### Features
- Respect exif rotation of images
- An italian translation (contributed by Lorenzo Ancora)
- Optional alerts in your taskbar (contributed by z33ky)
- Optional bigger emoji only messages in the timeline (contributed by lkito)
- Optional hover feedback on messages (contributed by lkito)
- `/roomnick` to change your displayname in a single room.
- Preliminary support for showing inline images.
- Warn about unencrypted messages in encrypted rooms.
### Improvements
- perf: Use less CPU to sort the room list.
- Limit size of replies. This currently looks a bit rough, but should improve in the future with a gradient or at some other transition.
- perf: Only clean out old messages from the database every 500 syncs. (There is usually more than one sync every second)
- Improve the login and register masks a bit with hints and validation.
- Descriptions for settings (contributed by lkito)
- A visual indicator, that nheko is fetching messages and improved scrolling (contributed by Lasath Fernando)
### Bugfixes
- Fix not being able to join rooms
- Fix scale factor setting
- Buildfixes against gcc10 and Qt5.15 (missing includes)
- Settings now apply immediately again after changing them (only exception should be the scale factor)
- Join messages should never have empty texts now
- Timeline should now fail to render less often on platforms with native sibling windows.
- Don't rescale images on every frame on highdpi screens.
### Upgrade Notes
<span style="color: red;">This updates includes some changes to the database. Older versions don't handle that gracefully and will delete your database. It is therefore recommended to not downgrade below this version!</span>
## [0.7.1] -- 2020-04-24
### Features
- Show decrypted message source (helps debugging)
- Allow user to show / hide messages in encrypted rooms in sidebar
### Bugfixes
- Fix display of images sent by the user (thank you, wnereiz and not-chicken for reporting)
- Fix crash when trying to maximize image, that wasn't downloaded yet.
- Fix Binding restoreMode flooding logs on Qt 5.14.2+
- Fix with some qml styles hidden menu items leave empty space
- Fix encrypted messages not showing a user in the sidebar
- Fix hangs when generating colors with some system theme color schemes (#172)
## [0.7.0] -- 2020-04-19
0.7.0 *requires* mtxclient 0.3.0. Make sure you compile against 0.3.0
if you do not use the mtxclient bundled with nheko.
#### Features
- Make nheko session import / export format match riot. Fixes #48 (WIP)
- Implement proper replies (WIP)
### Features
- Make nheko session import / export format match riot. Fixes #48
- Implement proper replies
- Add .well-known support for auto-completing homeserver information
- Add mentions viewer so you can see all the messages you have been mentioned in (WIP)
- Add mentions viewer so you can see all the messages you have been mentioned in
- Currently broken due to QML changes. Will be fixed in the future.
- Add emoji font selection preference
- Encryption and decryption of media in E2EE rooms
- Square avatars
- Support for muting and unmuting rooms
- Basic support for playing audio and video messages in the timeline
- Support for a lot more event types (hiding them will come in the future)
- Support for sending all messages as plain text
- Support for inviting, kicking, banning and unbanning users
- Sort the room list by importance of messages (thanks @Alch-Emi)
- Experimental support for [blurhashes](https://github.com/matrix-org/matrix-doc/pull/2448)
#### Improvements
### Improvements
- Add dedicated reply button to Timeline items. Add button for other options so
that right click isn't always required.
- Fix various things with regards to emoji rendering and the emoji picker (WIP)
- Fix various things with regards to emoji rendering and the emoji picker
- Lots and lots and lots of localization updates.
- Additional tweaks to the system theme
- Render timeline in Qml to drop memory usage
- Reduce memory usage of avatars
- Close notifications after they have been read on Linux
- Escape html properly in most places
- A lot of improvements around the image overlay
- The settings page now resizes properly for small screens
- Miscellaneous styling improvements
- Simplify and speedup build
- Display more emojis in the selected emoji font
- Use 'system' theme as default if QT_QPA_PLATFORMTHEME is set
### Bugfixes
- Fix messages stuck on unread
- Reduce the amount of messages shown as "xxx sent an encrypted message"
- Fix various race conditions and crashes
- Fix some compatibility issues with the construct homeserver
Be aware, that Nheko now requires Qt 5.10 and boost 1.70 or higher.
## [0.6.4] - 2019-05-22

@ -4,21 +4,23 @@ option(APPVEYOR_BUILD "Build on appveyor" OFF)
option(CI_BUILD "Set when building in CI. Enables -Werror where possible" OFF)
option(ASAN "Compile with address sanitizers" OFF)
option(QML_DEBUGGING "Enable qml debugging" OFF)
option(COMPILE_QML "Compile Qml. It will make Nheko faster, but you will need to recompile it, when you update Qt." OFF)
set(
CMAKE_TOOLCHAIN_FILE "${CMAKE_CURRENT_LIST_DIR}/toolchain.cmake"
CACHE
FILEPATH "Default toolchain"
)
set(CMAKE_CXX_STANDARD 17 CACHE STRING "C++ standard")
set(CMAKE_CXX_STANDARD_REQUIRED ON CACHE BOOL "Require C++ standard to be supported")
set(CMAKE_POSITION_INDEPENDENT_CODE ON CACHE BOOL "compile as PIC by default")
option(HUNTER_ENABLED "Enable Hunter package manager" OFF)
include("cmake/HunterGate.cmake")
HunterGate(
URL "https://github.com/cpp-pm/hunter/archive/v0.23.244.tar.gz"
SHA1 "2c0f491fd0b80f7b09e3d21adb97237161ef9835"
LOCAL
)
URL "https://github.com/cpp-pm/hunter/archive/v0.23.260.tar.gz"
SHA1 "13775235910a3fa85644568d1c5be8271de72e1c"
)
option(USE_BUNDLED_BOOST "Use the bundled version of Boost." ${HUNTER_ENABLED})
option(USE_BUNDLED_SPDLOG "Use the bundled version of spdlog."
@ -33,10 +35,6 @@ option(USE_BUNDLED_JSON "Use the bundled version of nlohmann json."
option(USE_BUNDLED_OPENSSL "Use the bundled version of OpenSSL."
${HUNTER_ENABLED})
option(USE_BUNDLED_MTXCLIENT "Use the bundled version of the Matrix Client library." ${HUNTER_ENABLED})
option(USE_BUNDLED_SODIUM "Use the bundled version of libsodium."
${HUNTER_ENABLED})
option(USE_BUNDLED_ZLIB "Use the bundled version of zlib."
${HUNTER_ENABLED})
option(USE_BUNDLED_LMDB "Use the bundled version of lmdb."
${HUNTER_ENABLED})
option(USE_BUNDLED_LMDBXX "Use the bundled version of lmdb++."
@ -80,7 +78,7 @@ include(QtCommon)
project(nheko LANGUAGES CXX C)
set(CPACK_PACKAGE_VERSION_MAJOR "0")
set(CPACK_PACKAGE_VERSION_MINOR "7")
set(CPACK_PACKAGE_VERSION_PATCH "0")
set(CPACK_PACKAGE_VERSION_PATCH "2")
set(PROJECT_VERSION_MAJOR ${CPACK_PACKAGE_VERSION_MAJOR})
set(PROJECT_VERSION_MINOR ${CPACK_PACKAGE_VERSION_MINOR})
set(PROJECT_VERSION_PATCH ${CPACK_PACKAGE_VERSION_PATCH})
@ -144,9 +142,9 @@ if (APPLE)
endif(APPLE)
if (Qt5Widgets_FOUND)
if (Qt5Widgets_VERSION VERSION_LESS 5.9.0)
if (Qt5Widgets_VERSION VERSION_LESS 5.10.0)
message(STATUS "Qt version ${Qt5Widgets_VERSION}")
message(WARNING "Minimum supported Qt5 version is 5.9!")
message(WARNING "Minimum supported Qt5 version is 5.10!")
endif()
endif(Qt5Widgets_FOUND)
@ -227,27 +225,34 @@ configure_file(cmake/nheko.h config/nheko.h)
#
set(SRC_FILES
# Dialogs
src/dialogs/AcceptCall.cpp
src/dialogs/CreateRoom.cpp
src/dialogs/FallbackAuth.cpp
src/dialogs/ImageOverlay.cpp
src/dialogs/PreviewUploadOverlay.cpp
src/dialogs/InviteUsers.cpp
src/dialogs/JoinRoom.cpp
src/dialogs/MemberList.cpp
src/dialogs/LeaveRoom.cpp
src/dialogs/Logout.cpp
src/dialogs/UserProfile.cpp
src/dialogs/ReadReceipts.cpp
src/dialogs/MemberList.cpp
src/dialogs/PlaceCall.cpp
src/dialogs/PreviewUploadOverlay.cpp
src/dialogs/ReCaptcha.cpp
src/dialogs/ReadReceipts.cpp
src/dialogs/RoomSettings.cpp
# Emoji
src/emoji/Category.cpp
src/emoji/EmojiModel.cpp
src/emoji/ItemDelegate.cpp
src/emoji/Panel.cpp
src/emoji/PickButton.cpp
src/emoji/Provider.cpp
src/emoji/Provider_new.cpp
# Timeline
src/timeline/EventStore.cpp
src/timeline/Reaction.cpp
src/timeline/TimelineViewManager.cpp
src/timeline/TimelineModel.cpp
src/timeline/DelegateChooser.cpp
@ -272,40 +277,45 @@ set(SRC_FILES
src/ui/ToggleButton.cpp
src/ui/Theme.cpp
src/ui/ThemeManager.cpp
src/ui/UserProfile.cpp
src/AvatarProvider.cpp
src/BlurhashProvider.cpp
src/Cache.cpp
src/CallManager.cpp
src/ChatPage.cpp
src/CommunitiesListItem.cpp
src/ColorImageProvider.cpp
src/CommunitiesList.cpp
src/CommunitiesListItem.cpp
src/DeviceVerificationFlow.cpp
src/EventAccessors.cpp
src/InviteeItem.cpp
src/LoginPage.cpp
src/Logging.cpp
src/LoginPage.cpp
src/MainWindow.cpp
src/MatrixClient.cpp
src/MxcImageProvider.cpp
src/ColorImageProvider.cpp
src/QuickSwitcher.cpp
src/Olm.cpp
src/QuickSwitcher.cpp
src/RegisterPage.cpp
src/RoomInfoListItem.cpp
src/RoomList.cpp
src/RunGuard.cpp
src/SSOHandler.cpp
src/SideBarActions.cpp
src/Splitter.cpp
src/popups/SuggestionsPopup.cpp
src/popups/PopupItem.cpp
src/popups/ReplyPopup.cpp
src/popups/UserMentions.cpp
src/TextInputWidget.cpp
src/TopRoomBar.cpp
src/TrayIcon.cpp
src/Utils.cpp
src/UserInfoWidget.cpp
src/UserSettingsPage.cpp
src/Utils.cpp
src/WebRTCSession.cpp
src/WelcomePage.cpp
src/popups/PopupItem.cpp
src/popups/SuggestionsPopup.cpp
src/popups/UserMentions.cpp
src/main.cpp
third_party/blurhash/blurhash.cpp
)
@ -318,14 +328,11 @@ find_package(Boost 1.70 REQUIRED
COMPONENTS iostreams
system
thread)
if(USE_BUNDLED_ZLIB)
hunter_add_package(ZLIB)
endif()
find_package(ZLIB REQUIRED)
if(USE_BUNDLED_OPENSSL)
hunter_add_package(OpenSSL)
endif()
find_package(OpenSSL REQUIRED)
find_package(OpenSSL 1.1.0 REQUIRED)
if(USE_BUNDLED_MTXCLIENT)
include(FetchContent)
set(BUILD_LIB_EXAMPLES OFF CACHE INTERNAL "")
@ -333,11 +340,11 @@ if(USE_BUNDLED_MTXCLIENT)
FetchContent_Declare(
MatrixClient
GIT_REPOSITORY https://github.com/Nheko-Reborn/mtxclient.git
GIT_TAG 5fbee216e640da45c05f25b1f84f03c88bca5910
GIT_TAG ad5575bc24089dc385e97d9ace026414b618775c
)
FetchContent_MakeAvailable(MatrixClient)
else()
find_package(MatrixClient 0.3.0 REQUIRED)
find_package(MatrixClient 0.3.1 REQUIRED)
endif()
if(USE_BUNDLED_OLM)
include(FetchContent)
@ -376,7 +383,7 @@ if(USE_BUNDLED_CMARK)
add_library(cmark::cmark ALIAS libcmark_static)
endif()
else()
find_package(cmark REQUIRED)
find_package(cmark REQUIRED 0.29.0)
endif()
if(USE_BUNDLED_JSON)
@ -419,31 +426,44 @@ else()
find_package(Tweeny REQUIRED)
endif()
include(FindPkgConfig)
pkg_check_modules(GSTREAMER IMPORTED_TARGET gstreamer-sdp-1.0>=1.14 gstreamer-webrtc-1.0>=1.14)
# single instance functionality
set(QAPPLICATION_CLASS QApplication CACHE STRING "Inheritance class for SingleApplication")
add_subdirectory(third_party/SingleApplication-3.1.3.1/)
feature_summary(WHAT ALL INCLUDE_QUIET_PACKAGES FATAL_ON_MISSING_REQUIRED_PACKAGES)
qt5_wrap_cpp(MOC_HEADERS
# Dialogs
src/dialogs/AcceptCall.h
src/dialogs/CreateRoom.h
src/dialogs/FallbackAuth.h
src/dialogs/ImageOverlay.h
src/dialogs/PreviewUploadOverlay.h
src/dialogs/InviteUsers.h
src/dialogs/JoinRoom.h
src/dialogs/MemberList.h
src/dialogs/LeaveRoom.h
src/dialogs/Logout.h
src/dialogs/UserProfile.h
src/dialogs/MemberList.h
src/dialogs/PlaceCall.h
src/dialogs/PreviewUploadOverlay.h
src/dialogs/RawMessage.h
src/dialogs/ReadReceipts.h
src/dialogs/ReCaptcha.h
src/dialogs/ReadReceipts.h
src/dialogs/RoomSettings.h
# Emoji
src/emoji/Category.h
src/emoji/EmojiModel.h
src/emoji/ItemDelegate.h
src/emoji/Panel.h
src/emoji/PickButton.h
src/emoji/Provider.h
# Timeline
src/timeline/EventStore.h
src/timeline/Reaction.h
src/timeline/TimelineViewManager.h
src/timeline/TimelineModel.h
src/timeline/DelegateChooser.h
@ -467,34 +487,38 @@ qt5_wrap_cpp(MOC_HEADERS
src/ui/ToggleButton.h
src/ui/Theme.h
src/ui/ThemeManager.h
src/ui/UserProfile.h
src/notifications/Manager.h
src/AvatarProvider.h
src/BlurhashProvider.h
src/Cache_p.h
src/CallManager.h
src/ChatPage.h
src/CommunitiesListItem.h
src/CommunitiesList.h
src/CommunitiesListItem.h
src/DeviceVerificationFlow.h
src/InviteeItem.h
src/LoginPage.h
src/MainWindow.h
src/MxcImageProvider.h
src/InviteeItem.h
src/QuickSwitcher.h
src/RegisterPage.h
src/RoomInfoListItem.h
src/RoomList.h
src/SSOHandler.h
src/SideBarActions.h
src/Splitter.h
src/popups/SuggestionsPopup.h
src/popups/ReplyPopup.h
src/popups/PopupItem.h
src/popups/UserMentions.h
src/TextInputWidget.h
src/TopRoomBar.h
src/TrayIcon.h
src/UserInfoWidget.h
src/UserSettingsPage.h
src/WebRTCSession.h
src/WelcomePage.h
src/popups/PopupItem.h
src/popups/SuggestionsPopup.h
src/popups/UserMentions.h
)
#
@ -506,6 +530,9 @@ set(TRANSLATION_DEPS ${LANG_QRC} ${QRC} ${QM_SRC})
if (APPLE)
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -framework Foundation -framework Cocoa")
set(SRC_FILES ${SRC_FILES} src/notifications/ManagerMac.mm src/emoji/MacHelper.mm)
if(${CMAKE_VERSION} VERSION_GREATER_EQUAL "3.16.0")
set_source_files_properties( src/notifications/ManagerMac.mm src/emoji/MacHelper.mm PROPERTIES SKIP_PRECOMPILE_HEADERS ON)
endif()
elseif (WIN32)
file(DOWNLOAD
"https://raw.githubusercontent.com/mohabouje/WinToast/41ed1c58d5dce0ee9c01dbdeac05be45358d4f57/src/wintoastlib.cpp"
@ -533,7 +560,12 @@ if(ASAN)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=address,undefined")
endif()
add_executable (nheko ${OS_BUNDLE} ${NHEKO_DEPS})
if(WIN32)
add_executable (nheko WIN32 ${OS_BUNDLE} ${NHEKO_DEPS})
else()
add_executable (nheko ${OS_BUNDLE} ${NHEKO_DEPS})
endif()
if(APPLE)
target_link_libraries (nheko PRIVATE Qt5::MacExtras)
elseif(WIN32)
@ -542,7 +574,7 @@ elseif(WIN32)
else()
target_link_libraries (nheko PRIVATE Qt5::DBus)
endif()
target_include_directories(nheko PRIVATE src includes)
target_include_directories(nheko PRIVATE src includes third_party/blurhash third_party/cpp-httplib-0.5.12)
target_link_libraries(nheko PRIVATE
MatrixClient::MatrixClient
@ -561,7 +593,20 @@ target_link_libraries(nheko PRIVATE
nlohmann_json::nlohmann_json
lmdbxx::lmdbxx
liblmdb::lmdb
tweeny)
tweeny
SingleApplication::SingleApplication)
if(${CMAKE_VERSION} VERSION_GREATER_EQUAL "3.16.0")
target_precompile_headers(nheko
PRIVATE
<string>
)
endif()
if (TARGET PkgConfig::GSTREAMER)
target_link_libraries(nheko PRIVATE PkgConfig::GSTREAMER)
target_compile_definitions(nheko PRIVATE GSTREAMER_AVAILABLE)
endif()
if(MSVC)
target_link_libraries(nheko PRIVATE ntdll)

@ -2,8 +2,8 @@ nheko
----
[![Build Status](https://travis-ci.org/Nheko-Reborn/nheko.svg?branch=master)](https://travis-ci.org/Nheko-Reborn/nheko)
[![Build status](https://ci.appveyor.com/api/projects/status/07qrqbfylsg4hw2h/branch/master?svg=true)](https://ci.appveyor.com/project/redsky17/nheko/branch/master)
[![Stable Version](https://img.shields.io/badge/download-stable-green.svg)](https://github.com/Nheko-Reborn/nheko/releases/v0.6.4)
[![Nightly](https://img.shields.io/badge/download-nightly-green.svg)](https://nheko-reborn-artifacts.s3.us-east-2.amazonaws.com/list.html)
[![Stable Version](https://img.shields.io/badge/download-stable-green.svg)](https://github.com/Nheko-Reborn/nheko/releases/v0.7.2)
[![Nightly](https://img.shields.io/badge/download-nightly-green.svg)](https://matrix-static.neko.dev/room/!TshDrgpBNBDmfDeEGN:neko.dev/)
[![#nheko-reborn:matrix.org](https://img.shields.io/matrix/nheko-reborn:matrix.org.svg?label=%23nheko-reborn:matrix.org)](https://matrix.to/#/#nheko-reborn:matrix.org)
[![AUR: nheko](https://img.shields.io/badge/AUR-nheko-blue.svg)](https://aur.archlinux.org/packages/nheko)
<a href='https://flathub.org/apps/details/io.github.NhekoReborn.Nheko'><img width='240' alt='Download on Flathub' src='https://flathub.org/assets/badges/flathub-badge-en.png'/></a>
@ -51,11 +51,19 @@ can be found in the [Github releases](https://github.com/Nheko-Reborn/nheko/rele
### Repositories
[![Packaging status](https://repology.org/badge/tiny-repos/nheko.svg)](https://repology.org/project/nheko/versions)
#### Arch Linux
```bash
pacaur -S nheko # nheko-git
```
#### Debian (10 and above) / Ubuntu (18.04 and above)
```bash
sudo apt install nheko
```
#### Fedora
```bash
sudo dnf install nheko
@ -67,6 +75,14 @@ sudo eselect repository enable matrix
sudo emerge -a nheko
```
#### Nix(os)
```bash
nix-env -iA nixpkgs.nheko
# or
nix-shell -p nheko --run nheko
```
#### Alpine Linux (and postmarketOS)
Make sure you have the testing repositories from `edge` enabled. Note that this is not needed on postmarketOS.
@ -81,12 +97,18 @@ sudo apk add nheko
flatpak install flathub io.github.NhekoReborn.Nheko
```
#### Guix
```
guix install nheko
```
#### macOS (10.14 and above)
with [macports](https://www.macports.org/) :
with [homebrew](https://brew.sh/):
```sh
sudo port install nheko
brew cask install nheko
```
### Build Requirements
@ -99,8 +121,7 @@ sudo port install nheko
- [LMDB](https://symas.com/lightning-memory-mapped-database/)
- [cmark](https://github.com/commonmark/cmark) 0.29 or greater.
- Boost 1.70 or greater.
- [libolm](https://git.matrix.org/git/olm)
- [libsodium](https://github.com/jedisct1/libsodium)
- [libolm](https://gitlab.matrix.org/matrix-org/olm)
- [spdlog](https://github.com/gabime/spdlog)
- A compiler that supports C++ 17:
- Clang 6 (tested on Travis CI)
@ -111,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:
@ -122,15 +144,13 @@ The bundle flags are currently:
- USE_BUNDLED_JSON
- USE_BUNDLED_OPENSSL
- USE_BUNDLED_MTXCLIENT
- USE_BUNDLED_SODIUM
- USE_BUNDLED_ZLIB
- USE_BUNDLED_LMDB
- USE_BUNDLED_LMDBXX
- USE_BUNDLED_TWEENY
#### Linux
If you don't want to install any external dependencies, you can generate an AppImage locally using docker.
If you don't want to install any external dependencies, you can generate an AppImage locally using docker. It is not that well maintained though...
```bash
make docker-app-image
@ -148,49 +168,47 @@ sudo pacman -S qt5-base \
fontconfig \
lmdb \
cmark \
boost \
libsodium
boost
```
##### Gentoo Linux
```bash
sudo emerge -a ">=dev-qt/qtgui-5.9.0" media-libs/fontconfig
```
##### Ubuntu 16.04
```bash
sudo add-apt-repository ppa:beineri/opt-qt592-xenial
sudo add-apt-repository ppa:george-edison55/cmake-3.x
sudo add-apt-repository ppa:ubuntu-toolchain-r-test
sudo apt-get update
sudo apt-get install -y g++-7 qt59base qt59svg qt59tools qt59multimedia cmake liblmdb-dev libsodium-dev
sudo emerge -a ">=dev-qt/qtgui-5.10.0" media-libs/fontconfig
```
##### Ubuntu 19.10
##### Ubuntu 20.04
```bash
# Build requirements + qml modules needed at runtime (you may not need all of them, but the following seem to work according to reports):
sudo apt install g++-7 cmake liblmdb-dev libsodium-dev qt{base,tools,multimedia}5-dev qml-module-qt{gstreamer,multimedia,quick-extras} libqt5svg5-dev qt{script,quickcontrols2-}5-dev
sudo apt install g++ cmake zlib1g-dev libssl-dev qt{base,declarative,tools,multimedia,quickcontrols2-}5-dev libqt5svg5-dev libboost-system-dev libboost-thread-dev libboost-iostreams-dev libolm-dev liblmdb++-dev libcmark-dev nlohmann-json3-dev libspdlog-dev libgtest-dev qml-module-qt{gstreamer,multimedia,quick-extras,-labs-settings,graphicaleffects,quick-controls2}
```
This will install all dependencies, except for tweeny (use bundled tweeny)
and mtxclient (needs to be build separately).
##### Debian Buster (or higher probably)
(User report, not sure if all of those are needed)
```bash
sudo apt install cmake gcc make automake liblmdb-dev libsodium-dev \
sudo apt install cmake gcc make automake liblmdb-dev \
qt5-default libssl-dev libqt5multimedia5-plugins libqt5multimediagsttools5 libqt5multimediaquick5 libqt5svg5-dev \
qml-module-qtgstreamer qtmultimedia5-dev qtquickcontrols2-5-dev qttools5-dev qttools5-dev-tools \
qml-module-qtgraphicaleffects qml-module-qtmultimedia qml-module-qtquick-controls2 qml-module-qtquick-layouts
```
##### Guix
```bash
guix environment nheko
```
##### macOS (Xcode 10.2 or later)
```bash
brew update
brew install qt5 lmdb cmake llvm libsodium spdlog boost cmark
brew install qt5 lmdb cmake llvm spdlog boost cmark libolm
```
##### Windows
@ -209,14 +227,14 @@ Make sure to install the `MSVC 2017 64-bit` toolset for at least Qt 5.10
We can now build nheko:
```bash
cmake -H. -Bbuild -DCMAKE_BUILD_TYPE=Release
cmake -S. -Bbuild -DCMAKE_BUILD_TYPE=Release
cmake --build build
```
To use bundled dependencies you can use hunter, i.e.:
```bash
cmake -H. -Bbuild -DHUNTER_ENABLED=ON -DBUILD_SHARED_LIBS=OFF -DUSE_BUNDLED_OPENSSL=OFF
cmake -S. -Bbuild -DHUNTER_ENABLED=ON -DBUILD_SHARED_LIBS=OFF -DUSE_BUNDLED_OPENSSL=OFF
cmake --build build --config Release
```
@ -235,7 +253,7 @@ You might need to pass `-DCMAKE_PREFIX_PATH` to cmake to point it at your qt5 in
e.g on macOS
```
cmake -H. -Bbuild -DCMAKE_BUILD_TYPE=Release -DCMAKE_PREFIX_PATH=$(brew --prefix qt5)
cmake -S. -Bbuild -DCMAKE_BUILD_TYPE=Release -DCMAKE_PREFIX_PATH=$(brew --prefix qt5)
cmake --build build
```
@ -255,7 +273,7 @@ Examples for the paths are:
You should also enable hunter by setting `HUNTER_ENABLED` to `ON` and `BUILD_SHARED_LIBS` to `OFF`.
Now right click into the root nheko source directory and choose `Open in Visual Studio`.
You can choose the build type Release and Debug in the top toolbar.
You can choose the build type Release and Debug in the top toolbar.
After a successful CMake generation you can select the `nheko.exe` as the run target.
Now choose `Build all` in the CMake menu or press `F7` to compile the executable.
@ -287,5 +305,9 @@ Here are some screen shots to get a feel for the UI, but things will probably ch
![nheko chat](https://nheko-reborn.github.io/images/screenshots/chat.png)
![nheko settings](https://nheko-reborn.github.io/images/screenshots/settings.png)
### Third party
[Single Application for Qt](https://github.com/itay-grudev/SingleApplication)
[Matrix]:https://matrix.org
[Riot]:https://riot.im

@ -1,6 +1,6 @@
---
version: 0.7.0-{build}
version: 0.7.2-{build}
configuration: Release
image: Visual Studio 2017
@ -9,10 +9,14 @@ platform: x64
environment:
BINTRAY_APIKEY:
secure: "iGl5mzE9/ta9kFELUxDw9XtlYMSCMai9xowXIkYzU8WKHz7NfW0mLwMJZvblZFXJ"
MATRIX_ACCESS_TOKEN:
secure: Qoy+QQ8zWXYCQrck9GtXJsoPTv9r/rhgCDUlKJ6ue+gkteYG40E9MxgwP1svn6bse20H4z6Svrxn8kFbcJB7Wg2Cnv1s326/vsJJzhWir2eHFFGK+f4SB992/U0HoQmk3Cq5hPk7dLcA7KqHIa1g1PTSFPfl1VODJ2UqqAyn8nzbC5ym+wwU1buJqoWPlTyHBW7eE8wNe77+qI18XpF7NN8yuOOyg3Tzup9YyXLrI36XiJu/5JD3j3s3V1QiUTpuLyQzqwuBUOf1MHTbzuPwHm3ZwzSM98WD6aL6riaK9qa7mDbSx1aY0ukIYSY9IdAfHNwZY/DEAn+QAVD+ZTvPq04ASv+kmSFpOBKr07kpqfM=
cache:
- c:\hunter\ -> appveyor.yml
- build\_deps -> appveyor.yml,deps\CMakeLists.txt
- c:\hunter\
- build\_deps -> appveyor.yml
build:
verbosity: minimal
@ -26,8 +30,8 @@ build_script:
# VERSION format: branch-master/branch-1.2
# INSTVERSION format: x.y.z
# WINVERSION format: 9999.0.0.123/1.2.0.234
- if "%APPVEYOR_REPO_TAG%"=="false" set INSTVERSION=0.6.4
- if "%APPVEYOR_REPO_TAG%"=="false" set VERSION=0.6.4
- if "%APPVEYOR_REPO_TAG%"=="false" set INSTVERSION=0.7.2
- if "%APPVEYOR_REPO_TAG%"=="false" set VERSION=0.7.2
- if "%APPVEYOR_REPO_TAG%"=="false" if "%APPVEYOR_REPO_BRANCH%"=="master" set INSTVERSION=9999.0
- if "%APPVEYOR_REPO_TAG%"=="false" set WINVERSION=%INSTVERSION%.0.%APPVEYOR_BUILD_NUMBER%
# VERSION format: v1.2.3/v1.3.4
@ -70,31 +74,31 @@ after_build:
- mkdir installer
- mkdir installer\config
- mkdir installer\packages
- mkdir installer\packages\com.mujx.nheko
- mkdir installer\packages\com.mujx.nheko\data
- mkdir installer\packages\com.mujx.nheko\meta
- mkdir installer\packages\com.mujx.nheko.cleanup\meta
- mkdir installer\packages\io.github.nhekoreborn.nheko
- mkdir installer\packages\io.github.nhekoreborn.nheko\data
- mkdir installer\packages\io.github.nhekoreborn.nheko\meta
- mkdir installer\packages\io.github.nhekoreborn.nheko.cleanup\meta
# Copy installer data
- copy %BUILD%\resources\nheko.ico installer\config
- copy %BUILD%\resources\nheko.png installer\config
- copy %BUILD%\COPYING installer\packages\com.mujx.nheko\meta\license.txt
- copy %BUILD%\COPYING installer\packages\com.mujx.nheko.cleanup\meta\license.txt
- copy %BUILD%\COPYING installer\packages\io.github.nhekoreborn.nheko\meta\license.txt
- copy %BUILD%\COPYING installer\packages\io.github.nhekoreborn.nheko.cleanup\meta\license.txt
- copy %BUILD%\deploy\installer\config.xml installer\config
- copy %BUILD%\deploy\installer\controlscript.qs installer\config
- copy %BUILD%\deploy\installer\uninstall.qs installer\packages\com.mujx.nheko\data
- copy %BUILD%\deploy\installer\gui\package.xml installer\packages\com.mujx.nheko\meta
- copy %BUILD%\deploy\installer\gui\installscript.qs installer\packages\com.mujx.nheko\meta
- copy %BUILD%\deploy\installer\cleanup\package.xml installer\packages\com.mujx.nheko.cleanup\meta
- copy %BUILD%\deploy\installer\cleanup\installscript.qs installer\packages\com.mujx.nheko.cleanup\meta
- copy %BUILD%\deploy\installer\uninstall.qs installer\packages\io.github.nhekoreborn.nheko\data
- copy %BUILD%\deploy\installer\gui\package.xml installer\packages\io.github.nhekoreborn.nheko\meta
- copy %BUILD%\deploy\installer\gui\installscript.qs installer\packages\io.github.nhekoreborn.nheko\meta
- copy %BUILD%\deploy\installer\cleanup\package.xml installer\packages\io.github.nhekoreborn.nheko.cleanup\meta
- copy %BUILD%\deploy\installer\cleanup\installscript.qs installer\packages\io.github.nhekoreborn.nheko.cleanup\meta
# Amend version and date
- sed -i "s/__VERSION__/0.6.4/" installer\config\config.xml
- sed -i "s/__VERSION__/0.6.4/" installer\packages\com.mujx.nheko\meta\package.xml
- sed -i "s/__VERSION__/0.6.4/" installer\packages\com.mujx.nheko.cleanup\meta\package.xml
- sed -i "s/__DATE__/%DATE%/" installer\packages\com.mujx.nheko\meta\package.xml
- sed -i "s/__DATE__/%DATE%/" installer\packages\com.mujx.nheko.cleanup\meta\package.xml
- sed -i "s/__VERSION__/0.7.2/" installer\config\config.xml
- sed -i "s/__VERSION__/0.7.2/" installer\packages\io.github.nhekoreborn.nheko\meta\package.xml
- sed -i "s/__VERSION__/0.7.2/" installer\packages\io.github.nhekoreborn.nheko.cleanup\meta\package.xml
- sed -i "s/__DATE__/%DATE%/" installer\packages\io.github.nhekoreborn.nheko\meta\package.xml
- sed -i "s/__DATE__/%DATE%/" installer\packages\io.github.nhekoreborn.nheko.cleanup\meta\package.xml
# Copy nheko data
- xcopy NhekoData\*.* installer\packages\com.mujx.nheko\data\*.* /s /e /c /y
- move NhekoRelease\nheko.exe installer\packages\com.mujx.nheko\data
- xcopy NhekoData\*.* installer\packages\io.github.nhekoreborn.nheko\data\*.* /s /e /c /y
- move NhekoRelease\nheko.exe installer\packages\io.github.nhekoreborn.nheko\data
- mkdir tools
- curl -L -O https://download.qt.io/official_releases/qt-installer-framework/3.0.4/QtInstallerFramework-win-x86.exe
- 7z x QtInstallerFramework-win-x86.exe -otools -aoa
@ -106,6 +110,8 @@ after_build:
on_success:
- if "%APPVEYOR_REPO_TAG%" == "true" (curl -T nheko-%APPVEYOR_REPO_TAG_NAME%-installer.exe -uredsky17:%BINTRAY_APIKEY% https://api.bintray.com/content/nheko-reborn/nheko/%APPVEYOR_REPO_TAG_NAME%/nheko/%APPVEYOR_REPO_TAG_NAME%/)
before_deploy:
- ps: .\.ci\upload-nightly.ps1
deploy:
- description: "Development builds"
provider: GitHub

@ -1,5 +1,5 @@
hunter_config(
Boost
VERSION "1.70.0-p0"
VERSION "1.70.0-p1"
CMAKE_ARGS IOSTREAMS_NO_BZIP2=1
)

@ -133,10 +133,14 @@ function(hunter_gate_self root version sha1 result)
string(SUBSTRING "${sha1}" 0 7 archive_id)
set(
hunter_self
"${root}/_Base/Download/Hunter/${version}/${archive_id}/Unpacked"
)
if(EXISTS "${root}/cmake/Hunter")
set(hunter_self "${root}")
else()
set(
hunter_self
"${root}/_Base/Download/Hunter/${version}/${archive_id}/Unpacked"
)
endif()
set("${result}" "${hunter_self}" PARENT_SCOPE)
endfunction()
@ -490,37 +494,44 @@ macro(HunterGate)
)
set(_master_location "${_hunter_self}/cmake/Hunter")
get_filename_component(_archive_id_location "${_hunter_self}/.." ABSOLUTE)
set(_done_location "${_archive_id_location}/DONE")
set(_sha1_location "${_archive_id_location}/SHA1")
# Check Hunter already downloaded by HunterGate
if(NOT EXISTS "${_done_location}")
hunter_gate_download("${_archive_id_location}")
endif()
if(EXISTS "${HUNTER_GATE_ROOT}/cmake/Hunter")
# Hunter downloaded manually (e.g. by 'git clone')
set(_unused "xxxxxxxxxx")
set(HUNTER_GATE_SHA1 "${_unused}")
set(HUNTER_GATE_VERSION "${_unused}")
else()
get_filename_component(_archive_id_location "${_hunter_self}/.." ABSOLUTE)
set(_done_location "${_archive_id_location}/DONE")
set(_sha1_location "${_archive_id_location}/SHA1")
# Check Hunter already downloaded by HunterGate
if(NOT EXISTS "${_done_location}")
hunter_gate_download("${_archive_id_location}")
endif()
if(NOT EXISTS "${_done_location}")
hunter_gate_internal_error("hunter_gate_download failed")
endif()
if(NOT EXISTS "${_done_location}")
hunter_gate_internal_error("hunter_gate_download failed")
endif()
if(NOT EXISTS "${_sha1_location}")
hunter_gate_internal_error("${_sha1_location} not found")
endif()
file(READ "${_sha1_location}" _sha1_value)
string(COMPARE EQUAL "${_sha1_value}" "${HUNTER_GATE_SHA1}" _is_equal)
if(NOT _is_equal)
hunter_gate_internal_error(
"Short SHA1 collision:"
" ${_sha1_value} (from ${_sha1_location})"
" ${HUNTER_GATE_SHA1} (HunterGate)"
)
endif()
if(NOT EXISTS "${_master_location}")
hunter_gate_user_error(
"Master file not found:"
" ${_master_location}"
"try to update Hunter/HunterGate"
)
if(NOT EXISTS "${_sha1_location}")
hunter_gate_internal_error("${_sha1_location} not found")
endif()
file(READ "${_sha1_location}" _sha1_value)
string(COMPARE EQUAL "${_sha1_value}" "${HUNTER_GATE_SHA1}" _is_equal)
if(NOT _is_equal)
hunter_gate_internal_error(
"Short SHA1 collision:"
" ${_sha1_value} (from ${_sha1_location})"
" ${HUNTER_GATE_SHA1} (HunterGate)"
)
endif()
if(NOT EXISTS "${_master_location}")
hunter_gate_user_error(
"Master file not found:"
" ${_master_location}"
"try to update Hunter/HunterGate"
)
endif()
endif()
include("${_master_location}")
set_property(GLOBAL PROPERTY HUNTER_GATE_DONE YES)

@ -21,7 +21,7 @@ if(NOT EXISTS ${_qrc})
endif()
qt5_add_resources(LANG_QRC ${_qrc})
if(Qt5QuickCompiler_FOUND)
if(Qt5QuickCompiler_FOUND AND COMPILE_QML)
qtquick_compiler_add_resources(QRC resources/res.qrc)
else()
qt5_add_resources(QRC resources/res.qrc)

@ -1,7 +1,7 @@
{
"id": "io.github.NhekoReborn.Nheko",
"command": "nheko",
"branch": "0.7.0-dev",
"branch": "master",
"runtime": "org.kde.Platform",
"runtime-version": "5.14",
"sdk": "org.kde.Sdk",
@ -10,7 +10,6 @@
"rename-appdata-file": "nheko.appdata.xml",
"finish-args": [
"--device=dri",
"--filesystem=home",
"--share=ipc",
"--share=network",
"--socket=pulseaudio",
@ -74,9 +73,9 @@
],
"sources": [
{
"sha256": "3dbcbfd8c07e25f5e0d662b194d3a7772ef214358c49ada23c044c4747ce8b19",
"sha256": "5197b3147cfcfaa67dd564db7b878e4a4b3d9f3443801722b3915cdeced656cb",
"type": "archive",
"url": "https://github.com/gabime/spdlog/archive/v1.1.0.tar.gz"
"url": "https://github.com/gabime/spdlog/archive/v1.8.1.tar.gz"
}
]
},
@ -132,7 +131,7 @@
{
"sha256": "59c9b274bc451cf91a9ba1dd2c7fdcaf5d60b1b3aa83f2c9fa143417cc660722",
"type": "archive",
"url": "https://dl.bintray.com/boostorg/release/1.72.0/source/boost_1_72_0.tar.bz2"
"url": "https://sourceforge.net/projects/boost/files/boost/1.72.0/boost_1_72_0.tar.bz2"
}
]
},
@ -147,9 +146,9 @@
"name": "mtxclient",
"sources": [
{
"sha256": "8cf5470570d2ed6affc0bbe0f4b6be9b0a2e2372e9f920b504126841bb73036f",
"type": "archive",
"url": "https://github.com/Nheko-Reborn/mtxclient/archive/5fbee216e640da45c05f25b1f84f03c88bca5910.tar.gz"
"commit": "ad5575bc24089dc385e97d9ace026414b618775c",
"type": "git",
"url": "https://github.com/Nheko-Reborn/mtxclient.git"
}
]
},
@ -172,7 +171,8 @@
{
"config-opts": [
"-DCMAKE_BUILD_TYPE=Release",
"-DLMDBXX_INCLUDE_DIR=.deps/lmdbxx"
"-DLMDBXX_INCLUDE_DIR=.deps/lmdbxx",
"-DCOMPILE_QML=ON"
],
"buildsystem": "cmake-ninja",
"name": "nheko",

Binary file not shown.

After

Width:  |  Height:  |  Size: 643 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 759 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 573 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 385 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 741 B

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

@ -0,0 +1,5 @@
The below media files were obtained from https://github.com/matrix-org/matrix-react-sdk/tree/develop/res/media
callend.ogg
ringback.ogg
ring.ogg

Binary file not shown.

Binary file not shown.

Binary file not shown.

@ -1,56 +1,67 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright 2019 mujx, nheko reborn developers -->
<?xml version="1.0" encoding="UTF-8"?>
<!-- Copyright 2020 mujx, nheko reborn developers -->
<component type="desktop">
<id>nheko.desktop</id>
<metadata_license>CC0-1.0</metadata_license>
<project_license>GPL-3.0-or-later and CC-BY</project_license>
<name>nheko</name>
<summary>Desktop client for the Matrix protocol</summary>
<description>
<p>The motivation behind the project is to provide a native
<id>nheko.desktop</id>
<metadata_license>CC0-1.0</metadata_license>
<project_license>GPL-3.0-or-later and CC-BY</project_license>
<name>nheko</name>
<summary>Desktop client for the Matrix protocol</summary>
<description>
<p>The motivation behind the project is to provide a native
desktop app for Matrix that feels more like a mainstream
chat app.</p>
</description>
<translation/>
<languages>
<lang>de</lang>
<lang>el</lang>
<lang>en</lang>
<lang>fr</lang>
<lang>nl</lang>
<lang>pl</lang>
<lang>ru</lang>
<lang>zh_CN</lang>
</languages>
<content_rating type="oars-1.0">
<content_attribute id="social-chat">intense</content_attribute>
<content_attribute id="social-audio">intense</content_attribute>
</content_rating>
<screenshots>
<screenshot type="default">
<image>https://nheko-reborn.github.io/images/screenshots/chat-resized.png</image>
</screenshot>
<screenshot>
<image>https://nheko-reborn.github.io/images/screenshots/Start-resized.png</image>
</screenshot>
<screenshot>
<image>https://nheko-reborn.github.io/images/screenshots/settings-resized.png</image>
</screenshot>
<screenshot>
<image>https://nheko-reborn.github.io/images/screenshots/login-resized.png</image>
</screenshot>
</screenshots>
<url type="homepage">https://github.com/Nheko-Reborn/nheko</url>
<update_contact>https://github.com/Nheko-Reborn</update_contact>
<releases>
<release version="0.6.4" date="2019-05-22" />
<release version="0.6.3" date="2019-02-08" />
<release version="0.6.2" date="2018-10-07" />
<release version="0.6.1" date="2018-09-26" />
<release version="0.6.0" date="2018-09-21" />
<release version="0.5.5" date="2018-09-01" />
<release version="0.5.4" date="2018-08-21" />
<release version="0.5.3" date="2018-08-12" />
<release version="0.5.2" date="2018-07-28" />
</releases>
</description>
<translation/>
<languages>
<lang>de</lang>
<lang>el</lang>
<lang>en</lang>
<lang>fr</lang>
<lang>nl</lang>
<lang>pl</lang>
<lang>ru</lang>
<lang>zh_CN</lang>
</languages>
<content_rating type="oars-1.0">
<content_attribute id="social-chat">intense</content_attribute>
<content_attribute id="social-audio">intense</content_attribute>
</content_rating>
<screenshots>
<screenshot type="default">
<image>https://nheko-reborn.github.io/images/screenshots/chat-resized.png</image>
</screenshot>
<screenshot>
<image>https://nheko-reborn.github.io/images/screenshots/Start-resized.png</image>
</screenshot>
<screenshot>
<image>https://nheko-reborn.github.io/images/screenshots/settings-resized.png</image>
</screenshot>
<screenshot>
<image>https://nheko-reborn.github.io/images/screenshots/login-resized.png</image>
</screenshot>
</screenshots>
<url type="homepage">https://github.com/Nheko-Reborn/nheko</url>
<update_contact>https://github.com/Nheko-Reborn</update_contact>
<releases>
<release date="2020-06-12" version="0.7.2"/>
<release date="2020-04-24" version="0.7.1"/>
<release date="2020-04-19" version="0.7.0"/>
<release date="2019-05-22" version="0.6.4"/>
<release date="2019-02-08" version="0.6.3"/>
<release date="2018-10-07" version="0.6.2"/>
<release date="2018-09-26" version="0.6.1"/>
<release date="2018-09-21" version="0.6.0"/>
<release date="2018-09-01" version="0.5.5"/>
<release date="2018-08-21" version="0.5.4"/>
<release date="2018-08-12" version="0.5.3"/>
<release date="2018-07-28" version="0.5.2"/>
</releases>
<developer_name>Nheko Reborn</developer_name>
<url type="bugtracker">https://github.com/Nheko-Reborn/nheko/issues</url>
<url type="help">https://github.com/Nheko-Reborn/nheko/</url>
<url type="translate">https://weblate.nheko.im/projects/nheko/</url>
</component>

@ -101,7 +101,7 @@
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
<dc:title />
</cc:Work>
</rdf:RDF>
</metadata>
@ -122,7 +122,7 @@
inkscape:export-ydpi="96.000008"
r="135.46666" />
<path
style="fill:#fffffe;fill-opacity:1;stroke:none;stroke-width:0.32663074px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
style="fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.32663074px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="M 48.965212,110.73276 H 239.52342 c 4.88824,0 4.88824,0 0,8.46688 L 180.59519,221.2662 c -4.6188,8.00001 -4.6188,8.00001 -9.50702,8.00001 h -19.55294 c -4.88824,0 -4.88824,0 -0.26944,-8.00001 l 44.2635,-76.66608 h -29.41224 l -43.91123,76.19952 c -4.88823,8.46657 -4.88823,8.46657 -9.77646,8.46657 H 29.329398 l 49.299816,-84.66609 h -49.29982 z"
id="path4834"
inkscape:connector-curvature="0"

Before

Width:  |  Height:  |  Size: 5.2 KiB

After

Width:  |  Height:  |  Size: 5.2 KiB

@ -0,0 +1,113 @@
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 {
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 = "";
}
}
target: TimelineManager
}
Timer {
id: callTimer
property int startTime
function pad(n) {
return (n < 10) ? ("0" + n) : n;
}
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);
}
}
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
}
}
}

@ -1,52 +1,75 @@
import QtQuick 2.6
import QtGraphicalEffects 1.0
import Qt.labs.settings 1.0
import QtQuick 2.6
import QtQuick.Controls 2.3
import im.nheko 1.0
Rectangle {
id: avatar
width: 48
height: 48
radius: settings.avatar_circles ? height/2 : 3
Settings {
id: settings
category: "user"
property bool avatar_circles: true
}
property alias url: img.source
property string displayName
Text {
anchors.fill: parent
text: chat.model.escapeEmoji(String.fromCodePoint(displayName.codePointAt(0)))
textFormat: Text.RichText
color: colors.text
font.pixelSize: avatar.height/2
verticalAlignment: Text.AlignVCenter
horizontalAlignment: Text.AlignHCenter
}
Image {
id: img
anchors.fill: parent
asynchronous: true
fillMode: Image.PreserveAspectCrop
mipmap: true
smooth: false
sourceSize.width: avatar.width
sourceSize.height: avatar.height
layer.enabled: true
layer.effect: OpacityMask {
maskSource: Rectangle {
anchors.fill: parent
width: avatar.width
height: avatar.height
radius: settings.avatar_circles ? height/2 : 3
}
}
}
color: colors.base
id: avatar
property alias url: img.source
property string userid
property string displayName
width: 48
height: 48
radius: Settings.avatarCircles ? height / 2 : 3
color: colors.base
Label {
anchors.fill: parent
text: TimelineManager.escapeEmoji(displayName ? String.fromCodePoint(displayName.codePointAt(0)) : "")
textFormat: Text.RichText
font.pixelSize: avatar.height / 2
verticalAlignment: Text.AlignVCenter
horizontalAlignment: Text.AlignHCenter
visible: img.status != Image.Ready
color: colors.text
}
Image {
id: img
anchors.fill: parent
asynchronous: true
fillMode: Image.PreserveAspectCrop
mipmap: true
smooth: false
sourceSize.width: avatar.width
sourceSize.height: avatar.height
layer.enabled: true
layer.effect: OpacityMask {
maskSource: Rectangle {
anchors.fill: parent
width: avatar.width
height: avatar.height
radius: Settings.avatarCircles ? height / 2 : 3
}
}
}
Rectangle {
anchors.bottom: avatar.bottom
anchors.right: avatar.right
visible: !!userid
height: avatar.height / 6
width: height
radius: Settings.avatarCircles ? height / 2 : height / 4
color: {
switch (TimelineManager.userPresence(userid)) {
case "online":
return "#00cc66";
case "unavailable":
return "#ff9933";
case "offline":
default:
// return "#a82353" don't show anything if offline, since it is confusing, if presence is disabled
"transparent";
}
}
}
}

@ -3,24 +3,42 @@ import QtQuick.Controls 2.1
import im.nheko 1.0
Rectangle {
id: indicator
color: "transparent"
width: 16
height: 16
ToolTip.visible: ma.containsMouse && indicator.visible
ToolTip.text: qsTr("Encrypted")
MouseArea{
id: ma
anchors.fill: parent
hoverEnabled: true
}
Image {
id: stateImg
anchors.fill: parent
source: "image://colorimage/:/icons/icons/ui/lock.png?"+colors.buttonText
}
}
id: indicator
property bool encrypted: false
function getEncryptionImage() {
if (encrypted)
return "image://colorimage/:/icons/icons/ui/lock.png?" + colors.buttonText;
else
return "image://colorimage/:/icons/icons/ui/unlock.png?#dd3d3d";
}
function getEncryptionTooltip() {
if (encrypted)
return qsTr("Encrypted");
else
return qsTr("This message is not encrypted!");
}
color: "transparent"
width: 16
height: 16
ToolTip.visible: ma.containsMouse && indicator.visible
ToolTip.text: getEncryptionTooltip()
MouseArea {
id: ma
anchors.fill: parent
hoverEnabled: true
}
Image {
id: stateImg
anchors.fill: parent
source: getEncryptionImage()
}
}

@ -1,29 +1,30 @@
import QtQuick 2.3
import QtQuick.Controls 2.3
Button {
property string image: undefined
id: button
flat: true
// disable background, because we don't want a border on hover
background: Item {
}
Image {
id: buttonImg
// Workaround, can't get icon.source working for now...
anchors.fill: parent
source: "image://colorimage/" + image + "?" + (button.hovered ? colors.highlight : colors.buttonText)
}
MouseArea
{
id: mouseArea
anchors.fill: parent
onPressed: mouse.accepted = false
cursorShape: Qt.PointingHandCursor
}
AbstractButton {
id: button
property string image: undefined
property color highlightColor: colors.highlight
property color buttonTextColor: colors.buttonText
width: 16
height: 16
Image {
id: buttonImg
// Workaround, can't get icon.source working for now...
anchors.fill: parent
source: "image://colorimage/" + image + "?" + (button.hovered ? highlightColor : buttonTextColor)
}
MouseArea {
id: mouseArea
anchors.fill: parent
onPressed: mouse.accepted = false
cursorShape: Qt.PointingHandCursor
}
}

@ -1,31 +1,37 @@
import QtQuick 2.5
import QtQuick.Controls 2.3
import im.nheko 1.0
TextEdit {
textFormat: TextEdit.RichText
readOnly: true
wrapMode: Text.Wrap
selectByMouse: true
color: colors.text
textFormat: TextEdit.RichText
readOnly: true
wrapMode: Text.Wrap
selectByMouse: true
activeFocusOnPress: false
color: colors.text
onLinkActivated: {
if (/^https:\/\/matrix.to\/#\/(@.*)$/.test(link)) {
chat.model.openUserProfile(/^https:\/\/matrix.to\/#\/(@.*)$/.exec(link)[1]);
} else if (/^https:\/\/matrix.to\/#\/(![^\/]*)$/.test(link)) {
TimelineManager.setHistoryView(/^https:\/\/matrix.to\/#\/(!.*)$/.exec(link)[1]);
} else if (/^https:\/\/matrix.to\/#\/(![^\/]*)\/(\$.*)$/.test(link)) {
var match = /^https:\/\/matrix.to\/#\/(![^\/]*)\/(\$.*)$/.exec(link);
TimelineManager.setHistoryView(match[1]);
chat.positionViewAtIndex(chat.model.idToIndex(match[2]), ListView.Contain);
} else {
TimelineManager.openLink(link);
}
}
ToolTip.visible: hoveredLink
ToolTip.text: hoveredLink
onLinkActivated: {
if (/^https:\/\/matrix.to\/#\/(@.*)$/.test(link)) chat.model.openUserProfile(/^https:\/\/matrix.to\/#\/(@.*)$/.exec(link)[1])
else if (/^https:\/\/matrix.to\/#\/(![^\/]*)$/.test(link)) timelineManager.setHistoryView(/^https:\/\/matrix.to\/#\/(!.*)$/.exec(link)[1])
else if (/^https:\/\/matrix.to\/#\/(![^\/]*)\/(\$.*)$/.test(link)) {
var match = /^https:\/\/matrix.to\/#\/(![^\/]*)\/(\$.*)$/.exec(link)
timelineManager.setHistoryView(match[1])
chat.positionViewAtIndex(chat.model.idToIndex(match[2]), ListView.Contain)
}
else Qt.openUrlExternally(link)
}
MouseArea
{
id: ma
anchors.fill: parent
onPressed: mouse.accepted = false
cursorShape: parent.hoveredLink ? Qt.PointingHandCursor : Qt.ArrowCursor
}
MouseArea {
id: ma
anchors.fill: parent
propagateComposedEvents: true
acceptedButtons: Qt.NoButton
cursorShape: parent.hoveredLink ? Qt.PointingHandCursor : Qt.ArrowCursor
}
ToolTip.visible: hoveredLink
ToolTip.text: hoveredLink
}

@ -0,0 +1,95 @@
import QtQuick 2.6
import QtQuick.Controls 2.2
import im.nheko 1.0
// This class is for showing Reactions in the timeline row, not for
// adding new reactions via the emoji picker
Flow {
id: reactionFlow
// highlight colors for selfReactedEvent background
property real highlightHue: colors.highlight.hslHue
property real highlightSat: colors.highlight.hslSaturation
property real highlightLight: colors.highlight.hslLightness
property string eventId
property alias reactions: repeater.model
anchors.left: parent.left
anchors.right: parent.right
spacing: 4
Repeater {
id: repeater
delegate: AbstractButton {
id: reaction
hoverEnabled: true
implicitWidth: contentItem.childrenRect.width + contentItem.leftPadding * 2
implicitHeight: contentItem.childrenRect.height
ToolTip.visible: hovered
ToolTip.text: modelData.users
onClicked: {
console.debug("Picked " + modelData.key + "in response to " + reactionFlow.eventId + ". selfReactedEvent: " + modelData.selfReactedEvent);
TimelineManager.queueReactionMessage(reactionFlow.eventId, modelData.key);
}
contentItem: Row {
anchors.centerIn: parent
spacing: reactionText.implicitHeight / 4
leftPadding: reactionText.implicitHeight / 2
rightPadding: reactionText.implicitHeight / 2
TextMetrics {
id: textMetrics
font.family: Settings.emojiFont
elide: Text.ElideRight
elideWidth: 150
text: modelData.key
}
Text {
id: reactionText
anchors.baseline: reactionCounter.baseline
text: textMetrics.elidedText + (textMetrics.elidedText == modelData.key ? "" : "…")
font.family: Settings.emojiFont
color: reaction.hovered ? colors.highlight : colors.text
maximumLineCount: 1
}
Rectangle {
id: divider
height: Math.floor(reactionCounter.implicitHeight * 1.4)
width: 1
color: (reaction.hovered || modelData.selfReactedEvent !== '') ? colors.highlight : colors.text
}
Text {
id: reactionCounter
anchors.verticalCenter: divider.verticalCenter
text: modelData.count
font: reaction.font
color: reaction.hovered ? colors.highlight : colors.text
}
}
background: Rectangle {
anchors.centerIn: parent
implicitWidth: reaction.implicitWidth
implicitHeight: reaction.implicitHeight
border.color: (reaction.hovered || modelData.selfReactedEvent !== '') ? colors.highlight : colors.text
color: modelData.selfReactedEvent !== '' ? Qt.hsla(highlightHue, highlightSat, highlightLight, 0.2) : colors.base
border.width: 1
radius: reaction.height / 2
}
}
}
}

@ -0,0 +1,108 @@
/*
* Copyright (C) 2016 Michael Bohlender, <michael.bohlender@kdemail.net>
* Copyright (C) 2017 Christian Mollekopf, <mollekopf@kolabsystems.com>
*
* 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 2 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, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
/*
* Shamelessly stolen from:
* https://cgit.kde.org/kube.git/tree/framework/qml/ScrollHelper.qml
*
* The MouseArea + interactive: false + maximumFlickVelocity are required
* to fix scrolling for desktop systems where we don't want flicking behaviour.
*
* See also:
* ScrollView.qml in qtquickcontrols
* qquickwheelarea.cpp in qtquickcontrols
*/
import QtQuick 2.9
import QtQuick.Controls 2.3
MouseArea {
// console.warn("Delta: ", wheel.pixelDelta.y);
// console.warn("Old position: ", flickable.contentY);
// console.warn("New position: ", newPos);
id: root
property Flickable flickable
property alias enabled: root.enabled
function calculateNewPosition(flickableItem, wheel) {
//Nothing to scroll
if (flickableItem.contentHeight < flickableItem.height)
return flickableItem.contentY;
//Ignore 0 events (happens at least with Christians trackpad)
if (wheel.pixelDelta.y == 0 && wheel.angleDelta.y == 0)
return flickableItem.contentY;
//pixelDelta seems to be the same as angleDelta/8
var pixelDelta = 0;
//The pixelDelta is a smaller number if both are provided, so pixelDelta can be 0 while angleDelta is still something. So we check the angleDelta
if (wheel.angleDelta.y) {
var wheelScrollLines = 3; //Default value of QApplication wheelScrollLines property
var pixelPerLine = 20; //Default value in Qt, originally comes from QTextEdit
var ticks = (wheel.angleDelta.y / 8) / 15; //Divide by 8 gives us pixels typically come in 15pixel steps.
pixelDelta = ticks * pixelPerLine * wheelScrollLines;
} else {
pixelDelta = wheel.pixelDelta.y;
}
pixelDelta = Math.round(pixelDelta);
if (!pixelDelta)
return flickableItem.contentY;
var minYExtent = flickableItem.originY + flickableItem.topMargin;
var maxYExtent = (flickableItem.contentHeight + flickableItem.bottomMargin + flickableItem.originY) - flickableItem.height;
if (typeof (flickableItem.headerItem) !== "undefined" && flickableItem.headerItem)
minYExtent += flickableItem.headerItem.height;
//Avoid overscrolling
return Math.max(minYExtent, Math.min(maxYExtent, flickableItem.contentY - pixelDelta));
}
propagateComposedEvents: true
//Place the mouse area under the flickable
z: -1
onFlickableChanged: {
if (enabled) {
flickable.maximumFlickVelocity = 100000;
flickable.boundsBehavior = Flickable.StopAtBounds;
root.parent = flickable;
}
}
acceptedButtons: Qt.NoButton
onWheel: {
var newPos = calculateNewPosition(flickable, wheel);
// Show the scrollbars
flickable.flick(0, 0);
flickable.contentY = newPos;
cancelFlickStateTimer.start();
}
Timer {
id: cancelFlickStateTimer
//How long the scrollbar will remain visible
interval: 500
// Hide the scrollbars
onTriggered: {
flickable.cancelFlick();
flickable.movementEnded();
}
}
}

@ -3,37 +3,55 @@ import QtQuick.Controls 2.1
import im.nheko 1.0
Rectangle {
id: indicator
property int state: 0
color: "transparent"
width: 16
height: 16
ToolTip.visible: ma.containsMouse && state != MtxEvent.Empty
ToolTip.text: switch (state) {
case MtxEvent.Failed: return qsTr("Failed")
case MtxEvent.Sent: return qsTr("Sent")
case MtxEvent.Received: return qsTr("Received")
case MtxEvent.Read: return qsTr("Read")
default: return ""
}
MouseArea{
id: ma
anchors.fill: parent
hoverEnabled: true
}
Image {
id: stateImg
// Workaround, can't get icon.source working for now...
anchors.fill: parent
source: switch (indicator.state) {
case MtxEvent.Failed: return "image://colorimage/:/icons/icons/ui/remove-symbol.png?" + colors.buttonText
case MtxEvent.Sent: return "image://colorimage/:/icons/icons/ui/clock.png?" + colors.buttonText
case MtxEvent.Received: return "image://colorimage/:/icons/icons/ui/checkmark.png?" + colors.buttonText
case MtxEvent.Read: return "image://colorimage/:/icons/icons/ui/double-tick-indicator.png?" + colors.buttonText
default: return ""
}
}
}
id: indicator
property int state: 0
color: "transparent"
width: 16
height: 16
ToolTip.visible: ma.containsMouse && state != MtxEvent.Empty
ToolTip.text: {
switch (state) {
case MtxEvent.Failed:
return qsTr("Failed");
case MtxEvent.Sent:
return qsTr("Sent");
case MtxEvent.Received:
return qsTr("Received");
case MtxEvent.Read:
return qsTr("Read");
default:
return "";
}
}
MouseArea {
id: ma
anchors.fill: parent
hoverEnabled: true
}
Image {
id: stateImg
// Workaround, can't get icon.source working for now...
anchors.fill: parent
source: {
switch (indicator.state) {
case MtxEvent.Failed:
return "image://colorimage/:/icons/icons/ui/remove-symbol.png?" + colors.buttonText;
case MtxEvent.Sent:
return "image://colorimage/:/icons/icons/ui/clock.png?" + colors.buttonText;
case MtxEvent.Received:
return "image://colorimage/:/icons/icons/ui/checkmark.png?" + colors.buttonText;
case MtxEvent.Read:
return "image://colorimage/:/icons/icons/ui/double-tick-indicator.png?" + colors.buttonText;
default:
return "";
}
}
}
}

@ -1,98 +1,148 @@
import "./delegates"
import "./emoji"
import QtQuick 2.6
import QtQuick.Controls 2.3
import QtQuick.Layouts 1.2
import QtQuick.Window 2.2
import im.nheko 1.0
import "./delegates"
Item {
anchors.left: parent.left
anchors.right: parent.right
height: row.height
MouseArea {
anchors.fill: parent
propagateComposedEvents: true
preventStealing: true
hoverEnabled: true
acceptedButtons: Qt.AllButtons
onClicked: {
if (mouse.button === Qt.RightButton)
messageContextMenu.show(model.id, model.type, model.isEncrypted, row);
}
onPressAndHold: {
messageContextMenu.show(model.id, model.type, model.isEncrypted, row, mapToItem(timelineRoot, mouse.x, mouse.y));
}
}
Rectangle {
color: (Settings.messageHoverHighlight && parent.containsMouse) ? colors.base : "transparent"
anchors.fill: row
}
RowLayout {
id: row
anchors.leftMargin: avatarSize + 16
anchors.left: parent.left
anchors.right: parent.right
Column {
Layout.fillWidth: true
Layout.alignment: Qt.AlignTop
spacing: 4
// fancy reply, if this is a reply
Reply {
visible: model.replyTo
modelData: chat.model.getDump(model.replyTo, model.id)
userColor: TimelineManager.userColor(modelData.userId, colors.window)
}
// actual message content
MessageDelegate {
id: contentItem
width: parent.width
modelData: model
}
Reactions {
id: reactionRow
reactions: model.reactions
eventId: model.id
}
}
StatusIndicator {
state: model.state
Layout.alignment: Qt.AlignRight | Qt.AlignTop
Layout.preferredHeight: 16
width: 16
}
EncryptionIndicator {
visible: model.isRoomEncrypted
encrypted: model.isEncrypted
Layout.alignment: Qt.AlignRight | Qt.AlignTop
Layout.preferredHeight: 16
width: 16
}
EmojiButton {
id: reactButton
visible: Settings.buttonsInTimeline
Layout.alignment: Qt.AlignRight | Qt.AlignTop
Layout.preferredHeight: 16
width: 16
hoverEnabled: true
ToolTip.visible: hovered
ToolTip.text: qsTr("React")
emojiPicker: emojiPopup
event_id: model.id
}
ImageButton {
id: replyButton
visible: Settings.buttonsInTimeline
Layout.alignment: Qt.AlignRight | Qt.AlignTop
Layout.preferredHeight: 16
width: 16
hoverEnabled: true
image: ":/icons/icons/ui/mail-reply.png"
ToolTip.visible: hovered
ToolTip.text: qsTr("Reply")
onClicked: chat.model.replyAction(model.id)
}
ImageButton {
id: optionsButton
visible: Settings.buttonsInTimeline
Layout.alignment: Qt.AlignRight | Qt.AlignTop
Layout.preferredHeight: 16
width: 16
hoverEnabled: true
image: ":/icons/icons/ui/vertical-ellipsis.png"
ToolTip.visible: hovered
ToolTip.text: qsTr("Options")
onClicked: messageContextMenu.show(model.id, model.type, model.isEncrypted, optionsButton)
}
Label {
Layout.alignment: Qt.AlignRight | Qt.AlignTop
text: model.timestamp.toLocaleTimeString("HH:mm")
width: Math.max(implicitWidth, text.length * fontMetrics.maximumCharacterWidth)
color: inactiveColors.text
ToolTip.visible: ma.containsMouse
ToolTip.text: Qt.formatDateTime(model.timestamp, Qt.DefaultLocaleLongDate)
MouseArea {
id: ma
anchors.fill: parent
hoverEnabled: true
propagateComposedEvents: true
}
}
}
RowLayout {
property var view: chat
anchors.leftMargin: avatarSize + 4
anchors.left: parent.left
anchors.right: parent.right
Column {
Layout.fillWidth: true
Layout.alignment: Qt.AlignTop
spacing: 4
// fancy reply, if this is a reply
Reply {
visible: model.replyTo
modelData: chat.model.getDump(model.replyTo)
userColor: timelineManager.userColor(modelData.userId, colors.window)
}
// actual message content
MessageDelegate {
id: contentItem
width: parent.width
modelData: model
}
}
StatusIndicator {
state: model.state
Layout.alignment: Qt.AlignRight | Qt.AlignTop
Layout.preferredHeight: 16
width: 16
}
EncryptionIndicator {
visible: model.isEncrypted
Layout.alignment: Qt.AlignRight | Qt.AlignTop
Layout.preferredHeight: 16
width: 16
}
ImageButton {
Layout.alignment: Qt.AlignRight | Qt.AlignTop
Layout.preferredHeight: 16
width: 16
id: replyButton
hoverEnabled: true
image: ":/icons/icons/ui/mail-reply.png"
ToolTip.visible: hovered
ToolTip.text: qsTr("Reply")
onClicked: view.model.replyAction(model.id)
}
ImageButton {
Layout.alignment: Qt.AlignRight | Qt.AlignTop
Layout.preferredHeight: 16
width: 16
id: optionsButton
hoverEnabled: true
image: ":/icons/icons/ui/vertical-ellipsis.png"
ToolTip.visible: hovered
ToolTip.text: qsTr("Options")
onClicked: messageContextMenu.show(model.id, model.type, optionsButton)
}
Text {
Layout.alignment: Qt.AlignRight | Qt.AlignTop
text: model.timestamp.toLocaleTimeString("HH:mm")
color: inactiveColors.text
MouseArea{
id: ma
anchors.fill: parent
hoverEnabled: true
}
ToolTip.visible: ma.containsMouse
ToolTip.text: Qt.formatDateTime(model.timestamp, Qt.DefaultLocaleLongDate)
}
}

@ -1,309 +1,582 @@
import "./delegates"
import "./device-verification"
import "./emoji"
import QtGraphicalEffects 1.0
import QtQuick 2.9
import QtQuick.Controls 2.3
import QtQuick.Layouts 1.2
import QtGraphicalEffects 1.0
import QtQuick.Window 2.2
import im.nheko 1.0
import im.nheko.EmojiModel 1.0
Page {
id: timelineRoot
property var colors: currentActivePalette
property var systemInactive
property var inactiveColors: currentInactivePalette ? currentInactivePalette : systemInactive
property int avatarSize: 40
property real highlightHue: colors.highlight.hslHue
property real highlightSat: colors.highlight.hslSaturation
property real highlightLight: colors.highlight.hslLightness
import "./delegates"
palette: colors
FontMetrics {
id: fontMetrics
}
EmojiPicker {
id: emojiPopup
width: 7 * 52 + 20
height: 6 * 52
colors: palette
model: EmojiProxyModel {
category: EmojiCategory.People
sourceModel: EmojiModel {
}
}
}
Menu {
id: messageContextMenu
property string eventId
property int eventType
property bool isEncrypted
function show(eventId_, eventType_, isEncrypted_, showAt_, position) {
eventId = eventId_;
eventType = eventType_;
isEncrypted = isEncrypted_;
if (position)
popup(position, showAt_);
else
popup(showAt_);
}
modal: true
MenuItem {
text: qsTr("React")
onClicked: emojiPopup.show(messageContextMenu.parent, messageContextMenu.eventId)
}
MenuItem {
text: qsTr("Reply")
onClicked: chat.model.replyAction(messageContextMenu.eventId)
}
MenuItem {
text: qsTr("Read receipts")
onTriggered: chat.model.readReceiptsAction(messageContextMenu.eventId)
}
MenuItem {
text: qsTr("Mark as read")
}
MenuItem {
text: qsTr("View raw message")
onTriggered: chat.model.viewRawMessage(messageContextMenu.eventId)
}
MenuItem {
visible: messageContextMenu.isEncrypted
height: visible ? implicitHeight : 0
text: qsTr("View decrypted raw message")
onTriggered: chat.model.viewDecryptedRawMessage(messageContextMenu.eventId)
}
MenuItem {
text: qsTr("Redact message")
onTriggered: chat.model.redactEvent(messageContextMenu.eventId)
}
MenuItem {
visible: messageContextMenu.eventType == MtxEvent.ImageMessage || messageContextMenu.eventType == MtxEvent.VideoMessage || messageContextMenu.eventType == MtxEvent.AudioMessage || messageContextMenu.eventType == MtxEvent.FileMessage || messageContextMenu.eventType == MtxEvent.Sticker
height: visible ? implicitHeight : 0
text: qsTr("Save as")
onTriggered: TimelineManager.timeline.saveMedia(messageContextMenu.eventId)
}
}
Rectangle {
anchors.fill: parent
color: colors.window
Component {
id: deviceVerificationDialog
DeviceVerification {
}
}
Connections {
function onNewDeviceVerificationRequest(flow, transactionId, userId, deviceId, isRequest) {
var dialog = deviceVerificationDialog.createObject(timelineRoot, {
"flow": flow
});
dialog.show();
}
target: TimelineManager
}
Connections {
function onOpenProfile(profile) {
var userProfile = userProfileComponent.createObject(timelineRoot, {
"profile": profile
});
userProfile.show();
}
target: TimelineManager.timeline
}
Label {
visible: !TimelineManager.timeline && !TimelineManager.isInitialSync
anchors.centerIn: parent
text: qsTr("No room open")
font.pointSize: 24
color: colors.text
}
BusyIndicator {
visible: running
anchors.centerIn: parent
running: TimelineManager.isInitialSync
height: 200
width: 200
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 {
//Layout.margins: 8
id: topLayout
anchors.left: parent.left
anchors.right: parent.right
anchors.margins: 8
anchors.verticalCenter: parent.verticalCenter
ImageButton {
id: backToRoomsButton
Layout.column: 0
Layout.row: 0
Layout.rowSpan: 2
Layout.alignment: Qt.AlignVCenter
visible: TimelineManager.isNarrowView
image: ":/icons/icons/ui/angle-pointing-to-left.png"
ToolTip.visible: hovered
ToolTip.text: qsTr("Back to room list")
onClicked: TimelineManager.backToRooms()
}
Avatar {
Layout.column: 1
Layout.row: 0
Layout.rowSpan: 2
Layout.alignment: Qt.AlignVCenter
width: avatarSize
height: avatarSize
url: chat.model ? chat.model.roomAvatarUrl.replace("mxc://", "image://MxcImage/") : ""
displayName: chat.model ? chat.model.roomName : qsTr("No room selected")
MouseArea {
anchors.fill: parent
onClicked: TimelineManager.openRoomSettings()
}
}
Label {
Layout.fillWidth: true
Layout.column: 2
Layout.row: 0
color: colors.text
font.pointSize: fontMetrics.font.pointSize * 1.1
text: chat.model ? chat.model.roomName : qsTr("No room selected")
MouseArea {
anchors.fill: parent
onClicked: TimelineManager.openRoomSettings()
}
}
MatrixText {
Layout.fillWidth: true
Layout.column: 2
Layout.row: 1
Layout.maximumHeight: fontMetrics.lineSpacing * 2 // show 2 lines
clip: true
text: chat.model ? chat.model.roomTopic : ""
}
ImageButton {
id: roomOptionsButton
Layout.column: 3
Layout.row: 0
Layout.rowSpan: 2
Layout.alignment: Qt.AlignVCenter
image: ":/icons/icons/ui/vertical-ellipsis.png"
ToolTip.visible: hovered
ToolTip.text: qsTr("Room options")
onClicked: roomOptionsMenu.popup(roomOptionsButton)
Menu {
id: roomOptionsMenu
MenuItem {
text: qsTr("Invite users")
onTriggered: TimelineManager.openInviteUsersDialog()
}
MenuItem {
text: qsTr("Members")
onTriggered: TimelineManager.openMemberListDialog()
}
MenuItem {
text: qsTr("Leave room")
onTriggered: TimelineManager.openLeaveRoomDialog()
}
MenuItem {
text: qsTr("Settings")
onTriggered: TimelineManager.openRoomSettings()
}
}
}
}
}
ListView {
id: chat
property int delegateMaxWidth: (Settings.timelineMaxWidth > 100 && (parent.width - Settings.timelineMaxWidth) > scrollbar.width * 2) ? Settings.timelineMaxWidth : (parent.width - scrollbar.width * 2)
visible: TimelineManager.timeline != null
cacheBuffer: 400
Layout.fillWidth: true
Layout.fillHeight: true
model: TimelineManager.timeline
boundsBehavior: Flickable.StopAtBounds
pixelAligned: true
spacing: 4
verticalLayoutDirection: ListView.BottomToTop
onCountChanged: {
if (atYEnd)
model.currentIndex = 0;
} // Mark last event as read, since we are at the bottom
ScrollHelper {
flickable: parent
anchors.fill: parent
}
Shortcut {
sequence: StandardKey.MoveToPreviousPage
onActivated: {
chat.contentY = chat.contentY - chat.height / 2;
chat.returnToBounds();
}
}
Shortcut {
sequence: StandardKey.MoveToNextPage
onActivated: {
chat.contentY = chat.contentY + chat.height / 2;
chat.returnToBounds();
}
}
Shortcut {
sequence: StandardKey.Cancel
onActivated: chat.model.reply = undefined
}
Shortcut {
sequence: "Alt+Up"
onActivated: chat.model.reply = chat.model.indexToId(chat.model.reply ? chat.model.idToIndex(chat.model.reply) + 1 : 0)
}
Shortcut {
sequence: "Alt+Down"
onActivated: {
var idx = chat.model.reply ? chat.model.idToIndex(chat.model.reply) - 1 : -1;
chat.model.reply = idx >= 0 ? chat.model.indexToId(idx) : undefined;
}
}
Component {
id: userProfileComponent
UserProfile {
}
}
section {
property: "section"
}
Component {
id: sectionHeader
Column {
property var modelData
property string section
property string nextSection
topPadding: 4
bottomPadding: 4
spacing: 8
visible: !!modelData
width: parent.width
height: (section.includes(" ") ? dateBubble.height + 8 + userName.height : userName.height) + 8
Label {
id: dateBubble
anchors.horizontalCenter: parent ? parent.horizontalCenter : undefined
visible: section.includes(" ")
text: chat.model.formatDateSeparator(modelData.timestamp)
color: colors.text
height: fontMetrics.height * 1.4
width: contentWidth * 1.2
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
background: Rectangle {
radius: parent.height / 2
color: colors.base
}
}
Row {
height: userName.height
spacing: 8
Avatar {
width: avatarSize
height: avatarSize
url: chat.model.avatarUrl(modelData.userId).replace("mxc://", "image://MxcImage/")
displayName: modelData.userName
userid: modelData.userId
MouseArea {
anchors.fill: parent
onClicked: chat.model.openUserProfile(modelData.userId)
cursorShape: Qt.PointingHandCursor
propagateComposedEvents: true
}
}
Label {
id: userName
text: TimelineManager.escapeEmoji(modelData.userName)
color: TimelineManager.userColor(modelData.userId, colors.window)
textFormat: Text.RichText
MouseArea {
anchors.fill: parent
Layout.alignment: Qt.AlignHCenter
onClicked: chat.model.openUserProfile(modelData.userId)
cursorShape: Qt.PointingHandCursor
propagateComposedEvents: true
}
}
}
}
}
ScrollBar.vertical: ScrollBar {
id: scrollbar
}
delegate: Item {
id: wrapper
// This would normally be previousSection, but our model's order is inverted.
property bool sectionBoundary: (ListView.nextSection != "" && ListView.nextSection !== ListView.section) || model.index === chat.count - 1
property Item section
anchors.horizontalCenter: parent ? parent.horizontalCenter : undefined
width: chat.delegateMaxWidth
height: section ? section.height + timelinerow.height : timelinerow.height
onSectionBoundaryChanged: {
if (sectionBoundary) {
var properties = {
"modelData": model.dump,
"section": ListView.section,
"nextSection": ListView.nextSection
};
section = sectionHeader.createObject(wrapper, properties);
} else {
section.destroy();
section = null;
}
}
TimelineRow {
id: timelinerow
y: section ? section.y + section.height : 0
}
Connections {
function onMovementEnded() {
if (y + height + 2 * chat.spacing > chat.contentY + chat.height && y < chat.contentY + chat.height)
chat.model.currentIndex = index;
}
target: chat
}
}
footer: BusyIndicator {
anchors.horizontalCenter: parent.horizontalCenter
running: chat.model && chat.model.paginationInProgress
height: 50
width: 50
z: 3
}
}
Item {
id: chatFooter
implicitHeight: Math.max(fontMetrics.height * 1.2, footerContent.height)
Layout.fillWidth: true
z: 3
Column {
id: footerContent
anchors.left: parent.left
anchors.right: parent.right
anchors.bottom: parent.bottom
Rectangle {
id: typingRect
anchors.left: parent.left
anchors.right: parent.right
color: (chat.model && chat.model.typingUsers.length > 0) ? colors.window : "transparent"
height: typingDisplay.height
Label {
id: typingDisplay
anchors.left: parent.left
anchors.leftMargin: 10
anchors.right: parent.right
anchors.rightMargin: 10
color: colors.text
text: chat.model ? chat.model.formatTypingUsers(chat.model.typingUsers, colors.window) : ""
textFormat: Text.RichText
}
}
Rectangle {
id: replyPopup
anchors.left: parent.left
anchors.right: parent.right
visible: chat.model && chat.model.reply
// Height of child, plus margins, plus border
height: replyPreview.height + 10
color: colors.base
Reply {
id: replyPreview
anchors.left: parent.left
anchors.leftMargin: 10
anchors.right: closeReplyButton.left
anchors.rightMargin: 20
anchors.bottom: parent.bottom
modelData: chat.model ? chat.model.getDump(chat.model.reply, chat.model.id) : {
}
userColor: TimelineManager.userColor(modelData.userId, colors.window)
}
ImageButton {
id: closeReplyButton
anchors.right: parent.right
anchors.rightMargin: 15
anchors.top: replyPreview.top
hoverEnabled: true
width: 16
height: 16
image: ":/icons/icons/ui/remove-symbol.png"
ToolTip.visible: closeReplyButton.hovered
ToolTip.text: qsTr("Close")
onClicked: chat.model.reply = undefined
}
}
}
}
ActiveCallBar {
Layout.fillWidth: true
z: 3
}
}
}
systemInactive: SystemPalette {
colorGroup: SystemPalette.Disabled
}
Item {
property var colors: currentActivePalette
property var systemInactive: SystemPalette { colorGroup: SystemPalette.Disabled }
property var inactiveColors: currentInactivePalette ? currentInactivePalette : systemInactive
property int avatarSize: 40
Menu {
id: messageContextMenu
palette: colors
modal: true
function show(eventId_, eventType_, showAt) {
eventId = eventId_
eventType = eventType_
popup(showAt)
}
property string eventId
property int eventType
MenuItem {
text: qsTr("Read receipts")
onTriggered: chat.model.readReceiptsAction(messageContextMenu.eventId)
}
MenuItem {
text: qsTr("Mark as read")
}
MenuItem {
text: qsTr("View raw message")
onTriggered: chat.model.viewRawMessage(messageContextMenu.eventId)
}
MenuItem {
text: qsTr("Redact message")
onTriggered: chat.model.redactEvent(messageContextMenu.eventId)
}
MenuItem {
visible: messageContextMenu.eventType == MtxEvent.ImageMessage || messageContextMenu.eventType == MtxEvent.VideoMessage || messageContextMenu.eventType == MtxEvent.AudioMessage || messageContextMenu.eventType == MtxEvent.FileMessage || messageContextMenu.eventType == MtxEvent.Sticker
text: qsTr("Save as")
onTriggered: timelineManager.timeline.saveMedia(messageContextMenu.eventId)
}
}
id: timelineRoot
Rectangle {
anchors.fill: parent
color: colors.window
Text {
visible: !timelineManager.timeline && !timelineManager.isInitialSync
anchors.centerIn: parent
text: qsTr("No room open")
font.pointSize: 24
color: colors.windowText
}
BusyIndicator {
anchors.centerIn: parent
running: timelineManager.isInitialSync
height: 200
width: 200
z: 3
}
ListView {
id: chat
visible: timelineManager.timeline != null
anchors.left: parent.left
anchors.right: parent.right
anchors.top: parent.top
anchors.bottom: chatFooter.top
anchors.leftMargin: 4
anchors.rightMargin: scrollbar.width
model: timelineManager.timeline
boundsBehavior: Flickable.StopAtBounds
pixelAligned: true
MouseArea {
anchors.fill: parent
acceptedButtons: Qt.NoButton
propagateComposedEvents: true
z: -1
onWheel: {
if (wheel.angleDelta != 0) {
chat.contentY = chat.contentY - wheel.angleDelta.y
wheel.accepted = true
chat.returnToBounds()
}
}
}
Shortcut {
sequence: StandardKey.MoveToPreviousPage
onActivated: { chat.contentY = chat.contentY - chat.height / 2; chat.returnToBounds(); }
}
Shortcut {
sequence: StandardKey.MoveToNextPage
onActivated: { chat.contentY = chat.contentY + chat.height / 2; chat.returnToBounds(); }
}
ScrollBar.vertical: ScrollBar {
id: scrollbar
parent: chat.parent
anchors.top: chat.top
anchors.left: chat.right
anchors.bottom: chat.bottom
}
spacing: 4
verticalLayoutDirection: ListView.BottomToTop
onCountChanged: if (atYEnd) model.currentIndex = 0 // Mark last event as read, since we are at the bottom
delegate: Rectangle {
// 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
id: wrapper
property Item section
width: chat.width
height: section ? section.height + timelinerow.height : timelinerow.height
color: "transparent"
TimelineRow {
id: timelinerow
y: section ? section.y + section.height : 0
}
onSectionBoundaryChanged: {
if (sectionBoundary) {
var properties = {
'modelData': model.dump,
'section': ListView.section,
'nextSection': ListView.nextSection
}
section = sectionHeader.createObject(wrapper, properties)
} else {
section.destroy()
section = null
}
}
Binding {
target: chat.model
property: "currentIndex"
when: y + height + 2 * chat.spacing > chat.contentY + chat.height && y < chat.contentY + chat.height
value: index
delayed: true
}
}
section {
property: "section"
}
Component {
id: sectionHeader
Column {
property var modelData
property string section
property string nextSection
topPadding: 4
bottomPadding: 4
spacing: 8
visible: !!modelData
width: parent.width
height: (section.includes(" ") ? dateBubble.height + 8 + userName.height : userName.height) + 8
Label {
id: dateBubble
anchors.horizontalCenter: parent ? parent.horizontalCenter : undefined
visible: section.includes(" ")
text: chat.model.formatDateSeparator(modelData.timestamp)
color: colors.windowText
height: contentHeight * 1.2
width: contentWidth * 1.2
horizontalAlignment: Text.AlignHCenter
background: Rectangle {
radius: parent.height / 2
color: colors.base
}
}
Row {
height: userName.height
spacing: 4
Avatar {
width: avatarSize
height: avatarSize
url: chat.model.avatarUrl(modelData.userId).replace("mxc://", "image://MxcImage/")
displayName: modelData.userName
MouseArea {
anchors.fill: parent
onClicked: chat.model.openUserProfile(modelData.userId)
cursorShape: Qt.PointingHandCursor
}
}
Text {
id: userName
text: chat.model.escapeEmoji(modelData.userName)
color: timelineManager.userColor(modelData.userId, colors.window)
textFormat: Text.RichText
MouseArea {
anchors.fill: parent
onClicked: chat.model.openUserProfile(section.split(" ")[0])
cursorShape: Qt.PointingHandCursor
}
}
}
}
}
}
Rectangle {
id: chatFooter
height: Math.max(16, footerContent.height)
anchors.left: parent.left
anchors.right: parent.right
anchors.bottom: parent.bottom
z: 3
color: "transparent"
Column {
id: footerContent
anchors.left: parent.left
anchors.right: parent.right
Text {
id: typingDisplay
anchors.left: parent.left
anchors.right: parent.right
anchors.leftMargin: 10
anchors.rightMargin: 10
text: chat.model ? chat.model.formatTypingUsers(chat.model.typingUsers, colors.window) : ""
textFormat: Text.RichText
color: colors.windowText
}
Rectangle {
anchors.left: parent.left
anchors.right: parent.right
id: replyPopup
visible: timelineManager.replyingEvent && chat.model
// Height of child, plus margins, plus border
height: replyPreview.height + 10
color: colors.base
Reply {
id: replyPreview
anchors.left: parent.left
anchors.leftMargin: 10
anchors.right: closeReplyButton.left
anchors.rightMargin: 20
anchors.bottom: parent.bottom
modelData: chat.model ? chat.model.getDump(timelineManager.replyingEvent) : {}
userColor: timelineManager.userColor(modelData.userId, colors.window)
}
ImageButton {
id: closeReplyButton
anchors.right: parent.right
anchors.rightMargin: 15
anchors.top: replyPreview.top
hoverEnabled: true
width: 16
height: 16
image: ":/icons/icons/ui/remove-symbol.png"
ToolTip.visible: closeReplyButton.hovered
ToolTip.text: qsTr("Close")
onClicked: timelineManager.closeReply()
}
}
}
}
}
}

@ -0,0 +1,175 @@
import "./device-verification"
import QtQuick 2.9
import QtQuick.Controls 2.3
import QtQuick.Layouts 1.2
import QtQuick.Window 2.3
import im.nheko 1.0
ApplicationWindow {
id: userProfileDialog
property var profile
height: 650
width: 420
minimumHeight: 420
palette: colors
Component {
id: deviceVerificationDialog
DeviceVerification {
}
}
ColumnLayout {
id: contentL
anchors.fill: parent
anchors.margins: 10
spacing: 10
Avatar {
url: profile.avatarUrl.replace("mxc://", "image://MxcImage/")
height: 130
width: 130
displayName: profile.displayName
userid: profile.userid
Layout.alignment: Qt.AlignHCenter
}
Label {
text: profile.displayName
fontSizeMode: Text.HorizontalFit
font.pixelSize: 20
color: TimelineManager.userColor(profile.userid, colors.window)
font.bold: true
Layout.alignment: Qt.AlignHCenter
}
MatrixText {
text: profile.userid
font.pixelSize: 15
Layout.alignment: Qt.AlignHCenter
}
Button {
id: verifyUserButton
text: qsTr("Verify")
Layout.alignment: Qt.AlignHCenter
enabled: !profile.isUserVerified
visible: !profile.isUserVerified
onClicked: profile.verify()
}
RowLayout {
Layout.alignment: Qt.AlignHCenter
spacing: 8
ImageButton {
image: ":/icons/icons/ui/do-not-disturb-rounded-sign.png"
hoverEnabled: true
ToolTip.visible: hovered
ToolTip.text: qsTr("Ban the user")
onClicked: profile.banUser()
}
// ImageButton{
// image:":/icons/icons/ui/volume-off-indicator.png"
// Layout.margins: {
// left: 5
// right: 5
// }
// ToolTip.visible: hovered
// ToolTip.text: qsTr("Ignore messages from this user")
// onClicked : {
// profile.ignoreUser()
// }
// }
ImageButton {
image: ":/icons/icons/ui/black-bubble-speech.png"
hoverEnabled: true
ToolTip.visible: hovered
ToolTip.text: qsTr("Start a private chat")
onClicked: profile.startChat()
}
ImageButton {
image: ":/icons/icons/ui/round-remove-button.png"
hoverEnabled: true
ToolTip.visible: hovered
ToolTip.text: qsTr("Kick the user")
onClicked: profile.kickUser()
}
}
ListView {
id: devicelist
Layout.fillHeight: true
Layout.minimumHeight: 200
Layout.fillWidth: true
clip: true
spacing: 8
boundsBehavior: Flickable.StopAtBounds
model: profile.deviceList
delegate: RowLayout {
width: devicelist.width
spacing: 4
ColumnLayout {
spacing: 0
Text {
Layout.fillWidth: true
Layout.alignment: Qt.AlignLeft
elide: Text.ElideRight
font.bold: true
color: colors.text
text: model.deviceId
}
Text {
Layout.fillWidth: true
Layout.alignment: Qt.AlignRight
elide: Text.ElideRight
color: colors.text
text: model.deviceName
}
}
Image {
Layout.preferredHeight: 16
Layout.preferredWidth: 16
source: ((model.verificationStatus == VerificationStatus.VERIFIED) ? "image://colorimage/:/icons/icons/ui/lock.png?green" : ((model.verificationStatus == VerificationStatus.UNVERIFIED) ? "image://colorimage/:/icons/icons/ui/unlock.png?yellow" : "image://colorimage/:/icons/icons/ui/unlock.png?red"))
}
Button {
id: verifyButton
text: (model.verificationStatus != VerificationStatus.VERIFIED) ? "Verify" : "Unverify"
onClicked: {
if (model.verificationStatus == VerificationStatus.VERIFIED)
profile.unverify(model.deviceId);
else
profile.verify(model.deviceId);
}
}
}
}
}
footer: DialogButtonBox {
standardButtons: DialogButtonBox.Ok
onAccepted: userProfileDialog.close()
}
}

@ -1,57 +1,75 @@
import QtQuick 2.6
import QtQuick.Layouts 1.2
import im.nheko 1.0
Item {
height: row.height + 24
width: parent ? parent.width : undefined
RowLayout {
id: row
anchors.centerIn: parent
width: parent.width - 24
spacing: 15
Rectangle {
id: button
color: colors.light
radius: 22
height: 44
width: 44
Image {
id: img
anchors.centerIn: parent
source: "qrc:/icons/icons/ui/arrow-pointing-down.png"
fillMode: Image.Pad
}
MouseArea {
anchors.fill: parent
onClicked: TimelineManager.timeline.saveMedia(model.data.id)
cursorShape: Qt.PointingHandCursor
}
}
ColumnLayout {
id: col
Text {
id: filename
Layout.fillWidth: true
text: model.data.filename
textFormat: Text.PlainText
elide: Text.ElideRight
color: colors.text
}
Text {
id: filesize
Layout.fillWidth: true
text: model.data.filesize
textFormat: Text.PlainText
elide: Text.ElideRight
color: colors.text
}
}
}
Rectangle {
color: colors.dark
z: -1
radius: 10
height: row.height + 24
width: 44 + 24 + 24 + Math.max(Math.min(filesize.width, filesize.implicitWidth), Math.min(filename.width, filename.implicitWidth))
}
Rectangle {
radius: 10
color: colors.base
height: row.height + 24
width: parent ? parent.width : undefined
RowLayout {
id: row
anchors.centerIn: parent
width: parent.width - 24
spacing: 15
Rectangle {
id: button
color: colors.light
radius: 22
height: 44
width: 44
Image {
id: img
anchors.centerIn: parent
source: "qrc:/icons/icons/ui/arrow-pointing-down.png"
fillMode: Image.Pad
}
MouseArea {
anchors.fill: parent
onClicked: timelineManager.timeline.saveMedia(model.data.id)
cursorShape: Qt.PointingHandCursor
}
}
ColumnLayout {
id: col
Text {
Layout.fillWidth: true
text: model.data.body
textFormat: Text.PlainText
elide: Text.ElideRight
color: colors.text
}
Text {
Layout.fillWidth: true
text: model.data.filesize
textFormat: Text.PlainText
elide: Text.ElideRight
color: colors.text
}
}
}
}

@ -1,28 +1,70 @@
import QtQuick 2.6
import im.nheko 1.0
Item {
property double tempWidth: Math.min(parent ? parent.width : undefined, model.data.width)
property double tempHeight: tempWidth * model.data.proportionalHeight
property double tempWidth: Math.min(parent ? parent.width : undefined, model.data.width < 1 ? parent.width : model.data.width)
property double tempHeight: tempWidth * model.data.proportionalHeight
property double divisor: model.isReply ? 4 : 2
property bool tooHigh: tempHeight > timelineRoot.height / divisor
height: Math.round(tooHigh ? timelineRoot.height / divisor : tempHeight)
width: Math.round(tooHigh ? (timelineRoot.height / divisor) / model.data.proportionalHeight : tempWidth)
Image {
id: blurhash
anchors.fill: parent
visible: img.status != Image.Ready
source: model.data.blurhash ? ("image://blurhash/" + model.data.blurhash) : ("image://colorimage/:/icons/icons/ui/do-not-disturb-rounded-sign@2x.png?" + colors.buttonText)
asynchronous: true
fillMode: Image.PreserveAspectFit
sourceSize.width: parent.width
sourceSize.height: parent.height
}
Image {
id: img
property bool tooHigh: tempHeight > timelineRoot.height / 2
anchors.fill: parent
source: model.data.url.replace("mxc://", "image://MxcImage/")
asynchronous: true
fillMode: Image.PreserveAspectFit
height: tooHigh ? timelineRoot.height / 2 : tempHeight
width: tooHigh ? (timelineRoot.height / 2) / model.data.proportionalHeight : tempWidth
MouseArea {
id: mouseArea
enabled: model.data.type == MtxEvent.ImageMessage && img.status == Image.Ready
hoverEnabled: true
anchors.fill: parent
onClicked: TimelineManager.openImageOverlay(model.data.url, model.data.id)
}
Image {
id: img
anchors.fill: parent
Item {
id: overlay
anchors.fill: parent
visible: mouseArea.containsMouse
source: model.data.url.replace("mxc://", "image://MxcImage/")
asynchronous: true
fillMode: Image.PreserveAspectFit
Rectangle {
id: container
width: parent.width
implicitHeight: imgcaption.implicitHeight
anchors.bottom: overlay.bottom
color: colors.window
opacity: 0.75
}
MouseArea {
enabled: model.data.type == MtxEvent.ImageMessage
anchors.fill: parent
onClicked: timelineManager.openImageOverlay(model.data.url, model.data.id)
}
}
Text {
id: imgcaption
anchors.fill: container
elide: Text.ElideMiddle
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
// See this MSC: https://github.com/matrix-org/matrix-doc/pull/2530
text: model.data.filename ? model.data.filename : model.data.body
color: colors.text
}
}
}
}

@ -2,93 +2,334 @@ import QtQuick 2.6
import im.nheko 1.0
Item {
// Workaround to have an assignable global property
Item {
id: model
property var data;
}
property alias modelData: model.data
height: chooser.childrenRect.height
DelegateChooser {
id: chooser
//role: "type" //< not supported in our custom implementation, have to use roleValue
roleValue: model.data.type
anchors.fill: parent
DelegateChoice {
roleValue: MtxEvent.UnknownMessage
Placeholder { text: "Unretrieved event" }
}
DelegateChoice {
roleValue: MtxEvent.TextMessage
TextMessage {}
}
DelegateChoice {
roleValue: MtxEvent.NoticeMessage
NoticeMessage {}
}
DelegateChoice {
roleValue: MtxEvent.EmoteMessage
NoticeMessage {
formatted: chat.model.escapeEmoji(modelData.userName) + " " + model.data.formattedBody
color: timelineManager.userColor(modelData.userId, colors.window)
}
}
DelegateChoice {
roleValue: MtxEvent.ImageMessage
ImageMessage {}
}
DelegateChoice {
roleValue: MtxEvent.Sticker
ImageMessage {}
}
DelegateChoice {
roleValue: MtxEvent.FileMessage
FileMessage {}
}
DelegateChoice {
roleValue: MtxEvent.VideoMessage
PlayableMediaMessage {}
}
DelegateChoice {
roleValue: MtxEvent.AudioMessage
PlayableMediaMessage {}
}
DelegateChoice {
roleValue: MtxEvent.Redacted
Pill {
text: qsTr("redacted")
}
}
DelegateChoice {
roleValue: MtxEvent.Encryption
Pill {
text: qsTr("Encryption enabled")
}
}
DelegateChoice {
roleValue: MtxEvent.Name
NoticeMessage {
text: model.data.roomName ? qsTr("room name changed to: %1").arg(model.data.roomName) : qsTr("removed room name")
}
}
DelegateChoice {
roleValue: MtxEvent.Topic
NoticeMessage {
text: model.data.roomTopic ? qsTr("topic changed to: %1").arg(model.data.roomTopic) : qsTr("removed topic")
}
}
DelegateChoice {
roleValue: MtxEvent.Member
NoticeMessage {
text: timelineManager.timeline.formatMemberEvent(model.data.id);
}
}
DelegateChoice {
Placeholder {}
}
}
property alias modelData: model.data
property alias isReply: model.isReply
property real implicitWidth: (chooser.child && chooser.child.implicitWidth) ? chooser.child.implicitWidth : width
height: chooser.childrenRect.height
// Workaround to have an assignable global property
Item {
id: model
property var data
property bool isReply: false
}
DelegateChooser {
id: chooser
//role: "type" //< not supported in our custom implementation, have to use roleValue
roleValue: model.data.type
anchors.fill: parent
DelegateChoice {
roleValue: MtxEvent.UnknownMessage
Placeholder {
text: "Unretrieved event"
}
}
DelegateChoice {
roleValue: MtxEvent.TextMessage
TextMessage {
}
}
DelegateChoice {
roleValue: MtxEvent.NoticeMessage
NoticeMessage {
}
}
DelegateChoice {
roleValue: MtxEvent.EmoteMessage
NoticeMessage {
formatted: TimelineManager.escapeEmoji(modelData.userName) + " " + model.data.formattedBody
color: TimelineManager.userColor(modelData.userId, colors.window)
}
}
DelegateChoice {
roleValue: MtxEvent.ImageMessage
ImageMessage {
}
}
DelegateChoice {
roleValue: MtxEvent.Sticker
ImageMessage {
}
}
DelegateChoice {
roleValue: MtxEvent.FileMessage
FileMessage {
}
}
DelegateChoice {
roleValue: MtxEvent.VideoMessage
PlayableMediaMessage {
}
}
DelegateChoice {
roleValue: MtxEvent.AudioMessage
PlayableMediaMessage {
}
}
DelegateChoice {
roleValue: MtxEvent.Redacted
Pill {
text: qsTr("redacted")
}
}
DelegateChoice {
roleValue: MtxEvent.Redaction
Pill {
text: qsTr("redacted")
}
}
DelegateChoice {
roleValue: MtxEvent.Encryption
Pill {
text: qsTr("Encryption enabled")
}
}
DelegateChoice {
roleValue: MtxEvent.Name
NoticeMessage {
text: model.data.roomName ? qsTr("room name changed to: %1").arg(model.data.roomName) : qsTr("removed room name")
}
}
DelegateChoice {
roleValue: MtxEvent.Topic
NoticeMessage {
text: model.data.roomTopic ? qsTr("topic changed to: %1").arg(model.data.roomTopic) : qsTr("removed topic")
}
}
DelegateChoice {
roleValue: MtxEvent.RoomCreate
NoticeMessage {
text: qsTr("%1 created and configured room: %2").arg(model.data.userName).arg(model.data.roomId)
}
}
DelegateChoice {
roleValue: MtxEvent.CallInvite
NoticeMessage {
text: {
switch (model.data.callType) {
case "voice":
return qsTr("%1 placed a voice call.").arg(model.data.userName);
case "video":
return qsTr("%1 placed a video call.").arg(model.data.userName);
default:
return qsTr("%1 placed a call.").arg(model.data.userName);
}
}
}
}
DelegateChoice {
roleValue: MtxEvent.CallAnswer
NoticeMessage {
text: qsTr("%1 answered the call.").arg(model.data.userName)
}
}
DelegateChoice {
roleValue: MtxEvent.CallHangUp
NoticeMessage {
text: qsTr("%1 ended the call.").arg(model.data.userName)
}
}
DelegateChoice {
roleValue: MtxEvent.CallCandidates
NoticeMessage {
text: qsTr("Negotiating call...")
}
}
DelegateChoice {
// TODO: make a more complex formatter for the power levels.
roleValue: MtxEvent.PowerLevels
NoticeMessage {
text: TimelineManager.timeline.formatPowerLevelEvent(model.data.id)
}
}
DelegateChoice {
roleValue: MtxEvent.RoomJoinRules
NoticeMessage {
text: TimelineManager.timeline.formatJoinRuleEvent(model.data.id)
}
}
DelegateChoice {
roleValue: MtxEvent.RoomHistoryVisibility
NoticeMessage {
text: TimelineManager.timeline.formatHistoryVisibilityEvent(model.data.id)
}
}
DelegateChoice {
roleValue: MtxEvent.RoomGuestAccess
NoticeMessage {
text: TimelineManager.timeline.formatGuestAccessEvent(model.data.id)
}
}
DelegateChoice {
roleValue: MtxEvent.Member
NoticeMessage {
text: TimelineManager.timeline.formatMemberEvent(model.data.id)
}
}
DelegateChoice {
roleValue: MtxEvent.KeyVerificationRequest
NoticeMessage {
text: "KeyVerificationRequest"
}
}
DelegateChoice {
roleValue: MtxEvent.KeyVerificationStart
NoticeMessage {
text: "KeyVerificationStart"
}
}
DelegateChoice {
roleValue: MtxEvent.KeyVerificationReady
NoticeMessage {
text: "KeyVerificationReady"
}
}
DelegateChoice {
roleValue: MtxEvent.KeyVerificationCancel
NoticeMessage {
text: "KeyVerificationCancel"
}
}
DelegateChoice {
roleValue: MtxEvent.KeyVerificationKey
NoticeMessage {
text: "KeyVerificationKey"
}
}
DelegateChoice {
roleValue: MtxEvent.KeyVerificationMac
NoticeMessage {
text: "KeyVerificationMac"
}
}
DelegateChoice {
roleValue: MtxEvent.KeyVerificationDone
NoticeMessage {
text: "KeyVerificationDone"
}
}
DelegateChoice {
roleValue: MtxEvent.KeyVerificationDone
NoticeMessage {
text: "KeyVerificationDone"
}
}
DelegateChoice {
roleValue: MtxEvent.KeyVerificationAccept
NoticeMessage {
text: "KeyVerificationAccept"
}
}
DelegateChoice {
Placeholder {
}
}
}
}

@ -1,4 +1,6 @@
TextMessage {
font.italic: true
color: inactiveColors.text
font.italic: true
color: colors.buttonText
height: isReply ? Math.min(chat.height / 8, implicitHeight) : undefined
clip: true
}

@ -2,13 +2,14 @@ import QtQuick 2.5
import QtQuick.Controls 2.1
Label {
color: inactiveColors.text
horizontalAlignment: Text.AlignHCenter
color: colors.brightText
horizontalAlignment: Text.AlignHCenter
height: contentHeight * 1.2
width: contentWidth * 1.2
background: Rectangle {
radius: parent.height / 2
color: colors.dark
}
height: contentHeight * 1.2
width: contentWidth * 1.2
background: Rectangle {
radius: parent.height / 2
color: colors.base
}
}

@ -1,7 +1,7 @@
import ".."
MatrixText {
text: qsTr("unimplemented event: ") + model.data.typeString
width: parent ? parent.width : undefined
color: inactiveColors.text
text: qsTr("unimplemented event: ") + model.data.typeString
width: parent ? parent.width : undefined
color: inactiveColors.text
}

@ -1,167 +1,216 @@
import QtMultimedia 5.6
import QtQuick 2.6
import QtQuick.Layouts 1.2
import QtQuick.Controls 2.1
import QtMultimedia 5.6
import QtQuick.Layouts 1.2
import im.nheko 1.0
Rectangle {
id: bg
radius: 10
color: colors.base
height: content.height + 24
width: parent ? parent.width : undefined
Column {
id: content
width: parent.width - 24
anchors.centerIn: parent
Rectangle {
id: videoContainer
visible: model.data.type == MtxEvent.VideoMessage
width: Math.min(parent.width, model.data.width ? model.data.width : 400) // some media has 0 as size...
height: width*model.data.proportionalHeight
Image {
anchors.fill: parent
source: model.data.thumbnailUrl.replace("mxc://", "image://MxcImage/")
asynchronous: true
fillMode: Image.PreserveAspectFit
VideoOutput {
anchors.fill: parent
fillMode: VideoOutput.PreserveAspectFit
source: media
}
}
}
RowLayout {
width: parent.width
Text {
id: positionText
text: "--:--:--"
color: colors.text
}
Slider {
Layout.fillWidth: true
id: progress
value: media.position
from: 0
to: media.duration
onMoved: media.seek(value)
//indeterminate: true
function updatePositionTexts() {
function formatTime(date) {
var hh = date.getUTCHours();
var mm = date.getUTCMinutes();
var ss = date.getSeconds();
if (hh < 10) {hh = "0"+hh;}
if (mm < 10) {mm = "0"+mm;}
if (ss < 10) {ss = "0"+ss;}
return hh+":"+mm+":"+ss;
}
positionText.text = formatTime(new Date(media.position))
durationText.text = formatTime(new Date(media.duration))
}
onValueChanged: updatePositionTexts()
palette: colors
}
Text {
id: durationText
text: "--:--:--"
color: colors.text
}
}
RowLayout {
width: parent.width
spacing: 15
Rectangle {
id: button
color: colors.window
radius: 22
height: 44
width: 44
Image {
id: img
anchors.centerIn: parent
z: 3
source: "image://colorimage/:/icons/icons/ui/arrow-pointing-down.png?"+colors.text
fillMode: Image.Pad
}
MouseArea {
anchors.fill: parent
onClicked: {
switch (button.state) {
case "": timelineManager.timeline.cacheMedia(model.data.id); break;
case "stopped":
media.play(); console.log("play");
button.state = "playing"
break
case "playing":
media.pause(); console.log("pause");
button.state = "stopped"
break
}
}
cursorShape: Qt.PointingHandCursor
}
MediaPlayer {
id: media
onError: console.log(errorString)
onStatusChanged: if(status == MediaPlayer.Loaded) progress.updatePositionTexts()
onStopped: button.state = "stopped"
}
Connections {
target: timelineManager.timeline
onMediaCached: {
if (mxcUrl == model.data.url) {
media.source = "file://" + cacheUrl
button.state = "stopped"
console.log("media loaded: " + mxcUrl + " at " + cacheUrl)
}
console.log("media cached: " + mxcUrl + " at " + cacheUrl)
}
}
states: [
State {
name: "stopped"
PropertyChanges { target: img; source: "image://colorimage/:/icons/icons/ui/play-sign.png?"+colors.text }
},
State {
name: "playing"
PropertyChanges { target: img; source: "image://colorimage/:/icons/icons/ui/pause-symbol.png?"+colors.text }
}
]
}
ColumnLayout {
id: col
Text {
Layout.fillWidth: true
text: model.data.body
textFormat: Text.PlainText
elide: Text.ElideRight
color: colors.text
}
Text {
Layout.fillWidth: true
text: model.data.filesize
textFormat: Text.PlainText
elide: Text.ElideRight
color: colors.text
}
}
}
}
}
id: bg
radius: 10
color: colors.dark
height: Math.round(content.height + 24)
width: parent ? parent.width : undefined
Column {
id: content
width: parent.width - 24
anchors.centerIn: parent
Rectangle {
id: videoContainer
property double tempWidth: Math.min(parent ? parent.width : undefined, model.data.width < 1 ? 400 : model.data.width)
property double tempHeight: tempWidth * model.data.proportionalHeight
property double divisor: model.isReply ? 4 : 2
property bool tooHigh: tempHeight > timelineRoot.height / divisor
visible: model.data.type == MtxEvent.VideoMessage
height: tooHigh ? timelineRoot.height / divisor : tempHeight
width: tooHigh ? (timelineRoot.height / divisor) / model.data.proportionalHeight : tempWidth
Image {
anchors.fill: parent
source: model.data.thumbnailUrl.replace("mxc://", "image://MxcImage/")
asynchronous: true
fillMode: Image.PreserveAspectFit
VideoOutput {
anchors.fill: parent
fillMode: VideoOutput.PreserveAspectFit
source: media
}
}
}
RowLayout {
width: parent.width
Text {
id: positionText
text: "--:--:--"
color: colors.text
}
Slider {
id: progress
//indeterminate: true
function updatePositionTexts() {
function formatTime(date) {
var hh = date.getUTCHours();
var mm = date.getUTCMinutes();
var ss = date.getSeconds();
if (hh < 10)
hh = "0" + hh;
if (mm < 10)
mm = "0" + mm;
if (ss < 10)
ss = "0" + ss;
return hh + ":" + mm + ":" + ss;
}
positionText.text = formatTime(new Date(media.position));
durationText.text = formatTime(new Date(media.duration));
}
Layout.fillWidth: true
value: media.position
from: 0
to: media.duration
onMoved: media.seek(value)
onValueChanged: updatePositionTexts()
palette: colors
}
Text {
id: durationText
text: "--:--:--"
color: colors.text
}
}
RowLayout {
width: parent.width
spacing: 15
Rectangle {
id: button
color: colors.window
radius: 22
height: 44
width: 44
states: [
State {
name: "stopped"
PropertyChanges {
target: img
source: "image://colorimage/:/icons/icons/ui/play-sign.png?" + colors.text
}
},
State {
name: "playing"
PropertyChanges {
target: img
source: "image://colorimage/:/icons/icons/ui/pause-symbol.png?" + colors.text
}
}
]
Image {
id: img
anchors.centerIn: parent
z: 3
source: "image://colorimage/:/icons/icons/ui/arrow-pointing-down.png?" + colors.text
fillMode: Image.Pad
}
MouseArea {
anchors.fill: parent
onClicked: {
switch (button.state) {
case "":
TimelineManager.timeline.cacheMedia(model.data.id);
break;
case "stopped":
media.play();
console.log("play");
button.state = "playing";
break;
case "playing":
media.pause();
console.log("pause");
button.state = "stopped";
break;
}
}
cursorShape: Qt.PointingHandCursor
}
MediaPlayer {
id: media
onError: console.log(errorString)
onStatusChanged: {
if (status == MediaPlayer.Loaded)
progress.updatePositionTexts();
}
onStopped: button.state = "stopped"
}
Connections {
target: TimelineManager.timeline
onMediaCached: {
if (mxcUrl == model.data.url) {
media.source = "file://" + cacheUrl;
button.state = "stopped";
console.log("media loaded: " + mxcUrl + " at " + cacheUrl);
}
console.log("media cached: " + mxcUrl + " at " + cacheUrl);
}
}
}
ColumnLayout {
id: col
Text {
Layout.fillWidth: true
text: model.data.body
textFormat: Text.PlainText
elide: Text.ElideRight
color: colors.text
}
Text {
Layout.fillWidth: true
text: model.data.filesize
textFormat: Text.PlainText
elide: Text.ElideRight
color: colors.text
}
}
}
}
}

@ -2,56 +2,71 @@ import QtQuick 2.6
import QtQuick.Controls 2.3
import QtQuick.Layouts 1.2
import QtQuick.Window 2.2
import im.nheko 1.0
Item {
id: replyComponent
property alias modelData: reply.modelData
property color userColor: "red"
width: parent.width
height: replyContainer.height
MouseArea {
anchors.fill: parent
preventStealing: true
onClicked: chat.positionViewAtIndex(chat.model.idToIndex(modelData.id), ListView.Contain)
cursorShape: Qt.PointingHandCursor
}
Rectangle {
id: colorLine
anchors.top: replyContainer.top
anchors.bottom: replyContainer.bottom
width: 4
color: TimelineManager.userColor(reply.modelData.userId, colors.window)
}
Column {
id: replyContainer
anchors.left: colorLine.right
anchors.leftMargin: 4
width: parent.width - 8
Text {
id: userName
text: TimelineManager.escapeEmoji(reply.modelData.userName)
color: replyComponent.userColor
textFormat: Text.RichText
MouseArea {
anchors.fill: parent
onClicked: chat.model.openUserProfile(reply.modelData.userId)
cursorShape: Qt.PointingHandCursor
}
}
MessageDelegate {
id: reply
width: parent.width
isReply: true
}
}
Rectangle {
id: backgroundItem
z: -1
height: replyContainer.height
width: Math.min(Math.max(reply.implicitWidth, userName.implicitWidth) + 8 + 4, parent.width)
color: Qt.rgba(userColor.r, userColor.g, userColor.b, 0.2)
}
Rectangle {
id: replyComponent
property alias modelData: reply.modelData
property color userColor: "red"
width: parent.width
height: replyContainer.height
Rectangle {
id: colorLine
anchors.top: replyContainer.top
anchors.bottom: replyContainer.bottom
width: 4
color: timelineManager.userColor(reply.modelData.userId, colors.window)
}
Column {
id: replyContainer
anchors.left: colorLine.right
anchors.leftMargin: 4
width: parent.width - 8
Text {
id: userName
text: chat.model ? chat.model.escapeEmoji(reply.modelData.userName) : ""
color: replyComponent.userColor
textFormat: Text.RichText
MouseArea {
anchors.fill: parent
onClicked: chat.model.openUserProfile(reply.modelData.userId)
cursorShape: Qt.PointingHandCursor
}
}
MessageDelegate {
id: reply
width: parent.width
}
}
color: Qt.rgba(userColor.r, userColor.g, userColor.b, 0.2)
MouseArea {
anchors.fill: parent
onClicked: chat.positionViewAtIndex(chat.model.idToIndex(timelineManager.replyingEvent), ListView.Contain)
cursorShape: Qt.PointingHandCursor
}
}

@ -1,7 +1,12 @@
import ".."
import im.nheko 1.0
MatrixText {
property string formatted: model.data.formattedBody
text: formatted.replace("<pre>", "<pre style='white-space: pre-wrap'>")
width: parent ? parent.width : undefined
property string formatted: model.data.formattedBody
text: "<style type=\"text/css\">a { color:" + colors.link + ";}</style>" + formatted.replace("<pre>", "<pre style='white-space: pre-wrap'>")
width: parent ? parent.width : undefined
height: isReply ? Math.round(Math.min(timelineRoot.height / 8, implicitHeight)) : undefined
clip: true
font.pointSize: (Settings.enlargeEmojiOnlyMessages && model.data.isOnlyEmoji > 0 && model.data.isOnlyEmoji < 4) ? Settings.fontSize * 3 : Settings.fontSize
}

@ -0,0 +1,46 @@
import QtQuick 2.3
import QtQuick.Controls 2.10
import QtQuick.Layouts 1.10
import im.nheko 1.0
Pane {
property string title: qsTr("Awaiting Confirmation")
ColumnLayout {
spacing: 16
Label {
id: content
Layout.maximumWidth: 400
Layout.fillHeight: true
Layout.fillWidth: true
wrapMode: Text.Wrap
text: qsTr("Waiting for other side to complete verification.")
color: colors.text
verticalAlignment: Text.AlignVCenter
}
BusyIndicator {
Layout.alignment: Qt.AlignHCenter
}
RowLayout {
Button {
Layout.alignment: Qt.AlignLeft
text: qsTr("Cancel")
onClicked: {
flow.cancel();
dialog.close();
}
}
Item {
Layout.fillWidth: true
}
}
}
}

@ -0,0 +1,144 @@
import QtQuick 2.10
import QtQuick.Controls 2.10
import QtQuick.Window 2.10
import im.nheko 1.0
ApplicationWindow {
id: dialog
property var flow
onClosing: TimelineManager.removeVerificationFlow(flow)
title: stack.currentItem.title
flags: Qt.Dialog
palette: colors
height: stack.implicitHeight
width: stack.implicitWidth
StackView {
id: stack
initialItem: newVerificationRequest
implicitWidth: currentItem.implicitWidth
implicitHeight: currentItem.implicitHeight
}
Component {
id: newVerificationRequest
NewVerificationRequest {
}
}
Component {
id: waiting
Waiting {
}
}
Component {
id: success
Success {
}
}
Component {
id: failed
Failed {
}
}
Component {
id: digitVerification
DigitVerification {
}
}
Component {
id: emojiVerification
EmojiVerification {
}
}
Item {
state: flow.state
states: [
State {
name: "PromptStartVerification"
StateChangeScript {
script: stack.replace(newVerificationRequest)
}
},
State {
name: "CompareEmoji"
StateChangeScript {
script: stack.replace(emojiVerification)
}
},
State {
name: "CompareNumber"
StateChangeScript {
script: stack.replace(digitVerification)
}
},
State {
name: "WaitingForKeys"
StateChangeScript {
script: stack.replace(waiting)
}
},
State {
name: "WaitingForOtherToAccept"
StateChangeScript {
script: stack.replace(waiting)
}
},
State {
name: "WaitingForMac"
StateChangeScript {
script: stack.replace(waiting)
}
},
State {
name: "Success"
StateChangeScript {
script: stack.replace(success)
}
},
State {
name: "Failed"
StateChangeScript {
script: stack.replace(failed)
}
}
]
}
}

@ -0,0 +1,69 @@
import QtQuick 2.3
import QtQuick.Controls 2.10
import QtQuick.Layouts 1.10
import im.nheko 1.0
Pane {
property string title: qsTr("Verification Code")
ColumnLayout {
spacing: 16
Label {
Layout.maximumWidth: 400
Layout.fillHeight: true
Layout.fillWidth: true
wrapMode: Text.Wrap
text: qsTr("Please verify the following digits. You should see the same numbers on both sides. If they differ, please press 'They do not match!' to abort verification!")
color: colors.text
verticalAlignment: Text.AlignVCenter
}
RowLayout {
Layout.alignment: Qt.AlignHCenter
Label {
font.pixelSize: Qt.application.font.pixelSize * 2
text: flow.sasList[0]
color: colors.text
}
Label {
font.pixelSize: Qt.application.font.pixelSize * 2
text: flow.sasList[1]
color: colors.text
}
Label {
font.pixelSize: Qt.application.font.pixelSize * 2
text: flow.sasList[2]
color: colors.text
}
}
RowLayout {
Button {
Layout.alignment: Qt.AlignLeft
text: qsTr("They do not match!")
onClicked: {
flow.cancel();
dialog.close();
}
}
Item {
Layout.fillWidth: true
}
Button {
Layout.alignment: Qt.AlignRight
text: qsTr("They match!")
onClicked: flow.next()
}
}
}
}

@ -0,0 +1,33 @@
import QtQuick 2.3
import QtQuick.Controls 2.10
import QtQuick.Layouts 1.10
Rectangle {
color: "red"
implicitHeight: Qt.application.font.pixelSize * 4
implicitWidth: col.width
height: Qt.application.font.pixelSize * 4
width: col.width
ColumnLayout {
id: col
property var emoji: emojis.mapping[Math.floor(Math.random() * 64)]
anchors.bottom: parent.bottom
Label {
height: font.pixelSize * 2
Layout.alignment: Qt.AlignHCenter
text: col.emoji.emoji
font.pixelSize: Qt.application.font.pixelSize * 2
}
Label {
Layout.alignment: Qt.AlignHCenter | Qt.AlignBottom
text: col.emoji.description
}
}
}

@ -0,0 +1,414 @@
import QtQuick 2.3
import QtQuick.Controls 2.10
import QtQuick.Layouts 1.10
import im.nheko 1.0
Pane {
property string title: qsTr("Verification Code")
ColumnLayout {
spacing: 16
Label {
Layout.maximumWidth: 400
Layout.fillHeight: true
Layout.fillWidth: true
wrapMode: Text.Wrap
text: qsTr("Please verify the following emoji. You should see the same emoji on both sides. If they differ, please press 'They do not match!' to abort verification!")
color: colors.text
verticalAlignment: Text.AlignVCenter
}
RowLayout {
id: emojis
property var mapping: [{
"number": 0,
"emoji": "🐶",
"description": "Dog",
"unicode": "U+1F436"
}, {
"number": 1,
"emoji": "🐱",
"description": "Cat",
"unicode": "U+1F431"
}, {
"number": 2,
"emoji": "🦁",
"description": "Lion",
"unicode": "U+1F981"
}, {
"number": 3,
"emoji": "🐎",
"description": "Horse",
"unicode": "U+1F40E"
}, {
"number": 4,
"emoji": "🦄",
"description": "Unicorn",
"unicode": "U+1F984"
}, {
"number": 5,
"emoji": "🐷",
"description": "Pig",
"unicode": "U+1F437"
}, {
"number": 6,
"emoji": "🐘",
"description": "Elephant",
"unicode": "U+1F418"
}, {
"number": 7,
"emoji": "🐰",
"description": "Rabbit",
"unicode": "U+1F430"
}, {
"number": 8,
"emoji": "🐼",
"description": "Panda",
"unicode": "U+1F43C"
}, {
"number": 9,
"emoji": "🐓",
"description": "Rooster",
"unicode": "U+1F413"
}, {
"number": 10,
"emoji": "🐧",
"description": "Penguin",
"unicode": "U+1F427"
}, {
"number": 11,
"emoji": "🐢",
"description": "Turtle",
"unicode": "U+1F422"
}, {
"number": 12,
"emoji": "🐟",
"description": "Fish",
"unicode": "U+1F41F"
}, {
"number": 13,
"emoji": "🐙",
"description": "Octopus",
"unicode": "U+1F419"
}, {
"number": 14,
"emoji": "🦋",
"description": "Butterfly",
"unicode": "U+1F98B"
}, {
"number": 15,
"emoji": "🌷",
"description": "Flower",
"unicode": "U+1F337"
}, {
"number": 16,
"emoji": "🌳",
"description": "Tree",
"unicode": "U+1F333"
}, {
"number": 17,
"emoji": "🌵",
"description": "Cactus",
"unicode": "U+1F335"
}, {
"number": 18,
"emoji": "🍄",
"description": "Mushroom",
"unicode": "U+1F344"
}, {
"number": 19,
"emoji": "🌏",
"description": "Globe",
"unicode": "U+1F30F"
}, {
"number": 20,
"emoji": "🌙",
"description": "Moon",
"unicode": "U+1F319"
}, {
"number": 21,
"emoji": "☁",
"description": "Cloud",
"unicode": "U+2601U+FE0F"
}, {
"number": 22,
"emoji": "🔥",
"description": "Fire",
"unicode": "U+1F525"
}, {
"number": 23,
"emoji": "🍌",
"description": "Banana",
"unicode": "U+1F34C"
}, {
"number": 24,
"emoji": "🍎",
"description": "Apple",
"unicode": "U+1F34E"
}, {
"number": 25,
"emoji": "🍓",
"description": "Strawberry",
"unicode": "U+1F353"
}, {
"number": 26,
"emoji": "🌽",
"description": "Corn",
"unicode": "U+1F33D"
}, {
"number": 27,
"emoji": "🍕",
"description": "Pizza",
"unicode": "U+1F355"
}, {
"number": 28,
"emoji": "🎂",
"description": "Cake",
"unicode": "U+1F382"
}, {
"number": 29,
"emoji": "❤",
"description": "Heart",
"unicode": "U+2764U+FE0F"
}, {
"number": 30,
"emoji": "😀",
"description": "Smiley",
"unicode": "U+1F600"
}, {
"number": 31,
"emoji": "🤖",
"description": "Robot",
"unicode": "U+1F916"
}, {
"number": 32,
"emoji": "🎩",
"description": "Hat",
"unicode": "U+1F3A9"
}, {
"number": 33,
"emoji": "👓",
"description": "Glasses",
"unicode": "U+1F453"
}, {
"number": 34,
"emoji": "🔧",
"description": "Spanner",
"unicode": "U+1F527"
}, {
"number": 35,
"emoji": "🎅",
"description": "Santa",
"unicode": "U+1F385"
}, {
"number": 36,
"emoji": "👍",
"description": "Thumbs Up",
"unicode": "U+1F44D"
}, {
"number": 37,
"emoji": "☂",
"description": "Umbrella",
"unicode": "U+2602U+FE0F"
}, {
"number": 38,
"emoji": "⌛",
"description": "Hourglass",
"unicode": "U+231B"
}, {
"number": 39,
"emoji": "⏰",
"description": "Clock",
"unicode": "U+23F0"
}, {
"number": 40,
"emoji": "🎁",
"description": "Gift",
"unicode": "U+1F381"
}, {
"number": 41,
"emoji": "💡",
"description": "Light Bulb",
"unicode": "U+1F4A1"
}, {
"number": 42,
"emoji": "📕",
"description": "Book",
"unicode": "U+1F4D5"
}, {
"number": 43,
"emoji": "✏",
"description": "Pencil",
"unicode": "U+270FU+FE0F"
}, {
"number": 44,
"emoji": "📎",
"description": "Paperclip",
"unicode": "U+1F4CE"
}, {
"number": 45,
"emoji": "✂",
"description": "Scissors",
"unicode": "U+2702U+FE0F"
}, {
"number": 46,
"emoji": "🔒",
"description": "Lock",
"unicode": "U+1F512"
}, {
"number": 47,
"emoji": "🔑",
"description": "Key",
"unicode": "U+1F511"
}, {
"number": 48,
"emoji": "🔨",
"description": "Hammer",
"unicode": "U+1F528"
}, {
"number": 49,
"emoji": "☎",
"description": "Telephone",
"unicode": "U+260EU+FE0F"
}, {
"number": 50,
"emoji": "🏁",
"description": "Flag",
"unicode": "U+1F3C1"
}, {
"number": 51,
"emoji": "🚂",
"description": "Train",
"unicode": "U+1F682"
}, {
"number": 52,
"emoji": "🚲",
"description": "Bicycle",
"unicode": "U+1F6B2"
}, {
"number": 53,
"emoji": "✈",
"description": "Aeroplane",
"unicode": "U+2708U+FE0F"
}, {
"number": 54,
"emoji": "🚀",
"description": "Rocket",
"unicode": "U+1F680"
}, {
"number": 55,
"emoji": "🏆",
"description": "Trophy",
"unicode": "U+1F3C6"
}, {
"number": 56,
"emoji": "⚽",
"description": "Ball",
"unicode": "U+26BD"
}, {
"number": 57,
"emoji": "🎸",
"description": "Guitar",
"unicode": "U+1F3B8"
}, {
"number": 58,
"emoji": "🎺",
"description": "Trumpet",
"unicode": "U+1F3BA"
}, {
"number": 59,
"emoji": "🔔",
"description": "Bell",
"unicode": "U+1F514"
}, {
"number": 60,
"emoji": "⚓",
"description": "Anchor",
"unicode": "U+2693"
}, {
"number": 61,
"emoji": "🎧",
"description": "Headphones",
"unicode": "U+1F3A7"
}, {
"number": 62,
"emoji": "📁",
"description": "Folder",
"unicode": "U+1F4C1"
}, {
"number": 63,
"emoji": "📌",
"description": "Pin",
"unicode": "U+1F4CC"
}]
Layout.alignment: Qt.AlignHCenter
Repeater {
id: repeater
model: 7
delegate: Rectangle {
color: "transparent"
implicitHeight: Qt.application.font.pixelSize * 8
implicitWidth: col.width
ColumnLayout {
id: col
property var emoji: emojis.mapping[flow.sasList[index]]
Layout.fillWidth: true
anchors.bottom: parent.bottom
Label {
//height: font.pixelSize * 2
Layout.alignment: Qt.AlignHCenter
text: col.emoji.emoji
font.pixelSize: Qt.application.font.pixelSize * 2
font.family: Settings.emojiFont
color: colors.text
}
Label {
Layout.alignment: Qt.AlignHCenter | Qt.AlignBottom
text: col.emoji.description
color: colors.text
}
}
}
}
}
RowLayout {
Button {
Layout.alignment: Qt.AlignLeft
text: qsTr("They do not match!")
onClicked: {
flow.cancel();
dialog.close();
}
}
Item {
Layout.fillWidth: true
}
Button {
Layout.alignment: Qt.AlignRight
text: qsTr("They match!")
onClicked: flow.next()
}
}
}
}

@ -0,0 +1,56 @@
import QtQuick 2.3
import QtQuick.Controls 2.10
import QtQuick.Layouts 1.10
import im.nheko 1.0
Pane {
property string title: qsTr("Verification failed")
ColumnLayout {
spacing: 16
Text {
id: content
Layout.maximumWidth: 400
Layout.fillHeight: true
Layout.fillWidth: true
wrapMode: Text.Wrap
text: {
switch (flow.error) {
case DeviceVerificationFlow.UnknownMethod:
return qsTr("Other client does not support our verification protocol.");
case DeviceVerificationFlow.MismatchedCommitment:
case DeviceVerificationFlow.MismatchedSAS:
case DeviceVerificationFlow.KeyMismatch:
return qsTr("Key mismatch detected!");
case DeviceVerificationFlow.Timeout:
return qsTr("Device verification timed out.");
case DeviceVerificationFlow.User:
return qsTr("Other party canceled the verification.");
case DeviceVerificationFlow.OutOfOrder:
return qsTr("Device verification timed out.");
default:
return "Unknown verification error.";
}
}
color: colors.text
verticalAlignment: Text.AlignVCenter
}
RowLayout {
Item {
Layout.fillWidth: true
}
Button {
Layout.alignment: Qt.AlignRight
text: qsTr("Close")
onClicked: dialog.close()
}
}
}
}

@ -0,0 +1,46 @@
import QtQuick 2.3
import QtQuick.Controls 2.10
import QtQuick.Layouts 1.10
import im.nheko 1.0
Pane {
property string title: flow.sender ? qsTr("Send Device Verification Request") : qsTr("Recieved Device Verification Request")
ColumnLayout {
spacing: 16
Label {
Layout.maximumWidth: 400
Layout.fillHeight: true
Layout.fillWidth: true
wrapMode: Text.Wrap
text: flow.sender ? qsTr("To ensure that no malicious user can eavesdrop on your encrypted communications, you can verify this device.") : qsTr("The device was requested to be verified")
color: colors.text
verticalAlignment: Text.AlignVCenter
}
RowLayout {
Button {
Layout.alignment: Qt.AlignLeft
text: flow.sender ? qsTr("Cancel") : qsTr("Deny")
onClicked: {
flow.cancel();
dialog.close();
}
}
Item {
Layout.fillWidth: true
}
Button {
Layout.alignment: Qt.AlignRight
text: flow.sender ? qsTr("Start verification") : qsTr("Accept")
onClicked: flow.next()
}
}
}
}

@ -0,0 +1,38 @@
import QtQuick 2.3
import QtQuick.Controls 2.10
import QtQuick.Layouts 1.10
Pane {
property string title: qsTr("Successful Verification")
ColumnLayout {
spacing: 16
Label {
id: content
Layout.maximumWidth: 400
Layout.fillHeight: true
Layout.fillWidth: true
wrapMode: Text.Wrap
text: qsTr("Verification successful! Both sides verified their devices!")
color: colors.text
verticalAlignment: Text.AlignVCenter
}
RowLayout {
Item {
Layout.fillWidth: true
}
Button {
Layout.alignment: Qt.AlignRight
text: qsTr("Close")
onClicked: dialog.close()
}
}
}
}

@ -0,0 +1,56 @@
import QtQuick 2.3
import QtQuick.Controls 2.10
import QtQuick.Layouts 1.10
import im.nheko 1.0
Pane {
property string title: qsTr("Waiting for other party")
ColumnLayout {
spacing: 16
Label {
id: content
Layout.maximumWidth: 400
Layout.fillHeight: true
Layout.fillWidth: true
wrapMode: Text.Wrap
text: {
switch (flow.state) {
case "WaitingForOtherToAccept":
return qsTr("Waiting for other side to accept the verification request.");
case "WaitingForKeys":
return qsTr("Waiting for other side to continue the verification request.");
case "WaitingForMac":
return qsTr("Waiting for other side to complete the verification request.");
}
}
color: colors.text
verticalAlignment: Text.AlignVCenter
}
BusyIndicator {
Layout.alignment: Qt.AlignHCenter
palette: colors
}
RowLayout {
Button {
Layout.alignment: Qt.AlignLeft
text: qsTr("Cancel")
onClicked: {
flow.cancel();
dialog.close();
}
}
Item {
Layout.fillWidth: true
}
}
}
}

@ -0,0 +1,66 @@
[
{"number": 0, "emoji": "🐶", "description": "Dog", "unicode": "U+1F436"},
{"number": 1, "emoji": "🐱", "description": "Cat", "unicode": "U+1F431"},
{"number": 2, "emoji": "🦁", "description": "Lion", "unicode": "U+1F981"},
{"number": 3, "emoji": "🐎", "description": "Horse", "unicode": "U+1F40E"},
{"number": 4, "emoji": "🦄", "description": "Unicorn", "unicode": "U+1F984"},
{"number": 5, "emoji": "🐷", "description": "Pig", "unicode": "U+1F437"},
{"number": 6, "emoji": "🐘", "description": "Elephant", "unicode": "U+1F418"},
{"number": 7, "emoji": "🐰", "description": "Rabbit", "unicode": "U+1F430"},
{"number": 8, "emoji": "🐼", "description": "Panda", "unicode": "U+1F43C"},
{"number": 9, "emoji": "🐓", "description": "Rooster", "unicode": "U+1F413"},
{"number": 10, "emoji": "🐧", "description": "Penguin", "unicode": "U+1F427"},
{"number": 11, "emoji": "🐢", "description": "Turtle", "unicode": "U+1F422"},
{"number": 12, "emoji": "🐟", "description": "Fish", "unicode": "U+1F41F"},
{"number": 13, "emoji": "🐙", "description": "Octopus", "unicode": "U+1F419"},
{"number": 14, "emoji": "🦋", "description": "Butterfly", "unicode": "U+1F98B"},
{"number": 15, "emoji": "🌷", "description": "Flower", "unicode": "U+1F337"},
{"number": 16, "emoji": "🌳", "description": "Tree", "unicode": "U+1F333"},
{"number": 17, "emoji": "🌵", "description": "Cactus", "unicode": "U+1F335"},
{"number": 18, "emoji": "🍄", "description": "Mushroom", "unicode": "U+1F344"},
{"number": 19, "emoji": "🌏", "description": "Globe", "unicode": "U+1F30F"},
{"number": 20, "emoji": "🌙", "description": "Moon", "unicode": "U+1F319"},
{"number": 21, "emoji": "☁", "description": "Cloud", "unicode": "U+2601U+FE0F"},
{"number": 22, "emoji": "🔥", "description": "Fire", "unicode": "U+1F525"},
{"number": 23, "emoji": "🍌", "description": "Banana", "unicode": "U+1F34C"},
{"number": 24, "emoji": "🍎", "description": "Apple", "unicode": "U+1F34E"},
{"number": 25, "emoji": "🍓", "description": "Strawberry", "unicode": "U+1F353"},
{"number": 26, "emoji": "🌽", "description": "Corn", "unicode": "U+1F33D"},
{"number": 27, "emoji": "🍕", "description": "Pizza", "unicode": "U+1F355"},
{"number": 28, "emoji": "🎂", "description": "Cake", "unicode": "U+1F382"},
{"number": 29, "emoji": "❤", "description": "Heart", "unicode": "U+2764U+FE0F"},
{"number": 30, "emoji": "😀", "description": "Smiley", "unicode": "U+1F600"},
{"number": 31, "emoji": "🤖", "description": "Robot", "unicode": "U+1F916"},
{"number": 32, "emoji": "🎩", "description": "Hat", "unicode": "U+1F3A9"},
{"number": 33, "emoji": "👓", "description": "Glasses", "unicode": "U+1F453"},
{"number": 34, "emoji": "🔧", "description": "Spanner", "unicode": "U+1F527"},
{"number": 35, "emoji": "🎅", "description": "Santa", "unicode": "U+1F385"},
{"number": 36, "emoji": "👍", "description": "Thumbs Up", "unicode": "U+1F44D"},
{"number": 37, "emoji": "☂", "description": "Umbrella", "unicode": "U+2602U+FE0F"},
{"number": 38, "emoji": "⌛", "description": "Hourglass", "unicode": "U+231B"},
{"number": 39, "emoji": "⏰", "description": "Clock", "unicode": "U+23F0"},
{"number": 40, "emoji": "🎁", "description": "Gift", "unicode": "U+1F381"},
{"number": 41, "emoji": "💡", "description": "Light Bulb", "unicode": "U+1F4A1"},
{"number": 42, "emoji": "📕", "description": "Book", "unicode": "U+1F4D5"},
{"number": 43, "emoji": "✏", "description": "Pencil", "unicode": "U+270FU+FE0F"},
{"number": 44, "emoji": "📎", "description": "Paperclip", "unicode": "U+1F4CE"},
{"number": 45, "emoji": "✂", "description": "Scissors", "unicode": "U+2702U+FE0F"},
{"number": 46, "emoji": "🔒", "description": "Lock", "unicode": "U+1F512"},
{"number": 47, "emoji": "🔑", "description": "Key", "unicode": "U+1F511"},
{"number": 48, "emoji": "🔨", "description": "Hammer", "unicode": "U+1F528"},
{"number": 49, "emoji": "☎", "description": "Telephone", "unicode": "U+260EU+FE0F"},
{"number": 50, "emoji": "🏁", "description": "Flag", "unicode": "U+1F3C1"},
{"number": 51, "emoji": "🚂", "description": "Train", "unicode": "U+1F682"},
{"number": 52, "emoji": "🚲", "description": "Bicycle", "unicode": "U+1F6B2"},
{"number": 53, "emoji": "✈", "description": "Aeroplane", "unicode": "U+2708U+FE0F"},
{"number": 54, "emoji": "🚀", "description": "Rocket", "unicode": "U+1F680"},
{"number": 55, "emoji": "🏆", "description": "Trophy", "unicode": "U+1F3C6"},
{"number": 56, "emoji": "⚽", "description": "Ball", "unicode": "U+26BD"},
{"number": 57, "emoji": "🎸", "description": "Guitar", "unicode": "U+1F3B8"},
{"number": 58, "emoji": "🎺", "description": "Trumpet", "unicode": "U+1F3BA"},
{"number": 59, "emoji": "🔔", "description": "Bell", "unicode": "U+1F514"},
{"number": 60, "emoji": "⚓", "description": "Anchor", "unicode": "U+2693"},
{"number": 61, "emoji": "🎧", "description": "Headphones", "unicode": "U+1F3A7"},
{"number": 62, "emoji": "📁", "description": "Folder", "unicode": "U+1F4C1"},
{"number": 63, "emoji": "📌", "description": "Pin", "unicode": "U+1F4CC"}
]

@ -0,0 +1,16 @@
import "../"
import QtQuick 2.10
import QtQuick.Controls 2.1
import im.nheko 1.0
import im.nheko.EmojiModel 1.0
ImageButton {
id: emojiButton
property var colors: currentActivePalette
property var emojiPicker
property string event_id
image: ":/icons/icons/ui/smile.png"
onClicked: emojiPicker.visible ? emojiPicker.close() : emojiPicker.show(emojiButton, event_id)
}

@ -0,0 +1,332 @@
import "../"
import QtGraphicalEffects 1.0
import QtQuick 2.9
import QtQuick.Controls 2.3
import QtQuick.Layouts 1.3
import im.nheko 1.0
import im.nheko.EmojiModel 1.0
Popup {
id: emojiPopup
property string event_id
property var colors
property alias model: gridView.model
property var textArea
property string emojiCategory: "people"
property real highlightHue: colors.highlight.hslHue
property real highlightSat: colors.highlight.hslSaturation
property real highlightLight: colors.highlight.hslLightness
function show(showAt, event_id) {
console.debug("Showing emojiPicker for " + event_id);
if (showAt) {
parent = showAt;
x = Math.round((showAt.width - width) / 2);
y = showAt.height;
}
emojiPopup.event_id = event_id;
open();
}
margins: 0
bottomPadding: 1
leftPadding: 1
rightPadding: 1
modal: true
focus: true
closePolicy: Popup.CloseOnEscape | Popup.CloseOnPressOutside
ColumnLayout {
id: columnView
anchors.fill: parent
spacing: 0
Layout.bottomMargin: 0
Layout.leftMargin: 3
Layout.rightMargin: 3
Layout.topMargin: 2
// emoji grid
GridView {
id: gridView
Layout.preferredHeight: emojiPopup.height
Layout.fillWidth: true
Layout.fillHeight: true
Layout.leftMargin: 4
cellWidth: 52
cellHeight: 52
boundsBehavior: Flickable.StopAtBounds
clip: true
currentIndex: -1 // prevent sorting from stealing focus
// Individual emoji
delegate: AbstractButton {
width: 48
height: 48
hoverEnabled: true
ToolTip.text: model.shortName
ToolTip.visible: hovered
// TODO: maybe add favorites at some point?
onClicked: {
console.debug("Picked " + model.unicode + "in response to " + emojiPopup.event_id);
emojiPopup.close();
TimelineManager.queueReactionMessage(emojiPopup.event_id, model.unicode);
}
// give the emoji a little oomf
DropShadow {
width: parent.width
height: parent.height
horizontalOffset: 3
verticalOffset: 3
radius: 8
samples: 17
color: "#80000000"
source: parent.contentItem
}
contentItem: Text {
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
font.family: Settings.emojiFont
font.pixelSize: 36
text: model.unicode
}
background: Rectangle {
anchors.fill: parent
color: hovered ? colors.highlight : 'transparent'
radius: 5
}
}
// Search field
header: TextField {
id: emojiSearch
anchors.left: parent.left
anchors.right: parent.right
anchors.rightMargin: emojiScroll.width + 4
placeholderText: qsTr("Search")
selectByMouse: true
rightPadding: clearSearch.width
onTextChanged: searchTimer.restart()
onVisibleChanged: {
if (visible)
forceActiveFocus();
}
Timer {
id: searchTimer
interval: 350 // tweak as needed?
onTriggered: {
emojiPopup.model.filter = emojiSearch.text;
emojiPopup.model.category = EmojiCategory.Search;
}
}
ToolButton {
id: clearSearch
visible: emojiSearch.text !== ''
icon.source: "image://colorimage/:/icons/icons/ui/round-remove-button.png?" + (clearSearch.hovered ? colors.highlight : colors.buttonText)
focusPolicy: Qt.NoFocus
onClicked: emojiSearch.clear()
anchors {
verticalCenter: parent.verticalCenter
right: parent.right
}
// clear the default hover effects.
background: Item {
}
}
}
ScrollBar.vertical: ScrollBar {
id: emojiScroll
}
}
// Separator
Rectangle {
Layout.fillWidth: true
Layout.preferredHeight: 1
color: emojiPopup.colors.dark
}
// Category picker row
RowLayout {
Layout.bottomMargin: 0
Layout.preferredHeight: 42
implicitHeight: 42
Layout.alignment: Qt.AlignHCenter | Qt.AlignBottom
// Display the normal categories
Repeater {
model: ListModel {
// TODO: Would like to get 'simple' icons for the categories
ListElement {
image: ":/icons/icons/emoji-categories/people.png"
category: EmojiCategory.People
}
ListElement {
image: ":/icons/icons/emoji-categories/nature.png"
category: EmojiCategory.Nature
}
ListElement {
image: ":/icons/icons/emoji-categories/foods.png"
category: EmojiCategory.Food
}
ListElement {
image: ":/icons/icons/emoji-categories/activity.png"
category: EmojiCategory.Activity
}
ListElement {
image: ":/icons/icons/emoji-categories/travel.png"
category: EmojiCategory.Travel
}
ListElement {
image: ":/icons/icons/emoji-categories/objects.png"
category: EmojiCategory.Objects
}
ListElement {
image: ":/icons/icons/emoji-categories/symbols.png"
category: EmojiCategory.Symbols
}
ListElement {
image: ":/icons/icons/emoji-categories/flags.png"
category: EmojiCategory.Flags
}
}
delegate: AbstractButton {
Layout.preferredWidth: 36
Layout.preferredHeight: 36
hoverEnabled: true
ToolTip.text: {
switch (model.category) {
case EmojiCategory.People:
return qsTr('People');
case EmojiCategory.Nature:
return qsTr('Nature');
case EmojiCategory.Food:
return qsTr('Food');
case EmojiCategory.Activity:
return qsTr('Activity');
case EmojiCategory.Travel:
return qsTr('Travel');
case EmojiCategory.Objects:
return qsTr('Objects');
case EmojiCategory.Symbols:
return qsTr('Symbols');
case EmojiCategory.Flags:
return qsTr('Flags');
}
}
ToolTip.visible: hovered
onClicked: {
emojiPopup.model.category = model.category;
}
MouseArea {
id: mouseArea
anchors.fill: parent
onPressed: mouse.accepted = false
cursorShape: Qt.PointingHandCursor
}
contentItem: Image {
horizontalAlignment: Image.AlignHCenter
verticalAlignment: Image.AlignVCenter
fillMode: Image.Pad
sourceSize.width: 32
sourceSize.height: 32
source: "image://colorimage/" + model.image + "?" + (hovered ? colors.highlight : colors.buttonText)
}
background: Rectangle {
anchors.fill: parent
color: emojiPopup.model.category === model.category ? Qt.hsla(highlightHue, highlightSat, highlightLight, 0.2) : 'transparent'
radius: 5
border.color: emojiPopup.model.category === model.category ? colors.highlight : 'transparent'
}
}
}
// Separator
Rectangle {
Layout.fillHeight: true
Layout.preferredWidth: 1
implicitWidth: 1
height: parent.height
color: emojiPopup.colors.dark
}
// Search Button is special
AbstractButton {
id: searchBtn
hoverEnabled: true
Layout.alignment: Qt.AlignRight
Layout.bottomMargin: 0
ToolTip.text: qsTr("Search")
ToolTip.visible: hovered
onClicked: {
// clear any filters
emojiPopup.model.category = EmojiCategory.Search;
gridView.positionViewAtBeginning();
emojiSearch.forceActiveFocus();
}
Layout.preferredWidth: 36
Layout.preferredHeight: 36
implicitWidth: 36
implicitHeight: 36
MouseArea {
id: mouseArea
anchors.fill: parent
onPressed: mouse.accepted = false
cursorShape: Qt.PointingHandCursor
}
contentItem: Image {
anchors.right: parent.right
horizontalAlignment: Image.AlignHCenter
verticalAlignment: Image.AlignVCenter
sourceSize.width: 32
sourceSize.height: 32
fillMode: Image.Pad
smooth: true
source: "image://colorimage/:/icons/icons/ui/search.png?" + (parent.hovered ? colors.highlight : colors.buttonText)
}
}
}
}
}

@ -0,0 +1,2 @@
[Controls]
FallbackStyle=Fusion

@ -14,12 +14,16 @@
<file>icons/ui/double-tick-indicator@2x.png</file>
<file>icons/ui/lock.png</file>
<file>icons/ui/lock@2x.png</file>
<file>icons/ui/unlock.png</file>
<file>icons/ui/unlock@2x.png</file>
<file>icons/ui/clock.png</file>
<file>icons/ui/clock@2x.png</file>
<file>icons/ui/checkmark.png</file>
<file>icons/ui/checkmark@2x.png</file>
<file>icons/ui/cursor.png</file>
<file>icons/ui/cursor@2x.png</file>
<file>icons/ui/search.png</file>
<file>icons/ui/search@2x.png</file>
<file>icons/ui/settings.png</file>
<file>icons/ui/settings@2x.png</file>
<file>icons/ui/smile.png</file>
@ -66,6 +70,11 @@
<file>icons/ui/mail-reply.png</file>
<file>icons/ui/place-call.png</file>
<file>icons/ui/end-call.png</file>
<file>icons/ui/microphone-mute.png</file>
<file>icons/ui/microphone-unmute.png</file>
<file>icons/emoji-categories/people.png</file>
<file>icons/emoji-categories/people@2x.png</file>
<file>icons/emoji-categories/nature.png</file>
@ -109,13 +118,21 @@
<file>styles/nheko-dark.qss</file>
</qresource>
<qresource prefix="/">
<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>
<file>qml/StatusIndicator.qml</file>
<file>qml/EncryptionIndicator.qml</file>
<file>qml/Reactions.qml</file>
<file>qml/ScrollHelper.qml</file>
<file>qml/TimelineRow.qml</file>
<file>qml/emoji/EmojiButton.qml</file>
<file>qml/emoji/EmojiPicker.qml</file>
<file>qml/UserProfile.qml</file>
<file>qml/delegates/MessageDelegate.qml</file>
<file>qml/delegates/TextMessage.qml</file>
<file>qml/delegates/NoticeMessage.qml</file>
@ -125,5 +142,17 @@
<file>qml/delegates/Pill.qml</file>
<file>qml/delegates/Placeholder.qml</file>
<file>qml/delegates/Reply.qml</file>
<file>qml/device-verification/Waiting.qml</file>
<file>qml/device-verification/DeviceVerification.qml</file>
<file>qml/device-verification/DigitVerification.qml</file>
<file>qml/device-verification/EmojiVerification.qml</file>
<file>qml/device-verification/NewVerificationRequest.qml</file>
<file>qml/device-verification/Failed.qml</file>
<file>qml/device-verification/Success.qml</file>
</qresource>
<qresource prefix="/media">
<file>media/ring.ogg</file>
<file>media/ringback.ogg</file>
<file>media/callend.ogg</file>
</qresource>
</RCC>

@ -3,6 +3,10 @@ QLabel {
color: #caccd1;
}
TextLabel::a {
color: #38a3d8;
}
QuickSwitcher,
ReplyPopup,
SuggestionsPopup,
@ -27,7 +31,6 @@ UserMentionsWidget > * {
}
QLineEdit,
QListWidget,
WelcomePage,
LoginPage,
RegisterPage,
@ -52,11 +55,18 @@ dialogs--JoinRoom > QLineEdit {
color: #caccd1;
}
emoji--Panel QWidget { border: none; }
emoji--Panel QScrollBar:vertical { width: 0px; margin: 0px; }
emoji--Panel QScrollBar::handle:vertical { min-height: 30px; }
emoji--Category,
emoji--Category > * {
background-color: #2d3139;
color: #caccd1;
color: #727274;
}
emoji--Category QLabel {
margin: 20px 0 20px 8px;
}
TimelineItem {
@ -115,7 +125,7 @@ RoomInfoListItem,
UserMentionsWidget {
qproperty-mentionedColor: #a82353;
qproperty-highlightedBackgroundColor: #4d84c7;
qproperty-hoverBackgroundColor: rgba(230, 230, 230, 30);
qproperty-hoverBackgroundColor: rgb(67, 70, 77);
qproperty-backgroundColor: #2d3139;
qproperty-titleColor: #e4e5e8;
@ -134,20 +144,22 @@ UserMentionsWidget {
qproperty-highlightedTimestampColor: #e7e7e9;
qproperty-hoverTimestampColor: #f4f5f8;
qproperty-avatarBgColor: #202228;
qproperty-avatarFgColor: white;
qproperty-bubbleFgColor: white;
qproperty-bubbleBgColor: #4d84c7;
}
RoomInfoListItem > Avatar {
qproperty-backgroundColor: #202228;
qproperty-textColor: white;
}
CommunitiesListItem {
qproperty-highlightedBackgroundColor: #4d84c7;
qproperty-hoverBackgroundColor: rgba(230, 230, 230, 30);
qproperty-hoverBackgroundColor: rgb(67, 70, 77);
qproperty-backgroundColor: #2d3139;
qproperty-avatarBgColor: #202228;
qproperty-avatarFgColor: white;
qproperty-avatarFgColor: palette(window);
}
LoadingIndicator {
@ -164,14 +176,6 @@ UserInfoWidget {
border-bottom: 1px solid #202228;
}
#UserSettingScrollWidget > QComboBox {
color: #202228;
}
#UserSettingScrollWidget > QComboBox {
color: #202228;
}
Avatar {
qproperty-textColor: white;
qproperty-backgroundColor: #2d3139;
@ -187,7 +191,7 @@ TopSection {
}
emoji--Category {
qproperty-hoverBackgroundColor: rgba(230, 230, 230, 30);
qproperty-hoverBackgroundColor: rgb(67, 70, 77);
}
FloatingButton {
@ -201,11 +205,6 @@ TextField {
qproperty-labelColor: #caccd1;
}
ScrollBar {
qproperty-handleColor: #2d3139;
qproperty-backgroundColor: #202228;
}
SideBarActions,
TopRoomBar
{
@ -233,7 +232,14 @@ Toggle {
qproperty-trackColor: rgb(240, 240, 240);
}
QListWidget {
color: #caccd1;
background-color: #202228;
}
SnackBar {
qproperty-textColor: #caccd1;
qproperty-bgColor: #202228;
}
QSplitter::handle { image: none; }

@ -3,6 +3,11 @@ QLabel {
color: #333;
}
TextLabel::a {
color: #0077b5;
}
QuickSwitcher,
ReplyPopup,
SuggestionsPopup,
@ -88,7 +93,7 @@ RaisedButton {
RoomInfoListItem {
qproperty-mentionedColor: #a82353;
qproperty-highlightedBackgroundColor: #38A3D8;
qproperty-hoverBackgroundColor: rgba(200, 200, 200, 40);
qproperty-hoverBackgroundColor: rgb(70, 77, 93);
qproperty-hoverTitleColor: #f2f5f8;
qproperty-hoverSubtitleColor: white;
qproperty-backgroundColor: #f2f5f8;
@ -106,16 +111,18 @@ RoomInfoListItem {
qproperty-highlightedTimestampColor: #f4f4f5;
qproperty-hoverTimestampColor: white;
qproperty-avatarBgColor: #eee;
qproperty-avatarFgColor: black;
qproperty-bubbleFgColor: white;
qproperty-bubbleBgColor: #38A3D8;
}
RoomInfoListItem > Avatar {
qproperty-backgroundColor: #eee;
qproperty-textColor: black;
}
CommunitiesListItem {
qproperty-highlightedBackgroundColor: #38A3D8;
qproperty-hoverBackgroundColor: rgba(200, 200, 200, 40);
qproperty-hoverBackgroundColor: rgb(70, 77, 93);
qproperty-backgroundColor: #f2f5f8;
qproperty-avatarBgColor: #eee;
@ -176,6 +183,8 @@ TopSection {
WelcomePage,
LoginPage,
QComboBox,
QPushButton,
RegisterPage {
background-color: white;
color: #333;
@ -187,8 +196,12 @@ emoji--Panel > * {
color: #333;
}
emoji--Panel QWidget { border: none; }
emoji--Panel QScrollBar:vertical { width: 0px; margin: 0px; }
emoji--Panel QScrollBar::handle:vertical { min-height: 30px; }
emoji--Category {
qproperty-hoverBackgroundColor: rgba(200, 200, 200, 70);
qproperty-hoverBackgroundColor: rgb(70, 77, 93);
}
emoji--Category,
@ -197,6 +210,8 @@ emoji--Category > * {
color: #ccc;
}
emoji--Category QLabel { margin: 20px 0 20px 8px; }
FloatingButton {
qproperty-backgroundColor: #efefef;
qproperty-foregroundColor: black;
@ -208,16 +223,19 @@ TextField {
qproperty-labelColor: #333;
}
QListWidget,
TextInputWidget,
QTextEdit,
QLineEdit {
background-color: white;
color: #333;
}
TextInputWidget {
border: none;
border-top: 1px solid #dcdcdc;
}
ScrollBar {
qproperty-handleColor: #ccc;
qproperty-backgroundColor: #efefef;
}
SideBarActions {
border: none;
border-top: 1px solid #dcdcdc;
@ -239,3 +257,5 @@ SnackBar {
qproperty-textColor: white;
qproperty-bgColor: #495057;
}
QSplitter::handle { image: none; }

@ -70,7 +70,7 @@ FileItem {
}
RaisedButton {
qproperty-foregroundColor: palette(buttonText);
qproperty-foregroundColor: palette(button-text);
}
TextField {
@ -95,26 +95,29 @@ UserMentionsWidget {
qproperty-titleColor: palette(text);
qproperty-subtitleColor: palette(text);
qproperty-highlightedTitleColor: palette(highlightedtext);
qproperty-highlightedSubtitleColor: palette(highlightedtext);
qproperty-highlightedTitleColor: palette(highlighted-text);
qproperty-highlightedSubtitleColor: palette(highlighted-text);
qproperty-hoverTitleColor: palette(highlightedtext);
qproperty-hoverSubtitleColor: palette(highlightedtext);
qproperty-hoverTitleColor: palette(dark);
qproperty-hoverSubtitleColor: palette(dark);
qproperty-btnColor: palette(button);
qproperty-btnTextColor: palette(buttonText);
qproperty-btnColor: palette(dark);
qproperty-btnTextColor: palette(bright-text);
qproperty-timestampColor: palette(text);
qproperty-highlightedTimestampColor: palette(highlightedtext);
qproperty-hoverTimestampColor: palette(highlightedtext);
qproperty-avatarBgColor: palette(base);
qproperty-avatarFgColor: palette(text);
qproperty-highlightedTimestampColor: palette(highlighted-text);
qproperty-hoverTimestampColor: palette(dark);
qproperty-bubbleBgColor: palette(base);
qproperty-bubbleFgColor: palette(text);
}
RoomInfoListItem > Avatar {
qproperty-backgroundColor: palette(base);
qproperty-textColor: palette(text);
}
CommunitiesListItem {
qproperty-highlightedBackgroundColor: palette(highlight);
qproperty-hoverBackgroundColor: palette(light);
@ -138,6 +141,10 @@ emoji--Panel > * {
color: palette(text);
}
emoji--Panel QWidget { border: none; }
emoji--Panel QScrollBar:vertical { width: 0px; margin: 0px; }
emoji--Panel QScrollBar::handle:vertical { min-height: 30px; }
emoji--Category {
qproperty-hoverBackgroundColor: palette(highlight);
}
@ -148,6 +155,10 @@ emoji--Category > * {
color: palette(text);
}
emoji--Category QLabel {
margin: 20px 0 20px 8px;
}
FloatingButton {
qproperty-backgroundColor: palette(base);
qproperty-foregroundColor: palette(text);
@ -164,3 +175,5 @@ Toggle {
qproperty-inactiveColor: palette(mid);
qproperty-trackColor: palette(base);
}
QSplitter::handle { image: none; }

@ -14,8 +14,9 @@ class Emoji(object):
def generate_code(emojis, category):
tmpl = Template('''
const std::vector<Emoji> emoji::Provider::{{ category }} = {
// {{ category.capitalize() }}
{%- for e in emoji %}
Emoji{QString::fromUtf8("{{ e.code }}"), "{{ e.shortname }}"},
Emoji{QString::fromUtf8("{{ e.code }}"), "{{ e.shortname }}", emoji::EmojiCategory::{{ category.capitalize() }}},
{%- endfor %}
};
''')
@ -23,6 +24,19 @@ const std::vector<Emoji> emoji::Provider::{{ category }} = {
d = dict(category=category, emoji=emojis)
print(tmpl.render(d))
def generate_qml_list(**kwargs):
tmpl = Template('''
const QVector<Emoji> emoji::Provider::emoji = {
{%- for c in kwargs.items() %}
// {{ c[0].capitalize() }}
{%- for e in c[1] %}
Emoji{QString::fromUtf8("{{ e.code }}"), "{{ e.shortname }}", emoji::EmojiCategory::{{ c[0].capitalize() }}},
{%- endfor %}
{%- endfor %}
};
''')
d = dict(kwargs=kwargs)
print(tmpl.render(d))
if __name__ == '__main__':
if len(sys.argv) < 2:
@ -87,3 +101,4 @@ if __name__ == '__main__':
generate_code(objects, 'objects')
generate_code(symbols, 'symbols')
generate_code(flags, 'flags')
generate_qml_list(people=people, nature=nature, food=food, activity=activity, travel=travel, objects=objects, symbols=symbols, flags=flags)

@ -9,19 +9,35 @@ set -eu
INPUT=$1
OUTPUT=nheko
filename=$(basename -- "$1")
extension="${filename##*.}"
mkdir ${OUTPUT}.iconset
sips -z 16 16 "${INPUT}" --out ${OUTPUT}.iconset/icon_16x16.png
sips -z 32 32 "${INPUT}" --out ${OUTPUT}.iconset/icon_16x16@2x.png
sips -z 32 32 "${INPUT}" --out ${OUTPUT}.iconset/icon_32x32.png
sips -z 64 64 "${INPUT}" --out ${OUTPUT}.iconset/icon_32x32@2x.png
sips -z 128 128 "${INPUT}" --out ${OUTPUT}.iconset/icon_128x128.png
sips -z 256 256 "${INPUT}" --out ${OUTPUT}.iconset/icon_128x128@2x.png
sips -z 256 256 "${INPUT}" --out ${OUTPUT}.iconset/icon_256x256.png
sips -z 512 512 "${INPUT}" --out ${OUTPUT}.iconset/icon_256x256@2x.png
sips -z 512 512 "${INPUT}" --out ${OUTPUT}.iconset/icon_512x512.png
cp "${INPUT}" ${OUTPUT}.iconset/icon_512x512@2x.png
if [ extension = "svg" ]; then
rsvg-convert -h 16 "${INPUT}" > ${OUTPUT}.iconset/icon_16x16.png
rsvg-convert -h 32 "${INPUT}" > ${OUTPUT}.iconset/icon_16x16@2x.png
rsvg-convert -h 32 "${INPUT}" > ${OUTPUT}.iconset/icon_32x32.png
rsvg-convert -h 64 "${INPUT}" > ${OUTPUT}.iconset/icon_32x32@2x.png
rsvg-convert -h 128 "${INPUT}" > ${OUTPUT}.iconset/icon_128x128.png
rsvg-convert -h 256 "${INPUT}" > ${OUTPUT}.iconset/icon_128x128@2x.png
rsvg-convert -h 256 "${INPUT}" > ${OUTPUT}.iconset/icon_256x256.png
rsvg-convert -h 512 "${INPUT}" > ${OUTPUT}.iconset/icon_256x256@2x.png
rsvg-convert -h 512 "${INPUT}" > ${OUTPUT}.iconset/icon_512x512.png
rsvg-convert -h 1024 "${INPUT}" > ${OUTPUT}.iconset/icon_512x512@2x.png
else
sips -z 16 16 "${INPUT}" --out ${OUTPUT}.iconset/icon_16x16.png
sips -z 32 32 "${INPUT}" --out ${OUTPUT}.iconset/icon_16x16@2x.png
sips -z 32 32 "${INPUT}" --out ${OUTPUT}.iconset/icon_32x32.png
sips -z 64 64 "${INPUT}" --out ${OUTPUT}.iconset/icon_32x32@2x.png
sips -z 128 128 "${INPUT}" --out ${OUTPUT}.iconset/icon_128x128.png
sips -z 256 256 "${INPUT}" --out ${OUTPUT}.iconset/icon_128x128@2x.png
sips -z 256 256 "${INPUT}" --out ${OUTPUT}.iconset/icon_256x256.png
sips -z 512 512 "${INPUT}" --out ${OUTPUT}.iconset/icon_256x256@2x.png
sips -z 512 512 "${INPUT}" --out ${OUTPUT}.iconset/icon_512x512.png
cp "${INPUT}" ${OUTPUT}.iconset/icon_512x512@2x.png
fi
iconutil -c icns ${OUTPUT}.iconset

@ -24,6 +24,7 @@
#include "Cache.h"
#include "Logging.h"
#include "MatrixClient.h"
#include "Utils.h"
static QPixmapCache avatar_cache;
@ -33,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())
QPixmap pixmap;
if (avatarUrl.isEmpty()) {
callback(pixmap);
return;
}
QPixmap pixmap;
if (avatar_cache.find(cacheKey, &pixmap)) {
callback(pixmap);
return;
@ -44,7 +47,7 @@ resolve(const QString &avatarUrl, int size, QObject *receiver, AvatarCallback ca
auto data = cache::image(cacheKey);
if (!data.isNull()) {
pixmap.loadFromData(data);
pixmap = QPixmap::fromImage(utils::readImage(&data));
avatar_cache.insert(cacheKey, pixmap);
callback(pixmap);
return;
@ -54,9 +57,8 @@ resolve(const QString &avatarUrl, int size, QObject *receiver, AvatarCallback ca
QObject::connect(proxy.get(),
&AvatarProxy::avatarDownloaded,
receiver,
[callback, cacheKey](const QByteArray &data) {
QPixmap pm;
pm.loadFromData(data);
[callback, cacheKey](QByteArray data) {
QPixmap pm = QPixmap::fromImage(utils::readImage(&data));
avatar_cache.insert(cacheKey, pm);
callback(pm);
});
@ -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()));
});
}

@ -0,0 +1,38 @@
#include "BlurhashProvider.h"
#include <algorithm>
#include <QUrl>
#include "blurhash.hpp"
void
BlurhashResponse::run()
{
if (m_requestedSize.width() < 0 || m_requestedSize.height() < 0) {
m_error = QStringLiteral("Blurhash needs size request");
emit finished();
return;
}
if (m_requestedSize.width() == 0 || m_requestedSize.height() == 0) {
m_image = QImage(m_requestedSize, QImage::Format_RGB32);
m_image.fill(QColor(0, 0, 0));
emit finished();
return;
}
auto decoded = blurhash::decode(QUrl::fromPercentEncoding(m_id.toUtf8()).toStdString(),
m_requestedSize.width(),
m_requestedSize.height(),
4);
if (decoded.image.empty()) {
m_error = QStringLiteral("Failed decode!");
emit finished();
return;
}
QImage image(decoded.image.data(), decoded.width, decoded.height, QImage::Format_RGB32);
m_image = image.copy();
emit finished();
}

@ -0,0 +1,51 @@
#pragma once
#include <QQuickAsyncImageProvider>
#include <QQuickImageResponse>
#include <QImage>
#include <QThreadPool>
class BlurhashResponse
: public QQuickImageResponse
, public QRunnable
{
public:
BlurhashResponse(const QString &id, const QSize &requestedSize)
: m_id(id)
, m_requestedSize(requestedSize)
{
setAutoDelete(false);
}
QQuickTextureFactory *textureFactory() const override
{
return QQuickTextureFactory::textureFactoryForImage(m_image);
}
QString errorString() const override { return m_error; }
void run() override;
QString m_id, m_error;
QSize m_requestedSize;
QImage m_image;
};
class BlurhashProvider
: public QObject
, public QQuickAsyncImageProvider
{
Q_OBJECT
public slots:
QQuickImageResponse *requestImageResponse(const QString &id,
const QSize &requestedSize) override
{
BlurhashResponse *response = new BlurhashResponse(id, requestedSize);
pool.start(response);
return response;
}
private:
QThreadPool pool;
};

File diff suppressed because it is too large Load Diff

@ -54,6 +54,26 @@ insertDisplayName(const QString &room_id, const QString &user_id, const QString
void
insertAvatarUrl(const QString &room_id, const QString &user_id, const QString &avatar_url);
// presence
mtx::presence::PresenceState
presenceState(const std::string &user_id);
std::string
statusMessage(const std::string &user_id);
// user cache stores user keys
std::optional<UserKeyCache>
userKeys(const std::string &user_id);
void
updateUserKeys(const std::string &sync_token, const mtx::responses::QueryKeys &keyQuery);
// device & user verification cache
std::optional<VerificationStatus>
verificationStatus(const std::string &user_id);
void
markDeviceVerified(const std::string &user_id, const std::string &device);
void
markDeviceUnverified(const std::string &user_id, const std::string &device);
//! Load saved data for the display names & avatars.
void
populateMembers();
@ -111,10 +131,15 @@ removeRoom(const QString &roomid);
void
setup();
bool
isFormatValid();
//! returns if the format is current, older or newer
cache::CacheVersion
formatVersion();
//! set the format version to the current version
void
setCurrentFormat();
//! migrates db to the current format
bool
runMigrations();
std::map<QString, mtx::responses::Timeline>
roomMessages();
@ -154,21 +179,6 @@ using UserReceipts = std::multimap<uint64_t, std::string, std::greater<uint64_t>
UserReceipts
readReceipts(const QString &event_id, const QString &room_id);
//! Filter the events that have at least one read receipt.
std::vector<QString>
filterReadEvents(const QString &room_id,
const std::vector<QString> &event_ids,
const std::string &excluded_user);
//! Add event for which we are expecting some read receipts.
void
addPendingReceipt(const QString &room_id, const QString &event_id);
void
removePendingReceipt(lmdb::txn &txn, const std::string &room_id, const std::string &event_id);
void
notifyForReadReceipts(const std::string &room_id);
std::vector<QString>
pendingReceiptsEvents(lmdb::txn &txn, const std::string &room_id);
QByteArray
image(const QString &url);
QByteArray
@ -259,6 +269,8 @@ bool
outboundMegolmSessionExists(const std::string &room_id) noexcept;
void
updateOutboundMegolmSession(const std::string &room_id, int message_index);
void
dropOutboundMegolmSession(const std::string &room_id);
void
importSessionKeys(const mtx::crypto::ExportedSessionKeys &keys);

@ -65,3 +65,53 @@ struct OlmSessionStorage
std::mutex group_outbound_mtx;
std::mutex group_inbound_mtx;
};
//! Verification status of a single user
struct VerificationStatus
{
//! True, if the users master key is verified
bool user_verified = false;
//! List of all devices marked as verified
std::vector<std::string> verified_devices;
};
//! In memory cache of verification status
struct VerificationStorage
{
//! mapping of user to verification status
std::map<std::string, VerificationStatus> status;
std::mutex verification_storage_mtx;
};
// this will store the keys of the user with whom a encrypted room is shared with
struct UserKeyCache
{
//! Device id to device keys
std::map<std::string, mtx::crypto::DeviceKeys> device_keys;
//! corss signing keys
mtx::crypto::CrossSigningKeys master_keys, user_signing_keys, self_signing_keys;
//! Sync token when nheko last fetched the keys
std::string updated_at;
//! Sync token when the keys last changed. updated != last_changed means they are outdated.
std::string last_changed;
};
void
to_json(nlohmann::json &j, const UserKeyCache &info);
void
from_json(const nlohmann::json &j, UserKeyCache &info);
// the reason these are stored in a seperate cache rather than storing it in the user cache is
// UserKeyCache stores only keys of users with which encrypted room is shared
struct VerificationCache
{
//! list of verified device_ids with device-verification
std::vector<std::string> device_verified;
//! list of devices the user blocks
std::vector<std::string> device_blocked;
};
void
to_json(nlohmann::json &j, const VerificationCache &info);
void
from_json(const nlohmann::json &j, VerificationCache &info);

@ -8,6 +8,15 @@
#include <mtx/events/join_rules.hpp>
namespace cache {
enum class CacheVersion : int
{
Older = -1,
Current = 0,
Newer = 1,
};
}
struct RoomMember
{
QString user_id;
@ -39,7 +48,8 @@ struct DescInfo
QString event_id;
QString userid;
QString body;
QString timestamp;
QString descriptiveTime;
uint64_t timestamp;
QDateTime datetime;
};

@ -18,6 +18,7 @@
#pragma once
#include <limits>
#include <optional>
#include <QDateTime>
@ -38,9 +39,6 @@
#include "CacheCryptoStructs.h"
#include "CacheStructs.h"
int
numeric_key_comparison(const MDB_val *a, const MDB_val *b);
class Cache : public QObject
{
Q_OBJECT
@ -52,6 +50,27 @@ public:
static QString displayName(const QString &room_id, const QString &user_id);
static QString avatarUrl(const QString &room_id, const QString &user_id);
// presence
mtx::presence::PresenceState presenceState(const std::string &user_id);
std::string statusMessage(const std::string &user_id);
// user cache stores user keys
std::optional<UserKeyCache> userKeys(const std::string &user_id);
void updateUserKeys(const std::string &sync_token,
const mtx::responses::QueryKeys &keyQuery);
void markUserKeysOutOfDate(lmdb::txn &txn,
lmdb::dbi &db,
const std::vector<std::string> &user_ids,
const std::string &sync_token);
void deleteUserKeys(lmdb::txn &txn,
lmdb::dbi &db,
const std::vector<std::string> &user_ids);
// device & user verification cache
VerificationStatus verificationStatus(const std::string &user_id);
void markDeviceVerified(const std::string &user_id, const std::string &device);
void markDeviceUnverified(const std::string &user_id, const std::string &device);
static void removeDisplayName(const QString &room_id, const QString &user_id);
static void removeAvatarUrl(const QString &room_id, const QString &user_id);
@ -102,8 +121,9 @@ public:
void removeRoom(const std::string &roomid);
void setup();
bool isFormatValid();
cache::CacheVersion formatVersion();
void setCurrentFormat();
bool runMigrations();
std::map<QString, mtx::responses::Timeline> roomMessages();
@ -137,18 +157,6 @@ public:
using UserReceipts = std::multimap<uint64_t, std::string, std::greater<uint64_t>>;
UserReceipts readReceipts(const QString &event_id, const QString &room_id);
//! Filter the events that have at least one read receipt.
std::vector<QString> filterReadEvents(const QString &room_id,
const std::vector<QString> &event_ids,
const std::string &excluded_user);
//! Add event for which we are expecting some read receipts.
void addPendingReceipt(const QString &room_id, const QString &event_id);
void removePendingReceipt(lmdb::txn &txn,
const std::string &room_id,
const std::string &event_id);
void notifyForReadReceipts(const std::string &room_id);
std::vector<QString> pendingReceiptsEvents(lmdb::txn &txn, const std::string &room_id);
QByteArray image(const QString &url) const;
QByteArray image(lmdb::txn &txn, const std::string &url) const;
void saveImage(const std::string &url, const std::string &data);
@ -179,6 +187,47 @@ public:
//! Add all notifications containing a user mention to the db.
void saveTimelineMentions(const mtx::responses::Notifications &res);
//! retrieve events in timeline and related functions
struct Messages
{
mtx::responses::Timeline timeline;
uint64_t next_index;
bool end_of_cache = false;
};
Messages getTimelineMessages(lmdb::txn &txn,
const std::string &room_id,
uint64_t index = std::numeric_limits<uint64_t>::max(),
bool forward = false);
std::optional<mtx::events::collections::TimelineEvent> getEvent(
const std::string &room_id,
const std::string &event_id);
void storeEvent(const std::string &room_id,
const std::string &event_id,
const mtx::events::collections::TimelineEvent &event);
std::vector<std::string> relatedEvents(const std::string &room_id,
const std::string &event_id);
struct TimelineRange
{
uint64_t first, last;
};
std::optional<TimelineRange> getTimelineRange(const std::string &room_id);
std::optional<uint64_t> getTimelineIndex(const std::string &room_id,
std::string_view event_id);
std::optional<std::string> getTimelineEventId(const std::string &room_id, uint64_t index);
std::string previousBatchToken(const std::string &room_id);
uint64_t saveOldMessages(const std::string &room_id, const mtx::responses::Messages &res);
void savePendingMessage(const std::string &room_id,
const mtx::events::collections::TimelineEvent &message);
std::optional<mtx::events::collections::TimelineEvent> firstPendingMessage(
const std::string &room_id);
void removePendingStatus(const std::string &room_id, const std::string &txn_id);
//! clear timeline keeping only the latest batch
void clearTimeline(const std::string &room_id);
//! Remove old unused data.
void deleteOldMessages();
void deleteOldData() noexcept;
@ -201,6 +250,7 @@ public:
OutboundGroupSessionDataRef getOutboundMegolmSession(const std::string &room_id);
bool outboundMegolmSessionExists(const std::string &room_id) noexcept;
void updateOutboundMegolmSession(const std::string &room_id, int message_index);
void dropOutboundMegolmSession(const std::string &room_id);
void importSessionKeys(const mtx::crypto::ExportedSessionKeys &keys);
mtx::crypto::ExportedSessionKeys exportSessionKeys();
@ -229,6 +279,10 @@ public:
signals:
void newReadReceipts(const QString &room_id, const std::vector<QString> &event_ids);
void roomReadStatus(const std::map<QString, bool> &status);
void removeNotification(const QString &room_id, const QString &event_id);
void userKeysUpdate(const std::string &sync_token,
const mtx::responses::QueryKeys &keyQuery);
void verificationStatusChanged(const std::string &userid);
private:
//! Save an invited room.
@ -250,12 +304,19 @@ private:
QString getInviteRoomTopic(lmdb::txn &txn, lmdb::dbi &statesdb);
QString getInviteRoomAvatarUrl(lmdb::txn &txn, lmdb::dbi &statesdb, lmdb::dbi &membersdb);
std::string getLastEventId(lmdb::txn &txn, const std::string &room_id);
DescInfo getLastMessageInfo(lmdb::txn &txn, const std::string &room_id);
void saveTimelineMessages(lmdb::txn &txn,
const std::string &room_id,
const mtx::responses::Timeline &res);
mtx::responses::Timeline getTimelineMessages(lmdb::txn &txn, const std::string &room_id);
//! retrieve a specific event from account data
//! pass empty room_id for global account data
std::optional<mtx::events::collections::RoomAccountDataEvents>
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);
@ -386,6 +447,10 @@ private:
void saveInvites(lmdb::txn &txn,
const std::map<std::string, mtx::responses::InvitedRoom> &rooms);
void savePresence(
lmdb::txn &txn,
const std::vector<mtx::events::Event<mtx::events::presence::Presence>> &presenceUpdates);
//! Sends signals for the rooms that are removed.
void removeLeftRooms(lmdb::txn &txn,
const std::map<std::string, mtx::responses::LeftRoom> &rooms)
@ -403,13 +468,46 @@ private:
return lmdb::dbi::open(txn, "pending_receipts", MDB_CREATE);
}
lmdb::dbi getMessagesDb(lmdb::txn &txn, const std::string &room_id)
lmdb::dbi getEventsDb(lmdb::txn &txn, const std::string &room_id)
{
auto db =
lmdb::dbi::open(txn, std::string(room_id + "/messages").c_str(), MDB_CREATE);
lmdb::dbi_set_compare(txn, db, numeric_key_comparison);
return lmdb::dbi::open(txn, std::string(room_id + "/events").c_str(), MDB_CREATE);
}
return db;
lmdb::dbi getEventOrderDb(lmdb::txn &txn, const std::string &room_id)
{
return lmdb::dbi::open(
txn, std::string(room_id + "/event_order").c_str(), MDB_CREATE | MDB_INTEGERKEY);
}
// inverse of EventOrderDb
lmdb::dbi getEventToOrderDb(lmdb::txn &txn, const std::string &room_id)
{
return lmdb::dbi::open(
txn, std::string(room_id + "/event2order").c_str(), MDB_CREATE);
}
lmdb::dbi getMessageToOrderDb(lmdb::txn &txn, const std::string &room_id)
{
return lmdb::dbi::open(
txn, std::string(room_id + "/msg2order").c_str(), MDB_CREATE);
}
lmdb::dbi getOrderToMessageDb(lmdb::txn &txn, const std::string &room_id)
{
return lmdb::dbi::open(
txn, std::string(room_id + "/order2msg").c_str(), MDB_CREATE | MDB_INTEGERKEY);
}
lmdb::dbi getPendingMessagesDb(lmdb::txn &txn, const std::string &room_id)
{
return lmdb::dbi::open(
txn, std::string(room_id + "/pending").c_str(), MDB_CREATE | MDB_INTEGERKEY);
}
lmdb::dbi getRelationsDb(lmdb::txn &txn, const std::string &room_id)
{
return lmdb::dbi::open(
txn, std::string(room_id + "/related").c_str(), MDB_CREATE | MDB_DUPSORT);
}
lmdb::dbi getInviteStatesDb(lmdb::txn &txn, const std::string &room_id)
@ -429,6 +527,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);
@ -439,6 +543,21 @@ private:
return lmdb::dbi::open(txn, std::string(room_id + "/mentions").c_str(), MDB_CREATE);
}
lmdb::dbi getPresenceDb(lmdb::txn &txn)
{
return lmdb::dbi::open(txn, "presence", MDB_CREATE);
}
lmdb::dbi getUserKeysDb(lmdb::txn &txn)
{
return lmdb::dbi::open(txn, "user_key", MDB_CREATE);
}
lmdb::dbi getVerificationDb(lmdb::txn &txn)
{
return lmdb::dbi::open(txn, "verified", MDB_CREATE);
}
//! Retrieves or creates the database that stores the open OLM sessions between our device
//! and the given curve25519 key which represents another device.
//!
@ -457,6 +576,8 @@ private:
return QString::fromStdString(event.state_key);
}
std::optional<VerificationCache> verificationCache(const std::string &user_id);
void setNextBatchToken(lmdb::txn &txn, const std::string &token);
void setNextBatchToken(lmdb::txn &txn, const QString &token);
@ -481,6 +602,7 @@ private:
static QHash<QString, QString> AvatarUrls;
OlmSessionStorage session_storage;
VerificationStorage verification_storage;
};
namespace cache {

@ -0,0 +1,457 @@
#include <algorithm>
#include <cctype>
#include <chrono>
#include <cstdint>
#include <QMediaPlaylist>
#include <QUrl>
#include "Cache.h"
#include "CallManager.h"
#include "ChatPage.h"
#include "Logging.h"
#include "MainWindow.h"
#include "MatrixClient.h"
#include "UserSettingsPage.h"
#include "WebRTCSession.h"
#include "dialogs/AcceptCall.h"
#include "mtx/responses/turn_server.hpp"
Q_DECLARE_METATYPE(std::vector<mtx::events::msg::CallCandidates::Candidate>)
Q_DECLARE_METATYPE(mtx::events::msg::CallCandidates::Candidate)
Q_DECLARE_METATYPE(mtx::responses::TurnServer)
using namespace mtx::events;
using namespace mtx::events::msg;
// https://github.com/vector-im/riot-web/issues/10173
#define STUN_SERVER "stun://turn.matrix.org:3478"
namespace {
std::vector<std::string>
getTurnURIs(const mtx::responses::TurnServer &turnServer);
}
CallManager::CallManager(QSharedPointer<UserSettings> userSettings)
: QObject()
, session_(WebRTCSession::instance())
, turnServerTimer_(this)
, settings_(userSettings)
{
qRegisterMetaType<std::vector<mtx::events::msg::CallCandidates::Candidate>>();
qRegisterMetaType<mtx::events::msg::CallCandidates::Candidate>();
qRegisterMetaType<mtx::responses::TurnServer>();
connect(
&session_,
&WebRTCSession::offerCreated,
this,
[this](const std::string &sdp, const std::vector<CallCandidates::Candidate> &candidates) {
nhlog::ui()->debug("WebRTC: call id: {} - sending offer", callid_);
emit newMessage(roomid_, CallInvite{callid_, sdp, 0, timeoutms_});
emit newMessage(roomid_, CallCandidates{callid_, candidates, 0});
QTimer::singleShot(timeoutms_, this, [this]() {
if (session_.state() == webrtc::State::OFFERSENT) {
hangUp(CallHangUp::Reason::InviteTimeOut);
emit ChatPage::instance()->showNotification(
"The remote side failed to pick up.");
}
});
});
connect(
&session_,
&WebRTCSession::answerCreated,
this,
[this](const std::string &sdp, const std::vector<CallCandidates::Candidate> &candidates) {
nhlog::ui()->debug("WebRTC: call id: {} - sending answer", callid_);
emit newMessage(roomid_, CallAnswer{callid_, sdp, 0});
emit newMessage(roomid_, CallCandidates{callid_, candidates, 0});
});
connect(&session_,
&WebRTCSession::newICECandidate,
this,
[this](const CallCandidates::Candidate &candidate) {
nhlog::ui()->debug("WebRTC: call id: {} - sending ice candidate", callid_);
emit newMessage(roomid_, CallCandidates{callid_, {candidate}, 0});
});
connect(&turnServerTimer_, &QTimer::timeout, this, &CallManager::retrieveTurnServer);
connect(this,
&CallManager::turnServerRetrieved,
this,
[this](const mtx::responses::TurnServer &res) {
nhlog::net()->info("TURN server(s) retrieved from homeserver:");
nhlog::net()->info("username: {}", res.username);
nhlog::net()->info("ttl: {} seconds", res.ttl);
for (const auto &u : res.uris)
nhlog::net()->info("uri: {}", u);
// Request new credentials close to expiry
// See https://tools.ietf.org/html/draft-uberti-behave-turn-rest-00
turnURIs_ = getTurnURIs(res);
uint32_t ttl = std::max(res.ttl, UINT32_C(3600));
if (res.ttl < 3600)
nhlog::net()->warn("Setting ttl to 1 hour");
turnServerTimer_.setInterval(ttl * 1000 * 0.9);
});
connect(&session_, &WebRTCSession::stateChanged, this, [this](webrtc::State state) {
switch (state) {
case webrtc::State::DISCONNECTED:
playRingtone("qrc:/media/media/callend.ogg", false);
clear();
break;
case webrtc::State::ICEFAILED: {
QString error("Call connection failed.");
if (turnURIs_.empty())
error += " Your homeserver has no configured TURN server.";
emit ChatPage::instance()->showNotification(error);
hangUp(CallHangUp::Reason::ICEFailed);
break;
}
default:
break;
}
});
connect(&player_,
&QMediaPlayer::mediaStatusChanged,
this,
[this](QMediaPlayer::MediaStatus status) {
if (status == QMediaPlayer::LoadedMedia)
player_.play();
});
}
void
CallManager::sendInvite(const QString &roomid)
{
if (onActiveCall())
return;
auto roomInfo = cache::singleRoomInfo(roomid.toStdString());
if (roomInfo.member_count != 2) {
emit ChatPage::instance()->showNotification(
"Voice calls are limited to 1:1 rooms.");
return;
}
std::string errorMessage;
if (!session_.init(&errorMessage)) {
emit ChatPage::instance()->showNotification(QString::fromStdString(errorMessage));
return;
}
roomid_ = roomid;
session_.setStunServer(settings_->useStunServer() ? STUN_SERVER : "");
session_.setTurnServers(turnURIs_);
generateCallID();
nhlog::ui()->debug("WebRTC: call id: {} - creating invite", callid_);
std::vector<RoomMember> members(cache::getMembers(roomid.toStdString()));
const RoomMember &callee =
members.front().user_id == utils::localUser() ? members.back() : members.front();
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.");
endCall();
}
}
namespace {
std::string
callHangUpReasonString(CallHangUp::Reason reason)
{
switch (reason) {
case CallHangUp::Reason::ICEFailed:
return "ICE failed";
case CallHangUp::Reason::InviteTimeOut:
return "Invite time out";
default:
return "User";
}
}
}
void
CallManager::hangUp(CallHangUp::Reason reason)
{
if (!callid_.empty()) {
nhlog::ui()->debug(
"WebRTC: call id: {} - hanging up ({})", callid_, callHangUpReasonString(reason));
emit newMessage(roomid_, CallHangUp{callid_, 0, reason});
endCall();
}
}
bool
CallManager::onActiveCall() const
{
return session_.state() != webrtc::State::DISCONNECTED;
}
void
CallManager::syncEvent(const mtx::events::collections::TimelineEvents &event)
{
#ifdef GSTREAMER_AVAILABLE
if (handleEvent_<CallInvite>(event) || handleEvent_<CallCandidates>(event) ||
handleEvent_<CallAnswer>(event) || handleEvent_<CallHangUp>(event))
return;
#else
(void)event;
#endif
}
template<typename T>
bool
CallManager::handleEvent_(const mtx::events::collections::TimelineEvents &event)
{
if (std::holds_alternative<RoomEvent<T>>(event)) {
handleEvent(std::get<RoomEvent<T>>(event));
return true;
}
return false;
}
void
CallManager::handleEvent(const RoomEvent<CallInvite> &callInviteEvent)
{
const char video[] = "m=video";
const std::string &sdp = callInviteEvent.content.sdp;
bool isVideo = std::search(sdp.cbegin(),
sdp.cend(),
std::cbegin(video),
std::cend(video) - 1,
[](unsigned char c1, unsigned char c2) {
return std::tolower(c1) == std::tolower(c2);
}) != sdp.cend();
nhlog::ui()->debug("WebRTC: call id: {} - incoming {} CallInvite from {}",
callInviteEvent.content.call_id,
(isVideo ? "video" : "voice"),
callInviteEvent.sender);
if (callInviteEvent.content.call_id.empty())
return;
auto roomInfo = cache::singleRoomInfo(callInviteEvent.room_id);
if (onActiveCall() || roomInfo.member_count != 2 || isVideo) {
emit newMessage(QString::fromStdString(callInviteEvent.room_id),
CallHangUp{callInviteEvent.content.call_id,
0,
CallHangUp::Reason::InviteTimeOut});
return;
}
playRingtone("qrc:/media/media/ring.ogg", true);
roomid_ = QString::fromStdString(callInviteEvent.room_id);
callid_ = callInviteEvent.content.call_id;
remoteICECandidates_.clear();
std::vector<RoomMember> members(cache::getMembers(callInviteEvent.room_id));
const RoomMember &caller =
members.front().user_id == utils::localUser() ? members.back() : members.front();
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),
QString::fromStdString(roomInfo.avatar_url),
settings_,
MainWindow::instance());
connect(dialog, &dialogs::AcceptCall::accept, this, [this, callInviteEvent]() {
MainWindow::instance()->hideOverlay();
answerInvite(callInviteEvent.content);
});
connect(dialog, &dialogs::AcceptCall::reject, this, [this]() {
MainWindow::instance()->hideOverlay();
hangUp();
});
MainWindow::instance()->showSolidOverlayModal(dialog);
}
void
CallManager::answerInvite(const CallInvite &invite)
{
stopRingtone();
std::string errorMessage;
if (!session_.init(&errorMessage)) {
emit ChatPage::instance()->showNotification(QString::fromStdString(errorMessage));
hangUp();
return;
}
session_.setStunServer(settings_->useStunServer() ? STUN_SERVER : "");
session_.setTurnServers(turnURIs_);
if (!session_.acceptOffer(invite.sdp)) {
emit ChatPage::instance()->showNotification("Problem setting up call.");
hangUp();
return;
}
session_.acceptICECandidates(remoteICECandidates_);
remoteICECandidates_.clear();
}
void
CallManager::handleEvent(const RoomEvent<CallCandidates> &callCandidatesEvent)
{
if (callCandidatesEvent.sender == utils::localUser().toStdString())
return;
nhlog::ui()->debug("WebRTC: call id: {} - incoming CallCandidates from {}",
callCandidatesEvent.content.call_id,
callCandidatesEvent.sender);
if (callid_ == callCandidatesEvent.content.call_id) {
if (onActiveCall())
session_.acceptICECandidates(callCandidatesEvent.content.candidates);
else {
// CallInvite has been received and we're awaiting localUser to accept or
// reject the call
for (const auto &c : callCandidatesEvent.content.candidates)
remoteICECandidates_.push_back(c);
}
}
}
void
CallManager::handleEvent(const RoomEvent<CallAnswer> &callAnswerEvent)
{
nhlog::ui()->debug("WebRTC: call id: {} - incoming CallAnswer from {}",
callAnswerEvent.content.call_id,
callAnswerEvent.sender);
if (!onActiveCall() && callAnswerEvent.sender == utils::localUser().toStdString() &&
callid_ == callAnswerEvent.content.call_id) {
emit ChatPage::instance()->showNotification("Call answered on another device.");
stopRingtone();
MainWindow::instance()->hideOverlay();
return;
}
if (onActiveCall() && callid_ == callAnswerEvent.content.call_id) {
stopRingtone();
if (!session_.acceptAnswer(callAnswerEvent.content.sdp)) {
emit ChatPage::instance()->showNotification("Problem setting up call.");
hangUp();
}
}
}
void
CallManager::handleEvent(const RoomEvent<CallHangUp> &callHangUpEvent)
{
nhlog::ui()->debug("WebRTC: call id: {} - incoming CallHangUp ({}) from {}",
callHangUpEvent.content.call_id,
callHangUpReasonString(callHangUpEvent.content.reason),
callHangUpEvent.sender);
if (callid_ == callHangUpEvent.content.call_id) {
MainWindow::instance()->hideOverlay();
endCall();
}
}
void
CallManager::generateCallID()
{
using namespace std::chrono;
uint64_t ms = duration_cast<milliseconds>(system_clock::now().time_since_epoch()).count();
callid_ = "c" + std::to_string(ms);
}
void
CallManager::clear()
{
roomid_.clear();
callPartyName_.clear();
callPartyAvatarUrl_.clear();
callid_.clear();
remoteICECandidates_.clear();
}
void
CallManager::endCall()
{
stopRingtone();
clear();
session_.end();
}
void
CallManager::refreshTurnServer()
{
turnURIs_.clear();
turnServerTimer_.start(2000);
}
void
CallManager::retrieveTurnServer()
{
http::client()->get_turn_server(
[this](const mtx::responses::TurnServer &res, mtx::http::RequestErr err) {
if (err) {
turnServerTimer_.setInterval(5000);
return;
}
emit turnServerRetrieved(res);
});
}
void
CallManager::playRingtone(const QString &ringtone, bool repeat)
{
static QMediaPlaylist playlist;
playlist.clear();
playlist.setPlaybackMode(repeat ? QMediaPlaylist::CurrentItemInLoop
: QMediaPlaylist::CurrentItemOnce);
playlist.addMedia(QUrl(ringtone));
player_.setVolume(100);
player_.setPlaylist(&playlist);
}
void
CallManager::stopRingtone()
{
player_.setPlaylist(nullptr);
}
namespace {
std::vector<std::string>
getTurnURIs(const mtx::responses::TurnServer &turnServer)
{
// gstreamer expects: turn(s)://username:password@host:port?transport=udp(tcp)
// where username and password are percent-encoded
std::vector<std::string> ret;
for (const auto &uri : turnServer.uris) {
if (auto c = uri.find(':'); c == std::string::npos) {
nhlog::ui()->error("Invalid TURN server uri: {}", uri);
continue;
} else {
std::string scheme = std::string(uri, 0, c);
if (scheme != "turn" && scheme != "turns") {
nhlog::ui()->error("Invalid TURN server uri: {}", uri);
continue;
}
QString encodedUri =
QString::fromStdString(scheme) + "://" +
QUrl::toPercentEncoding(QString::fromStdString(turnServer.username)) +
":" +
QUrl::toPercentEncoding(QString::fromStdString(turnServer.password)) +
"@" + QString::fromStdString(std::string(uri, ++c));
ret.push_back(encodedUri.toStdString());
}
}
return ret;
}
}

@ -0,0 +1,76 @@
#pragma once
#include <string>
#include <vector>
#include <QMediaPlayer>
#include <QObject>
#include <QSharedPointer>
#include <QString>
#include <QTimer>
#include "mtx/events/collections.hpp"
#include "mtx/events/voip.hpp"
namespace mtx::responses {
struct TurnServer;
}
class UserSettings;
class WebRTCSession;
class CallManager : public QObject
{
Q_OBJECT
public:
CallManager(QSharedPointer<UserSettings>);
void sendInvite(const QString &roomid);
void hangUp(
mtx::events::msg::CallHangUp::Reason = mtx::events::msg::CallHangUp::Reason::User);
bool onActiveCall() const;
QString callPartyName() const { return callPartyName_; }
QString callPartyAvatarUrl() const { return callPartyAvatarUrl_; }
void refreshTurnServer();
public slots:
void syncEvent(const mtx::events::collections::TimelineEvents &event);
signals:
void newMessage(const QString &roomid, const mtx::events::msg::CallInvite &);
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 &);
private slots:
void retrieveTurnServer();
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_;
std::vector<std::string> turnURIs_;
QTimer turnServerTimer_;
QSharedPointer<UserSettings> settings_;
QMediaPlayer player_;
template<typename T>
bool handleEvent_(const mtx::events::collections::TimelineEvents &event);
void handleEvent(const mtx::events::RoomEvent<mtx::events::msg::CallInvite> &);
void handleEvent(const mtx::events::RoomEvent<mtx::events::msg::CallCandidates> &);
void handleEvent(const mtx::events::RoomEvent<mtx::events::msg::CallAnswer> &);
void handleEvent(const mtx::events::RoomEvent<mtx::events::msg::CallHangUp> &);
void answerInvite(const mtx::events::msg::CallInvite &);
void generateCallID();
void clear();
void endCall();
void playRingtone(const QString &ringtone, bool repeat);
void stopRingtone();
};

File diff suppressed because it is too large Load Diff

@ -19,6 +19,7 @@
#include <atomic>
#include <optional>
#include <stack>
#include <variant>
#include <mtx/common.hpp>
@ -34,7 +35,9 @@
#include <QTimer>
#include <QWidget>
#include "CacheCryptoStructs.h"
#include "CacheStructs.h"
#include "CallManager.h"
#include "CommunitiesList.h"
#include "Utils.h"
#include "notifications/Manager.h"
@ -47,10 +50,10 @@ class SideBarActions;
class Splitter;
class TextInputWidget;
class TimelineViewManager;
class TopRoomBar;
class UserInfoWidget;
class UserSettings;
class NotificationsManager;
class TimelineModel;
constexpr int CONSENSUS_TIMEOUT = 1000;
constexpr int SHOW_CONTENT_TIMEOUT = 3000;
@ -77,14 +80,23 @@ public:
QSharedPointer<UserSettings> userSettings() { return userSettings_; }
void deleteConfigs();
CommunitiesList *communitiesList() { return communitiesList_; }
//! 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).
void showSideBars();
void initiateLogout();
void query_keys(const std::string &req,
std::function<void(const UserKeyCache &, mtx::http::RequestErr)> cb);
void focusMessageInput();
QString status() const;
void setStatus(const QString &status);
mtx::presence::PresenceState currentPresence() const;
public slots:
void leaveRoom(const QString &room_id);
@ -99,8 +111,6 @@ signals:
void connectionLost();
void connectionRestored();
void messageReply(const RelatedInfo &related);
void notificationsRetrieved(const mtx::responses::Notifications &);
void highlightedNotifsRetrieved(const mtx::responses::Notifications &,
const QPoint widgetPos);
@ -114,7 +124,7 @@ signals:
const QString &mime,
qint64 dsize,
const QSize &dimensions,
const std::optional<RelatedInfo> &related);
const QString &blurhash);
void contentLoaded();
void closing();
@ -133,6 +143,7 @@ signals:
void trySyncCb();
void tryDelayedSyncCb();
void tryInitialSyncCb();
void newSyncResponse(mtx::responses::Sync res);
void leftRoom(const QString &room_id);
void initializeRoomList(QMap<QString, RoomInfo>);
@ -142,7 +153,6 @@ signals:
void syncUI(const mtx::responses::Rooms &rooms);
void syncRoomlist(const std::map<QString, RoomInfo> &updates);
void syncTags(const std::map<QString, RoomInfo> &updates);
void syncTopBar(const std::map<QString, RoomInfo> &updates);
void dropToLoginPageCb(const QString &msg);
void notifyMessage(const QString &roomid,
@ -153,18 +163,37 @@ signals:
const QImage &icon);
void updateGroupsInfo(const mtx::responses::JoinedGroups &groups);
void retrievedPresence(const QString &statusMsg, mtx::presence::PresenceState state);
void themeChanged();
void decryptSidebarChanged();
//! Signals for device verificaiton
void receivedDeviceVerificationAccept(
const mtx::events::msg::KeyVerificationAccept &message);
void receivedDeviceVerificationRequest(
const mtx::events::msg::KeyVerificationRequest &message,
std::string sender);
void receivedRoomDeviceVerificationRequest(
const mtx::events::RoomEvent<mtx::events::msg::KeyVerificationRequest> &message,
TimelineModel *model);
void receivedDeviceVerificationCancel(
const mtx::events::msg::KeyVerificationCancel &message);
void receivedDeviceVerificationKey(const mtx::events::msg::KeyVerificationKey &message);
void receivedDeviceVerificationMac(const mtx::events::msg::KeyVerificationMac &message);
void receivedDeviceVerificationStart(const mtx::events::msg::KeyVerificationStart &message,
std::string sender);
void receivedDeviceVerificationReady(const mtx::events::msg::KeyVerificationReady &message);
void receivedDeviceVerificationDone(const mtx::events::msg::KeyVerificationDone &message);
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);
void joinRoom(const QString &room);
void sendTypingNotifications();
void handleSyncResponse(mtx::responses::Sync res);
private:
static ChatPage *instance_;
@ -204,10 +233,13 @@ private:
uint16_t notification_count,
uint16_t highlight_count);
//! Send desktop notification for the received messages.
void sendDesktopNotifications(const mtx::responses::Notifications &);
void sendNotifications(const mtx::responses::Notifications &);
void showNotificationsDialog(const QPoint &point);
template<typename T>
void connectCallMessage();
QHBoxLayout *topLayout_;
Splitter *splitter;
@ -225,7 +257,6 @@ private:
TimelineViewManager *view_manager_;
SideBarActions *sidebarActions_;
TopRoomBar *top_bar_;
TextInputWidget *text_input_;
QTimer connectivityTimer_;
@ -244,6 +275,7 @@ private:
QSharedPointer<UserSettings> userSettings_;
NotificationsManager notificationsManager;
CallManager callManager_;
};
template<class Collection>

@ -257,6 +257,18 @@ CommunitiesList::roomList(const QString &id) const
return {};
}
std::vector<std::string>
CommunitiesList::currentTags() const
{
std::vector<std::string> tags;
for (auto &entry : communities_) {
CommunitiesListItem *item = entry.second.data();
if (item->is_tag())
tags.push_back(entry.first.mid(4).toStdString());
}
return tags;
}
void
CommunitiesList::sortEntries()
{

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save