Compare commits

...

400 Commits

Author SHA1 Message Date
John Preston
d031046edb Version 4.11.2: Fix build with GCC. 2023-11-02 00:06:42 +04:00
John Preston
7808cc6d41 Version 4.11.2: Fix build with Xcode. 2023-11-01 22:57:36 +04:00
John Preston
a7b60c43b5 Version 4.11.2.
- Highlight quoted parts in jump-to-message from replies.
- Ctrl+Click on message field reply bar to jump to message.
- Fix empty link preview displaying when generation failed.
- Fix external replies in topic groups.
- Allow enabling legacy tray icon on Windows.
2023-11-01 22:26:15 +04:00
John Preston
a8b959826c Don't activate main window in case of visible call window.
Fixes #27017.
2023-11-01 22:24:09 +04:00
John Preston
42f96f3c43 Fix build with Xcode. 2023-11-01 22:24:09 +04:00
John Preston
cc97093c5a Instantly jump-to-message on reply bar ctrl+click. 2023-11-01 22:13:21 +04:00
John Preston
7d5d086ade Allow messages when accepting miniapp terms. 2023-11-01 21:04:25 +04:00
John Preston
3da44eb5dd Fix attach menu suggested bot launch. 2023-11-01 21:04:25 +04:00
John Preston
4955cdcdce Wait for main menu bot icon to load. 2023-11-01 21:04:25 +04:00
23rd
597195a2e2 Replaced Session Controller with Navigation for resolving of giftcodes. 2023-11-01 17:01:25 +03:00
23rd
5966c74a27 Added ability to go to message from message preview in statistics info. 2023-11-01 17:00:26 +03:00
John Preston
caa1ae4436 Fix inline bots with secondary usernames. 2023-11-01 16:39:46 +04:00
John Preston
af5ad84e72 Fix crashpad_handler linking workaround.
Fixes #26873.
2023-11-01 09:27:06 +04:00
John Preston
728ed02a1c Allow selecting text in webpage previews. 2023-11-01 09:17:08 +04:00
John Preston
8e369a4aa5 Fix moved up link preview with long text. 2023-11-01 08:44:25 +04:00
John Preston
35e457c924 Fix quote create in RepliesWidget. 2023-10-31 23:41:41 +04:00
John Preston
097c3c4a5a Allow changing album quote before sending. 2023-10-31 23:25:26 +04:00
John Preston
10022a3c6d Highlight quotes in replies to file albums. 2023-10-31 23:06:21 +04:00
John Preston
0dbb195106 Highlight quotes in replies to albums. 2023-10-31 22:37:59 +04:00
23rd
6493cb9ed8 Fixed mouse wheel handling in vertical drum picker without animation. 2023-10-31 13:11:01 +03:00
John Preston
1cfda38345 Fix reply in topic after creation. 2023-10-31 13:20:30 +04:00
John Preston
bf20dbe3bc Fix external replies to different topic groups. 2023-10-31 13:13:31 +04:00
John Preston
46d3f232af Don't drop reply info on empty message submit. 2023-10-31 13:08:32 +04:00
John Preston
ade97fd2d2 Fix unwrapped reply to monospace. 2023-10-31 13:00:03 +04:00
John Preston
39614aab3f Fix unwrapped reply overlapping timestamp. 2023-10-31 12:59:52 +04:00
John Preston
6bab2b4df6 Improve quotes composing. 2023-10-31 12:59:32 +04:00
John Preston
b4c7272351 Keep external replies in forwards. 2023-10-31 12:21:07 +04:00
John Preston
d831775e2f Fix replying in the same history. 2023-10-31 11:19:57 +04:00
John Preston
bde39970a0 Fix boost reassign. 2023-10-31 11:17:55 +04:00
John Preston
076291b98f Fix reply invalidation on message removal. 2023-10-31 10:53:20 +04:00
John Preston
1341907cfd Disable external replies for non-forwardable. 2023-10-31 10:50:17 +04:00
John Preston
b793c06759 Fix external replies in topic groups. 2023-10-31 10:39:54 +04:00
John Preston
475b2ac739 Fix external quote-reply to topic message. 2023-10-31 09:01:20 +04:00
John Preston
d1c310de00 Highlight reply quote in original message. 2023-10-31 09:01:20 +04:00
John Preston
8615a25cd1 Fix empty preview if sent while failing generating.
Fixes #27004.
2023-10-31 09:01:20 +04:00
Ilya Fedin
de4eb1e59b Disable glib tests and introspection generation in snap 2023-10-31 08:16:05 +04:00
Ilya Fedin
8e8f6f905f Build newer meson in snap 2023-10-31 08:16:05 +04:00
John Preston
a57eecd420 Add option to use old tray icon.
New monochrome icon is default for the new installations.

Fixes #26983, fixes #26988, fixes #26989, fixes #26991, fixes #27005.
2023-10-30 11:16:17 +04:00
John Preston
f306b11676 Add id-s only when displayed in profile. 2023-10-30 10:51:11 +04:00
John Preston
745ad45d47 Version 4.11.1: Update message text padding. 2023-10-29 12:31:56 +04:00
John Preston
5fee0a7a73 Don't allow replying to local messages. 2023-10-29 12:31:56 +04:00
John Preston
096ddcad6d Version 4.11.1.
- Fix crash in emoji status select.
- Fix crash in language change.
- Suggest shrinking only photos in webpage previews.
- Fix opening video files in webpage previews in-app.
- Fix sending links and markup with customized webpage previews.
- Show "Saved Messages" as a first row when replying in another chat.
- Fix selecting words by double-click with webpage previews.
- Fix delayed webpage preview generation in preview options.
- Add "show-peer-id-below-about" experimental option.
2023-10-29 12:15:20 +04:00
John Preston
3b7448ccab Version 4.12.
- Fix crash in emoji status select.
- Fix crash in language change.
- Suggest shrinking only photos in webpage previews.
- Fix opening video files in webpage previews in-app.
- Fix sending links and markup with customized webpage previews.
- Show "Saved Messages" as a first row when replying in another chat.
- Fix selecting words by double-click with webpage previews.
- Fix delayed webpage preview generation in preview options.
- Add "show-peer-id-below-about" experimental option.
2023-10-29 11:36:06 +04:00
John Preston
478c6c4d36 Fix pending link previews in preview options box. 2023-10-29 10:57:07 +04:00
John Preston
e2ea27cbef Fix select-by-words with link previews. 2023-10-29 10:25:52 +04:00
John Preston
625ae87eea Add "show-peer-id-below-about" option. 2023-10-29 10:12:48 +04:00
John Preston
597816db09 Show Saved Messages in Reply in another chat. 2023-10-29 09:52:42 +04:00
John Preston
ec3fc8c749 Send entities with customized webpage preview.
Fixes #26981.
2023-10-29 08:55:30 +04:00
John Preston
e64a096dca Don't suggest shrinking media of direct photo links. 2023-10-29 08:51:42 +04:00
John Preston
eaf30d58be Open documents-in-web-previews in-app. 2023-10-29 08:48:45 +04:00
John Preston
94ac9f93fa Don't shrink non-photo media. 2023-10-29 07:54:39 +04:00
John Preston
04f040c9c5 Fix a crash in emoji status set. 2023-10-29 07:09:42 +04:00
John Preston
af0e87c569 Fix a crash on languages box open. 2023-10-29 07:09:42 +04:00
John Preston
1a503e5f1d Version 4.11: Fix build with GCC. 2023-10-29 00:33:54 +04:00
John Preston
c46b659aa5 Version 4.11.
- View full statistics in your channels and group chats.
- Choose which link preview in added to the message.
- Choose if link preview is above or below the text.
- Choose if link preview has large or small image.
- Quote parts of text in replies.
- Add quote formatting.
- Reply in another chat.
- Add nice looking code blocks with syntax highlighting.
- Copy full code block by click on its header.
- Send a highlighted code block using ```language syntax.
- Change your name color in Chat Settings.
- Customize quotes, link previews and replies to your messages.
2023-10-28 23:39:31 +04:00
John Preston
7a754f8b00 Update reply preview in scale / themes. 2023-10-28 23:12:37 +04:00
John Preston
29fb263495 Use lang-packed "copy" instead of "code". 2023-10-28 22:34:08 +04:00
John Preston
c8660b5385 Improve color/emoji admin log events. 2023-10-28 22:27:30 +04:00
John Preston
d47b99b0b5 Don't set box show crash annotation (GenericBox). 2023-10-28 21:51:37 +04:00
John Preston
88e3c87cd9 Crash on assertion instead of stack overflow. 2023-10-28 21:41:00 +04:00
John Preston
fe0e526b79 Support phrases for unclaimed giveaway prizes. 2023-10-28 21:40:39 +04:00
John Preston
c480ab1a3b Fix possible crash in non-cancelled request. 2023-10-28 21:13:52 +04:00
John Preston
49bc8ccd6a Don't gray out background emoji reset icon. 2023-10-28 20:49:20 +04:00
John Preston
b180070ba3 Improve very small quote areas with icons. 2023-10-28 20:49:20 +04:00
John Preston
b40f30ca98 Update Implib.so. 2023-10-28 20:29:01 +04:00
John Preston
c2a1817400 Always use first color as the main one. 2023-10-28 20:19:09 +04:00
John Preston
cb6698cf4a Implement background emoji selector. 2023-10-28 18:06:17 +04:00
John Preston
bcdb1bdfd2 Name color changing for me / channels. 2023-10-27 23:27:10 +04:00
John Preston
effc9873c9 Remove test data in giveaways. 2023-10-27 17:49:31 +04:00
John Preston
9561026cd4 Add change color button with color sample. 2023-10-27 12:26:42 +04:00
John Preston
5d335341ab Support server-side colors by index, up to three. 2023-10-27 09:55:09 +04:00
John Preston
cc8408d11c Fix draft options edit, add to topics/replies. 2023-10-26 21:02:15 +04:00
John Preston
a197ed9e95 Allow choosing the link for the preview. 2023-10-26 14:08:52 +04:00
John Preston
3b91e2dee4 Improve editing messages with webpage previews. 2023-10-26 14:08:44 +04:00
John Preston
c035ec6917 Allow sending just webpage preview. 2023-10-26 14:08:44 +04:00
John Preston
041ec1157f Respect invert_media in service notifications. 2023-10-26 14:08:44 +04:00
John Preston
17578be4b9 Edit reply / webpage options together. 2023-10-26 14:08:43 +04:00
John Preston
1409d38ac3 Improve reply options edit design. 2023-10-26 14:08:43 +04:00
John Preston
b463c76eca Allow quote selection only in captions. 2023-10-26 14:08:43 +04:00
John Preston
d62fb5786d Support selecting quote in reply info edit. 2023-10-26 14:08:43 +04:00
John Preston
aad157cf56 Open link on webpreview attach click. 2023-10-26 14:08:43 +04:00
John Preston
2d3e2b1ef8 Fix drafts in topics. 2023-10-26 14:08:43 +04:00
John Preston
91ab82c9da Fix unread counter in General topic. 2023-10-26 14:08:43 +04:00
John Preston
9c23de7f1a Display reply background emoji. 2023-10-26 14:08:43 +04:00
John Preston
60fb5fdaf0 Update color index caches on palette change. 2023-10-26 14:08:43 +04:00
John Preston
4709e11e46 Support two-color quote outlines. 2023-10-26 14:08:43 +04:00
John Preston
8c28ce4c99 [stats] Fix build on layer 166. 2023-10-26 14:08:43 +04:00
John Preston
20c63b98c7 Fix build with Xcode. 2023-10-26 14:08:43 +04:00
John Preston
8b42161898 Allow sending custom webpage previews. 2023-10-26 14:08:43 +04:00
John Preston
b1823d981b Update API scheme, rich preview drafts. 2023-10-26 14:08:42 +04:00
John Preston
b2e8e0431e Start customizable webpages. 2023-10-26 14:08:42 +04:00
John Preston
486d5b63d3 Add countries to giveaway messages. 2023-10-26 14:08:42 +04:00
John Preston
84a1fec7b1 Add flag emoji by country iso2 method. 2023-10-26 14:08:41 +04:00
John Preston
3a84c6afdd Redesign webpage/giveaway/ads bottom button. 2023-10-26 14:08:41 +04:00
John Preston
16d18b437d Update API scheme on layer 166. 2023-10-26 14:08:41 +04:00
John Preston
12fab565a4 Fix Release build with Xcode. 2023-10-26 14:08:41 +04:00
John Preston
f8d5a8a203 Redesign web pages preview. 2023-10-26 14:08:41 +04:00
John Preston
b9af4f3cb0 Show nice replies with quotes. 2023-10-26 14:08:41 +04:00
John Preston
4b6107fa56 Use color index from API. 2023-10-26 14:08:41 +04:00
John Preston
f90a010b84 Update API scheme on layer 166. 2023-10-26 14:08:41 +04:00
John Preston
ef0539c9fc Allow replying with quoting message part. 2023-10-26 14:08:41 +04:00
John Preston
00db325e91 Fix view giveaway details button in forwards. 2023-10-26 14:08:41 +04:00
John Preston
d5429e769f Allow sharing gift code link. 2023-10-26 14:08:41 +04:00
John Preston
714dae054a Jump to giveaway link from gift code box. 2023-10-26 14:08:41 +04:00
John Preston
981babf302 Support giveaway information box. 2023-10-26 14:08:41 +04:00
John Preston
0926bb1288 Update API scheme on layer 166. 2023-10-26 14:08:40 +04:00
John Preston
e13768ea50 Fix build with Xcode. 2023-10-26 14:08:40 +04:00
John Preston
6c19274eac Support external reply to channel posts. 2023-10-26 14:08:40 +04:00
John Preston
394883b986 Support replying to a different chat. 2023-10-26 14:08:40 +04:00
John Preston
4240568ea5 Pass FullReplyTo everywhere. 2023-10-26 14:08:40 +04:00
John Preston
a77131dfd6 Add giveaway prize service message layout. 2023-10-26 14:08:40 +04:00
John Preston
caca679336 Add quantity badge to giveaway message. 2023-10-26 14:08:40 +04:00
John Preston
b08869abdb Support giveaway message layout. 2023-10-26 14:08:40 +04:00
John Preston
d5147c9d28 Partially (italic+colored) support blockquotes. 2023-10-26 14:08:40 +04:00
John Preston
859e41f95a Use webview bots window size like on mobiles. 2023-10-26 14:08:40 +04:00
John Preston
0e45f3ebd9 Provide more theme keys to webview bots. 2023-10-26 14:08:40 +04:00
John Preston
7f9461820b Don't close webview after 'web_app_open_tg_link'. 2023-10-26 14:08:40 +04:00
John Preston
e59a60b3b5 Handle new 'web_app_setup_settings_button' event. 2023-10-26 14:08:40 +04:00
John Preston
926aae6847 Update API scheme on layer 166. 2023-10-26 14:08:40 +04:00
John Preston
744c1b925e Handle GiftCode links, show Gift Link box. 2023-10-26 14:08:40 +04:00
John Preston
3fc9ed0ccb Update API scheme to layer 166. 2023-10-26 14:08:39 +04:00
John Preston
0b7d544615 Move Boost.regex definitions to cmake_helpers. 2023-10-26 14:08:39 +04:00
23rd
d19baeace2 Fixed show finishing of info layers. 2023-10-26 14:08:39 +04:00
Ilya Fedin
cd0f58fa65 Implement monochrome tray icon on Windows 2023-10-25 14:23:50 +04:00
John Preston
1f25301283 Beta version 4.10.5.
- Fix crash in replies to messages with spoilers.
- Enter boosts stats from three-dot menu.
2023-10-23 20:20:31 +04:00
23rd
0788f3d7b0 Increased size of arrow in chart point details widget. 2023-10-23 20:19:17 +04:00
23rd
f93b7a60f8 Fixed display of button to show more boosts when boosts are too few. 2023-10-23 20:19:17 +04:00
23rd
d0875a1178 Fixed sliders in footer in statistics info while restoring state. 2023-10-23 20:19:17 +04:00
23rd
29f8493a82 Removed error toast for async graphs. 2023-10-23 20:19:17 +04:00
23rd
d4db838d43 Added message preview to message statistics. 2023-10-23 20:19:17 +04:00
23rd
0be2e8b672 Fixed statistics overview of old messages. 2023-10-23 20:19:17 +04:00
23rd
0aa1031402 Added ability to open message statistics from context menu. 2023-10-23 20:19:17 +04:00
23rd
53c73accd0 Fixed typo in custom shortcut for full screen in media viewer. 2023-10-23 20:19:17 +04:00
23rd
be38800a70 Moved entry point for boosts statistic to channel context menu. 2023-10-23 20:19:17 +04:00
John Preston
f2fa1cd70d Fix crash in non-clickable spoilers. 2023-10-23 08:59:29 +04:00
Ilya Fedin
5dfce5f7b2 Fix direct include of third-party library 2023-10-22 22:20:52 +04:00
Ilya Fedin
2b10e1e595 Update lib_base 2023-10-22 22:20:52 +04:00
Ilya Fedin
eb1ef6d2a7 Fix getting the screen of viewer/pip
We need to workaround getting the actual screen for the parent by getting its position yet we need to get the setted screen for the widget itself as that's the screen used to compute the geometry
2023-10-22 22:20:52 +04:00
John Preston
fde63ccb21 Beta version 4.10.4: Fix build with GCC. 2023-10-21 22:28:23 +04:00
John Preston
b209683c8e Beta version 4.10.4.
- Statistics in channels and group chats.
- Nice looking code blocks with syntax highlight.
- Copy full code block by click on its header.
- Send a highlighted code block using ```language syntax.
2023-10-21 20:54:47 +04:00
23rd
b6be799938 Fixed processing of error on chart zooming. 2023-10-21 11:42:04 +03:00
23rd
ec8c634e9c Fixed display of arrow on point details widget when no values there. 2023-10-21 11:34:54 +03:00
23rd
b7a9aa9a0e Fixed processing of empty chart on message statistic. 2023-10-21 11:34:08 +03:00
23rd
2ca489b2fb Added initial ability to save and restore state for boosts info. 2023-10-21 09:37:30 +04:00
23rd
daf76c1bc2 Improved style of boosts info. 2023-10-21 09:37:30 +04:00
23rd
d3aa0664a7 Moved boost limits content to divider. 2023-10-21 09:37:30 +04:00
23rd
f61c22b065 Added initial boosts list to boost info. 2023-10-21 09:37:30 +04:00
23rd
8041941565 Added share label and buttons to boost info. 2023-10-21 09:37:30 +04:00
23rd
0aa1cd0b52 Removed three dots button from invite link label when menu is disabled. 2023-10-21 09:37:30 +04:00
23rd
1bbac5784b Added overview of boost info. 2023-10-21 09:37:30 +04:00
23rd
f925a9e961 Added initial boosts info to layer. 2023-10-21 09:37:30 +04:00
23rd
8b6d475882 Added initial entry point for boosts statistic. 2023-10-21 09:37:30 +04:00
23rd
d82c422ea1 Moved out boost limits content from boost box. 2023-10-21 09:37:30 +04:00
23rd
d7e57e42d8 Added API support for boost status and boosts list. 2023-10-21 09:37:30 +04:00
23rd
7ee2ec13f0 Added Data class for boosts. 2023-10-21 09:37:30 +04:00
23rd
a6e13a9f9e Added dummy layer class for boosts. 2023-10-21 09:37:30 +04:00
John Preston
66f73a5a64 Copy code blocks on header click. 2023-10-20 18:07:16 +04:00
John Preston
575684670c Improve quotes / code blocks geometries. 2023-10-20 17:59:34 +04:00
John Preston
9661bac876 Show blockquote icon. 2023-10-13 16:24:01 +04:00
John Preston
4b618a3578 Fix build with GCC. 2023-10-13 10:10:11 +04:00
John Preston
0e79bd3d12 Show nice padded code blocks. 2023-10-13 10:08:29 +04:00
John Preston
dd692f2d26 Use simplified TextStyle. 2023-10-13 10:08:29 +04:00
23rd
cba8387589 Fixed value types in struct data for drawing of charts. 2023-10-13 05:16:30 +03:00
23rd
01c2ade501 Fixed display of widget for point details on charts on retina. 2023-10-13 03:19:50 +03:00
23rd
b9fa14139a Fixed possible wrong range of clamp in footer of chart widget. 2023-10-13 03:19:50 +03:00
23rd
a8cb5419d6 Fixed available width for text in widget for details on pie charts. 2023-10-12 16:54:24 +03:00
23rd
f775670938 Moved out some classes for widgets in statistical info to directory. 2023-10-12 16:51:13 +03:00
23rd
c035a25aaa Added lottie icon to report box. 2023-10-12 04:42:27 +03:00
23rd
6cae088d1f Added ability to customize shortcut for full screen in media viewer. 2023-10-12 04:20:35 +03:00
23rd
bee0534052 Added emoji to poll preview. 2023-10-12 03:50:59 +03:00
John Preston
3e11d44cac Closed alpha version 4.10.3.1. 2023-10-11 22:12:35 +04:00
23rd
4d269f6e97 Added animation to pie chart while changing its parts. 2023-10-11 22:12:35 +04:00
23rd
e9496fb612 Improved concurrent API requests of async statistical charts. 2023-10-11 22:12:35 +04:00
23rd
c9c82446cb Added support of weekly range of days to chart views. 2023-10-11 22:12:35 +04:00
23rd
0dec803177 Fixed incorrect position of sliders in chart footer in some cases. 2023-10-11 22:12:35 +04:00
23rd
2dc45ac907 Added ability to restore first public forwards in statistical info. 2023-10-11 22:12:35 +04:00
23rd
a3d8db4ac0 Added ability to save state for recent posts in statistical info. 2023-10-11 22:12:35 +04:00
23rd
aee6b6e224 Fixed possible crash in loading of recent posts for statistical info. 2023-10-11 22:12:35 +04:00
23rd
736efd4692 Added ability to cache loaded chart data of async graphs. 2023-10-11 22:12:34 +04:00
23rd
ec5e846374 Added initial ability to save and restore state of statistical info. 2023-10-11 22:12:34 +04:00
23rd
caf32cccd3 Moved out inner widget of statistical info to separate class. 2023-10-11 22:12:34 +04:00
23rd
594b2bc8f2 Improved style of recent posts in statistical info. 2023-10-11 22:12:34 +04:00
23rd
d1ba270a8c Renamed file of utils for statistical lists. 2023-10-11 22:12:34 +04:00
23rd
2c1abd32bf Added list of members to statistical info of supergroups. 2023-10-11 22:12:34 +04:00
23rd
79662dffa4 Guarded cases when min and max values of charts are equal. 2023-10-11 22:12:34 +04:00
23rd
a79e025151 Slightly refactored code for info of statistic for single message. 2023-10-11 22:12:34 +04:00
23rd
3fa168cee0 Added API class for requesting full statistic of single message. 2023-10-11 22:12:34 +04:00
23rd
9c1ef76e49 Added overview info to statistic for single message. 2023-10-11 22:12:34 +04:00
23rd
8497b83f7c Added list of public forwards to statistics of single message. 2023-10-11 22:12:34 +04:00
23rd
393c23ad12 Added initial ability to open statistics for single message. 2023-10-11 22:12:34 +04:00
23rd
01821cd779 Added second type of info layer for statistics. 2023-10-11 22:12:34 +04:00
23rd
3da733520d Added API support to request list of public forwards for single message. 2023-10-11 22:12:34 +04:00
23rd
a605275157 Added icon to entry point for statistics. 2023-10-11 22:12:34 +04:00
23rd
8564e4d727 Added initial support of recent posts to statistical info. 2023-10-11 22:12:34 +04:00
23rd
fc3acff5d6 Added support of percentages display to details widget. 2023-10-11 22:12:34 +04:00
23rd
515850ec9b Decreased height of header for charts without dates. 2023-10-11 22:12:34 +04:00
23rd
837b256778 Added support of dark theme to statistical charts. 2023-10-11 22:12:34 +04:00
23rd
d16cab30d4 Fixed paint of rulers for stack chart view. 2023-10-11 22:12:34 +04:00
23rd
fcdd7ecd61 Fixed paint glitch of selected bar on stack chart view. 2023-10-11 22:12:34 +04:00
23rd
da9720530a Added ability to filter out lines from chart on demand from backend. 2023-10-11 22:12:34 +04:00
23rd
4a10d86a29 Fixed state losing in filter buttons on resize of statistics layer. 2023-10-11 22:12:34 +04:00
23rd
3b5a007db5 Added ability to hide footer of chart on demand from backend. 2023-10-11 22:12:34 +04:00
23rd
2479b56c3b Added ability to hide part of info on chart ruler when line is filtered. 2023-10-11 22:12:34 +04:00
23rd
0909e8bd08 Reduced line width in footer of charts. 2023-10-11 22:12:34 +04:00
23rd
6109ec70b8 Slightly improved format of dates on charts. 2023-10-11 22:12:34 +04:00
23rd
c20bd17029 Moved zoom out button to right side above chart. 2023-10-11 22:12:34 +04:00
23rd
10e3115c39 Improved style of line filter buttons under charts. 2023-10-11 22:12:34 +04:00
23rd
3425b40746 Improved style of widget for details of selected points on chart. 2023-10-11 22:12:34 +04:00
23rd
42fc4fbb31 Improved style of sliders in footer from charts. 2023-10-11 22:12:34 +04:00
23rd
f081917cd0 Improved style of rulers on charts in statistics. 2023-10-11 22:12:34 +04:00
23rd
bdfb0ffe04 Improved style of statistic overview. 2023-10-11 22:12:34 +04:00
23rd
2b282c8d7d Improved header style for charts. 2023-10-11 22:12:33 +04:00
23rd
77d23ad182 Replaced statistics box with info layer widget. 2023-10-11 22:12:33 +04:00
23rd
79442fde97 Fixed incorrect search of index by value in statistical chart data. 2023-10-11 22:12:33 +04:00
23rd
f8e80bc266 Improved limits of zoomed in slider in footer for stack linear chart. 2023-10-11 22:12:33 +04:00
23rd
cb4c629178 Slightly refactored code for statistical charts. 2023-10-11 22:12:33 +04:00
23rd
af0e11a1aa Moved out to td_ui all classes related to statistics. 2023-10-11 22:12:33 +04:00
23rd
8ac6aca315 Split out data for statistics and for charts. 2023-10-11 22:12:33 +04:00
23rd
2638e54181 Fixed available width for text in widget for point details on charts. 2023-10-11 22:12:33 +04:00
23rd
db97db4aba Fixed crash on closing statistics with locally zoomed in chart. 2023-10-11 22:12:33 +04:00
23rd
ded3f135bb Improved casting of limits for std::distance in stack linear chart view. 2023-10-11 22:12:33 +04:00
23rd
be82df72e6 Fixed possible crash in stack linear chart view. 2023-10-11 22:12:33 +04:00
23rd
3fa6335b24 Added API support to request statistical graph for single message. 2023-10-11 22:12:33 +04:00
23rd
23868bf9cc Added ability to hide charts without data in statistics box. 2023-10-11 22:12:33 +04:00
23rd
5b67f4ac9b Added overview info to statistic of supergroup. 2023-10-11 22:12:33 +04:00
23rd
17fdef7d9e Added chart widgets for statistic of supergroups. 2023-10-11 22:12:33 +04:00
23rd
cf82e12bf4 Added serialization from TL to statistics data for supergroups. 2023-10-11 22:12:33 +04:00
23rd
6f27aeef10 Added overview info to statistic of channels. 2023-10-11 22:12:33 +04:00
23rd
df53ddf837 Added all chart widgets for statistic of channels. 2023-10-11 22:12:33 +04:00
23rd
a3fd4f3fac Added label to box for statistic while loading. 2023-10-11 22:12:33 +04:00
23rd
24c0624704 Added support of default zoom to statistical charts. 2023-10-11 22:12:33 +04:00
23rd
33724be6ea Added support of theme colors for lines on statistical charts. 2023-10-11 22:12:33 +04:00
23rd
4624d34f68 Fixed display of outer points on linear chart. 2023-10-11 22:12:33 +04:00
23rd
aeee016dc9 Fixed animation of line filtering in stack linear chart view. 2023-10-11 22:12:33 +04:00
23rd
8ded88baf5 Moved out control of animations for line filtering to separated class. 2023-10-11 22:12:33 +04:00
23rd
bdd35a6e2b Added ability to handle mouse move to chart views. 2023-10-11 22:12:31 +04:00
23rd
d9a08bb6a6 Fixed smooth paint of stack linear chart on move of footer slider. 2023-10-11 22:12:31 +04:00
23rd
cee833f102 Fixed paint of selected X index in stack linear chart view. 2023-10-11 22:12:31 +04:00
23rd
c19a527872 Fixed position of zoomed in slider in footer for stack linear chart. 2023-10-11 22:12:31 +04:00
23rd
6995fcafb5 Fixed mouse selection of pie chart with single part. 2023-10-11 22:12:31 +04:00
23rd
b261d23645 Fixed display of zoomed days in pie chart view. 2023-10-11 22:12:31 +04:00
23rd
21c1ba7607 Fixed display of selected days in zoomed stack linear chart view. 2023-10-11 22:12:31 +04:00
23rd
5a2b8d06e3 Fixed paint of zoomed footer in stack linear chart view. 2023-10-11 22:12:31 +04:00
23rd
9046daa1a6 Fixed limit of days for zoom in stack linear chart view. 2023-10-11 22:12:31 +04:00
23rd
65ccb4059e Changed range of slider in footer from chart widget. 2023-10-11 22:12:31 +04:00
23rd
d2578e9e47 Added minimal size of pie part for text on pie chart view. 2023-10-11 22:12:31 +04:00
23rd
be17e2b919 Slightly refactored variables in stack linear chart view. 2023-10-11 22:12:31 +04:00
23rd
26c2e7f245 Improved round of percentages in pie chart view. 2023-10-11 22:12:31 +04:00
23rd
9051716172 Added initial support of sync zoom to chart widget. 2023-10-11 22:12:31 +04:00
23rd
71b6a58683 Added initial support of sync zoom of charts to stack linear chart view. 2023-10-11 22:12:31 +04:00
23rd
32cd454554 Moved out chart header to separated files. 2023-10-11 22:12:30 +04:00
23rd
6ffe555f6a Fixed animation processing after selecting part of pie chart. 2023-10-11 22:12:30 +04:00
23rd
7ac9ab3a51 Added support to hide chart elements with opacity from chart view. 2023-10-11 22:12:30 +04:00
23rd
a9b0464726 Moved out context for chart paint to separated structure. 2023-10-11 22:12:30 +04:00
23rd
e4e85e5a39 Added ability to move slider in chart widget footer from outside. 2023-10-11 22:12:30 +04:00
23rd
c5f294a1ac Added paint of footer for zoomed stack linear chart. 2023-10-11 22:12:30 +04:00
23rd
5dc078a3f8 Removed selection of last enabled part on pie chart. 2023-10-11 22:12:30 +04:00
23rd
83753343cb Added details popup to selected part of pie chart. 2023-10-11 22:12:30 +04:00
23rd
42215343cf Added ability to select part of pie chart. 2023-10-11 22:12:30 +04:00
23rd
bedefee1d1 Added initial appear animation of text to pie chart. 2023-10-11 22:12:30 +04:00
23rd
788eb014d4 Added ability to paint pie chart as zoomed stack linear chart. 2023-10-11 22:12:30 +04:00
23rd
13b7a07d2e Added initial transition animation to stack linear chart. 2023-10-11 22:12:30 +04:00
23rd
f026271436 Added initial implementation of stack linear chart. 2023-10-11 22:12:30 +04:00
23rd
d13fe39629 Added ability to paint horizontal lines for double linear charts. 2023-10-11 22:12:30 +04:00
23rd
62b3b60c45 Added initial support of double linear chart to view. 2023-10-11 22:12:30 +04:00
23rd
74313d23f3 Added new type of chart view for double linear charts. 2023-10-11 22:12:30 +04:00
23rd
646390141a Moved out paint of horizontal lines for charts to separated view class. 2023-10-11 22:12:30 +04:00
23rd
83cf12b475 Slightly reduced size of buttons in container to filter chart lines. 2023-10-11 22:12:30 +04:00
23rd
a0226f9789 Added support of different chart types in chart widget. 2023-10-11 22:12:30 +04:00
23rd
54fecd497e Added icon to widget for point details on chart when zoom is enabled. 2023-10-11 22:12:30 +04:00
23rd
2106747496 Decreased size of widget for point details on chart. 2023-10-11 22:12:30 +04:00
23rd
ecce9dbaaa Added ability to hide point details on chart by second click. 2023-10-11 22:12:30 +04:00
23rd
b606a7b21d Added initial animation and selection to stack chart view. 2023-10-11 22:12:30 +04:00
23rd
35ff45971f Added support of async charts for main graphs. 2023-10-11 22:12:30 +04:00
23rd
7a436f32dd Moved out search of clicked index on chart to chart view. 2023-10-11 22:12:30 +04:00
23rd
54d5358b75 Fixed position of selected X in linear chart animations. 2023-10-11 22:12:30 +04:00
23rd
20c2250abb Added initial implementation of painting of stack chart. 2023-10-11 22:12:30 +04:00
23rd
2ddc1ee2e1 Implemented calculation of chart height in stack chart view. 2023-10-11 22:12:30 +04:00
23rd
b55d2008c0 Added dummy class for stack chart view. 2023-10-11 22:12:30 +04:00
23rd
11b932707c Moved out calculation of height limits to abstract chart view class. 2023-10-11 22:12:30 +04:00
23rd
d50aca0d33 Created abstract chart view class. 2023-10-11 22:12:30 +04:00
23rd
671e81033c Moved files of linear chart view class to directory. 2023-10-11 22:12:30 +04:00
23rd
361d269bf3 Added support of custom header in chart widget with zoomed chart. 2023-10-11 22:12:30 +04:00
23rd
ae81373cff Slightly improved display management of delayed widgets in chart widget. 2023-10-11 22:12:30 +04:00
23rd
160794b26c Added support of chart titles to Data and API classes for statistics. 2023-10-11 22:12:30 +04:00
23rd
1dc57afbe1 Added some phrases for chart titles in chart widget. 2023-10-11 22:12:30 +04:00
23rd
d9f397ea3f Added display of toast with potential human-readable errors from server. 2023-10-11 22:12:30 +04:00
23rd
c9a976bf87 Added initial support of zooming single chart to chart widget. 2023-10-11 22:12:30 +04:00
23rd
7b921dea3b Replaced bad sizeValue with intended resizeGetHeight in chart widget. 2023-10-11 22:12:30 +04:00
23rd
fcc6aaed91 Added mouse click support to point details widget. 2023-10-11 22:12:30 +04:00
23rd
eb0ab9609f Added API support for request of async zoom single chart. 2023-10-11 22:12:30 +04:00
23rd
bb359f6493 Fixed crash in charts with equal minimum and maximum values. 2023-10-11 22:12:30 +04:00
23rd
b24be50afe Improved format of timestamp in point details widget. 2023-10-11 22:12:30 +04:00
23rd
3e55380eed Returned support of detail dots to linear chart view class. 2023-10-11 22:12:30 +04:00
23rd
25c97a3ee8 Cached both main and footer charts in single linear chart view. 2023-10-11 22:12:30 +04:00
23rd
2055cc70d1 Removed chart line view context. 2023-10-11 22:12:30 +04:00
23rd
788a81df6c Removed some duplicated code from chart line view context. 2023-10-11 22:12:30 +04:00
23rd
1209bd35c5 Replaced static function for linear chart paint with dedicated class. 2023-10-11 22:12:30 +04:00
23rd
f473a1a804 Attempted to increase performance of chart paint by caching every frame. 2023-10-11 22:12:30 +04:00
23rd
c8e95f7297 Improved y-axis animation again to look much better. 2023-10-11 22:12:30 +04:00
23rd
b1ed8cd1b1 Fixed bug of display for y-axis captions with instant delivered data. 2023-10-11 22:12:30 +04:00
23rd
64bb818fe9 Added initial display of footer while chart lines are filtering. 2023-10-11 22:12:29 +04:00
23rd
ee172d951d Added support to hide name and value of line in PointDetailsWidget. 2023-10-11 22:12:29 +04:00
23rd
41bc47eb6f Faded out detail dot of filtered chart line. 2023-10-11 22:12:29 +04:00
23rd
423d2293f9 Kept point details widget while chart lines are filtering. 2023-10-11 22:12:29 +04:00
23rd
e6559276c0 Added class to calculate context state while chart lines are filtering. 2023-10-11 22:12:29 +04:00
23rd
13959ca36c Added buttons container to filter chart line to chart widget. 2023-10-11 22:12:29 +04:00
23rd
520989a7e6 Added initial ability to filter chart lines. 2023-10-11 22:12:29 +04:00
23rd
4c02d19a51 Added implementation of buttons container to filter chart lines. 2023-10-11 22:12:29 +04:00
23rd
734e1166ad Removed from display point details widget with invalid index. 2023-10-11 22:12:29 +04:00
23rd
94fd3e32dd Added own value of height for chart widget. 2023-10-11 22:12:29 +04:00
23rd
367adaa44d Attempted to increase performance when paint complicated charts. 2023-10-11 22:12:29 +04:00
23rd
73b4621121 Cached current x-axis indices to reduce redundant calculations. 2023-10-11 22:12:29 +04:00
23rd
8256a4c686 Completely replaced widgets in footer with nice path paint. 2023-10-11 22:12:29 +04:00
23rd
b6b6673214 Initially replaced buttons in footer with cached arrows. 2023-10-11 22:12:29 +04:00
23rd
8ba2e95e6c Added paint of inactive area in footer in chart widget. 2023-10-11 22:12:29 +04:00
23rd
40ab042fb5 Added support of very large values in PointDetailsWidget. 2023-10-11 22:12:29 +04:00
23rd
25f401c22e Changed color of axis captions to grey. 2023-10-11 22:12:29 +04:00
23rd
d8566f770f Removed chart paint below bottom line in chart widget. 2023-10-11 22:12:29 +04:00
23rd
f76f69b5cd Moved out details dots above horizontal line captions. 2023-10-11 22:12:29 +04:00
23rd
487dd27ca1 Added padding to y-axis captions in chart widget. 2023-10-11 22:12:29 +04:00
23rd
32df03f08d Fixed text overlap on y-axis captions when mouse drag is really fast. 2023-10-11 22:12:29 +04:00
23rd
658db59aaf Replace Simple with Basic for animation of chart y-axis captions. 2023-10-11 22:12:29 +04:00
23rd
695542cfd2 Added initial animation of chart y-axis captions. 2023-10-11 22:12:29 +04:00
23rd
07cd35b1a8 Added fade animation to details widget on charts. 2023-10-11 22:12:29 +04:00
23rd
74aae29b64 Added point details widget to chart widget. 2023-10-11 22:12:29 +04:00
23rd
70713d5f62 Moved paint of chart to inner widget within chart widget. 2023-10-11 22:12:29 +04:00
23rd
dd1b006d8a Created RpMouseWidget class for easier mouse processing. 2023-10-11 22:12:29 +04:00
23rd
ec8d604db7 Added initial widget implementation of point details in charts. 2023-10-11 22:12:29 +04:00
23rd
9e8d60065b Slightly improved code style in PaintLinearChartView. 2023-10-11 22:12:29 +04:00
23rd
d603f4de51 Reduced redundant calculations of animation in chart widget. 2023-10-11 22:12:29 +04:00
23rd
77695091b3 Slightly clarified name of y-axis animation in chart widget. 2023-10-11 22:12:29 +04:00
23rd
20e81177a6 Fixed first show of chart widget with new data. 2023-10-11 22:12:29 +04:00
23rd
ce3ad95950 Removed useless processing for changing drag direction. 2023-10-11 22:12:29 +04:00
23rd
c5684e768a Slightly optimized footer in chart widget. 2023-10-11 22:12:29 +04:00
23rd
c8d5a60c74 Moved out processing of chart animation to separate class. 2023-10-11 22:12:29 +04:00
23rd
4dad0a215a Removed some unused data chart widget. 2023-10-11 22:12:29 +04:00
23rd
15698fd6f0 Tried to fix animation bug when mouse drag is really fast. 2023-10-11 22:12:29 +04:00
23rd
cd4654dfd2 Tried to fix some bugs with alpha of horizontal lines in chart widget. 2023-10-11 22:12:29 +04:00
23rd
3a3d4480cc Added acceleration to y-axis animation in chart widget. 2023-10-11 22:12:29 +04:00
23rd
5c3748db56 Added tools to test animation for left and right edges with same speed. 2023-10-11 22:12:29 +04:00
23rd
7dfdcc7be0 Added some work in progress to improve horizontal line animation. 2023-10-11 22:12:29 +04:00
23rd
d1f2950167 Added initial animation of horizontal lines in chart widget.
The animation is only top-down for now.
2023-10-11 22:12:29 +04:00
23rd
59f61586a9 Improved code style in chart widget. 2023-10-11 22:12:29 +04:00
23rd
629dd6f9de Added dirty implementation of chart animation with both axes. 2023-10-11 22:12:29 +04:00
23rd
f4fc8ec2c4 Added initial chart y-axis animation without x-axis. 2023-10-11 22:12:29 +04:00
23rd
e8aa55d4d8 Added initial support of static zoom for chart widget. 2023-10-11 22:12:29 +04:00
23rd
26b17325aa Added initial implementation of footer in statistic chart widget. 2023-10-11 22:12:29 +04:00
23rd
c71f35778d Added API support of channel flag for channel statistics. 2023-10-11 22:12:29 +04:00
23rd
c9eb9a3ee0 Added initial widget with full zoom static linear chart. 2023-10-11 22:12:29 +04:00
23rd
06948ad15e Added Data class for horizontal lines on statistic charts. 2023-10-11 22:12:29 +04:00
23rd
029e0c9488 Added deserialization from JSON to statistics data to API. 2023-10-11 22:12:29 +04:00
23rd
c0219cb95d Added deserialization from JSON to statistics data. 2023-10-11 22:12:29 +04:00
23rd
78e553b724 Added usage of segment tree in Data class for statistics. 2023-10-11 22:12:28 +04:00
23rd
b5b70beea0 Added implementation of class for segment tree. 2023-10-11 22:12:28 +04:00
23rd
177a7eaf43 Added initial serialization from TL data to statistics data. 2023-10-11 22:12:28 +04:00
23rd
ca863bfb5b Added dummy Data class for statistics. 2023-10-11 22:12:28 +04:00
23rd
c45025c6e5 Added dummy API class for statistics. 2023-10-11 22:12:28 +04:00
23rd
10968d0da2 Added dummy box class for statistics. 2023-10-11 22:12:28 +04:00
23rd
b0a65885c9 Added initial entry point for channel statistics. 2023-10-11 22:12:28 +04:00
John Preston
ad8f8513c3 Link crashpad_handler with Xcode bug workaround. 2023-10-11 22:12:28 +04:00
23rd
f457a9d109 Added ability to fast change forward options with right click on panel. 2023-10-11 21:10:56 +03:00
Ilya Fedin
830fb3ccc2 Update submodules 2023-10-11 21:54:42 +04:00
Ilya Fedin
9116328f29 Update to Qt 6.6.0 release on Linux 2023-10-11 21:54:42 +04:00
John Preston
aa7575dec4 Highlight more languages. 2023-10-11 08:45:50 +04:00
John Preston
bfe272e39f Fix highlighting of the closing bracket. 2023-10-11 08:45:50 +04:00
23rd
a7ca15657b Fixed master branch updater Github Action. 2023-10-11 06:06:03 +03:00
23rd
41dada2c06 Fixed shadows in userpic builder with non-default scale. 2023-10-11 06:06:03 +03:00
John Preston
501784cd15 Attempt to fix Snap build. 2023-10-08 07:02:40 +04:00
Ilya Fedin
ac699ccf80 Update submodules 2023-10-07 07:08:21 +04:00
Ilya Fedin
aadaf47569 Add boost-regex to snap 2023-10-07 07:08:21 +04:00
Ilya Fedin
6bc0179919 Work with GLIB_VERSION_MAX_ALLOWED 2023-10-07 07:08:21 +04:00
Ilya Fedin
93fbad50bc Downgrade qtwayland to 6.5.3 2023-10-07 07:08:21 +04:00
Ilya Fedin
a5ec616382 Downgrade qtsvg to 6.5.3 in snap
As a better crash workaround
2023-10-07 07:08:21 +04:00
John Preston
bf3f474195 Fix label position in peer editing. 2023-10-06 17:49:34 +04:00
John Preston
14f68c2f33 Fix webview on Windows & macOS. 2023-10-06 17:49:34 +04:00
John Preston
92fadd2652 Fix build with GCC 12. 2023-10-06 16:28:45 +04:00
John Preston
b68a7c7f04 Update submodules. 2023-10-06 09:03:28 +04:00
John Preston
50097e1a81 Show spellcheck suggestions on Ctrl+Space.
Fixes #26892.
2023-10-05 10:36:30 +04:00
John Preston
2414e927bd Add initial code syntax highlighting.
Thanks PrismJS and Fela for porting it to C++.
2023-10-04 22:29:16 +04:00
John Preston
da768ac1d1 Add libprisma from Fela for syntax highlighting. 2023-10-04 22:28:47 +04:00
John Preston
396c229a4d Improve Ui::Text::String features. 2023-10-04 22:24:25 +04:00
23rd
2b3f17e982 Removed call button from history with service user. 2023-10-04 20:38:14 +04:00
John Preston
5ef48cac9c Use shared_ptr<Factory> as settings section id. 2023-10-04 20:38:14 +04:00
John Preston
6ba922d7b0 Fix channel stories open from chats list. 2023-10-04 20:38:14 +04:00
Ilya Fedin
f881192dd0 Don't enter settings after update 2023-10-04 12:10:33 +04:00
Ilya Fedin
ef2a0bb05e Add libnvidia-egl-wayland1 to snap 2023-10-04 12:10:19 +04:00
Ilya Fedin
54efa2353e Update submodules 2023-10-04 12:07:36 +04:00
Ilya Fedin
2878533078 Re-throw original exception in Linux notification's StartServiceAsync 2023-10-04 12:07:36 +04:00
Sergey A. Osokin
ac520b314d Fix build on FreeBSD 2023-10-02 18:47:10 +04:00
John Preston
871fef2c4a Version 4.10.3.
- Fix crash on external link opening. (Linux only)
2023-10-02 17:56:10 +04:00
John Preston
99f4b93745 Attempt to fix build with Clang on Linux. 2023-10-02 17:56:10 +04:00
Ilya Fedin
a757e07c3a Line length clean up in notifications_manager_linux 2023-10-02 16:11:02 +04:00
Ilya Fedin
5c4f006550 Avoid Windows-specific hack to ruin initial main window geometry on Linux 2023-10-02 07:05:21 +04:00
John Preston
9ad38b9638 Update lib_base submodule. 2023-10-01 21:54:44 +04:00
Ilya Fedin
fe8ebc1659 Update patches on Linux 2023-10-01 07:25:58 +04:00
Ilya Fedin
a732d8f5e7 Disable vfork in Qt on Linux 2023-10-01 07:25:58 +04:00
Ilya Fedin
7ddcc47fcd Make UnsafeShowOpenWith inline on Linux 2023-10-01 06:54:39 +04:00
Ilya Fedin
70f22293cf Get rid of last non-standard piece in Linux FileDialog getter 2023-10-01 06:54:39 +04:00
John Preston
90fb59348c Try fixing Docker action. 2023-09-29 20:38:21 +04:00
410 changed files with 22574 additions and 4059 deletions

View File

@@ -30,7 +30,7 @@ jobs:
curl -sSL https://install.python-poetry.org | python3 -
- name: Free up some disk space.
uses: jlumbroso/free-disk-space@76866dbe54312617f00798d1762df7f43def6e5c
uses: jlumbroso/free-disk-space@f68fdb76e2ea636224182cfb7377ff9a1708f9b8
- name: Docker image build.
run: |

View File

@@ -11,7 +11,9 @@ jobs:
SKIP: "0"
to_branch: "master"
steps:
- uses: actions/checkout@v3.1.0
- uses: actions/checkout@v4.1.0
with:
fetch-depth: 0
if: env.SKIP == '0'
- name: Push the code to the master branch.
if: env.SKIP == '0'

3
.gitmodules vendored
View File

@@ -100,3 +100,6 @@
[submodule "Telegram/ThirdParty/wayland"]
path = Telegram/ThirdParty/wayland
url = https://github.com/gitlab-freedesktop-mirrors/wayland.git
[submodule "Telegram/ThirdParty/libprisma"]
path = Telegram/ThirdParty/libprisma
url = https://github.com/desktop-app/libprisma.git

View File

@@ -28,6 +28,7 @@ include(cmake/lib_ffmpeg.cmake)
include(cmake/lib_stripe.cmake)
include(cmake/lib_tgvoip.cmake)
include(cmake/lib_tgcalls.cmake)
include(cmake/lib_prisma.cmake)
include(cmake/td_export.cmake)
include(cmake/td_mtproto.cmake)
include(cmake/td_lang.cmake)
@@ -151,6 +152,8 @@ PRIVATE
api/api_sensitive_content.h
api/api_single_message_search.cpp
api/api_single_message_search.h
api/api_statistics.cpp
api/api_statistics.h
api/api_text_entities.cpp
api/api_text_entities.h
api/api_toggling_media.cpp
@@ -195,6 +198,8 @@ PRIVATE
boxes/peers/edit_participant_box.h
boxes/peers/edit_participants_box.cpp
boxes/peers/edit_participants_box.h
boxes/peers/edit_peer_color_box.cpp
boxes/peers/edit_peer_color_box.h
boxes/peers/edit_peer_common.h
boxes/peers/edit_peer_info_box.cpp
boxes/peers/edit_peer_info_box.h
@@ -450,6 +455,7 @@ PRIVATE
data/data_audio_msg_id.h
data/data_auto_download.cpp
data/data_auto_download.h
data/data_boosts.h
data/data_bot_app.cpp
data/data_bot_app.h
data/data_chat.cpp
@@ -552,6 +558,7 @@ PRIVATE
data/data_sparse_ids.h
data/data_sponsored_messages.cpp
data/data_sponsored_messages.h
data/data_statistics.h
data/data_stories.cpp
data/data_stories.h
data/data_stories_ids.cpp
@@ -646,6 +653,8 @@ PRIVATE
history/view/controls/history_view_compose_controls.h
history/view/controls/history_view_compose_search.cpp
history/view/controls/history_view_compose_search.h
history/view/controls/history_view_draft_options.cpp
history/view/controls/history_view_draft_options.h
history/view/controls/history_view_forward_panel.cpp
history/view/controls/history_view_forward_panel.h
history/view/controls/history_view_ttl_button.cpp
@@ -654,6 +663,8 @@ PRIVATE
history/view/controls/history_view_voice_record_bar.h
history/view/controls/history_view_voice_record_button.cpp
history/view/controls/history_view_voice_record_button.h
history/view/controls/history_view_webpage_processor.cpp
history/view/controls/history_view_webpage_processor.h
history/view/media/history_view_call.cpp
history/view/media/history_view_call.h
history/view/media/history_view_contact.cpp
@@ -672,6 +683,8 @@ PRIVATE
history/view/media/history_view_game.h
history/view/media/history_view_gif.cpp
history/view/media/history_view_gif.h
history/view/media/history_view_giveaway.cpp
history/view/media/history_view_giveaway.h
history/view/media/history_view_invoice.cpp
history/view/media/history_view_invoice.h
history/view/media/history_view_large_emoji.cpp
@@ -811,20 +824,10 @@ PRIVATE
history/history_view_highlight_manager.h
history/history_widget.cpp
history/history_widget.h
info/info_content_widget.cpp
info/info_content_widget.h
info/info_controller.cpp
info/info_controller.h
info/info_layer_widget.cpp
info/info_layer_widget.h
info/info_memento.cpp
info/info_memento.h
info/info_section_widget.cpp
info/info_section_widget.h
info/info_top_bar.cpp
info/info_top_bar.h
info/info_wrap_widget.cpp
info/info_wrap_widget.h
info/boosts/info_boosts_inner_widget.cpp
info/boosts/info_boosts_inner_widget.h
info/boosts/info_boosts_widget.cpp
info/boosts/info_boosts_widget.h
info/common_groups/info_common_groups_inner_widget.cpp
info/common_groups/info_common_groups_inner_widget.h
info/common_groups/info_common_groups_widget.cpp
@@ -880,6 +883,15 @@ PRIVATE
info/profile/info_profile_widget.h
info/settings/info_settings_widget.cpp
info/settings/info_settings_widget.h
info/statistics/info_statistics_common.h
info/statistics/info_statistics_inner_widget.cpp
info/statistics/info_statistics_inner_widget.h
info/statistics/info_statistics_list_controllers.cpp
info/statistics/info_statistics_list_controllers.h
info/statistics/info_statistics_recent_message.cpp
info/statistics/info_statistics_recent_message.h
info/statistics/info_statistics_widget.cpp
info/statistics/info_statistics_widget.h
info/stories/info_stories_inner_widget.cpp
info/stories/info_stories_inner_widget.h
info/stories/info_stories_provider.cpp
@@ -898,6 +910,20 @@ PRIVATE
info/userpic/info_userpic_emoji_builder_preview.h
info/userpic/info_userpic_emoji_builder_widget.cpp
info/userpic/info_userpic_emoji_builder_widget.h
info/info_content_widget.cpp
info/info_content_widget.h
info/info_controller.cpp
info/info_controller.h
info/info_layer_widget.cpp
info/info_layer_widget.h
info/info_memento.cpp
info/info_memento.h
info/info_section_widget.cpp
info/info_section_widget.h
info/info_top_bar.cpp
info/info_top_bar.h
info/info_wrap_widget.cpp
info/info_wrap_widget.h
inline_bots/bot_attach_web_view.cpp
inline_bots/bot_attach_web_view.h
inline_bots/inline_bot_layout_internal.cpp
@@ -1539,6 +1565,7 @@ elseif (APPLE)
PRE_LINK
COMMAND mkdir -p $<TARGET_FILE_DIR:Telegram>/../Resources
COMMAND cp ${CMAKE_BINARY_DIR}/lib_ui.rcc $<TARGET_FILE_DIR:Telegram>/../Resources
COMMAND cp ${CMAKE_BINARY_DIR}/lib_spellcheck.rcc $<TARGET_FILE_DIR:Telegram>/../Resources
)
if (NOT build_macstore)
add_custom_command(TARGET Telegram

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 324 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 484 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 623 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 589 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 450 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 725 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 454 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 761 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1017 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 582 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 965 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 542 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 991 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 548 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 938 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@@ -129,6 +129,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_reconnecting#other" = "Reconnect in {count} s...";
"lng_reconnecting_try_now" = "Try now";
"lng_code_block_header_copy" = "copy";
"lng_status_service_notifications" = "service notifications";
"lng_status_support" = "support";
"lng_status_bot" = "bot";
@@ -402,6 +404,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_dlg_search_from" = "From: {user}";
"lng_settings_save" = "Save";
"lng_settings_apply" = "Apply";
"lng_username_title" = "Username";
"lng_username_description1" = "You can choose a username on Telegram. If you do, other people will be able to find you by this username and contact you without knowing your phone number.";
@@ -543,6 +546,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_settings_workmode_tray" = "Show tray icon";
"lng_settings_workmode_window" = "Show taskbar icon";
"lng_settings_close_to_taskbar" = "Close to taskbar";
"lng_settings_monochrome_icon" = "Use monochrome icon";
"lng_settings_window_system" = "Window title";
"lng_settings_title_chat_name" = "Show chat name";
"lng_settings_title_account_name" = "Show active account";
@@ -770,11 +774,33 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_clear_payment_info_clear" = "Clear";
"lng_clear_payment_info_confirm" = "Delete your shipping info and instruct all payment providers to remove your saved credit cards? Note that Telegram never stores your credit card data.";
"lng_settings_theme_settings" = "Theme settings";
"lng_settings_theme_name_color" = "Your name color";
"lng_settings_auto_night_mode" = "Auto-Night mode";
"lng_settings_auto_night_enabled" = "Match the system settings";
"lng_settings_auto_night_mode_off" = "Off";
"lng_settings_auto_night_mode_on" = "System";
"lng_settings_auto_night_warning" = "You have enabled auto-night mode. If you want to change the dark mode settings, you'll need to disable it first.";
"lng_settings_auto_night_disable" = "Disable";
"lng_settings_color_title" = "Color preview";
"lng_settings_color_reply" = "Reply to your message";
"lng_settings_color_reply_channel" = "Reply to your channel message";
"lng_settings_color_text" = "Your name and replies to your messages will be shown in the selected color.";
"lng_settings_color_text_channel" = "The name of the channel and replies to its messages will be shown in the selected color.";
"lng_settings_color_link_name" = "Telegram";
"lng_settings_color_link_title" = "Link Preview";
"lng_settings_color_link_description" = "Your selected color will also tint the link preview.";
"lng_settings_color_about" = "You can choose a color to tint your name, the links you send, and replies to your messages.";
"lng_settings_color_about_channel" = "You can choose a color to tint your channel's name, the links it sends, and replies to its messages.";
"lng_settings_color_emoji" = "Add icons to replies";
"lng_settings_color_emoji_remove" = "Remove icon";
"lng_settings_color_emoji_off" = "Off";
"lng_settings_color_emoji_about" = "Make replies to your messages stand out by adding custom patterns to them.";
"lng_settings_color_emoji_about_channel" = "Make replies to your channel's messages stand out by adding custom patterns to them.";
"lng_settings_color_subscribe" = "Subscribe to {link} to choose a custom color for your name.";
"lng_settings_color_changed" = "Your name color has been updated!";
"lng_settings_color_changed_channel" = "Your channel color has been updated!";
"lng_suggest_hide_new_title" = "Hide new chats?";
"lng_suggest_hide_new_about" = "You are receiving lots of new chats from users who are not in your Contact List.\n\nDo you want to have such chats **automatically muted** and **archived**?";
"lng_suggest_hide_new_to_settings" = "Go to Settings";
@@ -802,7 +828,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_background_text1" = "Ah, you kids today with techno music! You should enjoy the classics, like Hasselhoff!";
"lng_background_text2" = "I can't even take you seriously right now.";
"lng_background_bad_link" = "This background link appears to be invalid.";
"lng_background_apply" = "Apply";
"lng_background_share" = "Share";
"lng_background_link_copied" = "Link copied to clipboard";
"lng_background_blur" = "Blurred";
@@ -1142,6 +1167,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_mute_box_title" = "Mute notifications for...";
"lng_preview_loading" = "Getting Link Info...";
"lng_preview_cant" = "Could not generate preview for this link.";
"lng_profile_settings_section" = "Settings";
"lng_profile_bot_settings" = "Bot Settings";
@@ -1640,6 +1666,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_action_story_mention_button" = "View Story";
"lng_action_story_mention_me_unavailable" = "The story where you mentioned {user} is no longer available.";
"lng_action_story_mention_unavailable" = "The story where {user} mentioned you is no longer available.";
"lng_action_giveaway_started" = "{from} just started a giveaway of Telegram Premium subscriptions to its followers.";
"lng_premium_gift_duration_months#one" = "for {count} month";
"lng_premium_gift_duration_months#other" = "for {count} months";
@@ -2034,6 +2061,129 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_boost_now_instead" = "You currently boost {channel}. Do you want to boost {other} instead?";
"lng_boost_now_replace" = "Replace";
"lng_boost_channel_title_color" = "Enable colors";
"lng_boost_channel_needs_level_color#one" = "Your channel needs to reach **Level {count}** to change channel color.";
"lng_boost_channel_needs_level_color#other" = "Your channel needs to reach **Level {count}** to change channel color.";
"lng_boost_channel_ask" = "Ask your **Premium** subscribers to boost your channel with this link:";
"lng_boost_channel_ask_button" = "Copy Link";
"lng_boost_channel_or" = "or";
"lng_boost_channel_gifting" = "Boost your channel by gifting your subscribers Telegram Premium. {link}";
"lng_boost_channel_gifting_link" = "Get boosts >";
"lng_giveaway_new_title" = "Boosts via Gifts";
"lng_giveaway_new_about" = "Get more boosts for your channel by gifting Premium to your subscribers.";
"lng_giveaway_create_option" = "Create Giveaway";
"lng_giveaway_create_subtitle" = "winners are chosen randomly";
"lng_giveaway_award_option" = "Award Specific Users";
"lng_giveaway_award_subtitle" = "Select recipients >";
"lng_giveaway_award_chosen#one" = "{count} recipient >";
"lng_giveaway_award_chosen#other" = "{count} recipients >";
"lng_giveaway_quantity_title" = "Quantity of prizes / boosts";
"lng_giveaway_quantity#one" = "{count} Subscription / Boost";
"lng_giveaway_quantity#other" = "{count} Subscriptions / Boosts";
"lng_giveaway_quantity_about" = "Choose how many Premium subscriptions to give away and boosts to receive.";
"lng_giveaway_channels_title" = "Channels included in the giveaway";
"lng_giveaway_channels_this#one" = "this channel will receive {count} boost";
"lng_giveaway_channels_this#other" = "this channel will receive {count} boosts";
"lng_giveaway_channels_add" = "Add Channel";
"lng_giveaway_channels_about" = "Choose the channels the users need to join to take part in the giveaway.";
"lng_giveaway_users_title" = "Users eligible for the giveaway";
"lng_giveaway_users_all" = "All subscribers";
"lng_giveaway_users_new" = "Only new subscribers";
"lng_giveaway_users_about" = "Choose if you want to limit the giveaway only to the newly joined subscribers.";
"lng_giveaway_start" = "Start Giveaway";
"lng_giveaway_award" = "Gift Premium";
"lng_giveaway_date_title" = "Date when giveaway ends";
"lng_giveaway_date" = "Date and Time";
"lng_giveaway_date_about#one" = "Choose when {count} subscriber of your channel will be randomly selected to receive Telegram Premium.";
"lng_giveaway_date_about#other" = "Choose when {count} subscribers of your channel will be randomly selected to receive Telegram Premium.";
"lng_giveaway_duration_title#one" = "Duration of Premium subscription";
"lng_giveaway_duration_title#other" = "Duration of Premium subscriptions";
"lng_giveaway_duration_price" = "{price} x {amount}";
"lng_giveaway_duration_about" = "You can review the list of features and terms of use for Telegram Premium {link}.";
"lng_giveaway_duration_about_link" = "here";
"lng_giveaway_date_select" = "Select Date and Time";
"lng_giveaway_date_confirm" = "Confirm";
"lng_giveaway_channels_select#one" = "Select up to {count} channel";
"lng_giveaway_channels_select#other" = "Select up to {count} channels";
"lng_giveaway_recipients_save" = "Save Recipients";
"lng_giveaway_recipients_deselect" = "Deselect All";
"lng_prize_title" = "Congratulations!";
"lng_prize_about" = "You won a prize in a giveaway organized by {channel}.";
"lng_prize_duration" = "Your prize is a **Telegram Premium** subscription {duration}.";
"lng_prize_gift_about" = "You've received a gift from {channel}.";
"lng_prize_gift_duration" = "Your gift is a **Telegram Premium** subscription {duration}.";
"lng_prize_open" = "Open Gift Link";
"lng_prize_unclaimed_title" = "Unclaimed Prize";
"lng_prize_unclaimed_about" = "You have an unclaimed prize from a giveaway by {channel}.";
"lng_prize_unclaimed_duration" = "This prize is a **Telegram Premium** subscription {duration}.";
"lng_prizes_title#one" = "Giveaway Prize";
"lng_prizes_title#other" = "Giveaway Prizes";
"lng_prizes_about#one" = "**{count}** Telegram Premium Subscription {duration}.";
"lng_prizes_about#other" = "**{count}** Telegram Premium Subscriptions {duration}.";
"lng_prizes_participants" = "Participants";
"lng_prizes_participants_all#one" = "All subscribers of the channel:";
"lng_prizes_participants_all#other" = "All subscribers of the channels:";
"lng_prizes_participants_new#one" = "All users who joined the channel below after this date:";
"lng_prizes_participants_new#other" = "All users who joined the channels below after this date:";
"lng_prizes_countries" = "from {countries}";
"lng_prizes_countries_and_one" = "{countries}, {country}";
"lng_prizes_countries_and_last" = "{countries} and {country}";
"lng_prizes_date" = "Winners Selection Date";
"lng_prizes_how_works" = "Learn more";
"lng_prizes_how_title" = "About this giveaway";
"lng_prizes_end_title" = "Giveaway ended";
"lng_prizes_how_text" = "This giveaway is sponsored by {admins}.";
"lng_prizes_end_text" = "This giveaway was sponsored by {admins}.";
"lng_prizes_admins#one" = "the admins of {channel}, who aquired **{count} Telegram Premium** subscription {duration} for its followers";
"lng_prizes_admins#other" = "the admins of {channel}, who aquired **{count} Telegram Premium** subscriptions {duration} for its followers.";
"lng_prizes_how_when_finish" = "On {date}, Telegram will automatically select {winners}.";
"lng_prizes_end_when_finish" = "On {date}, Telegram automatically selected {winners}.";
"lng_prizes_end_activated#one" = "**{count}** of the winners already used their gift link.";
"lng_prizes_end_activated#other" = "**{count}** of the winners already used their gift links.";
"lng_prizes_winners_all_of_one#one" = "{count} random subscribers of {channel}.";
"lng_prizes_winners_all_of_one#other" = "{count} random subscribers of {channel}.";
"lng_prizes_winners_all_of_many#one" = "{count} random subscribers of {channel} and other listed channels.";
"lng_prizes_winners_all_of_many#other" = "{count} random subscribers of {channel} and other listed channels.";
"lng_prizes_winners_new_of_one#one" = "{count} random user that joined {channel} after {start_date}";
"lng_prizes_winners_new_of_one#other" = "{count} random users that joined {channel} after {start_date}";
"lng_prizes_winners_new_of_many#one" = "{count} random user that joined {channel} and other listed channels after {start_date}";
"lng_prizes_winners_new_of_many#other" = "{count} random users that joined {channel} and other listed channels after {start_date}";
"lng_prizes_how_participate_one" = "To take part in this giveaway please join channel {channel} before {date}.";
"lng_prizes_how_participate_many" = "To take part in this giveaway please join channel {channel} and other listed channels before {date}.";
"lng_prizes_how_no_admin" = "You are not eligible to participate in this giveaway, because you are an admin of participating channel ({channel}).";
"lng_prizes_how_no_joined" = "You are not eligible to participate in this giveaway, because you joined this channel on {date}, which is before the contest started.";
"lng_prizes_how_no_country" = "You are not eligible to participate in this giveaway, because your country is not included in the terms of the giveaway.";
"lng_prizes_how_yes_joined_one" = "You are participating in this giveaway, because you have joined channel {channel}.";
"lng_prizes_how_yes_joined_many" = "You are participating in this giveaway, because you have joined channel {channel} (and other listed channels).";
"lng_prizes_you_won" = "You won a prize in this giveaway {cup}";
"lng_prizes_view_prize" = "View my prize";
"lng_prizes_you_didnt" = "You didn't win a prize in this giveaway.";
"lng_prizes_cancelled" = "The channel cancelled the prizes by reversing the payment for them.";
"lng_prizes_badge" = "x{amount}";
"lng_gift_link_title" = "Gift Link";
"lng_gift_link_about" = "This link allows you to activate\na **Telegram Premium** subscription.";
"lng_gift_link_label_from" = "From";
"lng_gift_link_label_to" = "To";
"lng_gift_link_label_to_unclaimed" = "No recipient";
"lng_gift_link_label_gift" = "Gift";
"lng_gift_link_gift_premium" = "Telegram Premium {duration}";
"lng_gift_link_label_reason" = "Reason";
"lng_gift_link_reason_giveaway" = "Giveaway";
"lng_gift_link_reason_unclaimed" = "Incomplete Giveaway";
"lng_gift_link_reason_chosen" = "You were selected by the channel";
"lng_gift_link_label_date" = "Date";
"lng_gift_link_also_send" = "You can also {link} to a friend as a gift.";
"lng_gift_link_also_send_link" = "send this link";
"lng_gift_link_use" = "Use Link";
"lng_gift_link_used_title" = "Used Gift Link";
"lng_gift_link_used_about" = "This link was used to activate\na **Telegram Premium** subscription.";
"lng_gift_link_used_footer" = "This link was used on {date}.";
"lng_gift_link_expired" = "Gift code link expired";
"lng_accounts_limit_title" = "Limit Reached";
"lng_accounts_limit1#one" = "You have reached the limit of **{count}** connected accounts.";
"lng_accounts_limit1#other" = "You have reached the limit of **{count}** connected accounts.";
@@ -2213,6 +2363,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_saved_messages" = "Saved Messages";
"lng_saved_short" = "Save";
"lng_saved_forward_here" = "Forward messages here for quick access";
"lng_saved_quote_here" = "Quote here to save";
"lng_scheduled_messages" = "Scheduled Messages";
"lng_scheduled_messages_empty" = "No scheduled messages here yet...";
@@ -2420,6 +2571,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_context_attached_stickers" = "Attached Stickers";
"lng_context_to_msg" = "Go To Message";
"lng_context_reply_msg" = "Reply";
"lng_context_quote_and_reply" = "Quote & Reply";
"lng_context_edit_msg" = "Edit";
"lng_context_forward_msg" = "Forward Message";
"lng_context_send_now_msg" = "Send now";
@@ -2524,6 +2676,25 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_inline_switch_choose" = "Choose conversation...";
"lng_inline_switch_cant" = "Sorry, no way to write here :(";
"lng_reply_in_another_title" = "Reply in...";
"lng_reply_in_another_chat" = "Reply in Another Chat";
"lng_reply_show_in_chat" = "Show in Chat";
"lng_reply_remove" = "Do Not Reply";
"lng_reply_about_quote" = "You can select specific part to quote.";
"lng_reply_options_header" = "Reply to Message";
"lng_reply_options_quote" = "Update Quote";
"lng_reply_header_short" = "Reply";
"lng_reply_quote_selected" = "Quote Selected";
"lng_reply_from_private_chat" = "This reply is from a private chat.";
"lng_link_options_header" = "Link Preview Settings";
"lng_link_header_short" = "Link";
"lng_link_move_up" = "Move Up";
"lng_link_move_down" = "Move Down";
"lng_link_shrink_photo" = "Shrink Photo";
"lng_link_enlarge_photo" = "Enlarge Photo";
"lng_link_remove" = "Do Not Preview";
"lng_link_about_choose" = "Click on a link to generate its preview.";
"lng_share_cant" = "Sorry, no way to share here :(";
"lng_reply_cant" = "Sorry, no way to reply to an old message in supergroup :(";
"lng_reply_cant_forward" = "Sorry, you can't reply to a message that was sent before the group was upgraded to a supergroup. Do you wish to forward it and add your comment?";
@@ -2546,6 +2717,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_edit_bot_title" = "Edit bot";
"lng_edit_sign_messages" = "Sign messages";
"lng_edit_group" = "Edit group";
"lng_edit_channel_color" = "Change name color";
"lng_edit_self_title" = "Edit your name";
"lng_confirm_contact_data" = "New Contact";
"lng_add_contact" = "Create";
@@ -2674,6 +2846,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_menu_formatting_italic" = "Italic";
"lng_menu_formatting_underline" = "Underline";
"lng_menu_formatting_strike_out" = "Strike-through";
"lng_menu_formatting_blockquote" = "Quote";
"lng_menu_formatting_monospace" = "Monospace";
"lng_menu_formatting_spoiler" = "Spoiler";
"lng_menu_formatting_link_create" = "Create link";
@@ -2686,6 +2859,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_formatting_link_create" = "Create";
"lng_text_copied" = "Text copied to clipboard.";
"lng_code_copied" = "Block copied to clipboard.";
"lng_spellchecker_submenu" = "Spelling";
"lng_spellchecker_add" = "Add to Dictionary";
@@ -3351,6 +3525,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_admin_log_participant_volume_channel" = "{from} changed live stream volume for {user} to {percent}";
"lng_admin_log_antispam_enabled" = "{from} enabled aggressive anti-spam";
"lng_admin_log_antispam_disabled" = "{from} disabled aggressive anti-spam";
"lng_admin_log_change_color" = "{from} changed channel color from {previous} to {color}";
"lng_admin_log_set_background_emoji" = "{from} set channel background emoji to {emoji}";
"lng_admin_log_change_background_emoji" = "{from} changed channel background emoji from {previous} to {emoji}";
"lng_admin_log_removed_background_emoji" = "{from} removed channel background emoji {emoji}";
"lng_admin_log_user_with_username" = "{name} ({mention})";
"lng_admin_log_messages_ttl_set" = "{from} enabled messages auto-delete after {duration}";
"lng_admin_log_messages_ttl_changed" = "{from} changed messages auto-delete period from {previous} to {duration}";
@@ -4065,6 +4243,80 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_stories_link_invalid" = "This link is broken or has expired.";
"lng_stats_title" = "Statistics";
"lng_stats_message_title" = "Message Statistic";
"lng_stats_zoom_out" = "Zoom Out";
"lng_stats_overview_title" = "Overview";
"lng_stats_overview_member_count" = "Followers";
"lng_stats_overview_mean_view_count" = "Views Per Post";
"lng_stats_overview_mean_share_count" = "Shared Per Post";
"lng_stats_overview_enabled_notifications" = "Enabled Notifications";
"lng_stats_overview_messages" = "Messages";
"lng_stats_overview_group_mean_view_count" = "Viewing Members";
"lng_stats_overview_group_mean_post_count" = "Posting Members";
"lng_stats_overview_message_private_shares" = "Private Shares";
"lng_stats_overview_message_public_shares" = "Public Shares";
"lng_stats_overview_message_views" = "Views";
"lng_stats_overview_message_public_share#one" = "{count} public share";
"lng_stats_overview_message_public_share#other" = "{count} public shares";
"lng_stats_members_title" = "Top members";
"lng_stats_admins_title" = "Top admins";
"lng_stats_inviters_title" = "Top inviters";
"lng_stats_member_messages#one" = "{count} message";
"lng_stats_member_messages#other" = "{count} messages";
"lng_stats_member_characters#one" = "{count} symbol per message";
"lng_stats_member_characters#other" = "{count} symbols per message";
"lng_stats_member_deletions#one" = "{count} deletions";
"lng_stats_member_deletions#other" = "{count} deletions";
"lng_stats_member_bans#one" = "{count} ban";
"lng_stats_member_bans#other" = "{count} bans";
"lng_stats_member_restrictions#one" = "{count} restriction";
"lng_stats_member_restrictions#other" = "{count} restrictions";
"lng_stats_member_invitations#one" = "{count} invitation";
"lng_stats_member_invitations#other" = "{count} invitations";
"lng_stats_recent_messages_title" = "Recent posts";
"lng_stats_recent_messages_views#one" = "{count} view";
"lng_stats_recent_messages_views#other" = "{count} views";
"lng_stats_recent_messages_shares#one" = "{count} share";
"lng_stats_recent_messages_shares#other" = "{count} shares";
"lng_stats_loading" = "Loading stats...";
"lng_stats_loading_subtext" = "Please wait a few moments while we generate your stats.";
"lng_chart_title_member_count" = "Growth";
"lng_chart_title_join" = "Followers";
"lng_chart_title_mute" = "Notifications";
"lng_chart_title_view_count_by_hour" = "Views by hours";
"lng_chart_title_view_count_by_source" = "Views by source";
"lng_chart_title_join_by_source" = "New followers by source";
"lng_chart_title_language" = "Languages";
"lng_chart_title_message_interaction" = "Interactions";
"lng_chart_title_instant_view_interaction" = "IV Interactions";
"lng_chart_title_group_join" = "Group members";
"lng_chart_title_group_join_by_source" = "New members by source";
"lng_chart_title_group_language" = "Members's primary language";
"lng_chart_title_group_message_content" = "Messages";
"lng_chart_title_group_action" = "Actions";
"lng_chart_title_group_day" = "Views by hours";
"lng_chart_title_group_week" = "Top days of week";
"lng_boosts_title" = "Boosts";
"lng_boosts_level" = "Level";
"lng_boosts_existing" = "Existing boosts";
"lng_boosts_premium_audience" = "Premium subscribers";
"lng_boosts_next_level" = "Boosts to level up";
"lng_boosts_list_title#one" = "{count} booster";
"lng_boosts_list_title#other" = "{count} boosters";
"lng_boosts_list_subtext" = "Your channel is currently boosted by these users.";
"lng_boosts_show_more" = "Show More Boosts";
"lng_boosts_list_status" = "boost expires on {date}";
"lng_boosts_link_title" = "Link for boosting";
"lng_boosts_link_subtext" = "Share this link with your subscribers to get more boosts.";
// Wnd specific
"lng_wnd_choose_program_menu" = "Choose Default Program...";

View File

@@ -10,5 +10,6 @@
<file alias="cloud_password/email.tgs">../../animations/cloud_password/email.tgs</file>
<file alias="ttl.tgs">../../animations/ttl.tgs</file>
<file alias="discussion.tgs">../../animations/discussion.tgs</file>
<file alias="stats.tgs">../../animations/stats.tgs</file>
</qresource>
</RCC>

View File

@@ -10,7 +10,7 @@
<Identity Name="TelegramMessengerLLP.TelegramDesktop"
ProcessorArchitecture="ARCHITECTURE"
Publisher="CN=536BC709-8EE1-4478-AF22-F0F0F26FF64A"
Version="4.10.2.0" />
Version="4.11.2.0" />
<Properties>
<DisplayName>Telegram Desktop</DisplayName>
<PublisherDisplayName>Telegram Messenger LLP</PublisherDisplayName>

View File

@@ -44,8 +44,8 @@ IDI_ICON1 ICON "..\\art\\icon256.ico"
//
VS_VERSION_INFO VERSIONINFO
FILEVERSION 4,10,2,0
PRODUCTVERSION 4,10,2,0
FILEVERSION 4,11,2,0
PRODUCTVERSION 4,11,2,0
FILEFLAGSMASK 0x3fL
#ifdef _DEBUG
FILEFLAGS 0x1L
@@ -62,10 +62,10 @@ BEGIN
BEGIN
VALUE "CompanyName", "Telegram FZ-LLC"
VALUE "FileDescription", "Telegram Desktop"
VALUE "FileVersion", "4.10.2.0"
VALUE "FileVersion", "4.11.2.0"
VALUE "LegalCopyright", "Copyright (C) 2014-2023"
VALUE "ProductName", "Telegram Desktop"
VALUE "ProductVersion", "4.10.2.0"
VALUE "ProductVersion", "4.11.2.0"
END
END
BLOCK "VarFileInfo"

View File

@@ -35,8 +35,8 @@ LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
//
VS_VERSION_INFO VERSIONINFO
FILEVERSION 4,10,2,0
PRODUCTVERSION 4,10,2,0
FILEVERSION 4,11,2,0
PRODUCTVERSION 4,11,2,0
FILEFLAGSMASK 0x3fL
#ifdef _DEBUG
FILEFLAGS 0x1L
@@ -53,10 +53,10 @@ BEGIN
BEGIN
VALUE "CompanyName", "Telegram FZ-LLC"
VALUE "FileDescription", "Telegram Desktop Updater"
VALUE "FileVersion", "4.10.2.0"
VALUE "FileVersion", "4.11.2.0"
VALUE "LegalCopyright", "Copyright (C) 2014-2023"
VALUE "ProductName", "Telegram Desktop"
VALUE "ProductVersion", "4.10.2.0"
VALUE "ProductVersion", "4.11.2.0"
END
END
BLOCK "VarFileInfo"

View File

@@ -169,9 +169,7 @@ void SendBotCallbackData(
void HideSingleUseKeyboard(
not_null<Window::SessionController*> controller,
not_null<HistoryItem*> item) {
controller->content()->hideSingleUseKeyboard(
item->history()->peer,
item->id);
controller->content()->hideSingleUseKeyboard(item->fullId());
}
} // namespace
@@ -312,12 +310,14 @@ void ActivateBotCommand(ClickHandlerContext context, int row, int column) {
case ButtonType::Default: {
// Copy string before passing it to the sending method
// because the original button can be destroyed inside.
const auto replyTo = item->isRegular() ? item->id : 0;
const auto replyTo = item->isRegular()
? item->fullId()
: FullMsgId();
controller->content()->sendBotCommand({
.peer = item->history()->peer,
.command = QString(button->text),
.context = item->fullId(),
.replyTo = replyTo,
.replyTo = { replyTo },
});
} break;
@@ -363,7 +363,7 @@ void ActivateBotCommand(ClickHandlerContext context, int row, int column) {
case ButtonType::RequestPhone: {
HideSingleUseKeyboard(controller, item);
const auto itemId = item->id;
const auto itemId = item->fullId();
const auto topicRootId = item->topicRootId();
const auto history = item->history();
controller->show(Ui::MakeConfirmBox({
@@ -376,7 +376,7 @@ void ActivateBotCommand(ClickHandlerContext context, int row, int column) {
auto action = Api::SendAction(history);
action.clearDraft = false;
action.replyTo = {
.msgId = itemId,
.messageId = itemId,
.topicRootId = topicRootId,
};
history->session().api().shareContact(
@@ -397,13 +397,11 @@ void ActivateBotCommand(ClickHandlerContext context, int row, int column) {
chosen |= PollData::Flag::Quiz;
}
}
const auto replyToId = MsgId(0);
const auto topicRootId = MsgId(0);
const auto replyTo = FullReplyTo();
Window::PeerMenuCreatePoll(
controller,
item->history()->peer,
replyToId,
topicRootId,
replyTo,
chosen,
disabled);
} break;

View File

@@ -19,8 +19,8 @@ SendAction::SendAction(
SendOptions options)
: history(thread->owningHistory())
, options(options)
, replyTo({ .msgId = thread->topicRootId() }) {
replyTo.topicRootId = replyTo.msgId;
, replyTo({ .messageId = { history->peer->id, thread->topicRootId() } }) {
replyTo.topicRootId = replyTo.messageId.msg;
}
SendOptions DefaultSendWhenOnlineOptions() {
@@ -31,7 +31,7 @@ SendOptions DefaultSendWhenOnlineOptions() {
}
MTPInputReplyTo SendAction::mtpReplyTo() const {
return Data::ReplyToForMTP(&history->owner(), replyTo);
return Data::ReplyToForMTP(history, replyTo);
}
} // namespace Api

View File

@@ -7,6 +7,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#pragma once
#include "data/data_drafts.h"
class History;
namespace Data {
@@ -22,7 +24,6 @@ struct SendOptions {
TimeId scheduled = 0;
bool silent = false;
bool handleSupportSwitch = false;
bool removeWebPageId = false;
bool hideViaBot = false;
};
[[nodiscard]] SendOptions DefaultSendWhenOnlineOptions();
@@ -54,7 +55,7 @@ struct MessageToSend {
SendAction action;
TextWithTags textWithTags;
WebPageId webPageId = 0;
Data::WebPageDraft webPage;
};
struct RemoteFileInfo {

View File

@@ -11,8 +11,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "api/api_media.h"
#include "api/api_text_entities.h"
#include "ui/boxes/confirm_box.h"
#include "data/data_histories.h"
#include "data/data_scheduled_messages.h"
#include "data/data_session.h"
#include "data/data_web_page.h"
#include "history/history.h"
#include "history/history_item.h"
#include "lang/lang_keys.h"
@@ -45,6 +47,7 @@ template <typename DoneCallback, typename FailCallback>
mtpRequestId EditMessage(
not_null<HistoryItem*> item,
const TextWithEntities &textWithEntities,
Data::WebPageDraft webpage,
SendOptions options,
DoneCallback &&done,
FailCallback &&fail,
@@ -65,15 +68,21 @@ mtpRequestId EditMessage(
const auto emptyFlag = MTPmessages_EditMessage::Flag(0);
const auto flags = emptyFlag
| (!text.isEmpty() || media
| ((!text.isEmpty() || media)
? MTPmessages_EditMessage::Flag::f_message
: emptyFlag)
| ((media && inputMedia.has_value())
? MTPmessages_EditMessage::Flag::f_media
: emptyFlag)
| (options.removeWebPageId
| (webpage.removed
? MTPmessages_EditMessage::Flag::f_no_webpage
: emptyFlag)
| ((!webpage.removed && !webpage.url.isEmpty())
? MTPmessages_EditMessage::Flag::f_media
: emptyFlag)
| ((!webpage.removed && !webpage.url.isEmpty() && webpage.invert)
? MTPmessages_EditMessage::Flag::f_invert_media
: emptyFlag)
| (!sentEntities.v.isEmpty()
? MTPmessages_EditMessage::Flag::f_entities
: emptyFlag)
@@ -89,7 +98,7 @@ mtpRequestId EditMessage(
item->history()->peer->input,
MTP_int(id),
MTP_string(text),
inputMedia.value_or(MTPInputMedia()),
inputMedia.value_or(Data::WebPageForMTP(webpage, text.isEmpty())),
MTPReplyMarkup(),
sentEntities,
MTP_int(options.scheduled)
@@ -133,9 +142,15 @@ mtpRequestId EditMessage(
FailCallback &&fail,
std::optional<MTPInputMedia> inputMedia = std::nullopt) {
const auto &text = item->originalText();
const auto webpage = (!item->media() || !item->media()->webpage())
? Data::WebPageDraft{ .removed = true }
: Data::WebPageDraft{
.id = item->media()->webpage()->id,
};
return EditMessage(
item,
text,
webpage,
options,
std::forward<DoneCallback>(done),
std::forward<FailCallback>(fail),
@@ -216,12 +231,19 @@ mtpRequestId EditCaption(
SendOptions options,
Fn<void()> done,
Fn<void(const QString &)> fail) {
return EditMessage(item, caption, options, done, fail);
return EditMessage(
item,
caption,
Data::WebPageDraft(),
options,
done,
fail);
}
mtpRequestId EditTextMessage(
not_null<HistoryItem*> item,
const TextWithEntities &caption,
Data::WebPageDraft webpage,
SendOptions options,
Fn<void(mtpRequestId requestId)> done,
Fn<void(const QString &, mtpRequestId requestId)> fail) {
@@ -229,7 +251,7 @@ mtpRequestId EditTextMessage(
applyUpdates();
done(id);
};
return EditMessage(item, caption, options, callback, fail);
return EditMessage(item, caption, webpage, options, callback, fail);
}
} // namespace Api

View File

@@ -9,6 +9,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
class HistoryItem;
namespace Data {
struct WebPageDraft;
} // namespace Data
namespace MTP {
class Error;
} // namespace MTP
@@ -48,6 +52,7 @@ mtpRequestId EditCaption(
mtpRequestId EditTextMessage(
not_null<HistoryItem*> item,
const TextWithEntities &caption,
Data::WebPageDraft webpage,
SendOptions options,
Fn<void(mtpRequestId requestId)> done,
Fn<void(const QString &error, mtpRequestId requestId)> fail);

View File

@@ -510,40 +510,57 @@ void PeerPhoto::requestUserPhotos(
_userPhotosRequests.emplace(user, requestId);
}
auto PeerPhoto::emojiList(EmojiListType type) -> EmojiListData & {
switch (type) {
case EmojiListType::Profile: return _profileEmojiList;
case EmojiListType::Group: return _groupEmojiList;
case EmojiListType::Background: return _backgroundEmojiList;
}
Unexpected("Type in PeerPhoto::emojiList.");
}
auto PeerPhoto::emojiList(EmojiListType type) const
-> const EmojiListData & {
return const_cast<PeerPhoto*>(this)->emojiList(type);
}
void PeerPhoto::requestEmojiList(EmojiListType type) {
if (_requestIdEmojiList) {
auto &list = emojiList(type);
if (list.requestId) {
return;
}
const auto isGroup = (type == EmojiListType::Group);
const auto d = [=](const MTPEmojiList &result) {
_requestIdEmojiList = 0;
result.match([](const MTPDemojiListNotModified &data) {
}, [&](const MTPDemojiList &data) {
auto &list = isGroup ? _profileEmojiList : _groupEmojiList;
list = ranges::views::all(
data.vdocument_id().v
) | ranges::views::transform(&MTPlong::v) | ranges::to_vector;
});
const auto send = [&](auto &&request) {
return _api.request(
std::move(request)
).done([=](const MTPEmojiList &result) {
auto &list = emojiList(type);
list.requestId = 0;
result.match([](const MTPDemojiListNotModified &data) {
}, [&](const MTPDemojiList &data) {
list.list = ranges::views::all(
data.vdocument_id().v
) | ranges::views::transform(
&MTPlong::v
) | ranges::to_vector;
});
}).fail([=] {
emojiList(type).requestId = 0;
}).send();
};
const auto f = [=] { _requestIdEmojiList = 0; };
_requestIdEmojiList = isGroup
? _api.request(
MTPaccount_GetDefaultGroupPhotoEmojis()
).done(d).fail(f).send()
: _api.request(
MTPaccount_GetDefaultProfilePhotoEmojis()
).done(d).fail(f).send();
list.requestId = (type == EmojiListType::Profile)
? send(MTPaccount_GetDefaultProfilePhotoEmojis())
: (type == EmojiListType::Group)
? send(MTPaccount_GetDefaultGroupPhotoEmojis())
: send(MTPaccount_GetDefaultBackgroundEmojis());
}
rpl::producer<PeerPhoto::EmojiList> PeerPhoto::emojiListValue(
EmojiListType type) {
auto &list = (type == EmojiListType::Group)
? _profileEmojiList
: _groupEmojiList;
if (list.current().empty() && !_requestIdEmojiList) {
auto &list = emojiList(type);
if (list.list.current().empty() && !list.requestId) {
requestEmojiList(type);
}
return list.value();
return list.list.value();
}
// Non-personal photo in case a personal photo is set.

View File

@@ -31,6 +31,7 @@ public:
enum class EmojiListType {
Profile,
Group,
Background,
};
struct UserPhoto {
@@ -73,6 +74,10 @@ private:
Suggestion,
Fallback,
};
struct EmojiListData {
rpl::variable<EmojiList> list;
mtpRequestId requestId = 0;
};
void ready(
const FullMsgId &msgId,
@@ -84,6 +89,9 @@ private:
UploadType type,
Fn<void()> done);
[[nodiscard]] EmojiListData &emojiList(EmojiListType type);
[[nodiscard]] const EmojiListData &emojiList(EmojiListType type) const;
const not_null<Main::Session*> _session;
MTP::Sender _api;
@@ -101,9 +109,9 @@ private:
not_null<UserData*>,
not_null<PhotoData*>> _nonPersonalPhotos;
mtpRequestId _requestIdEmojiList = 0;
rpl::variable<EmojiList> _profileEmojiList;
rpl::variable<EmojiList> _groupEmojiList;
EmojiListData _profileEmojiList;
EmojiListData _groupEmojiList;
EmojiListData _backgroundEmojiList;
};

View File

@@ -43,7 +43,7 @@ void Polls::create(
const auto history = action.history;
const auto peer = history->peer;
const auto topicRootId = action.replyTo.msgId
const auto topicRootId = action.replyTo.messageId
? action.replyTo.topicRootId
: 0;
auto sendFlags = MTPmessages_SendMedia::Flags(0);

View File

@@ -17,6 +17,21 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "apiwrap.h"
namespace Api {
namespace {
[[nodiscard]] GiftCode Parse(const MTPDpayments_checkedGiftCode &data) {
return {
.from = peerFromMTP(data.vfrom_id()),
.to = data.vto_id() ? peerFromUser(*data.vto_id()) : PeerId(),
.giveawayId = data.vgiveaway_msg_id().value_or_empty(),
.date = data.vdate().v,
.used = data.vused_date().value_or_empty(),
.months = data.vmonths().v,
.giveaway = data.is_via_giveaway(),
};
}
} // namespace
Premium::Premium(not_null<ApiWrap*> api)
: _session(&api->session())
@@ -183,6 +198,115 @@ void Premium::reloadCloudSet() {
}).send();
}
void Premium::checkGiftCode(
const QString &slug,
Fn<void(GiftCode)> done) {
if (_giftCodeRequestId) {
if (_giftCodeSlug == slug) {
return;
}
_api.request(_giftCodeRequestId).cancel();
}
_giftCodeSlug = slug;
_giftCodeRequestId = _api.request(MTPpayments_CheckGiftCode(
MTP_string(slug)
)).done([=](const MTPpayments_CheckedGiftCode &result) {
_giftCodeRequestId = 0;
const auto &data = result.data();
_session->data().processUsers(data.vusers());
_session->data().processChats(data.vchats());
done(updateGiftCode(slug, Parse(data)));
}).fail([=](const MTP::Error &error) {
_giftCodeRequestId = 0;
done(updateGiftCode(slug, {}));
}).send();
}
GiftCode Premium::updateGiftCode(
const QString &slug,
const GiftCode &code) {
auto &now = _giftCodes[slug];
if (now != code) {
now = code;
_giftCodeUpdated.fire_copy(slug);
}
return code;
}
rpl::producer<GiftCode> Premium::giftCodeValue(const QString &slug) const {
return _giftCodeUpdated.events_starting_with_copy(
slug
) | rpl::filter(rpl::mappers::_1 == slug) | rpl::map([=] {
const auto i = _giftCodes.find(slug);
return (i != end(_giftCodes)) ? i->second : GiftCode();
});
}
void Premium::applyGiftCode(const QString &slug, Fn<void(QString)> done) {
_api.request(MTPpayments_ApplyGiftCode(
MTP_string(slug)
)).done([=](const MTPUpdates &result) {
_session->api().applyUpdates(result);
done({});
}).fail([=](const MTP::Error &error) {
done(error.type());
}).send();
}
void Premium::resolveGiveawayInfo(
not_null<PeerData*> peer,
MsgId messageId,
Fn<void(GiveawayInfo)> done) {
Expects(done != nullptr);
_giveawayInfoDone = std::move(done);
if (_giveawayInfoRequestId) {
if (_giveawayInfoPeer == peer
&& _giveawayInfoMessageId == messageId) {
return;
}
_api.request(_giveawayInfoRequestId).cancel();
}
_giveawayInfoPeer = peer;
_giveawayInfoMessageId = messageId;
_giveawayInfoRequestId = _api.request(MTPpayments_GetGiveawayInfo(
_giveawayInfoPeer->input,
MTP_int(_giveawayInfoMessageId.bare)
)).done([=](const MTPpayments_GiveawayInfo &result) {
_giveawayInfoRequestId = 0;
auto info = GiveawayInfo();
result.match([&](const MTPDpayments_giveawayInfo &data) {
info.participating = data.is_participating();
info.state = data.is_preparing_results()
? GiveawayState::Preparing
: GiveawayState::Running;
info.adminChannelId = data.vadmin_disallowed_chat_id()
? ChannelId(*data.vadmin_disallowed_chat_id())
: ChannelId();
info.disallowedCountry = qs(
data.vdisallowed_country().value_or_empty());
info.tooEarlyDate
= data.vjoined_too_early_date().value_or_empty();
info.startDate = data.vstart_date().v;
}, [&](const MTPDpayments_giveawayInfoResults &data) {
info.state = data.is_refunded()
? GiveawayState::Refunded
: GiveawayState::Finished;
info.giftCode = qs(data.vgift_code_slug().value_or_empty());
info.activatedCount = data.vactivated_count().v;
info.finishDate = data.vfinish_date().v;
info.startDate = data.vstart_date().v;
});
_giveawayInfoDone(std::move(info));
}).fail([=] {
_giveawayInfoRequestId = 0;
_giveawayInfoDone({});
}).send();
}
const Data::SubscriptionOptions &Premium::subscriptionOptions() const {
return _subscriptionOptions;
}

View File

@@ -18,6 +18,49 @@ class Session;
namespace Api {
struct GiftCode {
PeerId from = 0;
PeerId to = 0;
MsgId giveawayId = 0;
TimeId date = 0;
TimeId used = 0; // 0 if not used.
int months = 0;
bool giveaway = false;
explicit operator bool() const {
return months != 0;
}
friend inline bool operator==(
const GiftCode&,
const GiftCode&) = default;
};
enum class GiveawayState {
Invalid,
Running,
Preparing,
Finished,
Refunded,
};
struct GiveawayInfo {
QString giftCode;
QString disallowedCountry;
ChannelId adminChannelId = 0;
GiveawayState state = GiveawayState::Invalid;
TimeId tooEarlyDate = 0;
TimeId finishDate = 0;
TimeId startDate = 0;
int winnersCount = 0;
int activatedCount = 0;
bool participating = false;
explicit operator bool() const {
return state != GiveawayState::Invalid;
}
};
class Premium final {
public:
explicit Premium(not_null<ApiWrap*> api);
@@ -40,6 +83,19 @@ public:
[[nodiscard]] int64 monthlyAmount() const;
[[nodiscard]] QString monthlyCurrency() const;
void checkGiftCode(
const QString &slug,
Fn<void(GiftCode)> done);
GiftCode updateGiftCode(const QString &slug, const GiftCode &code);
[[nodiscard]] rpl::producer<GiftCode> giftCodeValue(
const QString &slug) const;
void applyGiftCode(const QString &slug, Fn<void(QString)> done);
void resolveGiveawayInfo(
not_null<PeerData*> peer,
MsgId messageId,
Fn<void(GiveawayInfo)> done);
[[nodiscard]] auto subscriptionOptions() const
-> const Data::SubscriptionOptions &;
@@ -71,6 +127,16 @@ private:
int64 _monthlyAmount = 0;
QString _monthlyCurrency;
mtpRequestId _giftCodeRequestId = 0;
QString _giftCodeSlug;
base::flat_map<QString, GiftCode> _giftCodes;
rpl::event_stream<QString> _giftCodeUpdated;
mtpRequestId _giveawayInfoRequestId = 0;
PeerData *_giveawayInfoPeer = nullptr;
MsgId _giveawayInfoMessageId = 0;
Fn<void(GiveawayInfo)> _giveawayInfoDone;
Data::SubscriptionOptions _subscriptionOptions;
};

View File

@@ -270,7 +270,6 @@ bool SendDice(MessageToSend &message) {
flags |= MessageFlag::HasReplyInfo;
sendFlags |= MTPmessages_SendMedia::Flag::f_reply_to;
}
const auto replyHeader = NewMessageReplyHeader(message.action);
const auto anonymousPost = peer->amAnonymous();
const auto silentPost = ShouldSendSilent(peer, message.action.options);
InnerFillMessagePostFlags(message.action.options, peer, flags);
@@ -368,9 +367,9 @@ void SendConfirmedFile(
if (!isEditing) {
const auto histories = &session->data().histories();
file->to.replyTo.msgId = histories->convertTopicReplyToId(
file->to.replyTo.messageId = histories->convertTopicReplyToId(
history,
file->to.replyTo.msgId);
file->to.replyTo.messageId);
file->to.replyTo.topicRootId = histories->convertTopicReplyToId(
history,
file->to.replyTo.topicRootId);
@@ -399,7 +398,6 @@ void SendConfirmedFile(
if (file->to.replyTo) {
flags |= MessageFlag::HasReplyInfo;
}
const auto replyHeader = NewMessageReplyHeader(action);
const auto anonymousPost = peer->amAnonymous();
const auto silentPost = ShouldSendSilent(peer, file->to.options);
FillMessagePostFlags(action, peer, flags);

View File

@@ -0,0 +1,594 @@
/*
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 "api/api_statistics.h"
#include "apiwrap.h"
#include "data/data_channel.h"
#include "data/data_peer.h"
#include "data/data_session.h"
#include "history/history.h"
#include "main/main_session.h"
#include "statistics/statistics_data_deserialize.h"
namespace Api {
namespace {
[[nodiscard]] Data::StatisticalGraph StatisticalGraphFromTL(
const MTPStatsGraph &tl) {
return tl.match([&](const MTPDstatsGraph &d) {
using namespace Statistic;
const auto zoomToken = d.vzoom_token().has_value()
? qs(*d.vzoom_token()).toUtf8()
: QByteArray();
return Data::StatisticalGraph{
StatisticalChartFromJSON(qs(d.vjson().data().vdata()).toUtf8()),
zoomToken,
};
}, [&](const MTPDstatsGraphAsync &data) {
return Data::StatisticalGraph{
.zoomToken = qs(data.vtoken()).toUtf8(),
};
}, [&](const MTPDstatsGraphError &data) {
return Data::StatisticalGraph{ .error = qs(data.verror()) };
});
}
[[nodiscard]] Data::StatisticalValue StatisticalValueFromTL(
const MTPStatsAbsValueAndPrev &tl) {
const auto current = tl.data().vcurrent().v;
const auto previous = tl.data().vprevious().v;
return Data::StatisticalValue{
.value = current,
.previousValue = previous,
.growthRatePercentage = previous
? std::abs((current - previous) / float64(previous) * 100.)
: 0,
};
}
[[nodiscard]] Data::ChannelStatistics ChannelStatisticsFromTL(
const MTPDstats_broadcastStats &data) {
const auto &tlUnmuted = data.venabled_notifications().data();
const auto unmuted = (!tlUnmuted.vtotal().v)
? 0.
: std::clamp(
tlUnmuted.vpart().v / tlUnmuted.vtotal().v * 100.,
0.,
100.);
using Recent = MTPMessageInteractionCounters;
auto recentMessages = ranges::views::all(
data.vrecent_message_interactions().v
) | ranges::views::transform([&](const Recent &tl) {
return Data::StatisticsMessageInteractionInfo{
.messageId = tl.data().vmsg_id().v,
.viewsCount = tl.data().vviews().v,
.forwardsCount = tl.data().vforwards().v,
};
}) | ranges::to_vector;
return {
.startDate = data.vperiod().data().vmin_date().v,
.endDate = data.vperiod().data().vmax_date().v,
.memberCount = StatisticalValueFromTL(data.vfollowers()),
.meanViewCount = StatisticalValueFromTL(data.vviews_per_post()),
.meanShareCount = StatisticalValueFromTL(data.vshares_per_post()),
.enabledNotificationsPercentage = unmuted,
.memberCountGraph = StatisticalGraphFromTL(
data.vgrowth_graph()),
.joinGraph = StatisticalGraphFromTL(
data.vfollowers_graph()),
.muteGraph = StatisticalGraphFromTL(
data.vmute_graph()),
.viewCountByHourGraph = StatisticalGraphFromTL(
data.vtop_hours_graph()),
.viewCountBySourceGraph = StatisticalGraphFromTL(
data.vviews_by_source_graph()),
.joinBySourceGraph = StatisticalGraphFromTL(
data.vnew_followers_by_source_graph()),
.languageGraph = StatisticalGraphFromTL(
data.vlanguages_graph()),
.messageInteractionGraph = StatisticalGraphFromTL(
data.vinteractions_graph()),
.instantViewInteractionGraph = StatisticalGraphFromTL(
data.viv_interactions_graph()),
.recentMessageInteractions = std::move(recentMessages),
};
}
[[nodiscard]] Data::SupergroupStatistics SupergroupStatisticsFromTL(
const MTPDstats_megagroupStats &data) {
using Senders = MTPStatsGroupTopPoster;
using Administrators = MTPStatsGroupTopAdmin;
using Inviters = MTPStatsGroupTopInviter;
auto topSenders = ranges::views::all(
data.vtop_posters().v
) | ranges::views::transform([&](const Senders &tl) {
return Data::StatisticsMessageSenderInfo{
.userId = UserId(tl.data().vuser_id().v),
.sentMessageCount = tl.data().vmessages().v,
.averageCharacterCount = tl.data().vavg_chars().v,
};
}) | ranges::to_vector;
auto topAdministrators = ranges::views::all(
data.vtop_admins().v
) | ranges::views::transform([&](const Administrators &tl) {
return Data::StatisticsAdministratorActionsInfo{
.userId = UserId(tl.data().vuser_id().v),
.deletedMessageCount = tl.data().vdeleted().v,
.bannedUserCount = tl.data().vkicked().v,
.restrictedUserCount = tl.data().vbanned().v,
};
}) | ranges::to_vector;
auto topInviters = ranges::views::all(
data.vtop_inviters().v
) | ranges::views::transform([&](const Inviters &tl) {
return Data::StatisticsInviterInfo{
.userId = UserId(tl.data().vuser_id().v),
.addedMemberCount = tl.data().vinvitations().v,
};
}) | ranges::to_vector;
return {
.startDate = data.vperiod().data().vmin_date().v,
.endDate = data.vperiod().data().vmax_date().v,
.memberCount = StatisticalValueFromTL(data.vmembers()),
.messageCount = StatisticalValueFromTL(data.vmessages()),
.viewerCount = StatisticalValueFromTL(data.vviewers()),
.senderCount = StatisticalValueFromTL(data.vposters()),
.memberCountGraph = StatisticalGraphFromTL(
data.vgrowth_graph()),
.joinGraph = StatisticalGraphFromTL(
data.vmembers_graph()),
.joinBySourceGraph = StatisticalGraphFromTL(
data.vnew_members_by_source_graph()),
.languageGraph = StatisticalGraphFromTL(
data.vlanguages_graph()),
.messageContentGraph = StatisticalGraphFromTL(
data.vmessages_graph()),
.actionGraph = StatisticalGraphFromTL(
data.vactions_graph()),
.dayGraph = StatisticalGraphFromTL(
data.vtop_hours_graph()),
.weekGraph = StatisticalGraphFromTL(
data.vweekdays_graph()),
.topSenders = std::move(topSenders),
.topAdministrators = std::move(topAdministrators),
.topInviters = std::move(topInviters),
};
}
} // namespace
Statistics::Statistics(not_null<ApiWrap*> api)
: _api(&api->instance()) {
}
rpl::producer<rpl::no_value, QString> Statistics::request(
not_null<PeerData*> peer) {
return [=](auto consumer) {
auto lifetime = rpl::lifetime();
const auto channel = peer->asChannel();
if (!channel) {
return lifetime;
}
if (!channel->isMegagroup()) {
_api.request(MTPstats_GetBroadcastStats(
MTP_flags(MTPstats_GetBroadcastStats::Flags(0)),
channel->inputChannel
)).done([=](const MTPstats_BroadcastStats &result) {
_channelStats = ChannelStatisticsFromTL(result.data());
consumer.put_done();
}).fail([=](const MTP::Error &error) {
consumer.put_error_copy(error.type());
}).send();
} else {
_api.request(MTPstats_GetMegagroupStats(
MTP_flags(MTPstats_GetMegagroupStats::Flags(0)),
channel->inputChannel
)).done([=](const MTPstats_MegagroupStats &result) {
_supergroupStats = SupergroupStatisticsFromTL(result.data());
peer->owner().processUsers(result.data().vusers());
consumer.put_done();
}).fail([=](const MTP::Error &error) {
consumer.put_error_copy(error.type());
}).send();
}
return lifetime;
};
}
Statistics::GraphResult Statistics::requestZoom(
not_null<PeerData*> peer,
const QString &token,
float64 x) {
return [=](auto consumer) {
auto lifetime = rpl::lifetime();
const auto channel = peer->asChannel();
if (!channel) {
return lifetime;
}
const auto wasEmpty = _zoomDeque.empty();
_zoomDeque.push_back([=] {
_api.request(MTPstats_LoadAsyncGraph(
MTP_flags(x
? MTPstats_LoadAsyncGraph::Flag::f_x
: MTPstats_LoadAsyncGraph::Flag(0)),
MTP_string(token),
MTP_long(x)
)).done([=](const MTPStatsGraph &result) {
consumer.put_next(StatisticalGraphFromTL(result));
consumer.put_done();
if (!_zoomDeque.empty()) {
_zoomDeque.pop_front();
if (!_zoomDeque.empty()) {
_zoomDeque.front()();
}
}
}).fail([=](const MTP::Error &error) {
consumer.put_error_copy(error.type());
}).send();
});
if (wasEmpty) {
_zoomDeque.front()();
}
return lifetime;
};
}
Statistics::GraphResult Statistics::requestMessage(
not_null<PeerData*> peer,
MsgId msgId) {
return [=](auto consumer) {
auto lifetime = rpl::lifetime();
const auto channel = peer->asChannel();
if (!channel) {
return lifetime;
}
_api.request(MTPstats_GetMessageStats(
MTP_flags(MTPstats_GetMessageStats::Flags(0)),
channel->inputChannel,
MTP_int(msgId.bare)
)).done([=](const MTPstats_MessageStats &result) {
consumer.put_next(
StatisticalGraphFromTL(result.data().vviews_graph()));
consumer.put_done();
}).fail([=](const MTP::Error &error) {
consumer.put_error_copy(error.type());
}).send();
return lifetime;
};
}
Data::ChannelStatistics Statistics::channelStats() const {
return _channelStats;
}
Data::SupergroupStatistics Statistics::supergroupStats() const {
return _supergroupStats;
}
PublicForwards::PublicForwards(
not_null<ChannelData*> channel,
FullMsgId fullId)
: _channel(channel)
, _fullId(fullId)
, _api(&channel->session().api().instance()) {
}
void PublicForwards::request(
const Data::PublicForwardsSlice::OffsetToken &token,
Fn<void(Data::PublicForwardsSlice)> done) {
if (_requestId) {
return;
}
const auto offsetPeer = _channel->owner().peer(token.fullId.peer);
const auto tlOffsetPeer = offsetPeer
? offsetPeer->input
: MTP_inputPeerEmpty();
constexpr auto kLimit = tl::make_int(100);
_requestId = _api.request(MTPstats_GetMessagePublicForwards(
_channel->inputChannel,
MTP_int(_fullId.msg),
MTP_int(token.rate),
tlOffsetPeer,
MTP_int(token.fullId.msg),
kLimit
)).done([=, channel = _channel](const MTPmessages_Messages &result) {
using Messages = QVector<FullMsgId>;
_requestId = 0;
auto nextToken = Data::PublicForwardsSlice::OffsetToken();
const auto process = [&](const MTPVector<MTPMessage> &messages) {
auto result = Messages();
for (const auto &message : messages.v) {
const auto msgId = IdFromMessage(message);
const auto peerId = PeerFromMessage(message);
const auto lastDate = DateFromMessage(message);
if (const auto peer = channel->owner().peerLoaded(peerId)) {
if (lastDate) {
channel->owner().addNewMessage(
message,
MessageFlags(),
NewMessageType::Existing);
nextToken.fullId = { peerId, msgId };
result.push_back(nextToken.fullId);
}
}
}
return result;
};
auto allLoaded = false;
auto fullCount = 0;
auto messages = result.match([&](const MTPDmessages_messages &data) {
channel->owner().processUsers(data.vusers());
channel->owner().processChats(data.vchats());
auto list = process(data.vmessages());
allLoaded = true;
fullCount = list.size();
return list;
}, [&](const MTPDmessages_messagesSlice &data) {
channel->owner().processUsers(data.vusers());
channel->owner().processChats(data.vchats());
auto list = process(data.vmessages());
if (const auto nextRate = data.vnext_rate()) {
const auto rateUpdated = (nextRate->v != token.rate);
if (rateUpdated) {
nextToken.rate = nextRate->v;
} else {
allLoaded = true;
}
}
fullCount = data.vcount().v;
return list;
}, [&](const MTPDmessages_channelMessages &data) {
channel->owner().processUsers(data.vusers());
channel->owner().processChats(data.vchats());
auto list = process(data.vmessages());
allLoaded = true;
fullCount = data.vcount().v;
return list;
}, [&](const MTPDmessages_messagesNotModified &) {
allLoaded = true;
return Messages();
});
_lastTotal = std::max(_lastTotal, fullCount);
done({
.list = std::move(messages),
.total = _lastTotal,
.allLoaded = allLoaded,
.token = nextToken,
});
}).fail([=] {
_requestId = 0;
}).send();
}
MessageStatistics::MessageStatistics(
not_null<ChannelData*> channel,
FullMsgId fullId)
: _publicForwards(channel, fullId)
, _channel(channel)
, _fullId(fullId)
, _api(&channel->session().api().instance()) {
}
Data::PublicForwardsSlice MessageStatistics::firstSlice() const {
return _firstSlice;
}
void MessageStatistics::request(Fn<void(Data::MessageStatistics)> done) {
if (_channel->isMegagroup()) {
return;
}
const auto requestFirstPublicForwards = [=](
const Data::StatisticalGraph &messageGraph,
const Data::StatisticsMessageInteractionInfo &info) {
_publicForwards.request({}, [=](Data::PublicForwardsSlice slice) {
const auto total = slice.total;
_firstSlice = std::move(slice);
done({
.messageInteractionGraph = messageGraph,
.publicForwards = total,
.privateForwards = info.forwardsCount - total,
.views = info.viewsCount,
});
});
};
const auto requestPrivateForwards = [=](
const Data::StatisticalGraph &messageGraph) {
_api.request(MTPchannels_GetMessages(
_channel->inputChannel,
MTP_vector<MTPInputMessage>(
1,
MTP_inputMessageID(MTP_int(_fullId.msg))))
).done([=](const MTPmessages_Messages &result) {
const auto process = [&](const MTPVector<MTPMessage> &messages) {
const auto &message = messages.v.front();
return message.match([&](const MTPDmessage &data) {
return Data::StatisticsMessageInteractionInfo{
.messageId = IdFromMessage(message),
.viewsCount = data.vviews()
? data.vviews()->v
: 0,
.forwardsCount = data.vforwards()
? data.vforwards()->v
: 0,
};
}, [](const MTPDmessageEmpty &) {
return Data::StatisticsMessageInteractionInfo();
}, [](const MTPDmessageService &) {
return Data::StatisticsMessageInteractionInfo();
});
};
auto info = result.match([&](const MTPDmessages_messages &data) {
return process(data.vmessages());
}, [&](const MTPDmessages_messagesSlice &data) {
return process(data.vmessages());
}, [&](const MTPDmessages_channelMessages &data) {
return process(data.vmessages());
}, [](const MTPDmessages_messagesNotModified &) {
return Data::StatisticsMessageInteractionInfo();
});
requestFirstPublicForwards(messageGraph, std::move(info));
}).fail([=](const MTP::Error &error) {
requestFirstPublicForwards(messageGraph, {});
}).send();
};
_api.request(MTPstats_GetMessageStats(
MTP_flags(MTPstats_GetMessageStats::Flags(0)),
_channel->inputChannel,
MTP_int(_fullId.msg.bare)
)).done([=](const MTPstats_MessageStats &result) {
requestPrivateForwards(
StatisticalGraphFromTL(result.data().vviews_graph()));
}).fail([=](const MTP::Error &error) {
requestPrivateForwards({});
}).send();
}
Boosts::Boosts(not_null<PeerData*> peer)
: _peer(peer)
, _api(&peer->session().api().instance()) {
}
rpl::producer<rpl::no_value, QString> Boosts::request() {
return [=](auto consumer) {
auto lifetime = rpl::lifetime();
const auto channel = _peer->asChannel();
if (!channel || channel->isMegagroup()) {
return lifetime;
}
_api.request(MTPpremium_GetBoostsStatus(
_peer->input
)).done([=](const MTPpremium_BoostsStatus &result) {
const auto &data = result.data();
const auto hasPremium = !!data.vpremium_audience();
const auto premiumMemberCount = hasPremium
? std::max(0, int(data.vpremium_audience()->data().vpart().v))
: 0;
const auto participantCount = hasPremium
? std::max(
int(data.vpremium_audience()->data().vtotal().v),
premiumMemberCount)
: 0;
const auto premiumMemberPercentage = (participantCount > 0)
? (100. * premiumMemberCount / participantCount)
: 0;
_boostStatus.overview = Data::BoostsOverview{
.isBoosted = data.is_my_boost(),
.level = std::max(data.vlevel().v, 0),
.boostCount = std::max(
data.vboosts().v,
data.vcurrent_level_boosts().v),
.currentLevelBoostCount = data.vcurrent_level_boosts().v,
.nextLevelBoostCount = data.vnext_level_boosts()
? data.vnext_level_boosts()->v
: 0,
.premiumMemberCount = premiumMemberCount,
.premiumMemberPercentage = premiumMemberPercentage,
};
_boostStatus.link = qs(data.vboost_url());
requestBoosts({}, [=](Data::BoostsListSlice &&slice) {
_boostStatus.firstSlice = std::move(slice);
consumer.put_done();
});
}).fail([=](const MTP::Error &error) {
consumer.put_error_copy(error.type());
}).send();
return lifetime;
};
}
void Boosts::requestBoosts(
const Data::BoostsListSlice::OffsetToken &token,
Fn<void(Data::BoostsListSlice)> done) {
if (_requestId) {
return;
}
constexpr auto kTlFirstSlice = tl::make_int(kFirstSlice);
constexpr auto kTlLimit = tl::make_int(kLimit);
_requestId = _api.request(MTPpremium_GetBoostsList(
MTP_flags(0),
_peer->input,
MTP_string(token.next),
token.next.isEmpty() ? kTlFirstSlice : kTlLimit
)).done([=](const MTPpremium_BoostsList &result) {
_requestId = 0;
const auto &data = result.data();
_peer->owner().processUsers(data.vusers());
auto list = std::vector<Data::Boost>();
list.reserve(data.vboosts().v.size());
for (const auto &boost : data.vboosts().v) {
list.push_back({
boost.data().vuser_id().value_or_empty(),
QDateTime::fromSecsSinceEpoch(boost.data().vexpires().v),
});
}
done(Data::BoostsListSlice{
.list = std::move(list),
.total = data.vcount().v,
.allLoaded = (data.vcount().v == data.vboosts().v.size()),
.token = Data::BoostsListSlice::OffsetToken{
data.vnext_offset()
? qs(*data.vnext_offset())
: QString()
},
});
}).fail([=] {
_requestId = 0;
}).send();
}
Data::BoostStatus Boosts::boostStatus() const {
return _boostStatus;
}
} // namespace Api

View File

@@ -0,0 +1,110 @@
/*
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_boosts.h"
#include "data/data_statistics.h"
#include "mtproto/sender.h"
class ApiWrap;
class ChannelData;
class PeerData;
namespace Api {
class Statistics final {
public:
explicit Statistics(not_null<ApiWrap*> api);
[[nodiscard]] rpl::producer<rpl::no_value, QString> request(
not_null<PeerData*> peer);
using GraphResult = rpl::producer<Data::StatisticalGraph, QString>;
[[nodiscard]] GraphResult requestZoom(
not_null<PeerData*> peer,
const QString &token,
float64 x);
[[nodiscard]] GraphResult requestMessage(
not_null<PeerData*> peer,
MsgId msgId);
[[nodiscard]] Data::ChannelStatistics channelStats() const;
[[nodiscard]] Data::SupergroupStatistics supergroupStats() const;
private:
Data::ChannelStatistics _channelStats;
Data::SupergroupStatistics _supergroupStats;
MTP::Sender _api;
std::deque<Fn<void()>> _zoomDeque;
};
class PublicForwards final {
public:
explicit PublicForwards(not_null<ChannelData*> channel, FullMsgId fullId);
void request(
const Data::PublicForwardsSlice::OffsetToken &token,
Fn<void(Data::PublicForwardsSlice)> done);
private:
const not_null<ChannelData*> _channel;
const FullMsgId _fullId;
mtpRequestId _requestId = 0;
int _lastTotal = 0;
MTP::Sender _api;
};
class MessageStatistics final {
public:
explicit MessageStatistics(
not_null<ChannelData*> channel,
FullMsgId fullId);
void request(Fn<void(Data::MessageStatistics)> done);
[[nodiscard]] Data::PublicForwardsSlice firstSlice() const;
private:
PublicForwards _publicForwards;
const not_null<ChannelData*> _channel;
const FullMsgId _fullId;
Data::PublicForwardsSlice _firstSlice;
mtpRequestId _requestId = 0;
MTP::Sender _api;
};
class Boosts final {
public:
explicit Boosts(not_null<PeerData*> peer);
[[nodiscard]] rpl::producer<rpl::no_value, QString> request();
void requestBoosts(
const Data::BoostsListSlice::OffsetToken &token,
Fn<void(Data::BoostsListSlice)> done);
[[nodiscard]] Data::BoostStatus boostStatus() const;
static constexpr auto kFirstSlice = int(10);
static constexpr auto kLimit = int(40);
private:
const not_null<PeerData*> _peer;
Data::BoostStatus _boostStatus;
MTP::Sender _api;
mtpRequestId _requestId = 0;
};
} // namespace Api

View File

@@ -114,6 +114,7 @@ EntitiesInText EntitiesFromMTP(
case mtpc_messageEntityStrike: { auto &d = entity.c_messageEntityStrike(); result.push_back({ EntityType::StrikeOut, d.voffset().v, d.vlength().v }); } break;
case mtpc_messageEntityCode: { auto &d = entity.c_messageEntityCode(); result.push_back({ EntityType::Code, d.voffset().v, d.vlength().v }); } break;
case mtpc_messageEntityPre: { auto &d = entity.c_messageEntityPre(); result.push_back({ EntityType::Pre, d.voffset().v, d.vlength().v, qs(d.vlanguage()) }); } break;
case mtpc_messageEntityBlockquote: { auto &d = entity.c_messageEntityBlockquote(); result.push_back({ EntityType::Blockquote, d.voffset().v, d.vlength().v }); } break;
case mtpc_messageEntityBankCard: break; // Skipping cards. // #TODO entities
case mtpc_messageEntitySpoiler: { auto &d = entity.c_messageEntitySpoiler(); result.push_back({ EntityType::Spoiler, d.voffset().v, d.vlength().v }); } break;
case mtpc_messageEntityCustomEmoji: {
@@ -142,6 +143,7 @@ MTPVector<MTPMessageEntity> EntitiesToMTP(
&& entity.type() != EntityType::StrikeOut
&& entity.type() != EntityType::Code // #TODO entities
&& entity.type() != EntityType::Pre
&& entity.type() != EntityType::Blockquote
&& entity.type() != EntityType::Spoiler
&& entity.type() != EntityType::MentionName
&& entity.type() != EntityType::CustomUrl
@@ -170,6 +172,7 @@ MTPVector<MTPMessageEntity> EntitiesToMTP(
case EntityType::StrikeOut: v.push_back(MTP_messageEntityStrike(offset, length)); break;
case EntityType::Code: v.push_back(MTP_messageEntityCode(offset, length)); break; // #TODO entities
case EntityType::Pre: v.push_back(MTP_messageEntityPre(offset, length, MTP_string(entity.data()))); break;
case EntityType::Blockquote: v.push_back(MTP_messageEntityBlockquote(offset, length)); break;
case EntityType::Spoiler: v.push_back(MTP_messageEntitySpoiler(offset, length)); break;
case EntityType::CustomEmoji: {
if (const auto valid = CustomEmojiEntity(offset, length, entity.data())) {

View File

@@ -2071,7 +2071,10 @@ void Updates::feedUpdate(const MTPUpdate &update) {
windows.front()->window().show(Ui::MakeInformBox(text));
}
} else {
session().data().serviceNotification(text, d.vmedia());
session().data().serviceNotification(
text,
d.vmedia(),
d.is_invert_media());
session().api().authorizations().reload();
}
} break;

View File

@@ -2135,14 +2135,13 @@ void ApiWrap::saveDraftsToCloud() {
auto flags = MTPmessages_SaveDraft::Flags(0);
auto &textWithTags = cloudDraft->textWithTags;
if (cloudDraft->previewState != Data::PreviewState::Allowed) {
if (cloudDraft->webpage.removed) {
flags |= MTPmessages_SaveDraft::Flag::f_no_webpage;
} else if (!cloudDraft->webpage.url.isEmpty()) {
flags |= MTPmessages_SaveDraft::Flag::f_media;
}
if (cloudDraft->msgId) {
flags |= MTPmessages_SaveDraft::Flag::f_reply_to_msg_id;
}
if (cloudDraft->topicRootId) {
flags |= MTPmessages_SaveDraft::Flag::f_top_msg_id;
if (cloudDraft->reply.messageId || cloudDraft->reply.topicRootId) {
flags |= MTPmessages_SaveDraft::Flag::f_reply_to;
}
if (!textWithTags.tags.isEmpty()) {
flags |= MTPmessages_SaveDraft::Flag::f_entities;
@@ -2155,11 +2154,13 @@ void ApiWrap::saveDraftsToCloud() {
history->startSavingCloudDraft(topicRootId);
cloudDraft->saveRequestId = request(MTPmessages_SaveDraft(
MTP_flags(flags),
MTP_int(cloudDraft->msgId),
MTP_int(cloudDraft->topicRootId),
ReplyToForMTP(history, cloudDraft->reply),
history->peer->input,
MTP_string(textWithTags.text),
entities
entities,
Data::WebPageForMTP(
cloudDraft->webpage,
textWithTags.text.isEmpty())
)).done([=](const MTPBool &result, const MTP::Response &response) {
const auto requestId = response.requestId;
history->finishSavingCloudDraft(
@@ -2246,7 +2247,7 @@ void ApiWrap::gotStickerSet(
}
void ApiWrap::requestWebPageDelayed(not_null<WebPageData*> page) {
if (page->pendingTill <= 0) {
if (page->failed || !page->pendingTill) {
return;
}
_webPagesPending.emplace(page, 0);
@@ -2551,7 +2552,8 @@ void ApiWrap::gotWebPages(ChannelData *channel, const MTPmessages_Messages &resu
for (auto i = _webPagesPending.begin(); i != _webPagesPending.cend();) {
if (i->second == req) {
if (i->first->pendingTill > 0) {
i->first->pendingTill = -1;
i->first->pendingTill = 0;
i->first->failed = 1;
_session->data().notifyWebPageUpdateDelayed(i->first);
}
i = _webPagesPending.erase(i);
@@ -3374,7 +3376,6 @@ void ApiWrap::sendSharedContact(
if (action.replyTo) {
flags |= MessageFlag::HasReplyInfo;
}
const auto replyHeader = NewMessageReplyHeader(action);
FillMessagePostFlags(action, peer, flags);
if (action.options.scheduled) {
flags |= MessageFlag::IsOrWasScheduled;
@@ -3578,15 +3579,8 @@ void ApiWrap::sendMessage(MessageToSend &&message) {
action.generateLocal = true;
sendAction(action);
const auto replyToId = action.replyTo.msgId;
const auto replyTo = replyToId
? peer->owner().message(peer, replyToId)
: nullptr;
const auto topicRootId = replyTo
? replyTo->topicRootId()
: action.replyTo.topicRootId
? action.replyTo.topicRootId
: Data::ForumTopic::kGeneralId;
const auto clearCloudDraft = action.clearDraft;
const auto topicRootId = action.replyTo.topicRootId;
const auto topic = peer->forumTopicFor(topicRootId);
if (!(topic ? Data::CanSendTexts(topic) : Data::CanSendTexts(peer))
|| Api::SendDice(message)) {
@@ -3608,7 +3602,13 @@ void ApiWrap::sendMessage(MessageToSend &&message) {
auto &histories = history->owner().histories();
while (TextUtilities::CutPart(sending, left, MaxMessageSize)) {
const auto exactWebPage = !message.webPage.url.isEmpty();
auto isFirst = true;
while (TextUtilities::CutPart(sending, left, MaxMessageSize)
|| (isFirst && exactWebPage)) {
TextUtilities::Trim(left);
const auto isLast = left.empty();
auto newId = FullMsgId(
peer->id,
_session->data().nextLocalMessageId());
@@ -3622,26 +3622,51 @@ void ApiWrap::sendMessage(MessageToSend &&message) {
MTPstring msgText(MTP_string(sending.text));
auto flags = NewMessageFlags(peer);
auto sendFlags = MTPmessages_SendMessage::Flags(0);
auto mediaFlags = MTPmessages_SendMedia::Flags(0);
if (action.replyTo) {
flags |= MessageFlag::HasReplyInfo;
sendFlags |= MTPmessages_SendMessage::Flag::f_reply_to;
mediaFlags |= MTPmessages_SendMedia::Flag::f_reply_to;
}
const auto replyHeader = NewMessageReplyHeader(action);
const auto ignoreWebPage = message.webPage.removed
|| (exactWebPage && !isLast);
const auto manualWebPage = exactWebPage
&& !ignoreWebPage
&& (message.webPage.manual || (isLast && !isFirst));
MTPMessageMedia media = MTP_messageMediaEmpty();
if (message.webPageId == CancelledWebPageId) {
if (ignoreWebPage) {
sendFlags |= MTPmessages_SendMessage::Flag::f_no_webpage;
} else if (message.webPageId) {
auto page = _session->data().webpage(message.webPageId);
} else if (exactWebPage) {
using PageFlag = MTPDmessageMediaWebPage::Flag;
using PendingFlag = MTPDwebPagePending::Flag;
const auto &fields = message.webPage;
const auto page = _session->data().webpage(fields.id);
media = MTP_messageMediaWebPage(
MTP_flags(PageFlag()
| (manualWebPage ? PageFlag::f_manual : PageFlag())
| (fields.forceLargeMedia
? PageFlag::f_force_large_media
: PageFlag())
| (fields.forceSmallMedia
? PageFlag::f_force_small_media
: PageFlag())),
MTP_webPagePending(
MTP_long(page->id),
MTP_flags(PendingFlag::f_url),
MTP_long(fields.id),
MTP_string(fields.url),
MTP_int(page->pendingTill)));
}
const auto anonymousPost = peer->amAnonymous();
const auto silentPost = ShouldSendSilent(peer, action.options);
FillMessagePostFlags(action, peer, flags);
if (exactWebPage && !ignoreWebPage && message.webPage.invert) {
flags |= MessageFlag::InvertMedia;
sendFlags |= MTPmessages_SendMessage::Flag::f_invert_media;
mediaFlags |= MTPmessages_SendMedia::Flag::f_invert_media;
}
if (silentPost) {
sendFlags |= MTPmessages_SendMessage::Flag::f_silent;
mediaFlags |= MTPmessages_SendMedia::Flag::f_silent;
}
const auto sentEntities = Api::EntitiesToMTP(
_session,
@@ -3649,11 +3674,11 @@ void ApiWrap::sendMessage(MessageToSend &&message) {
Api::ConvertOption::SkipLocal);
if (!sentEntities.v.isEmpty()) {
sendFlags |= MTPmessages_SendMessage::Flag::f_entities;
mediaFlags |= MTPmessages_SendMedia::Flag::f_entities;
}
const auto clearCloudDraft = action.clearDraft;
const auto topicRootId = action.replyTo.topicRootId;
if (clearCloudDraft) {
sendFlags |= MTPmessages_SendMessage::Flag::f_clear_draft;
mediaFlags |= MTPmessages_SendMedia::Flag::f_clear_draft;
history->clearCloudDraft(topicRootId);
history->startSavingCloudDraft(topicRootId);
}
@@ -3665,6 +3690,7 @@ void ApiWrap::sendMessage(MessageToSend &&message) {
: _session->userPeerId();
if (sendAs) {
sendFlags |= MTPmessages_SendMessage::Flag::f_send_as;
mediaFlags |= MTPmessages_SendMedia::Flag::f_send_as;
}
const auto messagePostAuthor = peer->isBroadcast()
? _session->user()->name()
@@ -3672,6 +3698,7 @@ void ApiWrap::sendMessage(MessageToSend &&message) {
if (action.options.scheduled) {
flags |= MessageFlag::IsOrWasScheduled;
sendFlags |= MTPmessages_SendMessage::Flag::f_schedule_date;
mediaFlags |= MTPmessages_SendMedia::Flag::f_schedule_date;
}
const auto viaBotId = UserId();
lastMessage = history->addNewLocalMessage(
@@ -3685,27 +3712,18 @@ void ApiWrap::sendMessage(MessageToSend &&message) {
sending,
media,
HistoryMessageMarkupData());
histories.sendPreparedMessage(
history,
action.replyTo,
randomId,
Data::Histories::PrepareMessage<MTPmessages_SendMessage>(
MTP_flags(sendFlags),
peer->input,
Data::Histories::ReplyToPlaceholder(),
msgText,
MTP_long(randomId),
MTPReplyMarkup(),
sentEntities,
MTP_int(action.options.scheduled),
(sendAs ? sendAs->input : MTP_inputPeerEmpty())
), [=](const MTPUpdates &result, const MTP::Response &response) {
const auto done = [=](
const MTPUpdates &result,
const MTP::Response &response) {
if (clearCloudDraft) {
history->finishSavingCloudDraft(
topicRootId,
UnixtimeFromMsgId(response.outerMsgId));
}
}, [=](const MTP::Error &error, const MTP::Response &response) {
};
const auto fail = [=](
const MTP::Error &error,
const MTP::Response &response) {
if (error.type() == u"MESSAGE_EMPTY"_q) {
lastMessage->destroy();
} else {
@@ -3716,7 +3734,44 @@ void ApiWrap::sendMessage(MessageToSend &&message) {
topicRootId,
UnixtimeFromMsgId(response.outerMsgId));
}
});
};
if (exactWebPage
&& !ignoreWebPage
&& (manualWebPage || sending.empty())) {
histories.sendPreparedMessage(
history,
action.replyTo,
randomId,
Data::Histories::PrepareMessage<MTPmessages_SendMedia>(
MTP_flags(mediaFlags),
peer->input,
Data::Histories::ReplyToPlaceholder(),
Data::WebPageForMTP(message.webPage, true),
msgText,
MTP_long(randomId),
MTPReplyMarkup(),
sentEntities,
MTP_int(message.action.options.scheduled),
(sendAs ? sendAs->input : MTP_inputPeerEmpty())
), done, fail);
} else {
histories.sendPreparedMessage(
history,
action.replyTo,
randomId,
Data::Histories::PrepareMessage<MTPmessages_SendMessage>(
MTP_flags(sendFlags),
peer->input,
Data::Histories::ReplyToPlaceholder(),
msgText,
MTP_long(randomId),
MTPReplyMarkup(),
sentEntities,
MTP_int(action.options.scheduled),
(sendAs ? sendAs->input : MTP_inputPeerEmpty())
), done, fail);
}
isFirst = false;
}
finishForwarding(action);
@@ -3781,7 +3836,7 @@ void ApiWrap::sendInlineResult(
? (*localMessageId)
: _session->data().nextLocalMessageId());
const auto randomId = base::RandomValue<uint64>();
const auto topicRootId = action.replyTo.msgId
const auto topicRootId = action.replyTo.messageId
? action.replyTo.topicRootId
: 0;

View File

@@ -175,7 +175,8 @@ BackgroundPreviewBox::BackgroundPreviewBox(
, _controller(controller)
, _forPeer(args.forPeer)
, _fromMessageId(args.fromMessageId)
, _chatStyle(std::make_unique<Ui::ChatStyle>())
, _chatStyle(std::make_unique<Ui::ChatStyle>(
controller->session().colorIndicesValue()))
, _serviceHistory(_controller->session().data().history(
PeerData::kServiceNotificationsId))
, _service(nullptr)
@@ -434,7 +435,7 @@ void BackgroundPreviewBox::rebuildButtons(bool dark) {
clearButtons();
addButton(_forPeer
? tr::lng_background_apply_button()
: tr::lng_background_apply(), [=] { apply(); });
: tr::lng_settings_apply(), [=] { apply(); });
addButton(tr::lng_cancel(), [=] { closeBox(); });
if (!_forPeer && _paper.hasShareUrl()) {
addLeftButton(tr::lng_background_share(), [=] { share(); });

View File

@@ -85,8 +85,6 @@ confirmInviteTitle: FlatLabel(defaultFlatLabel) {
textFg: windowBoldFg;
style: TextStyle(defaultTextStyle) {
font: font(18px semibold);
linkFont: font(18px semibold);
linkFontOver: font(18px semibold underline);
}
}
confirmInviteAbout: FlatLabel(boxLabel) {
@@ -143,8 +141,6 @@ contactsPadding: margins(16px, 7px, 16px, 7px);
contactsNameTop: 2px;
contactsNameStyle: TextStyle(defaultTextStyle) {
font: semiboldFont;
linkFont: semiboldFont;
linkFontOver: semiboldFont;
}
contactsStatusTop: 23px;
contactsStatusFont: font(fsize);
@@ -199,8 +195,6 @@ localStorageRowTitle: FlatLabel(defaultFlatLabel) {
maxHeight: 20px;
style: TextStyle(defaultTextStyle) {
font: font(14px semibold);
linkFont: font(14px semibold);
linkFontOver: font(14px semibold);
}
}
localStorageRowSize: FlatLabel(defaultFlatLabel) {
@@ -208,8 +202,6 @@ localStorageRowSize: FlatLabel(defaultFlatLabel) {
maxHeight: 20px;
style: TextStyle(defaultTextStyle) {
font: font(14px);
linkFont: font(14px);
linkFontOver: font(14px);
}
}
localStorageClear: defaultBoxButton;
@@ -228,8 +220,6 @@ sharePhotoTop: 6px;
shareBoxListItem: PeerListItem(defaultPeerListItem) {
nameStyle: TextStyle(defaultTextStyle) {
font: font(11px);
linkFont: font(11px);
linkFontOver: font(11px);
}
nameFg: windowFg;
nameFgChecked: windowActiveTextFg;
@@ -537,8 +527,6 @@ adminLogFilterLittleSkip: 16px;
adminLogFilterCheckbox: Checkbox(defaultBoxCheckbox) {
style: TextStyle(boxTextStyle) {
font: font(boxFontSize semibold);
linkFont: font(boxFontSize semibold);
linkFontOver: font(boxFontSize semibold underline);
}
}
adminLogFilterSkip: 32px;
@@ -580,16 +568,12 @@ rightsPhotoButton: UserpicButton(defaultUserpicButton) {
rightsPhotoMargin: margins(20px, 0px, 15px, 18px);
rightsNameStyle: TextStyle(semiboldTextStyle) {
font: font(15px semibold);
linkFont: font(15px semibold);
linkFontOver: font(15px semibold underline);
}
rightsNameTop: 8px;
rightsStatusTop: 32px;
rightsHeaderLabel: FlatLabel(boxLabel) {
style: TextStyle(semiboldTextStyle) {
font: font(boxFontSize semibold);
linkFont: font(boxFontSize semibold);
linkFontOver: font(boxFontSize semibold underline);
}
textFg: windowActiveTextFg;
}
@@ -623,8 +607,6 @@ proxyRowTitlePalette: TextPalette(defaultTextPalette) {
}
proxyRowTitleStyle: TextStyle(defaultTextStyle) {
font: semiboldFont;
linkFont: normalFont;
linkFontOver: normalFont;
}
proxyRowStatusFg: windowSubTextFg;
proxyRowStatusFgOnline: windowActiveTextFg;
@@ -807,8 +789,6 @@ pollResultsQuestion: FlatLabel(defaultFlatLabel) {
textFg: windowBoldFg;
style: TextStyle(defaultTextStyle) {
font: font(16px semibold);
linkFont: font(16px semibold);
linkFontOver: font(16px semibold underline);
}
}
pollResultsVotesCount: FlatLabel(defaultFlatLabel) {
@@ -837,8 +817,6 @@ inviteViaLinkButton: SettingsButton(defaultSettingsButton) {
style: TextStyle(defaultTextStyle) {
font: font(14px semibold);
linkFont: font(14px semibold);
linkFontOver: font(14px semibold underline);
}
height: 20px;

View File

@@ -194,7 +194,7 @@ not_null<Ui::FlatLabel*> CreateWarningLabel(
if (value >= 0) {
result->setText(QString::number(value));
} else {
result->setMarkedText(Ui::Text::PlainLink(
result->setMarkedText(Ui::Text::Colorized(
QString::number(value)));
}
result->setVisible(shown);

View File

@@ -8,26 +8,36 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "boxes/gift_premium_box.h"
#include "apiwrap.h"
#include "api/api_premium.h"
#include "api/api_premium_option.h"
#include "base/unixtime.h"
#include "base/weak_ptr.h"
#include "boxes/peers/prepare_short_info_box.h"
#include "data/data_changes.h"
#include "data/data_channel.h"
#include "data/data_media_types.h" // Data::Giveaway
#include "data/data_peer_values.h" // Data::PeerPremiumValue.
#include "data/data_session.h"
#include "data/data_subscription_option.h"
#include "data/data_user.h"
#include "lang/lang_keys.h"
#include "main/main_session.h"
#include "mainwidget.h"
#include "settings/settings_premium.h"
#include "ui/basic_click_handlers.h" // UrlClickHandler::Open.
#include "ui/boxes/boost_box.h" // StartFireworks.
#include "ui/controls/userpic_button.h"
#include "ui/effects/premium_graphics.h"
#include "ui/effects/premium_stars_colored.h"
#include "ui/effects/premium_top_bar.h"
#include "ui/layers/generic_box.h"
#include "ui/text/format_values.h"
#include "ui/text/text_utilities.h"
#include "ui/widgets/checkbox.h"
#include "ui/widgets/gradient_round_button.h"
#include "ui/wrap/padding_wrap.h"
#include "ui/wrap/table_layout.h"
#include "window/window_peer_menu.h" // ShowChooseRecipientBox.
#include "window/window_session_controller.h"
#include "styles/style_boxes.h"
#include "styles/style_layers.h"
@@ -35,6 +45,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "styles/style_info.h"
#include "styles/style_premium.h"
#include <QtGui/QGuiApplication>
namespace {
constexpr auto kDiscountDivider = 5.;
@@ -225,6 +237,131 @@ void GiftBox(
}, box->lifetime());
}
struct GiftCodeLink {
QString text;
QString link;
};
[[nodiscard]] GiftCodeLink MakeGiftCodeLink(
not_null<Main::Session*> session,
const QString &slug) {
const auto path = u"giftcode/"_q + slug;
return {
session->createInternalLink(path),
session->createInternalLinkFull(path),
};
}
[[nodiscard]] object_ptr<Ui::RpWidget> MakeLinkCopyIcon(
not_null<QWidget*> parent) {
auto result = object_ptr<Ui::RpWidget>(parent);
const auto raw = result.data();
raw->paintRequest() | rpl::start_with_next([=] {
auto p = QPainter(raw);
const auto &icon = st::giveawayGiftCodeLinkCopy;
const auto left = (raw->width() - icon.width()) / 2;
const auto top = (raw->height() - icon.height()) / 2;
icon.paint(p, left, top, raw->width());
}, raw->lifetime());
raw->resize(
st::giveawayGiftCodeLinkCopyWidth,
st::giveawayGiftCodeLinkHeight);
raw->setAttribute(Qt::WA_TransparentForMouseEvents);
return result;
}
[[nodiscard]] tr::phrase<lngtag_count> GiftDurationPhrase(int months) {
return (months < 12)
? tr::lng_premium_gift_duration_months
: tr::lng_premium_gift_duration_years;
}
[[nodiscard]] object_ptr<Ui::RpWidget> MakePeerTableValue(
not_null<QWidget*> parent,
not_null<Window::SessionNavigation*> controller,
PeerId id) {
auto result = object_ptr<Ui::AbstractButton>(parent);
const auto raw = result.data();
const auto &st = st::giveawayGiftCodeUserpic;
raw->resize(raw->width(), st.photoSize);
const auto peer = controller->session().data().peer(id);
const auto userpic = Ui::CreateChild<Ui::UserpicButton>(raw, peer, st);
const auto label = Ui::CreateChild<Ui::FlatLabel>(
raw,
peer->name(),
st::giveawayGiftCodeValue);
raw->widthValue(
) | rpl::start_with_next([=](int width) {
const auto position = st::giveawayGiftCodeNamePosition;
label->resizeToNaturalWidth(width - position.x());
label->moveToLeft(position.x(), position.y(), width);
const auto top = (raw->height() - userpic->height()) / 2;
userpic->moveToLeft(0, top, width);
}, label->lifetime());
userpic->setAttribute(Qt::WA_TransparentForMouseEvents);
label->setAttribute(Qt::WA_TransparentForMouseEvents);
label->setTextColorOverride(st::windowActiveTextFg->c);
raw->setClickedCallback([=] {
controller->uiShow()->showBox(PrepareShortInfoBox(peer, controller));
});
return result;
}
void AddTableRow(
not_null<Ui::TableLayout*> table,
rpl::producer<QString> label,
object_ptr<Ui::RpWidget> value,
style::margins valueMargins) {
table->addRow(
object_ptr<Ui::FlatLabel>(
table,
std::move(label),
st::giveawayGiftCodeLabel),
std::move(value),
st::giveawayGiftCodeLabelMargin,
valueMargins);
}
not_null<Ui::FlatLabel*> AddTableRow(
not_null<Ui::TableLayout*> table,
rpl::producer<QString> label,
rpl::producer<TextWithEntities> value) {
auto widget = object_ptr<Ui::FlatLabel>(
table,
std::move(value),
st::giveawayGiftCodeValue);
const auto result = widget.data();
AddTableRow(
table,
std::move(label),
std::move(widget),
st::giveawayGiftCodeValueMargin);
return result;
}
void AddTableRow(
not_null<Ui::TableLayout*> table,
rpl::producer<QString> label,
not_null<Window::SessionNavigation*> controller,
PeerId id) {
if (!id) {
return;
}
AddTableRow(
table,
std::move(label),
MakePeerTableValue(table, controller, id),
st::giveawayGiftCodePeerMargin);
}
} // namespace
GiftPremiumValidator::GiftPremiumValidator(
@@ -263,3 +400,405 @@ void GiftPremiumValidator::showBox(not_null<UserData*> user) {
_requestId = 0;
}).send();
}
rpl::producer<QString> GiftDurationValue(int months) {
return GiftDurationPhrase(months)(
lt_count,
rpl::single(float64((months < 12) ? months : (months / 12))));
}
QString GiftDuration(int months) {
return GiftDurationPhrase(months)(
tr::now,
lt_count,
(months < 12) ? months : (months / 12));
}
void GiftCodeBox(
not_null<Ui::GenericBox*> box,
not_null<Window::SessionNavigation*> controller,
const QString &slug) {
struct State {
rpl::variable<Api::GiftCode> data;
rpl::variable<bool> used;
bool sent = false;
};
const auto session = &controller->session();
const auto state = box->lifetime().make_state<State>(State{});
state->data = session->api().premium().giftCodeValue(slug);
state->used = state->data.value(
) | rpl::map([=](const Api::GiftCode &data) {
return data.used;
});
box->setWidth(st::boxWideWidth);
box->setStyle(st::giveawayGiftCodeBox);
box->setNoContentMargin(true);
const auto bar = box->setPinnedToTopContent(
object_ptr<Ui::Premium::TopBar>(
box,
st::giveawayGiftCodeCover,
nullptr,
rpl::conditional(
state->used.value(),
tr::lng_gift_link_used_title(),
tr::lng_gift_link_title()),
rpl::conditional(
state->used.value(),
tr::lng_gift_link_used_about(Ui::Text::RichLangValue),
tr::lng_gift_link_about(Ui::Text::RichLangValue)),
true));
const auto max = st::giveawayGiftCodeTopHeight;
bar->setMaximumHeight(max);
bar->setMinimumHeight(st::infoLayerTopBarHeight);
bar->resize(bar->width(), bar->maximumHeight());
const auto link = MakeGiftCodeLink(&controller->session(), slug);
box->addRow(
Ui::MakeLinkLabel(
box,
rpl::single(link.text),
rpl::single(link.link),
box->uiShow(),
MakeLinkCopyIcon(box)),
st::giveawayGiftCodeLinkMargin);
auto table = box->addRow(
object_ptr<Ui::TableLayout>(
box,
st::giveawayGiftCodeTable),
st::giveawayGiftCodeTableMargin);
const auto current = state->data.current();
AddTableRow(
table,
tr::lng_gift_link_label_from(),
controller,
current.from);
if (current.to) {
AddTableRow(
table,
tr::lng_gift_link_label_to(),
controller,
current.to);
} else {
AddTableRow(
table,
tr::lng_gift_link_label_to(),
tr::lng_gift_link_label_to_unclaimed(Ui::Text::WithEntities));
}
AddTableRow(
table,
tr::lng_gift_link_label_gift(),
tr::lng_gift_link_gift_premium(
lt_duration,
GiftDurationValue(current.months) | Ui::Text::ToWithEntities(),
Ui::Text::WithEntities));
const auto reason = AddTableRow(
table,
tr::lng_gift_link_label_reason(),
(current.giveawayId
? ((current.to
? tr::lng_gift_link_reason_giveaway
: tr::lng_gift_link_reason_unclaimed)(
) | Ui::Text::ToLink())
: current.giveaway
? ((current.to
? tr::lng_gift_link_reason_giveaway
: tr::lng_gift_link_reason_unclaimed)(
Ui::Text::WithEntities
) | rpl::type_erased())
: tr::lng_gift_link_reason_chosen(Ui::Text::WithEntities)));
reason->setClickHandlerFilter([=](const auto &...) {
controller->showPeerHistory(
current.from,
Window::SectionShow::Way::Forward,
current.giveawayId);
return false;
});
if (current.date) {
AddTableRow(
table,
tr::lng_gift_link_label_date(),
rpl::single(Ui::Text::WithEntities(
langDateTime(base::unixtime::parse(current.date)))));
}
auto shareLink = tr::lng_gift_link_also_send_link(
) | rpl::map([](const QString &text) {
return Ui::Text::Link(text);
});
auto richDate = [](const Api::GiftCode &data) {
return TextWithEntities{
langDateTime(base::unixtime::parse(data.used)),
};
};
const auto footer = box->addRow(
object_ptr<Ui::FlatLabel>(
box,
rpl::conditional(
state->used.value(),
tr::lng_gift_link_used_footer(
lt_date,
state->data.value() | rpl::map(richDate),
Ui::Text::WithEntities),
tr::lng_gift_link_also_send(
lt_link,
std::move(shareLink),
Ui::Text::WithEntities)),
st::giveawayGiftCodeFooter),
st::giveawayGiftCodeFooterMargin);
footer->setClickHandlerFilter([=](const auto &...) {
const auto chosen = [=](not_null<Data::Thread*> thread) {
const auto content = controller->parentController()->content();
return content->shareUrl(
thread,
MakeGiftCodeLink(&controller->session(), slug).link,
QString());
};
Window::ShowChooseRecipientBox(controller, chosen);
return false;
});
const auto close = Ui::CreateChild<Ui::IconButton>(
box.get(),
st::boxTitleClose);
close->setClickedCallback([=] {
box->closeBox();
});
box->widthValue(
) | rpl::start_with_next([=](int width) {
close->moveToRight(0, 0);
}, box->lifetime());
const auto button = box->addButton(rpl::conditional(
state->used.value(),
tr::lng_box_ok(),
tr::lng_gift_link_use()
), [=] {
if (state->used.current()) {
box->closeBox();
} else if (!state->sent) {
state->sent = true;
const auto done = crl::guard(box, [=](const QString &error) {
if (error.isEmpty()) {
auto copy = state->data.current();
copy.used = base::unixtime::now();
state->data = std::move(copy);
Ui::StartFireworks(box->parentWidget());
} else {
box->uiShow()->showToast(error);
}
});
controller->session().api().premium().applyGiftCode(slug, done);
}
});
const auto buttonPadding = st::giveawayGiftCodeBox.buttonPadding;
const auto buttonWidth = st::boxWideWidth
- buttonPadding.left()
- buttonPadding.right();
button->widthValue() | rpl::filter([=] {
return (button->widthNoMargins() != buttonWidth);
}) | rpl::start_with_next([=] {
button->resizeToWidth(buttonWidth);
}, button->lifetime());
}
void ResolveGiftCode(
not_null<Window::SessionNavigation*> controller,
const QString &slug) {
const auto done = [=](Api::GiftCode code) {
if (!code) {
controller->showToast(tr::lng_gift_link_expired(tr::now));
} else {
controller->uiShow()->showBox(Box(GiftCodeBox, controller, slug));
}
};
controller->session().api().premium().checkGiftCode(
slug,
crl::guard(controller, done));
}
void GiveawayInfoBox(
not_null<Ui::GenericBox*> box,
not_null<Window::SessionNavigation*> controller,
Data::Giveaway giveaway,
Api::GiveawayInfo info) {
using State = Api::GiveawayState;
const auto finished = (info.state == State::Finished)
|| (info.state == State::Refunded);
box->setTitle((finished
? tr::lng_prizes_end_title
: tr::lng_prizes_how_title)());
const auto first = !giveaway.channels.empty()
? giveaway.channels.front()->name()
: u"channel"_q;
auto text = (finished
? tr::lng_prizes_end_text
: tr::lng_prizes_how_text)(
tr::now,
lt_admins,
tr::lng_prizes_admins(
tr::now,
lt_count,
giveaway.quantity,
lt_channel,
Ui::Text::Bold(first),
lt_duration,
TextWithEntities{ GiftDuration(giveaway.months) },
Ui::Text::RichLangValue),
Ui::Text::RichLangValue);
const auto many = (giveaway.channels.size() > 1);
const auto count = info.winnersCount
? info.winnersCount
: giveaway.quantity;
auto winners = giveaway.all
? (many
? tr::lng_prizes_winners_all_of_many
: tr::lng_prizes_winners_all_of_one)(
tr::now,
lt_count,
count,
lt_channel,
Ui::Text::Bold(first),
Ui::Text::RichLangValue)
: (many
? tr::lng_prizes_winners_new_of_many
: tr::lng_prizes_winners_new_of_one)(
tr::now,
lt_count,
count,
lt_channel,
Ui::Text::Bold(first),
lt_start_date,
Ui::Text::Bold(
langDateTime(base::unixtime::parse(info.startDate))),
Ui::Text::RichLangValue);
text.append("\n\n").append((finished
? tr::lng_prizes_end_when_finish
: tr::lng_prizes_how_when_finish)(
tr::now,
lt_date,
Ui::Text::Bold(langDayOfMonthFull(
base::unixtime::parse(giveaway.untilDate).date())),
lt_winners,
winners,
Ui::Text::RichLangValue));
if (info.activatedCount > 0) {
text.append(' ').append(tr::lng_prizes_end_activated(
tr::now,
lt_count,
info.activatedCount));
}
if (!info.giftCode.isEmpty()) {
text.append("\n\n");
text.append(tr::lng_prizes_you_won(
tr::now,
lt_cup,
QString::fromUtf8("\xf0\x9f\x8f\x86")));
} else if (info.state == State::Finished) {
text.append("\n\n");
text.append(tr::lng_prizes_you_didnt(tr::now));
} else if (info.state == State::Preparing) {
} else if (info.state != State::Refunded) {
if (info.adminChannelId) {
const auto channel = controller->session().data().channel(
info.adminChannelId);
text.append("\n\n").append(tr::lng_prizes_how_no_admin(
tr::now,
lt_channel,
Ui::Text::Bold(channel->name()),
Ui::Text::RichLangValue));
} else if (info.tooEarlyDate) {
text.append("\n\n").append(tr::lng_prizes_how_no_joined(
tr::now,
lt_date,
Ui::Text::Bold(
langDateTime(base::unixtime::parse(info.tooEarlyDate))),
Ui::Text::RichLangValue));
} else if (!info.disallowedCountry.isEmpty()) {
text.append("\n\n").append(tr::lng_prizes_how_no_country(
tr::now,
Ui::Text::RichLangValue));
} else if (info.participating) {
text.append("\n\n").append((many
? tr::lng_prizes_how_yes_joined_many
: tr::lng_prizes_how_yes_joined_one)(
tr::now,
lt_channel,
Ui::Text::Bold(first),
Ui::Text::RichLangValue));
} else {
text.append("\n\n").append((many
? tr::lng_prizes_how_participate_many
: tr::lng_prizes_how_participate_one)(
tr::now,
lt_channel,
Ui::Text::Bold(first),
lt_date,
Ui::Text::Bold(langDayOfMonthFull(
base::unixtime::parse(giveaway.untilDate).date())),
Ui::Text::RichLangValue));
}
}
const auto padding = st::boxPadding;
box->addRow(
object_ptr<Ui::FlatLabel>(
box.get(),
rpl::single(std::move(text)),
st::boxLabel),
{ padding.left(), 0, padding.right(), padding.bottom() });
if (info.state == State::Refunded) {
const auto wrap = box->addRow(
object_ptr<Ui::PaddingWrap<Ui::FlatLabel>>(
box.get(),
object_ptr<Ui::FlatLabel>(
box.get(),
tr::lng_prizes_cancelled(),
st::giveawayRefundedLabel),
st::giveawayRefundedPadding),
{ padding.left(), 0, padding.right(), padding.bottom() });
const auto bg = wrap->lifetime().make_state<Ui::RoundRect>(
st::boxRadius,
st::attentionBoxButton.textBgOver);
wrap->paintRequest() | rpl::start_with_next([=] {
auto p = QPainter(wrap);
bg->paint(p, wrap->rect());
}, wrap->lifetime());
}
if (const auto slug = info.giftCode; !slug.isEmpty()) {
box->addButton(tr::lng_prizes_view_prize(), [=] {
ResolveGiftCode(controller, slug);
});
box->addButton(tr::lng_cancel(), [=] { box->closeBox(); });
} else {
box->addButton(tr::lng_close(), [=] { box->closeBox(); });
}
}
void ResolveGiveawayInfo(
not_null<Window::SessionNavigation*> controller,
not_null<PeerData*> peer,
MsgId messageId,
Data::Giveaway giveaway) {
const auto show = [=](Api::GiveawayInfo info) {
if (!info) {
controller->showToast(
tr::lng_confirm_phone_link_invalid(tr::now));
} else {
controller->uiShow()->showBox(
Box(GiveawayInfoBox, controller, giveaway, info));
}
};
controller->session().api().premium().resolveGiveawayInfo(
peer,
messageId,
crl::guard(controller, show));
}

View File

@@ -11,8 +11,21 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
class UserData;
namespace Api {
struct GiftCode;
} // namespace Api
namespace Data {
struct Giveaway;
} // namespace Data
namespace Ui {
class GenericBox;
} // namespace Ui
namespace Window {
class SessionController;
class SessionNavigation;
} // namespace Window
class GiftPremiumValidator final {
@@ -29,3 +42,20 @@ private:
mtpRequestId _requestId = 0;
};
[[nodiscard]] rpl::producer<QString> GiftDurationValue(int months);
[[nodiscard]] QString GiftDuration(int months);
void GiftCodeBox(
not_null<Ui::GenericBox*> box,
not_null<Window::SessionNavigation*> controller,
const QString &slug);
void ResolveGiftCode(
not_null<Window::SessionNavigation*> controller,
const QString &slug);
void ResolveGiveawayInfo(
not_null<Window::SessionNavigation*> controller,
not_null<PeerData*> peer,
MsgId messageId,
Data::Giveaway giveaway);

View File

@@ -456,7 +456,7 @@ int PeerListController::descriptionTopSkipMin() const {
void PeerListBox::addSelectItem(
not_null<PeerData*> peer,
anim::type animated) {
const auto respect = _controller->respectSavedMessagesChat();
const auto respect = !_controller->savedMessagesChatStatus().isEmpty();
const auto text = (respect && peer->isSelf())
? tr::lng_saved_short(tr::now)
: (respect && peer->isRepliesChat())
@@ -579,8 +579,8 @@ void PeerListRow::refreshStatus() {
_statusType = StatusType::LastSeen;
_statusValidTill = 0;
if (auto user = peer()->asUser()) {
if (_isSavedMessagesChat) {
setStatusText(tr::lng_saved_forward_here(tr::now));
if (!_savedMessagesStatus.isEmpty()) {
setStatusText(_savedMessagesStatus);
} else {
auto time = base::unixtime::now();
setStatusText(Data::OnlineText(user, time));
@@ -613,7 +613,7 @@ void PeerListRow::refreshName(const style::PeerListItem &st) {
if (!_initialized) {
return;
}
const auto text = _isSavedMessagesChat
const auto text = !_savedMessagesStatus.isEmpty()
? tr::lng_saved_messages(tr::now)
: _isRepliesMessagesChat
? tr::lng_replies_messages(tr::now)
@@ -683,7 +683,7 @@ QString PeerListRow::generateName() {
}
QString PeerListRow::generateShortName() {
return _isSavedMessagesChat
return !_savedMessagesStatus.isEmpty()
? tr::lng_saved_short(tr::now)
: _isRepliesMessagesChat
? tr::lng_replies_messages(tr::now)
@@ -699,7 +699,7 @@ Ui::PeerUserpicView &PeerListRow::ensureUserpicView() {
PaintRoundImageCallback PeerListRow::generatePaintUserpicCallback(
bool forceRound) {
const auto saved = _isSavedMessagesChat;
const auto saved = !_savedMessagesStatus.isEmpty();
const auto replies = _isRepliesMessagesChat;
const auto peer = this->peer();
auto userpic = saved ? Ui::PeerUserpicView() : ensureUserpicView();
@@ -745,7 +745,9 @@ int PeerListRow::paintNameIconGetWidth(
int availableWidth,
int outerWidth,
bool selected) {
if (special() || _isSavedMessagesChat || _isRepliesMessagesChat) {
if (special()
|| !_savedMessagesStatus.isEmpty()
|| _isRepliesMessagesChat) {
return 0;
}
return _bagde.drawGetWidth(
@@ -758,7 +760,7 @@ int PeerListRow::paintNameIconGetWidth(
nameWidth,
outerWidth,
{
.peer = _peer,
.peer = peer(),
.verified = &(selected
? st::dialogsVerifiedIconOver
: st::dialogsVerifiedIcon),
@@ -855,7 +857,7 @@ void PeerListRow::paintDisabledCheckUserpic(
auto iconBorderPen = st.checkbox.check.border->p;
iconBorderPen.setWidth(st.checkbox.selectWidth);
if (_isSavedMessagesChat) {
if (!_savedMessagesStatus.isEmpty()) {
Ui::EmptyUserpic::PaintSavedMessages(p, userpicLeft, userpicTop, outerWidth, userpicRadius * 2);
} else if (_isRepliesMessagesChat) {
Ui::EmptyUserpic::PaintRepliesMessages(p, userpicLeft, userpicTop, outerWidth, userpicRadius * 2);
@@ -1046,9 +1048,10 @@ void PeerListContent::setRowHidden(not_null<PeerListRow*> row, bool hidden) {
}
void PeerListContent::addRowEntry(not_null<PeerListRow*> row) {
if (_controller->respectSavedMessagesChat() && !row->special()) {
const auto savedMessagesStatus = _controller->savedMessagesChatStatus();
if (!savedMessagesStatus.isEmpty() && !row->special()) {
if (row->peer()->isSelf()) {
row->setIsSavedMessagesChat(true);
row->setSavedMessagesChatStatus(savedMessagesStatus);
} else if (row->peer()->isRepliesChat()) {
row->setIsRepliesMessagesChat(true);
}

View File

@@ -185,8 +185,8 @@ public:
void setIsSearchResult(bool isSearchResult) {
_isSearchResult = isSearchResult;
}
void setIsSavedMessagesChat(bool isSavedMessagesChat) {
_isSavedMessagesChat = isSavedMessagesChat;
void setSavedMessagesChatStatus(QString savedMessagesStatus) {
_savedMessagesStatus = savedMessagesStatus;
}
void setIsRepliesMessagesChat(bool isRepliesMessagesChat) {
_isRepliesMessagesChat = isRepliesMessagesChat;
@@ -278,12 +278,12 @@ private:
StatusType _statusType = StatusType::Online;
crl::time _statusValidTill = 0;
base::flat_set<QChar> _nameFirstLetters;
QString _savedMessagesStatus;
int _absoluteIndex = -1;
State _disabledState = State::Active;
bool _hidden : 1 = false;
bool _initialized : 1 = false;
bool _isSearchResult : 1 = false;
bool _isSavedMessagesChat : 1 = false;
bool _isRepliesMessagesChat : 1 = false;
};
@@ -517,8 +517,8 @@ public:
void peerListSearchAddRow(PeerListRowId id) override;
void peerListSearchRefreshRows() override;
[[nodiscard]] virtual bool respectSavedMessagesChat() const {
return false;
[[nodiscard]] virtual QString savedMessagesChatStatus() const {
return QString();
}
[[nodiscard]] virtual int customRowHeight() {
Unexpected("PeerListController::customRowHeight.");

View File

@@ -170,7 +170,7 @@ void PeerListRowWithLink::rightActionPaint(
int outerWidth,
bool selected,
bool actionSelected) {
p.setFont(actionSelected ? st::linkOverFont : st::linkFont);
p.setFont(actionSelected ? st::linkFontOver : st::linkFont);
p.setPen(actionSelected ? st::defaultLinkButton.overColor : st::defaultLinkButton.color);
p.drawTextLeft(x, y, outerWidth, _action, _actionWidth);
}
@@ -313,7 +313,7 @@ void ChatsListBoxController::rebuildRows() {
return count;
};
auto added = 0;
if (respectSavedMessagesChat()) {
if (!savedMessagesChatStatus().isEmpty()) {
if (appendRow(session().data().history(session().user()))) {
++added;
}
@@ -330,7 +330,7 @@ void ChatsListBoxController::rebuildRows() {
const auto history = static_cast<const Row&>(a).history();
return history->inChatList();
});
if (respectSavedMessagesChat()) {
if (!savedMessagesChatStatus().isEmpty()) {
delegate()->peerListPartitionRows([](const PeerListRow &a) {
return a.peer()->isSelf();
});
@@ -696,6 +696,10 @@ void ChooseRecipientBoxController::rowClicked(not_null<PeerListRow*> row) {
}
}
QString ChooseRecipientBoxController::savedMessagesChatStatus() const {
return tr::lng_saved_forward_here(tr::now);
}
auto ChooseRecipientBoxController::createRow(
not_null<History*> history) -> std::unique_ptr<Row> {
const auto peer = history->peer;

View File

@@ -218,9 +218,7 @@ public:
Main::Session &session() const override;
void rowClicked(not_null<PeerListRow*> row) override;
bool respectSavedMessagesChat() const override {
return true;
}
QString savedMessagesChatStatus() const override;
protected:
void prepareViewHook() override;

View File

@@ -45,8 +45,8 @@ public:
Main::Session &session() const override;
void rowClicked(not_null<PeerListRow*> row) override;
bool respectSavedMessagesChat() const override {
return true;
QString savedMessagesChatStatus() const override {
return tr::lng_saved_forward_here(tr::now);
}
private:

View File

@@ -0,0 +1,936 @@
/*
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 "boxes/peers/edit_peer_color_box.h"
#include "apiwrap.h"
#include "base/unixtime.h"
#include "chat_helpers/compose/compose_show.h"
#include "data/data_changes.h"
#include "data/data_channel.h"
#include "data/stickers/data_custom_emoji.h"
#include "data/data_peer.h"
#include "data/data_session.h"
#include "data/data_web_page.h"
#include "history/view/history_view_element.h"
#include "history/history.h"
#include "history/history_item.h"
#include "info/boosts/info_boosts_widget.h"
#include "info/profile/info_profile_emoji_status_panel.h"
#include "info/info_memento.h"
#include "lang/lang_keys.h"
#include "main/main_account.h"
#include "main/main_app_config.h"
#include "main/main_session.h"
#include "settings/settings_common.h"
#include "settings/settings_premium.h"
#include "ui/boxes/boost_box.h"
#include "ui/chat/chat_style.h"
#include "ui/chat/chat_theme.h"
#include "ui/effects/path_shift_gradient.h"
#include "ui/layers/generic_box.h"
#include "ui/text/text_utilities.h"
#include "ui/widgets/buttons.h"
#include "ui/painter.h"
#include "window/themes/window_theme.h"
#include "window/section_widget.h"
#include "window/window_session_controller.h"
#include "styles/style_chat.h"
#include "styles/style_layers.h"
#include "styles/style_menu_icons.h"
#include "styles/style_settings.h"
#include "styles/style_widgets.h"
namespace {
using namespace Settings;
constexpr auto kFakeChannelId = ChannelId(0xFFFFFFF000ULL);
constexpr auto kFakeWebPageId = WebPageId(0xFFFFFFFF00000000ULL);
constexpr auto kSelectAnimationDuration = crl::time(150);
class ColorSample final : public Ui::AbstractButton {
public:
ColorSample(
not_null<QWidget*> parent,
std::shared_ptr<Ui::ChatStyle> style,
rpl::producer<uint8> colorIndex,
const QString &name);
ColorSample(
not_null<QWidget*> parent,
std::shared_ptr<Ui::ChatStyle> style,
uint8 colorIndex,
bool selected);
[[nodiscard]] uint8 index() const;
int naturalWidth() const override;
void setSelected(bool selected);
private:
void paintEvent(QPaintEvent *e) override;
std::shared_ptr<Ui::ChatStyle> _style;
Ui::Text::String _name;
uint8 _index = 0;
Ui::Animations::Simple _selectAnimation;
bool _selected = false;
bool _simple = false;
};
class PreviewDelegate final : public HistoryView::DefaultElementDelegate {
public:
PreviewDelegate(
not_null<QWidget*> parent,
not_null<Ui::ChatStyle*> st,
Fn<void()> update);
bool elementAnimationsPaused() override;
not_null<Ui::PathShiftGradient*> elementPathShiftGradient() override;
HistoryView::Context elementContext() override;
private:
const not_null<QWidget*> _parent;
const std::unique_ptr<Ui::PathShiftGradient> _pathGradient;
};
class PreviewWrap final : public Ui::RpWidget {
public:
PreviewWrap(
not_null<Ui::GenericBox*> box,
std::shared_ptr<Ui::ChatStyle> style,
std::shared_ptr<Ui::ChatTheme> theme,
not_null<PeerData*> peer,
rpl::producer<uint8> colorIndexValue,
rpl::producer<DocumentId> backgroundEmojiId);
~PreviewWrap();
private:
using Element = HistoryView::Element;
void paintEvent(QPaintEvent *e) override;
void initElements();
const not_null<Ui::GenericBox*> _box;
const not_null<PeerData*> _peer;
const not_null<ChannelData*> _fake;
const not_null<History*> _history;
const not_null<WebPageData*> _webpage;
const std::shared_ptr<Ui::ChatTheme> _theme;
const std::shared_ptr<Ui::ChatStyle> _style;
const std::unique_ptr<PreviewDelegate> _delegate;
const not_null<HistoryItem*> _replyToItem;
const not_null<HistoryItem*> _replyItem;
std::unique_ptr<Element> _element;
Ui::PeerUserpicView _userpic;
QPoint _position;
};
ColorSample::ColorSample(
not_null<QWidget*> parent,
std::shared_ptr<Ui::ChatStyle> style,
rpl::producer<uint8> colorIndex,
const QString &name)
: AbstractButton(parent)
, _style(style)
, _name(st::semiboldTextStyle, name) {
std::move(
colorIndex
) | rpl::start_with_next([=](uint8 index) {
_index = index;
update();
}, lifetime());
}
ColorSample::ColorSample(
not_null<QWidget*> parent,
std::shared_ptr<Ui::ChatStyle> style,
uint8 colorIndex,
bool selected)
: AbstractButton(parent)
, _style(style)
, _index(colorIndex)
, _selected(selected)
, _simple(true) {
}
void ColorSample::setSelected(bool selected) {
if (_selected == selected) {
return;
}
_selected = selected;
_selectAnimation.start(
[=] { update(); },
_selected ? 0. : 1.,
_selected ? 1. : 0.,
kSelectAnimationDuration);
}
void ColorSample::paintEvent(QPaintEvent *e) {
auto p = Painter(this);
auto hq = PainterHighQualityEnabler(p);
const auto colors = _style->coloredValues(false, _index);
if (!_simple && !colors.outlines[1].alpha()) {
const auto radius = height() / 2;
p.setPen(Qt::NoPen);
p.setBrush(colors.bg);
p.drawRoundedRect(rect(), radius, radius);
const auto padding = st::settingsColorSamplePadding;
p.setPen(colors.name);
p.setBrush(Qt::NoBrush);
p.setFont(st::semiboldFont);
_name.drawLeftElided(
p,
padding.left(),
padding.top(),
width() - padding.left() - padding.right(),
width(),
1,
style::al_top);
} else {
const auto size = float64(width());
const auto half = size / 2.;
const auto full = QRectF(-half, -half, size, size);
p.translate(size / 2., size / 2.);
p.setPen(Qt::NoPen);
if (colors.outlines[1].alpha()) {
p.rotate(-45.);
p.setClipRect(-size, 0, 3 * size, size);
p.setBrush(colors.outlines[1]);
p.drawEllipse(full);
p.setClipRect(-size, -size, 3 * size, size);
}
p.setBrush(colors.outlines[0]);
p.drawEllipse(full);
p.setClipping(false);
if (colors.outlines[2].alpha()) {
const auto multiplier = size / st::settingsColorSampleSize;
const auto center = st::settingsColorSampleCenter * multiplier;
const auto radius = st::settingsColorSampleCenterRadius
* multiplier;
p.setBrush(colors.outlines[2]);
p.drawRoundedRect(
QRectF(-center / 2., -center / 2., center, center),
radius,
radius);
}
const auto selected = _selectAnimation.value(_selected ? 1. : 0.);
if (selected > 0) {
const auto line = st::settingsColorRadioStroke * 1.;
const auto thickness = selected * line;
auto pen = st::boxBg->p;
pen.setWidthF(thickness);
p.setBrush(Qt::NoBrush);
p.setPen(pen);
const auto skip = 1.5 * line;
p.drawEllipse(full.marginsRemoved({ skip, skip, skip, skip }));
}
}
}
uint8 ColorSample::index() const {
return _index;
}
int ColorSample::naturalWidth() const {
if (_name.isEmpty() || _style->colorPatternIndex(_index)) {
return st::settingsColorSampleSize;
}
const auto padding = st::settingsColorSamplePadding;
return std::max(
padding.left() + _name.maxWidth() + padding.right(),
padding.top() + st::semiboldFont->height + padding.bottom());
}
PreviewWrap::PreviewWrap(
not_null<Ui::GenericBox*> box,
std::shared_ptr<Ui::ChatStyle> style,
std::shared_ptr<Ui::ChatTheme> theme,
not_null<PeerData*> peer,
rpl::producer<uint8> colorIndexValue,
rpl::producer<DocumentId> backgroundEmojiId)
: RpWidget(box)
, _box(box)
, _peer(peer)
, _fake(_peer->owner().channel(kFakeChannelId))
, _history(_fake->owner().history(_fake))
, _webpage(_peer->owner().webpage(
kFakeWebPageId,
WebPageType::Article,
u"internal:peer-color-webpage-preview"_q,
u"internal:peer-color-webpage-preview"_q,
tr::lng_settings_color_link_name(tr::now),
tr::lng_settings_color_link_title(tr::now),
{ tr::lng_settings_color_link_description(tr::now) },
nullptr, // photo
nullptr, // document
WebPageCollage(),
0, // duration
QString(), // author
false, // hasLargeMedia
0)) // pendingTill
, _theme(theme)
, _style(style)
, _delegate(std::make_unique<PreviewDelegate>(box, _style.get(), [=] {
update();
}))
, _replyToItem(_history->addNewLocalMessage(
_history->nextNonHistoryEntryId(),
(MessageFlag::FakeHistoryItem
| MessageFlag::HasFromId
| MessageFlag::Post),
UserId(), // via
FullReplyTo(),
base::unixtime::now(), // date
_fake->id,
QString(), // postAuthor
TextWithEntities{ _peer->isSelf()
? tr::lng_settings_color_reply(tr::now)
: tr::lng_settings_color_reply_channel(tr::now),
},
MTP_messageMediaEmpty(),
HistoryMessageMarkupData(),
uint64(0)))
, _replyItem(_history->addNewLocalMessage(
_history->nextNonHistoryEntryId(),
(MessageFlag::FakeHistoryItem
| MessageFlag::HasFromId
| MessageFlag::HasReplyInfo
| MessageFlag::Post),
UserId(), // via
FullReplyTo{ .messageId = _replyToItem->fullId() },
base::unixtime::now(), // date
_fake->id,
QString(), // postAuthor
TextWithEntities{ _peer->isSelf()
? tr::lng_settings_color_text(tr::now)
: tr::lng_settings_color_text_channel(tr::now),
},
MTP_messageMediaWebPage(
MTP_flags(0),
MTP_webPagePending(
MTP_flags(0),
MTP_long(_webpage->id),
MTPstring(),
MTP_int(0))),
HistoryMessageMarkupData(),
uint64(0)))
, _element(_replyItem->createView(_delegate.get()))
, _position(0, st::msgMargin.bottom()) {
_style->apply(_theme.get());
_fake->setName(peer->name(), QString());
std::move(colorIndexValue) | rpl::start_with_next([=](uint8 index) {
_fake->changeColorIndex(index);
update();
}, lifetime());
std::move(backgroundEmojiId) | rpl::start_with_next([=](DocumentId id) {
_fake->changeBackgroundEmojiId(id);
update();
}, lifetime());
const auto session = &_history->session();
session->data().viewRepaintRequest(
) | rpl::start_with_next([=](not_null<const Element*> view) {
if (view == _element.get()) {
update();
}
}, lifetime());
initElements();
}
PreviewWrap::~PreviewWrap() {
_element = nullptr;
_replyItem->destroy();
_replyToItem->destroy();
}
void PreviewWrap::paintEvent(QPaintEvent *e) {
auto p = Painter(this);
const auto clip = e->rect();
p.setClipRect(clip);
Window::SectionWidget::PaintBackground(
p,
_theme.get(),
QSize(_box->width(), _box->window()->height()),
clip);
auto context = _theme->preparePaintContext(
_style.get(),
rect(),
clip,
!window()->isActiveWindow());
p.translate(_position);
_element->draw(p, context);
if (_element->displayFromPhoto()) {
auto userpicBottom = height()
- _element->marginBottom()
- _element->marginTop();
const auto userpicTop = userpicBottom - st::msgPhotoSize;
_peer->paintUserpicLeft(
p,
_userpic,
st::historyPhotoLeft,
userpicTop,
width(),
st::msgPhotoSize);
}
}
void PreviewWrap::initElements() {
_element->initDimensions();
widthValue(
) | rpl::filter([=](int width) {
return width > st::msgMinWidth;
}) | rpl::start_with_next([=](int width) {
const auto height = _position.y()
+ _element->resizeGetHeight(width)
+ st::msgMargin.top();
resize(width, height);
}, lifetime());
}
PreviewDelegate::PreviewDelegate(
not_null<QWidget*> parent,
not_null<Ui::ChatStyle*> st,
Fn<void()> update)
: _parent(parent)
, _pathGradient(HistoryView::MakePathShiftGradient(st, update)) {
}
bool PreviewDelegate::elementAnimationsPaused() {
return _parent->window()->isActiveWindow();
}
auto PreviewDelegate::elementPathShiftGradient()
-> not_null<Ui::PathShiftGradient*> {
return _pathGradient.get();
}
HistoryView::Context PreviewDelegate::elementContext() {
return HistoryView::Context::AdminLog;
}
void Set(
std::shared_ptr<ChatHelpers::Show> show,
not_null<PeerData*> peer,
uint8 colorIndex,
DocumentId backgroundEmojiId) {
const auto wasIndex = peer->colorIndex();
const auto wasEmojiId = peer->backgroundEmojiId();
const auto setLocal = [=](uint8 index, DocumentId emojiId) {
using UpdateFlag = Data::PeerUpdate::Flag;
peer->changeColorIndex(index);
peer->changeBackgroundEmojiId(emojiId);
peer->session().changes().peerUpdated(
peer,
UpdateFlag::Color | UpdateFlag::BackgroundEmoji);
};
setLocal(colorIndex, backgroundEmojiId);
const auto done = [=] {
show->showToast(peer->isSelf()
? tr::lng_settings_color_changed(tr::now)
: tr::lng_settings_color_changed_channel(tr::now));
};
const auto fail = [=](const MTP::Error &error) {
setLocal(wasIndex, wasEmojiId);
show->showToast(error.type());
};
const auto send = [&](auto &&request) {
peer->session().api().request(
std::move(request)
).done(done).fail(fail).send();
};
if (peer->isSelf()) {
send(MTPaccount_UpdateColor(
MTP_flags(
MTPaccount_UpdateColor::Flag::f_background_emoji_id),
MTP_int(colorIndex),
MTP_long(backgroundEmojiId)));
} else if (const auto channel = peer->asChannel()) {
send(MTPchannels_UpdateColor(
MTP_flags(
MTPchannels_UpdateColor::Flag::f_background_emoji_id),
channel->inputChannel,
MTP_int(colorIndex),
MTP_long(backgroundEmojiId)));
} else {
Unexpected("Invalid peer type in Set(colorIndex).");
}
}
void Apply(
std::shared_ptr<ChatHelpers::Show> show,
not_null<PeerData*> peer,
uint8 colorIndex,
DocumentId backgroundEmojiId,
Fn<void()> close,
Fn<void()> cancel) {
const auto session = &peer->session();
if (peer->colorIndex() == colorIndex
&& peer->backgroundEmojiId() == backgroundEmojiId) {
close();
} else if (peer->isSelf() && !session->premium()) {
Settings::ShowPremiumPromoToast(
show,
tr::lng_settings_color_subscribe(
tr::now,
lt_link,
Ui::Text::Link(
Ui::Text::Bold(
tr::lng_send_as_premium_required_link(tr::now))),
Ui::Text::WithEntities),
u"name_color"_q);
cancel();
} else if (peer->isSelf()) {
Set(show, peer, colorIndex, backgroundEmojiId);
close();
} else {
session->api().request(MTPpremium_GetBoostsStatus(
peer->input
)).done([=](const MTPpremium_BoostsStatus &result) {
const auto &data = result.data();
const auto required = session->account().appConfig().get<int>(
"channel_color_level_min",
5);
if (data.vlevel().v >= required) {
Set(show, peer, colorIndex, backgroundEmojiId);
close();
return;
}
const auto next = data.vnext_level_boosts().value_or_empty();
const auto openStatistics = [=] {
if (const auto controller = show->resolveWindow(
ChatHelpers::WindowUsage::PremiumPromo)) {
controller->showSection(Info::Boosts::Make(peer));
}
};
show->show(Box(Ui::AskBoostBox, Ui::AskBoostBoxData{
.link = qs(data.vboost_url()),
.boost = {
.level = data.vlevel().v,
.boosts = data.vboosts().v,
.thisLevelBoosts = data.vcurrent_level_boosts().v,
.nextLevelBoosts = next,
},
.requiredLevel = required,
}, openStatistics, nullptr));
cancel();
}).fail([=](const MTP::Error &error) {
show->showToast(error.type());
cancel();
}).send();
}
}
class ColorSelector final : public Ui::RpWidget {
public:
ColorSelector(
not_null<Ui::GenericBox*> box,
std::shared_ptr<Ui::ChatStyle> style,
rpl::producer<std::vector<uint8>> indices,
uint8 index,
Fn<void(uint8)> callback);
private:
void fillFrom(std::vector<uint8> indices);
int resizeGetHeight(int newWidth) override;
const std::shared_ptr<Ui::ChatStyle> _style;
std::vector<std::unique_ptr<ColorSample>> _samples;
const Fn<void(uint8)> _callback;
uint8 _index = 0;
};
ColorSelector::ColorSelector(
not_null<Ui::GenericBox*> box,
std::shared_ptr<Ui::ChatStyle> style,
rpl::producer<std::vector<uint8>> indices,
uint8 index,
Fn<void(uint8)> callback)
: RpWidget(box)
, _style(style)
, _callback(std::move(callback))
, _index(index) {
std::move(
indices
) | rpl::start_with_next([=](std::vector<uint8> indices) {
fillFrom(std::move(indices));
}, lifetime());
}
void ColorSelector::fillFrom(std::vector<uint8> indices) {
auto samples = std::vector<std::unique_ptr<ColorSample>>();
const auto add = [&](uint8 index) {
auto i = ranges::find(_samples, index, &ColorSample::index);
if (i != end(_samples)) {
samples.push_back(std::move(*i));
_samples.erase(i);
} else {
samples.push_back(std::make_unique<ColorSample>(
this,
_style,
index,
index == _index));
samples.back()->show();
samples.back()->setClickedCallback([=] {
if (_index != index) {
_callback(index);
ranges::find(
_samples,
_index,
&ColorSample::index
)->get()->setSelected(false);
_index = index;
ranges::find(
_samples,
_index,
&ColorSample::index
)->get()->setSelected(true);
}
});
}
};
for (const auto index : indices) {
add(index);
}
if (!ranges::contains(indices, _index)) {
add(_index);
}
_samples = std::move(samples);
if (width() > 0) {
resizeToWidth(width());
}
}
int ColorSelector::resizeGetHeight(int newWidth) {
if (newWidth <= 0) {
return 0;
}
const auto count = int(_samples.size());
const auto columns = Ui::kSimpleColorIndexCount;
const auto skip = st::settingsColorRadioSkip;
const auto size = (newWidth - skip * (columns - 1)) / float64(columns);
const auto isize = int(base::SafeRound(size));
auto top = 0;
auto left = 0.;
for (auto i = 0; i != count; ++i) {
_samples[i]->resize(isize, isize);
_samples[i]->move(int(base::SafeRound(left)), top);
left += size + skip;
if (!((i + 1) % columns)) {
top += isize + skip;
left = 0.;
}
}
return (top - skip) + ((count % columns) ? (isize + skip) : 0);
}
[[nodiscard]] object_ptr<Ui::SettingsButton> CreateEmojiIconButton(
not_null<Ui::RpWidget*> parent,
std::shared_ptr<ChatHelpers::Show> show,
std::shared_ptr<Ui::ChatStyle> style,
rpl::producer<uint8> colorIndexValue,
rpl::producer<DocumentId> emojiIdValue,
Fn<void(DocumentId)> emojiIdChosen) {
const auto &basicSt = st::settingsButtonNoIcon;
const auto ratio = style::DevicePixelRatio();
const auto added = st::normalFont->spacew;
const auto emojiSize = Data::FrameSizeFromTag({}) / ratio;
const auto noneWidth = added
+ st::normalFont->width(tr::lng_settings_color_emoji_off(tr::now));
const auto emojiWidth = added + emojiSize;
const auto rightPadding = std::max(noneWidth, emojiWidth)
+ basicSt.padding.right();
const auto st = parent->lifetime().make_state<style::SettingsButton>(
basicSt);
st->padding.setRight(rightPadding);
auto result = CreateButton(
parent,
tr::lng_settings_color_emoji(),
*st,
{});
const auto raw = result.data();
const auto right = Ui::CreateChild<Ui::RpWidget>(raw);
right->show();
struct State {
Info::Profile::EmojiStatusPanel panel;
std::unique_ptr<Ui::Text::CustomEmoji> emoji;
DocumentId emojiId = 0;
uint8 index = 0;
};
const auto state = right->lifetime().make_state<State>();
state->panel.backgroundEmojiChosen(
) | rpl::start_with_next(emojiIdChosen, raw->lifetime());
std::move(colorIndexValue) | rpl::start_with_next([=](uint8 index) {
state->index = index;
if (state->emoji) {
right->update();
}
}, right->lifetime());
const auto session = &show->session();
std::move(emojiIdValue) | rpl::start_with_next([=](DocumentId emojiId) {
state->emojiId = emojiId;
state->emoji = emojiId
? session->data().customEmojiManager().create(
emojiId,
[=] { right->update(); })
: nullptr;
right->resize(
(emojiId ? emojiWidth : noneWidth) + added,
right->height());
right->update();
}, right->lifetime());
rpl::combine(
raw->sizeValue(),
right->widthValue()
) | rpl::start_with_next([=](QSize outer, int width) {
right->resize(width, outer.height());
const auto skip = st::settingsButton.padding.right();
right->moveToRight(skip - added, 0, outer.width());
}, right->lifetime());
right->paintRequest(
) | rpl::start_with_next([=] {
if (state->panel.paintBadgeFrame(right)) {
return;
}
auto p = QPainter(right);
const auto height = right->height();
if (state->emoji) {
const auto colors = style->coloredValues(false, state->index);
state->emoji->paint(p, {
.textColor = colors.name,
.position = QPoint(added, (height - emojiSize) / 2),
.internal = {
.forceFirstFrame = true,
},
});
} else {
const auto &font = st::normalFont;
p.setFont(font);
p.setPen(style->windowActiveTextFg());
p.drawText(
QPoint(added, (height - font->height) / 2 + font->ascent),
tr::lng_settings_color_emoji_off(tr::now));
}
}, right->lifetime());
raw->setClickedCallback([=] {
const auto customTextColor = [=] {
return style->coloredValues(false, state->index).name;
};
const auto controller = show->resolveWindow(
ChatHelpers::WindowUsage::PremiumPromo);
if (controller) {
state->panel.show({
.controller = controller,
.button = right,
.currentBackgroundEmojiId = state->emojiId,
.customTextColor = customTextColor,
.backgroundEmojiMode = true,
});
}
});
return result;
}
} // namespace
void EditPeerColorBox(
not_null<Ui::GenericBox*> box,
std::shared_ptr<ChatHelpers::Show> show,
not_null<PeerData*> peer,
std::shared_ptr<Ui::ChatStyle> style,
std::shared_ptr<Ui::ChatTheme> theme) {
box->setTitle(tr::lng_settings_color_title());
box->setWidth(st::boxWideWidth);
struct State {
rpl::variable<uint8> index;
rpl::variable<DocumentId> emojiId;
bool changing = false;
bool applying = false;
};
const auto state = box->lifetime().make_state<State>();
state->index = peer->colorIndex();
state->emojiId = peer->backgroundEmojiId();
box->addRow(object_ptr<PreviewWrap>(
box,
style,
theme,
peer,
state->index.value(),
state->emojiId.value()
), {});
const auto appConfig = &peer->session().account().appConfig();
auto indices = rpl::single(
rpl::empty
) | rpl::then(
appConfig->refreshed()
) | rpl::map([=] {
const auto list = appConfig->get<std::vector<int>>(
"peer_colors_available",
{ 0, 1, 2, 3, 4, 5, 6 });
return list | ranges::views::transform([](int i) {
return uint8(i);
}) | ranges::to_vector;
});
const auto margin = st::settingsColorRadioMargin;
const auto skip = st::settingsColorRadioSkip;
box->addRow(
object_ptr<ColorSelector>(
box,
style,
std::move(indices),
state->index.current(),
[=](uint8 index) { state->index = index; }),
{ margin, skip, margin, skip });
const auto container = box->verticalLayout();
AddDividerText(container, peer->isSelf()
? tr::lng_settings_color_about()
: tr::lng_settings_color_about_channel());
AddSkip(container, st::settingsColorSampleSkip);
container->add(CreateEmojiIconButton(
container,
show,
style,
state->index.value(),
state->emojiId.value(),
[=](DocumentId id) { state->emojiId = id; }));
AddSkip(container, st::settingsColorSampleSkip);
AddDividerText(container, peer->isSelf()
? tr::lng_settings_color_emoji_about()
: tr::lng_settings_color_emoji_about_channel());
box->addButton(tr::lng_settings_apply(), [=] {
if (state->applying) {
return;
}
state->applying = true;
const auto index = state->index.current();
const auto emojiId = state->emojiId.current();
Apply(show, peer, index, emojiId, crl::guard(box, [=] {
box->closeBox();
}), crl::guard(box, [=] {
state->applying = false;
}));
});
box->addButton(tr::lng_cancel(), [=] {
box->closeBox();
});
}
void AddPeerColorButton(
not_null<Ui::VerticalLayout*> container,
std::shared_ptr<ChatHelpers::Show> show,
not_null<PeerData*> peer) {
const auto button = AddButton(
container,
(peer->isSelf()
? tr::lng_settings_theme_name_color()
: tr::lng_edit_channel_color()),
st::settingsColorButton,
{ &st::menuIconChangeColors });
auto colorIndexValue = peer->session().changes().peerFlagsValue(
peer,
Data::PeerUpdate::Flag::Color
) | rpl::map([=] {
return peer->colorIndex();
});
const auto name = peer->shortName();
const auto style = std::make_shared<Ui::ChatStyle>(
peer->session().colorIndicesValue());
const auto theme = std::shared_ptr<Ui::ChatTheme>(
Window::Theme::DefaultChatThemeOn(button->lifetime()));
style->apply(theme.get());
const auto sample = Ui::CreateChild<ColorSample>(
button.get(),
style,
rpl::duplicate(colorIndexValue),
name);
sample->show();
rpl::combine(
button->widthValue(),
tr::lng_settings_theme_name_color(),
rpl::duplicate(colorIndexValue)
) | rpl::start_with_next([=](
int width,
const QString &button,
int colorIndex) {
const auto sampleSize = st::settingsColorSampleSize;
const auto available = width
- st::settingsButton.padding.left()
- (st::settingsColorButton.padding.right() - sampleSize)
- st::settingsButton.style.font->width(button)
- st::settingsButtonRightSkip;
if (style->colorPatternIndex(colorIndex)) {
sample->resize(sampleSize, sampleSize);
} else {
const auto padding = st::settingsColorSamplePadding;
const auto wantedHeight = padding.top()
+ st::semiboldFont->height
+ padding.bottom();
const auto wantedWidth = sample->naturalWidth();
sample->resize(std::min(wantedWidth, available), wantedHeight);
}
sample->update();
}, sample->lifetime());
rpl::combine(
button->sizeValue(),
sample->sizeValue(),
std::move(colorIndexValue)
) | rpl::start_with_next([=](QSize outer, QSize inner, int colorIndex) {
const auto right = st::settingsColorButton.padding.right()
- st::settingsColorSampleSkip
- st::settingsColorSampleSize
- (style->colorPatternIndex(colorIndex)
? 0
: st::settingsColorSamplePadding.right());
sample->move(
outer.width() - right - inner.width(),
(outer.height() - inner.height()) / 2);
}, sample->lifetime());
sample->setAttribute(Qt::WA_TransparentForMouseEvents);
button->setClickedCallback([=] {
show->show(Box(EditPeerColorBox, show, peer, style, theme));
});
}

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
*/
#pragma once
namespace ChatHelpers {
class Show;
} // namespace ChatHelpers
namespace Ui {
class GenericBox;
class ChatStyle;
class ChatTheme;
class VerticalLayout;
} // namespace Ui
void EditPeerColorBox(
not_null<Ui::GenericBox*> box,
std::shared_ptr<ChatHelpers::Show> show,
not_null<PeerData*> peer,
std::shared_ptr<Ui::ChatStyle> style = nullptr,
std::shared_ptr<Ui::ChatTheme> theme = nullptr);
void AddPeerColorButton(
not_null<Ui::VerticalLayout*> container,
std::shared_ptr<ChatHelpers::Show> show,
not_null<PeerData*> peer);

View File

@@ -13,8 +13,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "main/main_session.h"
#include "boxes/add_contact_box.h"
#include "ui/boxes/confirm_box.h"
#include "boxes/peer_list_controllers.h"
#include "boxes/peers/edit_participants_box.h"
#include "boxes/peers/edit_peer_color_box.h"
#include "boxes/peers/edit_peer_common.h"
#include "boxes/peers/edit_peer_type_box.h"
#include "boxes/peers/edit_peer_history_visibility_box.h"
@@ -23,6 +23,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "boxes/peers/edit_linked_chat_box.h"
#include "boxes/peers/edit_peer_requests_box.h"
#include "boxes/peers/edit_peer_reactions.h"
#include "boxes/peer_list_controllers.h"
#include "boxes/stickers_box.h"
#include "boxes/username_box.h"
#include "ui/boxes/single_choice_box.h"
@@ -302,6 +303,7 @@ private:
void fillLinkedChatButton();
//void fillInviteLinkButton();
void fillForumButton();
void fillColorIndexButton();
void fillSignaturesButton();
void fillHistoryVisibilityButton();
void fillManageSection();
@@ -905,6 +907,13 @@ void Controller::refreshForumToggleLocked() {
_controls.forumToggle->setToggleLocked(locked);
}
void Controller::fillColorIndexButton() {
Expects(_controls.buttonsLayout != nullptr);
const auto show = _navigation->uiShow();
AddPeerColorButton(_controls.buttonsLayout, show, _peer);
}
void Controller::fillSignaturesButton() {
Expects(_controls.buttonsLayout != nullptr);
@@ -1024,74 +1033,42 @@ void Controller::fillManageSection() {
return;
}
const auto canEditType = [&] {
return isChannel
? channel->amCreator()
: chat->amCreator();
}();
const auto canEditSignatures = [&] {
return isChannel
? (channel->canEditSignatures() && !channel->isMegagroup())
: false;
}();
const auto canEditPreHistoryHidden = [&] {
return isChannel
? channel->canEditPreHistoryHidden()
: chat->canEditPreHistoryHidden();
}();
const auto canEditType = isChannel
? channel->amCreator()
: chat->amCreator();
const auto canEditSignatures = isChannel
&& channel->canEditSignatures()
&& !channel->isMegagroup();
const auto canEditPreHistoryHidden = isChannel
? channel->canEditPreHistoryHidden()
: chat->canEditPreHistoryHidden();
const auto canEditForum = isChannel
? (channel->isMegagroup() && channel->amCreator())
: chat->amCreator();
const auto canEditPermissions = [&] {
return isChannel
? channel->canEditPermissions()
: chat->canEditPermissions();
}();
const auto canEditInviteLinks = [&] {
return isChannel
? channel->canHaveInviteLink()
: chat->canHaveInviteLink();
}();
const auto canViewAdmins = [&] {
return isChannel
? channel->canViewAdmins()
: chat->amIn();
}();
const auto canViewMembers = [&] {
return isChannel
? channel->canViewMembers()
: chat->amIn();
}();
const auto canViewKicked = [&] {
return isChannel
? (channel->isBroadcast() || channel->isGigagroup())
: false;
}();
const auto hasRecentActions = [&] {
return isChannel
? (channel->hasAdminRights() || channel->amCreator())
: false;
}();
const auto canEditStickers = [&] {
return isChannel
? channel->canEditStickers()
: false;
}();
const auto canDeleteChannel = [&] {
return isChannel
? channel->canDelete()
: false;
}();
const auto canViewOrEditLinkedChat = [&] {
return !isChannel
? false
: channel->linkedChat()
? true
: (channel->isBroadcast() && channel->canEditInformation());
}();
const auto canEditPermissions = isChannel
? channel->canEditPermissions()
: chat->canEditPermissions();
const auto canEditInviteLinks = isChannel
? channel->canHaveInviteLink()
: chat->canHaveInviteLink();
const auto canViewAdmins = isChannel
? channel->canViewAdmins()
: chat->amIn();
const auto canViewMembers = isChannel
? channel->canViewMembers()
: chat->amIn();
const auto canViewKicked = isChannel
&& (channel->isBroadcast() || channel->isGigagroup());
const auto hasRecentActions = isChannel
&& (channel->hasAdminRights() || channel->amCreator());
const auto canEditStickers = isChannel && channel->canEditStickers();
const auto canDeleteChannel = isChannel && channel->canDelete();
const auto canEditColorIndex = isChannel
&& !channel->isMegagroup()
&& channel->canEditInformation();
const auto canViewOrEditLinkedChat = isChannel
&& (channel->linkedChat()
|| (channel->isBroadcast() && channel->canEditInformation()));
AddSkip(_controls.buttonsLayout, 0);
@@ -1109,11 +1086,15 @@ void Controller::fillManageSection() {
if (canEditForum) {
fillForumButton();
}
if (canEditColorIndex) {
fillColorIndexButton();
}
if (canEditSignatures) {
fillSignaturesButton();
}
if (canEditPreHistoryHidden
|| canEditForum
|| canEditColorIndex
|| canEditSignatures
//|| canEditInviteLinks
|| canViewOrEditLinkedChat

View File

@@ -63,7 +63,9 @@ PeerId GenerateUser(not_null<History*> history, const QString &name) {
MTPstring(), // lang code
MTPEmojiStatus(),
MTPVector<MTPUsername>(),
MTPint())); // stories_max_id
MTPint(), // stories_max_id
MTP_int(0), // color
MTPlong())); // background_emoji_id
return peerId;
}
@@ -71,7 +73,7 @@ AdminLog::OwnedItem GenerateItem(
not_null<HistoryView::ElementDelegate*> delegate,
not_null<History*> history,
PeerId from,
MsgId replyTo,
FullMsgId replyTo,
const QString &text) {
Expects(history->peer->isUser());
@@ -81,7 +83,7 @@ AdminLog::OwnedItem GenerateItem(
| MessageFlag::HasFromId
| MessageFlag::HasReplyInfo),
UserId(), // via
FullReplyTo{ .msgId = replyTo },
FullReplyTo{ .messageId = replyTo },
base::unixtime::now(), // date
from,
QString(), // postAuthor
@@ -131,7 +133,8 @@ void AddMessage(
state->delegate = std::make_unique<Delegate>(
controller,
crl::guard(widget, [=] { widget->update(); }));
state->style = std::make_unique<Ui::ChatStyle>();
state->style = std::make_unique<Ui::ChatStyle>(
controller->session().colorIndicesValue());
state->style->apply(controller->defaultChatTheme().get());
state->icons.lifetimes = std::vector<rpl::lifetime>(2);
@@ -143,13 +146,13 @@ void AddMessage(
GenerateUser(
history,
tr::lng_settings_chat_message_reply_from(tr::now)),
0,
FullMsgId(),
tr::lng_settings_chat_message_reply(tr::now));
auto message = GenerateItem(
state->delegate.get(),
history,
history->peer->id,
state->reply->data()->fullId().msg,
state->reply->data()->fullId(),
tr::lng_settings_chat_message(tr::now));
const auto view = message.get();
state->item = std::move(message);

View File

@@ -97,8 +97,6 @@ callButtonLabel: FlatLabel(defaultFlatLabel) {
textFg: callNameFg;
style: TextStyle(defaultTextStyle) {
font: font(11px);
linkFont: font(11px);
linkFontOver: font(11px underline);
}
}
@@ -218,8 +216,6 @@ callMuteButtonLabel: FlatLabel(defaultFlatLabel) {
textFg: groupCallMembersFg;
style: TextStyle(defaultTextStyle) {
font: font(14px);
linkFont: font(14px);
linkFontOver: font(14px underline);
}
}
callMuteButtonActiveInner: IconButton {
@@ -294,8 +290,6 @@ callName: FlatLabel(defaultFlatLabel) {
align: align(top);
style: TextStyle(defaultTextStyle) {
font: font(21px semibold);
linkFont: font(21px semibold);
linkFontOver: font(21px semibold underline);
}
}
callStatus: FlatLabel(defaultFlatLabel) {
@@ -305,8 +299,6 @@ callStatus: FlatLabel(defaultFlatLabel) {
align: align(top);
style: TextStyle(defaultTextStyle) {
font: font(14px);
linkFont: font(14px);
linkFontOver: font(14px underline);
}
}
callRemoteAudioMute: FlatLabel(callStatus) {
@@ -314,8 +306,6 @@ callRemoteAudioMute: FlatLabel(callStatus) {
textFg: videoPlayIconFg;
style: TextStyle(defaultTextStyle) {
font: font(12px);
linkFont: font(12px);
linkFontOver: font(12px underline);
}
}
callRemoteAudioMuteSkip: 12px;
@@ -746,8 +736,6 @@ groupCallShareBoxList: PeerList(groupCallMembersList) {
item: PeerListItem(groupCallMembersListItem) {
nameStyle: TextStyle(defaultTextStyle) {
font: font(11px);
linkFont: font(11px);
linkFontOver: font(11px);
}
checkbox: RoundImageCheckbox(groupCallMembersListCheckbox) {
imageRadius: 28px;
@@ -784,8 +772,6 @@ groupCallTitleLabel: FlatLabel(groupCallSubtitleLabel) {
textFg: groupCallMembersFg;
style: TextStyle(defaultTextStyle) {
font: font(semibold 14px);
linkFont: font(semibold 14px);
linkFontOver: font(semibold 14px);
}
}
groupCallTitleSeparator: 4px;
@@ -1203,8 +1189,6 @@ callTopBarMuteCrossLine: CrossLineAnimation {
groupCallStartsIn: FlatLabel(defaultFlatLabel) {
style: TextStyle(defaultTextStyle) {
font: font(20px semibold);
linkFont: font(20px semibold);
linkFontOver: font(20px semibold underline);
}
textFg: groupCallMembersFg;
}

View File

@@ -712,13 +712,24 @@ void Instance::destroyCurrentCall() {
}
}
bool Instance::hasActivePanel(not_null<Main::Session*> session) const {
bool Instance::hasVisiblePanel(Main::Session *session) const {
if (inCall()) {
return (&_currentCall->user()->session() == session)
&& _currentCallPanel->isActive();
return _currentCallPanel->isVisible()
&& (!session || (&_currentCall->user()->session() == session));
} else if (inGroupCall()) {
return (&_currentGroupCall->peer()->session() == session)
&& _currentGroupCallPanel->isActive();
return _currentGroupCallPanel->isVisible()
&& (!session || (&_currentGroupCall->peer()->session() == session));
}
return false;
}
bool Instance::hasActivePanel(Main::Session *session) const {
if (inCall()) {
return _currentCallPanel->isActive()
&& (!session || (&_currentCall->user()->session() == session));
} else if (inGroupCall()) {
return _currentGroupCallPanel->isActive()
&& (!session || (&_currentGroupCall->peer()->session() == session));
}
return false;
}

View File

@@ -89,8 +89,10 @@ public:
[[nodiscard]] rpl::producer<GroupCall*> currentGroupCallValue() const;
[[nodiscard]] bool inCall() const;
[[nodiscard]] bool inGroupCall() const;
[[nodiscard]] bool hasVisiblePanel(
Main::Session *session = nullptr) const;
[[nodiscard]] bool hasActivePanel(
not_null<Main::Session*> session) const;
Main::Session *session = nullptr) const;
bool activateCurrentCall(const QString &joinHash = QString());
bool minimizeCurrentActiveCall();
bool toggleFullScreenCurrentActiveCall();

View File

@@ -106,12 +106,15 @@ Panel::Panel(not_null<Call*> call)
Panel::~Panel() = default;
bool Panel::isActive() const {
return window()->isActiveWindow()
&& window()->isVisible()
bool Panel::isVisible() const {
return window()->isVisible()
&& !(window()->windowState() & Qt::WindowMinimized);
}
bool Panel::isActive() const {
return window()->isActiveWindow() && isVisible();
}
void Panel::showAndActivate() {
if (window()->isHidden()) {
window()->show();

View File

@@ -61,6 +61,7 @@ public:
Panel(not_null<Call*> call);
~Panel();
[[nodiscard]] bool isVisible() const;
[[nodiscard]] bool isActive() const;
void showAndActivate();
void minimize();

View File

@@ -202,8 +202,7 @@ void Userpic::createCache(Image *image) {
{
auto p = QPainter(&filled);
Ui::EmptyUserpic(
Ui::EmptyUserpic::UserpicColor(
Data::PeerColorIndex(_peer->id)),
Ui::EmptyUserpic::UserpicColor(_peer->colorIndex()),
_peer->name()
).paintCircle(p, 0, 0, size, size);
}

View File

@@ -258,12 +258,15 @@ not_null<GroupCall*> Panel::call() const {
return _call;
}
bool Panel::isActive() const {
return window()->isActiveWindow()
&& window()->isVisible()
bool Panel::isVisible() const {
return window()->isVisible()
&& !(window()->windowState() & Qt::WindowMinimized);
}
bool Panel::isActive() const {
return window()->isActiveWindow() && isVisible();
}
base::weak_ptr<Ui::Toast::Instance> Panel::showToast(
const QString &text,
crl::time duration) {

View File

@@ -91,6 +91,7 @@ public:
[[nodiscard]] not_null<Ui::RpWidget*> widget() const;
[[nodiscard]] not_null<GroupCall*> call() const;
[[nodiscard]] bool isVisible() const;
[[nodiscard]] bool isActive() const;
base::weak_ptr<Ui::Toast::Instance> showToast(

View File

@@ -15,6 +15,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "styles/style_calls.h"
#include <QtCore/QDateTime>
#include <QtCore/QLocale>
namespace Calls::Group::Ui {

View File

@@ -16,7 +16,7 @@ struct SendCommandRequest {
not_null<PeerData*> peer;
QString command;
FullMsgId context;
MsgId replyTo = 0;
FullReplyTo replyTo;
};
[[nodiscard]] QString WrapCommandInChat(

View File

@@ -509,8 +509,6 @@ emojiPanColorAllLabel: FlatLabel(defaultFlatLabel) {
minWidth: 40px;
style: TextStyle(defaultTextStyle) {
font: font(12px);
linkFont: font(12px);
linkFontOver: font(12px);
}
}
emojiPanColorAllPadding: margins(10px, 6px, 10px, -1px);
@@ -664,6 +662,9 @@ statusEmojiPan: EmojiPan(defaultEmojiPan) {
fadeLeft: icon {{ "fade_horizontal-flip_horizontal", windowBg }};
fadeRight: icon {{ "fade_horizontal", windowBg }};
}
backgroundEmojiPan: EmojiPan(defaultEmojiPan) {
padding: margins(7px, 7px, 4px, 0px);
}
inlineBotsScroll: ScrollArea(defaultSolidScroll) {
deltat: stickerPanPadding;

View File

@@ -467,6 +467,7 @@ EmojiListWidget::EmojiListWidget(
, _localSetsManager(
std::make_unique<LocalStickersManager>(&session()))
, _customRecentFactory(std::move(descriptor.customRecentFactory))
, _customTextColor(std::move(descriptor.customTextColor))
, _overBg(st::emojiPanRadius, st().overBg)
, _collapsedBg(st::emojiPanExpand.height / 2, st().headerFg)
, _picker(this, st())
@@ -476,7 +477,7 @@ EmojiListWidget::EmojiListWidget(
setAttribute(Qt::WA_OpaquePaintEvent);
}
if (_mode != Mode::RecentReactions) {
if (_mode != Mode::RecentReactions && _mode != Mode::BackgroundEmoji) {
setupSearch();
}
@@ -791,10 +792,12 @@ object_ptr<TabbedSelector::InnerFooter> EmojiListWidget::createFooter() {
};
auto result = object_ptr<StickersListFooter>(FooterDescriptor{
.session = &session(),
.customTextColor = _customTextColor,
.paused = footerPaused,
.parent = this,
.st = &st(),
.features = { .stickersSettings = false },
.forceFirstFrame = (_mode == Mode::BackgroundEmoji),
});
_footer = result;
@@ -1027,6 +1030,14 @@ void EmojiListWidget::fillRecentFrom(const std::vector<DocumentId> &list) {
if (!id && _mode == Mode::EmojiStatus) {
const auto star = QString::fromUtf8("\xe2\xad\x90\xef\xb8\x8f");
_recent.push_back({ .id = { Ui::Emoji::Find(star) } });
} else if (!id && _mode == Mode::BackgroundEmoji) {
const auto fakeId = DocumentId(5246772116543512028ULL);
const auto no = QString::fromUtf8("\xe2\x9b\x94\xef\xb8\x8f");
_recent.push_back({
.custom = resolveCustomRecent(fakeId),
.id = { Ui::Emoji::Find(no) },
});
_recentCustomIds.emplace(fakeId);
} else {
_recent.push_back({
.custom = resolveCustomRecent(id),
@@ -1188,7 +1199,9 @@ void EmojiListWidget::paintEvent(QPaintEvent *e) {
void EmojiListWidget::validateEmojiPaintContext(
const ExpandingContext &context) {
auto value = Ui::Text::CustomEmojiPaintContext{
.textColor = (_mode == Mode::EmojiStatus
.textColor = (_customTextColor
? _customTextColor()
: (_mode == Mode::EmojiStatus)
? anim::color(
st::stickerPanPremium1,
st::stickerPanPremium2,
@@ -1199,6 +1212,7 @@ void EmojiListWidget::validateEmojiPaintContext(
.scale = context.progress,
.paused = On(powerSavingFlag()) || paused(),
.scaled = context.expanding,
.internal = { .forceFirstFrame = (_mode == Mode::BackgroundEmoji) },
};
if (!_emojiPaintContext) {
_emojiPaintContext = std::make_unique<
@@ -1629,6 +1643,9 @@ void EmojiListWidget::mouseReleaseEvent(QMouseEvent *e) {
case Mode::TopicIcon:
Settings::ShowPremium(resolved, u"forum_topic_icon"_q);
break;
case Mode::BackgroundEmoji:
Settings::ShowPremium(resolved, u"name_color"_q);
break;
}
}
}
@@ -1995,7 +2012,10 @@ void EmojiListWidget::refreshCustom() {
const auto &sets = owner->stickers().sets();
const auto push = [&](uint64 setId, bool installed) {
auto it = sets.find(setId);
if (it == sets.cend() || it->second->stickers.isEmpty()) {
if (it == sets.cend()
|| it->second->stickers.isEmpty()
|| (_mode == Mode::BackgroundEmoji
&& !it->second->textColor())) {
return;
}
const auto canRemove = !!(it->second->flags

View File

@@ -74,11 +74,13 @@ enum class EmojiListMode {
FullReactions,
RecentReactions,
UserpicBuilder,
BackgroundEmoji,
};
struct EmojiListDescriptor {
std::shared_ptr<Show> show;
EmojiListMode mode = EmojiListMode::Full;
Fn<QColor()> customTextColor;
Fn<bool()> paused;
std::vector<DocumentId> customRecentList;
Fn<std::unique_ptr<Ui::Text::CustomEmoji>(
@@ -386,6 +388,7 @@ private:
base::flat_map<
DocumentId,
std::unique_ptr<Ui::Text::CustomEmoji>> _customRecent;
Fn<QColor()> _customTextColor;
int _customSingleSize = 0;
bool _allowWithoutPremium = false;
Ui::RoundRect _overBg;

View File

@@ -493,10 +493,7 @@ InlineBotQuery ParseInlineBotQuery(
result.lookingUpBot = true;
}
}
if (result.lookingUpBot) {
result.query = QString();
return result;
} else if (result.bot
if (result.bot
&& (!result.bot->isBot()
|| result.bot->botInfo->inlinePlaceholder.isEmpty())) {
result.bot = nullptr;
@@ -601,6 +598,12 @@ MessageLinksParser::MessageLinksParser(not_null<Ui::InputField*> field)
_lifetime = _field->changes(
) | rpl::start_with_next([=] {
const auto length = _field->getTextWithTags().text.size();
if (!length) {
_lastLength = 0;
_timer.cancel();
parse();
return;
}
const auto timeout = (std::abs(length - _lastLength) > 2)
? 0
: kParseLinksTimeout;
@@ -642,16 +645,13 @@ bool MessageLinksParser::eventFilter(QObject *object, QEvent *event) {
return QObject::eventFilter(object, event);
}
const rpl::variable<QStringList> &MessageLinksParser::list() const {
return _list;
}
void MessageLinksParser::parse() {
const auto &textWithTags = _field->getTextWithTags();
const auto &text = textWithTags.text;
const auto &tags = textWithTags.tags;
const auto &markdownTags = _field->getMarkdownTags();
if (_disabled || text.isEmpty()) {
_ranges = {};
_list = QStringList();
return;
}
@@ -663,7 +663,7 @@ void MessageLinksParser::parse() {
|| (tag == Ui::InputField::kTagSpoiler);
};
auto ranges = QVector<LinkRange>();
_ranges.clear();
auto tag = tags.begin();
const auto tagsEnd = tags.end();
@@ -672,7 +672,7 @@ void MessageLinksParser::parse() {
if (Ui::InputField::IsValidMarkdownLink(tag->id)
&& !TextUtilities::IsMentionLink(tag->id)) {
ranges.push_back({ tag->offset, tag->length, tag->id });
_ranges.push_back({ tag->offset, tag->length, tag->id });
}
++tag;
};
@@ -774,7 +774,7 @@ void MessageLinksParser::parse() {
continue;
}
}
const auto range = LinkRange {
const auto range = MessageLinkRange{
int(domainOffset),
static_cast<int>(p - start - domainOffset),
QString()
@@ -782,22 +782,20 @@ void MessageLinksParser::parse() {
processTagsBefore(domainOffset);
if (!hasTagsIntersection(range.start + range.length)) {
if (markdownTagsAllow(range.start, range.length)) {
ranges.push_back(range);
_ranges.push_back(range);
}
}
offset = matchOffset = p - start;
}
processTagsBefore(QFIXED_MAX);
processTagsBefore(Ui::kQFixedMax);
apply(text, ranges);
applyRanges(text);
}
void MessageLinksParser::apply(
const QString &text,
const QVector<LinkRange> &ranges) {
const auto count = int(ranges.size());
void MessageLinksParser::applyRanges(const QString &text) {
const auto count = int(_ranges.size());
const auto current = _list.current();
const auto computeLink = [&](const LinkRange &range) {
const auto computeLink = [&](const MessageLinkRange &range) {
return range.custom.isEmpty()
? base::StringViewMid(text, range.start, range.length)
: QStringView(range.custom);
@@ -807,7 +805,7 @@ void MessageLinksParser::apply(
return true;
}
for (auto i = 0; i != count; ++i) {
if (computeLink(ranges[i]) != current[i]) {
if (computeLink(_ranges[i]) != current[i]) {
return true;
}
}
@@ -818,7 +816,7 @@ void MessageLinksParser::apply(
}
auto parsed = QStringList();
parsed.reserve(count);
for (const auto &range : ranges) {
for (const auto &range : _ranges) {
parsed.push_back(computeLink(range).toString());
}
_list = std::move(parsed);

View File

@@ -7,9 +7,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#pragma once
#include "ui/widgets/fields/input_field.h"
#include "base/qt/qt_compare.h"
#include "base/timer.h"
#include "chat_helpers/compose/compose_features.h"
#include "ui/widgets/fields/input_field.h"
#ifndef TDESKTOP_DISABLE_SPELLCHECK
#include "boxes/dictionaries_manager.h"
@@ -96,38 +97,42 @@ AutocompleteQuery ParseMentionHashtagBotCommandQuery(
not_null<const Ui::InputField*> field,
ChatHelpers::ComposeFeatures features);
class MessageLinksParser : private QObject {
struct MessageLinkRange {
int start = 0;
int length = 0;
QString custom;
friend inline auto operator<=>(
const MessageLinkRange&,
const MessageLinkRange&) = default;
friend inline bool operator==(
const MessageLinkRange&,
const MessageLinkRange&) = default;
};
class MessageLinksParser final : private QObject {
public:
MessageLinksParser(not_null<Ui::InputField*> field);
void parseNow();
void setDisabled(bool disabled);
[[nodiscard]] const rpl::variable<QStringList> &list() const;
protected:
bool eventFilter(QObject *object, QEvent *event) override;
[[nodiscard]] const rpl::variable<QStringList> &list() const {
return _list;
}
[[nodiscard]] const std::vector<MessageLinkRange> &ranges() const {
return _ranges;
}
private:
struct LinkRange {
int start;
int length;
QString custom;
};
friend inline bool operator==(const LinkRange &a, const LinkRange &b) {
return (a.start == b.start)
&& (a.length == b.length)
&& (a.custom == b.custom);
}
friend inline bool operator!=(const LinkRange &a, const LinkRange &b) {
return !(a == b);
}
bool eventFilter(QObject *object, QEvent *event) override;
void parse();
void apply(const QString &text, const QVector<LinkRange> &ranges);
void applyRanges(const QString &text);
not_null<Ui::InputField*> _field;
rpl::variable<QStringList> _list;
std::vector<MessageLinkRange> _ranges;
int _lastLength = 0;
bool _disabled = false;
base::Timer _timer;

View File

@@ -291,12 +291,14 @@ StickersListFooter::StickersListFooter(Descriptor &&descriptor)
descriptor.parent,
descriptor.st ? *descriptor.st : st::defaultEmojiPan)
, _session(descriptor.session)
, _paused(descriptor.paused)
, _customTextColor(std::move(descriptor.customTextColor))
, _paused(std::move(descriptor.paused))
, _features(descriptor.features)
, _iconState([=] { update(); })
, _subiconState([=] { update(); })
, _selectionBg(st::emojiPanRadius, st().categoriesBgOver)
, _subselectionBg(st().iconArea / 2, st().categoriesBgOver) {
, _subselectionBg(st().iconArea / 2, st().categoriesBgOver)
, _forceFirstFrame(descriptor.forceFirstFrame) {
setMouseTracking(true);
_iconsLeft = st().iconSkip
@@ -1345,13 +1347,16 @@ void StickersListFooter::paintSetIconToCache(
const auto y = (st().footer - icon.pixh) / 2;
if (icon.custom) {
icon.custom->paint(p, Ui::Text::CustomEmoji::Context{
.textColor = st().textFg->c,
.textColor = (_customTextColor
? _customTextColor()
: st().textFg->c),
.size = QSize(icon.pixw, icon.pixh),
.now = now,
.scale = context.progress,
.position = { x, y },
.paused = paused,
.scaled = context.expanding,
.internal = { .forceFirstFrame = _forceFirstFrame },
});
} else if (icon.lottie && icon.lottie->ready()) {
const auto frame = icon.lottie->frame();
@@ -1428,11 +1433,13 @@ void StickersListFooter::paintSetIconToCache(
return icons[index];
};
const auto paintOne = [&](int left, const style::icon *icon) {
icon->paint(
p,
left + (_singleWidth - icon->width()) / 2,
(st().footer - icon->height()) / 2,
width());
left += (_singleWidth - icon->width()) / 2;
const auto top = (st().footer - icon->height()) / 2;
if (_customTextColor) {
icon->paint(p, left, top, width(), _customTextColor());
} else {
icon->paint(p, left, top, width());
}
};
if (_icons[info.index].setId == AllEmojiSectionSetId()
&& info.width > _singleWidth) {

View File

@@ -115,10 +115,12 @@ class StickersListFooter final : public TabbedSelector::InnerFooter {
public:
struct Descriptor {
not_null<Main::Session*> session;
Fn<QColor()> customTextColor;
Fn<bool()> paused;
not_null<RpWidget*> parent;
const style::EmojiPan *st = nullptr;
ComposeFeatures features;
bool forceFirstFrame = false;
};
explicit StickersListFooter(Descriptor &&descriptor);
@@ -269,6 +271,7 @@ private:
void clipCallback(Media::Clip::Notification notification, uint64 setId);
const not_null<Main::Session*> _session;
const Fn<QColor()> _customTextColor;
const Fn<bool()> _paused;
const ComposeFeatures _features;
@@ -303,6 +306,7 @@ private:
int _subiconsWidth = 0;
bool _subiconsExpanded = false;
bool _repaintScheduled = false;
bool _forceFirstFrame = false;
rpl::event_stream<> _openSettingsRequests;
rpl::event_stream<uint64> _setChosen;

View File

@@ -331,7 +331,7 @@ TabbedSelector::TabbedSelector(
Mode mode)
: TabbedSelector(parent, {
.show = std::move(show),
.st = (mode == Mode::EmojiStatus
.st = ((mode == Mode::EmojiStatus || mode == Mode::BackgroundEmoji)
? st::statusEmojiPan
: st::defaultEmojiPan),
.level = level,
@@ -347,6 +347,7 @@ TabbedSelector::TabbedSelector(
, _features(descriptor.features)
, _show(std::move(descriptor.show))
, _level(descriptor.level)
, _customTextColor(std::move(descriptor.customTextColor))
, _mode(descriptor.mode)
, _panelRounding(Ui::PrepareCornerPixmaps(st::emojiPanRadius, _st.bg))
, _categoriesRounding(
@@ -512,7 +513,10 @@ TabbedSelector::Tab TabbedSelector::createTab(SelectorTab type, int index) {
.show = _show,
.mode = (_mode == Mode::EmojiStatus
? EmojiMode::EmojiStatus
: _mode == Mode::BackgroundEmoji
? EmojiMode::BackgroundEmoji
: EmojiMode::Full),
.customTextColor = _customTextColor,
.paused = paused,
.st = &_st,
.features = _features,

View File

@@ -81,6 +81,7 @@ enum class TabbedSelectorMode {
EmojiOnly,
MediaEditor,
EmojiStatus,
BackgroundEmoji,
};
struct TabbedSelectorDescriptor {
@@ -88,6 +89,7 @@ struct TabbedSelectorDescriptor {
const style::EmojiPan &st;
PauseReason level = {};
TabbedSelectorMode mode = TabbedSelectorMode::Full;
Fn<QColor()> customTextColor;
ComposeFeatures features;
};
@@ -272,6 +274,7 @@ private:
const ComposeFeatures _features;
const std::shared_ptr<Show> _show;
const PauseReason _level = {};
const Fn<QColor()> _customTextColor;
Mode _mode = Mode::Full;
int _roundRadius = 0;

View File

@@ -512,14 +512,16 @@ void Application::startMediaView() {
InvokeQueued(this, [=] {
_mediaView = std::make_unique<Media::View::OverlayWidget>();
});
#else // Q_OS_MAC
#elif defined Q_OS_WIN // Q_OS_MAC || Q_OS_WIN
// On Windows we needed such hack for the main window, otherwise
// somewhere inside the media viewer creating code its geometry
// was broken / lost to some invalid values.
const auto current = _lastActivePrimaryWindow->widget()->geometry();
_mediaView = std::make_unique<Media::View::OverlayWidget>();
_lastActivePrimaryWindow->widget()->Ui::RpWidget::setGeometry(current);
#endif // Q_OS_MAC
#else
_mediaView = std::make_unique<Media::View::OverlayWidget>();
#endif // Q_OS_MAC || Q_OS_WIN
}
void Application::startTray() {

View File

@@ -30,6 +30,16 @@ std::map<int, const char*> BetaLogs() {
"- Fix memory leak in Direct3D 11 media viewer on Windows.\n"
},
{
4010004,
"- Statistics in channels and group chats.\n"
"- Nice looking code blocks with syntax highlight.\n"
"- Copy full code block by click on its header.\n"
"- Send a highlighted code block using ```language syntax.\n"
}
};
};

View File

@@ -308,7 +308,6 @@ void BotCommandClickHandler::onClick(ClickContext context) const {
.peer = peer,
.command = _cmd,
.context = my.itemId,
.replyTo = 0,
});
}
}

View File

@@ -344,6 +344,8 @@ QByteArray Settings::serialize() const {
for (const auto &id : _recentEmojiSkip) {
stream << id;
}
stream
<< qint32(_trayIconMonochrome.current() ? 1 : 0);
}
return result;
}
@@ -451,6 +453,7 @@ void Settings::addFromSerialized(const QByteArray &serialized) {
quint64 macRoundIconDigest = _macRoundIconDigest.value_or(0);
qint32 storiesClickTooltipHidden = _storiesClickTooltipHidden.current() ? 1 : 0;
base::flat_set<QString> recentEmojiSkip;
qint32 trayIconMonochrome = (_trayIconMonochrome.current() ? 1 : 0);
stream >> themesAccentColors;
if (!stream.atEnd()) {
@@ -701,6 +704,12 @@ void Settings::addFromSerialized(const QByteArray &serialized) {
}
}
}
if (!stream.atEnd()) {
stream >> trayIconMonochrome;
} else {
// Let existing clients use the old value.
trayIconMonochrome = 0;
}
if (stream.status() != QDataStream::Ok) {
LOG(("App Error: "
"Bad data for Core::Settings::constructFromSerialized()"));
@@ -894,6 +903,7 @@ void Settings::addFromSerialized(const QByteArray &serialized) {
_macRoundIconDigest = macRoundIconDigest ? macRoundIconDigest : std::optional<uint64>();
_storiesClickTooltipHidden = (storiesClickTooltipHidden == 1);
_recentEmojiSkip = std::move(recentEmojiSkip);
_trayIconMonochrome = (trayIconMonochrome == 1);
}
QString Settings::getSoundPath(const QString &key) const {

View File

@@ -702,6 +702,15 @@ public:
[[nodiscard]] rpl::producer<bool> closeToTaskbarChanges() const {
return _closeToTaskbar.changes();
}
void setTrayIconMonochrome(bool value) {
_trayIconMonochrome = value;
}
[[nodiscard]] bool trayIconMonochrome() const {
return _trayIconMonochrome.current();
}
[[nodiscard]] rpl::producer<bool> trayIconMonochromeChanges() const {
return _trayIconMonochrome.changes();
}
void setCustomDeviceModel(const QString &model) {
_customDeviceModel = model;
@@ -918,6 +927,7 @@ private:
rpl::variable<WorkMode> _workMode = WorkMode::WindowAndTray;
base::flags<Calls::Group::StickedTooltip> _hiddenGroupCallTooltips;
rpl::variable<bool> _closeToTaskbar = false;
rpl::variable<bool> _trayIconMonochrome = true;
rpl::variable<QString> _customDeviceModel;
rpl::variable<Media::RepeatMode> _playerRepeatMode;
rpl::variable<Media::OrderMode> _playerOrderMode;

View File

@@ -12,6 +12,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "api/api_text_entities.h"
#include "api/api_chat_filters.h"
#include "api/api_chat_invite.h"
#include "api/api_premium.h"
#include "base/qthelp_regex.h"
#include "base/qthelp_url.h"
#include "lang/lang_cloud_manager.h"
@@ -23,6 +24,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/boxes/confirm_box.h"
#include "boxes/share_box.h"
#include "boxes/connection_box.h"
#include "boxes/gift_premium_box.h"
#include "boxes/sticker_set_box.h"
#include "boxes/sessions_box.h"
#include "boxes/language_box.h"
@@ -348,6 +350,11 @@ bool ResolveUsernameOrPhone(
const auto domainParam = params.value(u"domain"_q);
const auto appnameParam = params.value(u"appname"_q);
if (domainParam == u"giftcode"_q && !appnameParam.isEmpty()) {
ResolveGiftCode(controller, appnameParam);
return true;
}
// Fix t.me/s/username links.
const auto webChannelPreviewLink = (domainParam == u"s"_q)
&& !appnameParam.isEmpty();
@@ -637,6 +644,17 @@ bool OpenExternalLink(
context);
}
bool CopyPeerId(
Window::SessionController *controller,
const Match &match,
const QVariant &context) {
TextUtilities::SetClipboardText(TextForMimeData{ match->captured(1) });
if (controller) {
controller->showToast(tr::lng_text_copied(tr::now));
}
return true;
}
void ExportTestChatTheme(
not_null<Window::SessionController*> controller,
not_null<const Data::CloudTheme*> theme) {
@@ -978,6 +996,10 @@ const std::vector<LocalUrlHandler> &InternalUrlHandlers() {
u"^url:(.+)$"_q,
OpenExternalLink
},
{
u"^copy:(.+)$"_q,
CopyPeerId
}
};
return Result;
}
@@ -1078,7 +1100,7 @@ QString TryConvertUrlToLocal(QString url) {
"("
"/?\\?|"
"/?$|"
"/[a-zA-Z0-9\\.\\_]+/?(\\?|$)|"
"/[a-zA-Z0-9\\.\\_\\-]+/?(\\?|$)|"
"/\\d+/?(\\?|$)|"
"/s/\\d+/?(\\?|$)|"
"/\\d+/\\d+/?(\\?|$)"
@@ -1103,7 +1125,7 @@ QString TryConvertUrlToLocal(QString url) {
added = u"&post="_q + postMatch->captured(1);
} else if (const auto storyMatch = regex_match(u"^/s/(\\d+)(/?\\?|/?$)"_q, usernameMatch->captured(2))) {
added = u"&story="_q + storyMatch->captured(1);
} else if (const auto appNameMatch = regex_match(u"^/([a-zA-Z0-9\\.\\_]+)(/?\\?|/?$)"_q, usernameMatch->captured(2))) {
} else if (const auto appNameMatch = regex_match(u"^/([a-zA-Z0-9\\.\\_\\-]+)(/?\\?|/?$)"_q, usernameMatch->captured(2))) {
added = u"&appname="_q + appNameMatch->captured(1);
}
return base + added + (params.isEmpty() ? QString() : '&' + params);

View File

@@ -94,9 +94,10 @@ const auto CommandByName = base::flat_map<QString, Command>{
{ u"read_chat"_q , Command::ReadChat },
// Shortcuts that have no default values.
{ u"message"_q , Command::JustSendMessage },
{ u"message_silently"_q , Command::SendSilentMessage },
{ u"message_scheduled"_q , Command::ScheduleMessage },
{ u"message"_q , Command::JustSendMessage },
{ u"message_silently"_q , Command::SendSilentMessage },
{ u"message_scheduled"_q , Command::ScheduleMessage },
{ u"media_viewer_video_fullscreen"_q , Command::MediaViewerFullscreen },
//
};

View File

@@ -59,6 +59,8 @@ enum class Command {
ReadChat,
MediaViewerFullscreen,
SupportReloadTemplates,
SupportToggleMuted,
SupportScrollToCurrent,

View File

@@ -243,6 +243,16 @@ bool UiIntegration::handleUrlClick(
return true;
}
bool UiIntegration::copyPreOnClick(const QVariant &context) {
const auto my = context.value<ClickHandlerContext>();
if (const auto window = my.sessionWindow.get()) {
window->showToast(tr::lng_code_copied(tr::now));
} else if (my.show) {
my.show->showToast(tr::lng_code_copied(tr::now));
}
return true;
}
std::unique_ptr<Ui::Text::CustomEmoji> UiIntegration::createCustomEmoji(
const QString &data,
const std::any &context) {
@@ -338,6 +348,10 @@ QString UiIntegration::phraseFormattingStrikeOut() {
return tr::lng_menu_formatting_strike_out(tr::now);
}
QString UiIntegration::phraseFormattingBlockquote() {
return tr::lng_menu_formatting_blockquote(tr::now);
}
QString UiIntegration::phraseFormattingMonospace() {
return tr::lng_menu_formatting_monospace(tr::now);
}
@@ -394,6 +408,10 @@ QString UiIntegration::phraseBotAllowWriteConfirm() {
return tr::lng_bot_allow_write_confirm(tr::now);
}
QString UiIntegration::phraseQuoteHeaderCopy() {
return tr::lng_code_block_header_copy(tr::now);
}
bool OpenGLLastCheckFailed() {
return QFile::exists(OpenGLCheckFilePath());
}

View File

@@ -53,6 +53,7 @@ public:
bool handleUrlClick(
const QString &url,
const QVariant &context) override;
bool copyPreOnClick(const QVariant &context) override;
rpl::producer<> forcePopupMenuHideRequests() override;
const Ui::Emoji::One *defaultEmojiVariant(
const Ui::Emoji::One *emoji) override;
@@ -76,6 +77,7 @@ public:
QString phraseFormattingItalic() override;
QString phraseFormattingUnderline() override;
QString phraseFormattingStrikeOut() override;
QString phraseFormattingBlockquote() override;
QString phraseFormattingMonospace() override;
QString phraseFormattingSpoiler() override;
QString phraseButtonOk() override;
@@ -90,6 +92,7 @@ public:
QString phraseBotAllowWrite() override;
QString phraseBotAllowWriteTitle() override;
QString phraseBotAllowWriteConfirm() override;
QString phraseQuoteHeaderCopy() override;
};

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 = 4010002;
constexpr auto AppVersionStr = "4.10.2";
constexpr auto AppVersion = 4011002;
constexpr auto AppVersionStr = "4.11.2";
constexpr auto AppBetaVersion = false;
constexpr auto AppAlphaVersion = TDESKTOP_ALPHA_VERSION;

View File

@@ -8,6 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "countries/countries_instance.h"
#include "base/qt/qt_common_adapters.h"
#include "base/qt/qt_string_view.h"
namespace Countries {
namespace {
@@ -253,7 +254,7 @@ const std::array<Info, 231> FallbackList = { {
CountriesInstance::CountriesInstance() {
}
const std::vector<Info> &CountriesInstance::list() {
const std::vector<Info> &CountriesInstance::list() const {
if (_list.empty()) {
_list = (FallbackList | ranges::to_vector);
}
@@ -267,7 +268,7 @@ void CountriesInstance::setList(std::vector<Info> &&infos) {
_updated.fire({});
}
const CountriesInstance::Map &CountriesInstance::byCode() {
const CountriesInstance::Map &CountriesInstance::byCode() const {
if (_byCode.empty()) {
_byCode.reserve(list().size());
for (const auto &entry : list()) {
@@ -279,7 +280,7 @@ const CountriesInstance::Map &CountriesInstance::byCode() {
return _byCode;
}
const CountriesInstance::Map &CountriesInstance::byISO2() {
const CountriesInstance::Map &CountriesInstance::byISO2() const {
if (_byISO2.empty()) {
_byISO2.reserve(list().size());
for (const auto &entry : list()) {
@@ -289,7 +290,7 @@ const CountriesInstance::Map &CountriesInstance::byISO2() {
return _byISO2;
}
QString CountriesInstance::validPhoneCode(QString fullCode) {
QString CountriesInstance::validPhoneCode(QString fullCode) const {
const auto &listByCode = byCode();
while (fullCode.length()) {
const auto i = listByCode.constFind(fullCode);
@@ -301,20 +302,34 @@ QString CountriesInstance::validPhoneCode(QString fullCode) {
return QString();
}
QString CountriesInstance::countryNameByISO2(const QString &iso) {
QString CountriesInstance::countryNameByISO2(const QString &iso) const {
const auto &listByISO2 = byISO2();
const auto i = listByISO2.constFind(iso);
return (i != listByISO2.cend()) ? (*i)->name : QString();
}
QString CountriesInstance::countryISO2ByPhone(const QString &phone) {
QString CountriesInstance::countryISO2ByPhone(const QString &phone) const {
const auto &listByCode = byCode();
const auto code = validPhoneCode(phone);
const auto i = listByCode.find(code);
return (i != listByCode.cend()) ? (*i)->iso2 : QString();
}
FormatResult CountriesInstance::format(FormatArgs args) {
QString CountriesInstance::flagEmojiByISO2(const QString &iso) const {
if (iso.size() != 2
|| iso.front() < 'A'
|| iso.front() > 'Z'
|| iso.back() < 'A'
|| iso.back() > 'Z') {
return QString();
}
auto result = QString(4, QChar(0xD83C));
result[1] = QChar(iso.front().unicode() - 'A' + 0xDDE6);
result[3] = QChar(iso.back().unicode() - 'A' + 0xDDE6);
return result;
}
FormatResult CountriesInstance::format(FormatArgs args) const {
// Ported from TDLib.
if (args.phone.isEmpty()) {
return FormatResult();

View File

@@ -43,25 +43,26 @@ public:
using Map = QHash<QString, const Info *>;
CountriesInstance();
[[nodiscard]] const std::vector<Info> &list();
[[nodiscard]] const std::vector<Info> &list() const;
void setList(std::vector<Info> &&infos);
[[nodiscard]] const Map &byCode();
[[nodiscard]] const Map &byISO2();
[[nodiscard]] const Map &byCode() const;
[[nodiscard]] const Map &byISO2() const;
[[nodiscard]] QString validPhoneCode(QString fullCode);
[[nodiscard]] QString countryNameByISO2(const QString &iso);
[[nodiscard]] QString countryISO2ByPhone(const QString &phone);
[[nodiscard]] QString validPhoneCode(QString fullCode) const;
[[nodiscard]] QString countryNameByISO2(const QString &iso) const;
[[nodiscard]] QString countryISO2ByPhone(const QString &phone) const;
[[nodiscard]] QString flagEmojiByISO2(const QString &iso) const;
[[nodiscard]] FormatResult format(FormatArgs args);
[[nodiscard]] FormatResult format(FormatArgs args) const;
[[nodiscard]] rpl::producer<> updated() const;
private:
std::vector<Info> _list;
mutable std::vector<Info> _list;
Map _byCode;
Map _byISO2;
mutable Map _byCode;
mutable Map _byISO2;
rpl::event_stream<> _updated;

View File

@@ -0,0 +1,43 @@
/*
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
namespace Data {
struct BoostsOverview final {
bool isBoosted = false;
int level = 0;
int boostCount = 0;
int currentLevelBoostCount = 0;
int nextLevelBoostCount = 0;
int premiumMemberCount = 0;
float64 premiumMemberPercentage = 0;
};
struct Boost final {
UserId userId = UserId(0);
QDateTime expirationDate;
};
struct BoostsListSlice final {
struct OffsetToken final {
QString next;
};
std::vector<Boost> list;
int total = 0;
bool allLoaded = false;
OffsetToken token;
};
struct BoostStatus final {
BoostsOverview overview;
BoostsListSlice firstSlice;
QString link;
};
} // namespace Data

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