Compare commits

...

186 Commits

Author SHA1 Message Date
John Preston
9d1a4cdbfe Beta version 2.1.8: Fix build on 64 bit systems. 2020-06-03 16:18:03 +04:00
John Preston
383e6dec43 Beta version 2.1.8.
- Add support for full group message history export.
- Allow export of a single chat message history in JSON format.
2020-06-03 15:51:27 +04:00
John Preston
85904e3022 Update submodules. 2020-06-03 15:51:15 +04:00
John Preston
f88b97553e Fix crash in destructor of Data::CloudFile. 2020-06-03 13:48:11 +04:00
John Preston
63c6a1db82 Allow removing users invited by me. 2020-06-03 12:57:36 +04:00
John Preston
ca97e3c375 Add more warnings for suspicious urls. 2020-06-03 12:44:46 +04:00
John Preston
ef30c776bf Fix visual glitch in filter change from archived chat. 2020-06-03 12:31:15 +04:00
Ilya Fedin
d45e74619d Use Platform::IsWayland from lib_base 2020-06-03 11:43:55 +04:00
Ilya Fedin
d92b5eebcc Restore X error handler just like qgtk3 2020-06-03 11:31:34 +04:00
Ilya Fedin
5c6b4d95b0 Suppress warning about transient parent when opening gtk file dialog 2020-06-03 11:31:34 +04:00
Ilya Fedin
0fbec5eba1 Use QVersionNumber to compare version in native notifications 2020-06-03 11:31:34 +04:00
Ilya Fedin
ab13d9bdaf Skip empty parts in QT_QPA_PLATFORMTHEME 2020-06-03 11:31:34 +04:00
Ilya Fedin
0165e31ca7 Never use custom code for portal detecting in flatpak 2020-06-03 11:31:34 +04:00
Ilya Fedin
f1e75d809a Separate patches 2020-06-03 11:31:34 +04:00
Ilya Fedin
c776f81dc7 Add support for choosing directories via xdg-desktop-portal 2020-06-03 11:31:34 +04:00
John Preston
9fd62d3892 Add more deprecated system versions. 2020-06-02 22:26:59 +04:00
John Preston
793906ca9a Fix build on Windows. 2020-06-02 12:26:58 +04:00
23rd
35e575c2d7 Fixed build for macOS. 2020-06-01 19:28:19 +03:00
23rd
f5e84220eb Fixed crash in context menu for uploading scheduled messages. 2020-06-01 17:55:22 +03:00
Ilya Fedin
1d622fb3c0 Add patches with the fix for https://github.com/telegramdesktop/tdesktop/issues/6645 2020-06-01 18:43:42 +04:00
Nicholas Guriev
d8d3dda2f3 Fix little typo in theme names generator 2020-06-01 18:26:16 +04:00
Ilya Fedin
e098922a4b Add Platform::AutostartSupported 2020-06-01 18:25:21 +04:00
Ilya Fedin
413ddf285e Fix crash in gtk file dialog on Wayland 2020-06-01 18:22:53 +04:00
Ilya Fedin
7ac78be984 Load gtk2 even on Wayland 2020-06-01 18:22:53 +04:00
Ilya Fedin
4c546156da Remove duplicate log line 2020-06-01 18:22:53 +04:00
Ilya Fedin
db528b39e1 Fix macOS cache validating
macOS action has runner version in the workdir path, it should be a part of the cache key
2020-06-01 18:21:52 +04:00
Ilya Fedin
586744c112 Apply sway fixes to the PiP and export windows too 2020-06-01 18:21:30 +04:00
Ilya Fedin
7b106761be Remove cache from snap action since it works not so good 2020-06-01 18:19:34 +04:00
Ilya Fedin
8fb7f0fc73 Use TDESKTOP_USE_GTK_FILE_DIALOG in snap 2020-06-01 18:19:34 +04:00
Ilya Fedin
10b169f9f6 Make not supported errors static 2020-06-01 18:19:34 +04:00
Ilya Fedin
c83b8d4043 Fix naming of static variables 2020-06-01 18:19:34 +04:00
Ilya Fedin
1fc2b19c94 Add Cinnamon sound settings command 2020-06-01 18:19:34 +04:00
Ilya Fedin
fb97940cac Rename SandboxAutostart to PortalAutostart 2020-06-01 18:19:34 +04:00
Ilya Fedin
16c38b54e2 Rename InSandbox to InFlatpak 2020-06-01 18:19:34 +04:00
Ilya Fedin
7f29f57c3d Use custom gtk file dialog only on gtk-based DEs 2020-06-01 18:19:34 +04:00
Ilya Fedin
1fb1d57a27 Get system icon theme on gtk-based DEs 2020-06-01 18:19:34 +04:00
Ilya Fedin
47d7bd95ae Add a method to check if gtk integration is forced 2020-06-01 18:19:34 +04:00
John Preston
368eeaf754 Improve single chat export progress display. 2020-06-01 18:09:34 +04:00
John Preston
1686eb394d Add support for JSON single-chat export. 2020-06-01 18:09:34 +04:00
John Preston
02586ebe4b Allow export of just-converted supergroup. 2020-06-01 18:09:34 +04:00
John Preston
8f80c19ae1 Merge old group with supergroup history in export. 2020-06-01 18:09:34 +04:00
John Preston
1598165e2b Closed alpha version 2.1.7.4. 2020-06-01 18:09:34 +04:00
John Preston
f4cd84c313 Fix crash in stickers. 2020-06-01 18:09:34 +04:00
John Preston
9b574e497d Closed alpha version 2.1.7.3. 2020-06-01 18:09:34 +04:00
John Preston
6660338ccc Fix crash in sticker sets without a thumbnail. 2020-06-01 18:09:34 +04:00
John Preston
423ea5b499 Fix crash on invalid image data. 2020-06-01 18:09:34 +04:00
John Preston
4695ebae6e Fix crash in sticker set parsing. 2020-06-01 18:09:34 +04:00
John Preston
aaa4db7b27 Fix a crash in custom notifications. 2020-06-01 18:09:34 +04:00
John Preston
0965b06fa3 Closed alpha version 2.1.7.2. 2020-06-01 18:09:34 +04:00
Ilya Fedin
be96bf2812 Set parent for dialogs only on Wayland 2020-06-01 18:09:34 +04:00
John Preston
b7aa60bedf Fix build for Linux. 2020-06-01 18:09:34 +04:00
John Preston
d5b3fa017b Fix build for macOS. 2020-06-01 18:09:34 +04:00
John Preston
36fbdfb380 Simplify Image, remove ImageSource. 2020-06-01 18:09:33 +04:00
John Preston
d0c78eaddd Leave only one image source type. 2020-06-01 18:09:33 +04:00
John Preston
6513422e40 Remove legacy image-related code. 2020-06-01 18:09:33 +04:00
John Preston
f066e0f05a Use Data::CloudImage for userpics. 2020-06-01 18:09:33 +04:00
John Preston
249f7813c1 Don't hold session pointer in Data::CloudImage. 2020-06-01 18:09:33 +04:00
John Preston
29a498b959 Use Data::CloudImage for location thumbnails. 2020-06-01 18:09:33 +04:00
John Preston
ae9ed820ee Fix sticker set icons display. 2020-06-01 18:09:33 +04:00
John Preston
803593cd8d Change Stickers::Set from value to object type. 2020-06-01 18:09:33 +04:00
John Preston
897e432f40 Use CloudImageView in the inline bot thumbnails. 2020-06-01 18:09:33 +04:00
John Preston
50e0c3ee4d Fix preloading in media viewer. 2020-06-01 18:09:33 +04:00
John Preston
056945d9f5 Remove legacy image creation methods. 2020-06-01 18:09:32 +04:00
John Preston
a9b70a7d63 Closed alpha 2.1.7.1: Fix build for Xcode. 2020-06-01 18:09:32 +04:00
John Preston
6dabd87df3 Closed alpha version 2.1.7.1. 2020-06-01 18:09:32 +04:00
John Preston
b35b6c4449 Fix saving cache from InMemoryLocation. 2020-06-01 18:09:32 +04:00
John Preston
74ef8104a7 Fix photo edit caption box, remove 's' size. 2020-06-01 18:09:32 +04:00
John Preston
af0eebb6f1 Remove debug inline bot results marks. 2020-06-01 18:09:32 +04:00
John Preston
dbb46ce9b0 Let [Photo|Document]Media outlive message view. 2020-06-01 18:09:32 +04:00
John Preston
700d3db4cc Correctly unload heavy parts on quit. 2020-06-01 18:09:32 +04:00
John Preston
64cf0e1a44 Fix caching of sent photos and document previews. 2020-06-01 18:09:32 +04:00
John Preston
7ad660a0e7 Allow photos not have some of the thumbnails. 2020-06-01 18:09:32 +04:00
John Preston
e27d2bc2d5 Move photo data to Data::PhotoMedia. 2020-06-01 18:09:32 +04:00
John Preston
24fed8105c Fix stickers panel on Retina screens. 2020-06-01 18:09:32 +04:00
John Preston
9ce59730ff Collect local DocumentMedia data. 2020-06-01 18:09:32 +04:00
John Preston
3f26fc9f55 Allow WebDocument video thumbnails. 2020-06-01 18:09:32 +04:00
John Preston
0834920db8 Fix sending of video-thumbed GIFs from panel. 2020-06-01 18:09:32 +04:00
John Preston
f4ed2c26ba Save video thumbnail location to local storage. 2020-06-01 18:09:32 +04:00
John Preston
c63e2c01ac Use video thumbnail in media preview. 2020-06-01 18:09:31 +04:00
John Preston
c61f3a0aba Fix sending of thumbnailed inline result GIFs. 2020-06-01 18:09:31 +04:00
John Preston
3c9ca2eb94 Load and show video thumbnails in the panel. 2020-06-01 18:09:31 +04:00
John Preston
33c1c48ad9 Update API scheme to layer 114. 2020-06-01 18:09:31 +04:00
John Preston
a27aea3887 Allow retrying of updates build preparing. 2020-06-01 18:09:31 +04:00
John Preston
ea4044e38c Use TgVoip interface instead of VoIPController. 2020-06-01 18:09:31 +04:00
John Preston
c967a72dcb Save frame in GIFs panel. 2020-06-01 18:09:31 +04:00
John Preston
7d386b164b Save a frame in stickers panel. 2020-06-01 18:09:31 +04:00
John Preston
ccbbf6f5f3 Always download GIFs in the panel.
Fixes #6981.
2020-06-01 18:09:31 +04:00
John Preston
9725d4272e Clear DocumentMedia in sticker panel. 2020-06-01 18:09:31 +04:00
John Preston
eb75859dc0 Cache last frame of stickers panel footer icons. 2020-06-01 18:09:31 +04:00
John Preston
ad5507f2c8 Clear DocumentMedia in media overview. 2020-06-01 18:09:31 +04:00
John Preston
58f82620e0 Simplify media overview layouts. 2020-06-01 18:09:31 +04:00
John Preston
053eace154 Prepare overview layouts for media clearing. 2020-06-01 18:09:31 +04:00
John Preston
d64014c995 Clear DocumentMedia in ReplyPreview. 2020-06-01 18:09:31 +04:00
John Preston
44ec55b6a8 Clear DocumentMedia in links overview. 2020-06-01 18:09:31 +04:00
John Preston
9dba723643 Better DocumentMedia management in BackgroundBox. 2020-06-01 18:09:31 +04:00
John Preston
97a82762ef Fix build with MSVC 16.6.0. 2020-06-01 18:09:31 +04:00
John Preston
1542311d89 Preload documents in media viewer. 2020-06-01 18:09:31 +04:00
John Preston
fb322b5fc5 Use empty Storage::Cache::Key as nullopt. 2020-06-01 18:09:31 +04:00
John Preston
581a21dbd9 Use Media::Streaming in EditCaptionBox. 2020-06-01 18:09:31 +04:00
John Preston
3d431a27cb Improve inline thumbnail usage in PiP player. 2020-06-01 18:09:31 +04:00
John Preston
cbb9657044 Fix download task finalizing. 2020-06-01 18:09:30 +04:00
John Preston
3797753d16 Support different location types for thumbnails. 2020-06-01 18:09:30 +04:00
John Preston
37aabc0da9 Add generic DownloadLocation and ImageLocation. 2020-06-01 18:09:30 +04:00
John Preston
956c3af0ae Start DocumentData::thumbnail move to DocumentMedia. 2020-06-01 18:09:30 +04:00
John Preston
1329870c8e Fix build on macOS. 2020-06-01 18:09:30 +04:00
John Preston
ff6365ec72 Fix crash in still downloaded ~DocumentData. 2020-06-01 18:09:30 +04:00
John Preston
1e9c79ca85 Move automaticLoad() to DocumentMedia. 2020-06-01 18:09:30 +04:00
John Preston
40f12a2584 Keep document byte data only in DocumentMedia. 2020-06-01 18:09:30 +04:00
John Preston
97bab388ea Use rpl for file download progress notifications. 2020-06-01 18:09:30 +04:00
John Preston
bf616036b3 Check loaded status through DocumentMedia if possible. 2020-06-01 18:09:30 +04:00
John Preston
669b79588e Remove FilePathResolve::SaveFromData. 2020-06-01 18:09:30 +04:00
John Preston
33f4946242 Start using document bytes from DocumentMedia. 2020-06-01 18:09:30 +04:00
John Preston
888e42df34 Remove data_document_good_thumbnail module. 2020-06-01 18:09:30 +04:00
John Preston
70c79eb6bd Move sticker image to DocumentMedia. 2020-06-01 18:09:30 +04:00
John Preston
bdd3c51ab8 Move inline thumbnail image to DocumentMedia. 2020-06-01 18:09:30 +04:00
John Preston
6ca43153bb Improve clearing of DocumentMedia. 2020-06-01 18:09:29 +04:00
John Preston
7db53599e8 Use Data::DocumentMedia to store good thumbnails. 2020-06-01 18:09:29 +04:00
John Preston
61647275e8 Optimize image destruction.
No need to call _source->unload(), it leads to saving to PNG.
2020-06-01 18:09:29 +04:00
Ilya Fedin
a37138aa52 Fix signature key errors in snap action 2020-06-01 15:24:35 +04:00
Ilya Fedin
1504136828 Don't spam logs if there are no dbus 2020-05-26 07:24:18 +04:00
Ilya Fedin
c12356a032 Disable unneeded alsa dependency in ffmpeg 2020-05-25 10:34:12 +04:00
Ilya Fedin
126ed6e6e3 Fix path to compose file 2020-05-25 10:34:12 +04:00
Ilya Fedin
fa4236e9ea Add support for DESKTOP_APP_USE_PACKAGED on macOS 2020-05-25 10:29:40 +04:00
Ilya Fedin
b19dcf0653 Add possibility to control external upater flag with a config in /etc 2020-05-25 10:27:48 +04:00
Ilya Fedin
77d1f64e0e Disable fallback session management 2020-05-25 09:31:52 +04:00
Ilya Fedin
3479a4ec59 Add parent, minimum and maximum size to notifications 2020-05-25 09:29:15 +04:00
Ilya Fedin
bdf28370f9 Fix call window size on Sway 2020-05-25 09:29:15 +04:00
Ilya Fedin
cd81fc6727 Don't lose -freetype argument on restart 2020-05-25 09:26:49 +04:00
John Preston
7351641034 Version 2.1.7.
- Fix the Fcitx input method plugin.
2020-05-24 11:59:19 +04:00
Ilya Fedin
e0669e222d Update fcitx5-qt 2020-05-24 11:09:34 +04:00
Ilya Fedin
4c1f83daca Add a check for bundled Qt plugins 2020-05-24 10:57:37 +04:00
Ilya Fedin
ced2652deb OpenAL returns device names with UTF-8 2020-05-24 10:56:29 +04:00
23rd
8d1db85a28 Fixed album items selection in section of scheduled messages.
This bug relates only to albums with captions.
2020-05-20 12:00:44 +03:00
John Preston
97305c8cb5 Fix build. 2020-05-20 12:49:41 +04:00
23rd
1ef5d81270 Added ability to change months in calendar with mouse wheel. 2020-05-20 12:42:03 +04:00
23rd
9ff427afad Fixed indefinitely bouncing of dock icon. 2020-05-20 12:41:44 +04:00
23rd
1c5eadcd79 Fixed context menu for caption of scheduled album.
Fixed #6523.
2020-05-20 12:41:44 +04:00
23rd
bc6c01de7f Added Esc shortcut to clear selection in section of scheduled messages. 2020-05-20 12:41:44 +04:00
23rd
41255cab44 Removed display views and author for sent scheduled messages.
Moved filling of post flags to a single place.
2020-05-20 12:41:44 +04:00
23rd
ccbc63cd6e Added ability to paste data in section of scheduled messages.
Fixed #6702.
Fixed #6539.
2020-05-20 12:41:44 +04:00
23rd
97446ae783 Added ability to reschedule scheduled messages. 2020-05-20 12:41:44 +04:00
23rd
5a75dd2b6f Added handling of updates for rescheduled messages. 2020-05-20 12:41:43 +04:00
Wei Cheng
6559e83e83 fix: obtain doNotDisturb value correctly 2020-05-15 11:44:06 +04:00
John Preston
d679703bbf Version 2.1.6.
- Fix automatic downloads on Windows by clean rebuild.
2020-05-14 01:16:13 +04:00
John Preston
66a3e36024 Version 2.1.5.
- Disable the taskbar icon flash or the dock icon bounce
in Settings > Notifications.
- View messages containing long monospace texts in wide bubbles.
- Bug fixes and other minor improvements.
2020-05-13 18:22:05 +04:00
John Preston
31e38e1690 Fix layout of community transfer error box. 2020-05-13 18:19:09 +04:00
John Preston
da10059f45 Update lib_lottie, hide rlottie dependency. 2020-05-13 17:07:26 +04:00
John Preston
cb5863177f Apply edition updates to search result previews. 2020-05-12 20:29:18 +04:00
John Preston
84399286c1 Update build instructions. 2020-05-12 19:43:39 +04:00
John Preston
2e92441b3a Add input method field text edit workaround. 2020-05-12 19:26:50 +04:00
John Preston
7883f97c94 Use precise sync of the server unixtime. 2020-05-12 17:33:06 +04:00
Ilya Fedin
297b5d6a76 Update submodules 2020-05-12 17:32:40 +04:00
Ilya Fedin
492dc2568c Add DESKTOP_APP_USE_PACKAGED support for Windows 2020-05-12 17:32:40 +04:00
John Preston
547c657b1a Don't reset search results on dialogs re-open. 2020-05-12 16:30:31 +04:00
John Preston
c478d96385 Add debug logs for chats reading requests. 2020-05-12 16:18:19 +04:00
John Preston
2ede53e0ee Always try to open new provided URL.
Fixes #6941.
2020-05-12 16:15:22 +04:00
John Preston
6f760d513e Add a checkbox to disable taskbar flash.
Also add ability to set urgent flag for the window on Linux.

Fixes #223, fixes #897, fixes #906.
2020-05-12 14:16:24 +04:00
John Preston
f4f6550d66 Clear fake-unread status when switching folders. 2020-05-12 12:18:52 +04:00
John Preston
c7878f9d21 Pause by-emoji stickers on sticker preview. 2020-05-12 12:18:31 +04:00
John Preston
cd75a45673 Disable create polls in support accounts. 2020-05-12 11:26:47 +04:00
John Preston
07e3671ca8 Allow monospace blocks to extend bubble width.
This partially fixes #2060 instead of additional settings from #7822.
2020-05-12 11:07:41 +04:00
23rd
295aa644bf Fixed master branch updater Github Action. 2020-05-12 09:55:36 +04:00
John Preston
b5b78c0ade Update submodules. 2020-05-12 09:44:24 +04:00
John Preston
f5c0e5d31d Remove unnecessary include. 2020-05-12 09:43:54 +04:00
root
246ed43046 Remove replyTo from switchInlineBotButton in same peer 2020-05-12 09:29:30 +04:00
Ilya Fedin
701e1d7b4d Add fcitx5 support 2020-05-12 09:26:04 +04:00
Ilya Fedin
9cbe899688 Fix call window hiding when compositing is not supported 2020-05-12 09:17:27 +04:00
Ilya Fedin
7409d615a3 Add a cheat code to enable freetype on Windows and macOS 2020-05-10 17:09:59 +04:00
John Preston
c9553c2d4c Version 2.1.4.
- Improve bold font selection.
2020-05-08 20:34:00 +04:00
John Preston
bedefaee4d Version 2.1.3.
- Added support for new emoji.
- Channels to which you can't post will no longer be suggested when forwarding.
- Improved font selection and bold font support for CJK and Farsi.
2020-05-08 16:48:56 +04:00
John Preston
5d3b8f02fc Add Vazir font as a fallback for Farsi. 2020-05-08 13:38:23 +04:00
John Preston
5120d3ef2c Skip channels without write access in forward box. 2020-05-08 13:35:16 +04:00
John Preston
82a372873f Add two local urls to open language selection box.
tg://setlanguage and tg://settings/language

Fixes #7831.
2020-05-08 13:03:49 +04:00
Ilya Fedin
d1d1f83881 Remove outdated LIBGL_ALWAYS_INDIRECT hack 2020-05-08 12:54:21 +04:00
Ilya Fedin
78c3c86fe6 Check only if at least one audio device is exist on startup
This makes https://github.com/telegramdesktop/tdesktop/issues/1548 don't affect on startup, but only when capture feature is used
2020-05-08 12:50:25 +04:00
Ilya Fedin
447d4e6c47 Remove Portaudio from building instructions
Since it loaded at runtime with dlopen anyway and headers from the system package are OK
2020-05-08 12:49:21 +04:00
John Preston
d0e3d15e8e Update supported systems information. 2020-05-08 12:27:55 +04:00
John Preston
0251f58bf2 Use Semibold in names, use Bold in messages.
Fixes #7813, fixes #7823.
2020-05-08 12:12:47 +04:00
John Preston
36997f084a Automatically load and apply old emoji set by id. 2020-05-08 11:22:22 +04:00
John Preston
942fcb9aae Add new emoji sets file ids. 2020-05-07 19:05:57 +04:00
John Preston
6232dce1a3 Update emoji in the built-in data and sprites. 2020-05-06 19:29:02 +04:00
23rd
0c0fc46b90 Added Github Action that updates code in master branch. 2020-05-06 13:29:17 +04:00
23rd
dcf737bebe Fixed Linux build instruction. 2020-05-06 00:36:48 +03:00
23rd
919834093e Added TG for macOS version check to issue closer. 2020-05-05 18:22:54 +04:00
John Preston
99ccd49e13 Version 2.1.2: Update patches revision in docs. 2020-05-05 18:14:38 +04:00
John Preston
29896b2efd Version 2.1.2: Update Mac App Store build script. 2020-05-05 17:35:42 +04:00
364 changed files with 12028 additions and 8880 deletions

View File

@@ -14,6 +14,22 @@ jobs:
echo $tag
echo ::set-env name=LATEST_TAG::$tag
- name: Get the latest macOS version.
shell: python
run: |
import subprocess;
from xml.dom import minidom;
url = "https://osx.telegram.org/updates/versions.xml";
subprocess.check_call("wget %s" % url, shell=True);
xmldoc = minidom.parse('versions.xml');
itemlist = xmldoc.getElementsByTagName('enclosure');
ver = itemlist[0].attributes['sparkle:shortVersionString'].value;
print(ver);
subprocess.check_call("echo ::set-env name=%s::%s" % ("LATEST_MACOS", ver), shell=True);
- name: Check a version from an issue.
uses: actions/github-script@0.4.0
with:
@@ -75,10 +91,20 @@ jobs:
let issueNum = firstNum(issueVer);
let latestNum = firstNum(latestVer);
if (issueNum <= latestNum && issueNum < 5) {
let macos_ver = process.env.LATEST_MACOS;
console.log("Telegram for MacOS version from website: " + macos_ver);
if (issueNum <= latestNum && issueNum < macos_ver) {
console.log("Seems the version of this issue is fine!");
return;
}
if (issueNum > macos_ver) {
let message = `Seems like it's neither the Telegram Desktop\
nor the Telegram for macOS version.
`;
console.log(message);
return;
}
let message = `
Sorry, but according to the version you specify in this issue, \
@@ -87,7 +113,7 @@ jobs:
You can report your issue to [the group](https://t.me/macswift) \
or to [the repository of Telegram for macOS](https://github.com/overtake/TelegramSwift).
If I made a mistake and closed your issue wrongly, please reopen it. Thanks!
**If I made a mistake and closed your issue wrongly, please reopen it. Thanks!**
`;
let params = {

View File

@@ -221,6 +221,7 @@ jobs:
--disable-autodetect \
--disable-everything \
--disable-neon \
--disable-alsa \
--disable-iconv \
--enable-libopus \
--enable-vaapi \
@@ -335,19 +336,6 @@ jobs:
sudo cp -R ffmpeg-cache/. /
- name: PortAudio.
run: |
cd $LibrariesPath
git clone https://git.assembla.com/portaudio.git
cd portaudio
git checkout 396fe4b669
./configure
make -j$(nproc)
sudo make install
cd ..
rm -rf portaudio
- name: OpenAL Soft.
run: |
cd $LibrariesPath
@@ -391,36 +379,43 @@ jobs:
cd $LibrariesPath
sudo cp -R openssl-cache/. /
- name: Libxkbcommon.
run: |
cd $LibrariesPath
git clone -b xkbcommon-0.8.4 --depth=1 $GIT/xkbcommon/libxkbcommon.git
cd libxkbcommon
./autogen.sh
make -j$(nproc)
sudo make install
cd ..
rm -rf libxkbcommon
- name: Libwayland.
run: |
cd $LibrariesPath
git clone -b 1.16 https://gitlab.freedesktop.org/wayland/wayland
cd wayland
./autogen.sh --enable-static --disable-documentation --disable-dtd-validation
./autogen.sh \
--enable-static \
--disable-documentation \
--disable-dtd-validation
make -j$(nproc)
sudo make install
cd ..
rm -rf wayland
- name: Libxkbcommon.
run: |
cd $LibrariesPath
git clone -b xkbcommon-0.8.4 --depth=1 $GIT/xkbcommon/libxkbcommon.git
cd libxkbcommon
./autogen.sh \
--disable-docs \
--disable-wayland \
--with-xkb-config-root=/usr/share/X11/xkb \
--with-x-locale-root=/usr/share/X11/locale
make -j$(nproc)
sudo make install
cd ..
rm -rf libxkbcommon
- name: Qt 5.12.8 cache.
id: cache-qt
uses: actions/cache@v1
with:
path: ${{ env.LibrariesPath }}/qt-cache
key: ${{ runner.OS }}-qt-${{ env.CACHE_KEY }}-${{ hashFiles('**/qtbase_5_12_8.diff') }}
key: ${{ runner.OS }}-qt-${{ env.CACHE_KEY }}-${{ hashFiles('**/qt*_5_12_8/*') }}
- name: Qt 5.12.8 build.
if: steps.cache-qt.outputs.cache-hit != 'true'
run: |
@@ -428,11 +423,14 @@ jobs:
git clone -b v5.12.8 --depth=1 git://code.qt.io/qt/qt5.git qt_${QT}
cd qt_${QT}
perl init-repository --module-subset=qtbase,qtwayland,qtimageformats,qtsvg
git submodule update qtbase qtwayland qtimageformats qtsvg
perl init-repository --module-subset=qtbase,qtwayland,qtimageformats,qtsvg,qtx11extras
git submodule update qtbase qtwayland qtimageformats qtsvg qtx11extras
cd qtbase
git apply ../../patches/qtbase_${QT}.diff
cd ../
find ../../patches/qtbase_${QT} -type f -print0 | sort -z | xargs -r0 git apply
cd ..
cd qtwayland
find ../../patches/qtwayland_${QT} -type f -print0 | sort -z | xargs -r0 git apply
cd ..
./configure -prefix "$QT_PREFIX" \
-release \

View File

@@ -88,6 +88,7 @@ jobs:
echo $MIN_MAC >> CACHE_KEY.txt
echo $PREFIX >> CACHE_KEY.txt
echo $MANUAL_CACHING >> CACHE_KEY.txt
echo "$GITHUB_WORKSPACE" >> CACHE_KEY.txt
if [ "$AUTO_CACHING" == "1" ]; then
thisFile=$REPO_NAME/.github/workflows/mac.yml
echo `md5 -q $thisFile` >> CACHE_KEY.txt
@@ -423,7 +424,7 @@ jobs:
git submodule update qtbase
git submodule update qtimageformats
cd qtbase
git apply ../../patches/qtbase_$QT.diff
find ../../patches/qtbase_$QT -type f -print0 | sort -z | xargs -0 git apply
cd ..
./configure \

35
.github/workflows/master_updater.yml vendored Normal file
View File

@@ -0,0 +1,35 @@
name: Master branch updater.
on:
release:
types: released
jobs:
updater:
runs-on: ubuntu-latest
env:
SKIP: "0"
to_branch: "master"
steps:
- uses: actions/checkout@v1
if: env.SKIP == '0'
- name: Push the code to the master branch.
if: env.SKIP == '0'
run: |
token=${{ secrets.TOKEN_FOR_MASTER_UPDATER }}
if [ -z "${token}" ]; then
echo "Token is unset. Nothing to do."
exit 0
fi
url=https://x-access-token:$token@github.com/$GITHUB_REPOSITORY
latest_tag=$(git describe --tags --abbrev=0)
echo "Latest tag: $latest_tag"
git remote set-url origin $url
git remote -v
git checkout master
git merge $latest_tag
git push origin HEAD:refs/heads/$to_branch
echo "Done!"

View File

@@ -48,8 +48,6 @@ jobs:
env:
UPLOAD_ARTIFACT: "false"
ONLY_CACHE: "false"
MANUAL_CACHING: "5"
steps:
- name: Clone.
@@ -59,56 +57,17 @@ jobs:
- name: First set up.
run: |
# Workaround for permanent problems with third-party repository keys
sudo rm -rf /etc/apt/sources.list.d/*
sudo apt-get update
sudo apt-get install gcc-8 g++-8 -y
sudo snap install --classic snapcraft
# Workaround for snapcraft
# See https://forum.snapcraft.io/t/13258
sudo chown root:root /
md5() {
md5cache=$(md5sum $1.txt | cut -c -32)
echo ::set-env name=$1::$md5cache
}
keyFor() {
keyName="${1^^}_CACHE_KEY"
awk -v RS="" -v ORS="\n\n" '/^ '"$1"':/' snap/snapcraft.yaml > $keyName.txt
md5 $keyName
}
snap run snapcraft --version > CACHE_KEY.txt
gcc-8 --version >> CACHE_KEY.txt
echo $MANUAL_CACHING >> CACHE_KEY.txt
md5 CACHE_KEY
keyFor cmake
keyFor ffmpeg
- name: CMake cache.
id: cache-cmake
uses: actions/cache@v1
with:
path: parts/cmake
key: ${{ runner.OS }}-cmake-${{ env.CACHE_KEY }}-${{ env.CMAKE_CACHE_KEY }}
- name: CMake build.
if: steps.cache-cmake.outputs.cache-hit != 'true'
run: sudo snap run snapcraft build --destructive-mode cmake
- name: FFmpeg cache.
id: cache-ffmpeg
uses: actions/cache@v1
with:
path: parts/ffmpeg
key: ${{ runner.OS }}-ffmpeg-${{ env.CACHE_KEY }}-${{ env.FFMPEG_CACHE_KEY }}
- name: FFmpeg build.
if: steps.cache-ffmpeg.outputs.cache-hit != 'true'
run: sudo snap run snapcraft build --destructive-mode ffmpeg
- name: Telegram Desktop snap build.
if: env.ONLY_CACHE == 'false'
run: sudo snap run snapcraft --destructive-mode
- name: Move artifact.
@@ -126,8 +85,3 @@ jobs:
with:
name: ${{ env.ARTIFACT_NAME }}
path: artifact
- name: Remove unneeded directories for cache.
run: |
sudo rm -rf parts/*/{build,src,ubuntu}
sudo rm -rf parts/*/state/{stage,prime}

View File

@@ -308,7 +308,7 @@ jobs:
git submodule update qtbase
git submodule update qtimageformats
cd qtbase
git apply ../../patches/qtbase_%QT%.diff
for /r %%i in (..\..\patches\qtbase_%QT%\*) do git apply %%i
cd ..
SET SSL=%LibrariesPath%\openssl_1_1_1

5
.gitmodules vendored
View File

@@ -3,7 +3,7 @@
url = https://github.com/telegramdesktop/libtgvoip
[submodule "Telegram/ThirdParty/variant"]
path = Telegram/ThirdParty/variant
url = https://github.com/mapbox/variant
url = https://github.com/desktop-app/variant.git
[submodule "Telegram/ThirdParty/GSL"]
path = Telegram/ThirdParty/GSL
url = https://github.com/Microsoft/GSL.git
@@ -91,3 +91,6 @@
[submodule "Telegram/ThirdParty/libqtxdg"]
path = Telegram/ThirdParty/libqtxdg
url = https://github.com/lxqt/libqtxdg.git
[submodule "Telegram/ThirdParty/fcitx5-qt"]
path = Telegram/ThirdParty/fcitx5-qt
url = https://github.com/fcitx/fcitx5-qt.git

View File

@@ -13,18 +13,27 @@ The source code is published under GPLv3 with OpenSSL exception, the license is
## Supported systems
* Windows XP - Windows 10 (**not** RT)
* Mac OS X 10.8 - Mac OS X 10.15
* Mac OS X 10.6 - Mac OS X 10.7 (separate build)
* Ubuntu 12.04 - Ubuntu 20.04
* Fedora 22 - Fedora 31
* [Snappy](https://snapcraft.io/telegram-desktop)
* [Flathub](https://flathub.org/apps/details/org.telegram.desktop)
The latest version is available for
* [Windows 7 and above](https://telegram.org/dl/desktop/win) ([portable](https://telegram.org/dl/desktop/win_portable))
* [macOS 10.12 and above](https://telegram.org/dl/desktop/mac)
* [OS X 10.10 and 10.11](https://telegram.org/dl/desktop/osx)
* [Linux static build for 64 bit](https://telegram.org/dl/desktop/linux) ([32 bit](https://telegram.org/dl/desktop/linux32))
* [Snap](https://snapcraft.io/telegram-desktop)
* [Flatpak](https://flathub.org/apps/details/org.telegram.desktop)
## Old system versions
Version **1.8.15** was the last that supports older systems
* [Windows XP and Vista](https://updates.tdesktop.com/tsetup/tsetup.1.8.15.exe) ([portable](https://updates.tdesktop.com/tsetup/tportable.1.8.15.zip))
* [OS X 10.8 and 10.9](https://updates.tdesktop.com/tmac/tsetup.1.8.15.dmg)
* [OS X 10.6 and 10.7](https://updates.tdesktop.com/tmac32/tsetup32.1.8.15.dmg)
## Third-party
* Qt 5.12.8 and 5.6.2, slightly patched ([LGPL](http://doc.qt.io/qt-5/lgpl.html))
* OpenSSL 1.1.1 ([OpenSSL License](https://www.openssl.org/source/license.html))
* Qt 5.12.8, 5.6.2 and 5.3.2 slightly patched ([LGPL](http://doc.qt.io/qt-5/lgpl.html))
* OpenSSL 1.1.1 and 1.0.1 ([OpenSSL License](https://www.openssl.org/source/license.html))
* zlib 1.2.11 ([zlib License](http://www.zlib.net/zlib_license.html))
* LZMA SDK 9.20 ([public domain](http://www.7-zip.org/sdk.html))
* liblzma ([public domain](http://tukaani.org/xz/))
@@ -39,6 +48,7 @@ The source code is published under GPLv3 with OpenSSL exception, the license is
* Mapbox Variant ([BSD License](https://github.com/mapbox/variant/blob/master/LICENSE))
* Range-v3 ([Boost License](https://github.com/ericniebler/range-v3/blob/master/LICENSE.txt))
* Open Sans font ([Apache License 2.0](http://www.apache.org/licenses/LICENSE-2.0.html))
* Vazir font ([License](https://github.com/rastikerdar/vazir-font/blob/master/LICENSE))
* Emoji alpha codes ([MIT License](https://github.com/emojione/emojione/blob/master/extras/alpha-codes/LICENSE.md))
* Catch test framework ([Boost License](https://github.com/philsquared/Catch/blob/master/LICENSE.txt))
* xxHash ([BSD License](https://github.com/Cyan4973/xxHash/blob/dev/LICENSE))

View File

@@ -91,6 +91,7 @@ if (LINUX)
desktop-app::external_statusnotifieritem
desktop-app::external_dbusmenu_qt
desktop-app::external_fcitx_qt5
desktop-app::external_fcitx5_qt5
desktop-app::external_hime_qt
)
endif()
@@ -138,7 +139,7 @@ endif()
if (DESKTOP_APP_USE_PACKAGED)
set(CMAKE_THREAD_PREFER_PTHREAD TRUE)
find_package(Threads)
find_package(Threads REQUIRED)
target_link_libraries(Telegram
PRIVATE
@@ -263,6 +264,9 @@ PRIVATE
calls/calls_box_controller.h
calls/calls_call.cpp
calls/calls_call.h
calls/calls_controller.cpp
calls/calls_controller.h
calls/calls_controller_tgvoip.h
calls/calls_emoji_fingerprint.cpp
calls/calls_emoji_fingerprint.h
calls/calls_instance.cpp
@@ -297,6 +301,8 @@ PRIVATE
chat_helpers/stickers_dice_pack.h
chat_helpers/stickers_list_widget.cpp
chat_helpers/stickers_list_widget.h
chat_helpers/stickers_set.cpp
chat_helpers/stickers_set.h
chat_helpers/tabbed_panel.cpp
chat_helpers/tabbed_panel.h
chat_helpers/tabbed_section.cpp
@@ -325,7 +331,6 @@ PRIVATE
core/launcher.h
core/local_url_handlers.cpp
core/local_url_handlers.h
core/media_active_cache.h
core/mime_type.cpp
core/mime_type.h
core/sandbox.cpp
@@ -351,14 +356,16 @@ PRIVATE
data/data_channel.h
data/data_channel_admins.cpp
data/data_channel_admins.h
data/data_cloud_file.cpp
data/data_cloud_file.h
data/data_cloud_themes.cpp
data/data_cloud_themes.h
data/data_countries.cpp
data/data_countries.h
data/data_document.cpp
data/data_document.h
data/data_document_good_thumbnail.cpp
data/data_document_good_thumbnail.h
data/data_document_media.cpp
data/data_document_media.h
data/data_drafts.cpp
data/data_drafts.h
data/data_folder.cpp
@@ -389,10 +396,14 @@ PRIVATE
data/data_peer_values.h
data/data_photo.cpp
data/data_photo.h
data/data_photo_media.cpp
data/data_photo_media.h
data/data_poll.cpp
data/data_poll.h
data/data_pts_waiter.cpp
data/data_pts_waiter.h
data/data_reply_preview.cpp
data/data_reply_preview.h
data/data_search_controller.cpp
data/data_search_controller.h
data/data_session.cpp
@@ -753,6 +764,7 @@ PRIVATE
mtproto/type_utils.h
overview/overview_layout.cpp
overview/overview_layout.h
overview/overview_layout_delegate.h
passport/passport_encryption.cpp
passport/passport_encryption.h
passport/passport_form_controller.cpp
@@ -795,7 +807,6 @@ PRIVATE
platform/mac/file_utilities_mac.h
platform/mac/launcher_mac.mm
platform/mac/launcher_mac.h
platform/mac/mac_iconv_helper.c
platform/mac/main_window_mac.mm
platform/mac/main_window_mac.h
platform/mac/notifications_manager_mac.mm
@@ -922,8 +933,8 @@ PRIVATE
ui/image/image.h
ui/image/image_location.cpp
ui/image/image_location.h
ui/image/image_source.cpp
ui/image/image_source.h
ui/image/image_location_factory.cpp
ui/image/image_location_factory.h
ui/widgets/continuous_sliders.cpp
ui/widgets/continuous_sliders.h
ui/widgets/discrete_sliders.cpp
@@ -1033,6 +1044,8 @@ PRIVATE
if (DESKTOP_APP_USE_PACKAGED)
nice_target_sources(Telegram ${src_loc} PRIVATE qt_functions.cpp)
else()
nice_target_sources(Telegram ${src_loc} PRIVATE platform/mac/mac_iconv_helper.c)
endif()
nice_target_sources(Telegram ${res_loc}
@@ -1042,6 +1055,8 @@ PRIVATE
qrc/emoji_3.qrc
qrc/emoji_4.qrc
qrc/emoji_5.qrc
qrc/emoji_6.qrc
qrc/emoji_7.qrc
qrc/emoji_preview.qrc
qrc/telegram/telegram.qrc
qrc/telegram/sounds.qrc
@@ -1066,11 +1081,11 @@ if (WIN32)
# $<IF:${release},"Appending compatibility manifest.","Finalizing build.">
# )
elseif (APPLE)
target_link_libraries(Telegram
PRIVATE
desktop-app::external_sp_media_key_tap
desktop-app::external_iconv
)
target_link_libraries(Telegram PRIVATE desktop-app::external_sp_media_key_tap)
if (NOT DESKTOP_APP_USE_PACKAGED)
target_link_libraries(Telegram PRIVATE desktop-app::external_iconv)
endif()
set(icons_path ${CMAKE_CURRENT_SOURCE_DIR}/Telegram/Images.xcassets)
set_target_properties(Telegram PROPERTIES RESOURCE ${icons_path})
@@ -1210,6 +1225,10 @@ if ((NOT DESKTOP_APP_DISABLE_AUTOUPDATE OR NOT LINUX) AND NOT build_macstore AND
set_target_properties(Updater PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${output_folder})
if (WIN32 AND NOT CMAKE_CXX_COMPILER_ID STREQUAL "MSVC")
target_link_options(Updater PRIVATE -municode)
endif()
if (LINUX)
target_link_options(Updater PRIVATE -static-libstdc++)
endif()

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 MiB

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 MiB

After

Width:  |  Height:  |  Size: 1.3 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 MiB

After

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 MiB

After

Width:  |  Height:  |  Size: 1.4 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 894 KiB

After

Width:  |  Height:  |  Size: 1.4 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 862 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 98 KiB

View File

@@ -300,6 +300,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_settings_notifications_position" = "Location on the screen";
"lng_settings_notifications_count" = "Notifications count";
"lng_settings_sound_notify" = "Play sound";
"lng_settings_alert_windows" = "Flash the taskbar icon";
"lng_settings_alert_mac" = "Bounce the dock icon";
"lng_settings_alert_linux" = "Draw attention to the window";
"lng_settings_badge_title" = "Badge counter";
"lng_settings_include_muted" = "Include muted chats in unread count";
"lng_settings_count_unread" = "Count unread messages";
@@ -1451,6 +1454,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_context_edit_msg" = "Edit";
"lng_context_forward_msg" = "Forward Message";
"lng_context_send_now_msg" = "Send now";
"lng_context_reschedule" = "Reschedule";
"lng_context_delete_msg" = "Delete Message";
"lng_context_select_msg" = "Select Message";
"lng_context_report_msg" = "Report Message";
@@ -2146,6 +2150,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_export_option_size_limit" = "Size limit: {size}";
"lng_export_header_format" = "Location and format";
"lng_export_option_location" = "Download path: {path}";
"lng_export_option_format_location" = "Format: {format}, Path: {path}";
"lng_export_option_choose_format" = "Choose export format";
"lng_export_option_html" = "Human-readable HTML";
"lng_export_option_json" = "Machine-readable JSON";
"lng_export_limits" = "From: {from}, to: {till}";
@@ -2245,6 +2251,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_polls_votes_collapse" = "Collapse";
"lng_outdated_title" = "PLEASE UPDATE YOUR OPERATING SYSTEM.";
"lng_outdated_title_bits" = "PLEASE SWITCH TO 64 BIT OPERATING SYSTEM.";
"lng_outdated_soon" = "Otherwise, Telegram Desktop will stop updating on {date}.";
"lng_outdated_now" = "So that Telegram Desktop can update to newer versions.";

View File

@@ -0,0 +1,5 @@
<RCC>
<qresource prefix="/gui">
<file alias="emoji/emoji_6.webp">../emoji/emoji_6.webp</file>
</qresource>
</RCC>

View File

@@ -0,0 +1,5 @@
<RCC>
<qresource prefix="/gui">
<file alias="emoji/emoji_7.webp">../emoji/emoji_7.webp</file>
</qresource>
</RCC>

View File

@@ -357,6 +357,7 @@ updateMessagePollVote#42f88f2c poll_id:long user_id:int options:Vector<bytes> =
updateDialogFilter#26ffde7d flags:# id:int filter:flags.0?DialogFilter = Update;
updateDialogFilterOrder#a5d72105 order:Vector<int> = Update;
updateDialogFilters#3504914f = Update;
updatePhoneCallSignalingData#2661bf09 phone_call_id:long data:bytes = Update;
updates.state#a56c2a3e pts:int qts:int date:int seq:int unread_count:int = updates.State;
@@ -421,7 +422,7 @@ inputDocumentEmpty#72f0eaae = InputDocument;
inputDocument#1abfb575 id:long access_hash:long file_reference:bytes = InputDocument;
documentEmpty#36f8c871 id:long = Document;
document#9ba29cc1 flags:# id:long access_hash:long file_reference:bytes date:int mime_type:string size:int thumbs:flags.0?Vector<PhotoSize> dc_id:int attributes:Vector<DocumentAttribute> = Document;
document#1e87342b flags:# id:long access_hash:long file_reference:bytes date:int mime_type:string size:int thumbs:flags.0?Vector<PhotoSize> video_thumbs:flags.1?Vector<VideoSize> dc_id:int attributes:Vector<DocumentAttribute> = Document;
help.support#17c6b5f6 phone_number:string user:User = help.Support;
@@ -1140,6 +1141,8 @@ stats.broadcastStats#bdf78394 period:StatsDateRangeDays followers:StatsAbsValueA
help.promoDataEmpty#98f6ac75 expires:int = help.PromoData;
help.promoData#8c39793f flags:# proxy:flags.0?true expires:int peer:Peer chats:Vector<Chat> users:Vector<User> psa_type:flags.1?string psa_message:flags.2?string = help.PromoData;
videoSize#435bb987 type:string location:FileLocation w:int h:int size:int = VideoSize;
---functions---
invokeAfterMsg#cb9f372d {X:Type} msg_id:long query:!X = X;
@@ -1485,6 +1488,7 @@ phone.receivedCall#17d54f61 peer:InputPhoneCall = Bool;
phone.discardCall#b2cbc1c0 flags:# video:flags.0?true peer:InputPhoneCall duration:int reason:PhoneCallDiscardReason connection_id:long = Updates;
phone.setCallRating#59ead627 flags:# user_initiative:flags.0?true peer:InputPhoneCall rating:int comment:string = Updates;
phone.saveCallDebug#277add7e peer:InputPhoneCall debug:DataJSON = Bool;
phone.sendSignalingData#ff7a9383 peer:InputPhoneCall data:bytes = Bool;
langpack.getLangPack#f2f2330a lang_pack:string lang_code:string = LangPackDifference;
langpack.getStrings#efea3803 lang_pack:string lang_code:string keys:Vector<string> = Vector<LangPackString>;
@@ -1498,4 +1502,4 @@ folders.deleteFolder#1c295881 folder_id:int = Updates;
stats.getBroadcastStats#ab42441a flags:# dark:flags.0?true channel:InputChannel = stats.BroadcastStats;
stats.loadAsyncGraph#621d5fa0 flags:# token:string x:flags.0?long = StatsGraph;
// LAYER 113
// LAYER 114

View File

@@ -9,7 +9,7 @@
<Identity Name="TelegramMessengerLLP.TelegramDesktop"
ProcessorArchitecture="ARCHITECTURE"
Publisher="CN=536BC709-8EE1-4478-AF22-F0F0F26FF64A"
Version="2.1.2.0" />
Version="2.1.8.0" />
<Properties>
<DisplayName>Telegram Desktop</DisplayName>
<PublisherDisplayName>Telegram FZ-LLC</PublisherDisplayName>

View File

@@ -6,7 +6,18 @@
//
// Generated from the TEXTINCLUDE 2 resource.
//
#include "winres.h"
#if defined(__MINGW64__) || defined(__MINGW32__)
// MinGW-w64, MinGW
#if defined(__has_include) && __has_include(<winres.h>)
#include <winres.h>
#else
#include <afxres.h>
#include <winresrc.h>
#endif
#else
// MSVC, Windows SDK
#include <winres.h>
#endif
/////////////////////////////////////////////////////////////////////////////
#undef APSTUDIO_READONLY_SYMBOLS
@@ -33,8 +44,8 @@ IDI_ICON1 ICON "..\\art\\icon256.ico"
//
VS_VERSION_INFO VERSIONINFO
FILEVERSION 2,1,2,0
PRODUCTVERSION 2,1,2,0
FILEVERSION 2,1,8,0
PRODUCTVERSION 2,1,8,0
FILEFLAGSMASK 0x3fL
#ifdef _DEBUG
FILEFLAGS 0x1L
@@ -51,10 +62,10 @@ BEGIN
BEGIN
VALUE "CompanyName", "Telegram FZ-LLC"
VALUE "FileDescription", "Telegram Desktop"
VALUE "FileVersion", "2.1.2.0"
VALUE "FileVersion", "2.1.8.0"
VALUE "LegalCopyright", "Copyright (C) 2014-2020"
VALUE "ProductName", "Telegram Desktop"
VALUE "ProductVersion", "2.1.2.0"
VALUE "ProductVersion", "2.1.8.0"
END
END
BLOCK "VarFileInfo"

View File

@@ -6,7 +6,18 @@
//
// Generated from the TEXTINCLUDE 2 resource.
//
#include "winres.h"
#if defined(__MINGW64__) || defined(__MINGW32__)
// MinGW-w64, MinGW
#if defined(__has_include) && __has_include(<winres.h>)
#include <winres.h>
#else
#include <afxres.h>
#include <winresrc.h>
#endif
#else
// MSVC, Windows SDK
#include <winres.h>
#endif
/////////////////////////////////////////////////////////////////////////////
#undef APSTUDIO_READONLY_SYMBOLS
@@ -24,8 +35,8 @@ LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
//
VS_VERSION_INFO VERSIONINFO
FILEVERSION 2,1,2,0
PRODUCTVERSION 2,1,2,0
FILEVERSION 2,1,8,0
PRODUCTVERSION 2,1,8,0
FILEFLAGSMASK 0x3fL
#ifdef _DEBUG
FILEFLAGS 0x1L
@@ -42,10 +53,10 @@ BEGIN
BEGIN
VALUE "CompanyName", "Telegram FZ-LLC"
VALUE "FileDescription", "Telegram Desktop Updater"
VALUE "FileVersion", "2.1.2.0"
VALUE "FileVersion", "2.1.8.0"
VALUE "LegalCopyright", "Copyright (C) 2014-2020"
VALUE "ProductName", "Telegram Desktop"
VALUE "ProductVersion", "2.1.2.0"
VALUE "ProductVersion", "2.1.8.0"
END
END
BLOCK "VarFileInfo"

View File

@@ -268,7 +268,7 @@ int main(int argc, char *argv[])
cout << "Compression start, size: " << resultSize << "\n";
QByteArray compressed, resultCheck;
#ifdef Q_OS_WIN // use Lzma SDK for win
#if defined Q_OS_WIN && !defined DESKTOP_APP_USE_PACKAGED // use Lzma SDK for win
const int32 hSigLen = 128, hShaLen = 20, hPropsLen = LZMA_PROPS_SIZE, hOriginalSizeLen = sizeof(int32), hSize = hSigLen + hShaLen + hPropsLen + hOriginalSizeLen; // header
compressed.resize(hSize + resultSize + 1024 * 1024); // rsa signature + sha1 + lzma props + max compressed size

View File

@@ -27,7 +27,7 @@ extern "C" {
#include <openssl/evp.h>
} // extern "C"
#ifdef Q_OS_WIN // use Lzma SDK for win
#if defined Q_OS_WIN && !defined DESKTOP_APP_USE_PACKAGED // use Lzma SDK for win
#include <LzmaLib.h>
#else
#include <lzma.h>

View File

@@ -90,7 +90,7 @@ int main(int argc, const char * argv[]) {
openLog();
pid_t procId = 0;
BOOL update = YES, toSettings = NO, autoStart = NO, startInTray = NO, testMode = NO, externalUpdater = NO;
BOOL update = YES, toSettings = NO, autoStart = NO, startInTray = NO, testMode = NO, freeType = NO, externalUpdater = NO;
BOOL customWorkingDir = NO;
NSString *key = nil;
for (int i = 0; i < argc; ++i) {
@@ -116,6 +116,8 @@ int main(int argc, const char * argv[]) {
startInTray = YES;
} else if ([@"-testmode" isEqualToString:[NSString stringWithUTF8String:argv[i]]]) {
testMode = YES;
} else if ([@"-freetype" isEqualToString:[NSString stringWithUTF8String:argv[i]]]) {
freeType = YES;
} else if ([@"-externalupdater" isEqualToString:[NSString stringWithUTF8String:argv[i]]]) {
externalUpdater = YES;
} else if ([@"-workdir_custom" isEqualToString:[NSString stringWithUTF8String:argv[i]]]) {
@@ -255,6 +257,7 @@ int main(int argc, const char * argv[]) {
if (_debug) [args addObject:@"-debug"];
if (startInTray) [args addObject:@"-startintray"];
if (testMode) [args addObject:@"-testmode"];
if (freeType) [args addObject:@"-freetype"];
if (externalUpdater) [args addObject:@"-externalupdater"];
if (autoStart) [args addObject:@"-autostart"];
if (key) {

View File

@@ -339,7 +339,7 @@ int APIENTRY wWinMain(HINSTANCE instance, HINSTANCE prevInstance, LPWSTR cmdPara
LPWSTR *args;
int argsCount;
bool needupdate = false, autostart = false, debug = false, writeprotected = false, startintray = false, testmode = false, externalupdater = false;
bool needupdate = false, autostart = false, debug = false, writeprotected = false, startintray = false, testmode = false, freetype = false, externalupdater = false;
args = CommandLineToArgvW(GetCommandLine(), &argsCount);
if (args) {
for (int i = 1; i < argsCount; ++i) {
@@ -355,6 +355,8 @@ int APIENTRY wWinMain(HINSTANCE instance, HINSTANCE prevInstance, LPWSTR cmdPara
startintray = true;
} else if (equal(args[i], L"-testmode")) {
testmode = true;
} else if (equal(args[i], L"-freetype")) {
freetype = true;
} else if (equal(args[i], L"-externalupdater")) {
externalupdater = true;
} else if (equal(args[i], L"-writeprotected") && ++i < argsCount) {
@@ -427,6 +429,7 @@ int APIENTRY wWinMain(HINSTANCE instance, HINSTANCE prevInstance, LPWSTR cmdPara
if (debug) targs += L" -debug";
if (startintray) targs += L" -startintray";
if (testmode) targs += L" -testmode";
if (freetype) targs += L" -freetype";
if (externalupdater) targs += L" -externalupdater";
if (!customWorkingDir.empty()) {
targs += L" -workdir \"" + customWorkingDir + L"\"";

View File

@@ -30,6 +30,26 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
namespace Api {
namespace {
void InnerFillMessagePostFlags(
const Api::SendOptions &options,
not_null<PeerData*> peer,
MTPDmessage::Flags &flags) {
const auto channelPost = peer->isChannel() && !peer->isMegagroup();
if (!channelPost) {
flags |= MTPDmessage::Flag::f_from_id;
return;
}
flags |= MTPDmessage::Flag::f_post;
// Don't display views and author of a new post when it's scheduled.
if (options.scheduled) {
return;
}
flags |= MTPDmessage::Flag::f_views;
if (peer->asChannel()->addsSignature()) {
flags |= MTPDmessage::Flag::f_post_author;
}
}
template <typename MediaData>
void SendExistingMedia(
Api::MessageToSend &&message,
@@ -60,15 +80,7 @@ void SendExistingMedia(
const auto channelPost = peer->isChannel() && !peer->isMegagroup();
const auto silentPost = message.action.options.silent
|| (channelPost && session->data().notifySilentPosts(peer));
if (channelPost) {
flags |= MTPDmessage::Flag::f_views;
flags |= MTPDmessage::Flag::f_post;
}
if (!channelPost) {
flags |= MTPDmessage::Flag::f_from_id;
} else if (peer->asChannel()->addsSignature()) {
flags |= MTPDmessage::Flag::f_post_author;
}
InnerFillMessagePostFlags(message.action.options, peer, flags);
if (silentPost) {
sendFlags |= MTPmessages_SendMedia::Flag::f_silent;
}
@@ -246,15 +258,7 @@ bool SendDice(Api::MessageToSend &message) {
const auto channelPost = peer->isChannel() && !peer->isMegagroup();
const auto silentPost = message.action.options.silent
|| (channelPost && session->data().notifySilentPosts(peer));
if (channelPost) {
flags |= MTPDmessage::Flag::f_views;
flags |= MTPDmessage::Flag::f_post;
}
if (!channelPost) {
flags |= MTPDmessage::Flag::f_from_id;
} else if (peer->asChannel()->addsSignature()) {
flags |= MTPDmessage::Flag::f_post_author;
}
InnerFillMessagePostFlags(message.action.options, peer, flags);
if (silentPost) {
sendFlags |= MTPmessages_SendMedia::Flag::f_silent;
}
@@ -320,4 +324,11 @@ bool SendDice(Api::MessageToSend &message) {
return true;
}
void FillMessagePostFlags(
const Api::SendAction &action,
not_null<PeerData*> peer,
MTPDmessage::Flags &flags) {
InnerFillMessagePostFlags(action.options, peer, flags);
}
} // namespace Api

View File

@@ -25,4 +25,9 @@ void SendExistingPhoto(
bool SendDice(Api::MessageToSend &message);
void FillMessagePostFlags(
const SendAction &action,
not_null<PeerData*> peer,
MTPDmessage::Flags &flags);
} // namespace Api

View File

@@ -82,6 +82,7 @@ MTPVector<MTPMessageEntity> EntitiesToMTP(
if (entity.length() <= 0) continue;
if (option == ConvertOption::SkipLocal
&& entity.type() != EntityType::Bold
//&& entity.type() != EntityType::Semibold // Not in API.
&& entity.type() != EntityType::Italic
&& entity.type() != EntityType::Underline
&& entity.type() != EntityType::StrikeOut

View File

@@ -1865,7 +1865,7 @@ void ApiWrap::saveStickerSets(
auto &sets = _session->data().stickerSetsRef();
_stickersOrder = localOrder;
for_const (auto removedSetId, localRemoved) {
for (const auto removedSetId : localRemoved) {
if (removedSetId == Stickers::CloudRecentSetId) {
if (sets.remove(Stickers::CloudRecentSetId) != 0) {
writeCloudRecent = true;
@@ -1890,16 +1890,17 @@ void ApiWrap::saveStickerSets(
auto it = sets.find(removedSetId);
if (it != sets.cend()) {
const auto set = it->second.get();
for (auto i = recent.begin(); i != recent.cend();) {
if (it->stickers.indexOf(i->first) >= 0) {
if (set->stickers.indexOf(i->first) >= 0) {
i = recent.erase(i);
writeRecent = true;
} else {
++i;
}
}
if (!(it->flags & MTPDstickerSet::Flag::f_archived)) {
MTPInputStickerSet setId = (it->id && it->access) ? MTP_inputStickerSetID(MTP_long(it->id), MTP_long(it->access)) : MTP_inputStickerSetShortName(MTP_string(it->shortName));
if (!(set->flags & MTPDstickerSet::Flag::f_archived)) {
const auto setId = set->mtpInput();
auto requestId = request(MTPmessages_UninstallStickerSet(setId)).done([this](const MTPBool &result, mtpRequestId requestId) {
stickerSetDisenabled(requestId);
@@ -1909,60 +1910,69 @@ void ApiWrap::saveStickerSets(
_stickerSetDisenableRequests.insert(requestId);
int removeIndex = _session->data().stickerSetsOrder().indexOf(it->id);
int removeIndex = _session->data().stickerSetsOrder().indexOf(set->id);
if (removeIndex >= 0) _session->data().stickerSetsOrderRef().removeAt(removeIndex);
if (!(it->flags & MTPDstickerSet_ClientFlag::f_featured)
&& !(it->flags & MTPDstickerSet_ClientFlag::f_special)) {
if (!(set->flags & MTPDstickerSet_ClientFlag::f_featured)
&& !(set->flags & MTPDstickerSet_ClientFlag::f_special)) {
sets.erase(it);
} else {
if (it->flags & MTPDstickerSet::Flag::f_archived) {
if (set->flags & MTPDstickerSet::Flag::f_archived) {
writeArchived = true;
}
it->flags &= ~(MTPDstickerSet::Flag::f_installed_date | MTPDstickerSet::Flag::f_archived);
it->installDate = TimeId(0);
set->flags &= ~(MTPDstickerSet::Flag::f_installed_date | MTPDstickerSet::Flag::f_archived);
set->installDate = TimeId(0);
}
}
}
}
// Clear all installed flags, set only for sets from order.
for (auto &set : sets) {
if (!(set.flags & MTPDstickerSet::Flag::f_archived)) {
set.flags &= ~MTPDstickerSet::Flag::f_installed_date;
for (auto &[id, set] : sets) {
if (!(set->flags & MTPDstickerSet::Flag::f_archived)) {
set->flags &= ~MTPDstickerSet::Flag::f_installed_date;
}
}
auto &order = _session->data().stickerSetsOrderRef();
order.clear();
for_const (auto setId, _stickersOrder) {
for (const auto setId : std::as_const(_stickersOrder)) {
auto it = sets.find(setId);
if (it != sets.cend()) {
if ((it->flags & MTPDstickerSet::Flag::f_archived) && !localRemoved.contains(it->id)) {
MTPInputStickerSet mtpSetId = (it->id && it->access) ? MTP_inputStickerSetID(MTP_long(it->id), MTP_long(it->access)) : MTP_inputStickerSetShortName(MTP_string(it->shortName));
const auto set = it->second.get();
if ((set->flags & MTPDstickerSet::Flag::f_archived) && !localRemoved.contains(set->id)) {
const auto mtpSetId = set->mtpInput();
auto requestId = request(MTPmessages_InstallStickerSet(mtpSetId, MTP_boolFalse())).done([this](const MTPmessages_StickerSetInstallResult &result, mtpRequestId requestId) {
const auto requestId = request(MTPmessages_InstallStickerSet(
mtpSetId,
MTP_boolFalse()
)).done([=](
const MTPmessages_StickerSetInstallResult &result,
mtpRequestId requestId) {
stickerSetDisenabled(requestId);
}).fail([this](const RPCError &error, mtpRequestId requestId) {
}).fail([=](
const RPCError &error,
mtpRequestId requestId) {
stickerSetDisenabled(requestId);
}).afterDelay(kSmallDelayMs).send();
_stickerSetDisenableRequests.insert(requestId);
it->flags &= ~MTPDstickerSet::Flag::f_archived;
set->flags &= ~MTPDstickerSet::Flag::f_archived;
writeArchived = true;
}
order.push_back(setId);
it->flags |= MTPDstickerSet::Flag::f_installed_date;
if (!it->installDate) {
it->installDate = base::unixtime::now();
set->flags |= MTPDstickerSet::Flag::f_installed_date;
if (!set->installDate) {
set->installDate = base::unixtime::now();
}
}
}
for (auto it = sets.begin(); it != sets.cend();) {
if ((it->flags & MTPDstickerSet_ClientFlag::f_featured)
|| (it->flags & MTPDstickerSet::Flag::f_installed_date)
|| (it->flags & MTPDstickerSet::Flag::f_archived)
|| (it->flags & MTPDstickerSet_ClientFlag::f_special)) {
const auto set = it->second.get();
if ((set->flags & MTPDstickerSet_ClientFlag::f_featured)
|| (set->flags & MTPDstickerSet::Flag::f_installed_date)
|| (set->flags & MTPDstickerSet::Flag::f_archived)
|| (set->flags & MTPDstickerSet_ClientFlag::f_special)) {
++it;
} else {
it = sets.erase(it);
@@ -3334,14 +3344,14 @@ void ApiWrap::readFeaturedSetDelayed(uint64 setId) {
}
void ApiWrap::readFeaturedSets() {
auto &sets = _session->data().stickerSetsRef();
const auto &sets = _session->data().stickerSets();
auto count = _session->data().featuredStickerSetsUnreadCount();
QVector<MTPlong> wrappedIds;
wrappedIds.reserve(_featuredSetsRead.size());
for (auto setId : _featuredSetsRead) {
auto it = sets.find(setId);
for (const auto setId : _featuredSetsRead) {
const auto it = sets.find(setId);
if (it != sets.cend()) {
it->flags &= ~MTPDstickerSet_ClientFlag::f_unread;
it->second->flags &= ~MTPDstickerSet_ClientFlag::f_unread;
wrappedIds.append(MTP_long(setId));
if (count) {
--count;
@@ -4328,15 +4338,7 @@ void ApiWrap::forwardMessages(
auto flags = MTPDmessage::Flags(0);
auto clientFlags = MTPDmessage_ClientFlags();
auto sendFlags = MTPmessages_ForwardMessages::Flags(0);
if (channelPost) {
flags |= MTPDmessage::Flag::f_views;
flags |= MTPDmessage::Flag::f_post;
}
if (!channelPost) {
flags |= MTPDmessage::Flag::f_from_id;
} else if (peer->asChannel()->addsSignature()) {
flags |= MTPDmessage::Flag::f_post_author;
}
FillMessagePostFlags(action, peer, flags);
if (silentPost) {
sendFlags |= MTPmessages_ForwardMessages::Flag::f_silent;
}
@@ -4489,15 +4491,7 @@ void ApiWrap::sendSharedContact(
if (action.replyTo) {
flags |= MTPDmessage::Flag::f_reply_to_msg_id;
}
if (channelPost) {
flags |= MTPDmessage::Flag::f_views;
flags |= MTPDmessage::Flag::f_post;
if (peer->asChannel()->addsSignature()) {
flags |= MTPDmessage::Flag::f_post_author;
}
} else {
flags |= MTPDmessage::Flag::f_from_id;
}
FillMessagePostFlags(action, peer, flags);
if (action.options.scheduled) {
flags |= MTPDmessage::Flag::f_from_scheduled;
} else {
@@ -4874,15 +4868,7 @@ void ApiWrap::sendMessage(MessageToSend &&message) {
const auto channelPost = peer->isChannel() && !peer->isMegagroup();
const auto silentPost = action.options.silent
|| (channelPost && _session->data().notifySilentPosts(peer));
if (channelPost) {
flags |= MTPDmessage::Flag::f_views;
flags |= MTPDmessage::Flag::f_post;
}
if (!channelPost) {
flags |= MTPDmessage::Flag::f_from_id;
} else if (peer->asChannel()->addsSignature()) {
flags |= MTPDmessage::Flag::f_post_author;
}
FillMessagePostFlags(action, peer, flags);
if (silentPost) {
sendFlags |= MTPmessages_SendMessage::Flag::f_silent;
}
@@ -5019,15 +5005,7 @@ void ApiWrap::sendInlineResult(
bool channelPost = peer->isChannel() && !peer->isMegagroup();
bool silentPost = action.options.silent
|| (channelPost && _session->data().notifySilentPosts(peer));
if (channelPost) {
flags |= MTPDmessage::Flag::f_views;
flags |= MTPDmessage::Flag::f_post;
}
if (!channelPost) {
flags |= MTPDmessage::Flag::f_from_id;
} else if (peer->asChannel()->addsSignature()) {
flags |= MTPDmessage::Flag::f_post_author;
}
FillMessagePostFlags(action, peer, flags);
if (silentPost) {
sendFlags |= MTPmessages_SendInlineBotResult::Flag::f_silent;
}
@@ -5857,6 +5835,43 @@ void ApiWrap::closePoll(not_null<HistoryItem*> item) {
_pollCloseRequestIds.emplace(itemId, requestId);
}
void ApiWrap::rescheduleMessage(
not_null<HistoryItem*> item,
Api::SendOptions options) {
const auto text = item->originalText().text;
const auto sentEntities = Api::EntitiesToMTP(
item->originalText().entities,
Api::ConvertOption::SkipLocal);
const auto media = item->media();
const auto emptyFlag = MTPmessages_EditMessage::Flag(0);
const auto flags = MTPmessages_EditMessage::Flag::f_schedule_date
| (!text.isEmpty()
? MTPmessages_EditMessage::Flag::f_message
: emptyFlag)
| ((!media || !media->webpage())
? MTPmessages_EditMessage::Flag::f_no_webpage
: emptyFlag)
| (!sentEntities.v.isEmpty()
? MTPmessages_EditMessage::Flag::f_entities
: emptyFlag);
const auto id = _session->data().scheduledMessages().lookupId(item);
request(MTPmessages_EditMessage(
MTP_flags(flags),
item->history()->peer->input,
MTP_int(id),
MTP_string(text),
MTPInputMedia(),
MTPReplyMarkup(),
sentEntities,
MTP_int(options.scheduled)
)).done([=](const MTPUpdates &result) {
applyUpdates(result);
}).fail([](const RPCError &error) {
}).send();
}
void ApiWrap::reloadPollResults(not_null<HistoryItem*> item) {
const auto itemId = item->fullId();
if (!IsServerMsgId(item->id)

View File

@@ -12,7 +12,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "base/flat_map.h"
#include "base/flat_set.h"
#include "mtproto/sender.h"
#include "chat_helpers/stickers.h"
#include "chat_helpers/stickers_set.h"
#include "data/data_messages.h"
class TaskQueue;
@@ -476,6 +476,10 @@ public:
void closePoll(not_null<HistoryItem*> item);
void reloadPollResults(not_null<HistoryItem*> item);
void rescheduleMessage(
not_null<HistoryItem*> item,
Api::SendOptions options);
private:
struct MessageDataRequest {
using Callbacks = QList<RequestMessageDataCallback>;

View File

@@ -227,8 +227,6 @@ namespace App {
clearCorners();
Data::clearGlobalStructures();
Images::ClearAll();
}
void hoveredItem(HistoryView::Element *item) {
@@ -322,6 +320,9 @@ namespace App {
}
QImage readImage(QByteArray data, QByteArray *format, bool opaque, bool *animated) {
if (data.isEmpty()) {
return QImage();
}
QByteArray tmpFormat;
QImage result;
QBuffer buffer(&data);

View File

@@ -11,23 +11,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/rect_part.h"
enum class ImageRoundRadius;
class MainWindow;
class MainWidget;
class HistoryItem;
class History;
namespace HistoryView {
class Element;
} // namespace HistoryView
namespace Media {
namespace Clip {
class Reader;
} // namespace Clip
} // namespace Media
using HistoryItemsMap = base::flat_set<not_null<HistoryItem*>>;
using GifItems = QHash<Media::Clip::Reader*, HistoryItem*>;
enum RoundCorners : int {
SmallMaskCorners = 0x00, // for images
LargeMaskCorners,

View File

@@ -34,6 +34,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_chat.h"
#include "data/data_user.h"
#include "data/data_session.h"
#include "data/data_cloud_file.h"
#include "mainwidget.h"
#include "mainwindow.h"
#include "apiwrap.h"
@@ -222,6 +223,7 @@ private:
}
not_null<PeerData*> peer;
mutable std::shared_ptr<Data::CloudImageView> userpic;
Ui::Text::String name, status;
};
void paintChat(Painter &p, const ChatRow &row, bool selected) const;
@@ -1438,7 +1440,7 @@ void RevokePublicLinkBox::resizeEvent(QResizeEvent *e) {
void RevokePublicLinkBox::Inner::paintChat(Painter &p, const ChatRow &row, bool selected) const {
auto peer = row.peer;
peer->paintUserpicLeft(p, st::contactsPadding.left(), st::contactsPadding.top(), width(), st::contactsPhotoSize);
peer->paintUserpicLeft(p, row.userpic, st::contactsPadding.left(), st::contactsPadding.top(), width(), st::contactsPhotoSize);
p.setPen(st::contactsNameFg);

View File

@@ -16,6 +16,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "mtproto/sender.h"
#include "data/data_session.h"
#include "data/data_file_origin.h"
#include "data/data_document.h"
#include "data/data_document_media.h"
#include "boxes/background_preview_box.h"
#include "boxes/confirm_box.h"
#include "app.h"
@@ -53,28 +55,33 @@ QImage TakeMiddleSample(QImage original, QSize size) {
} // namespace
class BackgroundBox::Inner : public Ui::RpWidget, private base::Subscriber {
class BackgroundBox::Inner final
: public Ui::RpWidget
, private base::Subscriber {
public:
Inner(
QWidget *parent,
not_null<Main::Session*> session);
~Inner();
rpl::producer<Data::WallPaper> chooseEvents() const;
rpl::producer<Data::WallPaper> removeRequests() const;
void removePaper(const Data::WallPaper &data);
~Inner();
protected:
private:
void paintEvent(QPaintEvent *e) override;
void mouseMoveEvent(QMouseEvent *e) override;
void mousePressEvent(QMouseEvent *e) override;
void mouseReleaseEvent(QMouseEvent *e) override;
private:
void visibleTopBottomUpdated(
int visibleTop,
int visibleBottom) override;
struct Paper {
Data::WallPaper data;
mutable std::shared_ptr<Data::DocumentMedia> dataMedia;
mutable QPixmap thumbnail;
};
struct Selected {
@@ -252,11 +259,19 @@ void BackgroundBox::Inner::resizeToContentAndPreload() {
const auto rows = (count / kBackgroundsInRow)
+ (count % kBackgroundsInRow ? 1 : 0);
resize(st::boxWideWidth, rows * (st::backgroundSize.height() + st::backgroundPadding) + st::backgroundPadding);
resize(
st::boxWideWidth,
(rows * (st::backgroundSize.height() + st::backgroundPadding)
+ st::backgroundPadding));
const auto preload = kBackgroundsInRow * 3;
for (const auto &paper : _papers | ranges::view::take(preload)) {
paper.data.loadThumbnail();
if (!paper.data.localThumbnail() && !paper.dataMedia) {
if (const auto document = paper.data.document()) {
paper.dataMedia = document->createMediaView();
paper.dataMedia->thumbnailWanted(paper.data.fileOrigin());
}
}
}
update();
}
@@ -292,15 +307,24 @@ void BackgroundBox::Inner::paintEvent(QPaintEvent *e) {
void BackgroundBox::Inner::validatePaperThumbnail(
const Paper &paper) const {
Expects(paper.data.thumbnail() != nullptr);
const auto thumbnail = paper.data.thumbnail();
if (!paper.thumbnail.isNull()) {
return;
} else if (!thumbnail->loaded()) {
thumbnail->load(paper.data.fileOrigin());
return;
}
const auto localThumbnail = paper.data.localThumbnail();
if (!localThumbnail) {
if (const auto document = paper.data.document()) {
if (!paper.dataMedia) {
paper.dataMedia = document->createMediaView();
paper.dataMedia->thumbnailWanted(paper.data.fileOrigin());
}
}
if (!paper.dataMedia || !paper.dataMedia->thumbnail()) {
return;
}
}
const auto thumbnail = localThumbnail
? localThumbnail
: paper.dataMedia->thumbnail();
auto original = thumbnail->original();
if (paper.data.isPattern()) {
const auto color = *paper.data.backgroundColor();
@@ -428,7 +452,14 @@ void BackgroundBox::Inner::mouseReleaseEvent(QMouseEvent *e) {
if (base::get_if<DeleteSelected>(&_over)) {
_backgroundRemove.fire_copy(_papers[index].data);
} else if (base::get_if<Selected>(&_over)) {
_backgroundChosen.fire_copy(_papers[index].data);
auto &paper = _papers[index];
if (!paper.dataMedia) {
if (const auto document = paper.data.document()) {
// Keep it alive while it is on the screen.
paper.dataMedia = document->createMediaView();
}
}
_backgroundChosen.fire_copy(paper.data);
}
}
} else if (!_over.has_value()) {
@@ -436,6 +467,22 @@ void BackgroundBox::Inner::mouseReleaseEvent(QMouseEvent *e) {
}
}
void BackgroundBox::Inner::visibleTopBottomUpdated(
int visibleTop,
int visibleBottom) {
for (auto i = 0, count = int(_papers.size()); i != count; ++i) {
const auto row = (i / kBackgroundsInRow);
const auto height = st::backgroundSize.height();
const auto skip = st::backgroundPadding;
const auto top = skip + row * (height + skip);
const auto bottom = top + height;
if ((bottom <= visibleTop || top >= visibleBottom)
&& !_papers[i].thumbnail.isNull()) {
_papers[i].dataMedia = nullptr;
}
}
}
rpl::producer<Data::WallPaper> BackgroundBox::Inner::chooseEvents() const {
return _backgroundChosen.events();
}

View File

@@ -22,6 +22,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_session.h"
#include "data/data_user.h"
#include "data/data_document.h"
#include "data/data_document_media.h"
#include "data/data_file_origin.h"
#include "base/unixtime.h"
#include "boxes/confirm_box.h"
#include "boxes/background_preview_box.h"
@@ -410,7 +412,11 @@ BackgroundPreviewBox::BackgroundPreviewBox(
tr::lng_background_text2(tr::now),
true))
, _paper(paper)
, _media(_paper.document() ? _paper.document()->createMediaView() : nullptr)
, _radial([=](crl::time now) { radialAnimationCallback(now); }) {
if (_media) {
_media->thumbnailWanted(_paper.fileOrigin());
}
subscribe(_session->downloaderTaskFinished(), [=] { update(); });
}
@@ -428,12 +434,14 @@ void BackgroundPreviewBox::prepare() {
}
updateServiceBg(_paper.backgroundColor());
_paper.loadThumbnail();
_paper.loadDocument();
if (_paper.document() && _paper.document()->loading()) {
_radial.start(_paper.document()->progress());
const auto document = _paper.document();
if (document && document->loading()) {
_radial.start(_media->progress());
}
if (_paper.thumbnail() && !_paper.isPattern()) {
if (!_paper.isPattern()
&& (_paper.localThumbnail()
|| (document && document->hasThumbnail()))) {
createBlurCheckbox();
}
setScaledFromThumb();
@@ -634,7 +642,7 @@ void BackgroundPreviewBox::radialAnimationCallback(crl::time now) {
const auto document = _paper.document();
const auto wasAnimating = _radial.animating();
const auto updated = _radial.update(
document->progress(),
_media->progress(),
!document->loading(),
now);
if ((wasAnimating || _radial.animating())
@@ -645,8 +653,13 @@ void BackgroundPreviewBox::radialAnimationCallback(crl::time now) {
}
bool BackgroundPreviewBox::setScaledFromThumb() {
const auto thumbnail = _paper.thumbnail();
if (!thumbnail || !thumbnail->loaded()) {
const auto localThumbnail = _paper.localThumbnail();
const auto thumbnail = localThumbnail
? localThumbnail
: _media
? _media->thumbnail()
: nullptr;
if (!thumbnail) {
return false;
} else if (_paper.isPattern() && _paper.document() != nullptr) {
return false;
@@ -710,7 +723,7 @@ void BackgroundPreviewBox::checkLoadedDocument() {
const auto document = _paper.document();
if (!_full.isNull()
|| !document
|| !document->loaded(DocumentData::FilePathResolve::Checked)
|| !_media->loaded(true)
|| _generating) {
return;
}
@@ -744,7 +757,7 @@ void BackgroundPreviewBox::checkLoadedDocument() {
});
};
_generating = Data::ReadImageAsync(
document,
_media.get(),
Window::Theme::ProcessBackgroundImage,
generateCallback);
}

View File

@@ -15,6 +15,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/effects/animations.h"
#include "ui/effects/radial_animation.h"
namespace Data {
class DocumentMedia;
} // namespace Data
namespace Main {
class Session;
} // namespace Main
@@ -71,6 +75,7 @@ private:
AdminLog::OwnedItem _text1;
AdminLog::OwnedItem _text2;
Data::WallPaper _paper;
std::shared_ptr<Data::DocumentMedia> _media;
QImage _full;
QPixmap _scaled, _blurred, _fadeOutThumbnail;
Ui::Animations::Simple _fadeIn;

View File

@@ -915,7 +915,7 @@ blockUserConfirmation: FlatLabel(boxLabel) {
minWidth: 240px;
}
transferCheckWidth: 300px;
transferCheckWidth: 320px;
slowmodeLabelsMargin: margins(0px, 5px, 0px, 0px);
slowmodeLabel: LabelSimple(defaultLabelSimple) {

View File

@@ -599,4 +599,19 @@ void CalendarBox::keyPressEvent(QKeyEvent *e) {
}
}
void CalendarBox::wheelEvent(QWheelEvent *e) {
// Only a mouse wheel is accepted.
constexpr auto step = static_cast<int>(QWheelEvent::DefaultDeltasPerStep);
const auto delta = e->angleDelta().y();
if (std::abs(delta) != step) {
return;
}
if (delta < 0) {
goPreviousMonth();
} else {
goNextMonth();
}
}
CalendarBox::~CalendarBox() = default;

View File

@@ -46,6 +46,7 @@ protected:
void keyPressEvent(QKeyEvent *e) override;
void resizeEvent(QResizeEvent *e) override;
void wheelEvent(QWheelEvent *e) override;
private:
void monthChanged(QDate month);

View File

@@ -19,6 +19,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "main/main_session.h"
#include "data/data_session.h"
#include "data/data_user.h"
#include "mtproto/facade.h"
#include "app.h"
#include "styles/style_layers.h"
#include "styles/style_boxes.h"

View File

@@ -32,6 +32,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_user.h"
#include "data/data_file_origin.h"
#include "data/data_histories.h"
#include "data/data_photo_media.h"
#include "base/unixtime.h"
#include "main/main_session.h"
#include "observer_peer.h"
@@ -894,12 +895,12 @@ ConfirmInviteBox::ConfirmInviteBox(
const auto photo = _session->data().processPhoto(data.vphoto());
if (!photo->isNull()) {
_photo = photo->thumbnail();
if (!_photo->loaded()) {
_photo = photo->createMediaView();
_photo->wanted(Data::PhotoSize::Small, Data::FileOrigin());
if (!_photo->image(Data::PhotoSize::Small)) {
subscribe(_session->downloaderTaskFinished(), [=] {
update();
});
_photo->load(Data::FileOrigin());
}
} else {
_photoEmpty = std::make_unique<Ui::EmptyUserpic>(
@@ -908,19 +909,20 @@ ConfirmInviteBox::ConfirmInviteBox(
}
}
std::vector<not_null<UserData*>> ConfirmInviteBox::GetParticipants(
not_null<Main::Session*> session,
const MTPDchatInvite &data) {
auto ConfirmInviteBox::GetParticipants(
not_null<Main::Session*> session,
const MTPDchatInvite &data)
-> std::vector<Participant> {
const auto participants = data.vparticipants();
if (!participants) {
return {};
}
const auto &v = participants->v;
auto result = std::vector<not_null<UserData*>>();
auto result = std::vector<Participant>();
result.reserve(v.size());
for (const auto &participant : v) {
if (const auto user = session->data().processUser(participant)) {
result.push_back(user);
result.push_back(Participant{ user });
}
}
return result;
@@ -945,12 +947,12 @@ void ConfirmInviteBox::prepare() {
_userWidth = (st::confirmInviteUserPhotoSize + 2 * padding);
int sumWidth = _participants.size() * _userWidth;
int left = (st::boxWideWidth - sumWidth) / 2;
for (const auto user : _participants) {
for (const auto &participant : _participants) {
auto name = new Ui::FlatLabel(this, st::confirmInviteUserName);
name->resizeToWidth(st::confirmInviteUserPhotoSize + padding);
name->setText(user->firstName.isEmpty()
? user->name
: user->firstName);
name->setText(participant.user->firstName.isEmpty()
? participant.user->name
: participant.user->firstName);
name->moveToLeft(left + (padding / 2), st::confirmInviteUserNameTop);
left += _userWidth;
}
@@ -972,14 +974,15 @@ void ConfirmInviteBox::paintEvent(QPaintEvent *e) {
Painter p(this);
if (_photo) {
p.drawPixmap(
(width() - st::confirmInvitePhotoSize) / 2,
st::confirmInvitePhotoTop,
_photo->pixCircled(
Data::FileOrigin(),
st::confirmInvitePhotoSize,
st::confirmInvitePhotoSize));
} else {
if (const auto image = _photo->image(Data::PhotoSize::Small)) {
p.drawPixmap(
(width() - st::confirmInvitePhotoSize) / 2,
st::confirmInvitePhotoTop,
image->pixCircled(
st::confirmInvitePhotoSize,
st::confirmInvitePhotoSize));
}
} else if (_photoEmpty) {
_photoEmpty->paint(
p,
(width() - st::confirmInvitePhotoSize) / 2,
@@ -990,9 +993,10 @@ void ConfirmInviteBox::paintEvent(QPaintEvent *e) {
int sumWidth = _participants.size() * _userWidth;
int left = (width() - sumWidth) / 2;
for_const (auto user, _participants) {
user->paintUserpicLeft(
for (auto &participant : _participants) {
participant.user->paintUserpicLeft(
p,
participant.userpic,
left + (_userWidth - st::confirmInviteUserPhotoSize) / 2,
st::confirmInviteUserPhotoTop,
width(),

View File

@@ -10,6 +10,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "boxes/abstract_box.h"
#include "mtproto/mtproto_rpc_sender.h"
namespace Data {
class PhotoMedia;
class CloudImageView;
} // namespace Data
namespace Main {
class Session;
} // namespace Main
@@ -221,7 +226,11 @@ protected:
void paintEvent(QPaintEvent *e) override;
private:
static std::vector<not_null<UserData*>> GetParticipants(
struct Participant {
not_null<UserData*> user;
std::shared_ptr<Data::CloudImageView> userpic;
};
static std::vector<Participant> GetParticipants(
not_null<Main::Session*> session,
const MTPDchatInvite &data);
@@ -230,9 +239,9 @@ private:
Fn<void()> _submit;
object_ptr<Ui::FlatLabel> _title;
object_ptr<Ui::FlatLabel> _status;
Image *_photo = nullptr;
std::shared_ptr<Data::PhotoMedia> _photo;
std::unique_ptr<Ui::EmptyUserpic> _photoEmpty;
std::vector<not_null<UserData*>> _participants;
std::vector<Participant> _participants;
bool _isChannel = false;
int _userWidth = 0;

View File

@@ -83,7 +83,7 @@ private:
void show(anim::type animated);
void destroy(FnMut<void()> done);
[[nodisacrd]] bool hasShadow() const;
[[nodiscard]] bool hasShadow() const;
void createShadow();
void destroyShadow();

View File

@@ -22,12 +22,19 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_photo.h"
#include "data/data_user.h"
#include "data/data_session.h"
#include "data/data_streaming.h"
#include "data/data_file_origin.h"
#include "data/data_photo_media.h"
#include "data/data_document_media.h"
#include "history/history.h"
#include "history/history_item.h"
#include "lang/lang_keys.h"
#include "layout.h"
#include "media/clip/media_clip_reader.h"
#include "media/streaming/media_streaming_instance.h"
#include "media/streaming/media_streaming_player.h"
#include "media/streaming/media_streaming_document.h"
#include "media/streaming/media_streaming_loader_local.h"
#include "storage/localimageloader.h"
#include "storage/storage_media_prepare.h"
#include "ui/image/image.h"
#include "ui/widgets/input_fields.h"
@@ -46,6 +53,13 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include <QtCore/QMimeData>
namespace {
using namespace ::Media::Streaming;
using Data::PhotoSize;
} // namespace
EditCaptionBox::EditCaptionBox(
QWidget*,
not_null<Window::SessionController*> controller,
@@ -60,32 +74,46 @@ EditCaptionBox::EditCaptionBox(
QSize dimensions;
auto image = (Image*)nullptr;
DocumentData *doc = nullptr;
const auto media = item->media();
if (const auto photo = media->photo()) {
_photoMedia = photo->createMediaView();
_photoMedia->wanted(PhotoSize::Large, _msgId);
image = _photoMedia->image(PhotoSize::Large);
if (!image) {
image = _photoMedia->image(PhotoSize::Thumbnail);
if (!image) {
image = _photoMedia->image(PhotoSize::Small);
if (!image) {
image = _photoMedia->thumbnailInline();
}
}
}
dimensions = _photoMedia->size(PhotoSize::Large);
_photo = true;
dimensions = QSize(photo->width(), photo->height());
image = photo->large();
} else if (const auto document = media->document()) {
image = document->thumbnail();
_documentMedia = document->createMediaView();
_documentMedia->thumbnailWanted(_msgId);
image = _documentMedia->thumbnail();
dimensions = image
? image->size()
: document->dimensions;
if (document->isAnimation()) {
_gifw = document->dimensions.width();
_gifh = document->dimensions.height();
_gifw = style::ConvertScale(document->dimensions.width());
_gifh = style::ConvertScale(document->dimensions.height());
_animated = true;
} else if (document->isVideoFile()) {
_animated = true;
} else {
_doc = true;
}
doc = document;
}
const auto editData = PrepareEditText(item);
if (!_animated && (dimensions.isEmpty() || doc || !image)) {
if (!_animated
&& (dimensions.isEmpty()
|| _documentMedia
|| (!_photoMedia && !image))) {
if (!image) {
_thumbw = 0;
} else {
@@ -104,7 +132,7 @@ EditCaptionBox::EditCaptionBox(
| Images::Option::RoundedBottomLeft
| Images::Option::RoundedBottomRight;
_thumb = App::pixmapFromImageInPlace(Images::prepare(
image->pix(_msgId).toImage(),
image->original(),
_thumbw * cIntRetinaFactor(),
0,
options,
@@ -113,19 +141,21 @@ EditCaptionBox::EditCaptionBox(
};
}
if (doc) {
const auto nameString = doc->isVoiceMessage()
if (_documentMedia) {
const auto document = _documentMedia->owner();
const auto nameString = document->isVoiceMessage()
? tr::lng_media_audio(tr::now)
: doc->composeNameString();
setName(nameString, doc->size);
_isImage = doc->isImage();
_isAudio = (doc->isVoiceMessage() || doc->isAudioFile());
: document->composeNameString();
setName(nameString, document->size);
_isImage = document->isImage();
_isAudio = document->isVoiceMessage()
|| document->isAudioFile();
}
if (_refreshThumbnail) {
_refreshThumbnail();
}
} else {
if (!image) {
if (!image && !_photoMedia) {
image = Image::BlankMedia();
}
auto maxW = 0, maxH = 0;
@@ -150,24 +180,34 @@ EditCaptionBox::EditCaptionBox(
const auto options = Images::Option::Smooth
| Images::Option::Blurred;
_thumb = image->pixNoCache(
_msgId,
maxW * cIntRetinaFactor(),
maxH * cIntRetinaFactor(),
options,
maxW,
maxH);
};
prepareGifPreview(doc);
prepareStreamedPreview();
} else {
maxW = dimensions.width();
maxH = dimensions.height();
_thumbnailImage = image;
_refreshThumbnail = [=] {
_thumb = image->pixNoCache(
_msgId,
const auto photo = _photoMedia
? _photoMedia->image(Data::PhotoSize::Large)
: nullptr;
const auto use = photo
? photo
: _thumbnailImage
? _thumbnailImage
: Image::BlankMedia().get();
const auto options = Images::Option::Smooth
| ((_photoMedia && !photo)
? Images::Option::Blurred
: Images::Option(0));
_thumb = use->pixNoCache(
maxW * cIntRetinaFactor(),
maxH * cIntRetinaFactor(),
Images::Option::Smooth,
options,
maxW,
maxH);
};
@@ -205,7 +245,7 @@ EditCaptionBox::EditCaptionBox(
thumbX = (st::boxWideWidth - thumbWidth) / 2;
};
if (doc && doc->isAnimation()) {
if (_documentMedia && _documentMedia->owner()->isAnimation()) {
resizeDimensions(_gifw, _gifh, _gifx);
}
limitH = std::min(st::confirmMaxHeight, _gifh ? _gifh : INT_MAX);
@@ -237,22 +277,36 @@ EditCaptionBox::EditCaptionBox(
}
Assert(_animated || _photo || _doc);
_thumbnailImageLoaded = _thumbnailImage
? _thumbnailImage->loaded()
_thumbnailImageLoaded = _photoMedia
? (_photoMedia->image(Data::PhotoSize::Large) != nullptr)
: _thumbnailImage
? true
: _documentMedia
? !_documentMedia->owner()->hasThumbnail()
: true;
subscribe(_controller->session().downloaderTaskFinished(), [=] {
if (!_thumbnailImageLoaded
&& _thumbnailImage
&& _thumbnailImage->loaded()) {
_thumbnailImageLoaded = true;
_refreshThumbnail();
update();
}
if (doc && doc->isAnimation() && doc->loaded() && !_gifPreview) {
prepareGifPreview(doc);
}
});
if (!_thumbnailImageLoaded) {
subscribe(_controller->session().downloaderTaskFinished(), [=] {
if (_thumbnailImageLoaded) {
return;
} else if (!_thumbnailImage
&& _photoMedia
&& _photoMedia->image(PhotoSize::Large)) {
_thumbnailImage = _photoMedia->image(PhotoSize::Large);
} else if (!_thumbnailImage
&& _documentMedia
&& _documentMedia->owner()->hasThumbnail()) {
_thumbnailImage = _documentMedia->thumbnail();
}
if (_thumbnailImage) {
_thumbnailImageLoaded = !_photoMedia
|| _photoMedia->image(PhotoSize::Large);
if (_thumbnailImageLoaded) {
_refreshThumbnail();
update();
}
}
});
}
_field.create(
this,
st::confirmCaptionArea,
@@ -287,6 +341,8 @@ EditCaptionBox::EditCaptionBox(
}, _wayWrap->lifetime());
}
EditCaptionBox::~EditCaptionBox() = default;
void EditCaptionBox::emojiFilterForGeometry(not_null<QEvent*> event) {
const auto type = event->type();
if (type == QEvent::Move || type == QEvent::Resize) {
@@ -305,74 +361,86 @@ void EditCaptionBox::updateEmojiPanelGeometry() {
local.x() + _emojiToggle->width() * 3);
}
void EditCaptionBox::prepareGifPreview(DocumentData* document) {
void EditCaptionBox::prepareStreamedPreview() {
const auto isListEmpty = _preparedList.files.empty();
if (_gifPreview) {
if (_streamed) {
return;
} else if (!document && isListEmpty) {
} else if (!_documentMedia && isListEmpty) {
return;
}
const auto callback = [=](Media::Clip::Notification notification) {
clipCallback(notification);
};
if (document && document->isAnimation() && document->loaded()) {
_gifPreview = Media::Clip::MakeReader(
document,
_msgId,
callback);
const auto document = _documentMedia
? _documentMedia->owner().get()
: nullptr;
if (document && document->isAnimation()) {
setupStreamedPreview(
document->owner().streaming().sharedDocument(
document,
_msgId));
} else if (!isListEmpty) {
const auto file = &_preparedList.files.front();
if (file->path.isEmpty()) {
_gifPreview = Media::Clip::MakeReader(
file->content,
callback);
} else {
_gifPreview = Media::Clip::MakeReader(
file->path,
callback);
}
auto loader = file->path.isEmpty()
? MakeBytesLoader(file->content)
: MakeFileLoader(file->path);
setupStreamedPreview(std::make_shared<Document>(std::move(loader)));
}
if (_gifPreview) _gifPreview->setAutoplay();
}
void EditCaptionBox::clipCallback(Media::Clip::Notification notification) {
using namespace Media::Clip;
switch (notification) {
case NotificationReinit: {
if (_gifPreview && _gifPreview->state() == State::Error) {
_gifPreview.setBad();
}
void EditCaptionBox::setupStreamedPreview(std::shared_ptr<Document> shared) {
if (!shared) {
return;
}
_streamed = std::make_unique<Instance>(
std::move(shared),
[=] { update(); });
_streamed->lockPlayer();
_streamed->player().updates(
) | rpl::start_with_next_error([=](Update &&update) {
handleStreamingUpdate(std::move(update));
}, [=](Error &&error) {
handleStreamingError(std::move(error));
}, _streamed->lifetime());
if (_gifPreview && _gifPreview->ready() && !_gifPreview->started()) {
const auto calculateGifDimensions = [&]() {
const auto scaled = QSize(
_gifPreview->width(),
_gifPreview->height()).scaled(
st::sendMediaPreviewSize * cIntRetinaFactor(),
st::confirmMaxHeight * cIntRetinaFactor(),
Qt::KeepAspectRatio);
_thumbw = _gifw = scaled.width();
_thumbh = _gifh = scaled.height();
_thumbx = _gifx = (st::boxWideWidth - _gifw) / 2;
updateBoxSize();
};
// If gif file is not mp4,
// Its dimension values will be known only after reading.
if (_gifw <= 0 || _gifh <= 0) {
calculateGifDimensions();
}
const auto s = QSize(_gifw, _gifh);
_gifPreview->start(s.width(), s.height(), s.width(), s.height(), ImageRoundRadius::None, RectPart::None);
}
if (_streamed->ready()) {
streamingReady(base::duplicate(_streamed->info()));
}
checkStreamedIsStarted();
}
update();
} break;
void EditCaptionBox::handleStreamingUpdate(Update &&update) {
update.data.match([&](Information &update) {
streamingReady(std::move(update));
}, [&](const PreloadedVideo &update) {
}, [&](const UpdateVideo &update) {
this->update();
}, [&](const PreloadedAudio &update) {
}, [&](const UpdateAudio &update) {
}, [&](const WaitingForData &update) {
}, [&](MutedByOther) {
}, [&](Finished) {
});
}
case NotificationRepaint: {
if (_gifPreview && !_gifPreview->currentDisplayed()) {
update();
}
} break;
void EditCaptionBox::handleStreamingError(Error &&error) {
}
void EditCaptionBox::streamingReady(Information &&info) {
const auto calculateGifDimensions = [&]() {
const auto scaled = QSize(
info.video.size.width(),
info.video.size.height()
).scaled(
st::sendMediaPreviewSize * cIntRetinaFactor(),
st::confirmMaxHeight * cIntRetinaFactor(),
Qt::KeepAspectRatio);
_thumbw = _gifw = scaled.width();
_thumbh = _gifh = scaled.height();
_thumbx = _gifx = (st::boxWideWidth - _gifw) / 2;
updateBoxSize();
};
// If gif file is not mp4,
// Its dimension values will be known only after reading.
if (_gifw <= 0 || _gifh <= 0) {
calculateGifDimensions();
}
}
@@ -390,7 +458,7 @@ void EditCaptionBox::updateEditPreview() {
_animated = false;
_photo = false;
_doc = false;
_gifPreview = nullptr;
_streamed = nullptr;
_thumbw = _thumbh = _thumbx = 0;
_gifw = _gifh = _gifx = 0;
@@ -469,7 +537,7 @@ void EditCaptionBox::updateEditPreview() {
_gifw = _thumbw;
_gifh = _thumbh;
_gifx = _thumbx;
prepareGifPreview();
prepareStreamedPreview();
}
}
updateEditMediaButton();
@@ -551,10 +619,7 @@ void EditCaptionBox::prepare() {
} else if (data->hasImage()) {
return true;
} else if (const auto urls = data->urls(); !urls.empty()) {
if (ranges::find_if(
urls,
[](const QUrl &url) { return !url.isLocalFile(); }
) == urls.end()) {
if (ranges::all_of(urls, &QUrl::isLocalFile)) {
return true;
}
}
@@ -687,6 +752,31 @@ int EditCaptionBox::errorTopSkip() const {
return (st::defaultBox.buttonPadding.top() / 2);
}
void EditCaptionBox::checkStreamedIsStarted() {
if (!_streamed) {
return;
}
if (_streamed->paused()) {
_streamed->resume();
}
if (!_streamed->active() && !_streamed->failed()) {
startStreamedPlayer();
}
}
void EditCaptionBox::startStreamedPlayer() {
auto options = ::Media::Streaming::PlaybackOptions();
options.audioId = _documentMedia
? AudioMsgId(_documentMedia->owner(), _msgId)
: AudioMsgId();
options.waitForMarkAsShown = true;
//if (!_streamed->withSound) {
options.mode = ::Media::Streaming::Mode::Video;
options.loop = true;
//}
_streamed->play(options);
}
void EditCaptionBox::paintEvent(QPaintEvent *e) {
BoxContent::paintEvent(e);
@@ -700,16 +790,27 @@ void EditCaptionBox::paintEvent(QPaintEvent *e) {
if (_thumbx + _thumbw < width() - st::boxPhotoPadding.right()) {
p.fillRect(_thumbx + _thumbw, st::boxPhotoPadding.top(), width() - st::boxPhotoPadding.right() - _thumbx - _thumbw, th, st::confirmBg);
}
if (_gifPreview && _gifPreview->started()) {
checkStreamedIsStarted();
if (_streamed
&& _streamed->player().ready()
&& !_streamed->player().videoSize().isEmpty()) {
const auto s = QSize(_gifw, _gifh);
const auto paused = _controller->isGifPausedAtLeastFor(Window::GifPauseReason::Layer);
const auto frame = _gifPreview->current(s.width(), s.height(), s.width(), s.height(), ImageRoundRadius::None, RectPart::None, paused ? 0 : crl::now());
p.drawPixmap(_gifx, st::boxPhotoPadding.top(), frame);
auto request = ::Media::Streaming::FrameRequest();
request.outer = s * cIntRetinaFactor();
request.resize = s * cIntRetinaFactor();
p.drawImage(
QRect(_gifx, st::boxPhotoPadding.top(), _gifw, _gifh),
_streamed->frame(request));
if (!paused) {
_streamed->markFrameShown();
}
} else {
const auto offset = _gifh ? ((_gifh - _thumbh) / 2) : 0;
p.drawPixmap(_thumbx, st::boxPhotoPadding.top() + offset, _thumb);
}
if (_animated && !_gifPreview) {
if (_animated && !_streamed) {
QRect inner(_thumbx + (_thumbw - st::msgFileSize) / 2, st::boxPhotoPadding.top() + (th - st::msgFileSize) / 2, st::msgFileSize, st::msgFileSize);
p.setPen(Qt::NoPen);
p.setBrush(st::msgDateImgBg);

View File

@@ -10,9 +10,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "boxes/abstract_box.h"
#include "storage/storage_media_prepare.h"
#include "ui/wrap/slide_wrap.h"
#include "media/clip/media_clip_reader.h"
#include "mtproto/mtproto_rpc_sender.h"
class Image;
namespace ChatHelpers {
class TabbedPanel;
} // namespace ChatHelpers
@@ -23,6 +24,8 @@ class SessionController;
namespace Data {
class Media;
class PhotoMedia;
class DocumentMedia;
} // namespace Data
namespace Ui {
@@ -36,6 +39,16 @@ namespace Window {
class SessionController;
} // namespace Window
namespace Media {
namespace Streaming {
class Instance;
class Document;
struct Update;
enum class Error;
struct Information;
} // namespace Streaming
} // namespace Media
class EditCaptionBox
: public Ui::BoxContent
, public RPCSender
@@ -45,6 +58,7 @@ public:
QWidget*,
not_null<Window::SessionController*> controller,
not_null<HistoryItem*> item);
~EditCaptionBox();
protected:
void prepare() override;
@@ -56,8 +70,14 @@ protected:
private:
void updateBoxSize();
void prepareGifPreview(DocumentData* document = nullptr);
void clipCallback(Media::Clip::Notification notification);
void prepareStreamedPreview();
void checkStreamedIsStarted();
void setupStreamedPreview(
std::shared_ptr<::Media::Streaming::Document> shared);
void handleStreamingUpdate(::Media::Streaming::Update &&update);
void handleStreamingError(::Media::Streaming::Error &&error);
void streamingReady(::Media::Streaming::Information &&info);
void startStreamedPlayer();
void setupEmojiPanel();
void updateEmojiPanelGeometry();
@@ -86,6 +106,8 @@ private:
not_null<Window::SessionController*> _controller;
FullMsgId _msgId;
std::shared_ptr<Data::PhotoMedia> _photoMedia;
std::shared_ptr<Data::DocumentMedia> _documentMedia;
Image *_thumbnailImage = nullptr;
bool _thumbnailImageLoaded = false;
Fn<void()> _refreshThumbnail;
@@ -94,7 +116,7 @@ private:
bool _doc = false;
QPixmap _thumb;
Media::Clip::ReaderPointer _gifPreview;
std::unique_ptr<::Media::Streaming::Instance> _streamed;
object_ptr<Ui::InputField> _field = { nullptr };
object_ptr<Ui::EmojiButton> _emojiToggle = { nullptr };

View File

@@ -78,6 +78,7 @@ private:
};
struct PeerButton {
not_null<History*> history;
std::shared_ptr<Data::CloudImageView> userpic;
Button button;
};
@@ -184,9 +185,10 @@ void FilterChatsPreview::updateData(
}
}
for (const auto history : peers) {
_removePeer.push_back({
history,
makeButton([=] { removePeer(history); }) });
_removePeer.push_back(PeerButton{
.history = history,
.button = makeButton([=] { removePeer(history); })
});
}
refresh();
}
@@ -203,7 +205,7 @@ int FilterChatsPreview::resizeGetHeight(int newWidth) {
for (const auto &[flag, button] : _removeFlag) {
moveNextButton(button.get());
}
for (const auto &[history, button] : _removePeer) {
for (const auto &[history, userpic, button] : _removePeer) {
moveNextButton(button.get());
}
return top;
@@ -235,7 +237,7 @@ void FilterChatsPreview::paintEvent(QPaintEvent *e) {
FilterChatsTypeName(flag));
top += st.height;
}
for (const auto &[history, button] : _removePeer) {
for (auto &[history, userpic, button] : _removePeer) {
const auto savedMessages = history->peer->isSelf();
if (savedMessages) {
Ui::EmptyUserpic::PaintSavedMessages(
@@ -253,6 +255,7 @@ void FilterChatsPreview::paintEvent(QPaintEvent *e) {
} else {
history->peer->paintUserpicLeft(
p,
userpic,
iconLeft,
top + iconTop,
width(),

View File

@@ -180,11 +180,12 @@ QString ExceptionRow::generateShortName() {
PaintRoundImageCallback ExceptionRow::generatePaintUserpicCallback() {
const auto peer = this->peer();
const auto saved = peer->isSelf();
return [=](Painter &p, int x, int y, int outerWidth, int size) {
auto userpic = saved ? nullptr : ensureUserpicView();
return [=](Painter &p, int x, int y, int outerWidth, int size) mutable {
if (saved) {
Ui::EmptyUserpic::PaintSavedMessages(p, x, y, outerWidth, size);
} else {
peer->paintUserpicLeft(p, x, y, outerWidth, size);
peer->paintUserpicLeft(p, userpic, x, y, outerWidth, size);
}
};
}

View File

@@ -41,8 +41,9 @@ PaintRoundImageCallback PaintUserpicCallback(
Ui::EmptyUserpic::PaintSavedMessages(p, x, y, outerWidth, size);
};
}
return [=](Painter &p, int x, int y, int outerWidth, int size) {
peer->paintUserpicLeft(p, x, y, outerWidth, size);
auto userpic = std::shared_ptr<Data::CloudImageView>();
return [=](Painter &p, int x, int y, int outerWidth, int size) mutable {
peer->paintUserpicLeft(p, userpic, x, y, outerWidth, size);
};
}
@@ -483,14 +484,22 @@ QString PeerListRow::generateShortName() {
: peer()->shortName();
}
std::shared_ptr<Data::CloudImageView> PeerListRow::ensureUserpicView() {
if (!_userpic) {
_userpic = peer()->createUserpicView();
}
return _userpic;
}
PaintRoundImageCallback PeerListRow::generatePaintUserpicCallback() {
const auto saved = _isSavedMessagesChat;
const auto peer = this->peer();
return [=](Painter &p, int x, int y, int outerWidth, int size) {
auto userpic = saved ? nullptr : ensureUserpicView();
return [=](Painter &p, int x, int y, int outerWidth, int size) mutable {
if (saved) {
Ui::EmptyUserpic::PaintSavedMessages(p, x, y, outerWidth, size);
} else {
peer->paintUserpicLeft(p, x, y, outerWidth, size);
peer->paintUserpicLeft(p, userpic, x, y, outerWidth, size);
}
};
}
@@ -595,7 +604,7 @@ void PeerListRow::paintDisabledCheckUserpic(
if (_isSavedMessagesChat) {
Ui::EmptyUserpic::PaintSavedMessages(p, userpicLeft, userpicTop, outerWidth, userpicRadius * 2);
} else {
peer()->paintUserpicLeft(p, userpicLeft, userpicTop, outerWidth, userpicRadius * 2);
peer()->paintUserpicLeft(p, _userpic, userpicLeft, userpicTop, outerWidth, userpicRadius * 2);
}
{

View File

@@ -7,11 +7,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#pragma once
#include <rpl/event_stream.h>
#include "ui/rp_widget.h"
#include "ui/empty_userpic.h"
#include "boxes/abstract_box.h"
#include "mtproto/sender.h"
#include "data/data_cloud_file.h"
#include "base/timer.h"
namespace style {
@@ -85,6 +85,8 @@ public:
return _id;
}
[[nodiscard]] std::shared_ptr<Data::CloudImageView> ensureUserpicView();
[[nodiscard]] virtual QString generateName();
[[nodiscard]] virtual QString generateShortName();
[[nodiscard]] virtual auto generatePaintUserpicCallback()
@@ -223,6 +225,7 @@ private:
PeerListRowId _id = 0;
PeerData *_peer = nullptr;
mutable std::shared_ptr<Data::CloudImageView> _userpic;
std::unique_ptr<Ui::RippleAnimation> _ripple;
std::unique_ptr<Ui::RoundImageCheckbox> _checkbox;
Ui::Text::String _name;

View File

@@ -584,5 +584,9 @@ void ChooseRecipientBoxController::rowClicked(not_null<PeerListRow*> row) {
auto ChooseRecipientBoxController::createRow(
not_null<History*> history) -> std::unique_ptr<Row> {
return std::make_unique<Row>(history);
const auto peer = history->peer;
const auto skip = peer->isChannel()
&& !peer->isMegagroup()
&& !peer->canWrite();
return skip ? nullptr : std::make_unique<Row>(history);
}

View File

@@ -354,6 +354,16 @@ bool ParticipantsAdditionalData::canRestrictUser(
Unexpected("Peer in ParticipantsAdditionalData::canRestrictUser.");
}
bool ParticipantsAdditionalData::canRemoveUser(
not_null<UserData*> user) const {
if (canRestrictUser(user)) {
return true;
} else if (const auto chat = _peer->asChat()) {
return chat->invitedByMe.contains(user);
}
return false;
}
auto ParticipantsAdditionalData::adminRights(
not_null<UserData*> user) const
-> std::optional<MTPChatAdminRights> {
@@ -1436,6 +1446,8 @@ base::unique_qptr<Ui::PopupMenu> ParticipantsBoxController::rowContextMenu(
tr::lng_context_restrict_user(tr::now),
crl::guard(this, [=] { showRestricted(user); }));
}
}
if (_additional.canRemoveUser(user)) {
if (!_additional.isKicked(user)) {
const auto isGroup = _peer->isChat() || _peer->isMegagroup();
result->addAction(
@@ -1806,7 +1818,7 @@ auto ParticipantsBoxController::computeType(
: _additional.adminRights(user).has_value()
? Rights::Admin
: Rights::Normal;
result.canRemove = _additional.canRestrictUser(user);
result.canRemove = _additional.canRemoveUser(user);
return result;
}

View File

@@ -88,6 +88,7 @@ public:
[[nodiscard]] bool canEditAdmin(not_null<UserData*> user) const;
[[nodiscard]] bool canAddOrEditAdmin(not_null<UserData*> user) const;
[[nodiscard]] bool canRestrictUser(not_null<UserData*> user) const;
[[nodiscard]] bool canRemoveUser(not_null<UserData*> user) const;
[[nodiscard]] std::optional<MTPChatAdminRights> adminRights(
not_null<UserData*> user) const;
QString adminRank(not_null<UserData*> user) const;

View File

@@ -888,7 +888,6 @@ void SingleMediaPreview::prepareAnimatedPreview(
_gifPreview = Media::Clip::MakeReader(
animatedPreviewPath,
std::move(callback));
if (_gifPreview) _gifPreview->setAutoplay();
}
}

View File

@@ -10,6 +10,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_document.h"
#include "data/data_session.h"
#include "data/data_file_origin.h"
#include "data/data_document_media.h"
#include "lang/lang_keys.h"
#include "chat_helpers/stickers.h"
#include "boxes/confirm_box.h"
@@ -20,6 +21,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/widgets/buttons.h"
#include "ui/widgets/scroll_area.h"
#include "ui/image/image.h"
#include "ui/image/image_location_factory.h"
#include "ui/text/text_utilities.h"
#include "ui/emoji_config.h"
#include "lottie/lottie_multi_player.h"
@@ -72,6 +74,7 @@ protected:
private:
struct Element {
not_null<DocumentData*> document;
std::shared_ptr<Data::DocumentMedia> documentMedia;
Lottie::Animation *animated = nullptr;
Ui::Animations::Simple overAnimation;
};
@@ -113,7 +116,7 @@ private:
int32 _setHash = 0;
MTPDstickerSet::Flags _setFlags = 0;
TimeId _setInstallDate = TimeId(0);
ImagePtr _setThumbnail;
ImageWithLocation _setThumbnail;
MTPInputStickerSet _input;
@@ -267,7 +270,7 @@ void StickerSetBox::Inner::gotSet(const MTPmessages_StickerSet &set) {
continue;
}
_pack.push_back(document);
_elements.push_back({ document });
_elements.push_back({ document, document->createMediaView() });
}
for (const auto &pack : data.vpacks().v) {
pack.match([&](const MTPDstickerPack &pack) {
@@ -297,25 +300,29 @@ void StickerSetBox::Inner::gotSet(const MTPmessages_StickerSet &set) {
_setFlags = set.vflags().v;
_setInstallDate = set.vinstalled_date().value_or(0);
if (const auto thumb = set.vthumb()) {
_setThumbnail = Images::Create(set, *thumb);
_setThumbnail = Images::FromPhotoSize(
&_controller->session(),
set,
*thumb);
} else {
_setThumbnail = ImagePtr();
_setThumbnail = ImageWithLocation();
}
auto &sets = _controller->session().data().stickerSetsRef();
const auto &sets = _controller->session().data().stickerSets();
const auto it = sets.find(_setId);
if (it != sets.cend()) {
const auto set = it->second.get();
using ClientFlag = MTPDstickerSet_ClientFlag;
const auto clientFlags = it->flags
const auto clientFlags = set->flags
& (ClientFlag::f_featured
| ClientFlag::f_not_loaded
| ClientFlag::f_unread
| ClientFlag::f_special);
_setFlags |= clientFlags;
it->flags = _setFlags;
it->installDate = _setInstallDate;
it->stickers = _pack;
it->emoji = _emoji;
it->thumbnail = _setThumbnail;
set->flags = _setFlags;
set->installDate = _setInstallDate;
set->stickers = _pack;
set->emoji = _emoji;
set->setThumbnail(_setThumbnail);
}
});
});
@@ -357,9 +364,10 @@ void StickerSetBox::Inner::installDone(
_setFlags |= MTPDstickerSet::Flag::f_installed_date;
auto it = sets.find(_setId);
if (it == sets.cend()) {
it = sets.insert(
it = sets.emplace(
_setId,
Stickers::Set(
std::make_unique<Stickers::Set>(
&_controller->session().data(),
_setId,
_setAccess,
_setTitle,
@@ -367,14 +375,15 @@ void StickerSetBox::Inner::installDone(
_setCount,
_setHash,
_setFlags,
_setInstallDate,
_setThumbnail));
_setInstallDate)).first;
} else {
it->flags = _setFlags;
it->installDate = _setInstallDate;
it->second->flags = _setFlags;
it->second->installDate = _setInstallDate;
}
it->stickers = _pack;
it->emoji = _emoji;
const auto set = it->second.get();
set->setThumbnail(_setThumbnail);
set->stickers = _pack;
set->emoji = _emoji;
auto &order = _controller->session().data().stickerSetsOrderRef();
int insertAtIndex = 0, currentIndex = order.indexOf(_setId);
@@ -385,14 +394,15 @@ void StickerSetBox::Inner::installDone(
order.insert(insertAtIndex, _setId);
}
auto custom = sets.find(Stickers::CustomSetId);
if (custom != sets.cend()) {
for_const (auto sticker, _pack) {
const auto customIt = sets.find(Stickers::CustomSetId);
if (customIt != sets.cend()) {
const auto custom = customIt->second.get();
for (const auto sticker : std::as_const(_pack)) {
int removeIndex = custom->stickers.indexOf(sticker);
if (removeIndex >= 0) custom->stickers.removeAt(removeIndex);
}
if (custom->stickers.isEmpty()) {
sets.erase(custom);
sets.erase(customIt);
}
}
@@ -602,7 +612,7 @@ void StickerSetBox::Inner::setupLottie(int index) {
element.animated = Stickers::LottieAnimationFromDocument(
getLottiePlayer(),
document,
element.documentMedia.get(),
Stickers::LottieSize::StickerSet,
boundingBoxSize() * cIntRetinaFactor());
}
@@ -621,11 +631,12 @@ void StickerSetBox::Inner::paintSticker(
const auto &element = _elements[index];
const auto document = element.document;
document->checkStickerSmall();
const auto &media = element.documentMedia;
media->checkStickerSmall();
if (document->sticker()->animated
&& !element.animated
&& document->loaded()) {
&& media->loaded()) {
const_cast<Inner*>(this)->setupLottie(index);
}
@@ -650,11 +661,11 @@ void StickerSetBox::Inner::paintSticker(
frame);
_lottiePlayer->unpause(element.animated);
} else if (const auto image = document->getStickerSmall()) {
} else if (const auto image = media->getStickerSmall()) {
p.drawPixmapLeft(
ppos,
width(),
image->pix(document->stickerSetOrigin(), w, h));
image->pix(w, h));
}
}
@@ -666,10 +677,11 @@ bool StickerSetBox::Inner::notInstalled() const {
if (!_loaded) {
return false;
}
const auto it = _controller->session().data().stickerSets().constFind(_setId);
if ((it == _controller->session().data().stickerSets().cend())
|| !(it->flags & MTPDstickerSet::Flag::f_installed_date)
|| (it->flags & MTPDstickerSet::Flag::f_archived)) {
const auto &sets = _controller->session().data().stickerSets();
const auto it = sets.find(_setId);
if ((it == sets.cend())
|| !(it->second->flags & MTPDstickerSet::Flag::f_installed_date)
|| (it->second->flags & MTPDstickerSet::Flag::f_archived)) {
return !_pack.empty();
}
return false;

View File

@@ -11,6 +11,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_session.h"
#include "data/data_channel.h"
#include "data/data_file_origin.h"
#include "data/data_document_media.h"
#include "core/application.h"
#include "lang/lang_keys.h"
#include "mainwidget.h"
@@ -165,7 +166,7 @@ void StickersBox::showAttachedStickers() {
});
if (const auto set = Stickers::FeedSet(*setData)) {
if (_attached.widget()->appendSet(*set)) {
if (_attached.widget()->appendSet(set)) {
addedSet = true;
if (set->stickers.isEmpty() || (set->flags & MTPDstickerSet_ClientFlag::f_not_loaded)) {
_session->api().scheduleStickerSetRequest(set->id, set->access);
@@ -220,8 +221,8 @@ void StickersBox::getArchivedDone(uint64 offsetId, const MTPmessages_ArchivedSti
}
if (!setData) continue;
if (auto set = Stickers::FeedSet(*setData)) {
auto index = archived.indexOf(set->id);
if (const auto set = Stickers::FeedSet(*setData)) {
const auto index = archived.indexOf(set->id);
if (archived.isEmpty() || index != archived.size() - 1) {
changedSets = true;
if (index < archived.size() - 1) {
@@ -229,7 +230,7 @@ void StickersBox::getArchivedDone(uint64 offsetId, const MTPmessages_ArchivedSti
}
archived.push_back(set->id);
}
if (_archived.widget()->appendSet(*set)) {
if (_archived.widget()->appendSet(set)) {
addedSet = true;
if (set->stickers.isEmpty() || (set->flags & MTPDstickerSet_ClientFlag::f_not_loaded)) {
_session->api().scheduleStickerSetRequest(set->id, set->access);
@@ -373,17 +374,24 @@ void StickersBox::loadMoreArchived() {
}
uint64 lastId = 0;
for (auto setIt = _session->data().archivedStickerSetsOrder().cend(), e = _session->data().archivedStickerSetsOrder().cbegin(); setIt != e;) {
const auto &order = _session->data().archivedStickerSetsOrder();
const auto &sets = _session->data().stickerSets();
for (auto setIt = order.cend(), e = order.cbegin(); setIt != e;) {
--setIt;
auto it = _session->data().stickerSets().constFind(*setIt);
if (it != _session->data().stickerSets().cend()) {
if (it->flags & MTPDstickerSet::Flag::f_archived) {
lastId = it->id;
auto it = sets.find(*setIt);
if (it != sets.cend()) {
if (it->second->flags & MTPDstickerSet::Flag::f_archived) {
lastId = it->second->id;
break;
}
}
}
_archivedRequestId = MTP::send(MTPmessages_GetArchivedStickers(MTP_flags(0), MTP_long(lastId), MTP_int(kArchivedLimitPerPage)), rpcDone(&StickersBox::getArchivedDone, lastId));
_archivedRequestId = MTP::send(
MTPmessages_GetArchivedStickers(
MTP_flags(0),
MTP_long(lastId),
MTP_int(kArchivedLimitPerPage)),
rpcDone(&StickersBox::getArchivedDone, lastId));
}
void StickersBox::paintEvent(QPaintEvent *e) {
@@ -489,13 +497,14 @@ QPixmap StickersBox::grabContentCache() {
}
void StickersBox::installSet(uint64 setId) {
auto &sets = _session->data().stickerSetsRef();
auto it = sets.find(setId);
const auto &sets = _session->data().stickerSets();
const auto it = sets.find(setId);
if (it == sets.cend()) {
rebuildList();
return;
}
const auto set = it->second.get();
if (_localRemoved.contains(setId)) {
_localRemoved.removeOne(setId);
if (_installed.widget()) _installed.widget()->setRemovedSets(_localRemoved);
@@ -503,11 +512,11 @@ void StickersBox::installSet(uint64 setId) {
if (_archived.widget()) _archived.widget()->setRemovedSets(_localRemoved);
if (_attached.widget()) _attached.widget()->setRemovedSets(_localRemoved);
}
if (!(it->flags & MTPDstickerSet::Flag::f_installed_date)
|| (it->flags & MTPDstickerSet::Flag::f_archived)) {
if (!(set->flags & MTPDstickerSet::Flag::f_installed_date)
|| (set->flags & MTPDstickerSet::Flag::f_archived)) {
MTP::send(
MTPmessages_InstallStickerSet(
Stickers::inputSetId(*it),
set->mtpInput(),
MTP_boolFalse()),
rpcDone(&StickersBox::installDone),
rpcFail(&StickersBox::installFail, setId));
@@ -526,8 +535,8 @@ void StickersBox::installDone(const MTPmessages_StickerSetInstallResult &result)
bool StickersBox::installFail(uint64 setId, const RPCError &error) {
if (MTP::isDefaultHandledError(error)) return false;
auto &sets = _session->data().stickerSetsRef();
auto it = sets.find(setId);
const auto &sets = _session->data().stickerSets();
const auto it = sets.find(setId);
if (it == sets.cend()) {
rebuildList();
return true;
@@ -550,12 +559,14 @@ void StickersBox::requestArchivedSets() {
preloadArchivedSets();
}
auto &sets = _session->data().stickerSets();
for_const (auto setId, _session->data().archivedStickerSetsOrder()) {
auto it = sets.constFind(setId);
const auto &sets = _session->data().stickerSets();
const auto &order = _session->data().archivedStickerSetsOrder();
for (const auto setId : order) {
auto it = sets.find(setId);
if (it != sets.cend()) {
if (it->stickers.isEmpty() && (it->flags & MTPDstickerSet_ClientFlag::f_not_loaded)) {
_session->api().scheduleStickerSetRequest(setId, it->access);
const auto set = it->second.get();
if (set->stickers.isEmpty() && (set->flags & MTPDstickerSet_ClientFlag::f_not_loaded)) {
_session->api().scheduleStickerSetRequest(setId, set->access);
}
}
}
@@ -624,9 +635,7 @@ void StickersBox::setInnerFocus() {
StickersBox::~StickersBox() = default;
StickersBox::Inner::Row::Row(
uint64 id,
uint64 accessHash,
ImagePtr thumbnail,
not_null<Stickers::Set*> set,
DocumentData *sticker,
int32 count,
const QString &title,
@@ -638,9 +647,7 @@ StickersBox::Inner::Row::Row(
bool removed,
int32 pixw,
int32 pixh)
: id(id)
, accessHash(accessHash)
, thumbnail(thumbnail)
: set(set)
, sticker(sticker)
, count(count)
, title(title)
@@ -656,6 +663,10 @@ StickersBox::Inner::Row::Row(
StickersBox::Inner::Row::~Row() = default;
bool StickersBox::Inner::Row::isRecentSet() const {
return (set->id == Stickers::CloudRecentSetId);
}
StickersBox::Inner::Inner(
QWidget *parent,
not_null<Main::Session*> session,
@@ -804,8 +815,8 @@ QRect StickersBox::Inner::relativeButtonRect(bool removeButton) const {
return QRect(buttonx, buttony, buttonw, buttonh);
}
void StickersBox::Inner::paintRow(Painter &p, not_null<Row*> set, int index) {
auto xadd = 0, yadd = qRound(set->yadd.current());
void StickersBox::Inner::paintRow(Painter &p, not_null<Row*> row, int index) {
auto xadd = 0, yadd = qRound(row->yadd.current());
if (xadd || yadd) p.translate(xadd, yadd);
if (_megagroupSet) {
@@ -817,8 +828,8 @@ void StickersBox::Inner::paintRow(Painter &p, not_null<Row*> set, int index) {
}();
if (index >= 0 && index == selectedIndex) {
p.fillRect(0, 0, width(), _rowHeight, st::contactsBgOver);
if (set->ripple) {
set->ripple->paint(p, 0, 0, width());
if (row->ripple) {
row->ripple->paint(p, 0, 0, width());
}
}
}
@@ -833,24 +844,24 @@ void StickersBox::Inner::paintRow(Painter &p, not_null<Row*> set, int index) {
current = reachedOpacity;
}
}
auto row = myrtlrect(st::contactsPadding.left() / 2, st::contactsPadding.top() / 2, width() - (st::contactsPadding.left() / 2) - _scrollbar - st::contactsPadding.left() / 2, _rowHeight - ((st::contactsPadding.top() + st::contactsPadding.bottom()) / 2));
auto rect = myrtlrect(st::contactsPadding.left() / 2, st::contactsPadding.top() / 2, width() - (st::contactsPadding.left() / 2) - _scrollbar - st::contactsPadding.left() / 2, _rowHeight - ((st::contactsPadding.top() + st::contactsPadding.bottom()) / 2));
p.setOpacity(current);
Ui::Shadow::paint(p, row, width(), st::boxRoundShadow);
Ui::Shadow::paint(p, rect, width(), st::boxRoundShadow);
p.setOpacity(1);
App::roundRect(p, row, st::boxBg, BoxCorners);
App::roundRect(p, rect, st::boxBg, BoxCorners);
p.setOpacity(1. - current);
paintFakeButton(p, set, index);
paintFakeButton(p, row, index);
p.setOpacity(1.);
} else if (!_megagroupSet) {
paintFakeButton(p, set, index);
paintFakeButton(p, row, index);
}
} else if (!_megagroupSet) {
paintFakeButton(p, set, index);
paintFakeButton(p, row, index);
}
if (set->removed && _section == Section::Installed) {
if (row->removed && _section == Section::Installed) {
p.setOpacity(st::stickersRowDisabledOpacity);
}
@@ -858,13 +869,13 @@ void StickersBox::Inner::paintRow(Painter &p, not_null<Row*> set, int index) {
if (!_megagroupSet && _section == Section::Installed) {
stickerx += st::stickersReorderIcon.width() + st::stickersReorderSkip;
if (!set->isRecentSet()) {
if (!row->isRecentSet()) {
st::stickersReorderIcon.paint(p, st::contactsPadding.left(), (_rowHeight - st::stickersReorderIcon.height()) / 2, width());
}
}
if (set->sticker) {
paintRowThumbnail(p, set, stickerx);
if (row->sticker) {
paintRowThumbnail(p, row, stickerx);
}
int namex = stickerx + st::contactsPhotoSize + st::contactsPadding.left();
@@ -875,19 +886,19 @@ void StickersBox::Inner::paintRow(Painter &p, not_null<Row*> set, int index) {
p.setFont(st::contactsNameStyle.font);
p.setPen(st::contactsNameFg);
p.drawTextLeft(namex, namey, width(), set->title, set->titleWidth);
p.drawTextLeft(namex, namey, width(), row->title, row->titleWidth);
if (set->unread) {
if (row->unread) {
p.setPen(Qt::NoPen);
p.setBrush(st::stickersFeaturedUnreadBg);
{
PainterHighQualityEnabler hq(p);
p.drawEllipse(style::rtlrect(namex + set->titleWidth + st::stickersFeaturedUnreadSkip, namey + st::stickersFeaturedUnreadTop, st::stickersFeaturedUnreadSize, st::stickersFeaturedUnreadSize, width()));
p.drawEllipse(style::rtlrect(namex + row->titleWidth + st::stickersFeaturedUnreadSkip, namey + st::stickersFeaturedUnreadTop, st::stickersFeaturedUnreadSize, st::stickersFeaturedUnreadSize, width()));
}
}
auto statusText = (set->count > 0) ? tr::lng_stickers_count(tr::now, lt_count, set->count) : tr::lng_contacts_loading(tr::now);
auto statusText = (row->count > 0) ? tr::lng_stickers_count(tr::now, lt_count, row->count) : tr::lng_contacts_loading(tr::now);
p.setFont(st::contactsStatusFont);
p.setPen(st::contactsStatusFg);
@@ -899,30 +910,39 @@ void StickersBox::Inner::paintRow(Painter &p, not_null<Row*> set, int index) {
void StickersBox::Inner::paintRowThumbnail(
Painter &p,
not_null<Row*> set,
not_null<Row*> row,
int left) {
const auto origin = Data::FileOriginStickerSet(
set->id,
set->accessHash);
const auto thumb = set->thumbnail
? set->thumbnail.get()
: set->sticker->thumbnail();
if (!thumb) {
return;
row->set->id,
row->set->access);
if (row->set->hasThumbnail()) {
if (!row->thumbnailMedia) {
row->thumbnailMedia = row->set->createThumbnailView();
row->set->loadThumbnail();
}
} else if (row->sticker) {
if (!row->stickerMedia) {
row->stickerMedia = row->sticker->createMediaView();
row->stickerMedia->thumbnailWanted(origin);
}
}
thumb->load(origin);
validateLottieAnimation(set);
if (!set->lottie) {
if (!thumb->loaded()) {
validateLottieAnimation(row);
if (!row->lottie) {
const auto thumb = row->thumbnailMedia
? row->thumbnailMedia->image()
: row->stickerMedia
? row->stickerMedia->thumbnail()
: nullptr;
if (!thumb) {
return;
}
p.drawPixmapLeft(
left + (st::contactsPhotoSize - set->pixw) / 2,
st::contactsPadding.top() + (st::contactsPhotoSize - set->pixh) / 2,
left + (st::contactsPhotoSize - row->pixw) / 2,
st::contactsPadding.top() + (st::contactsPhotoSize - row->pixh) / 2,
width(),
thumb->pix(origin, set->pixw, set->pixh));
} else if (set->lottie->ready()) {
const auto frame = set->lottie->frame();
thumb->pix(row->pixw, row->pixh));
} else if (row->lottie->ready()) {
const auto frame = row->lottie->frame();
const auto size = frame.size() / cIntRetinaFactor();
p.drawImage(
QRect(
@@ -935,19 +955,21 @@ void StickersBox::Inner::paintRowThumbnail(
const auto paused = controller->isGifPausedAtLeastFor(
Window::GifPauseReason::Layer);
if (!paused) {
set->lottie->markFrameShown();
row->lottie->markFrameShown();
}
}
}
void StickersBox::Inner::validateLottieAnimation(not_null<Row*> set) {
if (set->lottie
|| !Stickers::HasLottieThumbnail(set->thumbnail, set->sticker)) {
void StickersBox::Inner::validateLottieAnimation(not_null<Row*> row) {
if (row->lottie
|| !Stickers::HasLottieThumbnail(
row->thumbnailMedia.get(),
row->stickerMedia.get())) {
return;
}
auto player = Stickers::LottieThumbnail(
set->thumbnail,
set->sticker,
row->thumbnailMedia.get(),
row->stickerMedia.get(),
Stickers::LottieSize::SetsListThumbnail,
QSize(
st::contactsPhotoSize,
@@ -955,21 +977,21 @@ void StickersBox::Inner::validateLottieAnimation(not_null<Row*> set) {
if (!player) {
return;
}
set->lottie = std::move(player);
set->lottie->updates(
row->lottie = std::move(player);
row->lottie->updates(
) | rpl::start_with_next([=] {
updateRowThumbnail(set);
updateRowThumbnail(row);
}, lifetime());
}
void StickersBox::Inner::updateRowThumbnail(not_null<Row*> set) {
void StickersBox::Inner::updateRowThumbnail(not_null<Row*> row) {
const auto rowTop = [&] {
if (set == _megagroupSelectedSet.get()) {
if (row == _megagroupSelectedSet.get()) {
return _megagroupDivider->y() - _rowHeight;
}
auto top = _itemsTop;
for (const auto &row : _rows) {
if (row.get() == set) {
for (const auto &entry : _rows) {
if (entry.get() == row) {
return top + qRound(row->yadd.current());
}
top += _rowHeight;
@@ -987,10 +1009,10 @@ void StickersBox::Inner::updateRowThumbnail(not_null<Row*> set) {
st::contactsPhotoSize);
}
void StickersBox::Inner::paintFakeButton(Painter &p, not_null<Row*> set, int index) {
auto removeButton = (_section == Section::Installed && !set->removed);
void StickersBox::Inner::paintFakeButton(Painter &p, not_null<Row*> row, int index) {
auto removeButton = (_section == Section::Installed && !row->removed);
auto rect = relativeButtonRect(removeButton);
if (_section != Section::Installed && set->installed && !set->archived && !set->removed) {
if (_section != Section::Installed && row->installed && !row->archived && !row->removed) {
// Checkbox after installed from Trending or Archived.
int checkx = width() - (st::contactsPadding.right() + st::contactsCheckPosition.x() + (rect.width() + st::stickersFeaturedInstalled.width()) / 2);
int checky = st::contactsPadding.top() + (st::contactsPhotoSize - st::stickersFeaturedInstalled.height()) / 2;
@@ -999,10 +1021,10 @@ void StickersBox::Inner::paintFakeButton(Painter &p, not_null<Row*> set, int ind
auto selected = (index == _actionSel && _actionDown < 0) || (index == _actionDown);
if (removeButton) {
// Trash icon button when not disabled in Installed.
if (set->ripple) {
set->ripple->paint(p, rect.x(), rect.y(), width());
if (set->ripple->empty()) {
set->ripple.reset();
if (row->ripple) {
row->ripple->paint(p, rect.x(), rect.y(), width());
if (row->ripple->empty()) {
row->ripple.reset();
}
}
auto &icon = selected ? st::stickersRemove.iconOver : st::stickersRemove.icon;
@@ -1018,10 +1040,10 @@ void StickersBox::Inner::paintFakeButton(Painter &p, not_null<Row*> set, int ind
auto &text = (_section == Section::Installed) ? _undoText : _addText;
auto &textBg = selected ? st.textBgOver : st.textBg;
App::roundRect(p, myrtlrect(rect), textBg, ImageRoundRadius::Small);
if (set->ripple) {
set->ripple->paint(p, rect.x(), rect.y(), width());
if (set->ripple->empty()) {
set->ripple.reset();
if (row->ripple) {
row->ripple->paint(p, rect.x(), rect.y(), width());
if (row->ripple->empty()) {
row->ripple.reset();
}
}
p.setFont(st.font);
@@ -1054,19 +1076,19 @@ void StickersBox::Inner::setActionDown(int newActionDown) {
}
if (_actionDown >= 0 && _actionDown < _rows.size()) {
update(0, _itemsTop + _actionDown * _rowHeight, width(), _rowHeight);
auto &set = _rows[_actionDown];
if (set->ripple) {
set->ripple->lastStop();
const auto row = _rows[_actionDown].get();
if (row->ripple) {
row->ripple->lastStop();
}
}
_actionDown = newActionDown;
if (_actionDown >= 0 && _actionDown < _rows.size()) {
update(0, _itemsTop + _actionDown * _rowHeight, width(), _rowHeight);
auto &set = _rows[_actionDown];
auto removeButton = (_section == Section::Installed && !set->removed);
if (!set->ripple) {
const auto row = _rows[_actionDown].get();
auto removeButton = (_section == Section::Installed && !row->removed);
if (!row->ripple) {
if (_section == Section::Installed) {
if (set->removed) {
if (row->removed) {
auto rippleSize = QSize(_undoWidth - st::stickersUndoRemove.width, st::stickersUndoRemove.height);
auto rippleMask = Ui::RippleAnimation::roundRectMask(rippleSize, st::buttonRadius);
ensureRipple(st::stickersUndoRemove.ripple, std::move(rippleMask), removeButton);
@@ -1075,15 +1097,15 @@ void StickersBox::Inner::setActionDown(int newActionDown) {
auto rippleMask = Ui::RippleAnimation::ellipseMask(QSize(rippleSize, rippleSize));
ensureRipple(st::stickersRemove.ripple, std::move(rippleMask), removeButton);
}
} else if (!set->installed || set->archived || set->removed) {
} else if (!row->installed || row->archived || row->removed) {
auto rippleSize = QSize(_addWidth - st::stickersTrendingAdd.width, st::stickersTrendingAdd.height);
auto rippleMask = Ui::RippleAnimation::roundRectMask(rippleSize, st::buttonRadius);
ensureRipple(st::stickersTrendingAdd.ripple, std::move(rippleMask), removeButton);
}
}
if (set->ripple) {
if (row->ripple) {
auto rect = relativeButtonRect(removeButton);
set->ripple->add(mapFromGlobal(QCursor::pos()) - QPoint(myrtlrect(rect).x(), _itemsTop + _actionDown * _rowHeight + rect.y()));
row->ripple->add(mapFromGlobal(QCursor::pos()) - QPoint(myrtlrect(rect).x(), _itemsTop + _actionDown * _rowHeight + rect.y()));
}
}
}
@@ -1092,14 +1114,6 @@ void StickersBox::Inner::setSelected(SelectedRow selected) {
if (_selected == selected) {
return;
}
if ((_megagroupSet || _section != Section::Installed)
&& ((_selected.has_value() || _pressed.has_value()) != (selected.has_value() || _pressed.has_value()))) {
if (!_inDragArea) {
setCursor((selected.has_value() || _pressed.has_value())
? style::cur_pointer
: style::cur_default);
}
}
auto countSelectedIndex = [&] {
if (auto index = base::get_if<int>(&_selected)) {
return *index;
@@ -1111,6 +1125,7 @@ void StickersBox::Inner::setSelected(SelectedRow selected) {
update(0, _itemsTop + selectedIndex * _rowHeight, width(), _rowHeight);
}
_selected = selected;
updateCursor();
selectedIndex = countSelectedIndex();
if (_megagroupSet && selectedIndex >= 0 && selectedIndex < _rows.size()) {
update(0, _itemsTop + selectedIndex * _rowHeight, width(), _rowHeight);
@@ -1130,9 +1145,9 @@ void StickersBox::Inner::setPressed(SelectedRow pressed) {
auto pressedIndex = countPressedIndex();
if (_megagroupSet && pressedIndex >= 0 && pressedIndex < _rows.size()) {
update(0, _itemsTop + pressedIndex * _rowHeight, width(), _rowHeight);
auto &set = _rows[pressedIndex];
if (set->ripple) {
set->ripple->lastStop();
const auto row = _rows[pressedIndex].get();
if (row->ripple) {
row->ripple->lastStop();
}
}
_pressed = pressed;
@@ -1216,15 +1231,15 @@ void StickersBox::Inner::onUpdateSelected() {
auto selectedIndex = floorclamp(local.y() - _itemsTop, _rowHeight, 0, _rows.size() - 1);
selected = selectedIndex;
local.setY(local.y() - _itemsTop - selectedIndex * _rowHeight);
auto &set = _rows[selectedIndex];
if (!_megagroupSet && (_section == Section::Installed || !set->installed || set->archived || set->removed)) {
auto removeButton = (_section == Section::Installed && !set->removed);
const auto row = _rows[selectedIndex].get();
if (!_megagroupSet && (_section == Section::Installed || !row->installed || row->archived || row->removed)) {
auto removeButton = (_section == Section::Installed && !row->removed);
auto rect = myrtlrect(relativeButtonRect(removeButton));
actionSel = rect.contains(local) ? selectedIndex : -1;
} else {
actionSel = -1;
}
if (!_megagroupSet && _section == Section::Installed && !set->isRecentSet()) {
if (!_megagroupSet && _section == Section::Installed && !row->isRecentSet()) {
auto dragAreaWidth = st::contactsPadding.left() + st::stickersReorderIcon.width() + st::stickersReorderSkip;
auto dragArea = myrtlrect(0, 0, dragAreaWidth, _rowHeight);
inDragArea = dragArea.contains(local);
@@ -1238,17 +1253,25 @@ void StickersBox::Inner::onUpdateSelected() {
setSelected(selected);
if (_inDragArea != inDragArea) {
_inDragArea = inDragArea;
setCursor(_inDragArea
? style::cur_sizeall
: ((_selected.has_value() || _pressed.has_value())
? style::cur_pointer
: style::cur_default));
updateCursor();
}
setActionSel(actionSel);
emit draggingScrollDelta(0);
}
}
void StickersBox::Inner::updateCursor() {
setCursor(_inDragArea
? style::cur_sizeall
: (!_megagroupSet && _section == Section::Installed)
? ((_actionSel >= 0 && (_actionDown < 0 || _actionDown == _actionSel))
? style::cur_pointer
: style::cur_default)
: (_selected.has_value() || _pressed.has_value())
? style::cur_pointer
: style::cur_default);
}
float64 StickersBox::Inner::aboveShadowOpacity() const {
if (_above < 0) return 0;
@@ -1260,9 +1283,7 @@ float64 StickersBox::Inner::aboveShadowOpacity() const {
void StickersBox::Inner::mouseReleaseEvent(QMouseEvent *e) {
auto pressed = std::exchange(_pressed, SelectedRow());
if (_section != Section::Installed && !_selected.has_value() && pressed.has_value()) {
setCursor(style::cur_default);
}
updateCursor();
_mouse = e->globalPos();
onUpdateSelected();
@@ -1270,7 +1291,7 @@ void StickersBox::Inner::mouseReleaseEvent(QMouseEvent *e) {
if (_section == Section::Installed) {
setRowRemoved(_actionDown, !_rows[_actionDown]->removed);
} else if (_installSetCallback) {
_installSetCallback(_rows[_actionDown]->id);
_installSetCallback(_rows[_actionDown]->set->id);
}
} else if (_dragging >= 0) {
QPoint local(mapFromGlobal(_mouse));
@@ -1283,42 +1304,28 @@ void StickersBox::Inner::mouseReleaseEvent(QMouseEvent *e) {
_dragging = _started = -1;
} else if (pressed == _selected && _actionSel < 0 && _actionDown < 0) {
auto selectedIndex = [&] {
const auto selectedIndex = [&] {
if (auto index = base::get_if<int>(&_selected)) {
return *index;
}
return -1;
}();
auto getSetByRow = [&](const Row &row) -> const Stickers::Set* {
auto &sets = _session->data().stickerSetsRef();
if (!row.isRecentSet()) {
auto it = sets.find(row.id);
if (it != sets.cend()) {
return &*it;
}
}
return nullptr;
};
auto showSetByRow = [&](const Row &row) {
if (auto set = getSetByRow(row)) {
setSelected(SelectedRow());
Ui::show(
Box<StickerSetBox>(
App::wnd()->sessionController(),
Stickers::inputSetId(*set)),
Ui::LayerOption::KeepOther);
}
const auto showSetByRow = [&](const Row &row) {
setSelected(SelectedRow());
Ui::show(
Box<StickerSetBox>(
App::wnd()->sessionController(),
row.set->mtpInput()),
Ui::LayerOption::KeepOther);
};
if (selectedIndex >= 0 && !_inDragArea) {
auto &row = *_rows[selectedIndex];
if (_megagroupSet) {
if (auto set = getSetByRow(row)) {
setMegagroupSelectedSet(MTP_inputStickerSetID(
MTP_long(set->id),
MTP_long(set->access)));
const auto row = _rows[selectedIndex].get();
if (!row->isRecentSet()) {
if (_megagroupSet) {
setMegagroupSelectedSet(row->set->mtpInput());
} else {
showSetByRow(*row);
}
} else {
showSetByRow(row);
}
} else if (_megagroupSelectedSet && _selected.is<MegagroupSet>()) {
showSetByRow(*_megagroupSelectedSet);
@@ -1429,9 +1436,7 @@ void StickersBox::Inner::setActionSel(int32 actionSel) {
if (_actionSel >= 0) update(0, _itemsTop + _actionSel * _rowHeight, width(), _rowHeight);
_actionSel = actionSel;
if (_actionSel >= 0) update(0, _itemsTop + _actionSel * _rowHeight, width(), _rowHeight);
if (_section == Section::Installed) {
setCursor((_actionSel >= 0 && (_actionDown < 0 || _actionDown == _actionSel)) ? style::cur_pointer : style::cur_default);
}
updateCursor();
}
}
@@ -1466,8 +1471,9 @@ void StickersBox::Inner::handleMegagroupSetAddressChange() {
auto text = _megagroupSetField->getLastText().trimmed();
if (text.isEmpty()) {
if (_megagroupSelectedSet) {
auto it = _session->data().stickerSets().constFind(_megagroupSelectedSet->id);
if (it != _session->data().stickerSets().cend() && !it->shortName.isEmpty()) {
const auto &sets = _session->data().stickerSets();
const auto it = sets.find(_megagroupSelectedSet->set->id);
if (it != sets.cend() && !it->second->shortName.isEmpty()) {
setMegagroupSelectedSet(MTP_inputStickerSetEmpty());
}
}
@@ -1477,7 +1483,9 @@ void StickersBox::Inner::handleMegagroupSetAddressChange() {
)).done([=](const MTPmessages_StickerSet &result) {
_megagroupSetRequestId = 0;
auto set = Stickers::FeedSetFull(result);
setMegagroupSelectedSet(MTP_inputStickerSetID(MTP_long(set->id), MTP_long(set->access)));
setMegagroupSelectedSet(MTP_inputStickerSetID(
MTP_long(set->id),
MTP_long(set->access)));
}).fail([=](const RPCError &error) {
_megagroupSetRequestId = 0;
setMegagroupSelectedSet(MTP_inputStickerSetEmpty());
@@ -1499,32 +1507,33 @@ void StickersBox::Inner::rebuildMegagroupSet() {
_megagroupSelectedShadow.destroy();
return;
}
auto &set = _megagroupSetInput.c_inputStickerSetID();
auto setId = set.vid().v;
auto &sets = _session->data().stickerSets();
auto &inputId = _megagroupSetInput.c_inputStickerSetID();
auto setId = inputId.vid().v;
const auto &sets = _session->data().stickerSets();
auto it = sets.find(setId);
if (it == sets.cend() || (it->flags & MTPDstickerSet_ClientFlag::f_not_loaded)) {
_session->api().scheduleStickerSetRequest(set.vid().v, set.vaccess_hash().v);
if (it == sets.cend()
|| (it->second->flags & MTPDstickerSet_ClientFlag::f_not_loaded)) {
_session->api().scheduleStickerSetRequest(
inputId.vid().v,
inputId.vaccess_hash().v);
return;
}
const auto set = it->second.get();
auto maxNameWidth = countMaxNameWidth();
auto titleWidth = 0;
auto title = fillSetTitle(*it, maxNameWidth, &titleWidth);
auto count = fillSetCount(*it);
auto thumbnail = ImagePtr();
auto title = fillSetTitle(set, maxNameWidth, &titleWidth);
auto count = fillSetCount(set);
auto sticker = (DocumentData*)nullptr;
auto pixw = 0, pixh = 0;
fillSetCover(*it, &thumbnail, &sticker, &pixw, &pixh);
fillSetCover(set, &sticker, &pixw, &pixh);
auto installed = true, official = false, unread = false, archived = false, removed = false;
if (!_megagroupSelectedSet || _megagroupSelectedSet->id != it->id) {
_megagroupSetField->setText(it->shortName);
if (!_megagroupSelectedSet || _megagroupSelectedSet->set->id != set->id) {
_megagroupSetField->setText(set->shortName);
_megagroupSetField->finishAnimating();
}
_megagroupSelectedSet = std::make_unique<Row>(
it->id,
it->access,
thumbnail,
set,
sticker,
count,
title,
@@ -1561,7 +1570,7 @@ void StickersBox::Inner::rebuild() {
auto maxNameWidth = countMaxNameWidth();
clear();
auto &order = ([&]() -> const Stickers::Order & {
const auto &order = ([&]() -> const Stickers::Order & {
if (_section == Section::Installed) {
auto &result = _session->data().stickerSetsOrder();
if (_megagroupSet && result.empty()) {
@@ -1576,7 +1585,7 @@ void StickersBox::Inner::rebuild() {
_rows.reserve(order.size() + 1);
_shiftingStartTimes.reserve(order.size() + 1);
auto &sets = _session->data().stickerSets();
const auto &sets = _session->data().stickerSets();
if (_megagroupSet) {
auto usingFeatured = _session->data().stickerSetsOrder().empty();
_megagroupSubTitle->setText(usingFeatured
@@ -1584,21 +1593,23 @@ void StickersBox::Inner::rebuild() {
: tr::lng_stickers_group_from_your(tr::now));
updateControlsGeometry();
} else if (_section == Section::Installed) {
auto cloudIt = sets.constFind(Stickers::CloudRecentSetId);
if (cloudIt != sets.cend() && !cloudIt->stickers.isEmpty()) {
rebuildAppendSet(cloudIt.value(), maxNameWidth);
auto cloudIt = sets.find(Stickers::CloudRecentSetId);
if (cloudIt != sets.cend() && !cloudIt->second->stickers.isEmpty()) {
rebuildAppendSet(cloudIt->second.get(), maxNameWidth);
}
}
for_const (auto setId, order) {
auto it = sets.constFind(setId);
for (const auto setId : order) {
auto it = sets.find(setId);
if (it == sets.cend()) {
continue;
}
rebuildAppendSet(it.value(), maxNameWidth);
const auto set = it->second.get();
rebuildAppendSet(set, maxNameWidth);
if (it->stickers.isEmpty() || (it->flags & MTPDstickerSet_ClientFlag::f_not_loaded)) {
_session->api().scheduleStickerSetRequest(it->id, it->access);
if (set->stickers.isEmpty()
|| (set->flags & MTPDstickerSet_ClientFlag::f_not_loaded)) {
_session->api().scheduleStickerSetRequest(set->id, set->access);
}
}
_session->api().requestStickerSets();
@@ -1626,24 +1637,22 @@ void StickersBox::Inner::updateSize(int newWidth) {
void StickersBox::Inner::updateRows() {
int maxNameWidth = countMaxNameWidth();
auto &sets = _session->data().stickerSets();
const auto &sets = _session->data().stickerSets();
for (const auto &row : _rows) {
const auto it = sets.constFind(row->id);
const auto it = sets.find(row->set->id);
if (it == sets.cend()) {
continue;
}
const auto &set = it.value();
const auto set = it->second.get();
if (!row->sticker) {
auto thumbnail = ImagePtr();
auto sticker = (DocumentData*)nullptr;
auto pixw = 0, pixh = 0;
fillSetCover(set, &thumbnail, &sticker, &pixw, &pixh);
fillSetCover(set, &sticker, &pixw, &pixh);
if (sticker) {
if ((row->thumbnail.get() != thumbnail.get())
|| (!thumbnail && row->sticker != sticker)) {
if (row->sticker != sticker && !row->thumbnailMedia) {
row->lottie = nullptr;
row->stickerMedia = nullptr;
}
row->thumbnail = thumbnail;
row->sticker = sticker;
row->pixw = pixw;
row->pixh = pixh;
@@ -1666,9 +1675,9 @@ void StickersBox::Inner::updateRows() {
update();
}
bool StickersBox::Inner::appendSet(const Stickers::Set &set) {
for_const (auto &row, _rows) {
if (row->id == set.id) {
bool StickersBox::Inner::appendSet(not_null<Stickers::Set*> set) {
for (const auto &row : _rows) {
if (row->set == set) {
return false;
}
}
@@ -1695,28 +1704,27 @@ int StickersBox::Inner::countMaxNameWidth() const {
return namew;
}
void StickersBox::Inner::rebuildAppendSet(const Stickers::Set &set, int maxNameWidth) {
void StickersBox::Inner::rebuildAppendSet(
not_null<Stickers::Set*> set,
int maxNameWidth) {
bool installed = true, official = true, unread = false, archived = false, removed = false;
if (set.id != Stickers::CloudRecentSetId) {
if (set->id != Stickers::CloudRecentSetId) {
fillSetFlags(set, &installed, &official, &unread, &archived);
}
if (_section == Section::Installed && archived) {
return;
}
ImagePtr thumbnail;
DocumentData *sticker = nullptr;
int pixw = 0, pixh = 0;
fillSetCover(set, &thumbnail, &sticker, &pixw, &pixh);
fillSetCover(set, &sticker, &pixw, &pixh);
int titleWidth = 0;
QString title = fillSetTitle(set, maxNameWidth, &titleWidth);
int count = fillSetCount(set);
_rows.push_back(std::make_unique<Row>(
set.id,
set.access,
thumbnail,
set,
sticker,
count,
title,
@@ -1731,19 +1739,26 @@ void StickersBox::Inner::rebuildAppendSet(const Stickers::Set &set, int maxNameW
_shiftingStartTimes.push_back(0);
}
void StickersBox::Inner::fillSetCover(const Stickers::Set &set, ImagePtr *thumbnail, DocumentData **outSticker, int *outWidth, int *outHeight) const {
*thumbnail = set.thumbnail;
if (set.stickers.isEmpty()) {
void StickersBox::Inner::fillSetCover(
not_null<Stickers::Set*> set,
DocumentData **outSticker,
int *outWidth,
int *outHeight) const {
if (set->stickers.isEmpty()) {
*outSticker = nullptr;
*outWidth = *outHeight = 0;
return;
}
auto sticker = *outSticker = set.stickers.front();
auto sticker = *outSticker = set->stickers.front();
const auto size = set.thumbnail
? set.thumbnail->size()
: sticker->thumbnail()
? sticker->thumbnail()->size()
const auto size = set->hasThumbnail()
? QSize(
set->thumbnailLocation().width(),
set->thumbnailLocation().height())
: sticker->hasThumbnail()
? QSize(
sticker->thumbnailLocation().width(),
sticker->thumbnailLocation().height())
: QSize(1, 1);
auto pixw = size.width();
auto pixh = size.height();
@@ -1763,14 +1778,19 @@ void StickersBox::Inner::fillSetCover(const Stickers::Set &set, ImagePtr *thumbn
*outHeight = pixh;
}
int StickersBox::Inner::fillSetCount(const Stickers::Set &set) const {
int result = set.stickers.isEmpty() ? set.count : set.stickers.size(), added = 0;
if (set.id == Stickers::CloudRecentSetId) {
auto customIt = _session->data().stickerSets().constFind(Stickers::CustomSetId);
if (customIt != _session->data().stickerSets().cend()) {
added = customIt->stickers.size();
for_const (auto &sticker, Stickers::GetRecentPack()) {
if (customIt->stickers.indexOf(sticker.first) < 0) {
int StickersBox::Inner::fillSetCount(not_null<Stickers::Set*> set) const {
int result = set->stickers.isEmpty()
? set->count
: set->stickers.size();
auto added = 0;
if (set->id == Stickers::CloudRecentSetId) {
const auto &sets = _session->data().stickerSets();
auto customIt = sets.find(Stickers::CustomSetId);
if (customIt != sets.cend()) {
added = customIt->second->stickers.size();
const auto &recent = Stickers::GetRecentPack();
for (const auto &sticker : recent) {
if (customIt->second->stickers.indexOf(sticker.first) < 0) {
++added;
}
}
@@ -1781,8 +1801,11 @@ int StickersBox::Inner::fillSetCount(const Stickers::Set &set) const {
return result + added;
}
QString StickersBox::Inner::fillSetTitle(const Stickers::Set &set, int maxNameWidth, int *outTitleWidth) const {
auto result = set.title;
QString StickersBox::Inner::fillSetTitle(
not_null<Stickers::Set*> set,
int maxNameWidth,
int *outTitleWidth) const {
auto result = set->title;
int titleWidth = st::contactsNameStyle.font->width(result);
if (titleWidth > maxNameWidth) {
result = st::contactsNameStyle.font->elided(result, maxNameWidth);
@@ -1795,16 +1818,16 @@ QString StickersBox::Inner::fillSetTitle(const Stickers::Set &set, int maxNameWi
}
void StickersBox::Inner::fillSetFlags(
const Stickers::Set &set,
not_null<Stickers::Set*> set,
bool *outInstalled,
bool *outOfficial,
bool *outUnread,
bool *outArchived) {
*outInstalled = (set.flags & MTPDstickerSet::Flag::f_installed_date);
*outOfficial = (set.flags & MTPDstickerSet::Flag::f_official);
*outArchived = (set.flags & MTPDstickerSet::Flag::f_archived);
*outInstalled = (set->flags & MTPDstickerSet::Flag::f_installed_date);
*outOfficial = (set->flags & MTPDstickerSet::Flag::f_official);
*outArchived = (set->flags & MTPDstickerSet::Flag::f_archived);
if (_section == Section::Featured) {
*outUnread = (set.flags & MTPDstickerSet_ClientFlag::f_unread);
*outUnread = (set->flags & MTPDstickerSet_ClientFlag::f_unread);
} else {
*outUnread = false;
}
@@ -1816,7 +1839,7 @@ Stickers::Order StickersBox::Inner::collectSets(Check check) const {
result.reserve(_rows.size());
for_const (auto &row, _rows) {
if (check(row.get())) {
result.push_back(row->id);
result.push_back(row->set->id);
}
}
return result;
@@ -1843,7 +1866,7 @@ Stickers::Order StickersBox::Inner::getRemovedSets() const {
int StickersBox::Inner::getRowIndex(uint64 setId) const {
for (auto i = 0, count = int(_rows.size()); i != count; ++i) {
auto &row = _rows[i];
if (row->id == setId) {
if (row->set->id == setId) {
return i;
}
}
@@ -1866,7 +1889,7 @@ void StickersBox::Inner::setFullOrder(const Stickers::Order &order) {
void StickersBox::Inner::setRemovedSets(const Stickers::Order &removed) {
for (auto i = 0, count = int(_rows.size()); i != count; ++i) {
setRowRemoved(i, removed.contains(_rows[i]->id));
setRowRemoved(i, removed.contains(_rows[i]->set->id));
}
}
@@ -1904,15 +1927,14 @@ void StickersBox::Inner::readVisibleSets() {
if (i * _rowHeight < itemsVisibleTop || (i + 1) * _rowHeight > itemsVisibleBottom) {
continue;
}
const auto thumbnail = !_rows[i]->sticker
? nullptr
: _rows[i]->thumbnail
? _rows[i]->thumbnail.get()
: _rows[i]->sticker->thumbnail();
if (!thumbnail
|| thumbnail->loaded()
|| _rows[i]->sticker->loaded()) {
_session->api().readFeaturedSetDelayed(_rows[i]->id);
const auto thumbnailLoading = _rows[i]->set->hasThumbnail()
? _rows[i]->set->thumbnailLoading()
: _rows[i]->sticker
? ((_rows[i]->stickerMedia && _rows[i]->stickerMedia->loaded())
|| _rows[i]->sticker->thumbnailLoading())
: false;
if (!thumbnailLoading || _rows[i]->stickerMedia->loaded()) {
_session->api().readFeaturedSetDelayed(_rows[i]->set->id);
}
}
}

View File

@@ -10,7 +10,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "boxes/abstract_box.h"
#include "base/timer.h"
#include "mtproto/sender.h"
#include "chat_helpers/stickers.h"
#include "chat_helpers/stickers_set.h"
#include "ui/effects/animations.h"
#include "ui/special_fields.h"
@@ -33,6 +33,18 @@ namespace Main {
class Session;
} // namespace Main
namespace Data {
class DocumentMedia;
} // namespace Data
namespace Lottie {
class SinglePlayer;
} // namespace Lottie
namespace Stickers {
class Set;
} // namespace Stickers
class StickersBox final
: public Ui::BoxContent
, public RPCSender
@@ -173,7 +185,7 @@ public:
void rebuild();
void updateSize(int newWidth = 0);
void updateRows(); // refresh only pack cover stickers
bool appendSet(const Stickers::Set &set);
bool appendSet(not_null<Stickers::Set*> set);
Stickers::Order getOrder() const;
Stickers::Order getFullOrder() const;
@@ -219,9 +231,7 @@ public slots:
private:
struct Row {
Row(
uint64 id,
uint64 accessHash,
ImagePtr thumbnail,
not_null<Stickers::Set*> set,
DocumentData *sticker,
int32 count,
const QString &title,
@@ -233,15 +243,14 @@ private:
bool removed,
int32 pixw,
int32 pixh);
bool isRecentSet() const {
return (id == Stickers::CloudRecentSetId);
}
~Row();
uint64 id = 0;
uint64 accessHash = 0;
ImagePtr thumbnail;
bool isRecentSet() const;
const not_null<Stickers::Set*> set;
DocumentData *sticker = nullptr;
std::shared_ptr<Data::DocumentMedia> stickerMedia;
std::shared_ptr<Stickers::SetThumbnailView> thumbnailMedia;
int32 count = 0;
QString title;
int titleWidth = 0;
@@ -294,23 +303,24 @@ private:
void ensureRipple(const style::RippleAnimation &st, QImage mask, bool removeButton);
bool shiftingAnimationCallback(crl::time now);
void paintRow(Painter &p, not_null<Row*> set, int index);
void paintRowThumbnail(Painter &p, not_null<Row*> set, int left);
void paintFakeButton(Painter &p, not_null<Row*> set, int index);
void paintRow(Painter &p, not_null<Row*> row, int index);
void paintRowThumbnail(Painter &p, not_null<Row*> row, int left);
void paintFakeButton(Painter &p, not_null<Row*> row, int index);
void clear();
void updateCursor();
void setActionSel(int32 actionSel);
float64 aboveShadowOpacity() const;
void validateLottieAnimation(not_null<Row*> set);
void updateRowThumbnail(not_null<Row*> set);
void validateLottieAnimation(not_null<Row*> row);
void updateRowThumbnail(not_null<Row*> row);
void readVisibleSets();
void updateControlsGeometry();
void rebuildAppendSet(const Stickers::Set &set, int maxNameWidth);
void fillSetCover(const Stickers::Set &set, ImagePtr *thumbnail, DocumentData **outSticker, int *outWidth, int *outHeight) const;
int fillSetCount(const Stickers::Set &set) const;
QString fillSetTitle(const Stickers::Set &set, int maxNameWidth, int *outTitleWidth) const;
void fillSetFlags(const Stickers::Set &set, bool *outInstalled, bool *outOfficial, bool *outUnread, bool *outArchived);
void rebuildAppendSet(not_null<Stickers::Set*> set, int maxNameWidth);
void fillSetCover(not_null<Stickers::Set*> set, DocumentData **outSticker, int *outWidth, int *outHeight) const;
int fillSetCount(not_null<Stickers::Set*> set) const;
QString fillSetTitle(not_null<Stickers::Set*> set, int maxNameWidth, int *outTitleWidth) const;
void fillSetFlags(not_null<Stickers::Set*> set, bool *outInstalled, bool *outOfficial, bool *outUnread, bool *outArchived);
void rebuildMegagroupSet();
void fixupMegagroupSetAddress();
void handleMegagroupSetAddressChange();

View File

@@ -17,6 +17,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "main/main_session.h"
#include "data/data_session.h"
#include "data/data_user.h"
#include "mtproto/facade.h"
#include "styles/style_layers.h"
#include "styles/style_boxes.h"

View File

@@ -18,23 +18,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "media/audio/media_audio_track.h"
#include "base/platform/base_platform_info.h"
#include "calls/calls_panel.h"
#include "calls/calls_controller.h"
#include "data/data_user.h"
#include "data/data_session.h"
#include "facades.h"
#ifdef slots
#undef slots
#define NEED_TO_RESTORE_SLOTS
#endif // slots
#include <VoIPController.h>
#include <VoIPServerConfig.h>
#ifdef NEED_TO_RESTORE_SLOTS
#define slots Q_SLOTS
#undef NEED_TO_RESTORE_SLOTS
#endif // NEED_TO_RESTORE_SLOTS
namespace Calls {
namespace {
@@ -43,25 +31,25 @@ constexpr auto kHangupTimeoutMs = 5000;
constexpr auto kSha256Size = 32;
void AppendEndpoint(
std::vector<tgvoip::Endpoint> &list,
std::vector<TgVoipEndpoint> &list,
const MTPPhoneConnection &connection) {
connection.match([&](const MTPDphoneConnection &data) {
if (data.vpeer_tag().v.length() != 16) {
return;
}
const auto ipv4 = tgvoip::IPv4Address(std::string(
data.vip().v.constData(),
data.vip().v.size()));
const auto ipv6 = tgvoip::IPv6Address(std::string(
data.vipv6().v.constData(),
data.vipv6().v.size()));
list.emplace_back(
(int64_t)data.vid().v,
(uint16_t)data.vport().v,
ipv4,
ipv6,
tgvoip::Endpoint::Type::UDP_RELAY,
(unsigned char*)data.vpeer_tag().v.data());
auto endpoint = TgVoipEndpoint{
.endpointId = (int64_t)data.vid().v,
.host = TgVoipEdpointHost{
.ipv4 = data.vip().v.toStdString(),
.ipv6 = data.vipv6().v.toStdString() },
.port = (uint16_t)data.vport().v,
.type = TgVoipEndpointType::UdpRelay
};
const auto tag = data.vpeer_tag().v;
if (tag.size() >= 16) {
memcpy(endpoint.peerTag, tag.data(), 16);
}
list.push_back(std::move(endpoint));
});
}
@@ -80,47 +68,25 @@ uint64 ComputeFingerprint(bytes::const_span authKey) {
| (gsl::to_integer<uint64>(hash[12]));
}
} // namespace
void Call::ControllerPointer::create() {
Expects(_data == nullptr);
_data = std::make_unique<tgvoip::VoIPController>();
[[nodiscard]] std::vector<std::string> CollectVersions() {
return { TgVoip::getVersion() };
}
void Call::ControllerPointer::reset() {
if (const auto controller = base::take(_data)) {
controller->Stop();
[[nodiscard]] QVector<MTPstring> WrapVersions(
const std::vector<std::string> &data) {
auto result = QVector<MTPstring>();
result.reserve(data.size());
for (const auto &version : data) {
result.push_back(MTP_string(version));
}
return result;
}
bool Call::ControllerPointer::empty() const {
return (_data == nullptr);
[[nodiscard]] QVector<MTPstring> CollectVersionsForApi() {
return WrapVersions(CollectVersions());
}
bool Call::ControllerPointer::operator==(std::nullptr_t) const {
return empty();
}
Call::ControllerPointer::operator bool() const {
return !empty();
}
tgvoip::VoIPController *Call::ControllerPointer::operator->() const {
Expects(!empty());
return _data.get();
}
tgvoip::VoIPController &Call::ControllerPointer::operator*() const {
Expects(!empty());
return *_data;
}
Call::ControllerPointer::~ControllerPointer() {
reset();
}
} // namespace
Call::Delegate::~Delegate() = default;
@@ -199,8 +165,8 @@ void Call::startOutgoing() {
MTP_flags(MTPDphoneCallProtocol::Flag::f_udp_p2p
| MTPDphoneCallProtocol::Flag::f_udp_reflector),
MTP_int(kMinLayer),
MTP_int(tgvoip::VoIPController::GetConnectionMaxLayer()),
MTP_vector(1, MTP_string("2.4.4")))
MTP_int(TgVoip::getConnectionMaxLayer()),
MTP_vector(CollectVersionsForApi()))
)).done([=](const MTPphone_PhoneCall &result) {
Expects(result.type() == mtpc_phone_phoneCall);
@@ -278,8 +244,8 @@ void Call::actuallyAnswer() {
MTP_flags(MTPDphoneCallProtocol::Flag::f_udp_p2p
| MTPDphoneCallProtocol::Flag::f_udp_reflector),
MTP_int(kMinLayer),
MTP_int(tgvoip::VoIPController::GetConnectionMaxLayer()),
MTP_vector(1, MTP_string("2.4.4")))
MTP_int(TgVoip::getConnectionMaxLayer()),
MTP_vector(CollectVersionsForApi()))
)).done([=](const MTPphone_PhoneCall &result) {
Expects(result.type() == mtpc_phone_phoneCall);
auto &call = result.c_phone_phoneCall();
@@ -300,7 +266,7 @@ void Call::actuallyAnswer() {
void Call::setMute(bool mute) {
_mute = mute;
if (_controller) {
_controller->SetMicMute(_mute);
_controller->setMuteMicrophone(_mute);
}
_muteChanged.notify(_mute);
}
@@ -334,8 +300,7 @@ void Call::redial() {
}
QString Call::getDebugLog() const {
const auto debug = _controller->GetDebugString();
return QString::fromUtf8(debug.data(), debug.size());
return QString::fromStdString(_controller->getDebugInfo());
}
void Call::startWaitingTrack() {
@@ -441,7 +406,9 @@ bool Call::handleUpdate(const MTPPhoneCall &call) {
return false;
}
if (data.is_need_debug()) {
auto debugLog = _controller ? _controller->GetDebugLog() : std::string();
auto debugLog = _controller
? _controller->getDebugInfo()
: std::string();
if (!debugLog.empty()) {
MTP::send(
MTPphone_SaveCallDebug(
@@ -517,8 +484,8 @@ void Call::confirmAcceptedCall(const MTPDphoneCallAccepted &call) {
MTP_flags(MTPDphoneCallProtocol::Flag::f_udp_p2p
| MTPDphoneCallProtocol::Flag::f_udp_reflector),
MTP_int(kMinLayer),
MTP_int(tgvoip::VoIPController::GetConnectionMaxLayer()),
MTP_vector(1, MTP_string("2.4.4")))
MTP_int(TgVoip::getConnectionMaxLayer()),
MTP_vector(CollectVersionsForApi()))
)).done([this](const MTPphone_PhoneCall &result) {
Expects(result.type() == mtpc_phone_phoneCall);
@@ -566,126 +533,123 @@ void Call::createAndStartController(const MTPDphoneCall &call) {
return;
}
tgvoip::VoIPController::Config config;
config.dataSaving = tgvoip::DATA_SAVING_NEVER;
const auto &protocol = call.vprotocol().c_phoneCallProtocol();
TgVoipConfig config;
config.dataSaving = TgVoipDataSaving::Never;
config.enableAEC = !Platform::IsMac10_7OrGreater();
config.enableNS = true;
config.enableAGC = true;
config.enableVolumeControl = true;
config.initTimeout = Global::CallConnectTimeoutMs() / 1000;
config.recvTimeout = Global::CallPacketTimeoutMs() / 1000;
config.initializationTimeout = Global::CallConnectTimeoutMs() / 1000.;
config.receiveTimeout = Global::CallPacketTimeoutMs() / 1000.;
config.enableP2P = call.is_p2p_allowed();
config.maxApiLayer = protocol.vmax_layer().v;
if (Logs::DebugEnabled()) {
auto callLogFolder = cWorkingDir() + qsl("DebugLogs");
auto callLogPath = callLogFolder + qsl("/last_call_log.txt");
auto callLogNative = QDir::toNativeSeparators(callLogPath);
#ifdef Q_OS_WIN
config.logFilePath = callLogNative.toStdWString();
config.logPath = callLogNative.toStdWString();
#else // Q_OS_WIN
const auto callLogUtf = QFile::encodeName(callLogNative);
config.logFilePath.resize(callLogUtf.size());
ranges::copy(callLogUtf, config.logFilePath.begin());
config.logPath.resize(callLogUtf.size());
ranges::copy(callLogUtf, config.logPath.begin());
#endif // Q_OS_WIN
QFile(callLogPath).remove();
QDir().mkpath(callLogFolder);
}
const auto &protocol = call.vprotocol().c_phoneCallProtocol();
auto endpoints = std::vector<tgvoip::Endpoint>();
auto endpoints = std::vector<TgVoipEndpoint>();
for (const auto &connection : call.vconnections().v) {
AppendEndpoint(endpoints, connection);
}
auto callbacks = tgvoip::VoIPController::Callbacks();
callbacks.connectionStateChanged = [](
tgvoip::VoIPController *controller,
int state) {
const auto call = static_cast<Call*>(controller->implData);
call->handleControllerStateChange(controller, state);
};
callbacks.signalBarCountChanged = [](
tgvoip::VoIPController *controller,
int count) {
const auto call = static_cast<Call*>(controller->implData);
call->handleControllerBarCountChange(controller, count);
};
_controller.create();
if (_mute) {
_controller->SetMicMute(_mute);
}
_controller->implData = static_cast<void*>(this);
_controller->SetRemoteEndpoints(
endpoints,
call.is_p2p_allowed(),
protocol.vmax_layer().v);
_controller->SetConfig(config);
_controller->SetCurrentAudioOutput(Global::CallOutputDeviceID().toStdString());
_controller->SetCurrentAudioInput(Global::CallInputDeviceID().toStdString());
_controller->SetOutputVolume(Global::CallOutputVolume()/100.0f);
_controller->SetInputVolume(Global::CallInputVolume()/100.0f);
#ifdef Q_OS_MAC
_controller->SetAudioOutputDuckingEnabled(Global::CallAudioDuckingEnabled());
#endif
_controller->SetEncryptionKey(reinterpret_cast<char*>(_authKey.data()), (_type == Type::Outgoing));
_controller->SetCallbacks(callbacks);
auto proxy = TgVoipProxy();
if (Global::UseProxyForCalls()
&& (Global::ProxySettings() == MTP::ProxyData::Settings::Enabled)) {
const auto &proxy = Global::SelectedProxy();
if (proxy.supportsCalls()) {
Assert(proxy.type == MTP::ProxyData::Type::Socks5);
_controller->SetProxy(
tgvoip::PROXY_SOCKS5,
proxy.host.toStdString(),
proxy.port,
proxy.user.toStdString(),
proxy.password.toStdString());
const auto &selected = Global::SelectedProxy();
if (selected.supportsCalls()) {
Assert(selected.type == MTP::ProxyData::Type::Socks5);
proxy.host = selected.host.toStdString();
proxy.port = selected.port;
proxy.login = selected.user.toStdString();
proxy.password = selected.password.toStdString();
}
}
_controller->Start();
_controller->Connect();
auto encryptionKey = TgVoipEncryptionKey();
encryptionKey.isOutgoing = (_type == Type::Outgoing);
encryptionKey.value = ranges::view::all(
_authKey
) | ranges::view::transform([](bytes::type byte) {
return static_cast<uint8_t>(byte);
}) | ranges::to_vector;
_controller = MakeController(
"2.4.4",
config,
TgVoipPersistentState(),
endpoints,
proxy.host.empty() ? nullptr : &proxy,
TgVoipNetworkType::Unknown,
encryptionKey);
const auto raw = _controller.get();
raw->setOnStateUpdated([=](TgVoipState state) {
handleControllerStateChange(raw, state);
});
raw->setOnSignalBarsUpdated([=](int count) {
handleControllerBarCountChange(count);
});
if (_mute) {
raw->setMuteMicrophone(_mute);
}
raw->setAudioOutputDevice(
Global::CallOutputDeviceID().toStdString());
raw->setAudioInputDevice(
Global::CallInputDeviceID().toStdString());
raw->setOutputVolume(Global::CallOutputVolume() / 100.0f);
raw->setInputVolume(Global::CallInputVolume() / 100.0f);
raw->setAudioOutputDuckingEnabled(Global::CallAudioDuckingEnabled());
}
void Call::handleControllerStateChange(
tgvoip::VoIPController *controller,
int state) {
not_null<Controller*> controller,
TgVoipState state) {
// NB! Can be called from an arbitrary thread!
// This can be called from ~VoIPController()!
// Expects(controller == _controller.get());
Expects(controller->implData == static_cast<void*>(this));
switch (state) {
case tgvoip::STATE_WAIT_INIT: {
case TgVoipState::WaitInit: {
DEBUG_LOG(("Call Info: State changed to WaitingInit."));
setStateQueued(State::WaitingInit);
} break;
case tgvoip::STATE_WAIT_INIT_ACK: {
case TgVoipState::WaitInitAck: {
DEBUG_LOG(("Call Info: State changed to WaitingInitAck."));
setStateQueued(State::WaitingInitAck);
} break;
case tgvoip::STATE_ESTABLISHED: {
case TgVoipState::Established: {
DEBUG_LOG(("Call Info: State changed to Established."));
setStateQueued(State::Established);
} break;
case tgvoip::STATE_FAILED: {
auto error = controller->GetLastError();
case TgVoipState::Failed: {
auto error = QString::fromStdString(controller->getLastError());
LOG(("Call Info: State changed to Failed, error: %1.").arg(error));
setFailedQueued(error);
} break;
default: LOG(("Call Error: Unexpected state in handleStateChange: %1").arg(state));
default: LOG(("Call Error: Unexpected state in handleStateChange: %1"
).arg(int(state)));
}
}
void Call::handleControllerBarCountChange(
tgvoip::VoIPController *controller,
int count) {
void Call::handleControllerBarCountChange(int count) {
// NB! Can be called from an arbitrary thread!
// This can be called from ~VoIPController()!
// Expects(controller == _controller.get());
Expects(controller->implData == static_cast<void*>(this));
crl::on_main(this, [=] {
setSignalBarCount(count);
@@ -790,32 +754,30 @@ void Call::setState(State state) {
}
}
void Call::setCurrentAudioDevice(bool input, std::string deviceID){
void Call::setCurrentAudioDevice(bool input, std::string deviceID) {
if (_controller) {
if (input) {
_controller->SetCurrentAudioInput(deviceID);
_controller->setAudioInputDevice(deviceID);
} else {
_controller->SetCurrentAudioOutput(deviceID);
_controller->setAudioOutputDevice(deviceID);
}
}
}
void Call::setAudioVolume(bool input, float level){
void Call::setAudioVolume(bool input, float level) {
if (_controller) {
if(input) {
_controller->SetInputVolume(level);
if (input) {
_controller->setInputVolume(level);
} else {
_controller->SetOutputVolume(level);
_controller->setOutputVolume(level);
}
}
}
void Call::setAudioDuckingEnabled(bool enabled){
#ifdef Q_OS_MAC
void Call::setAudioDuckingEnabled(bool enabled) {
if (_controller) {
_controller->SetAudioOutputDuckingEnabled(enabled);
_controller->setAudioOutputDuckingEnabled(enabled);
}
#endif
}
void Call::finish(FinishType type, const MTPPhoneCallDiscardReason &reason) {
@@ -844,7 +806,7 @@ void Call::finish(FinishType type, const MTPPhoneCallDiscardReason &reason) {
setState(hangupState);
auto duration = getDurationMs() / 1000;
auto connectionId = _controller ? _controller->GetPreferredRelayID() : 0;
auto connectionId = _controller ? _controller->getPreferredRelayId() : 0;
_finishByTimeoutTimer.call(kHangupTimeoutMs, [this, finalState] { setState(finalState); });
_api.request(MTPphone_DiscardCall(
MTP_flags(0),
@@ -870,7 +832,7 @@ void Call::setStateQueued(State state) {
});
}
void Call::setFailedQueued(int error) {
void Call::setFailedQueued(const QString &error) {
crl::on_main(this, [=] {
handleControllerError(error);
});
@@ -887,13 +849,13 @@ void Call::handleRequestError(const RPCError &error) {
finish(FinishType::Failed);
}
void Call::handleControllerError(int error) {
if (error == tgvoip::ERROR_INCOMPATIBLE) {
void Call::handleControllerError(const QString &error) {
if (error == u"ERROR_INCOMPATIBLE"_q) {
Ui::show(Box<InformBox>(
Lang::Hard::CallErrorIncompatible().replace(
"{user}",
_user->name)));
} else if (error == tgvoip::ERROR_AUDIO_IO) {
} else if (error == u"ERROR_AUDIO_IO"_q) {
Ui::show(Box<InformBox>(tr::lng_call_error_audio_io(tr::now)));
}
finish(FinishType::Failed);
@@ -912,8 +874,8 @@ Call::~Call() {
destroyController();
}
void UpdateConfig(const std::string& data) {
tgvoip::ServerConfig::GetSharedInstance()->Update(data);
void UpdateConfig(const std::string &data) {
TgVoip::setGlobalServerConfig(data);
}
} // namespace Calls

View File

@@ -19,12 +19,12 @@ class Track;
} // namespace Audio
} // namespace Media
namespace tgvoip {
class VoIPController;
} // namespace tgvoip
enum class TgVoipState;
namespace Calls {
class Controller;
struct DhConfig {
int32 version = 0;
int32 g = 0;
@@ -129,30 +129,13 @@ public:
~Call();
private:
class ControllerPointer {
public:
void create();
void reset();
bool empty() const;
bool operator==(std::nullptr_t) const;
explicit operator bool() const;
tgvoip::VoIPController *operator->() const;
tgvoip::VoIPController &operator*() const;
~ControllerPointer();
private:
std::unique_ptr<tgvoip::VoIPController> _data;
};
enum class FinishType {
None,
Ended,
Failed,
};
void handleRequestError(const RPCError &error);
void handleControllerError(int error);
void handleControllerError(const QString &error);
void finish(FinishType type, const MTPPhoneCallDiscardReason &reason = MTP_phoneCallDiscardReasonDisconnect());
void startOutgoing();
void startIncoming();
@@ -160,11 +143,9 @@ private:
void generateModExpFirst(bytes::const_span randomSeed);
void handleControllerStateChange(
tgvoip::VoIPController *controller,
int state);
void handleControllerBarCountChange(
tgvoip::VoIPController *controller,
int count);
not_null<Controller*> controller,
TgVoipState state);
void handleControllerBarCountChange(int count);
void createAndStartController(const MTPDphoneCall &call);
template <typename T>
@@ -177,7 +158,7 @@ private:
void startConfirmedCall(const MTPDphoneCall &call);
void setState(State state);
void setStateQueued(State state);
void setFailedQueued(int error);
void setFailedQueued(const QString &error);
void setSignalBarCount(int count);
void destroyController();
@@ -210,12 +191,12 @@ private:
uint64 _accessHash = 0;
uint64 _keyFingerprint = 0;
ControllerPointer _controller;
std::unique_ptr<Controller> _controller;
std::unique_ptr<Media::Audio::Track> _waitingTrack;
};
void UpdateConfig(const std::string& data);
void UpdateConfig(const std::string &data);
} // namespace Calls

View File

@@ -0,0 +1,31 @@
/*
This file is part of Telegram Desktop,
the official desktop application for the Telegram messaging service.
For license and copyright information please follow this link:
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#include "calls/calls_controller.h"
#include "calls/calls_controller_tgvoip.h"
namespace Calls {
[[nodiscard]] std::unique_ptr<Controller> MakeController(
const std::string &version,
const TgVoipConfig &config,
const TgVoipPersistentState &persistentState,
const std::vector<TgVoipEndpoint> &endpoints,
const TgVoipProxy *proxy,
TgVoipNetworkType initialNetworkType,
const TgVoipEncryptionKey &encryptionKey) {
return std::make_unique<TgVoipController>(
config,
persistentState,
endpoints,
proxy,
initialNetworkType,
encryptionKey);
}
} // namespace Calls

View File

@@ -0,0 +1,53 @@
/*
This file is part of Telegram Desktop,
the official desktop application for the Telegram messaging service.
For license and copyright information please follow this link:
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#pragma once
#include "TgVoip.h"
namespace Calls {
class Controller {
public:
virtual ~Controller() = default;
[[nodiscard]] virtual std::string version() = 0;
virtual void setNetworkType(TgVoipNetworkType networkType) = 0;
virtual void setMuteMicrophone(bool muteMicrophone) = 0;
virtual void setAudioOutputGainControlEnabled(bool enabled) = 0;
virtual void setEchoCancellationStrength(int strength) = 0;
virtual void setAudioInputDevice(std::string id) = 0;
virtual void setAudioOutputDevice(std::string id) = 0;
virtual void setInputVolume(float level) = 0;
virtual void setOutputVolume(float level) = 0;
virtual void setAudioOutputDuckingEnabled(bool enabled) = 0;
virtual std::string getLastError() = 0;
virtual std::string getDebugInfo() = 0;
virtual int64_t getPreferredRelayId() = 0;
virtual TgVoipTrafficStats getTrafficStats() = 0;
virtual TgVoipPersistentState getPersistentState() = 0;
virtual void setOnStateUpdated(Fn<void(TgVoipState)> onStateUpdated) = 0;
virtual void setOnSignalBarsUpdated(
Fn<void(int)> onSignalBarsUpdated) = 0;
virtual TgVoipFinalState stop() = 0;
};
[[nodiscard]] std::unique_ptr<Controller> MakeController(
const std::string &version,
const TgVoipConfig &config,
const TgVoipPersistentState &persistentState,
const std::vector<TgVoipEndpoint> &endpoints,
const TgVoipProxy *proxy,
TgVoipNetworkType initialNetworkType,
const TgVoipEncryptionKey &encryptionKey);
} // namespace Calls

View File

@@ -0,0 +1,97 @@
/*
This file is part of Telegram Desktop,
the official desktop application for the Telegram messaging service.
For license and copyright information please follow this link:
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#pragma once
#include "calls/calls_controller.h"
namespace Calls {
class TgVoipController final : public Controller {
public:
TgVoipController(
const TgVoipConfig &config,
const TgVoipPersistentState &persistentState,
const std::vector<TgVoipEndpoint> &endpoints,
const TgVoipProxy *proxy,
TgVoipNetworkType initialNetworkType,
const TgVoipEncryptionKey &encryptionKey)
: _impl(TgVoip::makeInstance(
config,
persistentState,
endpoints,
proxy,
initialNetworkType,
encryptionKey)) {
}
[[nodiscard]] static std::string Version() {
return TgVoip::getVersion();
}
[[nodiscard]] std::string version() override {
return Version();
}
void setNetworkType(TgVoipNetworkType networkType) override {
_impl->setNetworkType(networkType);
}
void setMuteMicrophone(bool muteMicrophone) override {
_impl->setMuteMicrophone(muteMicrophone);
}
void setAudioOutputGainControlEnabled(bool enabled) override {
_impl->setAudioOutputGainControlEnabled(enabled);
}
void setEchoCancellationStrength(int strength) override {
_impl->setEchoCancellationStrength(strength);
}
void setAudioInputDevice(std::string id) override {
_impl->setAudioInputDevice(id);
}
void setAudioOutputDevice(std::string id) override {
_impl->setAudioOutputDevice(id);
}
void setInputVolume(float level) override {
_impl->setInputVolume(level);
}
void setOutputVolume(float level) override {
_impl->setOutputVolume(level);
}
void setAudioOutputDuckingEnabled(bool enabled) override {
_impl->setAudioOutputDuckingEnabled(enabled);
}
std::string getLastError() override {
return _impl->getLastError();
}
std::string getDebugInfo() override {
return _impl->getDebugInfo();
}
int64_t getPreferredRelayId() override {
return _impl->getPreferredRelayId();
}
TgVoipTrafficStats getTrafficStats() override {
return _impl->getTrafficStats();
}
TgVoipPersistentState getPersistentState() override {
return _impl->getPersistentState();
}
void setOnStateUpdated(Fn<void(TgVoipState)> onStateUpdated) override {
_impl->setOnStateUpdated(std::move(onStateUpdated));
}
void setOnSignalBarsUpdated(
Fn<void(int)> onSignalBarsUpdated) override {
_impl->setOnSignalBarsUpdated(std::move(onSignalBarsUpdated));
}
TgVoipFinalState stop() override {
return _impl->stop();
}
private:
const std::unique_ptr<TgVoip> _impl;
};
} // namespace Calls

View File

@@ -11,6 +11,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_session.h"
#include "data/data_user.h"
#include "data/data_file_origin.h"
#include "data/data_photo_media.h"
#include "data/data_cloud_file.h"
#include "calls/calls_emoji_fingerprint.h"
#include "ui/widgets/buttons.h"
#include "ui/widgets/labels.h"
@@ -299,7 +301,8 @@ QImage Panel::Button::prepareRippleMask() const {
}
Panel::Panel(not_null<Call*> call)
: _call(call)
: RpWidget(Core::App().getModalParent())
, _call(call)
, _user(call->user())
, _answerHangupRedial(this, st::callAnswer, &st::callHangup)
, _decline(this, object_ptr<Button>(this, st::callHangup))
@@ -318,6 +321,8 @@ Panel::Panel(not_null<Call*> call)
showAndActivate();
}
Panel::~Panel() = default;
void Panel::showAndActivate() {
toggleOpacityAnimation(true);
raise();
@@ -463,6 +468,8 @@ void Panel::toggleOpacityAnimation(bool visible) {
_visible ? 1. : 0.,
st::callPanelDuration,
_visible ? anim::easeOutCirc : anim::easeInCirc);
} else if (!isHidden() && !_visible) {
hide();
}
if (isHidden() && _visible) {
show();
@@ -484,6 +491,7 @@ void Panel::finishAnimating() {
void Panel::showControls() {
Expects(_call != nullptr);
showChildren();
_decline->setVisible(_decline->toggled());
_cancel->setVisible(_cancel->toggled());
@@ -505,43 +513,39 @@ void Panel::hideAndDestroy() {
}
void Panel::processUserPhoto() {
if (!_user->userpicLoaded()) {
_user->loadUserpic();
}
_userpic = _user->createUserpicView();
_user->loadUserpic();
const auto photo = _user->userpicPhotoId()
? _user->owner().photo(_user->userpicPhotoId()).get()
: nullptr;
if (isGoodUserPhoto(photo)) {
photo->large()->load(_user->userpicPhotoOrigin());
} else if (_user->userpicPhotoUnknown() || (photo && !photo->date)) {
_user->session().api().requestFullPeer(_user);
_photo = photo->createMediaView();
_photo->wanted(Data::PhotoSize::Large, _user->userpicPhotoOrigin());
} else {
_photo = nullptr;
if (_user->userpicPhotoUnknown() || (photo && !photo->date)) {
_user->session().api().requestFullPeer(_user);
}
}
refreshUserPhoto();
}
void Panel::refreshUserPhoto() {
const auto photo = _user->userpicPhotoId()
? _user->owner().photo(_user->userpicPhotoId()).get()
: nullptr;
const auto isNewPhoto = [&](not_null<PhotoData*> photo) {
return photo->large()->loaded()
&& (photo->id != _userPhotoId || !_userPhotoFull);
};
if (isGoodUserPhoto(photo) && isNewPhoto(photo)) {
_userPhotoId = photo->id;
const auto isNewBigPhoto = [&] {
return _photo
&& _photo->loaded()
&& (_photo->owner()->id != _userPhotoId || !_userPhotoFull);
}();
if (isNewBigPhoto) {
_userPhotoId = _photo->owner()->id;
_userPhotoFull = true;
createUserpicCache(
photo->isNull() ? nullptr : photo->large().get(),
_user->userpicPhotoOrigin());
createUserpicCache(_photo->image(Data::PhotoSize::Large));
} else if (_userPhoto.isNull()) {
const auto userpic = _user->currentUserpic();
createUserpicCache(
userpic ? userpic.get() : nullptr,
_user->userpicOrigin());
createUserpicCache(_userpic ? _userpic->image() : nullptr);
}
}
void Panel::createUserpicCache(Image *image, Data::FileOrigin origin) {
void Panel::createUserpicCache(Image *image) {
auto size = st::callWidth * cIntRetinaFactor();
auto options = _useTransparency ? (Images::Option::RoundedLarge | Images::Option::RoundedTopLeft | Images::Option::RoundedTopRight | Images::Option::Smooth) : Images::Option::None;
if (image) {
@@ -555,7 +559,6 @@ void Panel::createUserpicCache(Image *image, Data::FileOrigin origin) {
width = size;
}
_userPhoto = image->pixNoCache(
origin,
width,
height,
options,
@@ -593,14 +596,18 @@ bool Panel::isGoodUserPhoto(PhotoData *photo) {
}
void Panel::initGeometry() {
auto center = Core::App().getPointForCallPanelCenter();
const auto center = Core::App().getPointForCallPanelCenter();
_useTransparency = Ui::Platform::TranslucentWindowsSupported(center);
setAttribute(Qt::WA_OpaquePaintEvent, !_useTransparency);
_padding = _useTransparency ? st::callShadow.extend : style::margins(st::lineWidth, st::lineWidth, st::lineWidth, st::lineWidth);
_contentTop = _padding.top() + st::callWidth;
auto screen = QApplication::desktop()->screenGeometry(center);
auto rect = QRect(0, 0, st::callWidth, st::callHeight);
setGeometry(rect.translated(center - rect.center()).marginsAdded(_padding));
const auto rect = [&] {
const QRect initRect(0, 0, st::callWidth, st::callHeight);
return initRect.translated(center - initRect.center()).marginsAdded(_padding);
}();
setGeometry(rect);
setMinimumSize(rect.size());
setMaximumSize(rect.size());
createBottomImage();
updateControlsGeometry();
}

View File

@@ -14,6 +14,13 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/effects/animations.h"
#include "ui/rp_widget.h"
class Image;
namespace Data {
class PhotoMedia;
class CloudImageView;
} // namespace Data
namespace Ui {
class IconButton;
class FlatLabel;
@@ -56,6 +63,7 @@ class Panel
public:
Panel(not_null<Call*> call);
~Panel();
void showAndActivate();
void replaceCall(not_null<Call*> call);
@@ -93,7 +101,7 @@ private:
void processUserPhoto();
void refreshUserPhoto();
bool isGoodUserPhoto(PhotoData *photo);
void createUserpicCache(Image *image, Data::FileOrigin origin);
void createUserpicCache(Image *image);
QRect signalBarsRect() const;
void paintSignalBarsBg(Painter &p);
@@ -111,6 +119,8 @@ private:
Call *_call = nullptr;
not_null<UserData*> _user;
std::shared_ptr<Data::CloudImageView> _userpic;
std::shared_ptr<Data::PhotoMedia> _photo;
bool _useTransparency = true;
style::margins _padding;

View File

@@ -40,15 +40,15 @@ inline auto PreviewPath(int i) {
const auto kSets = {
Set{ {0, 0, 0, "Mac"}, PreviewPath(0) },
Set{ {1, 246, 7'336'383, "Android"}, PreviewPath(1) },
Set{ {2, 206, 5'038'738, "Twemoji"}, PreviewPath(2) },
Set{ {3, 238, 6'992'260, "JoyPixels"}, PreviewPath(3) },
Set{ {1, 713, 7'313'166, "Android"}, PreviewPath(1) },
Set{ {2, 714, 4'690'333, "Twemoji"}, PreviewPath(2) },
Set{ {3, 716, 5'968'021, "JoyPixels"}, PreviewPath(3) },
};
using Loading = MTP::DedicatedLoader::Progress;
using SetState = BlobState;
class Loader : public BlobLoader {
class Loader final : public BlobLoader {
public:
Loader(
QObject *parent,
@@ -60,6 +60,9 @@ public:
void destroy() override;
void unpack(const QString &path) override;
private:
void fail() override;
};
class Inner : public Ui::RpWidget {
@@ -155,12 +158,14 @@ bool UnpackSet(const QString &path, const QString &folder) {
return UnpackBlob(path, folder, GoodSetPartName);
}
Loader::Loader(
QObject *parent,
int id,
MTP::DedicatedLoader::Location location,
const QString &folder,
int size) : BlobLoader(parent, id, location, folder, size) {
int size)
: BlobLoader(parent, id, location, folder, size) {
}
void Loader::unpack(const QString &path) {
@@ -190,6 +195,11 @@ void Loader::destroy() {
SetGlobalLoader(nullptr);
}
void Loader::fail() {
ClearNeedSwitchToId();
BlobLoader::fail();
}
Inner::Inner(QWidget *parent) : RpWidget(parent) {
setupContent();
}
@@ -401,12 +411,7 @@ void Row::setupHandler() {
}
void Row::load() {
SetGlobalLoader(base::make_unique_q<Loader>(
App::main(),
_id,
GetDownloadLocation(_id),
internal::SetDataPath(_id),
GetDownloadSize(_id)));
LoadAndSwitchTo(_id);
}
void Row::setupLabels(const Set &set) {
@@ -538,5 +543,20 @@ void ManageSetsBox::prepare() {
setDimensionsToContent(st::boxWidth, inner);
}
void LoadAndSwitchTo(int id) {
Expects(App::main() != nullptr);
if (!ranges::contains(kSets, id, &Set::id)) {
ClearNeedSwitchToId();
return;
}
SetGlobalLoader(base::make_unique_q<Loader>(
App::main(),
id,
GetDownloadLocation(id),
internal::SetDataPath(id),
GetDownloadSize(id)));
}
} // namespace Emoji
} // namespace Ui

View File

@@ -21,5 +21,7 @@ protected:
};
void LoadAndSwitchTo(int id);
} // namespace Emoji
} // namespace Ui

View File

@@ -8,6 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "chat_helpers/field_autocomplete.h"
#include "data/data_document.h"
#include "data/data_document_media.h"
#include "data/data_channel.h"
#include "data/data_chat.h"
#include "data/data_user.h"
@@ -20,9 +21,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/widgets/scroll_area.h"
#include "ui/image/image.h"
#include "ui/ui_utility.h"
#include "main/main_session.h"
#include "chat_helpers/stickers.h"
#include "base/unixtime.h"
#include "window/window_session_controller.h"
#include "facades.h"
#include "app.h"
#include "styles/style_history.h"
@@ -33,14 +34,15 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
FieldAutocomplete::FieldAutocomplete(
QWidget *parent,
not_null<Main::Session*> session)
not_null<Window::SessionController*> controller)
: RpWidget(parent)
, _session(session)
, _controller(controller)
, _scroll(this, st::mentionScroll) {
_scroll->setGeometry(rect());
_inner = _scroll->setOwnedWidget(
object_ptr<internal::FieldAutocompleteInner>(
_controller,
this,
&_mrows,
&_hrows,
@@ -48,9 +50,9 @@ FieldAutocomplete::FieldAutocomplete(
&_srows));
_inner->setGeometry(rect());
connect(_inner, SIGNAL(mentionChosen(UserData*, FieldAutocomplete::ChooseMethod)), this, SIGNAL(mentionChosen(UserData*, FieldAutocomplete::ChooseMethod)));
connect(_inner, SIGNAL(hashtagChosen(QString, FieldAutocomplete::ChooseMethod)), this, SIGNAL(hashtagChosen(QString, FieldAutocomplete::ChooseMethod)));
connect(_inner, SIGNAL(botCommandChosen(QString, FieldAutocomplete::ChooseMethod)), this, SIGNAL(botCommandChosen(QString, FieldAutocomplete::ChooseMethod)));
connect(_inner, SIGNAL(mentionChosen(not_null<UserData*>,FieldAutocomplete::ChooseMethod)), this, SIGNAL(mentionChosen(not_null<UserData*>,FieldAutocomplete::ChooseMethod)));
connect(_inner, SIGNAL(hashtagChosen(QString,FieldAutocomplete::ChooseMethod)), this, SIGNAL(hashtagChosen(QString,FieldAutocomplete::ChooseMethod)));
connect(_inner, SIGNAL(botCommandChosen(QString,FieldAutocomplete::ChooseMethod)), this, SIGNAL(botCommandChosen(QString,FieldAutocomplete::ChooseMethod)));
connect(_inner, SIGNAL(stickerChosen(not_null<DocumentData*>,FieldAutocomplete::ChooseMethod)), this, SIGNAL(stickerChosen(not_null<DocumentData*>,FieldAutocomplete::ChooseMethod)));
connect(_inner, SIGNAL(mustScrollTo(int, int)), _scroll, SLOT(scrollToY(int, int)));
@@ -150,7 +152,9 @@ void FieldAutocomplete::showStickers(EmojiPtr emoji) {
}
bool FieldAutocomplete::clearFilteredBotCommands() {
if (_brows.isEmpty()) return false;
if (_brows.empty()) {
return false;
}
_brows.clear();
return true;
}
@@ -158,8 +162,8 @@ bool FieldAutocomplete::clearFilteredBotCommands() {
namespace {
template <typename T, typename U>
inline int indexOfInFirstN(const T &v, const U &elem, int last) {
for (auto b = v.cbegin(), i = b, e = b + qMax(v.size(), last); i != e; ++i) {
if (*i == elem) {
for (auto b = v.cbegin(), i = b, e = b + std::max(int(v.size()), last); i != e; ++i) {
if (i->user == elem) {
return (i - b);
}
}
@@ -169,14 +173,17 @@ inline int indexOfInFirstN(const T &v, const U &elem, int last) {
internal::StickerRows FieldAutocomplete::getStickerSuggestions() {
const auto list = Stickers::GetListByEmoji(
_session,
&_controller->session(),
_emoji,
_stickersSeed
);
auto result = ranges::view::all(
list
) | ranges::view::transform([](not_null<DocumentData*> sticker) {
return internal::StickerSuggestion{ sticker };
return internal::StickerSuggestion{
sticker,
sticker->createMediaView()
};
}) | ranges::to_vector;
for (auto &suggestion : _srows) {
if (!suggestion.animated) {
@@ -236,12 +243,12 @@ void FieldAutocomplete::updateFiltered(bool resetScroll) {
for_const (auto user, cRecentInlineBots()) {
if (user->isInaccessible()) continue;
if (!listAllSuggestions && filterNotPassedByUsername(user)) continue;
mrows.push_back(user);
mrows.push_back({ user });
++recentInlineBots;
}
}
if (_chat) {
auto ordered = QMultiMap<TimeId, not_null<UserData*>>();
auto sorted = base::flat_multi_map<TimeId, not_null<UserData*>>();
const auto byOnline = [&](not_null<UserData*> user) {
return Data::SortByOnlineValue(user, now);
};
@@ -253,23 +260,19 @@ void FieldAutocomplete::updateFiltered(bool resetScroll) {
if (user->isInaccessible()) continue;
if (!listAllSuggestions && filterNotPassedByName(user)) continue;
if (indexOfInFirstN(mrows, user, recentInlineBots) >= 0) continue;
ordered.insertMulti(byOnline(user), user);
sorted.emplace(byOnline(user), user);
}
}
for (const auto user : _chat->lastAuthors) {
if (user->isInaccessible()) continue;
if (!listAllSuggestions && filterNotPassedByName(user)) continue;
if (indexOfInFirstN(mrows, user, recentInlineBots) >= 0) continue;
mrows.push_back(user);
if (!ordered.isEmpty()) {
ordered.remove(byOnline(user), user);
}
mrows.push_back({ user });
sorted.remove(byOnline(user), user);
}
if (!ordered.isEmpty()) {
for (auto i = ordered.cend(), b = ordered.cbegin(); i != b;) {
--i;
mrows.push_back(i.value());
}
for (auto i = sorted.cend(), b = sorted.cbegin(); i != b;) {
--i;
mrows.push_back({ i->second });
}
} else if (_channel && _channel->isMegagroup()) {
QMultiMap<int32, UserData*> ordered;
@@ -281,7 +284,7 @@ void FieldAutocomplete::updateFiltered(bool resetScroll) {
if (user->isInaccessible()) continue;
if (!listAllSuggestions && filterNotPassedByName(user)) continue;
if (indexOfInFirstN(mrows, user, recentInlineBots) >= 0) continue;
mrows.push_back(user);
mrows.push_back({ user });
}
}
}
@@ -373,7 +376,7 @@ void FieldAutocomplete::updateFiltered(bool resetScroll) {
continue;
}
}
brows.push_back(qMakePair(user, &user->botInfo->commands.at(j)));
brows.push_back({ user, &user->botInfo->commands.at(j) });
}
}
}
@@ -385,7 +388,7 @@ void FieldAutocomplete::updateFiltered(bool resetScroll) {
QString toFilter = (hasUsername || botStatus == 0 || botStatus == 2) ? user->botInfo->commands.at(j).command + '@' + user->username : user->botInfo->commands.at(j).command;
if (!toFilter.startsWith(_filter, Qt::CaseInsensitive)/* || toFilter.size() == _filter.size()*/) continue;
}
brows.push_back(qMakePair(user, &user->botInfo->commands.at(j)));
brows.push_back({ user, &user->botInfo->commands.at(j) });
}
}
}
@@ -406,7 +409,7 @@ void FieldAutocomplete::rowsUpdated(
internal::BotCommandRows &&brows,
internal::StickerRows &&srows,
bool resetScroll) {
if (mrows.isEmpty() && hrows.isEmpty() && brows.isEmpty() && srows.empty()) {
if (mrows.empty() && hrows.empty() && brows.empty() && srows.empty()) {
if (!isHidden()) {
hideAnimated();
}
@@ -447,11 +450,11 @@ void FieldAutocomplete::recount(bool resetScroll) {
int32 stickersPerRow = qMax(1, int32(_boundings.width() - 2 * st::stickerPanPadding) / int32(st::stickerPanSize.width()));
int32 rows = rowscount(_srows.size(), stickersPerRow);
h = st::stickerPanPadding + rows * st::stickerPanSize.height();
} else if (!_mrows.isEmpty()) {
} else if (!_mrows.empty()) {
h = _mrows.size() * st::mentionHeight;
} else if (!_hrows.isEmpty()) {
} else if (!_hrows.empty()) {
h = _hrows.size() * st::mentionHeight;
} else if (!_brows.isEmpty()) {
} else if (!_brows.empty()) {
h = _brows.size() * st::mentionHeight;
}
@@ -584,12 +587,14 @@ bool FieldAutocomplete::eventFilter(QObject *obj, QEvent *e) {
namespace internal {
FieldAutocompleteInner::FieldAutocompleteInner(
not_null<Window::SessionController*> controller,
not_null<FieldAutocomplete*> parent,
not_null<MentionRows*> mrows,
not_null<HashtagRows*> hrows,
not_null<BotCommandRows*> brows,
not_null<StickerRows*> srows)
: _parent(parent)
: _controller(controller)
, _parent(parent)
, _mrows(mrows)
, _hrows(hrows)
, _brows(brows)
@@ -631,11 +636,12 @@ void FieldAutocompleteInner::paintEvent(QPaintEvent *e) {
auto &sticker = (*_srows)[index];
const auto document = sticker.document;
const auto &media = sticker.documentMedia;
if (!document->sticker()) continue;
if (document->sticker()->animated
&& !sticker.animated
&& document->loaded()) {
&& media->loaded()) {
setupLottie(sticker);
}
@@ -646,7 +652,7 @@ void FieldAutocompleteInner::paintEvent(QPaintEvent *e) {
App::roundRect(p, QRect(tl, st::stickerPanSize), st::emojiPanHover, StickerHoverCorners);
}
document->checkStickerSmall();
media->checkStickerSmall();
auto w = 1;
auto h = 1;
if (sticker.animated && !document->dimensions.isEmpty()) {
@@ -665,7 +671,6 @@ void FieldAutocompleteInner::paintEvent(QPaintEvent *e) {
}
if (sticker.animated && sticker.animated->ready()) {
const auto frame = sticker.animated->frame();
sticker.animated->markFrameShown();
const auto size = frame.size() / cIntRetinaFactor();
const auto ppos = pos + QPoint(
(st::stickerPanSize.width() - size.width()) / 2,
@@ -673,15 +678,24 @@ void FieldAutocompleteInner::paintEvent(QPaintEvent *e) {
p.drawImage(
QRect(ppos, size),
frame);
} else if (const auto image = document->getStickerSmall()) {
const auto paused = _controller->isGifPausedAtLeastFor(
Window::GifPauseReason::SavedGifs);
if (!paused) {
sticker.animated->markFrameShown();
}
} else if (const auto image = media->getStickerSmall()) {
QPoint ppos = pos + QPoint((st::stickerPanSize.width() - w) / 2, (st::stickerPanSize.height() - h) / 2);
p.drawPixmapLeft(ppos, width(), image->pix(document->stickerSetOrigin(), w, h));
p.drawPixmapLeft(ppos, width(), image->pix(w, h));
}
}
}
} else {
int32 from = qFloor(e->rect().top() / st::mentionHeight), to = qFloor(e->rect().bottom() / st::mentionHeight) + 1;
int32 last = _mrows->isEmpty() ? (_hrows->isEmpty() ? _brows->size() : _hrows->size()) : _mrows->size();
int32 last = !_mrows->empty()
? _mrows->size()
: !_hrows->empty()
? _hrows->size()
: _brows->size();
auto filter = _parent->filter();
bool hasUsername = filter.indexOf('@') > 0;
int filterSize = filter.size();
@@ -693,12 +707,13 @@ void FieldAutocompleteInner::paintEvent(QPaintEvent *e) {
if (selected) {
p.fillRect(0, i * st::mentionHeight, width(), st::mentionHeight, st::mentionBgOver);
int skip = (st::mentionHeight - st::smallCloseIconOver.height()) / 2;
if (!_hrows->isEmpty() || (!_mrows->isEmpty() && i < _recentInlineBotsInRows)) {
if (!_hrows->empty() || (!_mrows->empty() && i < _recentInlineBotsInRows)) {
st::smallCloseIconOver.paint(p, QPoint(width() - st::smallCloseIconOver.width() - skip, i * st::mentionHeight + skip), width());
}
}
if (!_mrows->isEmpty()) {
const auto user = _mrows->at(i);
if (!_mrows->empty()) {
auto &row = _mrows->at(i);
const auto user = row.user;
auto first = (!filterIsEmpty && user->username.startsWith(filter, Qt::CaseInsensitive)) ? ('@' + user->username.mid(0, filterSize)) : QString();
auto second = first.isEmpty() ? (user->username.isEmpty() ? QString() : ('@' + user->username)) : user->username.mid(filterSize);
auto firstwidth = st::mentionFont->width(first);
@@ -720,7 +735,7 @@ void FieldAutocompleteInner::paintEvent(QPaintEvent *e) {
}
}
user->loadUserpic();
user->paintUserpicLeft(p, st::mentionPadding.left(), i * st::mentionHeight + st::mentionPadding.top(), width(), st::mentionPhotoSize);
user->paintUserpicLeft(p, row.userpic, st::mentionPadding.left(), i * st::mentionHeight + st::mentionPadding.top(), width(), st::mentionPhotoSize);
p.setPen(selected ? st::mentionNameFgOver : st::mentionNameFg);
user->nameText().drawElided(p, 2 * st::mentionPadding.left() + st::mentionPhotoSize, i * st::mentionHeight + st::mentionTop, namewidth);
@@ -732,7 +747,7 @@ void FieldAutocompleteInner::paintEvent(QPaintEvent *e) {
p.setPen(selected ? st::mentionFgOver : st::mentionFg);
p.drawText(mentionleft + namewidth + st::mentionPadding.right() + firstwidth, i * st::mentionHeight + st::mentionTop + st::mentionFont->ascent, second);
}
} else if (!_hrows->isEmpty()) {
} else if (!_hrows->empty()) {
QString hrow = _hrows->at(i);
QString first = filterIsEmpty ? QString() : ('#' + hrow.mid(0, filterSize));
QString second = filterIsEmpty ? ('#' + hrow) : hrow.mid(filterSize);
@@ -756,16 +771,17 @@ void FieldAutocompleteInner::paintEvent(QPaintEvent *e) {
p.drawText(htagleft + firstwidth, i * st::mentionHeight + st::mentionTop + st::mentionFont->ascent, second);
}
} else {
UserData *user = _brows->at(i).first;
auto &row = _brows->at(i);
const auto user = row.user;
const BotCommand *command = _brows->at(i).second;
QString toHighlight = command->command;
const auto command = row.command;
auto toHighlight = command->command;
int32 botStatus = _parent->chat() ? _parent->chat()->botStatus : ((_parent->channel() && _parent->channel()->isMegagroup()) ? _parent->channel()->mgInfo->botStatus : -1);
if (hasUsername || botStatus == 0 || botStatus == 2) {
toHighlight += '@' + user->username;
}
user->loadUserpic();
user->paintUserpicLeft(p, st::mentionPadding.left(), i * st::mentionHeight + st::mentionPadding.top(), width(), st::mentionPhotoSize);
user->paintUserpicLeft(p, row.userpic, st::mentionPadding.left(), i * st::mentionHeight + st::mentionPadding.top(), width(), st::mentionPhotoSize);
auto commandText = '/' + toHighlight;
@@ -808,7 +824,7 @@ void FieldAutocompleteInner::clearSel(bool hidden) {
_overDelete = false;
_mouseSelection = false;
_lastMousePosition = std::nullopt;
setSel((_mrows->isEmpty() && _brows->isEmpty() && _hrows->isEmpty()) ? -1 : 0);
setSel((_mrows->empty() && _brows->empty() && _hrows->empty()) ? -1 : 0);
if (hidden) {
_down = -1;
_previewShown = false;
@@ -819,7 +835,13 @@ bool FieldAutocompleteInner::moveSel(int key) {
_mouseSelection = false;
_lastMousePosition = std::nullopt;
int32 maxSel = (_mrows->isEmpty() ? (_hrows->isEmpty() ? (_brows->isEmpty() ? _srows->size() : _brows->size()) : _hrows->size()) : _mrows->size());
int32 maxSel = !_mrows->empty()
? _mrows->size()
: !_hrows->empty()
? _hrows->size()
: !_brows->empty()
? _brows->size()
: _srows->size();
int32 direction = (key == Qt::Key_Up) ? -1 : (key == Qt::Key_Down ? 1 : 0);
if (!_srows->empty()) {
if (key == Qt::Key_Left) {
@@ -850,20 +872,20 @@ bool FieldAutocompleteInner::chooseSelected(FieldAutocomplete::ChooseMethod meth
emit stickerChosen((*_srows)[_sel].document, method);
return true;
}
} else if (!_mrows->isEmpty()) {
} else if (!_mrows->empty()) {
if (_sel >= 0 && _sel < _mrows->size()) {
emit mentionChosen(_mrows->at(_sel), method);
emit mentionChosen(_mrows->at(_sel).user, method);
return true;
}
} else if (!_hrows->isEmpty()) {
} else if (!_hrows->empty()) {
if (_sel >= 0 && _sel < _hrows->size()) {
emit hashtagChosen('#' + _hrows->at(_sel), method);
return true;
}
} else if (!_brows->isEmpty()) {
} else if (!_brows->empty()) {
if (_sel >= 0 && _sel < _brows->size()) {
UserData *user = _brows->at(_sel).first;
const BotCommand *command(_brows->at(_sel).second);
const auto user = _brows->at(_sel).user;
const auto command = _brows->at(_sel).command;
int32 botStatus = _parent->chat() ? _parent->chat()->botStatus : ((_parent->channel() && _parent->channel()->isMegagroup()) ? _parent->channel()->mgInfo->botStatus : -1);
if (botStatus == 0 || botStatus == 2 || _parent->filter().indexOf('@') > 0) {
emit botCommandChosen('/' + command->command + '@' + user->username, method);
@@ -883,9 +905,9 @@ void FieldAutocompleteInner::setRecentInlineBotsInRows(int32 bots) {
void FieldAutocompleteInner::mousePressEvent(QMouseEvent *e) {
selectByMouse(e->globalPos());
if (e->button() == Qt::LeftButton) {
if (_overDelete && _sel >= 0 && _sel < (_mrows->isEmpty() ? _hrows->size() : _recentInlineBotsInRows)) {
if (_overDelete && _sel >= 0 && _sel < (_mrows->empty() ? _hrows->size() : _recentInlineBotsInRows)) {
bool removed = false;
if (_mrows->isEmpty()) {
if (_mrows->empty()) {
QString toRemove = _hrows->at(_sel);
RecentHashtagPack &recent(cRefRecentWriteHashtags());
for (RecentHashtagPack::iterator i = recent.begin(); i != recent.cend();) {
@@ -897,7 +919,7 @@ void FieldAutocompleteInner::mousePressEvent(QMouseEvent *e) {
}
}
} else {
UserData *toRemove = _mrows->at(_sel);
UserData *toRemove = _mrows->at(_sel).user;
RecentInlineBots &recent(cRefRecentInlineBots());
int32 index = recent.indexOf(toRemove);
if (index >= 0) {
@@ -996,7 +1018,7 @@ auto FieldAutocompleteInner::getLottieRenderer()
void FieldAutocompleteInner::setupLottie(StickerSuggestion &suggestion) {
const auto document = suggestion.document;
suggestion.animated = Stickers::LottiePlayerFromDocument(
document,
suggestion.documentMedia.get(),
Stickers::LottieSize::InlineResults,
stickerBoundingBox() * cIntRetinaFactor(),
Lottie::Quality::Default,
@@ -1054,8 +1076,12 @@ void FieldAutocompleteInner::selectByMouse(QPoint globalPosition) {
_overDelete = false;
} else {
sel = mouse.y() / int32(st::mentionHeight);
maxSel = _mrows->isEmpty() ? (_hrows->isEmpty() ? _brows->size() : _hrows->size()) : _mrows->size();
_overDelete = (!_hrows->isEmpty() || (!_mrows->isEmpty() && sel < _recentInlineBotsInRows)) ? (mouse.x() >= width() - st::mentionHeight) : false;
maxSel = !_mrows->empty()
? _mrows->size()
: !_hrows->empty()
? _hrows->size()
: _brows->size();
_overDelete = (!_hrows->empty() || (!_mrows->empty() && sel < _recentInlineBotsInRows)) ? (mouse.x() >= width() - st::mentionHeight) : false;
}
if (sel < 0 || sel >= maxSel) {
sel = -1;

View File

@@ -22,21 +22,38 @@ class SinglePlayer;
class FrameRenderer;
} // namespace Lottie;
namespace Main {
class Session;
} // namespace Main
namespace Window {
class SessionController;
} // namespace Window
namespace Data {
class DocumentMedia;
class CloudImageView;
} // namespace Data
namespace internal {
struct StickerSuggestion {
not_null<DocumentData*> document;
std::shared_ptr<Data::DocumentMedia> documentMedia;
std::unique_ptr<Lottie::SinglePlayer> animated;
};
using MentionRows = QList<UserData*>;
using HashtagRows = QList<QString>;
using BotCommandRows = QList<QPair<UserData*, const BotCommand*>>;
struct MentionRow {
not_null<UserData*> user;
std::shared_ptr<Data::CloudImageView> userpic;
};
struct BotCommandRow {
not_null<UserData*> user;
not_null<const BotCommand*> command;
std::shared_ptr<Data::CloudImageView> userpic;
};
using HashtagRows = std::vector<QString>;
using BotCommandRows = std::vector<BotCommandRow>;
using StickerRows = std::vector<StickerSuggestion>;
using MentionRows = std::vector<MentionRow>;
class FieldAutocompleteInner;
@@ -46,7 +63,9 @@ class FieldAutocomplete final : public Ui::RpWidget {
Q_OBJECT
public:
FieldAutocomplete(QWidget *parent, not_null<Main::Session*> session);
FieldAutocomplete(
QWidget *parent,
not_null<Window::SessionController*> controller);
~FieldAutocomplete();
bool clearFilteredBotCommands();
@@ -87,7 +106,7 @@ public:
void hideFast();
signals:
void mentionChosen(UserData *user, FieldAutocomplete::ChooseMethod method) const;
void mentionChosen(not_null<UserData*> user, FieldAutocomplete::ChooseMethod method) const;
void hashtagChosen(QString hashtag, FieldAutocomplete::ChooseMethod method) const;
void botCommandChosen(QString command, FieldAutocomplete::ChooseMethod method) const;
void stickerChosen(not_null<DocumentData*> sticker, FieldAutocomplete::ChooseMethod method) const;
@@ -109,7 +128,7 @@ private:
void recount(bool resetScroll = false);
internal::StickerRows getStickerSuggestions();
const not_null<Main::Session*> _session;
const not_null<Window::SessionController*> _controller;
QPixmap _cache;
internal::MentionRows _mrows;
internal::HashtagRows _hrows;
@@ -160,6 +179,7 @@ class FieldAutocompleteInner final
public:
FieldAutocompleteInner(
not_null<Window::SessionController*> controller,
not_null<FieldAutocomplete*> parent,
not_null<MentionRows*> mrows,
not_null<HashtagRows*> hrows,
@@ -174,7 +194,7 @@ public:
void rowsUpdated();
signals:
void mentionChosen(UserData *user, FieldAutocomplete::ChooseMethod method) const;
void mentionChosen(not_null<UserData*> user, FieldAutocomplete::ChooseMethod method) const;
void hashtagChosen(QString hashtag, FieldAutocomplete::ChooseMethod method) const;
void botCommandChosen(QString command, FieldAutocomplete::ChooseMethod method) const;
void stickerChosen(not_null<DocumentData*> sticker, FieldAutocomplete::ChooseMethod method) const;
@@ -204,11 +224,12 @@ private:
void repaintSticker(not_null<DocumentData*> document);
std::shared_ptr<Lottie::FrameRenderer> getLottieRenderer();
not_null<FieldAutocomplete*> _parent;
not_null<MentionRows*> _mrows;
not_null<HashtagRows*> _hrows;
not_null<BotCommandRows*> _brows;
not_null<StickerRows*> _srows;
const not_null<Window::SessionController*> _controller;
const not_null<FieldAutocomplete*> _parent;
const not_null<MentionRows*> _mrows;
const not_null<HashtagRows*> _hrows;
const not_null<BotCommandRows*> _brows;
const not_null<StickerRows*> _srows;
rpl::lifetime _stickersLifetime;
std::weak_ptr<Lottie::FrameRenderer> _lottieRenderer;
int _stickersPerRow = 1;

View File

@@ -13,6 +13,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_session.h"
#include "data/data_user.h"
#include "data/data_file_origin.h"
#include "data/data_photo_media.h"
#include "data/data_document_media.h"
#include "ui/widgets/buttons.h"
#include "ui/widgets/input_fields.h"
#include "ui/effects/ripple_animation.h"
@@ -248,14 +250,18 @@ void GifsListWidget::inlineResultsDone(const MTPmessages_BotResults &result) {
_inlineQuery,
std::make_unique<InlineCacheEntry>()).first;
}
auto entry = it->second.get();
const auto entry = it->second.get();
entry->nextOffset = qs(d.vnext_offset().value_or_empty());
if (auto count = v.size()) {
if (const auto count = v.size()) {
entry->results.reserve(entry->results.size() + count);
}
auto added = 0;
for_const (const auto &res, v) {
if (auto result = InlineBots::Result::create(queryId, res)) {
for (const auto &res : v) {
auto result = InlineBots::Result::Create(
&controller()->session(),
queryId,
res);
if (result) {
++added;
entry->results.push_back(std::move(result));
}
@@ -369,23 +375,32 @@ void GifsListWidget::selectInlineResult(int row, int column) {
return;
}
const auto ctrl = (QGuiApplication::keyboardModifiers()
== Qt::ControlModifier);
auto item = _rows[row].items[column];
if (const auto photo = item->getPhoto()) {
if (photo->thumbnail()->loaded()) {
using Data::PhotoSize;
const auto media = photo->activeMediaView();
if (ctrl
|| (media && media->image(PhotoSize::Thumbnail))
|| (media && media->image(PhotoSize::Large))) {
_photoChosen.fire_copy(photo);
} else if (!photo->thumbnail()->loading()) {
photo->thumbnail()->loadEvenCancelled(Data::FileOrigin());
} else if (!photo->loading(PhotoSize::Thumbnail)) {
photo->load(PhotoSize::Thumbnail, Data::FileOrigin());
}
} else if (const auto document = item->getDocument()) {
if (document->loaded()
|| QGuiApplication::keyboardModifiers() == Qt::ControlModifier) {
const auto media = document->activeMediaView();
const auto preview = Data::VideoPreviewState(media.get());
if (ctrl || (media && preview.loaded())) {
_fileChosen.fire_copy(document);
} else if (document->loading()) {
document->cancel();
} else {
document->save(
document->stickerOrGifOrigin(),
QString());
} else if (!preview.usingThumbnail()) {
if (preview.loading()) {
document->cancel();
} else {
document->save(
document->stickerOrGifOrigin(),
QString());
}
}
} else if (const auto inlineResult = item->getResult()) {
if (inlineResult->onChoose(item)) {
@@ -429,28 +444,21 @@ TabbedSelector::InnerFooter *GifsListWidget::getFooter() const {
void GifsListWidget::processHideFinished() {
clearSelection();
clearHeavyData();
}
void GifsListWidget::processPanelHideFinished() {
const auto itemForget = [](const auto &item) {
if (const auto document = item->getDocument()) {
document->unload();
}
if (const auto photo = item->getPhoto()) {
photo->unload();
}
if (const auto result = item->getResult()) {
result->unload();
}
item->unloadAnimation();
};
clearHeavyData();
}
void GifsListWidget::clearHeavyData() {
// Preserve panel state through visibility toggles.
//clearInlineRows(false);
for (const auto &[document, layout] : _gifLayouts) {
itemForget(layout);
layout->unloadHeavyPart();
}
for (const auto &[document, layout] : _inlineLayouts) {
itemForget(layout);
layout->unloadHeavyPart();
}
}
@@ -541,11 +549,13 @@ void GifsListWidget::clearInlineRows(bool resultsDeleted) {
_rows.clear();
}
GifsListWidget::LayoutItem *GifsListWidget::layoutPrepareSavedGif(DocumentData *doc, int32 position) {
auto it = _gifLayouts.find(doc);
GifsListWidget::LayoutItem *GifsListWidget::layoutPrepareSavedGif(
not_null<DocumentData*> document,
int32 position) {
auto it = _gifLayouts.find(document);
if (it == _gifLayouts.cend()) {
if (auto layout = LayoutItem::createLayoutGif(this, doc)) {
it = _gifLayouts.emplace(doc, std::move(layout)).first;
if (auto layout = LayoutItem::createLayoutGif(this, document)) {
it = _gifLayouts.emplace(document, std::move(layout)).first;
it->second->initDimensions();
} else {
return nullptr;
@@ -557,10 +567,15 @@ GifsListWidget::LayoutItem *GifsListWidget::layoutPrepareSavedGif(DocumentData *
return it->second.get();
}
GifsListWidget::LayoutItem *GifsListWidget::layoutPrepareInlineResult(InlineResult *result, int32 position) {
GifsListWidget::LayoutItem *GifsListWidget::layoutPrepareInlineResult(
not_null<InlineResult*> result,
int32 position) {
auto it = _inlineLayouts.find(result);
if (it == _inlineLayouts.cend()) {
if (auto layout = LayoutItem::createLayout(this, result, _inlineWithThumb)) {
if (auto layout = LayoutItem::createLayout(
this,
result,
_inlineWithThumb)) {
it = _inlineLayouts.emplace(result, std::move(layout)).first;
it->second->initDimensions();
} else {
@@ -1026,15 +1041,13 @@ void GifsListWidget::showPreview() {
auto layout = _rows[row].items[col];
if (const auto w = App::wnd()) {
if (const auto previewDocument = layout->getPreviewDocument()) {
w->showMediaPreview(
_previewShown = w->showMediaPreview(
Data::FileOriginSavedGifs(),
previewDocument);
_previewShown = true;
} else if (const auto previewPhoto = layout->getPreviewPhoto()) {
w->showMediaPreview(
_previewShown = w->showMediaPreview(
Data::FileOrigin(),
previewPhoto);
_previewShown = true;
}
}
}

View File

@@ -102,6 +102,7 @@ private:
InlineResults results;
};
void clearHeavyData();
void cancelGifsSearch();
void switchToSavedGifs();
void refreshSavedGifs();
@@ -133,11 +134,19 @@ private:
QVector<Row> _rows;
void clearInlineRows(bool resultsDeleted);
std::map<DocumentData*, std::unique_ptr<LayoutItem>> _gifLayouts;
LayoutItem *layoutPrepareSavedGif(DocumentData *doc, int32 position);
std::map<
not_null<DocumentData*>,
std::unique_ptr<LayoutItem>> _gifLayouts;
LayoutItem *layoutPrepareSavedGif(
not_null<DocumentData*> document,
int32 position);
std::map<InlineResult*, std::unique_ptr<LayoutItem>> _inlineLayouts;
LayoutItem *layoutPrepareInlineResult(InlineResult *result, int32 position);
std::map<
not_null<InlineResult*>,
std::unique_ptr<LayoutItem>> _inlineLayouts;
LayoutItem *layoutPrepareInlineResult(
not_null<InlineResult*> result,
int32 position);
bool inlineRowsAddItem(DocumentData *savedGif, InlineResult *result, Row &row, int32 &sumWidth);
bool inlineRowFinalize(Row &row, int32 &sumWidth, bool force = false);

View File

@@ -5,11 +5,13 @@ the official desktop application for the Telegram messaging service.
For license and copyright information please follow this link:
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#include "stickers.h"
#include "chat_helpers/stickers.h"
#include "data/data_document.h"
#include "data/data_document_media.h"
#include "data/data_session.h"
#include "data/data_file_origin.h"
#include "chat_helpers/stickers_set.h"
#include "boxes/stickers_box.h"
#include "boxes/confirm_box.h"
#include "lang/lang_keys.h"
@@ -20,6 +22,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "mainwindow.h"
#include "ui/toast/toast.h"
#include "ui/emoji_config.h"
#include "ui/image/image_location_factory.h"
#include "base/unixtime.h"
#include "lottie/lottie_single_player.h"
#include "lottie/lottie_multi_player.h"
@@ -40,7 +43,7 @@ void ApplyArchivedResult(const MTPDmessages_stickerSetInstallResultArchive &d) {
Order archived;
archived.reserve(v.size());
QMap<uint64, uint64> setsToRequest;
for_const (auto &stickerSet, v) {
for (const auto &stickerSet : v) {
const MTPDstickerSet *setData = nullptr;
switch (stickerSet.type()) {
case mtpc_stickerSetCovered: {
@@ -90,21 +93,22 @@ void ApplyArchivedResult(const MTPDmessages_stickerSetInstallResultArchive &d) {
// For testing: Just apply random subset or your sticker sets as archived.
bool ApplyArchivedResultFake() {
auto sets = QVector<MTPStickerSetCovered>();
for (auto &set : Auth().data().stickerSetsRef()) {
if ((set.flags & MTPDstickerSet::Flag::f_installed_date)
&& !(set.flags & MTPDstickerSet_ClientFlag::f_special)) {
for (const auto &[id, set] : Auth().data().stickerSets()) {
const auto raw = set.get();
if ((raw->flags & MTPDstickerSet::Flag::f_installed_date)
&& !(raw->flags & MTPDstickerSet_ClientFlag::f_special)) {
if (rand_value<uint32>() % 128 < 64) {
const auto data = MTP_stickerSet(
MTP_flags(set.flags | MTPDstickerSet::Flag::f_archived),
MTP_int(set.installDate),
MTP_long(set.id),
MTP_long(set.access),
MTP_string(set.title),
MTP_string(set.shortName),
MTP_flags(raw->flags | MTPDstickerSet::Flag::f_archived),
MTP_int(raw->installDate),
MTP_long(raw->id),
MTP_long(raw->access),
MTP_string(raw->title),
MTP_string(raw->shortName),
MTP_photoSizeEmpty(MTP_string()),
MTP_int(0),
MTP_int(set.count),
MTP_int(set.hash));
MTP_int(raw->count),
MTP_int(raw->hash));
sets.push_back(MTP_stickerSetCovered(
data,
MTP_documentEmpty(MTP_long(0))));
@@ -127,11 +131,12 @@ void InstallLocally(uint64 setId) {
return;
}
auto flags = it->flags;
it->flags &= ~(MTPDstickerSet::Flag::f_archived | MTPDstickerSet_ClientFlag::f_unread);
it->flags |= MTPDstickerSet::Flag::f_installed_date;
it->installDate = base::unixtime::now();
auto changedFlags = flags ^ it->flags;
const auto set = it->second.get();
auto flags = set->flags;
set->flags &= ~(MTPDstickerSet::Flag::f_archived | MTPDstickerSet_ClientFlag::f_unread);
set->flags |= MTPDstickerSet::Flag::f_installed_date;
set->installDate = base::unixtime::now();
auto changedFlags = flags ^ set->flags;
auto &order = Auth().data().stickerSetsOrderRef();
int insertAtIndex = 0, currentIndex = order.indexOf(setId);
@@ -142,14 +147,15 @@ void InstallLocally(uint64 setId) {
order.insert(insertAtIndex, setId);
}
auto custom = sets.find(CustomSetId);
if (custom != sets.cend()) {
for_const (auto sticker, it->stickers) {
auto customIt = sets.find(CustomSetId);
if (customIt != sets.cend()) {
const auto custom = customIt->second.get();
for (const auto sticker : std::as_const(set->stickers)) {
int removeIndex = custom->stickers.indexOf(sticker);
if (removeIndex >= 0) custom->stickers.removeAt(removeIndex);
}
if (custom->stickers.isEmpty()) {
sets.erase(custom);
sets.erase(customIt);
}
}
Local::writeInstalledStickers();
@@ -167,14 +173,15 @@ void InstallLocally(uint64 setId) {
}
void UndoInstallLocally(uint64 setId) {
auto &sets = Auth().data().stickerSetsRef();
auto it = sets.find(setId);
const auto &sets = Auth().data().stickerSets();
const auto it = sets.find(setId);
if (it == sets.end()) {
return;
}
it->flags &= ~MTPDstickerSet::Flag::f_installed_date;
it->installDate = TimeId(0);
const auto set = it->second.get();
set->flags &= ~MTPDstickerSet::Flag::f_installed_date;
set->installDate = TimeId(0);
auto &order = Auth().data().stickerSetsOrderRef();
int currentIndex = order.indexOf(setId);
@@ -191,11 +198,12 @@ void UndoInstallLocally(uint64 setId) {
}
bool IsFaved(not_null<const DocumentData*> document) {
const auto it = Auth().data().stickerSets().constFind(FavedSetId);
if (it == Auth().data().stickerSets().cend()) {
const auto &sets = Auth().data().stickerSets();
const auto it = sets.find(FavedSetId);
if (it == sets.cend()) {
return false;
}
for (const auto sticker : it->stickers) {
for (const auto sticker : it->second->stickers) {
if (sticker == document) {
return true;
}
@@ -253,11 +261,14 @@ void MoveFavedToFront(Set &set, int index) {
void RequestSetToPushFaved(not_null<DocumentData*> document);
void SetIsFaved(not_null<DocumentData*> document, std::optional<std::vector<not_null<EmojiPtr>>> emojiList = std::nullopt) {
auto &sets = Auth().data().stickerSetsRef();
void SetIsFaved(
not_null<DocumentData*> document,
std::optional<std::vector<not_null<EmojiPtr>>> emojiList = std::nullopt) {
auto &sets = document->owner().stickerSetsRef();
auto it = sets.find(FavedSetId);
if (it == sets.end()) {
it = sets.insert(FavedSetId, Set(
it = sets.emplace(FavedSetId, std::make_unique<Set>(
&document->owner(),
FavedSetId,
uint64(0),
Lang::Hard::FavedSetTitle(),
@@ -265,19 +276,19 @@ void SetIsFaved(not_null<DocumentData*> document, std::optional<std::vector<not_
0, // count
0, // hash
MTPDstickerSet_ClientFlag::f_special | 0,
TimeId(0),
ImagePtr()));
TimeId(0))).first;
}
auto index = it->stickers.indexOf(document);
const auto set = it->second.get();
auto index = set->stickers.indexOf(document);
if (index == 0) {
return;
}
if (index > 0) {
MoveFavedToFront(*it, index);
MoveFavedToFront(*set, index);
} else if (emojiList) {
PushFavedToFront(*it, document, *emojiList);
PushFavedToFront(*set, document, *emojiList);
} else if (auto list = GetEmojiListFromSet(document)) {
PushFavedToFront(*it, document, *list);
PushFavedToFront(*set, document, *list);
} else {
RequestSetToPushFaved(document);
return;
@@ -303,9 +314,9 @@ void RequestSetToPushFaved(not_null<DocumentData*> document) {
auto list = std::vector<not_null<EmojiPtr>>();
auto &d = result.c_messages_stickerSet();
list.reserve(d.vpacks().v.size());
for_const (auto &mtpPack, d.vpacks().v) {
for (const auto &mtpPack : d.vpacks().v) {
auto &pack = mtpPack.c_stickerPack();
for_const (auto &documentId, pack.vdocuments().v) {
for (const auto &documentId : pack.vdocuments().v) {
if (documentId.v == document->id) {
if (const auto emoji = Ui::Emoji::Find(qs(mtpPack.c_stickerPack().vemoticon()))) {
list.emplace_back(emoji);
@@ -331,23 +342,24 @@ void SetIsNotFaved(not_null<DocumentData*> document) {
if (it == sets.end()) {
return;
}
auto index = it->stickers.indexOf(document);
const auto set = it->second.get();
auto index = set->stickers.indexOf(document);
if (index < 0) {
return;
}
it->stickers.removeAt(index);
for (auto i = it->emoji.begin(); i != it->emoji.end();) {
set->stickers.removeAt(index);
for (auto i = set->emoji.begin(); i != set->emoji.end();) {
auto index = i->indexOf(document);
if (index >= 0) {
i->removeAt(index);
if (i->empty()) {
i = it->emoji.erase(i);
i = set->emoji.erase(i);
continue;
}
}
++i;
}
if (it->stickers.empty()) {
if (set->stickers.empty()) {
sets.erase(it);
}
Local::writeFavedStickers();
@@ -368,14 +380,14 @@ void SetsReceived(const QVector<MTPStickerSet> &data, int32 hash) {
auto &sets = Auth().data().stickerSetsRef();
QMap<uint64, uint64> setsToRequest;
for (auto &set : sets) {
if (!(set.flags & MTPDstickerSet::Flag::f_archived)) {
for (auto &[id, set] : sets) {
if (!(set->flags & MTPDstickerSet::Flag::f_archived)) {
// Mark for removing.
set.flags &= ~MTPDstickerSet::Flag::f_installed_date;
set.installDate = 0;
set->flags &= ~MTPDstickerSet::Flag::f_installed_date;
set->installDate = 0;
}
}
for_const (auto &setData, data) {
for (const auto &setData : data) {
if (setData.type() == mtpc_stickerSet) {
auto set = FeedSet(setData.c_stickerSet());
if (!(set->flags & MTPDstickerSet::Flag::f_archived) || (set->flags & MTPDstickerSet::Flag::f_official)) {
@@ -388,14 +400,15 @@ void SetsReceived(const QVector<MTPStickerSet> &data, int32 hash) {
}
auto writeRecent = false;
auto &recent = GetRecentPack();
for (auto it = sets.begin(), e = sets.end(); it != e;) {
bool installed = (it->flags & MTPDstickerSet::Flag::f_installed_date);
bool featured = (it->flags & MTPDstickerSet_ClientFlag::f_featured);
bool special = (it->flags & MTPDstickerSet_ClientFlag::f_special);
bool archived = (it->flags & MTPDstickerSet::Flag::f_archived);
for (auto it = sets.begin(); it != sets.end();) {
const auto set = it->second.get();
bool installed = (set->flags & MTPDstickerSet::Flag::f_installed_date);
bool featured = (set->flags & MTPDstickerSet_ClientFlag::f_featured);
bool special = (set->flags & MTPDstickerSet_ClientFlag::f_special);
bool archived = (set->flags & MTPDstickerSet::Flag::f_archived);
if (!installed) { // remove not mine sets from recent stickers
for (auto i = recent.begin(); i != recent.cend();) {
if (it->stickers.indexOf(i->first) >= 0) {
if (set->stickers.indexOf(i->first) >= 0) {
i = recent.erase(i);
writeRecent = true;
} else {
@@ -436,7 +449,7 @@ void SetPackAndEmoji(
set.stickers = std::move(pack);
set.dates = std::move(dates);
set.emoji.clear();
for_const (auto &mtpPack, packs) {
for (const auto &mtpPack : packs) {
Assert(mtpPack.type() == mtpc_stickerPack);
auto &pack = mtpPack.c_stickerPack();
if (auto emoji = Ui::Emoji::Find(qs(pack.vemoticon()))) {
@@ -472,7 +485,8 @@ void SpecialSetReceived(
}
} else {
if (it == sets.cend()) {
it = sets.insert(setId, Set(
it = sets.emplace(setId, std::make_unique<Set>(
&Auth().data(),
setId,
uint64(0),
setTitle,
@@ -480,19 +494,19 @@ void SpecialSetReceived(
0, // count
0, // hash
MTPDstickerSet_ClientFlag::f_special | 0,
TimeId(0),
ImagePtr()));
TimeId(0))).first;
} else {
it->title = setTitle;
it->second->title = setTitle;
}
it->hash = hash;
const auto set = it->second.get();
set->hash = hash;
auto dates = std::vector<TimeId>();
auto dateIndex = 0;
auto datesAvailable = (items.size() == usageDates.size())
&& (setId == CloudRecentSetId);
auto custom = sets.find(CustomSetId);
auto customIt = sets.find(CustomSetId);
auto pack = Pack();
pack.reserve(items.size());
for (const auto &item : items) {
@@ -506,22 +520,24 @@ void SpecialSetReceived(
if (datesAvailable) {
dates.push_back(TimeId(usageDates[dateIndex - 1].v));
}
if (custom != sets.cend()) {
if (customIt != sets.cend()) {
const auto custom = customIt->second.get();
auto index = custom->stickers.indexOf(document);
if (index >= 0) {
custom->stickers.removeAt(index);
}
}
}
if (custom != sets.cend() && custom->stickers.isEmpty()) {
sets.erase(custom);
custom = sets.end();
if (customIt != sets.cend()
&& customIt->second->stickers.isEmpty()) {
sets.erase(customIt);
customIt = sets.end();
}
auto writeRecent = false;
auto &recent = GetRecentPack();
for (auto i = recent.begin(); i != recent.cend();) {
if (it->stickers.indexOf(i->first) >= 0 && pack.indexOf(i->first) < 0) {
if (set->stickers.indexOf(i->first) >= 0 && pack.indexOf(i->first) < 0) {
i = recent.erase(i);
writeRecent = true;
} else {
@@ -532,7 +548,7 @@ void SpecialSetReceived(
if (pack.isEmpty()) {
sets.erase(it);
} else {
SetPackAndEmoji(*it, std::move(pack), std::move(dates), packs);
SetPackAndEmoji(*set, std::move(pack), std::move(dates), packs);
}
if (writeRecent) {
@@ -560,7 +576,7 @@ void SpecialSetReceived(
}
void FeaturedSetsReceived(
const QVector<MTPStickerSetCovered> &data,
const QVector<MTPStickerSetCovered> &list,
const QVector<MTPlong> &unread,
int32 hash) {
auto &&unreadIds = ranges::view::all(
@@ -578,87 +594,77 @@ void FeaturedSetsReceived(
auto &sets = Auth().data().stickerSetsRef();
auto setsToRequest = base::flat_map<uint64, uint64>();
for (auto &set : sets) {
for (auto &[id, set] : sets) {
// Mark for removing.
set.flags &= ~MTPDstickerSet_ClientFlag::f_featured;
set->flags &= ~MTPDstickerSet_ClientFlag::f_featured;
}
for (int i = 0, l = data.size(); i != l; ++i) {
auto &setData = data[i];
const MTPDstickerSet *set = nullptr;
switch (setData.type()) {
case mtpc_stickerSetCovered: {
auto &d = setData.c_stickerSetCovered();
if (d.vset().type() == mtpc_stickerSet) {
set = &d.vset().c_stickerSet();
for (const auto &entry : list) {
const auto data = entry.match([&](const auto &data) {
return data.vset().match([&](const MTPDstickerSet &data) {
return &data;
});
});
auto it = sets.find(data->vid().v);
const auto title = GetSetTitle(*data);
const auto installDate = data->vinstalled_date().value_or_empty();
const auto thumb = data->vthumb();
const auto thumbnail = thumb
? Images::FromPhotoSize(&Auth(), *data, *thumb)
: ImageWithLocation();
if (it == sets.cend()) {
auto setClientFlags = MTPDstickerSet_ClientFlag::f_featured
| MTPDstickerSet_ClientFlag::f_not_loaded;
if (unreadMap.contains(data->vid().v)) {
setClientFlags |= MTPDstickerSet_ClientFlag::f_unread;
}
} break;
case mtpc_stickerSetMultiCovered: {
auto &d = setData.c_stickerSetMultiCovered();
if (d.vset().type() == mtpc_stickerSet) {
set = &d.vset().c_stickerSet();
}
} break;
}
if (set) {
auto it = sets.find(set->vid().v);
const auto title = GetSetTitle(*set);
const auto installDate = set->vinstalled_date().value_or_empty();
const auto thumb = set->vthumb();
const auto thumbnail = thumb
? Images::Create(*set, *thumb)
: ImagePtr();
if (it == sets.cend()) {
auto setClientFlags = MTPDstickerSet_ClientFlag::f_featured
| MTPDstickerSet_ClientFlag::f_not_loaded;
if (unreadMap.contains(set->vid().v)) {
setClientFlags |= MTPDstickerSet_ClientFlag::f_unread;
}
it = sets.insert(set->vid().v, Set(
set->vid().v,
set->vaccess_hash().v,
title,
qs(set->vshort_name()),
set->vcount().v,
set->vhash().v,
set->vflags().v | setClientFlags,
installDate,
thumbnail));
it = sets.emplace(data->vid().v, std::make_unique<Set>(
&Auth().data(),
data->vid().v,
data->vaccess_hash().v,
title,
qs(data->vshort_name()),
data->vcount().v,
data->vhash().v,
data->vflags().v | setClientFlags,
installDate)).first;
it->second->setThumbnail(thumbnail);
} else {
const auto set = it->second.get();
set->access = data->vaccess_hash().v;
set->title = title;
set->shortName = qs(data->vshort_name());
auto clientFlags = set->flags & (MTPDstickerSet_ClientFlag::f_featured | MTPDstickerSet_ClientFlag::f_unread | MTPDstickerSet_ClientFlag::f_not_loaded | MTPDstickerSet_ClientFlag::f_special);
set->flags = data->vflags().v | clientFlags;
set->flags |= MTPDstickerSet_ClientFlag::f_featured;
set->installDate = installDate;
set->setThumbnail(thumbnail);
if (unreadMap.contains(set->id)) {
set->flags |= MTPDstickerSet_ClientFlag::f_unread;
} else {
it->access = set->vaccess_hash().v;
it->title = title;
it->shortName = qs(set->vshort_name());
auto clientFlags = it->flags & (MTPDstickerSet_ClientFlag::f_featured | MTPDstickerSet_ClientFlag::f_unread | MTPDstickerSet_ClientFlag::f_not_loaded | MTPDstickerSet_ClientFlag::f_special);
it->flags = set->vflags().v | clientFlags;
it->flags |= MTPDstickerSet_ClientFlag::f_featured;
it->installDate = installDate;
it->thumbnail = thumbnail;
if (unreadMap.contains(it->id)) {
it->flags |= MTPDstickerSet_ClientFlag::f_unread;
} else {
it->flags &= ~MTPDstickerSet_ClientFlag::f_unread;
}
if (it->count != set->vcount().v || it->hash != set->vhash().v || it->emoji.isEmpty()) {
it->count = set->vcount().v;
it->hash = set->vhash().v;
it->flags |= MTPDstickerSet_ClientFlag::f_not_loaded; // need to request this set
}
set->flags &= ~MTPDstickerSet_ClientFlag::f_unread;
}
setsOrder.push_back(set->vid().v);
if (it->stickers.isEmpty() || (it->flags & MTPDstickerSet_ClientFlag::f_not_loaded)) {
setsToRequest.emplace(set->vid().v, set->vaccess_hash().v);
if (set->count != data->vcount().v || set->hash != data->vhash().v || set->emoji.isEmpty()) {
set->count = data->vcount().v;
set->hash = data->vhash().v;
set->flags |= MTPDstickerSet_ClientFlag::f_not_loaded; // need to request this set
}
}
setsOrder.push_back(data->vid().v);
if (it->second->stickers.isEmpty()
|| (it->second->flags & MTPDstickerSet_ClientFlag::f_not_loaded)) {
setsToRequest.emplace(data->vid().v, data->vaccess_hash().v);
}
}
auto unreadCount = 0;
for (auto it = sets.begin(), e = sets.end(); it != e;) {
bool installed = (it->flags & MTPDstickerSet::Flag::f_installed_date);
bool featured = (it->flags & MTPDstickerSet_ClientFlag::f_featured);
bool special = (it->flags & MTPDstickerSet_ClientFlag::f_special);
bool archived = (it->flags & MTPDstickerSet::Flag::f_archived);
for (auto it = sets.begin(); it != sets.end();) {
const auto set = it->second.get();
bool installed = (set->flags & MTPDstickerSet::Flag::f_installed_date);
bool featured = (set->flags & MTPDstickerSet_ClientFlag::f_featured);
bool special = (set->flags & MTPDstickerSet_ClientFlag::f_special);
bool archived = (set->flags & MTPDstickerSet::Flag::f_archived);
if (installed || featured || special || archived) {
if (featured && (it->flags & MTPDstickerSet_ClientFlag::f_unread)) {
if (featured && (set->flags & MTPDstickerSet_ClientFlag::f_unread)) {
++unreadCount;
}
++it;
@@ -772,7 +778,7 @@ std::vector<not_null<DocumentData*>> GetListByEmoji(
const auto setId = sticker->set.c_inputStickerSetID().vid().v;
const auto setIt = sets.find(setId);
if (setIt != sets.end()) {
return InstallDateAdjusted(setIt->installDate, document);
return InstallDateAdjusted(setIt->second->installDate, document);
}
}
return TimeId(0);
@@ -780,20 +786,21 @@ std::vector<not_null<DocumentData*>> GetListByEmoji(
auto recentIt = sets.find(Stickers::CloudRecentSetId);
if (recentIt != sets.cend()) {
auto i = recentIt->emoji.constFind(original);
if (i != recentIt->emoji.cend()) {
const auto recent = recentIt->second.get();
auto i = recent->emoji.constFind(original);
if (i != recent->emoji.cend()) {
result.reserve(i->size());
for (const auto document : *i) {
const auto usageDate = [&] {
if (recentIt->dates.empty()) {
if (recent->dates.empty()) {
return TimeId(0);
}
const auto index = recentIt->stickers.indexOf(document);
const auto index = recent->stickers.indexOf(document);
if (index < 0) {
return TimeId(0);
}
Assert(index < recentIt->dates.size());
return recentIt->dates[index];
Assert(index < recent->dates.size());
return recent->dates[index];
}();
const auto date = usageDate
? usageDate
@@ -807,22 +814,23 @@ std::vector<not_null<DocumentData*>> GetListByEmoji(
const auto addList = [&](const Order &order, MTPDstickerSet::Flag skip) {
for (const auto setId : order) {
auto it = sets.find(setId);
if (it == sets.cend() || (it->flags & skip)) {
if (it == sets.cend() || (it->second->flags & skip)) {
continue;
}
if (it->emoji.isEmpty()) {
setsToRequest.emplace(it->id, it->access);
it->flags |= MTPDstickerSet_ClientFlag::f_not_loaded;
const auto set = it->second.get();
if (set->emoji.isEmpty()) {
setsToRequest.emplace(set->id, set->access);
set->flags |= MTPDstickerSet_ClientFlag::f_not_loaded;
continue;
}
auto i = it->emoji.constFind(original);
if (i == it->emoji.cend()) {
auto i = set->emoji.constFind(original);
if (i == set->emoji.cend()) {
continue;
}
const auto my = (it->flags & MTPDstickerSet::Flag::f_installed_date);
const auto my = (set->flags & MTPDstickerSet::Flag::f_installed_date);
result.reserve(result.size() + i->size());
for (const auto document : *i) {
const auto installDate = my ? it->installDate : TimeId(0);
const auto installDate = my ? set->installDate : TimeId(0);
const auto date = (installDate > 1)
? InstallDateAdjusted(installDate, document)
: my
@@ -877,13 +885,14 @@ std::optional<std::vector<not_null<EmojiPtr>>> GetEmojiListFromSet(
if (inputSet.type() != mtpc_inputStickerSetID) {
return std::nullopt;
}
auto &sets = Auth().data().stickerSets();
auto it = sets.constFind(inputSet.c_inputStickerSetID().vid().v);
const auto &sets = Auth().data().stickerSets();
auto it = sets.find(inputSet.c_inputStickerSetID().vid().v);
if (it == sets.cend()) {
return std::nullopt;
}
const auto set = it->second.get();
auto result = std::vector<not_null<EmojiPtr>>();
for (auto i = it->emoji.cbegin(), e = it->emoji.cend(); i != e; ++i) {
for (auto i = set->emoji.cbegin(), e = set->emoji.cend(); i != e; ++i) {
if (i->contains(document)) {
result.emplace_back(i.key());
}
@@ -896,61 +905,66 @@ std::optional<std::vector<not_null<EmojiPtr>>> GetEmojiListFromSet(
return std::nullopt;
}
Set *FeedSet(const MTPDstickerSet &set) {
Set *FeedSet(const MTPDstickerSet &data) {
auto &sets = Auth().data().stickerSetsRef();
auto it = sets.find(set.vid().v);
auto title = GetSetTitle(set);
auto it = sets.find(data.vid().v);
auto title = GetSetTitle(data);
auto flags = MTPDstickerSet::Flags(0);
const auto thumb = set.vthumb();
const auto thumbnail = thumb ? Images::Create(set, *thumb) : ImagePtr();
const auto thumb = data.vthumb();
const auto thumbnail = thumb
? Images::FromPhotoSize(&Auth(), data, *thumb)
: ImageWithLocation();
if (it == sets.cend()) {
it = sets.insert(set.vid().v, Stickers::Set(
set.vid().v,
set.vaccess_hash().v,
it = sets.emplace(data.vid().v, std::make_unique<Set>(
&Auth().data(),
data.vid().v,
data.vaccess_hash().v,
title,
qs(set.vshort_name()),
set.vcount().v,
set.vhash().v,
set.vflags().v | MTPDstickerSet_ClientFlag::f_not_loaded,
set.vinstalled_date().value_or_empty(),
thumbnail));
qs(data.vshort_name()),
data.vcount().v,
data.vhash().v,
data.vflags().v | MTPDstickerSet_ClientFlag::f_not_loaded,
data.vinstalled_date().value_or_empty())).first;
it->second->setThumbnail(thumbnail);
} else {
it->access = set.vaccess_hash().v;
it->title = title;
it->shortName = qs(set.vshort_name());
flags = it->flags;
auto clientFlags = it->flags
const auto set = it->second.get();
set->access = data.vaccess_hash().v;
set->title = title;
set->shortName = qs(data.vshort_name());
flags = set->flags;
auto clientFlags = set->flags
& (MTPDstickerSet_ClientFlag::f_featured
| MTPDstickerSet_ClientFlag::f_unread
| MTPDstickerSet_ClientFlag::f_not_loaded
| MTPDstickerSet_ClientFlag::f_special);
it->flags = set.vflags().v | clientFlags;
const auto installDate = set.vinstalled_date();
it->installDate = installDate
set->flags = data.vflags().v | clientFlags;
const auto installDate = data.vinstalled_date();
set->installDate = installDate
? (installDate->v ? installDate->v : base::unixtime::now())
: TimeId(0);
it->thumbnail = thumbnail;
if (it->count != set.vcount().v
|| it->hash != set.vhash().v
|| it->emoji.isEmpty()) {
// Need to request this set.
it->count = set.vcount().v;
it->hash = set.vhash().v;
it->flags |= MTPDstickerSet_ClientFlag::f_not_loaded;
it->second->setThumbnail(thumbnail);
if (set->count != data.vcount().v
|| set->hash != data.vhash().v
|| set->emoji.isEmpty()) {
// Need to request this data.
set->count = data.vcount().v;
set->hash = data.vhash().v;
set->flags |= MTPDstickerSet_ClientFlag::f_not_loaded;
}
}
auto changedFlags = (flags ^ it->flags);
const auto set = it->second.get();
auto changedFlags = (flags ^ set->flags);
if (changedFlags & MTPDstickerSet::Flag::f_archived) {
auto index = Auth().data().archivedStickerSetsOrder().indexOf(it->id);
if (it->flags & MTPDstickerSet::Flag::f_archived) {
auto index = Auth().data().archivedStickerSetsOrder().indexOf(set->id);
if (set->flags & MTPDstickerSet::Flag::f_archived) {
if (index < 0) {
Auth().data().archivedStickerSetsOrderRef().push_front(it->id);
Auth().data().archivedStickerSetsOrderRef().push_front(set->id);
}
} else if (index >= 0) {
Auth().data().archivedStickerSetsOrderRef().removeAt(index);
}
}
return &it.value();
return it->second.get();
}
Set *FeedSetFull(const MTPmessages_StickerSet &data) {
@@ -961,16 +975,19 @@ Set *FeedSetFull(const MTPmessages_StickerSet &data) {
const auto &s = d.vset().c_stickerSet();
auto &sets = Auth().data().stickerSetsRef();
auto it = sets.find(s.vid().v);
const auto wasArchived = (it->flags & MTPDstickerSet::Flag::f_archived);
const auto wasArchived = [&] {
auto it = sets.find(s.vid().v);
return (it != sets.end())
&& (it->second->flags & MTPDstickerSet::Flag::f_archived);
}();
auto set = FeedSet(s);
set->flags &= ~MTPDstickerSet_ClientFlag::f_not_loaded;
auto &d_docs = d.vdocuments().v;
auto custom = sets.find(Stickers::CustomSetId);
auto inputSet = MTP_inputStickerSetID(
const auto &d_docs = d.vdocuments().v;
auto customIt = sets.find(Stickers::CustomSetId);
const auto inputSet = MTP_inputStickerSetID(
MTP_long(set->id),
MTP_long(set->access));
@@ -984,16 +1001,17 @@ Set *FeedSetFull(const MTPmessages_StickerSet &data) {
if (document->sticker()->set.type() != mtpc_inputStickerSetID) {
document->sticker()->set = inputSet;
}
if (custom != sets.cend()) {
if (customIt != sets.cend()) {
const auto custom = customIt->second.get();
const auto index = custom->stickers.indexOf(document);
if (index >= 0) {
custom->stickers.removeAt(index);
}
}
}
if (custom != sets.cend() && custom->stickers.isEmpty()) {
sets.erase(custom);
custom = sets.end();
if (customIt != sets.cend() && customIt->second->stickers.isEmpty()) {
sets.erase(customIt);
customIt = sets.end();
}
auto writeRecent = false;
@@ -1042,7 +1060,7 @@ Set *FeedSetFull(const MTPmessages_StickerSet &data) {
}
if (set) {
const auto isArchived = (set->flags & MTPDstickerSet::Flag::f_archived);
const auto isArchived = !!(set->flags & MTPDstickerSet::Flag::f_archived);
if (set->flags & MTPDstickerSet::Flag::f_installed_date) {
if (!isArchived) {
Local::writeInstalledStickers();
@@ -1146,10 +1164,11 @@ auto LottieCachedFromContent(
template <typename Method>
auto LottieFromDocument(
Method &&method,
not_null<DocumentData*> document,
not_null<Data::DocumentMedia*> media,
uint8 keyShift,
QSize box) {
const auto data = document->data();
const auto document = media->owner();
const auto data = media->bytes();
const auto filepath = document->filepath();
if (box.width() * box.height() > kDontCacheLottieAfterArea) {
// Don't use frame caching for large stickers.
@@ -1160,7 +1179,7 @@ auto LottieFromDocument(
if (const auto baseKey = document->bigFileBaseCacheKey()) {
return LottieCachedFromContent(
std::forward<Method>(method),
*baseKey,
baseKey,
keyShift,
&document->session(),
Lottie::ReadContent(data, filepath),
@@ -1172,13 +1191,13 @@ auto LottieFromDocument(
}
std::unique_ptr<Lottie::SinglePlayer> LottiePlayerFromDocument(
not_null<DocumentData*> document,
not_null<Data::DocumentMedia*> media,
LottieSize sizeTag,
QSize box,
Lottie::Quality quality,
std::shared_ptr<Lottie::FrameRenderer> renderer) {
return LottiePlayerFromDocument(
document,
media,
nullptr,
sizeTag,
box,
@@ -1187,7 +1206,7 @@ std::unique_ptr<Lottie::SinglePlayer> LottiePlayerFromDocument(
}
std::unique_ptr<Lottie::SinglePlayer> LottiePlayerFromDocument(
not_null<DocumentData*> document,
not_null<Data::DocumentMedia*> media,
const Lottie::ColorReplacements *replacements,
LottieSize sizeTag,
QSize box,
@@ -1202,60 +1221,59 @@ std::unique_ptr<Lottie::SinglePlayer> LottiePlayerFromDocument(
};
const auto tag = replacements ? replacements->tag : uint8(0);
const auto keyShift = ((tag << 4) & 0xF0) | (uint8(sizeTag) & 0x0F);
return LottieFromDocument(method, document, uint8(keyShift), box);
return LottieFromDocument(method, media, uint8(keyShift), box);
}
not_null<Lottie::Animation*> LottieAnimationFromDocument(
not_null<Lottie::MultiPlayer*> player,
not_null<DocumentData*> document,
not_null<Data::DocumentMedia*> media,
LottieSize sizeTag,
QSize box) {
const auto method = [&](auto &&...args) {
return player->append(std::forward<decltype(args)>(args)...);
};
return LottieFromDocument(method, document, uint8(sizeTag), box);
return LottieFromDocument(method, media, uint8(sizeTag), box);
}
bool HasLottieThumbnail(
ImagePtr thumbnail,
not_null<DocumentData*> sticker) {
if (thumbnail) {
if (!thumbnail->loaded()) {
return false;
}
const auto &location = thumbnail->location();
const auto &bytes = thumbnail->bytesForCache();
return location.valid()
&& location.type() == StorageFileLocation::Type::StickerSetThumb
&& !bytes.isEmpty();
} else if (const auto info = sticker->sticker()) {
SetThumbnailView *thumb,
Data::DocumentMedia *media) {
if (thumb) {
return !thumb->content().isEmpty();
} else if (!media) {
return false;
}
const auto document = media->owner();
if (const auto info = document->sticker()) {
if (!info->animated) {
return false;
}
sticker->automaticLoad(sticker->stickerSetOrigin(), nullptr);
if (!sticker->loaded()) {
media->automaticLoad(document->stickerSetOrigin(), nullptr);
if (!media->loaded()) {
return false;
}
return sticker->bigFileBaseCacheKey().has_value();
return document->bigFileBaseCacheKey().valid();
}
return false;
}
std::unique_ptr<Lottie::SinglePlayer> LottieThumbnail(
ImagePtr thumbnail,
not_null<DocumentData*> sticker,
SetThumbnailView *thumb,
Data::DocumentMedia *media,
LottieSize sizeTag,
QSize box,
std::shared_ptr<Lottie::FrameRenderer> renderer) {
const auto baseKey = thumbnail
? thumbnail->location().file().bigFileBaseCacheKey()
: sticker->bigFileBaseCacheKey();
const auto baseKey = thumb
? thumb->owner()->thumbnailLocation().file().bigFileBaseCacheKey()
: media
? media->owner()->bigFileBaseCacheKey()
: Storage::Cache::Key();
if (!baseKey) {
return nullptr;
}
const auto content = (thumbnail
? thumbnail->bytesForCache()
: Lottie::ReadContent(sticker->data(), sticker->filepath()));
const auto content = thumb
? thumb->content()
: Lottie::ReadContent(media->bytes(), media->owner()->filepath());
if (content.isEmpty()) {
return nullptr;
}
@@ -1263,51 +1281,18 @@ std::unique_ptr<Lottie::SinglePlayer> LottieThumbnail(
return std::make_unique<Lottie::SinglePlayer>(
std::forward<decltype(args)>(args)...);
};
const auto session = thumb
? &thumb->owner()->session()
: media
? &media->owner()->session()
: nullptr;
return LottieCachedFromContent(
method,
*baseKey,
baseKey,
uint8(sizeTag),
&sticker->session(),
session,
content,
box);
}
ThumbnailSource::ThumbnailSource(
const StorageImageLocation &location,
int size)
: StorageSource(location, size) {
}
QImage ThumbnailSource::takeLoaded() {
if (_bytesForAnimated.isEmpty()
&& _loader
&& _loader->finished()
&& !_loader->cancelled()) {
_bytesForAnimated = _loader->bytes();
}
auto result = StorageSource::takeLoaded();
if (!_bytesForAnimated.isEmpty()
&& !result.isNull()
&& result.size() != Image::Empty()->original().size()) {
_bytesForAnimated = QByteArray();
}
return result;
}
QByteArray ThumbnailSource::bytesForCache() {
return _bytesForAnimated;
}
std::unique_ptr<FileLoader> ThumbnailSource::createLoader(
Data::FileOrigin origin,
LoadFromCloudSetting fromCloud,
bool autoLoading) {
auto result = StorageSource::createLoader(
origin,
fromCloud,
autoLoading);
_loader = result.get();
return result;
}
} // namespace Stickers

View File

@@ -8,10 +8,13 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#pragma once
#include "mtproto/sender.h"
#include "ui/image/image_source.h"
class DocumentData;
namespace Data {
class DocumentMedia;
} // namespace Data
namespace Main {
class Session;
} // namespace Main
@@ -42,53 +45,8 @@ constexpr auto FeaturedSetId = 0xFFFFFFFFFFFFFFFBULL; // for emoji/stickers pane
constexpr auto FavedSetId = 0xFFFFFFFFFFFFFFFAULL; // for cloud-stored faved stickers
constexpr auto MegagroupSetId = 0xFFFFFFFFFFFFFFEFULL; // for setting up megagroup sticker set
using Order = QList<uint64>;
using SavedGifs = QVector<DocumentData*>;
using Pack = QVector<DocumentData*>;
using ByEmojiMap = QMap<EmojiPtr, Pack>;
struct Set {
Set(
uint64 id,
uint64 access,
const QString &title,
const QString &shortName,
int count,
int32 hash,
MTPDstickerSet::Flags flags,
TimeId installDate,
ImagePtr thumbnail)
: id(id)
, access(access)
, title(title)
, shortName(shortName)
, count(count)
, hash(hash)
, flags(flags)
, installDate(installDate)
, thumbnail(thumbnail) {
}
uint64 id = 0;
uint64 access = 0;
QString title, shortName;
int count = 0;
int32 hash = 0;
MTPDstickerSet::Flags flags;
TimeId installDate = 0;
ImagePtr thumbnail;
Pack stickers;
std::vector<TimeId> dates;
Pack covers;
ByEmojiMap emoji;
};
using Sets = QMap<uint64, Set>;
inline MTPInputStickerSet inputSetId(const Set &set) {
if (set.id && set.access) {
return MTP_inputStickerSetID(MTP_long(set.id), MTP_long(set.access));
}
return MTP_inputStickerSetShortName(MTP_string(set.shortName));
}
class Set;
class SetThumbnailView;
void ApplyArchivedResult(const MTPDmessages_stickerSetInstallResultArchive &d);
bool ApplyArchivedResultFake(); // For testing.
@@ -106,7 +64,7 @@ void SpecialSetReceived(
const QVector<MTPStickerPack> &packs = QVector<MTPStickerPack>(),
const QVector<MTPint> &usageDates = QVector<MTPint>());
void FeaturedSetsReceived(
const QVector<MTPStickerSetCovered> &data,
const QVector<MTPStickerSetCovered> &list,
const QVector<MTPlong> &unread,
int32 hash);
void GifsReceived(const QVector<MTPDocument> &items, int32 hash);
@@ -136,13 +94,13 @@ enum class LottieSize : uchar {
};
[[nodiscard]] std::unique_ptr<Lottie::SinglePlayer> LottiePlayerFromDocument(
not_null<DocumentData*> document,
not_null<Data::DocumentMedia*> media,
LottieSize sizeTag,
QSize box,
Lottie::Quality quality = Lottie::Quality(),
std::shared_ptr<Lottie::FrameRenderer> renderer = nullptr);
[[nodiscard]] std::unique_ptr<Lottie::SinglePlayer> LottiePlayerFromDocument(
not_null<DocumentData*> document,
not_null<Data::DocumentMedia*> media,
const Lottie::ColorReplacements *replacements,
LottieSize sizeTag,
QSize box,
@@ -150,40 +108,18 @@ enum class LottieSize : uchar {
std::shared_ptr<Lottie::FrameRenderer> renderer = nullptr);
[[nodiscard]] not_null<Lottie::Animation*> LottieAnimationFromDocument(
not_null<Lottie::MultiPlayer*> player,
not_null<DocumentData*> document,
not_null<Data::DocumentMedia*> media,
LottieSize sizeTag,
QSize box);
[[nodiscard]] bool HasLottieThumbnail(
ImagePtr thumbnail,
not_null<DocumentData*> sticker);
SetThumbnailView *thumb,
Data::DocumentMedia *media);
[[nodiscard]] std::unique_ptr<Lottie::SinglePlayer> LottieThumbnail(
ImagePtr thumbnail,
not_null<DocumentData*> sticker,
SetThumbnailView *thumb,
Data::DocumentMedia *media,
LottieSize sizeTag,
QSize box,
std::shared_ptr<Lottie::FrameRenderer> renderer = nullptr);
class ThumbnailSource : public Images::StorageSource {
public:
ThumbnailSource(
const StorageImageLocation &location,
int size);
QImage takeLoaded() override;
QByteArray bytesForCache() override;
protected:
std::unique_ptr<FileLoader> createLoader(
Data::FileOrigin origin,
LoadFromCloudSetting fromCloud,
bool autoLoading) override;
private:
QPointer<FileLoader> _loader;
QByteArray _bytesForAnimated;
};
} // namespace Stickers

View File

@@ -10,6 +10,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "main/main_session.h"
#include "data/data_session.h"
#include "data/data_document.h"
#include "ui/image/image_location_factory.h"
#include "storage/localimageloader.h"
#include "base/unixtime.h"
#include "apiwrap.h"
@@ -112,7 +113,7 @@ void DicePack::tryGenerateLocalZero() {
Assert(result != nullptr);
const auto document = _session->data().processDocument(
result->document,
std::move(result->thumb));
Images::FromImageInMemory(result->thumb, "PNG"));
document->setLocation(FileLocation(path));
_map.emplace(0, document);

View File

@@ -11,7 +11,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "lottie/lottie_common.h"
#include "ui/emoji_config.h"
#include "ui/text/text_isolated_emoji.h"
#include "ui/image/image_source.h"
#include "ui/image/image.h"
#include "main/main_session.h"
#include "data/data_file_origin.h"
#include "data/data_session.h"
@@ -117,194 +117,6 @@ constexpr auto kClearSourceTimeout = 10 * crl::time(1000);
return list[index - 1];
}
class ImageSource : public Images::Source {
public:
explicit ImageSource(
EmojiPtr emoji,
not_null<crl::object_on_queue<EmojiImageLoader>*> loader);
void load(Data::FileOrigin origin) override;
void loadEvenCancelled(Data::FileOrigin origin) override;
QImage takeLoaded() override;
void unload() override;
void automaticLoad(
Data::FileOrigin origin,
const HistoryItem *item) override;
void automaticLoadSettingsChanged() override;
bool loading() override;
bool displayLoading() override;
void cancel() override;
float64 progress() override;
int loadOffset() override;
const StorageImageLocation &location() override;
void refreshFileReference(const QByteArray &data) override;
std::optional<Storage::Cache::Key> cacheKey() override;
void setDelayedStorageLocation(
const StorageImageLocation &location) override;
void performDelayedLoad(Data::FileOrigin origin) override;
bool isDelayedStorageImage() const override;
void setImageBytes(const QByteArray &bytes) override;
int width() override;
int height() override;
int bytesSize() override;
void setInformation(int size, int width, int height) override;
QByteArray bytesForCache() override;
private:
// While HistoryView::Element-s are almost never destroyed
// we make loading of the image lazy.
not_null<crl::object_on_queue<EmojiImageLoader>*> _loader;
EmojiPtr _emoji = nullptr;
QImage _data;
QByteArray _format;
QByteArray _bytes;
QSize _size;
base::binary_guard _loading;
};
ImageSource::ImageSource(
EmojiPtr emoji,
not_null<crl::object_on_queue<EmojiImageLoader>*> loader)
: _loader(loader)
, _emoji(emoji)
, _size(SingleSize()) {
}
void ImageSource::load(Data::FileOrigin origin) {
if (!_data.isNull()) {
return;
}
if (_bytes.isEmpty()) {
_loader->with([
this,
emoji = _emoji,
guard = _loading.make_guard()
](EmojiImageLoader &loader) mutable {
if (!guard) {
return;
}
crl::on_main(std::move(guard), [this, image = loader.prepare(emoji)]{
_data = image;
Auth().downloaderTaskFinished().notify();
});
});
} else {
_data = App::readImage(_bytes, &_format, false);
}
}
void ImageSource::loadEvenCancelled(Data::FileOrigin origin) {
load(origin);
}
QImage ImageSource::takeLoaded() {
load({});
return _data;
}
void ImageSource::unload() {
if (_bytes.isEmpty() && !_data.isNull()) {
if (_format != "JPG") {
_format = "PNG";
}
{
QBuffer buffer(&_bytes);
_data.save(&buffer, _format);
}
Assert(!_bytes.isEmpty());
}
_data = QImage();
}
void ImageSource::automaticLoad(
Data::FileOrigin origin,
const HistoryItem *item) {
}
void ImageSource::automaticLoadSettingsChanged() {
}
bool ImageSource::loading() {
return _data.isNull() && _bytes.isEmpty();
}
bool ImageSource::displayLoading() {
return false;
}
void ImageSource::cancel() {
}
float64 ImageSource::progress() {
return 1.;
}
int ImageSource::loadOffset() {
return 0;
}
const StorageImageLocation &ImageSource::location() {
return StorageImageLocation::Invalid();
}
void ImageSource::refreshFileReference(const QByteArray &data) {
}
std::optional<Storage::Cache::Key> ImageSource::cacheKey() {
return std::nullopt;
}
void ImageSource::setDelayedStorageLocation(
const StorageImageLocation &location) {
}
void ImageSource::performDelayedLoad(Data::FileOrigin origin) {
}
bool ImageSource::isDelayedStorageImage() const {
return false;
}
void ImageSource::setImageBytes(const QByteArray &bytes) {
}
int ImageSource::width() {
return _size.width();
}
int ImageSource::height() {
return _size.height();
}
int ImageSource::bytesSize() {
return _bytes.size();
}
void ImageSource::setInformation(int size, int width, int height) {
if (width && height) {
_size = QSize(width, height);
}
}
QByteArray ImageSource::bytesForCache() {
auto result = QByteArray();
{
QBuffer buffer(&result);
if (!_data.save(&buffer, _format)) {
if (_data.save(&buffer, "PNG")) {
_format = "PNG";
}
}
}
return result;
}
} // namespace
EmojiImageLoader::EmojiImageLoader(
@@ -390,6 +202,10 @@ std::shared_ptr<UniversalImages> EmojiImageLoader::releaseImages() {
} // namespace details
QSize LargeEmojiImage::Size() {
return details::SingleSize();
}
EmojiPack::EmojiPack(not_null<Main::Session*> session)
: _session(session)
, _imageLoader(prepareSourceImages(), session->settings().largeEmoji())
@@ -473,13 +289,36 @@ auto EmojiPack::stickerForEmoji(const IsolatedEmoji &emoji) -> Sticker {
return Sticker();
}
std::shared_ptr<Image> EmojiPack::image(EmojiPtr emoji) {
const auto i = _images.emplace(emoji, std::weak_ptr<Image>()).first;
std::shared_ptr<LargeEmojiImage> EmojiPack::image(EmojiPtr emoji) {
const auto i = _images.emplace(
emoji,
std::weak_ptr<LargeEmojiImage>()).first;
if (const auto result = i->second.lock()) {
return result;
}
auto result = std::make_shared<Image>(
std::make_unique<details::ImageSource>(emoji, &_imageLoader));
auto result = std::make_shared<LargeEmojiImage>();
const auto raw = result.get();
const auto weak = base::make_weak(_session.get());
raw->load = [=] {
_imageLoader.with([=](details::EmojiImageLoader &loader) mutable {
crl::on_main(weak, [
=,
image = loader.prepare(emoji)
]() mutable {
const auto i = _images.find(emoji);
if (i != end(_images)) {
if (const auto strong = i->second.lock()) {
if (!strong->image) {
strong->load = nullptr;
strong->image.emplace(std::move(image));
_session->downloaderTaskFinished().notify();
}
}
}
});
});
raw->load = nullptr;
};
i->second = result;
return result;
}

View File

@@ -8,6 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#pragma once
#include "ui/text/text_isolated_emoji.h"
#include "ui/image/image.h"
#include "base/timer.h"
#include <crl/crl_object_on_queue.h>
@@ -39,6 +40,13 @@ class EmojiImageLoader;
using IsolatedEmoji = Ui::Text::IsolatedEmoji;
struct LargeEmojiImage {
std::optional<Image> image;
FnMut<void()> load;
[[nodiscard]] static QSize Size();
};
class EmojiPack final {
public:
struct Sticker {
@@ -60,7 +68,7 @@ public:
void remove(not_null<const HistoryItem*> item);
[[nodiscard]] Sticker stickerForEmoji(const IsolatedEmoji &emoji);
[[nodiscard]] std::shared_ptr<Image> image(EmojiPtr emoji);
[[nodiscard]] std::shared_ptr<LargeEmojiImage> image(EmojiPtr emoji);
private:
class ImageLoader;
@@ -84,7 +92,7 @@ private:
base::flat_map<
IsolatedEmoji,
base::flat_set<not_null<HistoryItem*>>> _items;
base::flat_map<EmojiPtr, std::weak_ptr<Image>> _images;
base::flat_map<EmojiPtr, std::weak_ptr<LargeEmojiImage>> _images;
mtpRequestId _requestId = 0;
crl::object_on_queue<details::EmojiImageLoader> _imageLoader;

File diff suppressed because it is too large Load Diff

View File

@@ -32,6 +32,14 @@ class MultiPlayer;
class FrameRenderer;
} // namespace Lottie
namespace Data {
class DocumentMedia;
} // namespace Data
namespace Stickers {
class Set;
} // namespace Stickers
namespace ChatHelpers {
struct StickerIcon;
@@ -63,7 +71,7 @@ public:
void refreshStickers();
void fillIcons(QList<StickerIcon> &icons);
std::vector<StickerIcon> fillIcons();
bool preventAutoHide();
uint64 currentSet(int yOffset) const;
@@ -151,51 +159,49 @@ private:
struct Sticker {
not_null<DocumentData*> document;
std::shared_ptr<Data::DocumentMedia> documentMedia;
Lottie::Animation *animated = nullptr;
QPixmap savedFrame;
void ensureMediaCreated();
};
struct Set {
Set(
uint64 id,
Stickers::Set *set,
MTPDstickerSet::Flags flags,
const QString &title,
const QString &shortName,
ImagePtr thumbnail,
bool externalLayout,
int count,
bool externalLayout,
std::vector<Sticker> &&stickers = {});
Set(Set &&other);
Set &operator=(Set &&other);
~Set();
uint64 id = 0;
Stickers::Set *set = nullptr;
MTPDstickerSet::Flags flags = MTPDstickerSet::Flags();
QString title;
QString shortName;
ImagePtr thumbnail;
std::vector<Sticker> stickers;
std::unique_ptr<Ui::RippleAnimation> ripple;
Lottie::MultiPlayer *lottiePlayer = nullptr;
bool externalLayout = false;
std::unique_ptr<Lottie::MultiPlayer> lottiePlayer;
rpl::lifetime lottieLifetime;
int count = 0;
bool externalLayout = false;
};
struct FeaturedSet {
uint64 id = 0;
MTPDstickerSet::Flags flags = MTPDstickerSet::Flags();
std::vector<Sticker> stickers;
};
struct LottieSet {
struct Item {
not_null<Lottie::Animation*> animation;
bool stale = false;
};
std::unique_ptr<Lottie::MultiPlayer> player;
base::flat_map<DocumentId, Item> items;
bool stale = false;
rpl::lifetime lifetime;
};
static std::vector<Sticker> PrepareStickers(const Stickers::Pack &pack);
static std::vector<Sticker> PrepareStickers(
const QVector<DocumentData*> &pack);
void preloadMoreOfficial();
QSize boundingBoxSize() const;
@@ -260,10 +266,11 @@ private:
void markLottieFrameShown(Set &set);
void checkVisibleLottie();
void pauseInvisibleLottieIn(const SectionInfo &info);
void destroyLottieIn(Set &set);
void refillLottieData();
void refillLottieData(Set &set);
void clearLottieData();
void takeHeavyData(std::vector<Set> &to, std::vector<Set> &from);
void takeHeavyData(Set &to, Set &from);
void takeHeavyData(Sticker &to, Sticker &from);
void clearHeavyIn(Set &set, bool clearSavedFrames = true);
void clearHeavyData();
int stickersRight() const;
bool featuredHasAddButton(int index) const;
@@ -301,7 +308,7 @@ private:
void refreshSearchRows(const std::vector<uint64> *cloudSets);
void fillLocalSearchRows(const QString &query);
void fillCloudSearchRows(const std::vector<uint64> &cloudSets);
void addSearchRow(not_null<const Stickers::Set*> set);
void addSearchRow(not_null<Stickers::Set*> set);
void showPreview();
@@ -354,8 +361,6 @@ private:
QString _searchQuery, _searchNextQuery;
mtpRequestId _searchRequestId = 0;
base::flat_map<uint64, LottieSet> _lottieData;
rpl::event_stream<not_null<DocumentData*>> _chosen;
rpl::event_stream<> _scrollUpdated;
rpl::event_stream<> _checkForHide;

View File

@@ -0,0 +1,149 @@
/*
This file is part of Telegram Desktop,
the official desktop application for the Telegram messaging service.
For license and copyright information please follow this link:
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#include "chat_helpers/stickers_set.h"
#include "main/main_session.h"
#include "data/data_session.h"
#include "data/data_file_origin.h"
#include "storage/file_download.h"
#include "ui/image/image.h"
#include "app.h"
namespace Stickers {
SetThumbnailView::SetThumbnailView(not_null<Set*> owner) : _owner(owner) {
}
not_null<Set*> SetThumbnailView::owner() const {
return _owner;
}
void SetThumbnailView::set(
not_null<Main::Session*> session,
QByteArray content) {
auto image = App::readImage(content, nullptr, false);
if (image.isNull()) {
_content = std::move(content);
} else {
_image = std::make_unique<Image>(std::move(image));
}
session->downloaderTaskFinished().notify();
}
Image *SetThumbnailView::image() const {
return _image.get();
}
QByteArray SetThumbnailView::content() const {
return _content;
}
Set::Set(
not_null<Data::Session*> owner,
uint64 id,
uint64 access,
const QString &title,
const QString &shortName,
int count,
int32 hash,
MTPDstickerSet::Flags flags,
TimeId installDate)
: id(id)
, access(access)
, title(title)
, shortName(shortName)
, count(count)
, hash(hash)
, flags(flags)
, installDate(installDate)
, _owner(owner) {
}
Data::Session &Set::owner() const {
return *_owner;
}
Main::Session &Set::session() const {
return _owner->session();
}
MTPInputStickerSet Set::mtpInput() const {
return (id && access)
? MTP_inputStickerSetID(MTP_long(id), MTP_long(access))
: MTP_inputStickerSetShortName(MTP_string(shortName));
}
void Set::setThumbnail(const ImageWithLocation &data) {
Data::UpdateCloudFile(
_thumbnail,
data,
_owner->cache(),
Data::kImageCacheTag,
[=](Data::FileOrigin origin) { loadThumbnail(); });
if (!data.bytes.isEmpty()) {
if (_thumbnail.loader) {
_thumbnail.loader->cancel();
}
if (const auto view = activeThumbnailView()) {
view->set(&_owner->session(), data.bytes);
}
}
}
bool Set::hasThumbnail() const {
return _thumbnail.location.valid();
}
bool Set::thumbnailLoading() const {
return (_thumbnail.loader != nullptr);
}
bool Set::thumbnailFailed() const {
return (_thumbnail.flags & Data::CloudFile::Flag::Failed);
}
void Set::loadThumbnail() {
auto &file = _thumbnail;
const auto origin = Data::FileOriginStickerSet(id, access);
const auto fromCloud = LoadFromCloudOrLocal;
const auto cacheTag = Data::kImageCacheTag;
const auto autoLoading = false;
Data::LoadCloudFile(file, origin, fromCloud, autoLoading, cacheTag, [=] {
if (const auto active = activeThumbnailView()) {
return !active->image() && active->content().isEmpty();
}
return true;
}, [=](QByteArray result) {
if (const auto active = activeThumbnailView()) {
active->set(&_owner->session(), std::move(result));
}
});
}
const ImageLocation &Set::thumbnailLocation() const {
return _thumbnail.location;
}
int Set::thumbnailByteSize() const {
return _thumbnail.byteSize;
}
std::shared_ptr<SetThumbnailView> Set::createThumbnailView() {
if (auto active = activeThumbnailView()) {
return active;
}
auto view = std::make_shared<SetThumbnailView>(this);
_thumbnailView = view;
return view;
}
std::shared_ptr<SetThumbnailView> Set::activeThumbnailView() {
return _thumbnailView.lock();
}
} // namespace Stickers

View File

@@ -0,0 +1,100 @@
/*
This file is part of Telegram Desktop,
the official desktop application for the Telegram messaging service.
For license and copyright information please follow this link:
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#pragma once
#include "data/data_cloud_file.h"
class DocumentData;
namespace Data {
class Session;
} // namespace Data
namespace Main {
class Session;
} // namespace Main
namespace Stickers {
using Order = QList<uint64>;
using SavedGifs = QVector<DocumentData*>;
using Pack = QVector<DocumentData*>;
using ByEmojiMap = QMap<EmojiPtr, Pack>;
class Set;
using Sets = base::flat_map<uint64, std::unique_ptr<Set>>;
class SetThumbnailView final {
public:
explicit SetThumbnailView(not_null<Set*> owner);
[[nodiscard]] not_null<Set*> owner() const;
void set(not_null<Main::Session*> session, QByteArray content);
[[nodiscard]] Image *image() const;
[[nodiscard]] QByteArray content() const;
private:
const not_null<Set*> _owner;
std::unique_ptr<Image> _image;
QByteArray _content;
};
class Set final {
public:
Set(
not_null<Data::Session*> owner,
uint64 id,
uint64 access,
const QString &title,
const QString &shortName,
int count,
int32 hash,
MTPDstickerSet::Flags flags,
TimeId installDate);
[[nodiscard]] Data::Session &owner() const;
[[nodiscard]] Main::Session &session() const;
[[nodiscard]] MTPInputStickerSet mtpInput() const;
void setThumbnail(const ImageWithLocation &data);
[[nodiscard]] bool hasThumbnail() const;
[[nodiscard]] bool thumbnailLoading() const;
[[nodiscard]] bool thumbnailFailed() const;
void loadThumbnail();
[[nodiscard]] const ImageLocation &thumbnailLocation() const;
[[nodiscard]] int thumbnailByteSize() const;
[[nodiscard]] std::shared_ptr<SetThumbnailView> createThumbnailView();
[[nodiscard]] std::shared_ptr<SetThumbnailView> activeThumbnailView();
uint64 id = 0;
uint64 access = 0;
QString title, shortName;
int count = 0;
int32 hash = 0;
MTPDstickerSet::Flags flags;
TimeId installDate = 0;
Pack stickers;
std::vector<TimeId> dates;
Pack covers;
ByEmojiMap emoji;
private:
const not_null<Data::Session*> _owner;
Data::CloudFile _thumbnail;
std::weak_ptr<SetThumbnailView> _thumbnailView;
};
} // namespace Stickers

View File

@@ -66,56 +66,6 @@ inline const char *cGUIDStr() {
return gGuidStr;
}
struct BuiltInDc {
int id;
const char *ip;
int port;
};
static const BuiltInDc _builtInDcs[] = {
{ 1, "149.154.175.50", 443 },
{ 2, "149.154.167.51", 443 },
{ 3, "149.154.175.100", 443 },
{ 4, "149.154.167.91", 443 },
{ 5, "149.154.171.5", 443 }
};
static const BuiltInDc _builtInDcsIPv6[] = {
{ 1, "2001:0b28:f23d:f001:0000:0000:0000:000a", 443 },
{ 2, "2001:067c:04e8:f002:0000:0000:0000:000a", 443 },
{ 3, "2001:0b28:f23d:f003:0000:0000:0000:000a", 443 },
{ 4, "2001:067c:04e8:f004:0000:0000:0000:000a", 443 },
{ 5, "2001:0b28:f23f:f005:0000:0000:0000:000a", 443 }
};
static const BuiltInDc _builtInTestDcs[] = {
{ 1, "149.154.175.10", 443 },
{ 2, "149.154.167.40", 443 },
{ 3, "149.154.175.117", 443 }
};
static const BuiltInDc _builtInTestDcsIPv6[] = {
{ 1, "2001:0b28:f23d:f001:0000:0000:0000:000e", 443 },
{ 2, "2001:067c:04e8:f002:0000:0000:0000:000e", 443 },
{ 3, "2001:0b28:f23d:f003:0000:0000:0000:000e", 443 }
};
inline const BuiltInDc *builtInDcs() {
return cTestMode() ? _builtInTestDcs : _builtInDcs;
}
inline int builtInDcsCount() {
return (cTestMode() ? sizeof(_builtInTestDcs) : sizeof(_builtInDcs)) / sizeof(BuiltInDc);
}
inline const BuiltInDc *builtInDcsIPv6() {
return cTestMode() ? _builtInTestDcsIPv6 : _builtInDcsIPv6;
}
inline int builtInDcsCountIPv6() {
return (cTestMode() ? sizeof(_builtInTestDcsIPv6) : sizeof(_builtInDcsIPv6)) / sizeof(BuiltInDc);
}
static const char *UpdatesPublicKey = "\
-----BEGIN RSA PUBLIC KEY-----\n\
MIGJAoGBAMA4ViQrjkPZ9xj0lrer3r23JvxOnrtE8nI69XLGSr+sRERz9YnUptnU\n\

View File

@@ -22,6 +22,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "core/ui_integration.h"
#include "chat_helpers/emoji_keywords.h"
#include "storage/localstorage.h"
#include "base/platform/base_platform_info.h"
#include "platform/platform_specific.h"
#include "mainwindow.h"
#include "dialogs/dialogs_entry.h"
@@ -174,6 +175,10 @@ void Application::run() {
psNewVersion();
}
if (cAutoStart() && !Platform::AutostartSupported()) {
cSetAutoStart(false);
}
if (cLaunchMode() == LaunchModeAutoStart && !cAutoStart()) {
psAutoStart(false, true);
App::quit();
@@ -300,7 +305,7 @@ void Application::showDocument(not_null<DocumentData*> document, HistoryItem *it
if (cUseExternalVideoPlayer()
&& document->isVideoFile()
&& document->loaded()) {
&& !document->filepath().isEmpty()) {
File::Launch(document->location(false).fname);
} else {
_mediaView->showDocument(document, item);
@@ -518,6 +523,21 @@ void Application::switchTestMode() {
App::restart();
}
void Application::switchFreeType() {
if (cUseFreeType()) {
QFile(cWorkingDir() + qsl("tdata/withfreetype")).remove();
cSetUseFreeType(false);
} else {
QFile f(cWorkingDir() + qsl("tdata/withfreetype"));
if (f.open(QIODevice::WriteOnly)) {
f.write("1");
f.close();
}
cSetUseFreeType(true);
}
App::restart();
}
void Application::writeInstallBetaVersionsSetting() {
_launcher->writeInstallBetaVersionsSetting();
}
@@ -752,6 +772,17 @@ void Application::notifyFileDialogShown(bool shown) {
}
}
QWidget *Application::getModalParent() {
#ifdef Q_OS_LINUX
return Platform::IsWayland()
? App::wnd()
: nullptr;
#endif // Q_OS_LINUX
return nullptr;
}
void Application::checkMediaViewActivation() {
if (_mediaView && !_mediaView->isHidden()) {
_mediaView->activateWindow();

View File

@@ -101,6 +101,7 @@ public:
bool minimizeActiveWindow();
QWidget *getFileDialogParent();
void notifyFileDialogShown(bool shown);
QWidget *getModalParent();
// Media view interface.
void checkMediaViewActivation();
@@ -215,6 +216,7 @@ public:
void switchDebugMode();
void switchTestMode();
void switchFreeType();
void writeInstallBetaVersionsSetting();
void call_handleUnreadCounterUpdate();

View File

@@ -18,30 +18,6 @@ namespace {
std::map<int, const char*> BetaLogs() {
return {
{
1009010,
"\xE2\x80\xA2 Switch to the Picture-in-Picture mode "
"to watch your video in a small window.\n"
"\xE2\x80\xA2 Change video playback speed "
"in the playback controls '...' menu.\n"
"\xE2\x80\xA2 Rotate photos and videos in the media viewer "
"using the rotate button in the bottom right corner.\n"
},
{
1009015,
"\xE2\x80\xA2 Mark new messages as read "
"while scrolling down through them.\n"
"\xE2\x80\xA2 Bug fixes and other minor improvements."
},
{
1009017,
"\xE2\x80\xA2 Spell checker on Windows 7.\n"
"\xE2\x80\xA2 Bug fixes and other minor improvements."
},
{
1009020,
"\xE2\x80\xA2 Fix crash in shared links search.\n"
@@ -63,6 +39,13 @@ std::map<int, const char*> BetaLogs() {
"was added to archive.\n"
"\xE2\x80\xA2 Fix font issues in Linux version."
},
{
2001008,
"\xE2\x80\xA2 Add support for full group message history export.\n"
"\xE2\x80\xA2 Allow export of a single chat message history "
"in JSON format."
}
};
};

View File

@@ -34,26 +34,47 @@ private:
static constexpr auto kForwardArgumentCount = 1;
int _count = 0;
char *_arguments[kForwardArgumentCount + 1] = { nullptr };
std::vector<QByteArray> _owned;
std::vector<char*> _arguments;
void pushArgument(const char *text);
};
FilteredCommandLineArguments::FilteredCommandLineArguments(
int argc,
char **argv)
: _count(std::clamp(argc, 0, kForwardArgumentCount)) {
char **argv) {
// For now just pass only the first argument, the executable path.
for (auto i = 0; i != _count; ++i) {
_arguments[i] = argv[i];
for (auto i = 0; i != kForwardArgumentCount; ++i) {
pushArgument(argv[i]);
}
#if defined Q_OS_WIN || defined Q_OS_MAC
if (cUseFreeType()) {
pushArgument("-platform");
#ifdef Q_OS_WIN
pushArgument("windows:fontengine=freetype");
#else // Q_OS_WIN
pushArgument("cocoa:fontengine=freetype");
#endif // !Q_OS_WIN
}
#endif // Q_OS_WIN || Q_OS_MAC
pushArgument(nullptr);
}
int &FilteredCommandLineArguments::count() {
_count = _arguments.size() - 1;
return _count;
}
char **FilteredCommandLineArguments::values() {
return _arguments;
return _arguments.data();
}
void FilteredCommandLineArguments::pushArgument(const char *text) {
_owned.emplace_back(text);
_arguments.push_back(_owned.back().data());
}
QString DebugModeSettingPath() {
@@ -77,11 +98,33 @@ void ComputeDebugMode() {
}
void ComputeTestMode() {
if (QFile(cWorkingDir() + qsl("tdata/withtestmode")).exists()) {
if (QFile::exists(cWorkingDir() + qsl("tdata/withtestmode"))) {
cSetTestMode(true);
}
}
void ComputeExternalUpdater() {
QFile file(qsl("/etc/tdesktop/externalupdater"));
if (file.exists() && file.open(QIODevice::ReadOnly)) {
QTextStream fileStream(&file);
while (!fileStream.atEnd()) {
const auto path = fileStream.readLine();
if (path == (cWorkingDir() + cExeName())) {
SetUpdaterDisabledAtStartup();
return;
}
}
}
}
void ComputeFreeType() {
if (QFile::exists(cWorkingDir() + qsl("tdata/withfreetype"))) {
cSetUseFreeType(true);
}
}
QString InstallBetaVersionsSettingPath() {
return cWorkingDir() + qsl("tdata/devversion");
}
@@ -97,7 +140,7 @@ void ComputeInstallBetaVersions() {
const auto installBetaSettingPath = InstallBetaVersionsSettingPath();
if (cAlphaVersion()) {
cSetInstallBetaVersion(false);
} else if (QFile(installBetaSettingPath).exists()) {
} else if (QFile::exists(installBetaSettingPath)) {
QFile f(installBetaSettingPath);
if (f.open(QIODevice::ReadOnly)) {
cSetInstallBetaVersion(f.read(1) != "0");
@@ -141,7 +184,7 @@ bool MoveLegacyAlphaFolder(const QString &folder, const QString &file) {
if (QDir(was).exists() && !QDir(now).exists()) {
const auto oldFile = was + "/tdata/" + file;
const auto newFile = was + "/tdata/alpha";
if (QFile(oldFile).exists() && !QFile(newFile).exists()) {
if (QFile::exists(oldFile) && !QFile::exists(newFile)) {
if (!QFile(oldFile).copy(newFile)) {
LOG(("FATAL: Could not copy '%1' to '%2'"
).arg(oldFile
@@ -256,6 +299,14 @@ void Launcher::init() {
QApplication::setAttribute(Qt::AA_DisableHighDpiScaling, true);
#endif // OS_MAC_OLD
// fallback session management is useless for tdesktop since it doesn't have
// any "are you sure you want to close this window?" dialogs
// but it produces bugs like https://github.com/telegramdesktop/tdesktop/issues/5022
// and https://github.com/telegramdesktop/tdesktop/issues/7549
// and https://github.com/telegramdesktop/tdesktop/issues/948
// more info: https://doc.qt.io/qt-5/qguiapplication.html#isFallbackSessionManagementEnabled
QApplication::setFallbackSessionManagementEnabled(false);
initHook();
}
@@ -301,6 +352,8 @@ void Launcher::workingFolderReady() {
ComputeTestMode();
ComputeDebugMode();
ComputeExternalUpdater();
ComputeFreeType();
ComputeInstallBetaVersions();
ComputeInstallationTag();
}
@@ -382,6 +435,7 @@ void Launcher::processArguments() {
auto parseMap = std::map<QByteArray, KeyFormat> {
{ "-testmode" , KeyFormat::NoValues },
{ "-debug" , KeyFormat::NoValues },
{ "-freetype" , KeyFormat::NoValues },
{ "-many" , KeyFormat::NoValues },
{ "-key" , KeyFormat::OneValue },
{ "-autostart" , KeyFormat::NoValues },
@@ -423,6 +477,7 @@ void Launcher::processArguments() {
SetUpdaterDisabledAtStartup();
}
gTestMode = parseResult.contains("-testmode");
gUseFreeType = parseResult.contains("-freetype");
Logs::SetDebugEnabled(parseResult.contains("-debug"));
gManyInstance = parseResult.contains("-many");
gKeyFile = parseResult.value("-key", {}).join(QString()).toLower();

View File

@@ -21,6 +21,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "boxes/connection_box.h"
#include "boxes/sticker_set_box.h"
#include "boxes/sessions_box.h"
#include "boxes/language_box.h"
#include "passport/passport_form_controller.h"
#include "window/window_session_controller.h"
#include "data/data_session.h"
@@ -101,12 +102,21 @@ bool ShowTheme(
return true;
}
void ShowLanguagesBox() {
static auto Guard = base::binary_guard();
Guard = LanguageBox::Show();
}
bool SetLanguage(
Main::Session *session,
const Match &match,
const QVariant &context) {
const auto languageId = match->captured(1);
Lang::CurrentCloudManager().switchWithWarning(languageId);
if (match->capturedRef(1).isEmpty()) {
ShowLanguagesBox();
} else {
const auto languageId = match->captured(2);
Lang::CurrentCloudManager().switchWithWarning(languageId);
}
return true;
}
@@ -351,6 +361,9 @@ bool ResolveSettings(
if (section == qstr("devices")) {
Ui::show(Box<SessionsBox>(session));
return true;
} else if (section == qstr("language")) {
ShowLanguagesBox();
return true;
}
const auto type = (section == qstr("folders"))
? ::Settings::Type::Folders
@@ -441,7 +454,7 @@ const std::vector<LocalUrlHandler> &LocalUrlHandlers() {
ShowTheme
},
{
qsl("^setlanguage/?\\?lang=([a-zA-Z0-9\\.\\_\\-]+)(&|$)"),
qsl("^setlanguage/?(\\?lang=([a-zA-Z0-9\\.\\_\\-]+))?(&|$)"),
SetLanguage
},
{
@@ -481,7 +494,7 @@ const std::vector<LocalUrlHandler> &LocalUrlHandlers() {
ResolvePrivatePost
},
{
qsl("^settings(/folders|/devices)?$"),
qsl("^settings(/folders|/devices|/language)?$"),
ResolveSettings
},
{

View File

@@ -1,83 +0,0 @@
/*
This file is part of Telegram Desktop,
the official desktop application for the Telegram messaging service.
For license and copyright information please follow this link:
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#pragma once
#include "base/last_used_cache.h"
namespace Core {
template <typename Type>
class MediaActiveCache {
public:
template <typename Unload>
MediaActiveCache(int64 limit, Unload &&unload);
void up(Type *entry);
void remove(Type *entry);
void clear();
void increment(int64 amount);
void decrement(int64 amount);
private:
template <typename Unload>
void check(Unload &&unload);
base::last_used_cache<Type*> _cache;
SingleQueuedInvokation _delayed;
int64 _usage = 0;
int64 _limit = 0;
};
template <typename Type>
template <typename Unload>
MediaActiveCache<Type>::MediaActiveCache(int64 limit, Unload &&unload)
: _delayed([=] { check(unload); })
, _limit(limit) {
}
template <typename Type>
void MediaActiveCache<Type>::up(Type *entry) {
_cache.up(entry);
_delayed.call();
}
template <typename Type>
void MediaActiveCache<Type>::remove(Type *entry) {
_cache.remove(entry);
}
template <typename Type>
void MediaActiveCache<Type>::clear() {
_cache.clear();
}
template <typename Type>
void MediaActiveCache<Type>::increment(int64 amount) {
_usage += amount;
}
template <typename Type>
void MediaActiveCache<Type>::decrement(int64 amount) {
_usage -= amount;
}
template <typename Type>
template <typename Unload>
void MediaActiveCache<Type>::check(Unload &&unload) {
while (_usage > _limit) {
if (const auto entry = _cache.take_lowest()) {
unload(entry);
} else {
break;
}
}
}
} // namespace Core

View File

@@ -373,11 +373,8 @@ void Sandbox::readClients() {
toSend.append(_escapeFrom7bit(cmds.mid(from + 5, to - from - 5)));
}
} else if (cmd.startsWith(qsl("OPEN:"))) {
auto activateRequired = true;
if (cStartUrl().isEmpty()) {
startUrl = _escapeFrom7bit(cmds.mid(from + 5, to - from - 5)).mid(0, 8192);
activateRequired = StartUrlRequiresActivate(startUrl);
}
startUrl = _escapeFrom7bit(cmds.mid(from + 5, to - from - 5)).mid(0, 8192);
auto activateRequired = StartUrlRequiresActivate(startUrl);
if (activateRequired) {
execExternal("show");
}

View File

@@ -33,11 +33,11 @@ extern "C" {
#include <openssl/err.h>
} // extern "C"
#ifdef Q_OS_WIN // use Lzma SDK for win
#if defined Q_OS_WIN && !defined DESKTOP_APP_USE_PACKAGED // use Lzma SDK for win
#include <LzmaLib.h>
#else // Q_OS_WIN
#else // Q_OS_WIN && !DESKTOP_APP_USE_PACKAGED
#include <lzma.h>
#endif // else of Q_OS_WIN
#endif // else of Q_OS_WIN && !DESKTOP_APP_USE_PACKAGED
namespace Core {
namespace {
@@ -252,11 +252,11 @@ bool UnpackUpdate(const QString &filepath) {
return false;
}
#ifdef Q_OS_WIN // use Lzma SDK for win
#if defined Q_OS_WIN && !defined DESKTOP_APP_USE_PACKAGED // use Lzma SDK for win
const int32 hSigLen = 128, hShaLen = 20, hPropsLen = LZMA_PROPS_SIZE, hOriginalSizeLen = sizeof(int32), hSize = hSigLen + hShaLen + hPropsLen + hOriginalSizeLen; // header
#else // Q_OS_WIN
#else // Q_OS_WIN && !DESKTOP_APP_USE_PACKAGED
const int32 hSigLen = 128, hShaLen = 20, hPropsLen = 0, hOriginalSizeLen = sizeof(int32), hSize = hSigLen + hShaLen + hOriginalSizeLen; // header
#endif // Q_OS_WIN
#endif // Q_OS_WIN && !DESKTOP_APP_USE_PACKAGED
QByteArray compressed = input.readAll();
int32 compressedLen = compressed.size() - hSize;
@@ -311,14 +311,14 @@ bool UnpackUpdate(const QString &filepath) {
uncompressed.resize(uncompressedLen);
size_t resultLen = uncompressed.size();
#ifdef Q_OS_WIN // use Lzma SDK for win
#if defined Q_OS_WIN && !defined DESKTOP_APP_USE_PACKAGED // use Lzma SDK for win
SizeT srcLen = compressedLen;
int uncompressRes = LzmaUncompress((uchar*)uncompressed.data(), &resultLen, (const uchar*)(compressed.constData() + hSize), &srcLen, (const uchar*)(compressed.constData() + hSigLen + hShaLen), LZMA_PROPS_SIZE);
if (uncompressRes != SZ_OK) {
LOG(("Update Error: could not uncompress lzma, code: %1").arg(uncompressRes));
return false;
}
#else // Q_OS_WIN
#else // Q_OS_WIN && !DESKTOP_APP_USE_PACKAGED
lzma_stream stream = LZMA_STREAM_INIT;
lzma_ret ret = lzma_stream_decoder(&stream, UINT64_MAX, LZMA_CONCATENATED);
@@ -361,7 +361,7 @@ bool UnpackUpdate(const QString &filepath) {
LOG(("Error in decompression: %1 (error code %2)").arg(msg).arg(res));
return false;
}
#endif // Q_OS_WIN
#endif // Q_OS_WIN && !DESKTOP_APP_USE_PACKAGED
tempDir.mkdir(tempDir.absolutePath());

View File

@@ -22,7 +22,7 @@ constexpr auto AppId = "{53F49750-6209-4FBF-9CA8-7A333C87D1ED}"_cs;
constexpr auto AppNameOld = "Telegram Win (Unofficial)"_cs;
constexpr auto AppName = "Telegram Desktop"_cs;
constexpr auto AppFile = "Telegram"_cs;
constexpr auto AppVersion = 2001002;
constexpr auto AppVersionStr = "2.1.2";
constexpr auto AppBetaVersion = false;
constexpr auto AppVersion = 2001008;
constexpr auto AppVersionStr = "2.1.8";
constexpr auto AppBetaVersion = true;
constexpr auto AppAlphaVersion = TDESKTOP_ALPHA_VERSION;

View File

@@ -10,8 +10,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_peer.h"
#include "data/data_photo.h"
#include "data/data_document.h"
#include "ui/image/image_source.h"
#include "ui/image/image.h"
#include <QtCore/QBuffer>
@@ -261,7 +259,7 @@ bool Should(
const Full &data,
Source source,
not_null<DocumentData*> document) {
if (document->sticker()) {
if (document->sticker() || document->isGifv()) {
return true;
} else if (document->isVoiceMessage()
|| document->isVideoMessage()
@@ -294,11 +292,11 @@ bool Should(
bool Should(
const Full &data,
not_null<PeerData*> peer,
not_null<Images::Source*> image) {
not_null<PhotoData*> photo) {
return data.shouldDownload(
SourceFromPeer(peer),
Type::Photo,
image->bytesSize());
photo->imageByteSize(PhotoSize::Large));
}
bool ShouldAutoPlay(

View File

@@ -9,10 +9,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include <array>
namespace Images {
class Source;
} // namespace Images
namespace Data {
namespace AutoDownload {
@@ -118,7 +114,7 @@ private:
[[nodiscard]] bool Should(
const Full &data,
not_null<PeerData*> peer,
not_null<Images::Source*> image);
not_null<PhotoData*> photo);
[[nodiscard]] bool ShouldAutoPlay(
const Full &data,

View File

@@ -175,7 +175,6 @@ public:
std::deque<not_null<UserData*>> lastAuthors;
base::flat_set<not_null<PeerData*>> markupSenders;
int botStatus = 0; // -1 - no bots, 0 - unknown, 1 - one bot, that sees all history, 2 - other
// ImagePtr photoFull;
private:
Flags _flags;

View File

@@ -0,0 +1,322 @@
/*
This file is part of Telegram Desktop,
the official desktop application for the Telegram messaging service.
For license and copyright information please follow this link:
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#include "data/data_cloud_file.h"
#include "data/data_file_origin.h"
#include "data/data_session.h"
#include "storage/cache/storage_cache_database.h"
#include "storage/file_download.h"
#include "ui/image/image.h"
#include "main/main_session.h"
namespace Data {
CloudFile::~CloudFile() {
// Destroy loader with still alive CloudFile with already zero '.loader'.
// Otherwise in ~FileLoader it tries to clear file.loader and crashes.
base::take(loader);
}
void CloudImageView::set(
not_null<Main::Session*> session,
QImage image) {
_image.emplace(std::move(image));
session->downloaderTaskFinished().notify();
}
CloudImage::CloudImage() = default;
CloudImage::CloudImage(
not_null<Main::Session*> session,
const ImageWithLocation &data) {
update(session, data);
}
Image *CloudImageView::image() {
return _image ? &*_image : nullptr;
}
void CloudImage::set(
not_null<Main::Session*> session,
const ImageWithLocation &data) {
const auto &was = _file.location.file().data;
const auto &now = data.location.file().data;
if (!data.location.valid()) {
_file.flags |= CloudFile::Flag::Cancelled;
_file.loader = nullptr;
_file.location = ImageLocation();
_file.byteSize = 0;
_file.flags = CloudFile::Flag();
_view = std::weak_ptr<CloudImageView>();
} else if (was != now
&& (!was.is<InMemoryLocation>() || now.is<InMemoryLocation>())) {
_file.location = ImageLocation();
_view = std::weak_ptr<CloudImageView>();
}
UpdateCloudFile(
_file,
data,
session->data().cache(),
kImageCacheTag,
[=](FileOrigin origin) { load(session, origin); },
[=](QImage preloaded) {
if (const auto view = activeView()) {
view->set(session, data.preloaded);
}
});
}
void CloudImage::update(
not_null<Main::Session*> session,
const ImageWithLocation &data) {
UpdateCloudFile(
_file,
data,
session->data().cache(),
kImageCacheTag,
[=](FileOrigin origin) { load(session, origin); },
[=](QImage preloaded) {
if (const auto view = activeView()) {
view->set(session, data.preloaded);
}
});
}
bool CloudImage::empty() const {
return !_file.location.valid();
}
bool CloudImage::loading() const {
return (_file.loader != nullptr);
}
bool CloudImage::failed() const {
return (_file.flags & CloudFile::Flag::Failed);
}
void CloudImage::load(not_null<Main::Session*> session, FileOrigin origin) {
const auto fromCloud = LoadFromCloudOrLocal;
const auto cacheTag = kImageCacheTag;
const auto autoLoading = false;
LoadCloudFile(_file, origin, fromCloud, autoLoading, cacheTag, [=] {
if (const auto active = activeView()) {
return !active->image();
}
return true;
}, [=](QImage result) {
if (const auto active = activeView()) {
active->set(session, std::move(result));
}
});
}
const ImageLocation &CloudImage::location() const {
return _file.location;
}
int CloudImage::byteSize() const {
return _file.byteSize;
}
std::shared_ptr<CloudImageView> CloudImage::createView() {
if (auto active = activeView()) {
return active;
}
auto view = std::make_shared<CloudImageView>();
_view = view;
return view;
}
std::shared_ptr<CloudImageView> CloudImage::activeView() {
return _view.lock();
}
bool CloudImage::isCurrentView(
const std::shared_ptr<CloudImageView> &view) const {
if (!view) {
return empty();
}
return !view.owner_before(_view) && !_view.owner_before(view);
}
void UpdateCloudFile(
CloudFile &file,
const ImageWithLocation &data,
Storage::Cache::Database &cache,
uint8 cacheTag,
Fn<void(FileOrigin)> restartLoader,
Fn<void(QImage)> usePreloaded) {
if (!data.location.valid()) {
return;
}
const auto update = !file.location.valid()
|| (data.location.file().cacheKey()
&& (!file.location.file().cacheKey()
|| (file.location.width() < data.location.width())
|| (file.location.height() < data.location.height())));
if (!update) {
return;
}
auto cacheBytes = !data.bytes.isEmpty()
? data.bytes
: file.location.file().data.is<InMemoryLocation>()
? file.location.file().data.get_unchecked<InMemoryLocation>().bytes
: QByteArray();
if (!cacheBytes.isEmpty()) {
if (const auto cacheKey = data.location.file().cacheKey()) {
cache.putIfEmpty(
cacheKey,
Storage::Cache::Database::TaggedValue(
std::move(cacheBytes),
cacheTag));
}
}
file.location = data.location;
file.byteSize = data.bytesCount;
if (!data.preloaded.isNull()) {
file.loader = nullptr;
if (usePreloaded) {
usePreloaded(data.preloaded);
}
} else if (file.loader) {
const auto origin = base::take(file.loader)->fileOrigin();
restartLoader(origin);
}
}
void LoadCloudFile(
CloudFile &file,
FileOrigin origin,
LoadFromCloudSetting fromCloud,
bool autoLoading,
uint8 cacheTag,
Fn<bool()> finalCheck,
Fn<void(CloudFile&)> done,
Fn<void(bool)> fail,
Fn<void()> progress) {
if (file.loader) {
if (fromCloud == LoadFromCloudOrLocal) {
file.loader->permitLoadFromCloud();
}
return;
} else if ((file.flags & CloudFile::Flag::Failed)
|| !file.location.valid()
|| (finalCheck && !finalCheck())) {
return;
}
file.flags &= ~CloudFile::Flag::Cancelled;
file.loader = CreateFileLoader(
file.location.file(),
origin,
QString(),
file.byteSize,
UnknownFileLocation,
LoadToCacheAsWell,
fromCloud,
autoLoading,
cacheTag);
const auto finish = [done](CloudFile &file) {
if (!file.loader || file.loader->cancelled()) {
file.flags |= CloudFile::Flag::Cancelled;
} else {
done(file);
}
// NB! file.loader may be in ~FileLoader() already.
if (const auto loader = base::take(file.loader)) {
if ((file.flags & CloudFile::Flag::Cancelled)
&& !loader->cancelled()) {
loader->cancel();
}
}
};
file.loader->updates(
) | rpl::start_with_next_error_done([=] {
if (const auto onstack = progress) {
onstack();
}
}, [=, &file](bool started) {
finish(file);
file.flags |= CloudFile::Flag::Failed;
if (const auto onstack = fail) {
onstack(started);
}
}, [=, &file] {
finish(file);
}, file.loader->lifetime());
file.loader->start();
}
void LoadCloudFile(
CloudFile &file,
FileOrigin origin,
LoadFromCloudSetting fromCloud,
bool autoLoading,
uint8 cacheTag,
Fn<bool()> finalCheck,
Fn<void(QImage)> done,
Fn<void(bool)> fail,
Fn<void()> progress) {
const auto callback = [=](CloudFile &file) {
if (auto read = file.loader->imageData(); read.isNull()) {
file.flags |= CloudFile::Flag::Failed;
if (const auto onstack = fail) {
onstack(true);
}
} else if (const auto onstack = done) {
onstack(std::move(read));
}
};
LoadCloudFile(
file,
origin,
fromCloud,
autoLoading,
cacheTag,
finalCheck,
callback,
std::move(fail),
std::move(progress));
}
void LoadCloudFile(
CloudFile &file,
FileOrigin origin,
LoadFromCloudSetting fromCloud,
bool autoLoading,
uint8 cacheTag,
Fn<bool()> finalCheck,
Fn<void(QByteArray)> done,
Fn<void(bool)> fail,
Fn<void()> progress) {
const auto callback = [=](CloudFile &file) {
if (auto bytes = file.loader->bytes(); bytes.isEmpty()) {
file.flags |= CloudFile::Flag::Failed;
if (const auto onstack = fail) {
onstack(true);
}
} else if (const auto onstack = done) {
onstack(std::move(bytes));
}
};
LoadCloudFile(
file,
origin,
fromCloud,
autoLoading,
cacheTag,
finalCheck,
callback,
std::move(fail),
std::move(progress));
}
} // namespace Data

View File

@@ -0,0 +1,120 @@
/*
This file is part of Telegram Desktop,
the official desktop application for the Telegram messaging service.
For license and copyright information please follow this link:
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#pragma once
#include "base/flags.h"
#include "ui/image/image.h"
#include "ui/image/image_location.h"
class FileLoader;
namespace Storage {
namespace Cache {
class Database;
} // namespace Cache
} // namespace Storage
namespace Main {
class Session;
} // namespace Main
namespace Data {
struct FileOrigin;
struct CloudFile final {
enum class Flag : uchar {
Cancelled = 0x01,
Failed = 0x02,
};
friend inline constexpr bool is_flag_type(Flag) { return true; };
~CloudFile();
ImageLocation location;
std::unique_ptr<FileLoader> loader;
int byteSize = 0;
base::flags<Flag> flags;
};
class CloudImageView final {
public:
void set(not_null<Main::Session*> session, QImage image);
[[nodiscard]] Image *image();
private:
std::optional<Image> _image;
};
class CloudImage final {
public:
CloudImage();
CloudImage(
not_null<Main::Session*> session,
const ImageWithLocation &data);
// This method will replace the location and zero the _view pointer.
void set(
not_null<Main::Session*> session,
const ImageWithLocation &data);
void update(
not_null<Main::Session*> session,
const ImageWithLocation &data);
[[nodiscard]] bool empty() const;
[[nodiscard]] bool loading() const;
[[nodiscard]] bool failed() const;
void load(not_null<Main::Session*> session, FileOrigin origin);
[[nodiscard]] const ImageLocation &location() const;
[[nodiscard]] int byteSize() const;
[[nodiscard]] std::shared_ptr<CloudImageView> createView();
[[nodiscard]] std::shared_ptr<CloudImageView> activeView();
[[nodiscard]] bool isCurrentView(
const std::shared_ptr<CloudImageView> &view) const;
private:
CloudFile _file;
std::weak_ptr<CloudImageView> _view;
};
void UpdateCloudFile(
CloudFile &file,
const ImageWithLocation &data,
Storage::Cache::Database &cache,
uint8 cacheTag,
Fn<void(FileOrigin)> restartLoader,
Fn<void(QImage)> usePreloaded = nullptr);
void LoadCloudFile(
CloudFile &file,
FileOrigin origin,
LoadFromCloudSetting fromCloud,
bool autoLoading,
uint8 cacheTag,
Fn<bool()> finalCheck,
Fn<void(QImage)> done,
Fn<void(bool)> fail = nullptr,
Fn<void()> progress = nullptr);
void LoadCloudFile(
CloudFile &file,
FileOrigin origin,
LoadFromCloudSetting fromCloud,
bool autoLoading,
uint8 cacheTag,
Fn<bool()> finalCheck,
Fn<void(QByteArray)> done,
Fn<void(bool)> fail = nullptr,
Fn<void()> progress = nullptr);
} // namespace Data

View File

@@ -14,6 +14,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_session.h"
#include "data/data_document.h"
#include "data/data_file_origin.h"
#include "data/data_document_media.h"
#include "main/main_session.h"
#include "boxes/confirm_box.h"
#include "core/application.h" // Core::App().showTheme.
@@ -178,9 +179,11 @@ void CloudThemes::showPreview(const CloudTheme &cloud) {
void CloudThemes::applyFromDocument(const CloudTheme &cloud) {
const auto document = _session->data().document(cloud.documentId);
loadDocumentAndInvoke(_updatingFrom, cloud, document, [=] {
loadDocumentAndInvoke(_updatingFrom, cloud, document, [=](
std::shared_ptr<Data::DocumentMedia> media) {
const auto document = media->owner();
auto preview = Window::Theme::PreviewFromFile(
document->data(),
media->bytes(),
document->location().name(),
cloud);
if (preview) {
@@ -192,7 +195,9 @@ void CloudThemes::applyFromDocument(const CloudTheme &cloud) {
void CloudThemes::previewFromDocument(const CloudTheme &cloud) {
const auto document = _session->data().document(cloud.documentId);
loadDocumentAndInvoke(_previewFrom, cloud, document, [=] {
loadDocumentAndInvoke(_previewFrom, cloud, document, [=](
std::shared_ptr<Data::DocumentMedia> media) {
const auto document = media->owner();
Core::App().showTheme(document, cloud);
});
}
@@ -201,25 +206,26 @@ void CloudThemes::loadDocumentAndInvoke(
LoadingDocument &value,
const CloudTheme &cloud,
not_null<DocumentData*> document,
Fn<void()> callback) {
Fn<void(std::shared_ptr<Data::DocumentMedia>)> callback) {
const auto alreadyWaiting = (value.document != nullptr);
if (alreadyWaiting) {
value.document->cancel();
}
value.document = document;
value.documentMedia = document->createMediaView();
value.document->save(
Data::FileOriginTheme(cloud.id, cloud.accessHash),
QString());
value.callback = std::move(callback);
if (document->loaded()) {
if (value.documentMedia->loaded()) {
invokeForLoaded(value);
return;
}
if (!alreadyWaiting) {
base::ObservableViewer(
_session->downloaderTaskFinished()
) | rpl::filter([=] {
return document->loaded();
) | rpl::filter([=, &value] {
return value.documentMedia->loaded();
}) | rpl::start_with_next([=, &value] {
invokeForLoaded(value);
}, value.subscription);
@@ -228,8 +234,9 @@ void CloudThemes::loadDocumentAndInvoke(
void CloudThemes::invokeForLoaded(LoadingDocument &value) {
const auto onstack = std::move(value.callback);
auto media = std::move(value.documentMedia);
value = LoadingDocument();
onstack();
onstack(std::move(media));
}
void CloudThemes::scheduleReload() {

View File

@@ -17,6 +17,8 @@ class Session;
namespace Data {
class DocumentMedia;
struct CloudTheme {
uint64 id = 0;
uint64 accessHash = 0;
@@ -54,8 +56,9 @@ private:
struct LoadingDocument {
CloudTheme theme;
DocumentData *document = nullptr;
std::shared_ptr<Data::DocumentMedia> documentMedia;
rpl::lifetime subscription;
Fn<void()> callback;
Fn<void(std::shared_ptr<Data::DocumentMedia>)> callback;
};
void parseThemes(const QVector<MTPTheme> &list);
@@ -71,7 +74,7 @@ private:
LoadingDocument &value,
const CloudTheme &cloud,
not_null<DocumentData*> document,
Fn<void()> callback);
Fn<void(std::shared_ptr<Data::DocumentMedia>)> callback);
void invokeForLoaded(LoadingDocument &value);
const not_null<Main::Session*> _session;

File diff suppressed because it is too large Load Diff

View File

@@ -10,6 +10,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "base/flags.h"
#include "base/binary_guard.h"
#include "data/data_types.h"
#include "data/data_cloud_file.h"
#include "ui/image/image.h"
class mtpFileLoader;
@@ -32,6 +33,8 @@ class Loader;
namespace Data {
class Session;
class DocumentMedia;
class ReplyPreview;
} // namespace Data
namespace Main {
@@ -56,11 +59,9 @@ struct DocumentAdditionalData {
struct StickerData : public DocumentAdditionalData {
Data::FileOrigin setOrigin() const;
std::unique_ptr<Image> image;
bool animated = false;
QString alt;
MTPInputStickerSet set = MTP_inputStickerSetEmpty();
StorageImageLocation loc; // doc thumb location
};
struct SongData : public DocumentAdditionalData {
@@ -83,9 +84,10 @@ namespace Serialize {
class Document;
} // namespace Serialize;
class DocumentData {
class DocumentData final {
public:
DocumentData(not_null<Data::Session*> owner, DocumentId id);
~DocumentData();
[[nodiscard]] Data::Session &owner() const;
[[nodiscard]] Main::Session &session() const;
@@ -93,19 +95,8 @@ public:
void setattributes(
const QVector<MTPDocumentAttribute> &attributes);
void automaticLoad(
Data::FileOrigin origin,
const HistoryItem *item);
void automaticLoadSettingsChanged();
enum class FilePathResolve {
Cached,
Checked,
SaveFromData,
SaveFromDataSilent,
};
[[nodiscard]] bool loaded(
FilePathResolve resolve = FilePathResolve::Cached) const;
[[nodiscard]] bool loading() const;
[[nodiscard]] QString loadingFilePath() const;
[[nodiscard]] bool displayLoading() const;
@@ -125,23 +116,18 @@ public:
void setWaitingForAlbum();
[[nodiscard]] bool waitingForAlbum() const;
[[nodiscard]] QByteArray data() const;
[[nodiscard]] const FileLocation &location(bool check = false) const;
void setLocation(const FileLocation &loc);
[[nodiscard]] QString filepath(
FilePathResolve resolve = FilePathResolve::Cached) const;
bool saveFromData();
bool saveFromDataSilent();
[[nodiscard]] QString filepath(bool check = false) const;
[[nodiscard]] bool saveToCache() const;
void unload();
[[nodiscard]] Image *getReplyPreview(Data::FileOrigin origin);
[[nodiscard]] StickerData *sticker() const;
void checkStickerLarge();
void checkStickerSmall();
[[nodiscard]] Image *getStickerSmall();
[[nodiscard]] Image *getStickerLarge();
[[nodiscard]] Data::FileOrigin stickerSetOrigin() const;
[[nodiscard]] Data::FileOrigin stickerOrGifOrigin() const;
[[nodiscard]] bool isStickerSetInstalled() const;
@@ -164,30 +150,52 @@ public:
void recountIsImage();
[[nodiscard]] bool supportsStreaming() const;
void setNotSupportsStreaming();
void setData(const QByteArray &data) {
_data = data;
}
void setDataAndCache(const QByteArray &data);
bool checkWallPaperProperties();
[[nodiscard]] bool isWallPaper() const;
[[nodiscard]] bool isPatternWallPaper() const;
[[nodiscard]] bool hasThumbnail() const;
[[nodiscard]] bool thumbnailLoading() const;
[[nodiscard]] bool thumbnailFailed() const;
void loadThumbnail(Data::FileOrigin origin);
[[nodiscard]] Image *thumbnailInline() const;
[[nodiscard]] Image *thumbnail() const;
[[nodiscard]] const ImageLocation &thumbnailLocation() const;
[[nodiscard]] int thumbnailByteSize() const;
[[nodiscard]] bool hasVideoThumbnail() const;
[[nodiscard]] bool videoThumbnailLoading() const;
[[nodiscard]] bool videoThumbnailFailed() const;
void loadVideoThumbnail(Data::FileOrigin origin);
const ImageLocation &videoThumbnailLocation() const;
int videoThumbnailByteSize() const;
void updateThumbnails(
ImagePtr thumbnailInline,
ImagePtr thumbnail);
const QByteArray &inlineThumbnailBytes,
const ImageWithLocation &thumbnail,
const ImageWithLocation &videoThumbnail);
[[nodiscard]] QByteArray inlineThumbnailBytes() const {
return _inlineThumbnailBytes;
}
void clearInlineThumbnailBytes() {
_inlineThumbnailBytes = QByteArray();
}
[[nodiscard]] Image *goodThumbnail() const;
[[nodiscard]] Storage::Cache::Key goodThumbnailCacheKey() const;
void setGoodThumbnailOnUpload(QImage &&image, QByteArray &&bytes);
void refreshGoodThumbnail();
void replaceGoodThumbnail(std::unique_ptr<Images::Source> &&source);
[[nodiscard]] bool goodThumbnailChecked() const;
[[nodiscard]] bool goodThumbnailGenerating() const;
[[nodiscard]] bool goodThumbnailNoData() const;
void setGoodThumbnailGenerating();
void setGoodThumbnailDataReady();
void setGoodThumbnailChecked(bool hasData);
[[nodiscard]] auto bigFileBaseCacheKey() const
-> std::optional<Storage::Cache::Key>;
[[nodiscard]] std::shared_ptr<Data::DocumentMedia> createMediaView();
[[nodiscard]] auto activeMediaView() const
-> std::shared_ptr<Data::DocumentMedia>;
void setGoodThumbnailPhoto(not_null<PhotoData*> photo);
[[nodiscard]] PhotoData *goodThumbnailPhoto() const;
[[nodiscard]] Storage::Cache::Key bigFileBaseCacheKey() const;
void setRemoteLocation(
int32 dc,
@@ -201,7 +209,6 @@ public:
[[nodiscard]] MTPInputDocument mtpInput() const;
[[nodiscard]] QByteArray fileReference() const;
void refreshFileReference(const QByteArray &value);
void refreshStickerThumbFileReference();
// When we have some client-side generated document
// (for example for displaying an external inline bot result)
@@ -224,18 +231,16 @@ public:
const QString &songPerformer);
[[nodiscard]] QString composeNameString() const;
[[nodiscard]] bool canBePlayed() const;
[[nodiscard]] bool canBeStreamed() const;
[[nodiscard]] auto createStreamingLoader(
Data::FileOrigin origin,
bool forceRemoteLoader) const
-> std::unique_ptr<Media::Streaming::Loader>;
[[nodiscard]] bool useStreamingLoader() const;
void setInappPlaybackFailed();
[[nodiscard]] bool inappPlaybackFailed() const;
~DocumentData();
DocumentId id = 0;
DocumentType type = FileDocument;
QSize dimensions;
@@ -258,6 +263,17 @@ private:
using Flags = base::flags<Flag>;
friend constexpr bool is_flag_type(Flag) { return true; };
enum class GoodThumbnailFlag : uchar {
Checked = 0x01,
Generating = 0x02,
NoData = 0x03,
Mask = 0x03,
DataReady = 0x04,
};
using GoodThumbnailState = base::flags<GoodThumbnailFlag>;
friend constexpr bool is_flag_type(GoodThumbnailFlag) { return true; };
static constexpr Flags kStreamingSupportedMask = Flags()
| Flag::StreamingMaybeYes
| Flag::StreamingMaybeNo;
@@ -272,16 +288,18 @@ private:
friend class Serialize::Document;
LocationType locationType() const;
[[nodiscard]] LocationType locationType() const;
void validateLottieSticker();
void validateGoodThumbnail();
void setMaybeSupportsStreaming(bool supports);
void setLoadedInMediaCacheLocation();
void destroyLoader() const;
void finishLoad();
void handleLoaderUpdates();
void destroyLoader();
[[nodiscard]] bool useStreamingLoader() const;
[[nodiscard]] bool thumbnailEnoughForSticker() const;
bool saveFromDataChecked();
const not_null<Data::Session*> _owner;
// Two types of location: from MTProto by dc+access or from web by url
int32 _dc = 0;
@@ -292,19 +310,19 @@ private:
QString _mimeString;
WebFileLocation _urlLocation;
ImagePtr _thumbnailInline;
ImagePtr _thumbnail;
std::unique_ptr<Image> _goodThumbnail;
Data::ReplyPreview _replyPreview;
not_null<Data::Session*> _owner;
QByteArray _inlineThumbnailBytes;
Data::CloudFile _thumbnail;
Data::CloudFile _videoThumbnail;
std::unique_ptr<Data::ReplyPreview> _replyPreview;
std::weak_ptr<Data::DocumentMedia> _media;
PhotoData *_goodThumbnailPhoto = nullptr;
FileLocation _location;
QByteArray _data;
std::unique_ptr<DocumentAdditionalData> _additional;
int32 _duration = -1;
mutable Flags _flags = kStreamingSupportedUnknown;
mutable std::unique_ptr<FileLoader> _loader;
GoodThumbnailState _goodThumbnailState = GoodThumbnailState();
std::unique_ptr<FileLoader> _loader;
};
@@ -421,13 +439,19 @@ QString FileNameForSave(
bool savingAs,
const QDir &dir = QDir());
QString DocumentFileNameForSave(
not_null<const DocumentData*> data,
bool forceSavingAs = false,
const QString &already = QString(),
const QDir &dir = QDir());
namespace Data {
QString FileExtension(const QString &filepath);
bool IsValidMediaFile(const QString &filepath);
bool IsExecutableName(const QString &filepath);
base::binary_guard ReadImageAsync(
not_null<DocumentData*> document,
not_null<Data::DocumentMedia*> media,
FnMut<QImage(QImage)> postprocess,
FnMut<void(QImage&&)> done);

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