Compare commits

...

195 Commits

Author SHA1 Message Date
John Preston
f75429cbaa Version 5.2.1: Shorten webview socket path.
Fixes #28106.
2024-07-01 21:35:39 +04:00
John Preston
dc8c4a8332 Version 5.2.1: No auto-enable system password unlock on macOS. 2024-07-01 21:03:05 +04:00
John Preston
096530c96a Version 5.2.1: Fix drafts / translations crash on macOS. 2024-07-01 19:21:27 +04:00
23rd
ed0850d823 Renamed files for bot and channel earn sections with different names. 2024-07-01 15:40:39 +03:00
John Preston
4a2173deaf Version 5.2.1.
- Fix crash when opening topic in a new window.
- Fix crash in topic search scope dropdown.
- Fix crash in video player.
- Fix feeze and crash in Instant View (Windows).
- Allow unlock by Apple Watch or System Password (macOS).
2024-07-01 15:01:35 +04:00
John Preston
f40a584905 Support unlock with Watch/SystemPassword. 2024-07-01 15:01:35 +04:00
John Preston
e54204b136 Don't allow forwarding local items by drag. 2024-07-01 13:20:45 +04:00
John Preston
715dff0a3e Fix a crash and a memory leak in video player.
Fixes #28104.
2024-07-01 11:03:19 +04:00
23rd
558daa3382 Fixed align of icons in list of top-up credit options. 2024-07-01 10:50:52 +04:00
GitHub Action
7265297b19 Update User-Agent for DNS to Chrome 126.0.0.0. 2024-07-01 10:49:45 +04:00
John Preston
1fac6db8bd Fix crash in topic search context menu destruction. 2024-07-01 10:46:34 +04:00
John Preston
c9bd776d1e Fix freeze in IV due to cyclic focus request. 2024-07-01 10:38:41 +04:00
John Preston
179e81478e Ignore filter switching in separate window archive. 2024-07-01 09:21:18 +04:00
John Preston
9ef74c510c Fix crash in opening topics in a new window. 2024-07-01 09:07:45 +04:00
Ilya Fedin
36f6917bd3 Query QWindow::windowState in media viewer 2024-07-01 07:56:55 +04:00
John Preston
707951accb Version 5.2: Fix build with GCC. 2024-06-30 23:59:03 +04:00
John Preston
7eb98b50ec Version 5.2.
- Pay for content with Telegram Stars.
- Enable local passcode unlock by Windows Hello and Touch ID.
- Allow opening forums, topics and archive in a separate window.
2024-06-30 21:21:51 +04:00
John Preston
65f7bdb914 Update API scheme on layer 183. 2024-06-30 21:16:11 +04:00
John Preston
876c47c436 Add 'section_separator_color' to webview palette. 2024-06-30 21:16:11 +04:00
John Preston
e8f16840de Fix wrong search focusing.
Fixes #28052.
2024-06-30 21:16:11 +04:00
John Preston
dd57ad567f Don't break by '/', '.' in the middle of the word.
Fixes #27999.
2024-06-30 21:16:11 +04:00
John Preston
54934fb835 Fix problems with message effect preview. 2024-06-30 21:16:11 +04:00
John Preston
bd49887607 Fix paid media price tag background. 2024-06-30 21:16:11 +04:00
John Preston
aaa72b7c30 Improve system unlock button area. 2024-06-30 21:16:10 +04:00
John Preston
4cafacc8db Add message effects promo section. 2024-06-30 21:16:10 +04:00
John Preston
374b3c68ac Fix cancel search with a similar channel open.
Fixes #28079.
2024-06-30 21:16:10 +04:00
John Preston
69d21f73ef Separate window for forums/topics/sublists/archive. 2024-06-30 21:16:10 +04:00
23rd
6dc3bd65e8 Improved spell checking on macOS. 2024-06-30 21:16:10 +04:00
23rd
deb9aa435b Improved first display of personal channel in user profiles. 2024-06-30 21:16:10 +04:00
23rd
68cb568898 Fixed build with Qt6. 2024-06-30 21:16:10 +04:00
23rd
1ed81b1c9c Improved process of mtp updates for revenue credits in earn section. 2024-06-30 21:16:10 +04:00
23rd
a95fb5b28d Slightly improved withdraw phrases to fit small buttons. 2024-06-30 21:16:10 +04:00
23rd
4f7b5ca7da Added process of server notification for disabled withdrawal feature. 2024-06-30 21:16:10 +04:00
23rd
29b7673b88 Simplified handle of incoming credit history entries. 2024-06-30 21:16:10 +04:00
23rd
66f375d2c6 Fixed endless memory copying in list of credits history entries. 2024-06-30 21:16:10 +04:00
John Preston
77d214d2a5 Support paid_media_allowed flag in ChannelFull. 2024-06-30 21:16:10 +04:00
John Preston
09e6077e97 Suggest global search of cashtags. 2024-06-30 21:16:10 +04:00
John Preston
631d6abb06 Improve formatting in message field. 2024-06-30 21:16:10 +04:00
John Preston
c833b8a1b0 Make purchased media badge better looking. 2024-06-30 21:16:10 +04:00
John Preston
b58c03f0de Fix build with Xcode. 2024-06-30 21:16:10 +04:00
John Preston
1988435cdf Add a Windows Hello / Touch ID system unlock. 2024-06-30 21:16:10 +04:00
John Preston
7e704d9529 Show price in my/purchased paid media. 2024-06-30 21:16:10 +04:00
John Preston
a39a8dbd2c Show paid media from transactions history. 2024-06-30 21:16:10 +04:00
John Preston
90dfae52f5 Show nice thumbnails for paid albums. 2024-06-30 21:16:10 +04:00
John Preston
dfc422b505 Show paid media albums with context. 2024-06-30 21:16:10 +04:00
John Preston
54cc12cf22 Show media payments as outgoing. 2024-06-30 21:16:10 +04:00
John Preston
d81e832ae6 Use lang key for about stars link. 2024-06-30 21:16:10 +04:00
John Preston
1399d2501d Show first paid media in transactions history. 2024-06-30 21:16:09 +04:00
John Preston
57254ca259 Show a credits emoji in chats list preview. 2024-06-30 21:16:09 +04:00
John Preston
043e3ae97e Use star emoji fallback for credits custom emoji. 2024-06-30 21:16:09 +04:00
John Preston
68b9a8bc6a Fix file reference refresh for paid media. 2024-06-30 21:16:09 +04:00
23rd
157d5c743b Renamed file for data of credits earn statistics. 2024-06-30 21:16:09 +04:00
23rd
54d0290ba2 Added warning to channel earn section when user has no cloud password. 2024-06-30 21:16:09 +04:00
23rd
0fc2df8eec Added transaction link and date to details of credits history entries. 2024-06-30 21:16:09 +04:00
23rd
d9caf15d1d Added initial ability to reinvest existing credits for channels. 2024-06-30 21:16:09 +04:00
23rd
b674826392 Added special widget for credits input to section of channel earn. 2024-06-30 21:16:09 +04:00
23rd
37181f9d0a Moved out special widget for credits input to single place. 2024-06-30 21:16:09 +04:00
23rd
93aebc747d Moved out special input for credits to td_ui. 2024-06-30 21:16:09 +04:00
23rd
a84ac933dd Added support of api update to history lists in section of channel earn. 2024-06-30 21:16:09 +04:00
23rd
b79c306bfe Added list of credits history to section of channel earn. 2024-06-30 21:16:09 +04:00
23rd
57d62423b3 Moved out custom slider class with natural width to single place. 2024-06-30 21:16:09 +04:00
23rd
f4674389d5 Added chart of credits revenue to section of channel earn. 2024-06-30 21:16:09 +04:00
23rd
63c4c5064f Added support of api updates for overview in channel earn section. 2024-06-30 21:16:09 +04:00
23rd
ae1f364730 Added rpl support to label with icon in section of channel earn. 2024-06-30 21:16:09 +04:00
23rd
c6e322de86 Added credits oveview to section of channel earn. 2024-06-30 21:16:09 +04:00
23rd
68bf6f991c Moved out util for wrapping credits icon to widget. 2024-06-30 21:16:09 +04:00
23rd
b15f5f8596 Added credits data to saved state of channel earn section. 2024-06-30 21:16:09 +04:00
23rd
27a71a8dcd Renamed BotEarnStatistics to CreditsEarnStatistics. 2024-06-30 21:16:08 +04:00
John Preston
2044f8f9ad Update API scheme on layer 183. 2024-06-30 21:16:08 +04:00
23rd
ffa4b1db87 Fixed withdrawal button for credits with minimal amount. 2024-06-30 21:16:08 +04:00
23rd
cad25ae644 Fixed unique row id in lists of credits history entries. 2024-06-30 21:16:08 +04:00
23rd
21094fe11b Slightly improved process of incoming credit history entries. 2024-06-30 21:16:08 +04:00
23rd
101dbdf243 Added support of MTP updates to lists of credits history entries. 2024-06-30 21:16:08 +04:00
John Preston
0dc92762bc Show link to message in transactions history. 2024-06-30 21:16:08 +04:00
John Preston
5fdaa6b91f Record bareMsgId in CreditsHistoryEntry. 2024-06-30 21:16:08 +04:00
John Preston
968d036834 Use Ui::NumberInput for the paid media price. 2024-06-30 21:16:08 +04:00
John Preston
d47c5df73d Nice price tag on sending media. 2024-06-30 21:16:08 +04:00
John Preston
6c1e7357c6 Nice unlock media stars, unlock done tooltip. 2024-06-30 21:16:08 +04:00
John Preston
479b63c33a Customize pay-by-stars box for paid media. 2024-06-30 21:16:08 +04:00
John Preston
950a946a16 Show correct chat preview for paid media. 2024-06-30 21:16:08 +04:00
John Preston
5f8da27c86 Display nice price tag, handle pay in albums. 2024-06-30 21:16:08 +04:00
John Preston
a9bd7803e6 Edit price on send, send single paid media. 2024-06-30 21:16:08 +04:00
John Preston
3ece9b1566 Update API scheme to layer 183. Paid media. 2024-06-30 21:16:08 +04:00
John Preston
e71a067f4b Fix build on Windows. 2024-06-30 21:16:08 +04:00
23rd
ebf456abe4 Added initial support of lists for credits history entries in bot earn. 2024-06-30 21:16:08 +04:00
23rd
3552da5ce7 Added additional row in table for some types of credits history entries. 2024-06-30 21:16:08 +04:00
23rd
b5bd0f53ad Added support of new statuses of credits history entries. 2024-06-30 21:16:08 +04:00
23rd
7d115b3fab Improved api support to credit history entries. 2024-06-30 21:16:08 +04:00
23rd
d0a030ab58 Added support of minimal amount of credits to withdrawal button. 2024-06-30 21:16:08 +04:00
23rd
712c06756e Added icon to label in withdrawal button from bot earn section. 2024-06-30 21:16:08 +04:00
23rd
301ffc15ef Added countdown label to withdrawal button in bot earn section. 2024-06-30 21:16:07 +04:00
23rd
3c4a711b5d Added withdrawal button to bot earn sections. 2024-06-30 21:16:07 +04:00
23rd
989145726d Made withdrawal button handler in earn sections more universal. 2024-06-30 21:16:07 +04:00
23rd
9eebd3b514 Added statistical chart and balance overview to bot earn section. 2024-06-30 21:16:07 +04:00
23rd
eb997ae9e3 Added initial implementation of filter for MTP updates. 2024-06-30 21:16:07 +04:00
23rd
db4c9b83f3 Added credits icon to statistical charts. 2024-06-30 21:16:07 +04:00
23rd
1196b6a3fb Moved out generating of credits icon to td_ui. 2024-06-30 21:16:07 +04:00
23rd
bef216bc93 Moved out colorizing of svg star to td_ui. 2024-06-30 21:16:07 +04:00
23rd
811d75e383 Improved display of different inner currencies in statistical charts. 2024-06-30 21:16:07 +04:00
23rd
049cde48ee Added initial api support of bot earn statistics. 2024-06-30 21:16:07 +04:00
23rd
cb65c50c19 Moved out deserialization of MTP statistical graph to separated file. 2024-06-30 21:16:07 +04:00
23rd
f23c9a61bc Moved out StatisticsRequestSender to separated file. 2024-06-30 21:16:07 +04:00
23rd
b5d5ff3cbb Improved naming of classes for channel earn. 2024-06-30 21:16:07 +04:00
23rd
c5ba0fa705 Added initial structure for bot earn data. 2024-06-30 21:16:07 +04:00
23rd
71893f4ef7 Added initial implementation of entry point to bot earn section. 2024-06-30 21:16:07 +04:00
23rd
4a60c57661 Added dummy files of bot earn section. 2024-06-30 21:16:07 +04:00
John Preston
fbbcc21198 Update API scheme to layer 182. 2024-06-30 21:16:07 +04:00
Ilya Fedin
d993386756 Fix debug-only openssl build on Windows 2024-06-30 11:31:57 +04:00
Ilya Fedin
30819509d3 Set shortcut context for global menu shortcuts 2024-06-30 10:11:40 +04:00
mrbesen
10c3fe0f63 add option to export html and json in global export 2024-06-27 10:49:39 +04:00
Ilya Fedin
3498a7f0ee Update submodules 2024-06-24 16:23:32 +04:00
Ilya Fedin
648b23b548 Do non-getting XCB requests synchronously 2024-06-24 16:23:32 +04:00
Ilya Fedin
ba89912834 Switch more XCB code to base::Platform::XCB::Connection 2024-06-24 16:23:32 +04:00
Ilya Fedin
c1bc7e6ab1 Simplify PipPanel::handleWaylandResize 2024-06-22 11:32:36 +04:00
Ilya Fedin
eea50ed6b0 Delay UpdatePowerSaveBlocker to show in media viewer 2024-06-22 11:32:36 +04:00
Ilya Fedin
006d6fe2c0 Use RpWidget::screenValue 2024-06-22 11:32:36 +04:00
Ilya Fedin
5180e0ec57 Use QWidget::setScreen with Qt 6 2024-06-22 11:32:36 +04:00
Ilya Fedin
490ec7949f Don't call hide/createWinId just to call show right after that in Window::Notifications::Default::HideAllButton 2024-06-22 11:32:36 +04:00
Ilya Fedin
671a15d763 Update skip taskbar hint on Linux without QWindow events 2024-06-22 11:32:36 +04:00
Ilya Fedin
d2d5226dc7 Update exposed state for connecting widgets without QWindow events 2024-06-22 11:32:36 +04:00
Ilya Fedin
b58ece3a38 Update lib_ui 2024-06-22 11:32:36 +04:00
Ilya Fedin
2b4a2b5b97 Fix a crash in Dialogs::Widget::setupStories 2024-06-22 11:32:36 +04:00
Ilya Fedin
382b175db2 Fix build on Linux 2024-06-22 11:32:36 +04:00
John Preston
2db81211c8 Fix configure on macOS. 2024-06-21 22:38:49 +04:00
John Preston
46157c99c4 Allow saving empty syntax highlight language. 2024-06-21 22:38:49 +04:00
John Preston
c3ed5224c2 Use tripple-enter to jump out of a block. 2024-06-21 22:38:49 +04:00
John Preston
acee7c7cfc More delay-load libraries on Windows with Qt 6. 2024-06-21 13:25:48 +04:00
John Preston
002fe9a72a Fix build. 2024-06-21 11:55:25 +04:00
John Preston
93b7c47cda Support building on Windows with Qt 6. 2024-06-21 11:36:18 +04:00
Ilya Fedin
053f8ad1c0 Ensure media viewer has native window handle before using it 2024-06-21 08:19:44 +04:00
Ilya Fedin
60ca6895db Update submodules 2024-06-19 10:33:33 +04:00
Ilya Fedin
fc5e9414b7 Subscribe to QEvent::WindowStateChange instead of QWindow::windowStateChanged 2024-06-19 10:33:33 +04:00
Ilya Fedin
f768e405fa Use RpWidget::shownValue instead of QWindow::visibleChanged 2024-06-19 10:33:33 +04:00
Ilya Fedin
a22cf8e303 Use RpWidget::windowActiveValue 2024-06-19 10:33:33 +04:00
Ilya Fedin
8a5797e1bd Re-create QSystemTrayIcon on SNI watcher owner change 2024-06-19 10:31:59 +04:00
Ilya Fedin
750ad600be Store media viewer geometry the same way as main window 2024-06-19 10:08:14 +04:00
Ilya Fedin
eaf4575eb8 Fix IV window geometry restoration 2024-06-19 10:08:14 +04:00
Ilya Fedin
b9677fe1db Update Qt to 6.7.2 on Linux 2024-06-18 22:01:23 +04:00
Ilya Fedin
4047f1733d Update submodules 2024-06-16 20:59:32 +04:00
John Preston
3d114131e0 Fix premium gifting recipient choosing. 2024-06-16 10:27:38 +04:00
John Preston
5810149a77 Improve chats search loading indication. 2024-06-16 10:27:38 +04:00
Ilya Fedin
032fe3e0fc Remove std::optional indrection for nullable XCB types 2024-06-16 09:54:04 +04:00
John Preston
81d7fcba7e Beta version 5.1.8: Fix build with GCC. 2024-06-15 23:03:55 +04:00
John Preston
4e9b5b0d33 Beta version 5.1.8: Fix build. 2024-06-15 22:45:21 +04:00
John Preston
90068f6261 Beta version 5.1.8: Update tg_owt on Linux. 2024-06-15 21:16:55 +04:00
John Preston
f37d056c14 Beta version 5.1.8.
- Support nice blockquotes and code blocks edition when composing messages.
- Support collapsing blockquotes and specifying syntax highlight language.
- Support nice spoiler animation in the message composing input field.
2024-06-15 21:06:23 +04:00
John Preston
f748988ae3 Update Qt patches. 2024-06-15 20:59:19 +04:00
John Preston
9a25d2c413 Simplify correct unicode characters counting. 2024-06-15 20:57:48 +04:00
John Preston
ec40292cbf Allow editing of collapsed quotes. 2024-06-15 20:57:30 +04:00
John Preston
5e8c3fb146 Allow editing syntax highlighting language. 2024-06-15 20:57:00 +04:00
John Preston
fa8ed186d8 Improve field fade conditions. 2024-06-15 20:56:45 +04:00
John Preston
c1f36d43d0 Support modern blockquote/pre display. 2024-06-15 20:56:44 +04:00
John Preston
501cae2200 Change InputField::font to InputField::style in styles. 2024-06-15 20:56:24 +04:00
John Preston
8ad5117495 Update lib_ui. 2024-06-15 20:55:09 +04:00
Ilya Fedin
d79da3d884 Remove old notification descturction workaround for old X11 systems
There seem to be no need in it anymore
2024-06-15 11:08:06 +04:00
Ilya Fedin
e4d88f829c Remove old window activation workaround for X11 systems
There seem to be no need in it anymore
2024-06-15 11:08:06 +04:00
Ilya Fedin
2673e1df53 Fix IV window on Linux 2024-06-15 11:04:45 +04:00
John Preston
99e88d74bc Version 5.1.7: Fix build with Qt 6. 2024-06-14 19:06:19 +04:00
John Preston
43ffc9d67c Version 5.1.7.
- Fix recently searched hashtags in chats search.
- Fix formatting shortcuts on macOS.
- Fix non-Telegram-Stars-invoice bot buttons with star emoji.
2024-06-14 18:36:28 +04:00
John Preston
053c462dc0 Fix recent hashtags in chats search. 2024-06-14 18:34:59 +04:00
John Preston
174b627a78 Fix markdown shortcuts on macOS / some Linux. 2024-06-14 17:39:16 +04:00
23rd
bb33d0b997 Replaced credits currency with icon only in buttons with Buy type. 2024-06-14 15:32:02 +03:00
pandaninjas
6f6fb3d1b6 Add xlsb to list of executable extensions 2024-06-14 13:49:15 +04:00
Ilya Fedin
3f216ad946 Enable parallelism for more dependencies 2024-06-13 21:35:34 +04:00
John Preston
c49eb7041f Version 5.1.6: Fix sticker effect filtering. 2024-06-13 20:55:49 +04:00
John Preston
a1d8202644 Version 5.1.6.
- Fix search in archived chats in single-column layout.
- Improve chat previews for topics, Saved Messages and groups.
- Fix formatting shortcuts on Linux.
- Fix options for Telegram Stars buying in case of large amounts.
2024-06-13 18:48:35 +04:00
23rd
82428aef28 Added support for quoted-printable encoding to contact media. 2024-06-13 18:44:33 +04:00
23rd
10f7b985c7 Added decimal separators to count of members in shared similar channels. 2024-06-13 18:44:33 +04:00
23rd
06075411a5 Fixed cases when list of top-up credit options is empty. 2024-06-13 18:44:33 +04:00
23rd
7e01b12825 Replaced credits currency with icon in buttons of reply keyboard. 2024-06-13 18:44:33 +04:00
John Preston
273119fc55 Fix preview messages load in migrated groups. 2024-06-13 18:43:58 +04:00
John Preston
e47e4ba338 Update submodules. 2024-06-13 18:08:43 +04:00
John Preston
cbcdeae200 Don't show topic buttons in topic preview. 2024-06-13 18:08:36 +04:00
John Preston
c585112e37 Fix jump to archive from search in chat. 2024-06-13 16:33:58 +04:00
John Preston
392df8b56f Fix search in archived chats and return back. 2024-06-13 16:24:41 +04:00
John Preston
0e6470a087 Don't restrict Saved Messages by premium-only privacy. 2024-06-13 15:47:40 +04:00
John Preston
6c0ea0eb9f Version 5.1.5.
- Return WebView on Windows.
2024-06-07 06:57:27 +04:00
John Preston
9229de2658 Return webview on Windows. 2024-06-07 06:57:06 +04:00
John Preston
2935721cd0 Version 5.1.4.
- Improve design of search in chat.
- Show vCard information for shared contacts.
- Allow scheduling media in topic groups.
- Several minor bugfixes.
2024-06-06 22:19:53 +04:00
John Preston
a48cd5f15a Don't show IV buttons on old Windows. 2024-06-06 22:17:30 +04:00
John Preston
9e77e80f92 Moderation on context menu album delete.
Fixes #28012.
2024-06-06 22:16:57 +04:00
John Preston
deb50ee528 Fix video full-window toggle in windowed mode. 2024-06-06 22:16:53 +04:00
John Preston
14909ae913 Fix empty space to the right of photo media. 2024-06-06 22:16:48 +04:00
John Preston
d5d9da7d0a Allow scheduling media in topic groups. 2024-06-06 22:16:43 +04:00
John Preston
86a048a021 Update libvpx to 1.14.1. 2024-06-06 22:16:38 +04:00
23rd
aafa8631e0 Moved out url for credits terms to lang pack. 2024-06-06 22:16:38 +04:00
Ilya Fedin
9176bf2e47 Update lib_webview 2024-06-06 21:08:54 +04:00
John Preston
72c667b153 Redesign search scope selection. 2024-06-06 09:12:23 +04:00
23rd
fe6f65b3ab Fixed text elision of peer name in section of chat preview. 2024-06-06 09:12:23 +04:00
23rd
5db2821f8c Removed unused refund api support for credits. 2024-06-06 09:12:23 +04:00
23rd
9e3e7265d2 Improved display of cached message preview in profile sections. 2024-06-06 09:12:23 +04:00
23rd
749b2e0e95 Added ability to see vcard if available to all types if contacts media. 2024-06-06 09:12:23 +04:00
Ilya Fedin
5b45397383 Update lib_webview 2024-06-06 09:12:03 +04:00
Ilya Fedin
61c17c0a93 libasound2 -> libasound2t64 2024-06-04 15:48:18 +04:00
John Preston
93e592472c Version 5.1.3.
- Rebuild version for macOS to fix the phrases.
2024-06-04 14:38:05 +04:00
307 changed files with 8776 additions and 3389 deletions

View File

@@ -57,14 +57,6 @@ include(cmake/validate_d3d_compiler.cmake)
include(cmake/target_prepare_qrc.cmake)
include(cmake/options.cmake)
if (NOT DESKTOP_APP_USE_PACKAGED)
if (WIN32)
set(qt_version 5.15.13)
elseif (APPLE)
set(qt_version 6.2.8)
endif()
endif()
include(cmake/external/qt/package.cmake)
set(desktop_app_skip_libs

View File

@@ -126,6 +126,7 @@ PRIVATE
api/api_earn.h
api/api_editing.cpp
api/api_editing.h
api/api_filter_updates.h
api/api_global_privacy.cpp
api/api_global_privacy.h
api/api_hash.cpp
@@ -164,6 +165,10 @@ PRIVATE
api/api_single_message_search.h
api/api_statistics.cpp
api/api_statistics.h
api/api_statistics_data_deserialize.cpp
api/api_statistics_data_deserialize.h
api/api_statistics_sender.cpp
api/api_statistics_sender.h
api/api_text_entities.cpp
api/api_text_entities.h
api/api_toggling_media.cpp
@@ -722,8 +727,6 @@ PRIVATE
history/view/media/history_view_dice.h
history/view/media/history_view_document.cpp
history/view/media/history_view_document.h
history/view/media/history_view_extended_preview.cpp
history/view/media/history_view_extended_preview.h
history/view/media/history_view_file.cpp
history/view/media/history_view_file.h
history/view/media/history_view_game.cpp
@@ -887,6 +890,10 @@ PRIVATE
history/history_view_highlight_manager.h
history/history_widget.cpp
history/history_widget.h
info/bot/earn/info_bot_earn_list.cpp
info/bot/earn/info_bot_earn_list.h
info/bot/earn/info_bot_earn_widget.cpp
info/bot/earn/info_bot_earn_widget.h
info/channel_statistics/boosts/create_giveaway_box.cpp
info/channel_statistics/boosts/create_giveaway_box.h
info/channel_statistics/boosts/giveaway/giveaway_list_controllers.cpp
@@ -895,10 +902,10 @@ PRIVATE
info/channel_statistics/boosts/info_boosts_inner_widget.h
info/channel_statistics/boosts/info_boosts_widget.cpp
info/channel_statistics/boosts/info_boosts_widget.h
info/channel_statistics/earn/info_earn_inner_widget.cpp
info/channel_statistics/earn/info_earn_inner_widget.h
info/channel_statistics/earn/info_earn_widget.cpp
info/channel_statistics/earn/info_earn_widget.h
info/channel_statistics/earn/info_channel_earn_list.cpp
info/channel_statistics/earn/info_channel_earn_list.h
info/channel_statistics/earn/info_channel_earn_widget.cpp
info/channel_statistics/earn/info_channel_earn_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
@@ -1539,6 +1546,8 @@ PRIVATE
window/window_peer_menu.cpp
window/window_peer_menu.h
window/window_section_common.h
window/window_separate_id.cpp
window/window_separate_id.h
window/window_session_controller.cpp
window/window_session_controller.h
window/window_session_controller_link_info.h
@@ -1828,12 +1837,44 @@ if (WIN32)
/DELAYLOAD:uxtheme.dll
/DELAYLOAD:crypt32.dll
/DELAYLOAD:bcrypt.dll
/DELAYLOAD:imm32.dll
/DELAYLOAD:netapi32.dll
/DELAYLOAD:imm32.dll
/DELAYLOAD:userenv.dll
/DELAYLOAD:wtsapi32.dll
/DELAYLOAD:propsys.dll
)
if (QT_VERSION GREATER 6)
target_link_options(Telegram
PRIVATE
/DELAYLOAD:API-MS-Win-EventLog-Legacy-l1-1-0.dll
/DELAYLOAD:API-MS-Win-Core-Console-l1-1-0.dll
/DELAYLOAD:API-MS-Win-Core-Fibers-l2-1-0.dll
/DELAYLOAD:API-MS-Win-Core-Fibers-l2-1-1.dll
/DELAYLOAD:API-MS-Win-Core-File-l1-1-0.dll
/DELAYLOAD:API-MS-Win-Core-LibraryLoader-l1-2-0.dll
/DELAYLOAD:API-MS-Win-Core-Localization-l1-2-0.dll
/DELAYLOAD:API-MS-Win-Core-Memory-l1-1-0.dll
/DELAYLOAD:API-MS-Win-Core-Memory-l1-1-1.dll
/DELAYLOAD:API-MS-Win-Core-ProcessThreads-l1-1-0.dll
/DELAYLOAD:API-MS-Win-Core-Synch-l1-2-0.dll # Synchronization.lib
/DELAYLOAD:API-MS-Win-Core-SysInfo-l1-1-0.dll
/DELAYLOAD:API-MS-Win-Core-Timezone-l1-1-0.dll
/DELAYLOAD:API-MS-Win-Core-WinRT-l1-1-0.dll
/DELAYLOAD:API-MS-Win-Core-WinRT-Error-l1-1-0.dll
/DELAYLOAD:API-MS-Win-Core-WinRT-String-l1-1-0.dll
/DELAYLOAD:API-MS-Win-Security-CryptoAPI-l1-1-0.dll
/DELAYLOAD:API-MS-Win-Shcore-Scaling-l1-1-1.dll
/DELAYLOAD:authz.dll # Authz.lib
/DELAYLOAD:comdlg32.dll
/DELAYLOAD:dwrite.dll # DWrite.lib
/DELAYLOAD:dxgi.dll # DXGI.lib
/DELAYLOAD:d3d9.dll # D3D9.lib
/DELAYLOAD:d3d11.dll # D3D11.lib
/DELAYLOAD:d3d12.dll # D3D12.lib
/DELAYLOAD:setupapi.dll # SetupAPI.lib
/DELAYLOAD:winhttp.dll
)
endif()
endif()
target_prepare_qrc(Telegram)

Binary file not shown.

After

Width:  |  Height:  |  Size: 766 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 911 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 438 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 631 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 886 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 454 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 772 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 484 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 860 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 726 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

View File

@@ -683,6 +683,14 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_settings_privacy_premium_link" = "Telegram Premium";
"lng_settings_passcode_disable" = "Disable Passcode";
"lng_settings_passcode_disable_sure" = "Are you sure you want to disable passcode?";
"lng_settings_use_winhello" = "Unlock with Windows Hello";
"lng_settings_use_winhello_about" = "You need to enter your passcode once before unlocking Telegram with Windows Hello.";
"lng_settings_use_touchid" = "Unlock with Touch ID";
"lng_settings_use_touchid_about" = "You need to enter your passcode once before unlocking Telegram with Touch ID.";
"lng_settings_use_applewatch" = "Unlock with Apple Watch";
"lng_settings_use_applewatch_about" = "You need to enter your passcode once before unlocking Telegram with Apple Watch.";
"lng_settings_use_systempwd" = "Unlock with System Password";
"lng_settings_use_systempwd_about" = "You need to enter your passcode once before unlocking Telegram with System Password.";
"lng_settings_password_disable" = "Disable Cloud Password";
"lng_settings_password_abort" = "Abort two-step verification setup";
"lng_settings_about_bio" = "Any details such as age, occupation or city.\nExample: 23 y.o. designer from San Francisco";
@@ -897,6 +905,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_settings_gift_premium_users_confirm" = "Proceed";
"lng_settings_gift_premium_users_error#one" = "You can select maximum {count} user.";
"lng_settings_gift_premium_users_error#other" = "You can select maximum {count} users.";
"lng_settings_gift_premium_choose" = "Please choose at least one recipient.";
"lng_backgrounds_header" = "Choose Wallpaper";
"lng_theme_sure_keep" = "Keep this theme?";
@@ -991,6 +1000,12 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_passcode_ph" = "Your passcode";
"lng_passcode_submit" = "Submit";
"lng_passcode_logout" = "Log out";
"lng_passcode_winhello" = "You need to enter your passcode\nbefore you can use Windows Hello.";
"lng_passcode_touchid" = "You need to enter your passcode\nbefore you can use Touch ID.";
"lng_passcode_applewatch" = "You need to enter your passcode\nbefore you can use Watch to unlock.";
"lng_passcode_systempwd" = "You need to enter your passcode\nbefore you can use system password.";
"lng_passcode_winhello_unlock" = "Telegram wants to unlock with Windows Hello.";
"lng_passcode_touchid_unlock" = "unlock";
"lng_passcode_create_button" = "Save Passcode";
"lng_passcode_check_button" = "Submit";
"lng_passcode_change_button" = "Save Passcode";
@@ -1560,6 +1575,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_manage_peer_bot_public_link" = "Public Link";
"lng_manage_peer_bot_public_links" = "Public Links";
"lng_manage_peer_bot_balance" = "Balance";
"lng_manage_peer_bot_edit_intro" = "Edit Intro";
"lng_manage_peer_bot_edit_commands" = "Edit Commands";
"lng_manage_peer_bot_edit_settings" = "Change Bot Settings";
@@ -2185,6 +2201,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_premium_summary_about_translation" = "Real-time translation of channels and chats into other languages.";
"lng_premium_summary_subtitle_business" = "Telegram Business";
"lng_premium_summary_about_business" = "Upgrade your account with business features such as location, opening hours and quick replies.";
"lng_premium_summary_subtitle_effects" = "Message Effects";
"lng_premium_summary_about_effects" = "Add over 500 animated effects to private messages.";
"lng_premium_summary_bottom_subtitle" = "About Telegram Premium";
"lng_premium_summary_bottom_about" = "While the free version of Telegram already gives its users more than any other messaging application, **Telegram Premium** pushes its capabilities even further.\n\n**Telegram Premium** is a paid option, because most Premium Features require additional expenses from Telegram to third parties such as data center providers and server manufacturers. Contributions from **Telegram Premium** users allow us to cover such costs and also help Telegram stay free for everyone.";
"lng_premium_summary_button" = "Subscribe for {cost} per month";
@@ -2316,6 +2334,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_credits_summary_options_more" = "More Options";
"lng_credits_summary_options_about" = "By proceeding and purchasing Stars, you agree with the {link}.";
"lng_credits_summary_options_about_link" = "Terms and Conditions";
"lng_credits_summary_options_about_url" = "https://telegram.org/tos/stars";
"lng_credits_summary_history_tab_full" = "All Transactions";
"lng_credits_summary_history_tab_in" = "Incoming";
"lng_credits_summary_history_tab_out" = "Outgoing";
@@ -2324,15 +2343,35 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_credits_box_out_title" = "Confirm Your Purchase";
"lng_credits_box_out_sure#one" = "Do you want to buy **\"{text}\"** in **{bot}** for **{count} Star**?";
"lng_credits_box_out_sure#other" = "Do you want to buy **\"{text}\"** in **{bot}** for **{count} Stars**?";
"lng_credits_box_out_media#one" = "Do you want to unlock {media} in {chat} for **{count} Star**?";
"lng_credits_box_out_media#other" = "Do you want to unlock {media} in {chat} for **{count} Stars**?";
"lng_credits_box_out_photo" = "a photo";
"lng_credits_box_out_photos#one" = "{count} photo";
"lng_credits_box_out_photos#other" = "{count} photos";
"lng_credits_box_out_video" = "a video";
"lng_credits_box_out_videos#one" = "{count} video";
"lng_credits_box_out_videos#other" = "{count} videos";
"lng_credits_box_out_both" = "{photo} and {video}";
"lng_credits_box_out_confirm#one" = "Confirm and Pay {emoji} {count} Star";
"lng_credits_box_out_confirm#other" = "Confirm and Pay {emoji} {count} Stars";
"lng_credits_box_out_about" = "Review the {link} for Stars.";
"lng_credits_media_done_title" = "Media Unlocked";
"lng_credits_media_done_text#one" = "**{count} Star** transferred to {chat}.";
"lng_credits_media_done_text#other" = "**{count} Stars** transferred to {chat}.";
"lng_credits_summary_in_toast_title" = "Stars Acquired";
"lng_credits_summary_in_toast_about#one" = "**{count}** Star added to your balance.";
"lng_credits_summary_in_toast_about#other" = "**{count}** Stars added to your balance.";
"lng_credits_box_history_entry_peer" = "Recipient";
"lng_credits_box_history_entry_via" = "Via";
"lng_credits_box_history_entry_play_market" = "Play Market";
"lng_credits_box_history_entry_app_store" = "App Store";
"lng_credits_box_history_entry_fragment" = "Fragment";
"lng_credits_box_history_entry_ads" = "Ads Platform";
"lng_credits_box_history_entry_id" = "Transaction ID";
"lng_credits_box_history_entry_id_copied" = "Transaction ID copied to clipboard.";
"lng_credits_box_history_entry_success_date" = "Transaction date";
"lng_credits_box_history_entry_success_url" = "Transaction link";
"lng_credits_box_history_entry_media" = "Media";
"lng_credits_box_history_entry_about" = "You can dispute this transaction {link}.";
"lng_credits_box_history_entry_about_link" = "here";
"lng_credits_small_balance_title#one" = "{count} Star Needed";
@@ -3304,6 +3343,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_context_spoiler_effect" = "Hide with Spoiler";
"lng_context_disable_spoiler" = "Remove Spoiler";
"lng_context_make_paid" = "Make This Content Paid";
"lng_context_change_price" = "Change Price";
"lng_factcheck_title" = "Fact Check";
"lng_factcheck_placeholder" = "Add Facts or Context";
@@ -3315,6 +3356,14 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_factcheck_bottom" = "This clarification was provided by a fact checking agency assigned by the department of the government of your country ({country}) responsible for combatting misinformation.";
"lng_factcheck_links" = "Only **t.me/** links are allowed.";
"lng_paid_title" = "Paid Content";
"lng_paid_enter_cost" = "Enter Unlock Cost";
"lng_paid_cost_placeholder" = "Stars to Unlock";
"lng_paid_about" = "Users will have to transfer this amount of Stars to your channel in order to view this media. {link}";
"lng_paid_about_link" = "More about stars >";
"lng_paid_about_link_url" = "https://telegram.org/blog/telegram-stars";
"lng_paid_price" = "Unlock for {price}";
"lng_translate_show_original" = "Show Original";
"lng_translate_bar_to" = "Translate to {name}";
"lng_translate_bar_to_other" = "Translate to {name}";
@@ -3581,6 +3630,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_formatting_link_text" = "Text";
"lng_formatting_link_url" = "URL";
"lng_formatting_link_create" = "Create";
"lng_formatting_code_title" = "Code Language";
"lng_formatting_code_language" = "Language for syntax highlighting.";
"lng_formatting_code_auto" = "Auto-Detect";
"lng_text_copied" = "Text copied to clipboard.";
"lng_code_copied" = "Block copied to clipboard.";
@@ -4131,6 +4183,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_restricted_send_polls_all" = "Posting polls isn't allowed in this group.";
"lng_restricted_send_public_polls" = "Sorry, public polls can't be forwarded to channels.";
"lng_restricted_send_paid_media" = "Sorry, paid media can't be sent to this channel.";
"lng_restricted_send_voice_messages" = "{user} restricted sending of voice messages to them.";
"lng_restricted_send_video_messages" = "{user} restricted sending of video messages to them.";
@@ -5158,6 +5211,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_channel_earn_history_return" = "Refund";
"lng_channel_earn_history_return_about" = "Refunded back";
"lng_channel_earn_history_pending" = "Pending";
"lng_channel_earn_history_failed" = "Failed";
"lng_channel_earn_history_show_more#one" = "Show {count} More Transaction";
"lng_channel_earn_history_show_more#other" = "Show {count} More Transactions";
"lng_channel_earn_off" = "Switch Off Ads";
@@ -5180,6 +5234,30 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_channel_earn_chart_revenue" = "Ad revenue";
"lng_channel_earn_chart_overriden_detail_currency" = "Revenue in TON";
"lng_channel_earn_chart_overriden_detail_usd" = "Revenue in USD";
"lng_channel_earn_currency_history" = "TON Transactions";
"lng_channel_earn_credits_history" = "Stars Transactions";
"lng_channel_earn_out_check_password_about" = "You can withdraw only if you have:";
"lng_bot_earn_title" = "Stars Balance";
"lng_bot_earn_chart_revenue" = "Revenue";
"lng_bot_earn_overview_title" = "Proceeds overview";
"lng_bot_earn_available" = "Available balance";
"lng_bot_earn_total" = "Total lifetime proceeds";
"lng_bot_earn_balance_title" = "Available balance";
"lng_bot_earn_balance_about" = "Stars from your total balance become available for spending on ads and rewards 21 days after they are earned.";
"lng_bot_earn_balance_about_url" = "https://telegram.org/tos/stars";
"lng_bot_earn_balance_button#one" = "Withdraw {emoji} {count}";
"lng_bot_earn_balance_button#other" = "Withdraw {emoji} {count}";
"lng_bot_earn_balance_button_all" = "Withdraw all stars";
"lng_bot_earn_balance_button_locked" = "Withdraw";
"lng_bot_earn_balance_button_buy_ads" = "Buy Ads";
"lng_bot_earn_learn_credits_out_about" = "You can withdraw Stars using Fragment, or use Stars to advertise your bot. {link}";
"lng_bot_earn_out_ph" = "Enter amount to withdraw";
"lng_bot_earn_balance_password_title" = "Two-step verification";
"lng_bot_earn_balance_password_description" = "Please enter your password to collect.";
"lng_bot_earn_credits_out_minimal" = "You cannot withdraw less then {link}.";
"lng_bot_earn_credits_out_minimal_link#one" = "{count} star";
"lng_bot_earn_credits_out_minimal_link#other" = "{count} stars";
"lng_contact_add" = "Add";
"lng_contact_send_message" = "message";

View File

@@ -10,7 +10,7 @@
<Identity Name="TelegramMessengerLLP.TelegramDesktop"
ProcessorArchitecture="ARCHITECTURE"
Publisher="CN=536BC709-8EE1-4478-AF22-F0F0F26FF64A"
Version="5.1.2.0" />
Version="5.2.1.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 5,1,2,0
PRODUCTVERSION 5,1,2,0
FILEVERSION 5,2,1,0
PRODUCTVERSION 5,2,1,0
FILEFLAGSMASK 0x3fL
#ifdef _DEBUG
FILEFLAGS 0x1L
@@ -62,10 +62,10 @@ BEGIN
BEGIN
VALUE "CompanyName", "Telegram FZ-LLC"
VALUE "FileDescription", "Telegram Desktop"
VALUE "FileVersion", "5.1.2.0"
VALUE "FileVersion", "5.2.1.0"
VALUE "LegalCopyright", "Copyright (C) 2014-2024"
VALUE "ProductName", "Telegram Desktop"
VALUE "ProductVersion", "5.1.2.0"
VALUE "ProductVersion", "5.2.1.0"
END
END
BLOCK "VarFileInfo"

View File

@@ -35,8 +35,8 @@ LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
//
VS_VERSION_INFO VERSIONINFO
FILEVERSION 5,1,2,0
PRODUCTVERSION 5,1,2,0
FILEVERSION 5,2,1,0
PRODUCTVERSION 5,2,1,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", "5.1.2.0"
VALUE "FileVersion", "5.2.1.0"
VALUE "LegalCopyright", "Copyright (C) 2014-2024"
VALUE "ProductName", "Telegram Desktop"
VALUE "ProductVersion", "5.1.2.0"
VALUE "ProductVersion", "5.2.1.0"
END
END
BLOCK "VarFileInfo"

View File

@@ -20,6 +20,7 @@ namespace Api {
inline constexpr auto kScheduledUntilOnlineTimestamp = TimeId(0x7FFFFFFE);
struct SendOptions {
uint64 price = 0;
PeerData *sendAs = nullptr;
TimeId scheduled = 0;
BusinessShortcutId shortcutId = 0;

View File

@@ -7,9 +7,12 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#include "api/api_credits.h"
#include "apiwrap.h"
#include "api/api_statistics_data_deserialize.h"
#include "api/api_updates.h"
#include "apiwrap.h"
#include "base/unixtime.h"
#include "data/data_channel.h"
#include "data/data_document.h"
#include "data/data_peer.h"
#include "data/data_photo.h"
#include "data/data_session.h"
@@ -20,25 +23,62 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
namespace Api {
namespace {
constexpr auto kTransactionsLimit = 100;
[[nodiscard]] Data::CreditsHistoryEntry HistoryFromTL(
const MTPStarsTransaction &tl,
not_null<PeerData*> peer) {
using HistoryPeerTL = MTPDstarsTransactionPeer;
using namespace Data;
const auto owner = &peer->owner();
const auto photo = tl.data().vphoto()
? peer->owner().photoFromWeb(*tl.data().vphoto(), ImageLocation())
? owner->photoFromWeb(*tl.data().vphoto(), ImageLocation())
: nullptr;
auto extended = std::vector<CreditsHistoryMedia>();
if (const auto list = tl.data().vextended_media()) {
extended.reserve(list->v.size());
for (const auto &media : list->v) {
media.match([&](const MTPDmessageMediaPhoto &photo) {
if (const auto inner = photo.vphoto()) {
const auto photo = owner->processPhoto(*inner);
if (!photo->isNull()) {
extended.push_back(CreditsHistoryMedia{
.type = CreditsHistoryMediaType::Photo,
.id = photo->id,
});
}
}
}, [&](const MTPDmessageMediaDocument &document) {
if (const auto inner = document.vdocument()) {
const auto document = owner->processDocument(*inner);
if (document->isAnimation()
|| document->isVideoFile()
|| document->isGifv()) {
extended.push_back(CreditsHistoryMedia{
.type = CreditsHistoryMediaType::Video,
.id = document->id,
});
}
}
}, [&](const auto &) {});
}
}
const auto barePeerId = tl.data().vpeer().match([](
const HistoryPeerTL &p) {
return peerFromMTP(p.vpeer());
}, [](const auto &) {
return PeerId(0);
}).value;
return Data::CreditsHistoryEntry{
.id = qs(tl.data().vid()),
.title = qs(tl.data().vtitle().value_or_empty()),
.description = qs(tl.data().vdescription().value_or_empty()),
.date = base::unixtime::parse(tl.data().vdate().v),
.photoId = photo ? photo->id : 0,
.extended = std::move(extended),
.credits = tl.data().vstars().v,
.bareId = tl.data().vpeer().match([](const HistoryPeerTL &p) {
return peerFromMTP(p.vpeer());
}, [](const auto &) {
return PeerId(0);
}).value,
.bareMsgId = uint64(tl.data().vmsg_id().value_or_empty()),
.barePeerId = barePeerId,
.peerType = tl.data().vpeer().match([](const HistoryPeerTL &) {
return Data::CreditsHistoryEntry::PeerType::Peer;
}, [](const MTPDstarsTransactionPeerPlayMarket &) {
@@ -51,8 +91,17 @@ namespace {
return Data::CreditsHistoryEntry::PeerType::Unsupported;
}, [](const MTPDstarsTransactionPeerPremiumBot &) {
return Data::CreditsHistoryEntry::PeerType::PremiumBot;
}, [](const MTPDstarsTransactionPeerAds &) {
return Data::CreditsHistoryEntry::PeerType::Ads;
}),
.refunded = tl.data().is_refund(),
.pending = tl.data().is_pending(),
.failed = tl.data().is_failed(),
.successDate = tl.data().vtransaction_date()
? base::unixtime::parse(tl.data().vtransaction_date()->v)
: QDateTime(),
.successLink = qs(tl.data().vtransaction_url().value_or_empty()),
.in = (int64(tl.data().vstars().v) >= 0),
};
}
@@ -152,7 +201,8 @@ void CreditsHistory::request(
_requestId = _api.request(MTPpayments_GetStarsTransactions(
MTP_flags(_flags),
_peer->isSelf() ? MTP_inputPeerSelf() : _peer->input,
MTP_string(token)
MTP_string(token),
MTP_int(kTransactionsLimit)
)).done([=](const MTPpayments_StarsStatus &result) {
_requestId = 0;
done(StatusFromTL(result, _peer));
@@ -199,24 +249,58 @@ rpl::producer<not_null<PeerData*>> PremiumPeerBot(
};
}
void CreditsRefund(
not_null<PeerData*> peer,
const QString &entryId,
Fn<void()> done,
Fn<void(QString)> fail) {
const auto user = peer->asUser();
if (!user) {
return;
}
peer->session().api().request(MTPpayments_RefundStarsCharge(
user->inputUser,
MTP_string(entryId)
)).done([=](const MTPUpdates &result) {
peer->session().api().updates().applyUpdates(result);
done();
}).fail([=](const MTP::Error &error) {
fail(error.type());
}).send();
CreditsEarnStatistics::CreditsEarnStatistics(not_null<PeerData*> peer)
: StatisticsRequestSender(peer)
, _isUser(peer->isUser()) {
}
rpl::producer<rpl::no_value, QString> CreditsEarnStatistics::request() {
return [=](auto consumer) {
auto lifetime = rpl::lifetime();
const auto finish = [=](const QString &url) {
makeRequest(MTPpayments_GetStarsRevenueStats(
MTP_flags(0),
(_isUser ? user()->input : channel()->input)
)).done([=](const MTPpayments_StarsRevenueStats &result) {
const auto &data = result.data();
const auto &status = data.vstatus().data();
_data = Data::CreditsEarnStatistics{
.revenueGraph = StatisticalGraphFromTL(
data.vrevenue_graph()),
.currentBalance = status.vcurrent_balance().v,
.availableBalance = status.vavailable_balance().v,
.overallRevenue = status.voverall_revenue().v,
.usdRate = data.vusd_rate().v,
.isWithdrawalEnabled = status.is_withdrawal_enabled(),
.nextWithdrawalAt = status.vnext_withdrawal_at()
? base::unixtime::parse(
status.vnext_withdrawal_at()->v)
: QDateTime(),
.buyAdsUrl = url,
};
consumer.put_done();
}).fail([=](const MTP::Error &error) {
consumer.put_error_copy(error.type());
}).send();
};
makeRequest(
MTPpayments_GetStarsRevenueAdsAccountUrl(
(_isUser ? user()->input : channel()->input))
).done([=](const MTPpayments_StarsRevenueAdsAccountUrl &result) {
finish(qs(result.data().vurl()));
}).fail([=](const MTP::Error &error) {
finish({});
}).send();
return lifetime;
};
}
Data::CreditsEarnStatistics CreditsEarnStatistics::data() const {
return _data;
}
} // namespace Api

View File

@@ -7,13 +7,17 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#pragma once
#include "api/api_statistics_sender.h"
#include "data/data_credits.h"
#include "data/data_credits_earn.h"
#include "mtproto/sender.h"
namespace Main {
class Session;
} // namespace Main
class UserData;
namespace Api {
class CreditsTopupOptions final {
@@ -68,11 +72,20 @@ private:
};
void CreditsRefund(
not_null<PeerData*> peer,
const QString &entryId,
Fn<void()> done,
Fn<void(QString)> fail);
class CreditsEarnStatistics final : public StatisticsRequestSender {
public:
explicit CreditsEarnStatistics(not_null<PeerData*>);
[[nodiscard]] rpl::producer<rpl::no_value, QString> request();
[[nodiscard]] Data::CreditsEarnStatistics data() const;
private:
Data::CreditsEarnStatistics _data;
bool _isUser = false;
mtpRequestId _requestId = 0;
};
[[nodiscard]] rpl::producer<not_null<PeerData*>> PremiumPeerBot(
not_null<Main::Session*> session);

View File

@@ -9,6 +9,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "api/api_cloud_password.h"
#include "apiwrap.h"
#include "ui/layers/generic_box.h"
#include "boxes/passcode_box.h"
#include "data/data_channel.h"
#include "data/data_session.h"
@@ -34,22 +35,33 @@ void RestrictSponsored(
}
void HandleWithdrawalButton(
not_null<ChannelData*> channel,
RewardReceiver receiver,
not_null<Ui::RippleButton*> button,
std::shared_ptr<Ui::Show> show) {
Expects(receiver.currencyReceiver
|| (receiver.creditsReceiver && receiver.creditsAmount));
struct State {
rpl::lifetime lifetime;
bool loading = false;
};
const auto channel = receiver.currencyReceiver;
const auto peer = receiver.creditsReceiver;
const auto state = button->lifetime().make_state<State>();
const auto session = &channel->session();
const auto session = (channel ? &channel->session() : &peer->session());
using ChannelOutUrl = MTPstats_BroadcastRevenueWithdrawalUrl;
using CreditsOutUrl = MTPpayments_StarsRevenueWithdrawalUrl;
session->api().cloudPassword().reload();
button->setClickedCallback([=] {
const auto processOut = [=] {
if (state->loading) {
return;
}
if (peer && !receiver.creditsAmount()) {
return;
}
state->loading = true;
state->lifetime = session->api().cloudPassword().state(
) | rpl::take(
@@ -58,10 +70,12 @@ void HandleWithdrawalButton(
state->loading = false;
auto fields = PasscodeBox::CloudFields::From(pass);
fields.customTitle
= tr::lng_channel_earn_balance_password_title();
fields.customDescription
= tr::lng_channel_earn_balance_password_description(tr::now);
fields.customTitle = channel
? tr::lng_channel_earn_balance_password_title()
: tr::lng_bot_earn_balance_password_title();
fields.customDescription = channel
? tr::lng_channel_earn_balance_password_description(tr::now)
: tr::lng_bot_earn_balance_password_description(tr::now);
fields.customSubmitButton = tr::lng_passcode_submit();
fields.customCheckCallback = crl::guard(button, [=](
const Core::CloudPasswordResult &result,
@@ -74,22 +88,63 @@ void HandleWithdrawalButton(
}
}
};
const auto fail = [=](const QString &error) {
show->showToast(error);
const auto fail = [=](const MTP::Error &error) {
show->showToast(error.type());
};
session->api().request(
MTPstats_GetBroadcastRevenueWithdrawalUrl(
channel->inputChannel,
result.result
)).done([=](const MTPstats_BroadcastRevenueWithdrawalUrl &r) {
done(qs(r.data().vurl()));
}).fail([=](const MTP::Error &error) {
fail(error.type());
}).send();
if (channel) {
session->api().request(
MTPstats_GetBroadcastRevenueWithdrawalUrl(
channel->inputChannel,
result.result
)).done([=](const ChannelOutUrl &r) {
done(qs(r.data().vurl()));
}).fail(fail).send();
} else if (peer) {
session->api().request(
MTPpayments_GetStarsRevenueWithdrawalUrl(
peer->input,
MTP_long(receiver.creditsAmount()),
result.result
)).done([=](const CreditsOutUrl &r) {
done(qs(r.data().vurl()));
}).fail(fail).send();
}
});
show->show(Box<PasscodeBox>(session, fields));
});
};
button->setClickedCallback([=] {
if (state->loading) {
return;
}
const auto fail = [=](const MTP::Error &error) {
auto box = PrePasswordErrorBox(
error.type(),
session,
TextWithEntities{
tr::lng_channel_earn_out_check_password_about(tr::now),
});
if (box) {
show->show(std::move(box));
state->loading = false;
} else {
processOut();
}
};
if (channel) {
session->api().request(
MTPstats_GetBroadcastRevenueWithdrawalUrl(
channel->inputChannel,
MTP_inputCheckPasswordEmpty()
)).fail(fail).send();
} else if (peer) {
session->api().request(
MTPpayments_GetStarsRevenueWithdrawalUrl(
peer->input,
MTP_long(std::numeric_limits<int64_t>::max()),
MTP_inputCheckPasswordEmpty()
)).fail(fail).send();
}
});
}

View File

@@ -21,8 +21,14 @@ void RestrictSponsored(
bool restricted,
Fn<void(QString)> failed);
struct RewardReceiver final {
ChannelData *currencyReceiver = nullptr;
PeerData *creditsReceiver = nullptr;
Fn<uint64()> creditsAmount;
};
void HandleWithdrawalButton(
not_null<ChannelData*> channel,
RewardReceiver receiver,
not_null<Ui::RippleButton*> button,
std::shared_ptr<Ui::Show> show);

View File

@@ -0,0 +1,27 @@
/*
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 Api {
template <typename Type>
void PerformForUpdate(
const MTPUpdates &updates,
Fn<void(const Type &)> callback) {
updates.match([&](const MTPDupdates &updates) {
for (const auto &update : updates.vupdates().v) {
update.match([&](const Type &d) {
callback(d);
}, [](const auto &) {
});
}
}, [](const auto &) {
});
}
} // namespace Api

View File

@@ -7,6 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#include "api/api_statistics.h"
#include "api/api_statistics_data_deserialize.h"
#include "apiwrap.h"
#include "base/unixtime.h"
#include "data/data_channel.h"
@@ -15,33 +16,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_story.h"
#include "history/history.h"
#include "main/main_session.h"
#include "statistics/statistics_data_deserialize.h"
namespace Api {
namespace {
constexpr auto kCheckRequestsTimer = 10 * crl::time(1000);
[[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;
@@ -223,61 +201,6 @@ Statistics::Statistics(not_null<ChannelData*> channel)
: StatisticsRequestSender(channel) {
}
StatisticsRequestSender::StatisticsRequestSender(not_null<ChannelData *> channel)
: _channel(channel)
, _api(&_channel->session().api().instance())
, _timer([=] { checkRequests(); }) {
}
StatisticsRequestSender::~StatisticsRequestSender() {
for (const auto &[dcId, ids] : _requests) {
for (const auto id : ids) {
_channel->session().api().unregisterStatsRequest(dcId, id);
}
}
}
void StatisticsRequestSender::checkRequests() {
for (auto i = begin(_requests); i != end(_requests);) {
for (auto j = begin(i->second); j != end(i->second);) {
if (_api.pending(*j)) {
++j;
} else {
_channel->session().api().unregisterStatsRequest(
i->first,
*j);
j = i->second.erase(j);
}
}
if (i->second.empty()) {
i = _requests.erase(i);
} else {
++i;
}
}
if (_requests.empty()) {
_timer.cancel();
}
}
template <typename Request, typename, typename>
auto StatisticsRequestSender::makeRequest(Request &&request) {
const auto id = _api.allocateRequestId();
const auto dcId = _channel->owner().statsDcId(_channel);
if (dcId) {
_channel->session().api().registerStatsRequest(dcId, id);
_requests[dcId].emplace(id);
if (!_timer.isActive()) {
_timer.callEach(kCheckRequestsTimer);
}
}
return std::move(_api.request(
std::forward<Request>(request)
).toDC(
dcId ? MTP::ShiftDcId(dcId, MTP::kStatsDcShift) : 0
).overrideId(id));
}
rpl::producer<rpl::no_value, QString> Statistics::request() {
return [=](auto consumer) {
auto lifetime = rpl::lifetime();
@@ -747,11 +670,11 @@ Data::BoostStatus Boosts::boostStatus() const {
return _boostStatus;
}
EarnStatistics::EarnStatistics(not_null<ChannelData*> channel)
ChannelEarnStatistics::ChannelEarnStatistics(not_null<ChannelData*> channel)
: StatisticsRequestSender(channel) {
}
rpl::producer<rpl::no_value, QString> EarnStatistics::request() {
rpl::producer<rpl::no_value, QString> ChannelEarnStatistics::request() {
return [=](auto consumer) {
auto lifetime = rpl::lifetime();
@@ -795,7 +718,7 @@ rpl::producer<rpl::no_value, QString> EarnStatistics::request() {
};
}
void EarnStatistics::requestHistory(
void ChannelEarnStatistics::requestHistory(
const Data::EarnHistorySlice::OffsetToken &token,
Fn<void(Data::EarnHistorySlice)> done) {
if (_requestId) {
@@ -865,7 +788,7 @@ void EarnStatistics::requestHistory(
}).send();
}
Data::EarnStatistics EarnStatistics::data() const {
Data::EarnStatistics ChannelEarnStatistics::data() const {
return _data;
}

View File

@@ -7,45 +7,16 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#pragma once
#include "base/timer.h"
#include "api/api_statistics_sender.h"
#include "data/data_boosts.h"
#include "data/data_channel_earn.h"
#include "data/data_statistics.h"
#include "mtproto/sender.h"
class ChannelData;
class PeerData;
namespace Api {
class StatisticsRequestSender {
protected:
explicit StatisticsRequestSender(not_null<ChannelData*> channel);
~StatisticsRequestSender();
template <
typename Request,
typename = std::enable_if_t<!std::is_reference_v<Request>>,
typename = typename Request::Unboxed>
[[nodiscard]] auto makeRequest(Request &&request);
[[nodiscard]] MTP::Sender &api() {
return _api;
}
[[nodiscard]] not_null<ChannelData*> channel() {
return _channel;
}
private:
void checkRequests();
const not_null<ChannelData*> _channel;
MTP::Sender _api;
base::Timer _timer;
base::flat_map<MTP::DcId, base::flat_set<mtpRequestId>> _requests;
};
class Statistics final : public StatisticsRequestSender {
public:
explicit Statistics(not_null<ChannelData*> channel);
@@ -108,9 +79,9 @@ private:
};
class EarnStatistics final : public StatisticsRequestSender {
class ChannelEarnStatistics final : public StatisticsRequestSender {
public:
explicit EarnStatistics(not_null<ChannelData*> channel);
explicit ChannelEarnStatistics(not_null<ChannelData*> channel);
[[nodiscard]] rpl::producer<rpl::no_value, QString> request();
void requestHistory(

View File

@@ -0,0 +1,35 @@
/*
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_data_deserialize.h"
#include "data/data_statistics_chart.h"
#include "statistics/statistics_data_deserialize.h"
namespace Api {
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()) };
});
}
} // namespace Api

View File

@@ -0,0 +1,19 @@
/*
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 StatisticalGraph;
} // namespace Data
namespace Api {
[[nodiscard]] Data::StatisticalGraph StatisticalGraphFromTL(
const MTPStatsGraph &tl);
} // namespace Api

View File

@@ -0,0 +1,86 @@
/*
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_sender.h"
#include "apiwrap.h"
#include "data/data_peer.h"
#include "data/data_session.h"
#include "main/main_session.h"
namespace Api {
StatisticsRequestSender::StatisticsRequestSender(
not_null<PeerData*> peer)
: _peer(peer)
, _channel(peer->asChannel())
, _user(peer->asUser())
, _api(&_peer->session().api().instance())
, _timer([=] { checkRequests(); }) {
}
MTP::Sender &StatisticsRequestSender::api() {
return _api;
}
not_null<ChannelData*> StatisticsRequestSender::channel() {
Expects(_channel);
return _channel;
}
not_null<UserData*> StatisticsRequestSender::user() {
Expects(_user);
return _user;
}
void StatisticsRequestSender::checkRequests() {
for (auto i = begin(_requests); i != end(_requests);) {
for (auto j = begin(i->second); j != end(i->second);) {
if (_api.pending(*j)) {
++j;
} else {
_peer->session().api().unregisterStatsRequest(
i->first,
*j);
j = i->second.erase(j);
}
}
if (i->second.empty()) {
i = _requests.erase(i);
} else {
++i;
}
}
if (_requests.empty()) {
_timer.cancel();
}
}
auto StatisticsRequestSender::ensureRequestIsRegistered()
-> StatisticsRequestSender::Registered {
const auto id = _api.allocateRequestId();
const auto dcId = _peer->owner().statsDcId(_peer);
if (dcId) {
_peer->session().api().registerStatsRequest(dcId, id);
_requests[dcId].emplace(id);
if (!_timer.isActive()) {
constexpr auto kCheckRequestsTimer = 10 * crl::time(1000);
_timer.callEach(kCheckRequestsTimer);
}
}
return StatisticsRequestSender::Registered{ id, dcId };
}
StatisticsRequestSender::~StatisticsRequestSender() {
for (const auto &[dcId, ids] : _requests) {
for (const auto id : ids) {
_peer->session().api().unregisterStatsRequest(dcId, id);
}
}
}
} // namespace Api

View File

@@ -0,0 +1,58 @@
/*
This file is part of Telegram Desktop,
the official desktop application for the Telegram messaging service.
For license and copyright information please follow this link:
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#pragma once
#include "base/timer.h"
#include "mtproto/sender.h"
class ChannelData;
class PeerData;
class UserData;
namespace Api {
class StatisticsRequestSender {
protected:
explicit StatisticsRequestSender(not_null<PeerData*> peer);
~StatisticsRequestSender();
template <
typename Request,
typename = std::enable_if_t<!std::is_reference_v<Request>>,
typename = typename Request::Unboxed>
[[nodiscard]] auto makeRequest(Request &&request) {
const auto [id, dcId] = ensureRequestIsRegistered();
return std::move(_api.request(
std::forward<Request>(request)
).toDC(
dcId ? MTP::ShiftDcId(dcId, MTP::kStatsDcShift) : 0
).overrideId(id));
}
[[nodiscard]] MTP::Sender &api();
[[nodiscard]] not_null<ChannelData*> channel();
[[nodiscard]] not_null<UserData*> user();
private:
struct Registered final {
mtpRequestId id;
MTP::DcId dcId;
};
[[nodiscard]] Registered ensureRequestIsRegistered();
void checkRequests();
const not_null<PeerData*> _peer;
ChannelData * const _channel;
UserData * const _user;
MTP::Sender _api;
base::Timer _timer;
base::flat_map<MTP::DcId, base::flat_set<mtpRequestId>> _requests;
};
} // namespace Api

View File

@@ -1696,7 +1696,7 @@ void Updates::feedUpdate(const MTPUpdate &update) {
const auto peerId = peerFromMTP(d.vpeer());
const auto msgId = d.vmsg_id().v;
if (const auto item = session().data().message(peerId, msgId)) {
item->applyEdition(d.vextended_media());
item->applyEdition(d.vextended_media().v);
}
} break;
@@ -2121,6 +2121,8 @@ void Updates::feedUpdate(const MTPUpdate &update) {
};
if (IsForceLogoutNotification(d)) {
Core::App().forceLogOut(&session().account(), text);
} else if (IsWithdrawalNotification(d)) {
return;
} else if (d.is_popup()) {
const auto &windows = session().windows();
if (!windows.empty()) {
@@ -2622,4 +2624,8 @@ void Updates::feedUpdate(const MTPUpdate &update) {
}
}
bool IsWithdrawalNotification(const MTPDupdateServiceNotification &data) {
return qs(data.vtype()).startsWith(u"API_WITHDRAWAL_FEATURE_DISABLED_"_q);
}
} // namespace Api

View File

@@ -211,4 +211,7 @@ private:
};
[[nodiscard]] bool IsWithdrawalNotification(
const MTPDupdateServiceNotification &);
} // namespace Api

View File

@@ -55,7 +55,9 @@ void ViewsManager::removeIncremented(not_null<PeerData*> peer) {
_incremented.remove(peer);
}
void ViewsManager::pollExtendedMedia(not_null<HistoryItem*> item) {
void ViewsManager::pollExtendedMedia(
not_null<HistoryItem*> item,
bool force) {
if (!item->isRegular()) {
return;
}
@@ -63,14 +65,20 @@ void ViewsManager::pollExtendedMedia(not_null<HistoryItem*> item) {
const auto peer = item->history()->peer;
auto &request = _pollRequests[peer];
if (request.ids.contains(id) || request.sent.contains(id)) {
return;
if (!force || request.forced) {
return;
}
}
request.ids.emplace(id);
if (!request.id && !request.when) {
request.when = crl::now() + kPollExtendedMediaPeriod;
if (force) {
request.forced = true;
}
if (!_pollTimer.isActive()) {
_pollTimer.callOnce(kPollExtendedMediaPeriod);
const auto delay = force ? 1 : kPollExtendedMediaPeriod;
if (!request.id && (!request.when || force)) {
request.when = crl::now() + delay;
}
if (!_pollTimer.isActive() || force) {
_pollTimer.callOnce(delay);
}
}
@@ -160,9 +168,12 @@ void ViewsManager::sendPollRequests(
if (i->second.ids.empty()) {
i = _pollRequests.erase(i);
} else {
i->second.when = now + kPollExtendedMediaPeriod;
if (!_pollTimer.isActive()) {
_pollTimer.callOnce(kPollExtendedMediaPeriod);
const auto delay = i->second.forced
? 1
: kPollExtendedMediaPeriod;
i->second.when = now + delay;
if (!_pollTimer.isActive() || i->second.forced) {
_pollTimer.callOnce(delay);
}
++i;
}

View File

@@ -26,7 +26,7 @@ public:
void scheduleIncrement(not_null<HistoryItem*> item);
void removeIncremented(not_null<PeerData*> peer);
void pollExtendedMedia(not_null<HistoryItem*> item);
void pollExtendedMedia(not_null<HistoryItem*> item, bool force = false);
private:
struct PollExtendedMediaRequest {
@@ -34,6 +34,7 @@ private:
mtpRequestId id = 0;
base::flat_set<MsgId> ids;
base::flat_set<MsgId> sent;
bool forced = false;
};
void viewsIncrement();

View File

@@ -2165,7 +2165,8 @@ void ApiWrap::saveDraftsToCloud() {
entities,
Data::WebPageForMTP(
cloudDraft->webpage,
textWithTags.text.isEmpty())
textWithTags.text.isEmpty()),
MTP_long(0) // effect
)).done([=](const MTPBool &result, const MTP::Response &response) {
const auto requestId = response.requestId;
history->finishSavingCloudDraft(
@@ -4187,7 +4188,11 @@ void ApiWrap::sendMediaWithRandomId(
MTP_flags(flags),
peer->input,
Data::Histories::ReplyToPlaceholder(),
media,
(options.price
? MTPInputMedia(MTP_inputMediaPaidMedia(
MTP_long(options.price),
MTP_vector<MTPInputMedia>(1, media)))
: media),
MTP_string(caption.text),
MTP_long(randomId),
MTPReplyMarkup(),
@@ -4207,6 +4212,82 @@ void ApiWrap::sendMediaWithRandomId(
});
}
void ApiWrap::sendMultiPaidMedia(
not_null<HistoryItem*> item,
not_null<SendingAlbum*> album,
Fn<void(bool)> done) {
Expects(album->options.price > 0);
const auto groupId = album->groupId;
const auto &options = album->options;
const auto randomId = album->items.front().randomId;
auto medias = album->items | ranges::view::transform([](
const SendingAlbum::Item &part) {
Assert(part.media.has_value());
return MTPInputMedia(part.media->data().vmedia());
}) | ranges::to<QVector<MTPInputMedia>>();
const auto history = item->history();
const auto replyTo = item->replyTo();
auto caption = item->originalText();
TextUtilities::Trim(caption);
auto sentEntities = Api::EntitiesToMTP(
_session,
caption.entities,
Api::ConvertOption::SkipLocal);
using Flag = MTPmessages_SendMedia::Flag;
const auto flags = Flag(0)
| (replyTo ? Flag::f_reply_to : Flag(0))
| (ShouldSendSilent(history->peer, options)
? Flag::f_silent
: Flag(0))
| (!sentEntities.v.isEmpty() ? Flag::f_entities : Flag(0))
| (options.scheduled ? Flag::f_schedule_date : Flag(0))
| (options.sendAs ? Flag::f_send_as : Flag(0))
| (options.shortcutId ? Flag::f_quick_reply_shortcut : Flag(0))
| (options.effectId ? Flag::f_effect : Flag(0))
| (options.invertCaption ? Flag::f_invert_media : Flag(0));
auto &histories = history->owner().histories();
const auto peer = history->peer;
const auto itemId = item->fullId();
histories.sendPreparedMessage(
history,
replyTo,
randomId,
Data::Histories::PrepareMessage<MTPmessages_SendMedia>(
MTP_flags(flags),
peer->input,
Data::Histories::ReplyToPlaceholder(),
MTP_inputMediaPaidMedia(
MTP_long(options.price),
MTP_vector<MTPInputMedia>(std::move(medias))),
MTP_string(caption.text),
MTP_long(randomId),
MTPReplyMarkup(),
sentEntities,
MTP_int(options.scheduled),
(options.sendAs ? options.sendAs->input : MTP_inputPeerEmpty()),
Data::ShortcutIdToMTP(_session, options.shortcutId),
MTP_long(options.effectId)
), [=](const MTPUpdates &result, const MTP::Response &response) {
if (const auto album = _sendingAlbums.take(groupId)) {
const auto copy = (*album)->items;
for (const auto &part : copy) {
if (const auto item = history->owner().message(part.msgId)) {
item->destroy();
}
}
}
if (done) done(true);
}, [=](const MTP::Error &error, const MTP::Response &response) {
if (done) done(false);
sendMessageFail(error, peer, randomId, itemId);
});
}
void ApiWrap::sendAlbumWithUploaded(
not_null<HistoryItem*> item,
const MessageGroupId &groupId,
@@ -4260,8 +4341,11 @@ void ApiWrap::sendAlbumIfReady(not_null<SendingAlbum*> album) {
if (!sample) {
_sendingAlbums.remove(groupId);
return;
} else if (album->options.price > 0) {
sendMultiPaidMedia(sample, album);
return;
} else if (medias.size() < 2) {
const auto &single = medias.front().c_inputSingleMedia();
const auto &single = medias.front().data();
sendMediaWithRandomId(
sample,
single.vmedia(),

View File

@@ -545,6 +545,10 @@ private:
Api::SendOptions options,
uint64 randomId,
Fn<void(bool)> done = nullptr);
void sendMultiPaidMedia(
not_null<HistoryItem*> item,
not_null<SendingAlbum*> album,
Fn<void(bool)> done = nullptr);
void getTopPromotionDelayed(TimeId now, TimeId next);
void topPromotionDone(const MTPhelp_PromoData &proxy);

View File

@@ -237,7 +237,7 @@ shareColumnSkip: 6px;
shareActivateDuration: 150;
shareScrollDuration: 300;
shareComment: InputField(defaultInputField) {
font: normalFont;
style: defaultTextStyle;
textMargins: margins(8px, 8px, 8px, 6px);
heightMin: 36px;
heightMax: 72px;
@@ -290,6 +290,26 @@ passcodeTextLine: 28px;
passcodeLittleSkip: 5px;
passcodeAboutSkip: 7px;
passcodeSkip: 23px;
passcodeSystemUnlock: IconButton(defaultIconButton) {
width: 32px;
height: 36px;
icon: icon{{ "menu/passcode_winhello", lightButtonFg }};
iconOver: icon{{ "menu/passcode_winhello", lightButtonFg }};
iconPosition: point(4px, 4px);
rippleAreaSize: 32px;
rippleAreaPosition: point(0px, 0px);
ripple: RippleAnimation(defaultRippleAnimation) {
color: lightButtonBgOver;
}
}
passcodeSystemTouchID: icon{{ "menu/passcode_finger", lightButtonFg }};
passcodeSystemAppleWatch: icon{{ "menu/passcode_watch", lightButtonFg }};
passcodeSystemSystemPwd: icon{{ "menu/permissions", lightButtonFg }};
passcodeSystemUnlockLater: FlatLabel(defaultFlatLabel) {
align: align(top);
textFg: windowSubTextFg;
}
passcodeSystemUnlockSkip: 12px;
newGroupAboutFg: windowSubTextFg;
newGroupPadding: margins(4px, 6px, 4px, 3px);
@@ -585,7 +605,7 @@ groupStickersRemovePosition: point(6px, 6px);
groupStickersFieldPadding: margins(8px, 6px, 8px, 6px);
groupStickersField: InputField(defaultMultiSelectSearchField) {
placeholderFont: boxTextFont;
font: boxTextFont;
style: boxTextStyle;
placeholderMargins: margins(0px, 0px, 0px, 0px);
textMargins: margins(0px, 7px, 0px, 0px);
textBg: boxBg;
@@ -672,7 +692,6 @@ themesMenuToggle: IconButton(defaultIconButton) {
themesMenuPosition: point(-2px, 25px);
createPollField: InputField(defaultInputField) {
font: boxTextFont;
textMargins: margins(0px, 4px, 0px, 4px);
textAlign: align(left);
heightMin: 36px;
@@ -877,7 +896,6 @@ scheduleDateField: InputField(defaultInputField) {
placeholderScale: 0.;
heightMin: 30px;
textAlign: align(top);
font: font(14px);
}
scheduleTimeField: InputField(scheduleDateField) {
border: 0px;
@@ -905,7 +923,6 @@ muteBoxTimeField: InputField(scheduleDateField) {
placeholderScale: 0.;
heightMin: 30px;
textAlign: align(left);
font: font(14px);
}
muteBoxTimeFieldPadding: margins(5px, 0px, 5px, 0px);

View File

@@ -81,7 +81,7 @@ void ChangeFilterById(
MTP_int(filter.id()),
filter.tl()
)).done([=, chat = history->peer->name(), name = filter.title()] {
const auto account = &history->session().account();
const auto account = not_null(&history->session().account());
if (const auto controller = Core::App().windowFor(account)) {
controller->showToast((add
? tr::lng_filters_toast_add

View File

@@ -1044,7 +1044,16 @@ not_null<Ui::InputField*> CreatePollBox::setupSolution(
solution->setInstantReplaces(Ui::InstantReplaces::Default());
solution->setInstantReplacesEnabled(
Core::App().settings().replaceEmojiValue());
solution->setMarkdownReplacesEnabled(true);
solution->setMarkdownReplacesEnabled(rpl::single(
Ui::MarkdownEnabledState{ Ui::MarkdownEnabled{ {
Ui::InputField::kTagBold,
Ui::InputField::kTagItalic,
Ui::InputField::kTagUnderline,
Ui::InputField::kTagStrikeOut,
Ui::InputField::kTagCode,
Ui::InputField::kTagSpoiler,
} } }
));
solution->setEditLinkCallback(
DefaultEditLinkCallback(_controller->uiShow(), solution));
solution->customTab(true);

View File

@@ -463,6 +463,7 @@ void EditCaptionBox::rebuildPreview() {
st::defaultComposeControls,
gifPaused,
file,
[] { return true; },
Ui::AttachControls::Type::EditOnly);
_isPhoto = (media && media->isPhoto());
const auto withCheckbox = _isPhoto && CanBeCompressed(_albumType);

View File

@@ -1011,14 +1011,16 @@ void GiftPremiumValidator::showChoosePeerBox(const QString &ref) {
}) | ranges::views::filter([](UserData *u) -> bool {
return u;
}) | ranges::to<std::vector<not_null<UserData*>>>();
if (!users.empty()) {
const auto giftBox = show->show(
Box(GiftsBox, _controller, users, api, ref));
giftBox->boxClosing(
) | rpl::start_with_next([=] {
_manyGiftsLifetime.destroy();
}, giftBox->lifetime());
if (users.empty()) {
show->showToast(
tr::lng_settings_gift_premium_choose(tr::now));
}
const auto giftBox = show->show(
Box(GiftsBox, _controller, users, api, ref));
giftBox->boxClosing(
) | rpl::start_with_next([=] {
_manyGiftsLifetime.destroy();
}, giftBox->lifetime());
(*ignoreClose) = true;
peersBox->closeBox();
};
@@ -1644,12 +1646,60 @@ void AddCreditsHistoryEntryTable(
container,
st::giveawayGiftCodeTable),
st::giveawayGiftCodeTableMargin);
if (entry.bareId) {
const auto peerId = PeerId(entry.barePeerId);
if (peerId) {
auto text = tr::lng_credits_box_history_entry_peer();
AddTableRow(table, std::move(text), controller, peerId);
}
if (const auto msgId = MsgId(peerId ? entry.bareMsgId : 0)) {
const auto session = &controller->session();
const auto peer = session->data().peer(peerId);
if (const auto channel = peer->asBroadcast()) {
const auto username = channel->username();
const auto base = username.isEmpty()
? u"c/%1"_q.arg(peerToChannel(channel->id).bare)
: username;
const auto query = base + '/' + QString::number(msgId.bare);
const auto link = session->createInternalLink(query);
auto label = object_ptr<Ui::FlatLabel>(
table,
rpl::single(Ui::Text::Link(link)),
st::giveawayGiftCodeValue);
label->setClickHandlerFilter([=](const auto &...) {
controller->showPeerHistory(channel, {}, msgId);
return false;
});
AddTableRow(
table,
tr::lng_credits_box_history_entry_media(),
std::move(label),
st::giveawayGiftCodeValueMargin);
}
}
using Type = Data::CreditsHistoryEntry::PeerType;
if (entry.peerType == Type::AppStore) {
AddTableRow(
table,
tr::lng_credits_box_history_entry_peer(),
controller,
PeerId(entry.bareId));
tr::lng_credits_box_history_entry_via(),
tr::lng_credits_box_history_entry_app_store(
Ui::Text::RichLangValue));
} else if (entry.peerType == Type::PlayMarket) {
AddTableRow(
table,
tr::lng_credits_box_history_entry_via(),
tr::lng_credits_box_history_entry_play_market(
Ui::Text::RichLangValue));
} else if (entry.peerType == Type::Fragment) {
AddTableRow(
table,
tr::lng_credits_box_history_entry_via(),
tr::lng_credits_box_history_entry_fragment(
Ui::Text::RichLangValue));
} else if (entry.peerType == Type::Ads) {
AddTableRow(
table,
tr::lng_credits_box_history_entry_via(),
tr::lng_credits_box_history_entry_ads(Ui::Text::RichLangValue));
}
if (!entry.id.isEmpty()) {
constexpr auto kOneLineCount = 18;
@@ -1680,4 +1730,17 @@ void AddCreditsHistoryEntryTable(
tr::lng_gift_link_label_date(),
rpl::single(Ui::Text::WithEntities(langDateTime(entry.date))));
}
if (!entry.successDate.isNull()) {
AddTableRow(
table,
tr::lng_credits_box_history_entry_success_date(),
rpl::single(Ui::Text::WithEntities(langDateTime(entry.date))));
}
if (!entry.successLink.isEmpty()) {
AddTableRow(
table,
tr::lng_credits_box_history_entry_success_url(),
rpl::single(
Ui::Text::Link(entry.successLink, entry.successLink)));
}
}

View File

@@ -133,8 +133,8 @@ void MaxInviteBox::paintEvent(QPaintEvent *e) {
auto option = QTextOption(style::al_left);
option.setWrapMode(QTextOption::WrapAnywhere);
p.setFont(_linkOver
? st::defaultInputField.font->underline()
: st::defaultInputField.font);
? st::defaultInputField.style.font->underline()
: st::defaultInputField.style.font);
p.setPen(st::defaultLinkButton.color);
const auto inviteLinkText = _channel->inviteLink().isEmpty()
? tr::lng_group_invite_create(tr::now)

View File

@@ -8,6 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "boxes/peers/edit_peer_info_box.h"
#include "apiwrap.h"
#include "api/api_credits.h"
#include "api/api_peer_photo.h"
#include "api/api_user_names.h"
#include "main/main_session.h"
@@ -42,6 +43,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_premium_limits.h"
#include "data/data_user.h"
#include "history/admin_log/history_admin_log_section.h"
#include "info/bot/earn/info_bot_earn_widget.h"
#include "info/channel_statistics/boosts/info_boosts_widget.h"
#include "info/profile/info_profile_values.h"
#include "info/info_memento.h"
@@ -52,6 +54,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/boxes/boost_box.h"
#include "ui/controls/emoji_button.h"
#include "ui/controls/userpic_button.h"
#include "ui/effects/premium_graphics.h"
#include "ui/rect.h"
#include "ui/rp_widget.h"
#include "ui/vertical_list.h"
#include "ui/toast/toast.h"
@@ -71,6 +75,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "styles/style_boxes.h"
#include "styles/style_info.h"
#include <QtSvg/QSvgRenderer>
namespace {
constexpr auto kBotManagerUsername = "BotFather"_cs;
@@ -343,6 +349,7 @@ private:
void fillPendingRequestsButton();
void fillBotUsernamesButton();
void fillBotBalanceButton();
void fillBotEditIntroButton();
void fillBotEditCommandsButton();
void fillBotEditSettingsButton();
@@ -1126,6 +1133,7 @@ void Controller::fillManageSection() {
::AddSkip(container, 0);
fillBotUsernamesButton();
fillBotBalanceButton();
fillBotEditIntroButton();
fillBotEditCommandsButton();
fillBotEditSettingsButton();
@@ -1536,6 +1544,84 @@ void Controller::fillBotUsernamesButton() {
{ &st::menuIconLinks });
}
void Controller::fillBotBalanceButton() {
Expects(_isBot);
struct State final {
rpl::variable<QString> balance;
};
auto &lifetime = _controls.buttonsLayout->lifetime();
const auto state = lifetime.make_state<State>();
const auto wrap = _controls.buttonsLayout->add(
object_ptr<Ui::SlideWrap<Ui::SettingsButton>>(
_controls.buttonsLayout,
EditPeerInfoBox::CreateButton(
_controls.buttonsLayout,
tr::lng_manage_peer_bot_balance(),
state->balance.value(),
[controller = _navigation->parentController(), peer = _peer] {
controller->showSection(Info::BotEarn::Make(peer));
},
st::manageGroupButton,
{})));
wrap->toggle(false, anim::type::instant);
const auto button = wrap->entity();
{
const auto api = button->lifetime().make_state<Api::CreditsStatus>(
_peer);
api->request({}, [=](Data::CreditsStatusSlice data) {
if (data.balance) {
wrap->toggle(true, anim::type::normal);
}
state->balance = QString::number(data.balance);
});
}
{
constexpr auto kSizeShift = 3;
constexpr auto kStrokeWidth = 5;
const auto icon = Ui::CreateChild<Ui::RpWidget>(button);
icon->resize(Size(st::menuIconLinks.width() - kSizeShift));
auto colorized = [&] {
auto f = QFile(Ui::Premium::Svg());
if (!f.open(QIODevice::ReadOnly)) {
return QString();
}
return QString::fromUtf8(
f.readAll()).replace(u"#fff"_q, u"#ffffff00"_q);
}();
colorized.replace(
u"stroke=\"none\""_q,
u"stroke=\"%1\""_q.arg(st::menuIconColor->c.name()));
colorized.replace(
u"stroke-width=\"1\""_q,
u"stroke-width=\"%1\""_q.arg(kStrokeWidth));
const auto svg = icon->lifetime().make_state<QSvgRenderer>(
colorized.toUtf8());
svg->setViewBox(svg->viewBox() + Margins(kStrokeWidth));
const auto starSize = Size(icon->height());
icon->paintRequest(
) | rpl::start_with_next([=] {
auto p = QPainter(icon);
svg->render(&p, Rect(starSize));
}, icon->lifetime());
button->sizeValue(
) | rpl::start_with_next([=](const QSize &size) {
icon->moveToLeft(
button->st().iconLeft + kSizeShift / 2.,
(size.height() - icon->height()) / 2);
}, icon->lifetime());
}
}
void Controller::fillBotEditIntroButton() {
Expects(_isBot);

View File

@@ -11,6 +11,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "base/event_filter.h"
#include "chat_helpers/tabbed_panel.h"
#include "chat_helpers/tabbed_selector.h"
#include "core/ui_integration.h"
#include "data/data_channel.h"
#include "data/data_chat.h"
#include "data/data_document.h"
@@ -351,8 +352,8 @@ object_ptr<Ui::RpWidget> AddReactionsSelector(
const auto customEmojiPaused = [controller = args.controller] {
return controller->isGifPausedAtLeastFor(PauseReason::Layer);
};
raw->setCustomEmojiFactory([=](QStringView data, Fn<void()> update)
-> std::unique_ptr<Ui::Text::CustomEmoji> {
auto factory = [=](QStringView data, Fn<void()> update)
-> std::unique_ptr<Ui::Text::CustomEmoji> {
const auto id = Data::ParseCustomEmojiData(data);
auto result = owner->customEmojiManager().create(
data,
@@ -364,7 +365,13 @@ object_ptr<Ui::RpWidget> AddReactionsSelector(
}
using namespace Ui::Text;
return std::make_unique<FirstFrameEmoji>(std::move(result));
}, std::move(customEmojiPaused));
};
raw->setCustomTextContext([=](Fn<void()> repaint) {
return std::any(Core::MarkedTextContext{
.session = session,
.customEmojiRepaint = std::move(repaint),
});
}, customEmojiPaused, customEmojiPaused, std::move(factory));
const auto callback = args.callback;
const auto isCustom = [=](DocumentId id) {

View File

@@ -131,6 +131,8 @@ void PreloadSticker(const std::shared_ptr<Data::DocumentMedia> &media) {
return tr::lng_premium_summary_subtitle_translation();
case PremiumFeature::Business:
return tr::lng_premium_summary_subtitle_business();
case PremiumFeature::Effects:
return tr::lng_premium_summary_subtitle_effects();
case PremiumFeature::BusinessLocation:
return tr::lng_business_subtitle_location();
@@ -192,6 +194,8 @@ void PreloadSticker(const std::shared_ptr<Data::DocumentMedia> &media) {
return tr::lng_premium_summary_about_translation();
case PremiumFeature::Business:
return tr::lng_premium_summary_about_business();
case PremiumFeature::Effects:
return tr::lng_premium_summary_about_effects();
case PremiumFeature::BusinessLocation:
return tr::lng_business_about_location();
@@ -529,6 +533,7 @@ struct VideoPreviewDocument {
case PremiumFeature::Wallpapers: return "wallpapers";
case PremiumFeature::LastSeen: return "last_seen";
case PremiumFeature::MessagePrivacy: return "message_privacy";
case PremiumFeature::Effects: return "effects";
case PremiumFeature::BusinessLocation: return "business_location";
case PremiumFeature::BusinessHours: return "business_hours";

View File

@@ -70,6 +70,7 @@ enum class PremiumFeature {
LastSeen,
MessagePrivacy,
Business,
Effects,
// Business features.
BusinessLocation,

View File

@@ -11,6 +11,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "apiwrap.h"
#include "core/ui_integration.h" // Core::MarkedTextContext.
#include "data/data_credits.h"
#include "data/data_photo.h"
#include "data/data_session.h"
#include "data/data_user.h"
#include "data/stickers/data_custom_emoji.h"
@@ -39,6 +40,147 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "styles/style_settings.h"
namespace Ui {
namespace {
struct PaidMediaData {
const Data::Invoice *invoice = nullptr;
HistoryItem *item = nullptr;
PeerData *peer = nullptr;
int photos = 0;
int videos = 0;
explicit operator bool() const {
return invoice && item && peer && (photos || videos);
}
};
[[nodiscard]] PaidMediaData LookupPaidMediaData(
not_null<Main::Session*> session,
not_null<Payments::CreditsFormData*> form) {
using namespace Payments;
const auto message = std::get_if<InvoiceMessage>(&form->id.value);
const auto item = message
? session->data().message(message->peer, message->itemId)
: nullptr;
const auto media = item ? item->media() : nullptr;
const auto invoice = media ? media->invoice() : nullptr;
if (!invoice || !invoice->isPaidMedia) {
return {};
}
auto photos = 0;
auto videos = 0;
for (const auto &media : invoice->extendedMedia) {
const auto photo = media->photo();
if (photo && !photo->extendedMediaVideoDuration().has_value()) {
++photos;
} else {
++videos;
}
}
const auto sender = item->originalSender();
const auto broadcast = (sender && sender->isBroadcast())
? sender
: message->peer.get();
return {
.invoice = invoice,
.item = item,
.peer = broadcast,
.photos = photos,
.videos = videos,
};
}
[[nodiscard]] rpl::producer<TextWithEntities> SendCreditsConfirmText(
not_null<Main::Session*> session,
not_null<Payments::CreditsFormData*> form) {
if (const auto data = LookupPaidMediaData(session, form)) {
auto photos = 0;
auto videos = 0;
for (const auto &media : data.invoice->extendedMedia) {
const auto photo = media->photo();
if (photo && !photo->extendedMediaVideoDuration().has_value()) {
++photos;
} else {
++videos;
}
}
auto photosBold = tr::lng_credits_box_out_photos(
lt_count,
rpl::single(photos) | tr::to_count(),
Ui::Text::Bold);
auto videosBold = tr::lng_credits_box_out_videos(
lt_count,
rpl::single(videos) | tr::to_count(),
Ui::Text::Bold);
auto media = (!videos)
? ((photos > 1)
? std::move(photosBold)
: tr::lng_credits_box_out_photo(Ui::Text::WithEntities))
: (!photos)
? ((videos > 1)
? std::move(videosBold)
: tr::lng_credits_box_out_video(Ui::Text::WithEntities))
: tr::lng_credits_box_out_both(
lt_photo,
std::move(photosBold),
lt_video,
std::move(videosBold),
Ui::Text::WithEntities);
return tr::lng_credits_box_out_media(
lt_count,
rpl::single(form->invoice.amount) | tr::to_count(),
lt_media,
std::move(media),
lt_chat,
rpl::single(Ui::Text::Bold(data.peer->name())),
Ui::Text::RichLangValue);
}
const auto bot = session->data().user(form->botId);
return tr::lng_credits_box_out_sure(
lt_count,
rpl::single(form->invoice.amount) | tr::to_count(),
lt_text,
rpl::single(TextWithEntities{ form->title }),
lt_bot,
rpl::single(TextWithEntities{ bot->name() }),
Ui::Text::RichLangValue);
}
[[nodiscard]] object_ptr<Ui::RpWidget> SendCreditsThumbnail(
not_null<Ui::RpWidget*> parent,
not_null<Main::Session*> session,
not_null<Payments::CreditsFormData*> form,
int photoSize) {
if (const auto data = LookupPaidMediaData(session, form)) {
const auto first = data.invoice->extendedMedia[0]->photo();
const auto second = (data.photos > 1)
? data.invoice->extendedMedia[1]->photo()
: nullptr;
const auto totalCount = int(data.invoice->extendedMedia.size());
if (first && first->extendedMediaPreview()) {
return Settings::PaidMediaThumbnail(
parent,
first,
second,
totalCount,
photoSize);
}
}
if (form->photo) {
return Settings::HistoryEntryPhoto(parent, form->photo, photoSize);
}
const auto bot = session->data().user(form->botId);
return object_ptr<Ui::UserpicButton>(
parent,
bot,
st::defaultUserpicButton);
}
} // namespace
void SendCreditsBox(
not_null<Ui::GenericBox*> box,
@@ -89,22 +231,10 @@ void SendCreditsBox(
}, ministarsContainer->lifetime());
}
const auto bot = session->data().user(form->botId);
if (form->photo) {
box->addRow(object_ptr<Ui::CenterWrap<>>(
content,
Settings::HistoryEntryPhoto(content, form->photo, photoSize)));
} else {
const auto widget = box->addRow(
object_ptr<Ui::CenterWrap<>>(
content,
object_ptr<Ui::UserpicButton>(
content,
bot,
st::defaultUserpicButton)));
widget->setAttribute(Qt::WA_TransparentForMouseEvents);
}
const auto thumb = box->addRow(object_ptr<Ui::CenterWrap<>>(
content,
SendCreditsThumbnail(content, session, form.get(), photoSize)));
thumb->setAttribute(Qt::WA_TransparentForMouseEvents);
Ui::AddSkip(content);
box->addRow(object_ptr<Ui::CenterWrap<>>(
@@ -118,14 +248,7 @@ void SendCreditsBox(
box,
object_ptr<Ui::FlatLabel>(
box,
tr::lng_credits_box_out_sure(
lt_count,
rpl::single(form->invoice.amount) | tr::to_count(),
lt_text,
rpl::single(TextWithEntities{ form->title }),
lt_bot,
rpl::single(TextWithEntities{ bot->name() }),
Ui::Text::RichLangValue),
SendCreditsConfirmText(session, form.get()),
st::creditsBoxAbout)));
Ui::AddSkip(content);
Ui::AddSkip(content);
@@ -158,26 +281,16 @@ void SendCreditsBox(
loadingAnimation->showOn(state->confirmButtonBusy.value());
}
{
const auto emojiMargin = QMargins(
0,
-st::moderateBoxExpandInnerSkip,
0,
0);
const auto buttonEmoji = Ui::Text::SingleCustomEmoji(
session->data().customEmojiManager().registerInternalEmoji(
st::settingsPremiumIconStar,
emojiMargin,
true));
auto buttonText = tr::lng_credits_box_out_confirm(
lt_count,
rpl::single(form->invoice.amount) | tr::to_count(),
lt_emoji,
rpl::single(buttonEmoji),
rpl::single(CreditsEmojiSmall(session)),
Ui::Text::RichLangValue);
const auto buttonLabel = Ui::CreateChild<Ui::FlatLabel>(
button,
rpl::single(QString()),
st::defaultFlatLabel);
st::creditsBoxButtonLabel);
std::move(
buttonText
) | rpl::start_with_next([=](const TextWithEntities &text) {
@@ -247,4 +360,22 @@ void SendCreditsBox(
}
}
TextWithEntities CreditsEmoji(not_null<Main::Session*> session) {
return Ui::Text::SingleCustomEmoji(
session->data().customEmojiManager().registerInternalEmoji(
st::settingsPremiumIconStar,
QMargins{ 0, -st::moderateBoxExpandInnerSkip, 0, 0 },
true),
QString(QChar(0x2B50)));
}
TextWithEntities CreditsEmojiSmall(not_null<Main::Session*> session) {
return Ui::Text::SingleCustomEmoji(
session->data().customEmojiManager().registerInternalEmoji(
st::starIconSmall,
st::starIconSmallPadding,
true),
QString(QChar(0x2B50)));
}
} // namespace Ui

View File

@@ -9,6 +9,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
class HistoryItem;
namespace Main {
class Session;
} // namespace Main
namespace Payments {
struct CreditsFormData;
} // namespace Payments
@@ -22,4 +26,10 @@ void SendCreditsBox(
std::shared_ptr<Payments::CreditsFormData> data,
Fn<void()> sent);
[[nodiscard]] TextWithEntities CreditsEmoji(
not_null<Main::Session*> session);
[[nodiscard]] TextWithEntities CreditsEmojiSmall(
not_null<Main::Session*> session);
} // namespace Ui

View File

@@ -10,7 +10,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "lang/lang_keys.h"
#include "storage/localstorage.h"
#include "storage/storage_media_prepare.h"
#include "iv/iv_instance.h"
#include "mainwidget.h"
#include "main/main_app_config.h"
#include "main/main_session.h"
#include "main/main_session_settings.h"
#include "mtproto/mtproto_config.h"
@@ -24,11 +26,14 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "history/view/controls/history_view_characters_limit.h"
#include "history/view/history_view_schedule_box.h"
#include "core/mime_type.h"
#include "core/ui_integration.h"
#include "base/event_filter.h"
#include "base/call_delayed.h"
#include "boxes/premium_limits_box.h"
#include "boxes/premium_preview_box.h"
#include "boxes/send_credits_box.h"
#include "ui/effects/scroll_content_shadow.h"
#include "ui/widgets/fields/number_input.h"
#include "ui/widgets/checkbox.h"
#include "ui/widgets/scroll_area.h"
#include "ui/widgets/popup_menu.h"
@@ -36,10 +41,13 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/chat/attach/attach_single_file_preview.h"
#include "ui/chat/attach/attach_single_media_preview.h"
#include "ui/grouped_layout.h"
#include "ui/text/text_utilities.h"
#include "ui/toast/toast.h"
#include "ui/controls/emoji_button.h"
#include "ui/painter.h"
#include "ui/vertical_list.h"
#include "lottie/lottie_single_player.h"
#include "data/data_channel.h"
#include "data/data_document.h"
#include "data/data_user.h"
#include "data/data_peer_values.h" // Data::AmPremiumValue.
@@ -103,6 +111,84 @@ rpl::producer<QString> FieldPlaceholder(
: tr::lng_photos_comment();
}
void EditPriceBox(
not_null<Ui::GenericBox*> box,
not_null<Main::Session*> session,
uint64 price,
Fn<void(uint64)> apply) {
box->setTitle(tr::lng_paid_title());
AddSubsectionTitle(
box->verticalLayout(),
tr::lng_paid_enter_cost(),
(st::boxRowPadding - QMargins(
st::defaultSubsectionTitlePadding.left(),
0,
st::defaultSubsectionTitlePadding.right(),
0)));
const auto limit = session->appConfig().get<int>(
u"stars_paid_post_amount_max"_q,
10'000);
const auto wrap = box->addRow(object_ptr<Ui::FixedHeightWidget>(
box,
st::editTagField.heightMin));
auto owned = object_ptr<Ui::NumberInput>(
wrap,
st::editTagField,
tr::lng_paid_cost_placeholder(),
price ? QString::number(price) : QString(),
limit);
const auto field = owned.data();
wrap->widthValue() | rpl::start_with_next([=](int width) {
field->move(0, 0);
field->resize(width, field->height());
wrap->resize(width, field->height());
}, wrap->lifetime());
field->selectAll();
box->setFocusCallback([=] {
field->setFocusFast();
});
const auto about = box->addRow(
object_ptr<Ui::FlatLabel>(
box,
tr::lng_paid_about(
lt_link,
tr::lng_paid_about_link() | Ui::Text::ToLink(),
Ui::Text::WithEntities),
st::paidAmountAbout),
st::boxRowPadding + QMargins(0, st::sendMediaRowSkip, 0, 0));
about->setClickHandlerFilter([=](const auto &...) {
Core::App().iv().openWithIvPreferred(
session,
tr::lng_paid_about_link_url(tr::now));
return false;
});
field->paintRequest() | rpl::start_with_next([=](QRect clip) {
auto p = QPainter(field);
st::paidStarIcon.paint(p, 0, st::paidStarIconTop, field->width());
}, field->lifetime());
const auto save = [=] {
const auto now = field->getLastText().toULongLong();
if (now > limit) {
field->showError();
return;
}
const auto weak = Ui::MakeWeak(box);
apply(now);
if (const auto strong = weak.data()) {
strong->closeBox();
}
};
QObject::connect(field, &Ui::NumberInput::submitted, box, save);
box->addButton(tr::lng_settings_save(), save);
box->addButton(tr::lng_cancel(), [=] {
box->closeBox();
});
}
} // namespace
SendFilesLimits DefaultLimitsForPeer(not_null<PeerData*> peer) {
@@ -153,7 +239,8 @@ SendFilesBox::Block::Block(
int from,
int till,
Fn<bool()> gifPaused,
SendFilesWay way)
SendFilesWay way,
Fn<bool()> canToggleSpoiler)
: _items(items)
, _from(from)
, _till(till) {
@@ -170,14 +257,16 @@ SendFilesBox::Block::Block(
parent.get(),
st,
my,
way);
way,
std::move(canToggleSpoiler));
_preview.reset(preview);
} else {
const auto media = Ui::SingleMediaPreview::Create(
parent,
st,
gifPaused,
first);
first,
std::move(canToggleSpoiler));
if (media) {
_isSingleMedia = true;
_preview.reset(media);
@@ -253,6 +342,14 @@ rpl::producer<int> SendFilesBox::Block::itemModifyRequest() const {
}
}
rpl::producer<> SendFilesBox::Block::orderUpdated() const {
if (_isAlbum) {
const auto album = static_cast<Ui::AlbumPreview*>(_preview.get());
return album->orderUpdated();
}
return rpl::never<>();
}
void SendFilesBox::Block::setSendWay(Ui::SendFilesWay way) {
if (!_isAlbum) {
if (_isSingleMedia) {
@@ -321,6 +418,18 @@ void SendFilesBox::Block::applyChanges() {
}
}
QImage SendFilesBox::Block::generatePriceTagBackground() const {
const auto preview = _preview.get();
if (_isAlbum) {
const auto album = static_cast<Ui::AlbumPreview*>(preview);
return album->generatePriceTagBackground();
} else if (_isSingleMedia) {
const auto media = static_cast<Ui::SingleMediaPreview*>(preview);
return media->generatePriceTagBackground();
}
return QImage();
}
SendFilesBox::SendFilesBox(
QWidget*,
not_null<Window::SessionController*> controller,
@@ -385,6 +494,9 @@ Fn<SendMenu::Details()> SendFilesBox::prepareSendMenuDetails(
: _invertCaption
? SendMenu::CaptionState::Above
: SendMenu::CaptionState::Below;
result.price = canChangePrice()
? _price.current()
: std::optional<uint64>();
return result;
});
}
@@ -398,6 +510,7 @@ auto SendFilesBox::prepareSendMenuCallback()
case Type::CaptionUp: _invertCaption = true; break;
case Type::SpoilerOn: toggleSpoilers(true); break;
case Type::SpoilerOff: toggleSpoilers(false); break;
case Type::ChangePrice: changePrice(); break;
default:
SendMenu::DefaultCallback(
_show,
@@ -588,14 +701,27 @@ void SendFilesBox::refreshButtons() {
addMenuButton();
}
bool SendFilesBox::hasSendMenu(const SendMenu::Details &details) const {
bool SendFilesBox::hasSendMenu(const MenuDetails &details) const {
return (details.type != SendMenu::Type::Disabled)
|| (details.spoiler != SendMenu::SpoilerState::None)
|| (details.caption != SendMenu::CaptionState::None);
}
bool SendFilesBox::hasSpoilerMenu() const {
return _list.hasSpoilerMenu(_sendWay.current().sendImagesAsPhotos());
return !hasPrice()
&& _list.hasSpoilerMenu(_sendWay.current().sendImagesAsPhotos());
}
bool SendFilesBox::canChangePrice() const {
const auto way = _sendWay.current();
const auto broadcast = _captionToPeer
? _captionToPeer->asBroadcast()
: nullptr;
return broadcast
&& broadcast->canPostPaidMedia()
&& _list.canChangePrice(
way.groupFiles() && way.sendImagesAsPhotos(),
way.sendImagesAsPhotos());
}
void SendFilesBox::applyBlockChanges() {
@@ -618,6 +744,118 @@ void SendFilesBox::toggleSpoilers(bool enabled) {
}
}
void SendFilesBox::changePrice() {
const auto weak = Ui::MakeWeak(this);
const auto session = &_show->session();
const auto now = _price.current();
_show->show(Box(EditPriceBox, session, now, [=](uint64 price) {
if (weak && price != now) {
_price = price;
refreshPriceTag();
}
}));
}
bool SendFilesBox::hasPrice() const {
return canChangePrice() && _price.current() > 0;
}
void SendFilesBox::refreshPriceTag() {
const auto resetSpoilers = hasPrice() || _priceTag;
if (resetSpoilers) {
for (auto &file : _list.files) {
file.spoiler = false;
}
for (auto &block : _blocks) {
block.toggleSpoilers(hasPrice());
}
}
if (!hasPrice()) {
_priceTag = nullptr;
_priceTagBg = QImage();
} else if (!_priceTag) {
_priceTag = std::make_unique<Ui::RpWidget>(_inner.data());
const auto raw = _priceTag.get();
raw->show();
raw->paintRequest() | rpl::start_with_next([=] {
if (_priceTagBg.isNull()) {
_priceTagBg = preparePriceTagBg(raw->size());
}
QPainter(raw).drawImage(0, 0, _priceTagBg);
}, raw->lifetime());
const auto session = &_show->session();
auto price = _price.value() | rpl::map([=](uint64 amount) {
auto result = Ui::Text::Colorized(Ui::CreditsEmoji(session));
result.append(Lang::FormatCountDecimal(amount));
return result;
});
auto text = tr::lng_paid_price(
lt_price,
std::move(price),
Ui::Text::WithEntities);
const auto label = Ui::CreateChild<Ui::FlatLabel>(
raw,
QString(),
st::paidTagLabel);
std::move(text) | rpl::start_with_next([=](TextWithEntities &&text) {
label->setMarkedText(text, Core::MarkedTextContext{
.session = session,
.customEmojiRepaint = [=] { label->update(); },
});
}, label->lifetime());
label->show();
label->sizeValue() | rpl::start_with_next([=](QSize size) {
const auto inner = QRect(QPoint(), size);
const auto rect = inner.marginsAdded(st::paidTagPadding);
raw->resize(rect.size());
label->move(-rect.topLeft());
}, label->lifetime());
_inner->sizeValue() | rpl::start_with_next([=](QSize size) {
raw->move(
(size.width() - raw->width()) / 2,
(size.height() - raw->height()) / 2);
}, raw->lifetime());
} else {
_priceTag->raise();
_priceTag->update();
_priceTagBg = QImage();
}
}
QImage SendFilesBox::preparePriceTagBg(QSize size) const {
const auto ratio = style::DevicePixelRatio();
const auto outer = _blocks.empty()
? size
: _inner->widgetAt(0)->geometry().size();
auto bg = _blocks.empty()
? QImage()
: _blocks.front().generatePriceTagBackground();
if (bg.isNull()) {
bg = QImage(ratio, ratio, QImage::Format_ARGB32_Premultiplied);
bg.fill(Qt::black);
}
auto result = QImage(size * ratio, QImage::Format_ARGB32_Premultiplied);
result.setDevicePixelRatio(ratio);
result.fill(Qt::black);
auto p = QPainter(&result);
auto hq = PainterHighQualityEnabler(p);
p.drawImage(
QRect(
(size.width() - outer.width()) / 2,
(size.height() - outer.height()) / 2,
outer.width(),
outer.height()),
bg);
p.fillRect(QRect(QPoint(), size), st::msgDateImgBg);
p.end();
const auto radius = std::min(size.width(), size.height()) / 2;
return Images::Round(std::move(result), Images::CornersMask(radius));
}
void SendFilesBox::addMenuButton() {
const auto details = _sendMenuDetails();
if (!hasSendMenu(details)) {
@@ -681,6 +919,7 @@ void SendFilesBox::initSendWay() {
block.setSendWay(value);
}
refreshButtons();
refreshPriceTag();
if (was != hidden()) {
updateBoxSize();
updateControlsGeometry();
@@ -766,7 +1005,8 @@ void SendFilesBox::pushBlock(int from, int till) {
from,
till,
gifPaused,
_sendWay.current());
_sendWay.current(),
[=] { return !hasPrice(); });
auto &block = _blocks.back();
const auto widget = _inner->add(
block.takeWidget(),
@@ -889,10 +1129,18 @@ void SendFilesBox::pushBlock(int from, int till) {
st::sendMediaPreviewSize,
[=] { refreshAllAfterChanges(from); });
}, widget->lifetime());
block.orderUpdated() | rpl::start_with_next([=]{
if (_priceTag) {
_priceTagBg = QImage();
_priceTag->update();
}
}, widget->lifetime());
}
void SendFilesBox::refreshControls(bool initial) {
refreshButtons();
refreshPriceTag();
refreshTitleText();
updateSendWayControls();
updateCaptionPlaceholder();
@@ -1447,6 +1695,7 @@ void SendFilesBox::send(
auto child = _sendMenuDetails();
child.spoiler = SendMenu::SpoilerState::None;
child.caption = SendMenu::CaptionState::None;
child.price = std::nullopt;
return SendMenu::DefaultCallback(_show, sendCallback())(
{ .type = SendMenu::ActionType::Schedule },
child);
@@ -1474,10 +1723,16 @@ void SendFilesBox::send(
auto caption = (_caption && !_caption->isHidden())
? _caption->getTextWithAppliedMarkdown()
: TextWithTags();
options.invertCaption = _invertCaption;
if (!validateLength(caption.text)) {
return;
}
options.invertCaption = _invertCaption;
options.price = hasPrice() ? _price.current() : 0;
if (options.price > 0) {
for (auto &file : _list.files) {
file.spoiler = false;
}
}
_confirmedCallback(
std::move(_list),
_sendWay.current(),

View File

@@ -149,7 +149,8 @@ private:
int from,
int till,
Fn<bool()> gifPaused,
Ui::SendFilesWay way);
Ui::SendFilesWay way,
Fn<bool()> canToggleSpoiler);
Block(Block &&other) = default;
Block &operator=(Block &&other) = default;
@@ -160,11 +161,14 @@ private:
[[nodiscard]] rpl::producer<int> itemDeleteRequest() const;
[[nodiscard]] rpl::producer<int> itemReplaceRequest() const;
[[nodiscard]] rpl::producer<int> itemModifyRequest() const;
[[nodiscard]] rpl::producer<> orderUpdated() const;
void setSendWay(Ui::SendFilesWay way);
void toggleSpoilers(bool enabled);
void applyChanges();
[[nodiscard]] QImage generatePriceTagBackground() const;
private:
base::unique_qptr<Ui::RpWidget> _preview;
not_null<std::vector<Ui::PreparedFile>*> _items;
@@ -190,6 +194,12 @@ private:
void addMenuButton();
void applyBlockChanges();
void toggleSpoilers(bool enabled);
void changePrice();
[[nodiscard]] bool canChangePrice() const;
[[nodiscard]] bool hasPrice() const;
void refreshPriceTag();
[[nodiscard]] QImage preparePriceTagBg(QSize size) const;
bool validateLength(const QString &text) const;
void refreshButtons();
@@ -251,6 +261,9 @@ private:
SendFilesCheck _check;
SendFilesConfirmed _confirmedCallback;
Fn<void()> _cancelledCallback;
rpl::variable<uint64> _price = 0;
std::unique_ptr<Ui::RpWidget> _priceTag;
QImage _priceTagBg;
bool _confirmed = false;
bool _invertCaption = false;

View File

@@ -1393,7 +1393,6 @@ groupCallScheduleDateField: InputField(groupCallField) {
placeholderScale: 0.;
heightMin: 30px;
textAlign: align(top);
font: font(14px);
}
groupCallScheduleTimeField: InputField(groupCallScheduleDateField) {
textBg: groupCallMembersBg;

View File

@@ -706,7 +706,8 @@ bool Call::handleUpdate(const MTPPhoneCall &call) {
}
}
if (data.is_need_rating() && _id && _accessHash) {
const auto window = Core::App().windowFor(_user);
const auto window = Core::App().windowFor(
Window::SeparateId(_user));
const auto session = &_user->session();
const auto callId = _id;
const auto callAccessHash = _accessHash;
@@ -1402,7 +1403,8 @@ void Call::handleRequestError(const QString &error) {
_user->name())
: QString();
if (!inform.isEmpty()) {
if (const auto window = Core::App().windowFor(_user)) {
if (const auto window = Core::App().windowFor(
Window::SeparateId(_user))) {
window->show(Ui::MakeInformBox(inform));
} else {
Ui::show(Ui::MakeInformBox(inform));
@@ -1420,7 +1422,8 @@ void Call::handleControllerError(const QString &error) {
? tr::lng_call_error_audio_io(tr::now)
: QString();
if (!inform.isEmpty()) {
if (const auto window = Core::App().windowFor(_user)) {
if (const auto window = Core::App().windowFor(
Window::SeparateId(_user))) {
window->show(Ui::MakeInformBox(inform));
} else {
Ui::show(Ui::MakeInformBox(inform));

View File

@@ -383,20 +383,14 @@ void Panel::initWindow() {
&& _fullScreenOrMaximized.current()) {
toggleFullScreen();
}
} else if (e->type() == QEvent::WindowStateChange && _call->rtmp()) {
const auto state = window()->windowState();
_fullScreenOrMaximized = (state & Qt::WindowFullScreen)
|| (state & Qt::WindowMaximized);
}
return base::EventFilterResult::Continue;
});
if (_call->rtmp()) {
QObject::connect(
window()->windowHandle(),
&QWindow::windowStateChanged,
[=](Qt::WindowState state) {
_fullScreenOrMaximized = (state == Qt::WindowFullScreen)
|| (state == Qt::WindowMaximized);
});
}
window()->setBodyTitleArea([=](QPoint widgetPoint) {
using Flag = Ui::WindowTitleHitTestFlag;
const auto titleRect = QRect(

View File

@@ -585,7 +585,6 @@ void ChooseSourceProcess::setupSourcesGeometry() {
void ChooseSourceProcess::setupGeometryWithParent(
not_null<QWidget*> parent) {
_window->createWinId();
const auto parentScreen = [&] {
if (const auto screen = QGuiApplication::screenAt(
parent->geometry().center())) {
@@ -595,7 +594,12 @@ void ChooseSourceProcess::setupGeometryWithParent(
}();
const auto myScreen = _window->screen();
if (parentScreen && myScreen != parentScreen) {
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
_window->setScreen(parentScreen);
#else // Qt >= 6.0.0
_window->createWinId();
_window->windowHandle()->setScreen(parentScreen);
#endif // Qt < 6.0.0
}
_window->setFixedSize(_fixedSize);
_window->move(

View File

@@ -71,6 +71,7 @@ ComposeIcons {
menuSpoilerOff: icon;
menuBelow: icon;
menuAbove: icon;
menuPrice: icon;
stripBubble: icon;
stripExpandPanel: icon;
@@ -610,6 +611,7 @@ defaultComposeIcons: ComposeIcons {
menuSpoilerOff: menuIconSpoilerOff;
menuBelow: menuIconBelow;
menuAbove: menuIconAbove;
menuPrice: menuIconEarn;
stripBubble: icon{
{ "chat/reactions_bubble_shadow", windowShadowFg },
@@ -989,8 +991,33 @@ historyUnreadReactions: TwoIconButton(historyToDown) {
}
historyUnreadThingsSkip: 4px;
historyQuoteStyle: QuoteStyle(defaultQuoteStyle) {
padding: margins(10px, 2px, 4px, 2px);
verticalSkip: 4px;
outline: 3px;
outlineShift: 2px;
radius: 5px;
}
historyTextStyle: TextStyle(defaultTextStyle) {
blockquote: QuoteStyle(historyQuoteStyle) {
padding: margins(10px, 2px, 20px, 2px);
icon: icon{{ "chat/mini_quote", windowFg }};
iconPosition: point(4px, 4px);
expand: icon{{ "intro_country_dropdown", windowFg }};
expandPosition: point(6px, 4px);
collapse: icon{{ "intro_country_dropdown-flip_vertical", windowFg }};
collapsePosition: point(6px, 4px);
}
pre: QuoteStyle(historyQuoteStyle) {
header: 20px;
headerPosition: point(10px, 2px);
scrollable: true;
icon: icon{{ "chat/mini_copy", windowFg }};
iconPosition: point(4px, 2px);
}
}
historyComposeField: InputField(defaultInputField) {
font: normalFont;
style: historyTextStyle;
textMargins: margins(0px, 0px, 0px, 0px);
textAlign: align(left);
textFg: historyComposeAreaFg;
@@ -1381,3 +1408,18 @@ editTagField: InputField(defaultInputField) {
editTagLimit: FlatLabel(defaultFlatLabel) {
textFg: windowSubTextFg;
}
paidStarIcon: icon {{ "settings/premium/star", creditsBg1 }};
paidStarIconTop: 7px;
paidAmountAbout: FlatLabel(defaultFlatLabel) {
minWidth: 256px;
textFg: windowSubTextFg;
}
paidTagLabel: FlatLabel(defaultFlatLabel) {
textFg: radialFg;
palette: TextPalette(defaultTextPalette) {
linkFg: creditsBg1;
}
style: semiboldTextStyle;
}
paidTagPadding: margins(16px, 6px, 16px, 6px);

View File

@@ -30,15 +30,15 @@ ResolveWindow ResolveWindowDefault() {
return (Window::SessionController*)nullptr;
};
auto &app = Core::App();
const auto account = not_null(&session->account());
if (const auto a = check(app.activeWindow())) {
return a;
} else if (const auto b = check(app.activePrimaryWindow())) {
return b;
} else if (const auto c = check(app.windowFor(&session->account()))) {
} else if (const auto c = check(app.windowFor(account))) {
return c;
} else if (const auto d = check(
app.ensureSeparateWindowForAccount(
&session->account()))) {
} else if (const auto d = check(app.ensureSeparateWindowFor(
account))) {
return d;
}
return nullptr;

View File

@@ -14,11 +14,13 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "base/qthelp_regex.h"
#include "base/qthelp_url.h"
#include "base/event_filter.h"
#include "ui/chat/chat_style.h"
#include "ui/layers/generic_box.h"
#include "ui/rect.h"
#include "core/shortcuts.h"
#include "core/application.h"
#include "core/core_settings.h"
#include "core/ui_integration.h"
#include "ui/text/text_utilities.h"
#include "ui/toast/toast.h"
#include "ui/wrap/vertical_layout.h"
@@ -40,6 +42,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "styles/style_boxes.h"
#include "styles/style_chat.h"
#include "styles/style_chat_helpers.h"
#include "styles/style_settings.h"
#include "base/qt/qt_common_adapters.h"
#include <QtCore/QMimeData>
@@ -58,6 +61,7 @@ using EditLinkSelection = Ui::InputField::EditLinkSelection;
constexpr auto kParseLinksTimeout = crl::time(1000);
constexpr auto kTypesDuration = 4 * crl::time(1000);
constexpr auto kCodeLanguageLimit = 32;
// For mention / custom emoji tags save and validate selfId,
// ignore tags for different users.
@@ -222,6 +226,51 @@ void EditLinkBox(
}, text->lifetime());
}
void EditCodeLanguageBox(
not_null<Ui::GenericBox*> box,
QString now,
Fn<void(QString)> save) {
Expects(save != nullptr);
box->setTitle(tr::lng_formatting_code_title());
box->addRow(object_ptr<Ui::FlatLabel>(
box,
tr::lng_formatting_code_language(),
st::settingsAddReplyLabel));
const auto field = box->addRow(object_ptr<Ui::InputField>(
box,
st::settingsAddReplyField,
tr::lng_formatting_code_auto(),
now.trimmed()));
box->setFocusCallback([=] {
field->setFocusFast();
});
field->selectAll();
field->setMaxLength(kCodeLanguageLimit);
Ui::AddLengthLimitLabel(field, kCodeLanguageLimit);
const auto callback = [=] {
const auto name = field->getLastText().trimmed();
const auto check = QRegularExpression("^[a-zA-Z0-9\\+\\-]*$");
if (check.match(name).hasMatch()) {
auto weak = Ui::MakeWeak(box);
save(name);
if (const auto strong = weak.data()) {
strong->closeBox();
}
} else {
field->showError();
}
};
field->submits(
) | rpl::start_with_next(callback, field->lifetime());
box->addButton(tr::lng_settings_save(), callback);
box->addButton(tr::lng_cancel(), [=] {
box->closeBox();
});
}
TextWithEntities StripSupportHashtag(TextWithEntities text) {
static const auto expression = QRegularExpression(
u"\\n?#tsf[a-z0-9_-]*[\\s#a-z0-9_-]*$"_q,
@@ -274,15 +323,24 @@ TextWithTags PrepareEditText(not_null<HistoryItem*> item) {
bool EditTextChanged(
not_null<HistoryItem*> item,
const TextWithTags &updated) {
TextWithTags updated) {
const auto original = PrepareEditText(item);
auto originalWithEntities = TextWithEntities{
std::move(original.text),
TextUtilities::ConvertTextTagsToEntities(original.tags)
};
auto updatedWithEntities = TextWithEntities{
std::move(updated.text),
TextUtilities::ConvertTextTagsToEntities(updated.tags)
};
TextUtilities::PrepareForSending(originalWithEntities, 0);
TextUtilities::PrepareForSending(updatedWithEntities, 0);
// Tags can be different for the same entities, because for
// animated emoji each tag contains a different random number.
// So we compare entities instead of tags.
return (original.text != updated.text)
|| (TextUtilities::ConvertTextTagsToEntities(original.tags)
!= TextUtilities::ConvertTextTagsToEntities(updated.tags));
return originalWithEntities != updatedWithEntities;
}
Fn<bool(
@@ -320,6 +378,13 @@ Fn<bool(
};
}
Fn<void(QString now, Fn<void(QString)> save)> DefaultEditLanguageCallback(
std::shared_ptr<Ui::Show> show) {
return [=](QString now, Fn<void(QString)> save) {
show->showBox(Box(EditCodeLanguageBox, now, save));
};
}
void InitMessageFieldHandlers(
not_null<Main::Session*> session,
std::shared_ptr<Main::SessionShow> show,
@@ -329,12 +394,16 @@ void InitMessageFieldHandlers(
const style::InputField *fieldStyle) {
field->setTagMimeProcessor(
FieldTagMimeProcessor(session, allowPremiumEmoji));
const auto paused = [customEmojiPaused] {
field->setCustomTextContext([=](Fn<void()> repaint) {
return std::any(Core::MarkedTextContext{
.session = session,
.customEmojiRepaint = std::move(repaint),
});
}, [customEmojiPaused] {
return On(PowerSaving::kEmojiChat) || customEmojiPaused();
};
field->setCustomEmojiFactory(
session->data().customEmojiManager().factory(),
std::move(customEmojiPaused));
}, [customEmojiPaused] {
return On(PowerSaving::kChatSpoiler) || customEmojiPaused();
});
field->setInstantReplaces(Ui::InstantReplaces::Default());
field->setInstantReplacesEnabled(
Core::App().settings().replaceEmojiValue());
@@ -342,8 +411,18 @@ void InitMessageFieldHandlers(
if (show) {
field->setEditLinkCallback(
DefaultEditLinkCallback(show, field, fieldStyle));
field->setEditLanguageCallback(DefaultEditLanguageCallback(show));
InitSpellchecker(show, field, fieldStyle != nullptr);
}
const auto style = field->lifetime().make_state<Ui::ChatStyle>(
session->colorIndicesValue());
field->setPreCache([=] {
return style->messageStyle(false, false).preCache.get();
});
field->setBlockquoteCache([=] {
const auto colorIndex = session->user()->colorIndex();
return style->coloredQuoteCache(false, colorIndex).get();
});
}
[[nodiscard]] bool IsGoodFactcheckUrl(QStringView url) {
@@ -569,12 +648,26 @@ void InitMessageFieldFade(
Ui::DestroyChild(b.data());
}, topFade->lifetime());
topFade->show();
bottomFade->showOn(
field->scrollTop().value(
) | rpl::map([field, descent = field->st().font->descent](int scroll) {
return (scroll + descent) < field->scrollTopMax();
}) | rpl::distinct_until_changed());
const auto descent = field->st().style.font->descent;
rpl::merge(
field->changes(),
field->scrollTop().changes() | rpl::to_empty,
field->sizeValue() | rpl::to_empty
) | rpl::start_with_next([=] {
// InputField::changes fires before the auto-resize is being applied,
// so for the scroll values to be accurate we enqueue the check.
InvokeQueued(field, [=] {
const auto topHidden = !field->scrollTop().current();
if (topFade->isHidden() != topHidden) {
topFade->setVisible(!topHidden);
}
const auto adjusted = field->scrollTop().current() + descent;
const auto bottomHidden = (adjusted >= field->scrollTopMax());
if (bottomFade->isHidden() != bottomHidden) {
bottomFade->setVisible(!bottomHidden);
}
});
}, topFade->lifetime());
}
InlineBotQuery ParseInlineBotQuery(
@@ -766,11 +859,7 @@ bool MessageLinksParser::eventFilter(QObject *object, QEvent *event) {
const auto text = static_cast<QKeyEvent*>(event)->text();
if (!text.isEmpty() && text.size() < 3) {
const auto ch = text[0];
if (false
|| ch == '\n'
|| ch == '\r'
|| ch.isSpace()
|| ch == QChar::LineSeparator) {
if (IsSpace(ch)) {
_timer.callOnce(0);
}
}
@@ -1097,7 +1186,9 @@ base::unique_qptr<Ui::RpWidget> PremiumRequiredSendRestriction(
const auto margins = (st.textMargins + st.placeholderMargins);
const auto available = width - margins.left() - margins.right();
label->resizeToWidth(available);
label->moveToLeft(margins.left(), margins.top(), width);
const auto height = label->height() + link->height();
const auto top = (raw->height() - height) / 2;
label->moveToLeft(margins.left(), top, width);
link->move(
(width - link->width()) / 2,
label->y() + label->height());
@@ -1117,8 +1208,8 @@ void SelectTextInFieldWithMargins(
auto textCursor = field->textCursor();
// Try to set equal margins for top and bottom sides.
const auto charsCountInLine = field->width()
/ field->st().font->width('W');
const auto linesCount = (field->height() / field->st().font->height);
/ field->st().style.font->width('W');
const auto linesCount = field->height() / field->st().style.font->height;
const auto selectedLines = (selection.to - selection.from)
/ charsCountInLine;
constexpr auto kMinDiff = ushort(3);

View File

@@ -35,13 +35,14 @@ class Show;
namespace Ui {
class PopupMenu;
class Show;
} // namespace Ui
[[nodiscard]] QString PrepareMentionTag(not_null<UserData*> user);
[[nodiscard]] TextWithTags PrepareEditText(not_null<HistoryItem*> item);
[[nodiscard]] bool EditTextChanged(
not_null<HistoryItem*> item,
const TextWithTags &updated);
TextWithTags updated);
Fn<bool(
Ui::InputField::EditLinkSelection selection,
@@ -51,6 +52,8 @@ Fn<bool(
std::shared_ptr<Main::SessionShow> show,
not_null<Ui::InputField*> field,
const style::InputField *fieldStyle = nullptr);
Fn<void(QString now, Fn<void(QString)> save)> DefaultEditLanguageCallback(
std::shared_ptr<Ui::Show> show);
void InitMessageFieldHandlers(
not_null<Main::Session*> session,
std::shared_ptr<Main::SessionShow> show, // may be null

View File

@@ -8,6 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "core/application.h"
#include "data/data_abstract_structure.h"
#include "data/data_forum.h"
#include "data/data_photo.h"
#include "data/data_document.h"
#include "data/data_session.h"
@@ -91,6 +92,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "payments/payments_checkout_process.h"
#include "export/export_manager.h"
#include "webrtc/webrtc_environment.h"
#include "window/window_separate_id.h"
#include "window/window_session_controller.h"
#include "window/window_controller.h"
#include "boxes/abstract_box.h"
@@ -118,7 +120,7 @@ constexpr auto kFileOpenTimeoutMs = crl::time(1000);
LaunchState GlobalLaunchState/* = LaunchState::Running*/;
void SetCrashAnnotationsGL() {
#ifdef Q_OS_WIN
#ifdef DESKTOP_APP_USE_ANGLE
CrashReports::SetAnnotation("OpenGL ANGLE", [] {
if (Core::App().settings().disableOpenGL()) {
return "Disabled";
@@ -131,11 +133,11 @@ void SetCrashAnnotationsGL() {
}
Unexpected("Ui::GL::CurrentANGLE value in SetupANGLE.");
}());
#else // Q_OS_WIN
#else // DESKTOP_APP_USE_ANGLE
CrashReports::SetAnnotation(
"OpenGL",
Core::App().settings().disableOpenGL() ? "Disabled" : "Enabled");
#endif // Q_OS_WIN
#endif // DESKTOP_APP_USE_ANGLE
}
} // namespace
@@ -209,8 +211,7 @@ Application::~Application() {
setLastActiveWindow(nullptr);
_windowInSettings = _lastActivePrimaryWindow = nullptr;
_closingAsyncWindows.clear();
_secondaryWindows.clear();
_primaryWindows.clear();
_windows.clear();
_mediaView = nullptr;
_notifications->clearAllFast();
@@ -315,8 +316,8 @@ void Application::run() {
// Create mime database, so it won't be slow later.
QMimeDatabase().mimeTypeForName(u"text/plain"_q);
_primaryWindows.emplace(nullptr, std::make_unique<Window::Controller>());
setLastActiveWindow(_primaryWindows.front().second.get());
_windows.emplace(nullptr, std::make_unique<Window::Controller>());
setLastActiveWindow(_windows.front().second.get());
_windowInSettings = _lastActivePrimaryWindow = _lastActiveWindow;
_domain->activeChanges(
@@ -405,7 +406,7 @@ void Application::run() {
}
void Application::showAccount(not_null<Main::Account*> account) {
if (const auto separate = separateWindowForAccount(account)) {
if (const auto separate = separateWindowFor(account)) {
_lastActivePrimaryWindow = separate;
separate->activate();
} else if (const auto last = activePrimaryWindow()) {
@@ -413,13 +414,13 @@ void Application::showAccount(not_null<Main::Account*> account) {
}
}
void Application::checkWindowAccount(not_null<Window::Controller*> window) {
const auto account = window->maybeAccount();
for (auto &[key, existing] : _primaryWindows) {
if (existing.get() == window && key != account) {
void Application::checkWindowId(not_null<Window::Controller*> window) {
const auto id = window->id();
for (auto &[existingId, existing] : _windows) {
if (existing.get() == window && existingId != id) {
auto found = std::move(existing);
_primaryWindows.remove(key);
_primaryWindows.emplace(account, std::move(found));
_windows.remove(existingId);
_windows.emplace(id, std::move(found));
break;
}
}
@@ -495,10 +496,7 @@ void Application::startSystemDarkModeViewer() {
void Application::enumerateWindows(Fn<void(
not_null<Window::Controller*>)> callback) const {
for (const auto &window : ranges::views::values(_primaryWindows)) {
callback(window.get());
}
for (const auto &window : ranges::views::values(_secondaryWindows)) {
for (const auto &window : ranges::views::values(_windows)) {
callback(window.get());
}
}
@@ -607,10 +605,7 @@ void Application::clearEmojiSourceImages() {
}
bool Application::isActiveForTrayMenu() const {
return ranges::any_of(ranges::views::values(_primaryWindows), [=](
const std::unique_ptr<Window::Controller> &controller) {
return controller->widget()->isActiveForTrayMenu();
}) || ranges::any_of(ranges::views::values(_secondaryWindows), [=](
return ranges::any_of(ranges::views::values(_windows), [=](
const std::unique_ptr<Window::Controller> &controller) {
return controller->widget()->isActiveForTrayMenu();
});
@@ -1287,44 +1282,36 @@ Window::Controller *Application::activePrimaryWindow() const {
return _lastActivePrimaryWindow;
}
Window::Controller *Application::separateWindowForAccount(
not_null<Main::Account*> account) const {
for (const auto &[openedAccount, window] : _primaryWindows) {
if (openedAccount == account.get()) {
Window::Controller *Application::separateWindowFor(
Window::SeparateId id) const {
for (const auto &[existingId, window] : _windows) {
if (existingId == id) {
return window.get();
}
}
return nullptr;
}
Window::Controller *Application::separateWindowForPeer(
not_null<PeerData*> peer) const {
for (const auto &[history, window] : _secondaryWindows) {
if (history->peer == peer) {
return window.get();
}
}
return nullptr;
}
Window::Controller *Application::ensureSeparateWindowForPeer(
not_null<PeerData*> peer,
Window::Controller *Application::ensureSeparateWindowFor(
Window::SeparateId id,
MsgId showAtMsgId) {
const auto activate = [&](not_null<Window::Controller*> window) {
window->activate();
return window;
};
if (const auto existing = separateWindowForPeer(peer)) {
existing->sessionController()->showPeerHistory(
peer,
Window::SectionShow::Way::ClearStack,
showAtMsgId);
if (const auto existing = separateWindowFor(id)) {
if (id.thread && id.type == Window::SeparateType::Chat) {
existing->sessionController()->showThread(
id.thread,
showAtMsgId,
Window::SectionShow::Way::ClearStack);
}
return activate(existing);
}
const auto result = _secondaryWindows.emplace(
peer->owner().history(peer),
std::make_unique<Window::Controller>(peer, showAtMsgId)
const auto result = _windows.emplace(
id,
std::make_unique<Window::Controller>(id, showAtMsgId)
).first->second.get();
processCreatedWindow(result);
result->firstShow();
@@ -1332,55 +1319,63 @@ Window::Controller *Application::ensureSeparateWindowForPeer(
return activate(result);
}
Window::Controller *Application::ensureSeparateWindowForAccount(
not_null<Main::Account*> account) {
const auto activate = [&](not_null<Window::Controller*> window) {
window->activate();
return window;
};
if (const auto existing = separateWindowForAccount(account)) {
return activate(existing);
}
const auto result = _primaryWindows.emplace(
account,
std::make_unique<Window::Controller>(account)
).first->second.get();
processCreatedWindow(result);
result->firstShow();
result->finishFirstShow();
return activate(result);
}
Window::Controller *Application::windowFor(not_null<PeerData*> peer) const {
if (const auto separate = separateWindowForPeer(peer)) {
return separate;
}
return windowFor(&peer->account());
}
Window::Controller *Application::windowFor(
not_null<Main::Account*> account) const {
if (const auto separate = separateWindowForAccount(account)) {
Window::Controller *Application::windowFor(Window::SeparateId id) const {
if (const auto separate = separateWindowFor(id)) {
return separate;
} else if (id && id.primary()) {
return windowFor(not_null(id.account));
}
return activePrimaryWindow();
}
Window::Controller *Application::windowForShowingHistory(
not_null<PeerData*> peer) const {
if (const auto separate = separateWindowFor(peer)) {
return separate;
}
auto result = (Window::Controller*)nullptr;
enumerateWindows([&](not_null<Window::Controller*> window) {
if (const auto controller = window->sessionController()) {
const auto current = controller->activeChatCurrent();
if (const auto history = current.history()) {
if (history->peer == peer) {
result = window;
}
}
}
});
return result;
}
Window::Controller *Application::windowForShowingForum(
not_null<Data::Forum*> forum) const {
const auto id = Window::SeparateId(
Window::SeparateType::Forum,
forum->history());
if (const auto separate = separateWindowFor(id)) {
return separate;
}
auto result = (Window::Controller*)nullptr;
enumerateWindows([&](not_null<Window::Controller*> window) {
if (const auto controller = window->sessionController()) {
const auto current = controller->shownForum().current();
if (forum == current) {
result = window;
}
}
});
return result;
}
Window::Controller *Application::findWindow(
not_null<QWidget*> widget) const {
const auto window = widget->window();
if (_lastActiveWindow && _lastActiveWindow->widget() == window) {
return _lastActiveWindow;
}
for (const auto &[account, primary] : _primaryWindows) {
if (primary->widget() == window) {
return primary.get();
}
}
for (const auto &[history, secondary] : _secondaryWindows) {
if (secondary->widget() == window) {
return secondary.get();
for (const auto &[id, controller] : _windows) {
if (controller->widget() == window) {
return controller.get();
}
}
return nullptr;
@@ -1392,10 +1387,11 @@ Window::Controller *Application::activeWindow() const {
bool Application::closeNonLastAsync(not_null<Window::Controller*> window) {
const auto hasOther = [&] {
for (const auto &[account, primary] : _primaryWindows) {
if (!_closingAsyncWindows.contains(primary.get())
&& primary.get() != window
&& primary->maybeSession()) {
for (const auto &[id, controller] : _windows) {
if (id.primary()
&& !_closingAsyncWindows.contains(controller.get())
&& controller.get() != window
&& controller->maybeSession()) {
return true;
}
}
@@ -1457,10 +1453,10 @@ void Application::closeWindow(not_null<Window::Controller*> window) {
: nullptr;
const auto next = nextFromStack
? nextFromStack
: (_primaryWindows.front().second.get() != window)
? _primaryWindows.front().second.get()
: (_primaryWindows.back().second.get() != window)
? _primaryWindows.back().second.get()
: (_windows.front().second.get() != window)
? _windows.front().second.get()
: (_windows.back().second.get() != window)
? _windows.back().second.get()
: nullptr;
Assert(next != window);
@@ -1481,20 +1477,12 @@ void Application::closeWindow(not_null<Window::Controller*> window) {
}
}
_closingAsyncWindows.remove(window);
for (auto i = begin(_primaryWindows); i != end(_primaryWindows);) {
for (auto i = begin(_windows); i != end(_windows);) {
if (i->second.get() == window) {
Assert(_lastActiveWindow != window);
Assert(_lastActivePrimaryWindow != window);
Assert(_windowInSettings != window);
i = _primaryWindows.erase(i);
} else {
++i;
}
}
for (auto i = begin(_secondaryWindows); i != end(_secondaryWindows);) {
if (i->second.get() == window) {
Assert(_lastActiveWindow != window);
i = _secondaryWindows.erase(i);
i = _windows.erase(i);
} else {
++i;
}
@@ -1502,36 +1490,34 @@ void Application::closeWindow(not_null<Window::Controller*> window) {
const auto account = domain().started()
? &domain().active()
: nullptr;
if (account && !_primaryWindows.contains(account) && _lastActiveWindow) {
if (account
&& !_windows.contains(Window::SeparateId(account))
&& _lastActiveWindow) {
domain().activate(&_lastActiveWindow->account());
}
}
void Application::closeChatFromWindows(not_null<PeerData*> peer) {
if (const auto window = windowFor(peer)
; window && !window->isPrimary()) {
closeWindow(window);
}
for (const auto &[history, window] : _secondaryWindows) {
if (const auto session = window->sessionController()) {
if (session->activeChatCurrent().peer() == peer) {
session->showPeerHistory(
window->singlePeer()->id,
Window::SectionShow::Way::ClearStack);
}
}
}
if (const auto window = windowFor(&peer->account())) {
if (const auto primary = window->sessionController()) {
if (primary->activeChatCurrent().peer() == peer) {
primary->clearSectionStack();
}
if (const auto forum = primary->shownForum().current()) {
if (peer->forum() == forum) {
primary->closeForum();
const auto closeOne = [&] {
for (const auto &[id, window] : _windows) {
if (id.thread && id.thread->peer() == peer) {
closeWindow(window.get());
return true;
} else if (const auto controller = window->sessionController()) {
if (controller->activeChatCurrent().peer() == peer) {
controller->showByInitialId();
}
if (const auto forum = controller->shownForum().current()) {
if (peer->forum() == forum) {
controller->closeForum();
}
}
}
}
return false;
};
while (closeOne()) {
}
}
@@ -1737,11 +1723,8 @@ void Application::quitPreventFinished() {
}
void Application::quitDelayed() {
for (const auto &[account, window] : _primaryWindows) {
window->widget()->hide();
}
for (const auto &[history, window] : _secondaryWindows) {
window->widget()->hide();
for (const auto &[id, controller] : _windows) {
controller->widget()->hide();
}
if (!_private->quitTimer.isActive()) {
_private->quitTimer.setCallback([] { Sandbox::QuitWhenStarted(); });

View File

@@ -7,9 +7,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#pragma once
#include "base/timer.h"
#include "mtproto/mtproto_auth_key.h"
#include "mtproto/mtproto_proxy_data.h"
#include "base/timer.h"
#include "window/window_separate_id.h"
class History;
@@ -29,11 +30,9 @@ namespace Window {
class Controller;
} // namespace Window
namespace Window {
namespace Notifications {
namespace Window::Notifications {
class System;
} // namespace Notifications
} // namespace Window
} // namespace Window::Notifications
namespace ChatHelpers {
class EmojiKeywords;
@@ -170,19 +169,17 @@ public:
not_null<QWidget*> widget) const;
[[nodiscard]] Window::Controller *activeWindow() const;
[[nodiscard]] Window::Controller *activePrimaryWindow() const;
[[nodiscard]] Window::Controller *separateWindowForAccount(
not_null<Main::Account*> account) const;
[[nodiscard]] Window::Controller *separateWindowForPeer(
not_null<PeerData*> peer) const;
Window::Controller *ensureSeparateWindowForPeer(
not_null<PeerData*> peer,
MsgId showAtMsgId);
Window::Controller *ensureSeparateWindowForAccount(
not_null<Main::Account*> account);
[[nodiscard]] Window::Controller *separateWindowFor(
Window::SeparateId id) const;
Window::Controller *ensureSeparateWindowFor(
Window::SeparateId id,
MsgId showAtMsgId = 0);
[[nodiscard]] Window::Controller *windowFor( // Doesn't auto-switch.
Window::SeparateId id) const;
[[nodiscard]] Window::Controller *windowForShowingHistory(
not_null<PeerData*> peer) const;
[[nodiscard]] Window::Controller *windowFor( // Doesn't auto-switch.
not_null<Main::Account*> account) const;
[[nodiscard]] Window::Controller *windowForShowingForum(
not_null<Data::Forum*> forum) const;
[[nodiscard]] bool closeNonLastAsync(
not_null<Window::Controller*> window);
void closeWindow(not_null<Window::Controller*> window);
@@ -195,7 +192,7 @@ public:
void checkSystemDarkMode();
[[nodiscard]] bool isActiveForTrayMenu() const;
void closeChatFromWindows(not_null<PeerData*> peer);
void checkWindowAccount(not_null<Window::Controller*> window);
void checkWindowId(not_null<Window::Controller*> window);
void activate();
// Media view interface.
@@ -423,12 +420,9 @@ private:
const std::unique_ptr<Calls::Instance> _calls;
const std::unique_ptr<Iv::Instance> _iv;
base::flat_map<
Main::Account*,
std::unique_ptr<Window::Controller>> _primaryWindows;
Window::SeparateId,
std::unique_ptr<Window::Controller>> _windows;
base::flat_set<not_null<Window::Controller*>> _closingAsyncWindows;
base::flat_map<
not_null<History*>,
std::unique_ptr<Window::Controller>> _secondaryWindows;
std::vector<not_null<Window::Controller*>> _windowStack;
Window::Controller *_lastActiveWindow = nullptr;
Window::Controller *_lastActivePrimaryWindow = nullptr;

View File

@@ -25,6 +25,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "window/window_controller.h"
#include "window/window_session_controller.h"
#include "window/window_session_controller_link_info.h"
#include "styles/style_calls.h" // groupCallBoxLabel
#include "styles/style_layers.h"
namespace {
@@ -121,7 +122,10 @@ void HiddenUrlClickHandler::Open(QString url, QVariant context) {
} else {
const auto parsedUrl = QUrl::fromUserInput(url);
if (UrlRequiresConfirmation(parsedUrl) && !base::IsCtrlPressed()) {
Core::App().hideMediaView();
const auto my = context.value<ClickHandlerContext>();
if (!my.show) {
Core::App().hideMediaView();
}
const auto displayed = parsedUrl.isValid()
? parsedUrl.toDisplayString()
: url;
@@ -130,7 +134,6 @@ void HiddenUrlClickHandler::Open(QString url, QVariant context) {
: parsedUrl.isValid()
? QString::fromUtf8(parsedUrl.toEncoded())
: ShowEncoded(displayed);
const auto my = context.value<ClickHandlerContext>();
const auto controller = my.sessionWindow.get();
const auto use = controller
? &controller->window()
@@ -140,8 +143,11 @@ void HiddenUrlClickHandler::Open(QString url, QVariant context) {
.text = (tr::lng_open_this_link(tr::now)),
.confirmed = [=](Fn<void()> hide) { hide(); open(); },
.confirmText = tr::lng_open_link(),
.labelStyle = my.dark ? &st::groupCallBoxLabel : nullptr,
});
const auto &st = st::boxLabel;
const auto &st = my.dark
? st::groupCallBoxLabel
: st::boxLabel;
box->addSkip(st.style.lineHeight - st::boxPadding.bottom());
const auto url = box->addRow(
object_ptr<Ui::FlatLabel>(box, displayUrl, st));
@@ -190,6 +196,7 @@ void BotGameUrlClickHandler::onClick(ClickContext context) const {
_bot->name()),
.confirmed = callback,
.confirmText = tr::lng_allow_bot(),
.labelStyle = my.dark ? &st::groupCallBoxLabel : nullptr,
}));
}
}

View File

@@ -47,6 +47,7 @@ struct ClickHandlerContext {
bool skipBotAutoLogin = false;
bool botStartAutoSubmit = false;
bool ignoreIv = false;
bool dark = false;
// Is filled from peer info.
PeerData *peer = nullptr;
};

View File

@@ -220,7 +220,7 @@ QByteArray Settings::serialize() const {
+ Serialize::bytearraySize(ivPosition)
+ Serialize::stringSize(noWarningExtensions)
+ Serialize::stringSize(_customFontFamily)
+ sizeof(qint32);
+ sizeof(qint32) * 2;
auto result = QByteArray();
result.reserve(size);
@@ -371,7 +371,8 @@ QByteArray Settings::serialize() const {
<< qint32(std::clamp(
qRound(_dialogsNoChatWidthRatio.current() * 1000000),
0,
1000000));
1000000))
<< qint32(_systemUnlockEnabled ? 1 : 0);
}
Ensures(result.size() == size);
@@ -491,6 +492,7 @@ void Settings::addFromSerialized(const QByteArray &serialized) {
qint32 ttlVoiceClickTooltipHidden = _ttlVoiceClickTooltipHidden.current() ? 1 : 0;
QByteArray ivPosition;
QString customFontFamily = _customFontFamily;
qint32 systemUnlockEnabled = _systemUnlockEnabled ? 1 : 0;
stream >> themesAccentColors;
if (!stream.atEnd()) {
@@ -788,6 +790,9 @@ void Settings::addFromSerialized(const QByteArray &serialized) {
0.,
1.);
}
if (!stream.atEnd()) {
stream >> systemUnlockEnabled;
}
if (stream.status() != QDataStream::Ok) {
LOG(("App Error: "
"Bad data for Core::Settings::constructFromSerialized()"));
@@ -995,6 +1000,7 @@ void Settings::addFromSerialized(const QByteArray &serialized) {
_ivPosition = Deserialize(ivPosition);
}
_customFontFamily = customFontFamily;
_systemUnlockEnabled = (systemUnlockEnabled == 1);
}
QString Settings::getSoundPath(const QString &key) const {

View File

@@ -884,6 +884,13 @@ public:
_customFontFamily = value;
}
[[nodiscard]] bool systemUnlockEnabled() const {
return _systemUnlockEnabled;
}
void setSystemUnlockEnabled(bool enabled) {
_systemUnlockEnabled = enabled;
}
[[nodiscard]] static bool ThirdColumnByDefault();
[[nodiscard]] static float64 DefaultDialogsWidthRatio();
@@ -1014,6 +1021,7 @@ private:
rpl::variable<bool> _ttlVoiceClickTooltipHidden = false;
WindowPosition _ivPosition;
QString _customFontFamily;
bool _systemUnlockEnabled = false;
bool _tabbedReplacedWithInfo = false; // per-window
rpl::event_stream<bool> _tabbedReplacedWithInfoValue; // per-window

View File

@@ -285,7 +285,7 @@ pod prf prg ps1 ps2 ps1xml ps2xml psc1 psc2 psd1 psm1 pssc pst py py3 pyc \
pyd pyi pyo pyw pyzw pyz rb reg rgs scf scr sct search-ms settingcontent-ms \
sh shb shs slk sys swf t tmp u3p url vb vbe vbp vbs vbscript vdx vsmacros \
vsd vsdm vsdx vss vssm vssx vst vstm vstx vsw vsx vtx website wlua ws wsc \
wsf wsh xbap xll xlsm xnk xs"_q
wsf wsh xbap xll xlsb xlsm xnk xs"_q
#elif defined Q_OS_MAC // Q_OS_MAC
u"\
applescript action app bin command csh osx workflow terminal url caction \

View File

@@ -620,7 +620,7 @@ void Sandbox::processPostponedCalls(int level) {
bool Sandbox::nativeEventFilter(
const QByteArray &eventType,
void *message,
base::NativeEventResult *result) {
native_event_filter_result *result) {
registerEnterFromEventLoop();
return false;
}

View File

@@ -8,7 +8,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#pragma once
#include "mtproto/mtproto_proxy_data.h"
#include "base/qt/qt_common_adapters.h"
#include <QtWidgets/QApplication>
#include <QtNetwork/QLocalServer>
@@ -87,7 +86,7 @@ private:
bool nativeEventFilter(
const QByteArray &eventType,
void *message,
base::NativeEventResult *result) override;
native_event_filter_result *result) override;
void processPostponedCalls(int level);
void singleInstanceChecked();
void launchApplication();

View File

@@ -271,7 +271,7 @@ bool UiIntegration::copyPreOnClick(const QVariant &context) {
}
std::unique_ptr<Ui::Text::CustomEmoji> UiIntegration::createCustomEmoji(
const QString &data,
QStringView data,
const std::any &context) {
const auto my = std::any_cast<MarkedTextContext>(&context);
if (!my || !my->session) {

View File

@@ -58,7 +58,7 @@ public:
const Ui::Emoji::One *defaultEmojiVariant(
const Ui::Emoji::One *emoji) override;
std::unique_ptr<Ui::Text::CustomEmoji> createCustomEmoji(
const QString &data,
QStringView data,
const std::any &context) override;
Fn<void()> createSpoilerRepaint(const std::any &context) override;
bool allowClickHandlerActivation(

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 = 5001002;
constexpr auto AppVersionStr = "5.1.2";
constexpr auto AppVersion = 5002001;
constexpr auto AppVersionStr = "5.2.1";
constexpr auto AppBetaVersion = false;
constexpr auto AppAlphaVersion = TDESKTOP_ALPHA_VERSION;

View File

@@ -583,6 +583,10 @@ bool ChannelData::canDeleteStories() const {
|| (adminRights() & AdminRight::DeleteStories);
}
bool ChannelData::canPostPaidMedia() const {
return canPostMessages() && (flags() & Flag::PaidMediaAllowed);
}
bool ChannelData::anyoneCanAddMembers() const {
return !(defaultRestrictions() & Restriction::AddParticipants);
}
@@ -1084,7 +1088,8 @@ void ApplyChannelUpdate(
| Flag::ParticipantsHidden
| Flag::CanGetStatistics
| Flag::ViewAsMessages
| Flag::CanViewRevenue;
| Flag::CanViewRevenue
| Flag::PaidMediaAllowed;
channel->setFlags((channel->flags() & ~mask)
| (update.is_can_set_username() ? Flag::CanSetUsername : Flag())
| (update.is_can_view_participants()
@@ -1101,6 +1106,7 @@ void ApplyChannelUpdate(
| (update.is_view_forum_as_messages()
? Flag::ViewAsMessages
: Flag())
| (update.is_paid_media_allowed() ? Flag::PaidMediaAllowed : Flag())
| (update.is_can_view_revenue() ? Flag::CanViewRevenue : Flag()));
channel->setUserpicPhoto(update.vchat_photo());
if (const auto migratedFrom = update.vmigrated_from_chat_id()) {

View File

@@ -66,6 +66,7 @@ enum class ChannelDataFlag : uint64 {
ViewAsMessages = (1ULL << 30),
SimilarExpanded = (1ULL << 31),
CanViewRevenue = (1ULL << 32),
PaidMediaAllowed = (1ULL << 33),
};
inline constexpr bool is_flag_type(ChannelDataFlag) { return true; };
using ChannelDataFlags = base::flags<ChannelDataFlag>;
@@ -357,6 +358,7 @@ public:
[[nodiscard]] bool canPostStories() const;
[[nodiscard]] bool canEditStories() const;
[[nodiscard]] bool canDeleteStories() const;
[[nodiscard]] bool canPostPaidMedia() const;
[[nodiscard]] bool hiddenPreHistory() const;
[[nodiscard]] bool canViewMembers() const;
[[nodiscard]] bool canViewAdmins() const;

View File

@@ -170,6 +170,12 @@ void UpdateCloudFile(
if (data.progressivePartSize && !file.location.valid()) {
file.progressivePartSize = data.progressivePartSize;
}
if (data.location.width()
&& data.location.height()
&& !file.location.valid()
&& !file.location.width()) {
file.location = data.location;
}
return;
}

View File

@@ -19,6 +19,16 @@ struct CreditTopupOption final {
using CreditTopupOptions = std::vector<CreditTopupOption>;
enum class CreditsHistoryMediaType {
Photo,
Video,
};
struct CreditsHistoryMedia {
CreditsHistoryMediaType type = CreditsHistoryMediaType::Photo;
uint64 id = 0;
};
struct CreditsHistoryEntry final {
using PhotoId = uint64;
enum class PeerType {
@@ -28,16 +38,26 @@ struct CreditsHistoryEntry final {
Fragment,
Unsupported,
PremiumBot,
Ads,
};
QString id;
QString title;
QString description;
QDateTime date;
PhotoId photoId = 0;
std::vector<CreditsHistoryMedia> extended;
uint64 credits = 0;
uint64 bareId = 0;
uint64 bareMsgId = 0;
uint64 barePeerId = 0;
PeerType peerType;
bool refunded = false;
bool pending = false;
bool failed = false;
QDateTime successDate;
QString successLink;
bool in = false;
};
struct CreditsStatusSlice final {

View File

@@ -0,0 +1,32 @@
/*
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_statistics_chart.h"
#include <QtCore/QDateTime>
namespace Data {
using CreditsEarnInt = uint64;
struct CreditsEarnStatistics final {
explicit operator bool() const {
return !!usdRate;
}
Data::StatisticalGraph revenueGraph;
CreditsEarnInt currentBalance = 0;
CreditsEarnInt availableBalance = 0;
CreditsEarnInt overallRevenue = 0;
float64 usdRate = 0.;
bool isWithdrawalEnabled = false;
QDateTime nextWithdrawalAt;
QString buyAdsUrl;
};
} // namespace Data

View File

@@ -531,7 +531,7 @@ void DownloadManager::loadingStopWithConfirmation(
return;
}
const auto window = Core::App().windowFor(
&item->history()->session().account());
not_null(&item->history()->session().account()));
if (!window) {
return;
}

View File

@@ -76,6 +76,12 @@ struct FileReferenceAccumulator {
}, [](const auto &data) {
});
}
void push(const MTPMessageExtendedMedia &data) {
data.match([&](const MTPDmessageExtendedMediaPreview &data) {
}, [&](const MTPDmessageExtendedMedia &data) {
push(data.vmedia());
});
}
void push(const MTPMessageMedia &data) {
data.match([&](const MTPDmessageMediaPhoto &data) {
push(data.vphoto());
@@ -85,6 +91,10 @@ struct FileReferenceAccumulator {
push(data.vwebpage());
}, [&](const MTPDmessageMediaGame &data) {
push(data.vgame());
}, [&](const MTPDmessageMediaInvoice &data) {
push(data.vextended_media());
}, [&](const MTPDmessageMediaPaidMedia &data) {
push(data.vextended_media());
}, [](const auto &data) {
});
}

View File

@@ -81,6 +81,7 @@ void Groups::refreshMessage(
bool justRefreshViews) {
if (!isGrouped(item)) {
unregisterMessage(item);
_data->requestItemViewRefresh(item);
return;
}
if (!item->isRegular() && !item->isScheduled()) {

View File

@@ -7,12 +7,13 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#include "data/data_media_types.h"
#include "base/random.h"
#include "boxes/send_credits_box.h" // CreditsEmoji.
#include "history/history.h"
#include "history/history_item.h" // CreateMedia.
#include "history/history_location_manager.h"
#include "history/view/history_view_element.h"
#include "history/view/history_view_item_preview.h"
#include "history/view/media/history_view_extended_preview.h"
#include "history/view/media/history_view_photo.h"
#include "history/view/media/history_view_sticker.h"
#include "history/view/media/history_view_gif.h"
@@ -23,6 +24,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "history/view/media/history_view_giveaway.h"
#include "history/view/media/history_view_invoice.h"
#include "history/view/media/history_view_media_generic.h"
#include "history/view/media/history_view_media_grouped.h"
#include "history/view/media/history_view_call.h"
#include "history/view/media/history_view_web_page.h"
#include "history/view/media/history_view_poll.h"
@@ -84,6 +86,13 @@ constexpr auto kLoadingStoryPhotoId = PhotoId(0x7FFF'DEAD'FFFF'FFFFULL);
using ItemPreview = HistoryView::ItemPreview;
using ItemPreviewImage = HistoryView::ItemPreviewImage;
struct AlbumCounts {
int photos = 0;
int videos = 0;
int audios = 0;
int files = 0;
};
[[nodiscard]] TextWithEntities WithCaptionNotificationText(
const QString &attachType,
const TextWithEntities &caption,
@@ -165,7 +174,7 @@ template <typename MediaType>
return (reinterpret_cast<uint64>(data.get()) & ~1) | (spoiler ? 1 : 0);
}
[[nodiscard]] ItemPreviewImage PreparePhotoPreview(
[[nodiscard]] ItemPreviewImage PreparePhotoPreviewImage(
not_null<const HistoryItem*> item,
const std::shared_ptr<PhotoMedia> &media,
ImageRoundRadius radius,
@@ -181,14 +190,15 @@ template <typename MediaType>
}
const auto allowedToDownload = media->autoLoadThumbnailAllowed(
item->history()->peer);
const auto cacheKey = allowedToDownload ? 0 : counted;
const auto spoilered = uint64(spoiler ? 1 : 0);
const auto cacheKey = allowedToDownload ? spoilered : counted;
if (allowedToDownload) {
media->owner()->load(PhotoSize::Small, item->fullId());
}
if (const auto blurred = media->thumbnailInline()) {
return { PreparePreviewImage(blurred, radius, spoiler), cacheKey };
}
return { QImage(), allowedToDownload ? 0 : cacheKey };
return { QImage(), allowedToDownload ? spoilered : cacheKey };
}
[[nodiscard]] ItemPreviewImage PrepareFilePreviewImage(
@@ -207,10 +217,11 @@ template <typename MediaType>
};
}
document->loadThumbnail(item->fullId());
const auto spoilered = uint64(spoiler ? 1 : 0);
if (const auto blurred = media->thumbnailInline()) {
return { PreparePreviewImage(blurred, radius, spoiler), 0 };
return { PreparePreviewImage(blurred, radius, spoiler), spoilered };
}
return { QImage(), 0 };
return { QImage(), spoilered };
}
[[nodiscard]] QImage PutPlayIcon(QImage preview) {
@@ -225,6 +236,18 @@ template <typename MediaType>
return preview;
}
[[nodiscard]] ItemPreviewImage PreparePhotoPreview(
not_null<const HistoryItem*> item,
const std::shared_ptr<PhotoMedia> &media,
ImageRoundRadius radius,
bool spoiler) {
auto result = PreparePhotoPreviewImage(item, media, radius, spoiler);
if (media->owner()->extendedMediaVideoDuration().has_value()) {
result.data = PutPlayIcon(std::move(result.data));
}
return result;
}
[[nodiscard]] ItemPreviewImage PrepareFilePreview(
not_null<const HistoryItem*> item,
const std::shared_ptr<DocumentMedia> &media,
@@ -261,48 +284,82 @@ template <typename MediaType>
}
bool UpdateExtendedMedia(
Invoice &invoice,
std::unique_ptr<Media> &media,
not_null<HistoryItem*> item,
const MTPMessageExtendedMedia &media) {
return media.match([&](const MTPDmessageExtendedMediaPreview &data) {
if (invoice.extendedMedia) {
return false;
const MTPMessageExtendedMedia &extended) {
return extended.match([&](const MTPDmessageExtendedMediaPreview &data) {
auto photo = (PhotoData*)nullptr;
if (!media) {
const auto id = base::RandomValue<PhotoId>();
photo = item->history()->owner().photo(id);
} else {
photo = media->photo();
if (!photo || !photo->extendedMediaPreview()) {
return false;
}
}
auto changed = false;
auto &preview = invoice.extendedPreview;
auto size = QSize();
auto thumbnail = QByteArray();
auto videoDuration = std::optional<TimeId>();
if (const auto &w = data.vw()) {
const auto &h = data.vh();
Assert(h.has_value());
const auto dimensions = QSize(w->v, h->v);
if (preview.dimensions != dimensions) {
preview.dimensions = dimensions;
size = QSize(w->v, h->v);
if (!changed && photo->size(PhotoSize::Large) != size) {
changed = true;
}
}
if (const auto &thumb = data.vthumb()) {
if (thumb->type() == mtpc_photoStrippedSize) {
const auto bytes = thumb->c_photoStrippedSize().vbytes().v;
if (preview.inlineThumbnailBytes != bytes) {
preview.inlineThumbnailBytes = bytes;
thumbnail = thumb->c_photoStrippedSize().vbytes().v;
if (!changed && photo->inlineThumbnailBytes() != thumbnail) {
changed = true;
}
}
}
if (const auto &duration = data.vvideo_duration()) {
if (preview.videoDuration != duration->v) {
preview.videoDuration = duration->v;
videoDuration = duration->v;
if (photo->extendedMediaVideoDuration() != videoDuration) {
changed = true;
}
} else if (photo->extendedMediaVideoDuration().has_value()) {
changed = true;
}
if (changed) {
photo->setExtendedMediaPreview(size, thumbnail, videoDuration);
}
if (!media) {
media = std::make_unique<MediaPhoto>(item, photo, true);
}
return changed;
}, [&](const MTPDmessageExtendedMedia &data) {
invoice.extendedMedia = HistoryItem::CreateMedia(
item,
data.vmedia());
media = HistoryItem::CreateMedia(item, data.vmedia());
return true;
});
}
bool UpdateExtendedMedia(
Invoice &invoice,
not_null<HistoryItem*> item,
const QVector<MTPMessageExtendedMedia> &media) {
auto changed = false;
const auto count = int(media.size());
for (auto i = 0; i != count; ++i) {
if (i <= invoice.extendedMedia.size()) {
invoice.extendedMedia.emplace_back();
changed = true;
}
UpdateExtendedMedia(invoice.extendedMedia[i], item, media[i]);
}
if (count < invoice.extendedMedia.size()) {
invoice.extendedMedia.resize(count);
changed = true;
}
return changed;
}
TextForMimeData WithCaptionClipboardText(
const QString &attachType,
TextForMimeData &&caption) {
@@ -322,6 +379,29 @@ TextForMimeData WithCaptionClipboardText(
return result;
}
[[nodiscard]] QString ComputeAlbumCountsString(AlbumCounts counts) {
const auto medias = counts.photos + counts.videos;
return (counts.photos && counts.videos)
? tr::lng_in_dlg_media_count(tr::now, lt_count, medias)
: (counts.photos > 1)
? tr::lng_in_dlg_photo_count(tr::now, lt_count, counts.photos)
: counts.photos
? tr::lng_in_dlg_photo(tr::now)
: (counts.videos > 1)
? tr::lng_in_dlg_video_count(tr::now, lt_count, counts.videos)
: counts.videos
? tr::lng_in_dlg_video(tr::now)
: (counts.audios > 1)
? tr::lng_in_dlg_audio_count(tr::now, lt_count, counts.audios)
: counts.audios
? tr::lng_in_dlg_audio(tr::now)
: (counts.files > 1)
? tr::lng_in_dlg_file_count(tr::now, lt_count, counts.files)
: counts.files
? tr::lng_in_dlg_file(tr::now)
: tr::lng_in_dlg_album(tr::now);
}
} // namespace
Invoice ComputeInvoiceData(
@@ -344,11 +424,23 @@ Invoice ComputeInvoiceData(
.isTest = data.is_test(),
};
if (const auto &media = data.vextended_media()) {
UpdateExtendedMedia(result, item, *media);
UpdateExtendedMedia(result, item, { *media });
}
return result;
}
Invoice ComputeInvoiceData(
not_null<HistoryItem*> item,
const MTPDmessageMediaPaidMedia &data) {
auto result = Invoice{
.amount = data.vstars_amount().v,
.currency = Ui::kCreditsCurrency,
.isPaidMedia = true,
};
UpdateExtendedMedia(result, item, data.vextended_media().v);
return result;
}
Call ComputeCallData(const MTPDmessageActionPhoneCall &call) {
auto result = Call();
result.finishReason = [&] {
@@ -424,6 +516,27 @@ GiveawayResults ComputeGiveawayResultsData(
return result;
}
bool HasExtendedMedia(const Invoice &invoice) {
return !invoice.extendedMedia.empty();
}
bool HasUnpaidMedia(const Invoice &invoice) {
for (const auto &media : invoice.extendedMedia) {
const auto photo = media->photo();
return photo && photo->extendedMediaPreview();
}
return false;
}
bool IsFirstVideo(const Invoice &invoice) {
if (invoice.extendedMedia.empty()) {
return false;
} else if (const auto photo = invoice.extendedMedia.front()->photo()) {
return photo->extendedMediaVideoDuration().has_value();
}
return true;
}
Media::Media(not_null<HistoryItem*> parent) : _parent(parent) {
}
@@ -586,21 +699,18 @@ ItemPreview Media::toGroupPreview(
ToPreviewOptions options) const {
auto result = ItemPreview();
auto loadingContext = std::vector<std::any>();
auto photoCount = 0;
auto videoCount = 0;
auto audioCount = 0;
auto fileCount = 0;
auto counts = AlbumCounts();
auto manyCaptions = false;
for (const auto &item : items) {
if (const auto media = item->media()) {
if (media->photo()) {
photoCount++;
counts.photos++;
} else if (const auto document = media->document()) {
(document->isVideoFile()
? videoCount
? counts.videos
: document->isAudioFile()
? audioCount
: fileCount)++;
? counts.audios
: counts.files)++;
}
auto copy = options;
copy.ignoreGroup = true;
@@ -630,19 +740,7 @@ ItemPreview Media::toGroupPreview(
}
}
if (manyCaptions || result.text.text.isEmpty()) {
const auto mediaCount = photoCount + videoCount;
auto genericText = (photoCount && videoCount)
? tr::lng_in_dlg_media_count(tr::now, lt_count, mediaCount)
: photoCount
? tr::lng_in_dlg_photo_count(tr::now, lt_count, photoCount)
: videoCount
? tr::lng_in_dlg_video_count(tr::now, lt_count, videoCount)
: audioCount
? tr::lng_in_dlg_audio_count(tr::now, lt_count, audioCount)
: fileCount
? tr::lng_in_dlg_file_count(tr::now, lt_count, fileCount)
: tr::lng_in_dlg_album(tr::now);
result.text = Ui::Text::Colorized(genericText);
result.text = Ui::Text::Colorized(ComputeAlbumCountsString(counts));
}
if (!loadingContext.empty()) {
result.loadingContext = std::move(loadingContext);
@@ -1219,6 +1317,92 @@ std::unique_ptr<HistoryView::Media> MediaFile::createView(
_document);
}
SharedContact::VcardItems SharedContact::ParseVcard(const QString &data) {
const auto decode = [&](const QByteArray &input) -> QString {
auto output = QByteArray();
for (auto i = 0; i < input.size(); ++i) {
if ((input.at(i) == '=') && ((i + 2) < input.size())) {
const auto value = input.mid((++i)++, 2);
auto converted = false;
const auto character = char(value.toUInt(&converted, 16));
if (converted) {
output.append(character);
} else {
output.append('=');
output.append(value);
}
} else {
output.append(input.at(i));
}
}
return QString::fromUtf8(output);
};
using Type = SharedContact::VcardItemType;
auto items = SharedContact::VcardItems();
for (const auto &item : data.split('\n')) {
const auto parts = item.split(':');
if (parts.size() == 2) {
const auto &type = parts.front();
const auto attributes = type.split(';', Qt::SkipEmptyParts);
const auto c = Qt::CaseInsensitive;
auto isQuotedPrintable = false;
for (const auto &attribute : attributes) {
const auto parts = attribute.split('=', Qt::SkipEmptyParts);
if (parts.size() == 2) {
if (parts.front().startsWith("ENCODING", c)) {
isQuotedPrintable = parts[1].startsWith(
"QUOTED-PRINTABLE",
c);
break;
}
}
}
const auto &value = isQuotedPrintable
? decode(parts[1].toUtf8())
: parts[1];
if (type.startsWith("TEL")) {
const auto telType = type.contains("PREF")
? Type::PhoneMain
: type.contains("HOME")
? Type::PhoneHome
: type.contains("WORK")
? Type::PhoneWork
: (type.contains("CELL")
|| type.contains("MOBILE"))
? Type::PhoneMobile
: type.contains("OTHER")
? Type::PhoneOther
: Type::Phone;
items[telType] = value;
} else if (type.startsWith("EMAIL")) {
items[Type::Email] = value;
} else if (type.startsWith("URL")) {
items[Type::Url] = value;
} else if (type.startsWith("NOTE")) {
items[Type::Note] = value;
} else if (type.startsWith("ORG")) {
items[Type::Organization] = base::duplicate(value)
.replace(';', ' ')
.trimmed();
} else if (type.startsWith("ADR")) {
items[Type::Address] = value;
} else if (type.startsWith("BDAY")) {
items[Type::Birthday] = value;
} else if (type.startsWith("N")) {
items[Type::Name] = base::duplicate(value)
.replace(';', ' ')
.trimmed();
}
}
}
return items;
}
MediaContact::MediaContact(
not_null<HistoryItem*> parent,
UserId userId,
@@ -1765,14 +1949,15 @@ MediaInvoice::MediaInvoice(
.currency = data.currency,
.title = data.title,
.description = data.description,
.extendedPreview = data.extendedPreview,
.extendedMedia = (data.extendedMedia
? data.extendedMedia->clone(parent)
: nullptr),
.photo = data.photo,
.isPaidMedia = data.isPaidMedia,
.isTest = data.isTest,
} {
if (_invoice.extendedPreview && !_invoice.extendedMedia) {
_invoice.extendedMedia.reserve(data.extendedMedia.size());
for (auto &item : data.extendedMedia) {
_invoice.extendedMedia.push_back(item->clone(parent));
}
if (HasUnpaidMedia(_invoice)) {
Ui::PreloadImageSpoiler();
}
}
@@ -1808,9 +1993,89 @@ bool MediaInvoice::replyPreviewLoaded() const {
}
TextWithEntities MediaInvoice::notificationText() const {
if (_invoice.isPaidMedia && !_invoice.extendedMedia.empty()) {
return WithCaptionNotificationText(
(IsFirstVideo(_invoice)
? tr::lng_in_dlg_video
: tr::lng_in_dlg_photo)(tr::now),
parent()->originalText());
}
return { .text = _invoice.title };
}
ItemPreview MediaInvoice::toPreview(ToPreviewOptions options) const {
if (!_invoice.isPaidMedia || _invoice.extendedMedia.empty()) {
return Media::toPreview(options);
}
auto counts = AlbumCounts();
auto images = std::vector<ItemPreviewImage>();
auto context = std::vector<std::any>();
const auto existing = options.existing;
const auto spoiler = HasUnpaidMedia(_invoice);
for (const auto &media : _invoice.extendedMedia) {
const auto raw = media.get();
const auto photo = raw->photo();
const auto document = raw->document();
if (!photo && !document) {
continue;
} else if (images.size() < kMaxPreviewImages) {
auto found = photo
? FindCachedPreview(existing, not_null(photo), spoiler)
: FindCachedPreview(existing, not_null(document), spoiler);
const auto radius = ImageRoundRadius::Small;
if (found) {
images.push_back(std::move(found));
} else if (photo) {
const auto media = photo->createMediaView();
if (auto prepared = PreparePhotoPreview(
parent(),
media,
radius,
spoiler)
; prepared || !prepared.cacheKey) {
images.push_back(std::move(prepared));
if (!prepared.cacheKey) {
context.push_back(media);
}
}
} else if (TryFilePreview(document)) {
const auto media = document->createMediaView();
if (auto prepared = PrepareFilePreview(
parent(),
media,
radius,
spoiler)
; prepared || !prepared.cacheKey) {
images.push_back(std::move(prepared));
if (!prepared.cacheKey) {
context.push_back(media);
}
}
}
}
if (photo && !photo->extendedMediaVideoDuration().has_value()) {
++counts.photos;
} else {
++counts.videos;
}
}
const auto type = ComputeAlbumCountsString(counts);
const auto caption = (options.hideCaption || options.ignoreMessageText)
? TextWithEntities()
: options.translated
? parent()->translatedText()
: parent()->originalText();
const auto hasMiniImages = !images.empty();
auto nice = Ui::Text::Colorized(
Ui::CreditsEmojiSmall(&parent()->history()->session()));
nice.append(WithCaptionNotificationText(type, caption, hasMiniImages));
return {
.text = std::move(nice),
.images = std::move(images),
.loadingContext = std::move(context),
};
}
QString MediaInvoice::pinnedTextSubstring() const {
return QString::fromUtf8("\xC2\xAB")
+ _invoice.title
@@ -1831,7 +2096,7 @@ bool MediaInvoice::updateSentMedia(const MTPMessageMedia &media) {
bool MediaInvoice::updateExtendedMedia(
not_null<HistoryItem*> item,
const MTPMessageExtendedMedia &media) {
const QVector<MTPMessageExtendedMedia> &media) {
Expects(item == parent());
return UpdateExtendedMedia(_invoice, item, media);
@@ -1841,15 +2106,15 @@ std::unique_ptr<HistoryView::Media> MediaInvoice::createView(
not_null<HistoryView::Element*> message,
not_null<HistoryItem*> realParent,
HistoryView::Element *replacing) {
if (_invoice.extendedMedia) {
return _invoice.extendedMedia->createView(
if (_invoice.extendedMedia.size() == 1) {
return _invoice.extendedMedia.front()->createView(
message,
realParent,
replacing);
} else if (_invoice.extendedPreview) {
return std::make_unique<HistoryView::ExtendedPreview>(
} else if (!_invoice.extendedMedia.empty()) {
return std::make_unique<HistoryView::GroupedMedia>(
message,
&_invoice);
_invoice.extendedMedia);
}
return std::make_unique<HistoryView::Invoice>(message, &_invoice);
}

View File

@@ -70,6 +70,8 @@ struct SharedContact final {
};
using VcardItems = base::flat_map<VcardItemType, QString>;
static VcardItems ParseVcard(const QString &);
VcardItems vcardItems;
};
@@ -82,19 +84,6 @@ struct Call {
};
struct ExtendedPreview {
QByteArray inlineThumbnailBytes;
QSize dimensions;
TimeId videoDuration = -1;
[[nodiscard]] bool empty() const {
return dimensions.isEmpty();
}
explicit operator bool() const {
return !empty();
}
};
class Media;
struct Invoice {
@@ -103,11 +92,14 @@ struct Invoice {
QString currency;
QString title;
TextWithEntities description;
ExtendedPreview extendedPreview;
std::unique_ptr<Media> extendedMedia;
std::vector<std::unique_ptr<Media>> extendedMedia;
PhotoData *photo = nullptr;
bool isPaidMedia = false;
bool isTest = false;
};
[[nodiscard]] bool HasExtendedMedia(const Invoice &invoice);
[[nodiscard]] bool HasUnpaidMedia(const Invoice &invoice);
[[nodiscard]] bool IsFirstVideo(const Invoice &invoice);
struct GiveawayStart {
std::vector<not_null<ChannelData*>> channels;
@@ -205,7 +197,7 @@ public:
virtual bool updateSentMedia(const MTPMessageMedia &media) = 0;
virtual bool updateExtendedMedia(
not_null<HistoryItem*> item,
const MTPMessageExtendedMedia &media) {
const QVector<MTPMessageExtendedMedia> &media) {
return false;
}
virtual std::unique_ptr<HistoryView::Media> createView(
@@ -515,6 +507,7 @@ public:
Image *replyPreview() const override;
bool replyPreviewLoaded() const override;
TextWithEntities notificationText() const override;
ItemPreview toPreview(ToPreviewOptions way) const override;
QString pinnedTextSubstring() const override;
TextForMimeData clipboardText() const override;
@@ -522,7 +515,7 @@ public:
bool updateSentMedia(const MTPMessageMedia &media) override;
bool updateExtendedMedia(
not_null<HistoryItem*> item,
const MTPMessageExtendedMedia &media) override;
const QVector<MTPMessageExtendedMedia> &media) override;
std::unique_ptr<HistoryView::Media> createView(
not_null<HistoryView::Element*> message,
not_null<HistoryItem*> realParent,
@@ -748,6 +741,9 @@ private:
[[nodiscard]] Invoice ComputeInvoiceData(
not_null<HistoryItem*> item,
const MTPDmessageMediaInvoice &data);
[[nodiscard]] Invoice ComputeInvoiceData(
not_null<HistoryItem*> item,
const MTPDmessageMediaPaidMedia &data);
[[nodiscard]] Call ComputeCallData(const MTPDmessageActionPhoneCall &call);

View File

@@ -50,6 +50,38 @@ PhotoData::~PhotoData() {
base::take(_videoSizes);
}
void PhotoData::setFields(TimeId date, bool hasAttachedStickers) {
_dateOrExtendedVideoDuration = date;
_hasStickers = hasAttachedStickers;
_extendedMediaPreview = false;
}
void PhotoData::setExtendedMediaPreview(
QSize dimensions,
const QByteArray &inlineThumbnailBytes,
std::optional<TimeId> videoDuration) {
_extendedMediaPreview = true;
updateImages(
inlineThumbnailBytes,
{},
{},
{ .location = { {}, dimensions.width(), dimensions.height() } },
{},
{},
{});
_dateOrExtendedVideoDuration = videoDuration ? (*videoDuration + 1) : 0;
}
bool PhotoData::extendedMediaPreview() const {
return _extendedMediaPreview;
}
std::optional<TimeId> PhotoData::extendedMediaVideoDuration() const {
return (_extendedMediaPreview && _dateOrExtendedVideoDuration)
? TimeId(_dateOrExtendedVideoDuration - 1)
: std::optional<TimeId>();
}
Data::Session &PhotoData::owner() const {
return *_owner;
}
@@ -74,6 +106,10 @@ void PhotoData::load(
load(PhotoSize::Large, origin, fromCloud, autoLoading);
}
TimeId PhotoData::date() const {
return _extendedMediaPreview ? 0 : _dateOrExtendedVideoDuration;
}
bool PhotoData::loading() const {
return loading(PhotoSize::Large);
}

View File

@@ -53,6 +53,7 @@ public:
void automaticLoadSettingsChanged();
[[nodiscard]] TimeId date() const;
[[nodiscard]] bool loading() const;
[[nodiscard]] bool displayLoading() const;
void cancel();
@@ -89,6 +90,14 @@ public:
[[nodiscard]] auto activeMediaView() const
-> std::shared_ptr<Data::PhotoMedia>;
void setFields(TimeId date, bool hasAttachedStickers);
void setExtendedMediaPreview(
QSize dimensions,
const QByteArray &inlineThumbnailBytes,
std::optional<TimeId> videoDuration);
[[nodiscard]] bool extendedMediaPreview() const;
[[nodiscard]] std::optional<TimeId> extendedMediaVideoDuration() const;
void updateImages(
const QByteArray &inlineThumbnailBytes,
const ImageWithLocation &small,
@@ -148,11 +157,10 @@ public:
void setHasAttachedStickers(bool value);
// For now they return size of the 'large' image.
int width() const;
int height() const;
[[nodiscard]] int width() const;
[[nodiscard]] int height() const;
PhotoId id = 0;
TimeId date = 0;
PeerData *peer = nullptr; // for chat and channel photos connection
// geo, caption
@@ -164,6 +172,8 @@ private:
[[nodiscard]] const Data::CloudFile &videoFile(
Data::PhotoSize size) const;
TimeId _dateOrExtendedVideoDuration = 0;
struct VideoSizes {
Data::CloudFile small;
Data::CloudFile large;
@@ -177,6 +187,8 @@ private:
int32 _dc = 0;
uint64 _access = 0;
bool _hasStickers = false;
bool _extendedMediaPreview = false;
QByteArray _fileReference;
std::unique_ptr<Data::ReplyPreview> _replyPreview;
std::weak_ptr<Data::PhotoMedia> _media;

View File

@@ -3077,8 +3077,7 @@ void Session::photoApplyFields(
return;
}
photo->setRemoteLocation(dc, access, fileReference);
photo->date = date;
photo->setHasAttachedStickers(hasStickers);
photo->setFields(date, hasStickers);
photo->updateImages(
inlineThumbnailBytes,
small,
@@ -4683,16 +4682,16 @@ uint64 Session::wallpapersHash() const {
return _wallpapersHash;
}
MTP::DcId Session::statsDcId(not_null<ChannelData*> channel) {
const auto it = _channelStatsDcIds.find(channel);
return (it == end(_channelStatsDcIds)) ? MTP::DcId(0) : it->second;
MTP::DcId Session::statsDcId(not_null<PeerData*> peer) {
const auto it = _peerStatsDcIds.find(peer);
return (it == end(_peerStatsDcIds)) ? MTP::DcId(0) : it->second;
}
void Session::applyStatsDcId(
not_null<ChannelData*> channel,
not_null<PeerData*> peer,
MTP::DcId dcId) {
if (dcId != channel->session().mainDcId()) {
_channelStatsDcIds[channel] = dcId;
if (dcId != peer->session().mainDcId()) {
_peerStatsDcIds[peer] = dcId;
}
}

View File

@@ -753,8 +753,8 @@ public:
[[nodiscard]] auto peerDecorationsUpdated() const
-> rpl::producer<not_null<PeerData*>>;
void applyStatsDcId(not_null<ChannelData*>, MTP::DcId);
[[nodiscard]] MTP::DcId statsDcId(not_null<ChannelData*>);
void applyStatsDcId(not_null<PeerData*>, MTP::DcId);
[[nodiscard]] MTP::DcId statsDcId(not_null<PeerData*>);
void viewTagsChanged(
not_null<ViewElement*> view,
@@ -1053,7 +1053,7 @@ private:
base::flat_map<not_null<UserData*>, TimeId> _watchingForOffline;
base::Timer _watchForOfflineTimer;
base::flat_map<not_null<ChannelData*>, MTP::DcId> _channelStatsDcIds;
base::flat_map<not_null<PeerData*>, MTP::DcId> _peerStatsDcIds;
rpl::event_stream<WebViewResultSent> _webViewResultSent;

View File

@@ -11,6 +11,12 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
namespace Data {
enum class StatisticalCurrency {
None,
Ton,
Credits,
};
struct StatisticalChart {
StatisticalChart() = default;
@@ -67,6 +73,8 @@ struct StatisticalChart {
bool isFooterHidden = false;
bool hasPercentages = false;
bool weekFormat = false;
StatisticalCurrency currency = StatisticalCurrency::None;
float64 currencyRate = 0.;
// View data.

View File

@@ -82,6 +82,7 @@ using UpdateFlag = StoryUpdate::Flag;
});
}, [&](const MTPDmediaAreaSuggestedReaction &data) {
}, [&](const MTPDmediaAreaChannelPost &data) {
}, [&](const MTPDmediaAreaUrl &data) {
}, [&](const MTPDinputMediaAreaChannelPost &data) {
LOG(("API Error: Unexpected inputMediaAreaChannelPost from API."));
}, [&](const MTPDinputMediaAreaVenue &data) {
@@ -103,6 +104,7 @@ using UpdateFlag = StoryUpdate::Flag;
.dark = data.is_dark(),
});
}, [&](const MTPDmediaAreaChannelPost &data) {
}, [&](const MTPDmediaAreaUrl &data) {
}, [&](const MTPDinputMediaAreaChannelPost &data) {
LOG(("API Error: Unexpected inputMediaAreaChannelPost from API."));
}, [&](const MTPDinputMediaAreaVenue &data) {
@@ -124,6 +126,27 @@ using UpdateFlag = StoryUpdate::Flag;
peerFromChannel(data.vchannel_id()),
data.vmsg_id().v),
});
}, [&](const MTPDmediaAreaUrl &data) {
}, [&](const MTPDinputMediaAreaChannelPost &data) {
LOG(("API Error: Unexpected inputMediaAreaChannelPost from API."));
}, [&](const MTPDinputMediaAreaVenue &data) {
LOG(("API Error: Unexpected inputMediaAreaVenue from API."));
});
return result;
}
[[nodiscard]] auto ParseUrlArea(const MTPMediaArea &area)
-> std::optional<UrlArea> {
auto result = std::optional<UrlArea>();
area.match([&](const MTPDmediaAreaVenue &data) {
}, [&](const MTPDmediaAreaGeoPoint &data) {
}, [&](const MTPDmediaAreaSuggestedReaction &data) {
}, [&](const MTPDmediaAreaChannelPost &data) {
}, [&](const MTPDmediaAreaUrl &data) {
result.emplace(UrlArea{
.area = ParseArea(data.vcoordinates()),
.url = qs(data.vurl()),
});
}, [&](const MTPDinputMediaAreaChannelPost &data) {
LOG(("API Error: Unexpected inputMediaAreaChannelPost from API."));
}, [&](const MTPDinputMediaAreaVenue &data) {
@@ -662,6 +685,10 @@ const std::vector<ChannelPost> &Story::channelPosts() const {
return _channelPosts;
}
const std::vector<UrlArea> &Story::urlAreas() const {
return _urlAreas;
}
void Story::applyChanges(
StoryMedia media,
const MTPDstoryItem &data,
@@ -765,6 +792,7 @@ void Story::applyFields(
auto locations = std::vector<StoryLocation>();
auto suggestedReactions = std::vector<SuggestedReaction>();
auto channelPosts = std::vector<ChannelPost>();
auto urlAreas = std::vector<UrlArea>();
if (const auto areas = data.vmedia_areas()) {
for (const auto &area : areas->v) {
if (const auto location = ParseLocation(area)) {
@@ -778,6 +806,8 @@ void Story::applyFields(
suggestedReactions.push_back(*reaction);
} else if (auto post = ParseChannelPost(area)) {
channelPosts.push_back(*post);
} else if (auto url = ParseUrlArea(area)) {
urlAreas.push_back(*url);
}
}
}
@@ -790,6 +820,7 @@ void Story::applyFields(
const auto suggestedReactionsChanged
= (_suggestedReactions != suggestedReactions);
const auto channelPostsChanged = (_channelPosts != channelPosts);
const auto urlAreasChanged = (_urlAreas != urlAreas);
const auto reactionChanged = (_sentReactionId != reaction);
_out = out;
@@ -815,6 +846,9 @@ void Story::applyFields(
if (channelPostsChanged) {
_channelPosts = std::move(channelPosts);
}
if (urlAreasChanged) {
_urlAreas = std::move(urlAreas);
}
if (reactionChanged) {
_sentReactionId = reaction;
}
@@ -824,7 +858,8 @@ void Story::applyFields(
|| captionChanged
|| mediaChanged
|| locationsChanged
|| channelPostsChanged;
|| channelPostsChanged
|| urlAreasChanged;
const auto reactionsChanged = reactionChanged
|| suggestedReactionsChanged;
if (!initial && (changed || reactionsChanged)) {

View File

@@ -122,6 +122,15 @@ struct ChannelPost {
const ChannelPost &) = default;
};
struct UrlArea {
StoryArea area;
QString url;
friend inline bool operator==(
const UrlArea &,
const UrlArea &) = default;
};
class Story final {
public:
Story(
@@ -197,6 +206,8 @@ public:
-> const std::vector<SuggestedReaction> &;
[[nodiscard]] auto channelPosts() const
-> const std::vector<ChannelPost> &;
[[nodiscard]] auto urlAreas() const
-> const std::vector<UrlArea> &;
void applyChanges(
StoryMedia media,
@@ -247,6 +258,7 @@ private:
std::vector<StoryLocation> _locations;
std::vector<SuggestedReaction> _suggestedReactions;
std::vector<ChannelPost> _channelPosts;
std::vector<UrlArea> _urlAreas;
StoryViews _views;
StoryViews _channelReactions;
const TimeId _date = 0;

View File

@@ -433,7 +433,7 @@ bool UserData::someRequirePremiumToWrite() const {
}
bool UserData::meRequiresPremiumToWrite() const {
return (flags() & UserDataFlag::MeRequiresPremiumToWrite);
return !isSelf() && (flags() & UserDataFlag::MeRequiresPremiumToWrite);
}
bool UserData::requirePremiumToWriteKnown() const {

View File

@@ -7,6 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
using "ui/basic.style";
using "ui/layers/layers.style"; // boxRoundShadow
using "ui/widgets/widgets.style";
DialogRow {
@@ -284,7 +285,7 @@ dialogsFilter: InputField(defaultInputField) {
borderRadius: 18px;
borderDenominator: 2;
font: normalFont;
style: defaultTextStyle;
heightMin: 35px;
}
@@ -446,10 +447,25 @@ dialogsLoadMoreLoading: InfiniteRadialAnimation(defaultInfiniteRadialAnimation)
size: size(12px, 12px);
}
dialogsSearchInHeight: 52px;
dialogsSearchInPhotoSize: 36px;
dialogsSearchInPhotoPadding: 10px;
dialogsSearchInSkip: 7px;
dialogsSearchInHeight: 38px;
dialogsSearchInPhotoSize: 26px;
dialogsSearchInPhotoPadding: 12px;
dialogsSearchInSkip: 10px;
dialogsSearchInNameTop: 9px;
dialogsSearchInDownTop: 15px;
dialogsSearchInDown: icon {{ "intro_country_dropdown", windowBoldFg }};
dialogsSearchInDownSkip: 4px;
dialogsSearchInMenu: PopupMenu(defaultPopupMenu) {
shadow: boxRoundShadow;
animation: PanelAnimation(defaultPanelAnimation) {
shadow: boxRoundShadow;
}
scrollPadding: margins(0px, 0px, 0px, 0px);
radius: 8px;
menu: menuWithIcons;
}
dialogsSearchInCheck: icon {{ "player/player_check", mediaPlayerActiveFg }};
dialogsSearchInCheckSkip: 8px;
dialogsSearchFromStyle: defaultTextStyle;
dialogsSearchFromPalette: TextPalette(defaultTextPalette) {
linkFg: dialogsNameFg;
@@ -694,17 +710,17 @@ dialogsStoriesTooltipHide: IconButton(defaultIconButton) {
ripple: emptyRippleAnimation;
}
searchedBarHeight: 32px;
searchedBarHeight: 28px;
searchedBarFont: normalFont;
searchedBarPosition: point(17px, 7px);
searchedBarPosition: point(14px, 5px);
searchedBarLabel: FlatLabel(defaultFlatLabel) {
textFg: searchedBarFg;
margin: margins(17px, 7px, 17px, 7px);
margin: margins(14px, 5px, 14px, 5px);
}
searchedBarLink: LinkButton(defaultLinkButton) {
color: searchedBarFg;
overColor: searchedBarFg;
padding: margins(17px, 7px, 17px, 7px);
padding: margins(14px, 5px, 14px, 5px);
}
dialogsSearchTagSkip: point(8px, 4px);

View File

@@ -9,7 +9,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "dialogs/dialogs_three_state_icon.h"
#include "dialogs/ui/chat_search_empty.h"
#include "dialogs/ui/chat_search_tabs.h"
#include "dialogs/ui/chat_search_in.h"
#include "dialogs/ui/dialogs_layout.h"
#include "dialogs/ui/dialogs_stories_content.h"
#include "dialogs/ui/dialogs_video_userpic.h"
@@ -29,6 +29,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/widgets/scroll_area.h"
#include "ui/text/text_utilities.h"
#include "ui/text/text_options.h"
#include "ui/dynamic_thumbnails.h"
#include "ui/painter.h"
#include "ui/ui_utility.h"
#include "data/data_drafts.h"
@@ -137,9 +138,7 @@ constexpr auto kStartReorderThreshold = 30;
if (hashtag) {
text.append(tr::lng_search_tab_by_hashtag(tr::now));
} else {
text.append(
tr::lng_dlg_search_for_messages(tr::now)
).append('\n').append(Ui::Text::Link(tr::lng_cancel(tr::now)));
text.append(tr::lng_dlg_search_for_messages(tr::now));
}
} else {
text.append(tr::lng_search_tab_no_results(
@@ -214,12 +213,9 @@ InnerWidget::InnerWidget(
, _narrowWidth(st::defaultDialogRow.padding.left()
+ st::defaultDialogRow.photoSize
+ st::defaultDialogRow.padding.left())
, _cancelSearchFromUser(this, st::dialogsCancelSearchInPeer)
, _childListShown(std::move(childListShown)) {
setAttribute(Qt::WA_OpaquePaintEvent, true);
_cancelSearchFromUser->hide();
style::PaletteChanged(
) | rpl::start_with_next([=] {
_topicJumpCache = nullptr;
@@ -516,8 +512,12 @@ int InnerWidget::pinnedOffset() const {
return dialogsOffset() + shownHeight(fixedOnTopCount());
}
int InnerWidget::hashtagsOffset() const {
return searchInChatOffset() + searchInChatSkip();
}
int InnerWidget::filteredOffset() const {
return _hashtagResults.size() * st::mentionHeight;
return hashtagsOffset() + (_hashtagResults.size() * st::mentionHeight);
}
int InnerWidget::filteredIndex(int y) const {
@@ -543,33 +543,19 @@ int InnerWidget::peerSearchOffset() const {
+ st::searchedBarHeight;
}
int InnerWidget::searchTagsOffset() const {
auto result = peerSearchOffset() - st::searchedBarHeight;
if (!_peerSearchResults.empty()) {
result += (_peerSearchResults.size() * st::dialogsRowHeight)
+ st::searchedBarHeight;
}
return result;
}
int InnerWidget::searchInChatOffset() const {
auto result = searchTagsOffset();
if (_searchTags) {
result += _searchTags->height();
}
return result;
}
int InnerWidget::searchedOffset() const {
return searchInChatOffset()
+ searchInChatSkip()
+ st::searchedBarHeight;
return (_searchTags ? _searchTags->height() : 0);
}
int InnerWidget::searchInChatSkip() const {
auto result = 0;
if (_searchFromShown) {
result += st::dialogsSearchInHeight;
return _searchIn ? _searchIn->height() : 0;
}
int InnerWidget::searchedOffset() const {
auto result = peerSearchOffset();
if (!_peerSearchResults.empty()) {
result += (_peerSearchResults.size() * st::dialogsRowHeight)
+ st::searchedBarHeight;
}
return result;
}
@@ -808,10 +794,26 @@ void InnerWidget::paintEvent(QPaintEvent *e) {
p.fillRect(dialogsClip, currentBg());
}
} else if (_state == WidgetState::Filtered) {
[[maybe_unused]] auto top = 0;
if (_searchTags) {
paintSearchTags(p, {
.st = &st::forumTopicRow,
.currentBg = currentBg(),
.now = ms,
.width = fullWidth,
.paused = videoPaused,
});
p.translate(0, _searchTags->height());
}
if (_searchIn) {
p.translate(0, searchInChatSkip());
if (_searchResults.empty()) {
p.fillRect(0, 0, fullWidth, st::lineWidth, st::shadowFg);
}
}
if (!_hashtagResults.empty()) {
auto from = floorclamp(r.y(), st::mentionHeight, 0, _hashtagResults.size());
auto to = ceilclamp(r.y() + r.height(), st::mentionHeight, 0, _hashtagResults.size());
const auto skip = hashtagsOffset();
auto from = floorclamp(r.y() - skip, st::mentionHeight, 0, _hashtagResults.size());
auto to = ceilclamp(r.y() + r.height() - skip, st::mentionHeight, 0, _hashtagResults.size());
p.translate(0, from * st::mentionHeight);
if (from < _hashtagResults.size()) {
const auto htagleft = st::defaultDialogRow.padding.left();
@@ -853,7 +855,9 @@ void InnerWidget::paintEvent(QPaintEvent *e) {
p.drawText(htagleft + firstwidth, st::mentionTop + st::mentionFont->ascent, second);
}
p.translate(0, st::mentionHeight);
top += st::mentionHeight;
}
if (to < _hashtagResults.size()) {
p.translate(0, (_hashtagResults.size() - to) * st::mentionHeight);
}
}
}
@@ -865,7 +869,6 @@ void InnerWidget::paintEvent(QPaintEvent *e) {
int(_filterResults.size()));
const auto height = filteredHeight(from);
p.translate(0, height);
top += height;
for (; from < to; ++from) {
const auto selected = isPressed()
? (from == _filteredPressed)
@@ -873,7 +876,6 @@ void InnerWidget::paintEvent(QPaintEvent *e) {
const auto row = _filterResults[from].row;
paintRow(row, selected, !activeEntry.fullId);
p.translate(0, row->height());
top += row->height();
}
}
@@ -883,13 +885,11 @@ void InnerWidget::paintEvent(QPaintEvent *e) {
p.setPen(st::searchedBarFg);
p.drawTextLeft(st::searchedBarPosition.x(), st::searchedBarPosition.y(), width(), tr::lng_search_global_results(tr::now));
p.translate(0, st::searchedBarHeight);
top += st::searchedBarHeight;
auto skip = peerSearchOffset();
auto from = floorclamp(r.y() - skip, st::dialogsRowHeight, 0, _peerSearchResults.size());
auto to = ceilclamp(r.y() + r.height() - skip, st::dialogsRowHeight, 0, _peerSearchResults.size());
p.translate(0, from * st::dialogsRowHeight);
top += from * st::dialogsRowHeight;
if (from < _peerSearchResults.size()) {
const auto activePeer = activeEntry.key.peer();
for (; from < to; ++from) {
@@ -912,34 +912,10 @@ void InnerWidget::paintEvent(QPaintEvent *e) {
.paused = videoPaused,
});
p.translate(0, st::dialogsRowHeight);
top += st::dialogsRowHeight;
}
}
}
if (_searchTags) {
paintSearchTags(p, {
.st = &st::forumTopicRow,
.currentBg = currentBg(),
.now = ms,
.width = fullWidth,
.paused = videoPaused,
});
p.translate(0, _searchTags->height());
top += _searchTags->height();
}
if (_searchFromShown) {
paintSearchInChat(p, {
.st = &st::forumTopicRow,
.currentBg = currentBg(),
.now = ms,
.width = fullWidth,
.paused = videoPaused,
});
p.translate(0, searchInChatSkip());
top += searchInChatSkip();
if (_searchResults.empty()) {
p.fillRect(0, 0, fullWidth, st::lineWidth, st::shadowFg);
if (to < _peerSearchResults.size()) {
p.translate(0, (_peerSearchResults.size() - to) * st::dialogsRowHeight);
}
}
}
@@ -952,7 +928,6 @@ void InnerWidget::paintEvent(QPaintEvent *e) {
p.setPen(st::searchedBarFg);
p.drawTextLeft(st::searchedBarPosition.x(), st::searchedBarPosition.y(), width(), text);
p.translate(0, st::searchedBarHeight);
top += st::searchedBarHeight;
}
} else {
const auto text = showUnreadInSearchResults
@@ -966,13 +941,11 @@ void InnerWidget::paintEvent(QPaintEvent *e) {
p.setPen(st::searchedBarFg);
p.drawTextLeft(st::searchedBarPosition.x(), st::searchedBarPosition.y(), width(), text);
p.translate(0, st::searchedBarHeight);
top += st::searchedBarHeight;
auto skip = searchedOffset();
auto from = floorclamp(r.y() - skip, _st->height, 0, _searchResults.size());
auto to = ceilclamp(r.y() + r.height() - skip, _st->height, 0, _searchResults.size());
p.translate(0, from * _st->height);
top += from * _st->height;
if (from < _searchResults.size()) {
for (; from < to; ++from) {
const auto &result = _searchResults[from];
@@ -1000,7 +973,6 @@ void InnerWidget::paintEvent(QPaintEvent *e) {
.displayUnreadInfo = showUnreadInSearchResults,
});
p.translate(0, _st->height);
top += _st->height;
}
}
}
@@ -1211,111 +1183,112 @@ void InnerWidget::paintSearchTags(
const auto position = QPoint(_searchTagsLeft, top);
_searchTags->paint(p, position, context.now, context.paused);
}
void InnerWidget::paintSearchInChat(
Painter &p,
const Ui::PaintContext &context) const {
auto height = searchInChatSkip();
auto top = 0;
p.setFont(st::searchedBarFont);
auto fullRect = QRect(0, top, width(), height - top);
p.fillRect(fullRect, currentBg());
if (_searchFromShown) {
p.setPen(st::dialogsTextFg);
p.setTextPalette(st::dialogsSearchFromPalette);
paintSearchInPeer(p, _searchFromShown, _searchFromUserUserpic, top, _searchFromUserText);
p.restoreTextPalette();
}
}
template <typename PaintUserpic>
void InnerWidget::paintSearchInFilter(
Painter &p,
PaintUserpic paintUserpic,
int top,
const style::icon *icon,
const Ui::Text::String &text) const {
const auto savedPen = p.pen();
const auto userpicLeft = st::defaultDialogRow.padding.left();
const auto userpicTop = top
+ (st::dialogsSearchInHeight - st::dialogsSearchInPhotoSize) / 2;
paintUserpic(p, userpicLeft, userpicTop, st::dialogsSearchInPhotoSize);
const auto nameleft = st::defaultDialogRow.padding.left()
+ st::dialogsSearchInPhotoSize
+ st::dialogsSearchInPhotoPadding;
const auto namewidth = width()
- nameleft
- st::defaultDialogRow.padding.left()
- st::defaultDialogRow.padding.right()
- st::dialogsCancelSearch.width;
auto rectForName = QRect(
nameleft,
top + (st::dialogsSearchInHeight - st::semiboldFont->height) / 2,
namewidth,
st::semiboldFont->height);
if (icon) {
icon->paint(p, rectForName.topLeft(), width());
rectForName.setLeft(rectForName.left()
+ icon->width()
+ st::dialogsChatTypeSkip);
}
p.setPen(savedPen);
text.drawLeftElided(
p,
rectForName.left(),
rectForName.top(),
rectForName.width(),
width());
}
void InnerWidget::paintSearchInPeer(
Painter &p,
not_null<PeerData*> peer,
Ui::PeerUserpicView &userpic,
int top,
const Ui::Text::String &text) const {
const auto paintUserpic = [&](Painter &p, int x, int y, int size) {
peer->paintUserpicLeft(p, userpic, x, y, width(), size);
};
const auto icon = Ui::ChatTypeIcon(peer);
paintSearchInFilter(p, paintUserpic, top, icon, text);
}
void InnerWidget::paintSearchInSaved(
Painter &p,
int top,
const Ui::Text::String &text) const {
const auto paintUserpic = [&](Painter &p, int x, int y, int size) {
Ui::EmptyUserpic::PaintSavedMessages(p, x, y, width(), size);
};
paintSearchInFilter(p, paintUserpic, top, nullptr, text);
}
void InnerWidget::paintSearchInReplies(
Painter &p,
int top,
const Ui::Text::String &text) const {
const auto paintUserpic = [&](Painter &p, int x, int y, int size) {
Ui::EmptyUserpic::PaintRepliesMessages(p, x, y, width(), size);
};
paintSearchInFilter(p, paintUserpic, top, nullptr, text);
}
void InnerWidget::paintSearchInTopic(
Painter &p,
const Ui::PaintContext &context,
not_null<Data::ForumTopic*> topic,
Ui::PeerUserpicView &userpic,
int top,
const Ui::Text::String &text) const {
const auto paintUserpic = [&](Painter &p, int x, int y, int size) {
p.translate(x, y);
topic->paintUserpic(p, userpic, context);
p.translate(-x, -y);
};
paintSearchInFilter(p, paintUserpic, top, nullptr, text);
}
//
//void InnerWidget::paintSearchInChat(
// Painter &p,
// const Ui::PaintContext &context) const {
// auto height = searchInChatSkip();
//
// auto top = 0;
// p.setFont(st::searchedBarFont);
// auto fullRect = QRect(0, top, width(), height - top);
// p.fillRect(fullRect, currentBg());
// if (_searchFromShown) {
// p.setPen(st::dialogsTextFg);
// p.setTextPalette(st::dialogsSearchFromPalette);
// paintSearchInPeer(p, _searchFromShown, _searchFromUserUserpic, top, _searchFromUserText);
// p.restoreTextPalette();
// }
//}
//
//template <typename PaintUserpic>
//void InnerWidget::paintSearchInFilter(
// Painter &p,
// PaintUserpic paintUserpic,
// int top,
// const style::icon *icon,
// const Ui::Text::String &text) const {
// const auto savedPen = p.pen();
// const auto userpicLeft = st::defaultDialogRow.padding.left();
// const auto userpicTop = top
// + (st::dialogsSearchInHeight - st::dialogsSearchInPhotoSize) / 2;
// paintUserpic(p, userpicLeft, userpicTop, st::dialogsSearchInPhotoSize);
//
// const auto nameleft = st::defaultDialogRow.padding.left()
// + st::dialogsSearchInPhotoSize
// + st::dialogsSearchInPhotoPadding;
// const auto namewidth = width()
// - nameleft
// - st::defaultDialogRow.padding.left()
// - st::defaultDialogRow.padding.right()
// - st::dialogsCancelSearch.width;
// auto rectForName = QRect(
// nameleft,
// top + (st::dialogsSearchInHeight - st::semiboldFont->height) / 2,
// namewidth,
// st::semiboldFont->height);
// if (icon) {
// icon->paint(p, rectForName.topLeft(), width());
// rectForName.setLeft(rectForName.left()
// + icon->width()
// + st::dialogsChatTypeSkip);
// }
// p.setPen(savedPen);
// text.drawLeftElided(
// p,
// rectForName.left(),
// rectForName.top(),
// rectForName.width(),
// width());
//}
//
//void InnerWidget::paintSearchInPeer(
// Painter &p,
// not_null<PeerData*> peer,
// Ui::PeerUserpicView &userpic,
// int top,
// const Ui::Text::String &text) const {
// const auto paintUserpic = [&](Painter &p, int x, int y, int size) {
// peer->paintUserpicLeft(p, userpic, x, y, width(), size);
// };
// const auto icon = Ui::ChatTypeIcon(peer);
// paintSearchInFilter(p, paintUserpic, top, icon, text);
//}
//
//void InnerWidget::paintSearchInSaved(
// Painter &p,
// int top,
// const Ui::Text::String &text) const {
// const auto paintUserpic = [&](Painter &p, int x, int y, int size) {
// Ui::EmptyUserpic::PaintSavedMessages(p, x, y, width(), size);
// };
// paintSearchInFilter(p, paintUserpic, top, nullptr, text);
//}
//
//void InnerWidget::paintSearchInReplies(
// Painter &p,
// int top,
// const Ui::Text::String &text) const {
// const auto paintUserpic = [&](Painter &p, int x, int y, int size) {
// Ui::EmptyUserpic::PaintRepliesMessages(p, x, y, width(), size);
// };
// paintSearchInFilter(p, paintUserpic, top, nullptr, text);
//}
//
//void InnerWidget::paintSearchInTopic(
// Painter &p,
// const Ui::PaintContext &context,
// not_null<Data::ForumTopic*> topic,
// Ui::PeerUserpicView &userpic,
// int top,
// const Ui::Text::String &text) const {
// const auto paintUserpic = [&](Painter &p, int x, int y, int size) {
// p.translate(x, y);
// topic->paintUserpic(p, userpic, context);
// p.translate(-x, -y);
// };
// paintSearchInFilter(p, paintUserpic, top, nullptr, text);
//}
void InnerWidget::mouseMoveEvent(QMouseEvent *e) {
if (_chatPreviewTouchGlobal || _touchDragStartGlobal) {
@@ -1374,7 +1347,7 @@ void InnerWidget::selectByMouse(QPoint globalPosition) {
const auto tagBase = QPoint(
_searchTagsLeft,
searchTagsOffset() + st::dialogsSearchTagBottom / 2);
st::dialogsSearchTagBottom / 2);
const auto tagPoint = local - tagBase;
const auto inTags = _searchTags
&& QRect(
@@ -1425,7 +1398,7 @@ void InnerWidget::selectByMouse(QPoint globalPosition) {
_hashtagSelected = -1;
_hashtagDeleteSelected = false;
} else {
auto skip = 0;
auto skip = hashtagsOffset();
auto hashtagSelected = (mouseY >= skip) ? ((mouseY - skip) / st::mentionHeight) : -1;
if (hashtagSelected < 0 || hashtagSelected >= _hashtagResults.size()) {
hashtagSelected = -1;
@@ -1558,8 +1531,9 @@ void InnerWidget::mousePressEvent(QMouseEvent *e) {
_dragStart = e->pos();
} else if (base::in_range(_hashtagPressed, 0, _hashtagResults.size()) && !_hashtagDeletePressed) {
auto row = &_hashtagResults[_hashtagPressed]->row;
row->addRipple(e->pos(), QSize(width(), st::mentionHeight), [this, index = _hashtagPressed] {
update(0, index * st::mentionHeight, width(), st::mentionHeight);
const auto origin = e->pos() - QPoint(0, hashtagsOffset() + _hashtagPressed * st::mentionHeight);
row->addRipple(origin, QSize(width(), st::mentionHeight), [this, index = _hashtagPressed] {
update(0, hashtagsOffset() + index * st::mentionHeight, width(), st::mentionHeight);
});
} else if (base::in_range(_filteredPressed, 0, _filterResults.size())) {
const auto &result = _filterResults[_filteredPressed];
@@ -1989,17 +1963,18 @@ void InnerWidget::resizeEvent(QResizeEvent *e) {
_searchTags->resizeToWidth(width() - 2 * _searchTagsLeft);
}
resizeEmpty();
moveCancelSearchButtons();
moveSearchIn();
}
void InnerWidget::moveCancelSearchButtons() {
const auto widthForCancelButton = qMax(
void InnerWidget::moveSearchIn() {
if (!_searchIn) {
return;
}
const auto searchInWidth = std::max(
width(),
st::columnMinimalWidthLeft - _narrowWidth);
const auto left = widthForCancelButton - st::dialogsSearchInSkip - _cancelSearchFromUser->width();
const auto top = (st::dialogsSearchInHeight - st::dialogsCancelSearchInPeer.height) / 2;
const auto skip = (_searchTags ? _searchTags->height() : 0);
_cancelSearchFromUser->moveToLeft(left, skip + top);
_searchIn->resizeToWidth(searchInWidth);
_searchIn->moveToLeft(0, searchInChatOffset());
}
void InnerWidget::dialogRowReplaced(
@@ -2326,7 +2301,7 @@ void InnerWidget::updateSelectedRow(Key key) {
}
}
} else if (_hashtagSelected >= 0) {
update(0, _hashtagSelected * st::mentionHeight, width(), st::mentionHeight);
update(0, hashtagsOffset() + _hashtagSelected * st::mentionHeight, width(), st::mentionHeight);
} else if (_filteredSelected >= 0) {
if (_filteredSelected < _filterResults.size()) {
const auto &result = _filterResults[_filteredSelected];
@@ -2613,6 +2588,15 @@ void InnerWidget::dragPinnedFromTouch() {
updateReorderPinned(now);
}
void InnerWidget::searchRequested(bool loading) {
_searchWaiting = false;
_searchLoading = loading;
if (loading) {
clearSearchResults(true);
}
refresh(true);
}
void InnerWidget::applySearchState(SearchState state) {
if (_searchState == state) {
return;
@@ -2639,7 +2623,7 @@ void InnerWidget::applySearchState(SearchState state) {
_searchTags->repaintRequests() | rpl::start_with_next([=] {
const auto height = _searchTags->height();
update(0, searchTagsOffset(), width(), height);
update(0, 0, width(), height);
}, _searchTags->lifetime());
_searchTags->menuRequests(
@@ -2656,7 +2640,7 @@ void InnerWidget::applySearchState(SearchState state) {
1
) | rpl::start_with_next([=] {
refresh();
moveCancelSearchButtons();
moveSearchIn();
}, _searchTags->lifetime());
} else {
_searchTags = nullptr;
@@ -2670,17 +2654,12 @@ void InnerWidget::applySearchState(SearchState state) {
if (state.inChat) {
onHashtagFilterUpdate(QStringView());
}
if (_searchFromShown) {
_cancelSearchFromUser->show();
_searchFromUserUserpic = _searchFromShown->createUserpicView();
} else {
_cancelSearchFromUser->hide();
_searchFromUserUserpic = {};
}
refreshSearchInChatLabel();
moveCancelSearchButtons();
_searchState = std::move(state);
_searchHashOrCashtag = IsHashOrCashtagSearchQuery(_searchState.query);
updateSearchIn();
moveSearchIn();
auto newFilter = _searchState.query;
const auto mentionsSearch = (newFilter == u"@"_q);
const auto words = mentionsSearch
@@ -2730,9 +2709,13 @@ void InnerWidget::applySearchState(SearchState state) {
clearMouseSelection(true);
}
if (_state != WidgetState::Default) {
_searchLoading = true;
_searchMessages.fire({});
refresh(true);
_searchWaiting = true;
_searchRequests.fire(otherChanged
? SearchRequestDelay::Instant
: SearchRequestDelay::Delayed);
if (_searchWaiting) {
refresh(true);
}
}
}
@@ -2924,12 +2907,20 @@ rpl::producer<> InnerWidget::listBottomReached() const {
return _listBottomReached.events();
}
rpl::producer<> InnerWidget::cancelSearchFromUserRequests() const {
return _cancelSearchFromUser->clicks() | rpl::to_empty;
rpl::producer<ChatSearchTab> InnerWidget::changeSearchTabRequests() const {
return _changeSearchTabRequests.events();
}
rpl::producer<> InnerWidget::cancelSearchRequests() const {
return _cancelSearch.events();
return _cancelSearchRequests.events();
}
rpl::producer<> InnerWidget::cancelSearchFromRequests() const {
return _cancelSearchFromRequests.events();
}
rpl::producer<> InnerWidget::changeSearchFromRequests() const {
return _changeSearchFromRequests.events();
}
rpl::producer<Ui::ScrollToRequest> InnerWidget::mustScrollTo() const {
@@ -2940,8 +2931,8 @@ rpl::producer<Ui::ScrollToRequest> InnerWidget::dialogMoved() const {
return _dialogMoved.events();
}
rpl::producer<> InnerWidget::searchMessages() const {
return _searchMessages.events();
rpl::producer<SearchRequestDelay> InnerWidget::searchRequests() const {
return _searchRequests.events();
}
rpl::producer<QString> InnerWidget::completeHashtagRequests() const {
@@ -3029,6 +3020,7 @@ void InnerWidget::searchReceived(
HistoryItem *inject,
SearchRequestType type,
int fullCount) {
_searchWaiting = false;
_searchLoading = false;
const auto uniquePeers = uniqueSearchResults();
@@ -3193,16 +3185,13 @@ void InnerWidget::refreshEmpty() {
&& _searchResults.empty()
&& _peerSearchResults.empty()
&& _hashtagResults.empty();
if (_searchLoading || !empty) {
if (_searchLoading || _searchWaiting || !empty) {
if (_searchEmpty) {
_searchEmpty->hide();
}
} else if (_searchEmptyState != _searchState) {
_searchEmptyState = _searchState;
_searchEmpty = MakeSearchEmpty(this, _searchState);
_searchEmpty->linkClicks() | rpl::start_with_next([=] {
_cancelSearch.fire({});
}, _searchEmpty->lifetime());
if (_controller->session().data().chatsListLoaded()) {
_searchEmpty->animate();
}
@@ -3210,7 +3199,7 @@ void InnerWidget::refreshEmpty() {
_searchEmpty->show();
}
if (!_searchLoading || !empty) {
if ((!_searchLoading && !_searchWaiting) || !empty) {
_loadingAnimation.destroy();
} else if (!_loadingAnimation) {
_loadingAnimation = Ui::CreateLoadingDialogRowWidget(
@@ -3342,19 +3331,77 @@ auto InnerWidget::searchTagsChanges() const
: rpl::never<std::vector<Data::ReactionId>>();
}
void InnerWidget::refreshSearchInChatLabel() {
const auto from = _searchFromShown ? _searchFromShown->name() : u""_q;
if (!from.isEmpty()) {
const auto fromUserText = tr::lng_dlg_search_from(
tr::now,
lt_user,
Ui::Text::Semibold(from),
Ui::Text::WithEntities);
_searchFromUserText.setMarkedText(
st::dialogsSearchFromStyle,
fromUserText,
Ui::DialogTextOptions());
void InnerWidget::updateSearchIn() {
if (!_searchState.inChat
&& _searchHashOrCashtag == HashOrCashtag::None) {
_searchIn = nullptr;
return;
} else if (!_searchIn) {
_searchIn = std::make_unique<ChatSearchIn>(this);
_searchIn->show();
_searchIn->changeFromRequests() | rpl::start_to_stream(
_changeSearchFromRequests,
_searchIn->lifetime());
_searchIn->cancelFromRequests() | rpl::start_to_stream(
_cancelSearchFromRequests,
_searchIn->lifetime());
_searchIn->cancelInRequests() | rpl::start_to_stream(
_cancelSearchRequests,
_searchIn->lifetime());
_searchIn->tabChanges() | rpl::start_to_stream(
_changeSearchTabRequests,
_searchIn->lifetime());
}
const auto sublist = _searchState.inChat.sublist();
const auto topic = _searchState.inChat.topic();
const auto peer = _searchState.inChat.owningHistory()
? _searchState.inChat.owningHistory()->peer.get()
: _openedForum
? _openedForum->channel().get()
: nullptr;
const auto topicIcon = !topic
? nullptr
: topic->iconId()
? Ui::MakeEmojiThumbnail(
&topic->owner(),
Data::SerializeCustomEmojiId(topic->iconId()))
: Ui::MakeEmojiThumbnail(
&topic->owner(),
Data::TopicIconEmojiEntity({
.title = (topic->isGeneral()
? Data::ForumGeneralIconTitle()
: topic->title()),
.colorId = (topic->isGeneral()
? Data::ForumGeneralIconColor(st::windowSubTextFg->c)
: topic->colorId()),
}));
const auto peerIcon = peer
? Ui::MakeUserpicThumbnail(peer)
: sublist
? Ui::MakeUserpicThumbnail(sublist->peer())
: nullptr;
const auto myIcon = Ui::MakeIconThumbnail(st::menuIconChats);
const auto publicIcon = (_searchHashOrCashtag != HashOrCashtag::None)
? Ui::MakeIconThumbnail(st::menuIconChannel)
: nullptr;
const auto peerTabType = (peer && peer->isBroadcast())
? ChatSearchPeerTabType::Channel
: (peer && (peer->isChat() || peer->isMegagroup()))
? ChatSearchPeerTabType::Group
: ChatSearchPeerTabType::Chat;
const auto fromImage = _searchFromShown
? Ui::MakeUserpicThumbnail(_searchFromShown)
: nullptr;
const auto fromName = _searchFromShown
? _searchFromShown->shortName()
: QString();
_searchIn->apply({
{ ChatSearchTab::ThisTopic, topicIcon },
{ ChatSearchTab::ThisPeer, peerIcon },
{ ChatSearchTab::MyMessages, myIcon },
{ ChatSearchTab::PublicPosts, publicIcon },
}, _searchState.tab, peerTabType, fromImage, fromName);
}
void InnerWidget::repaintSearchResult(int index) {
@@ -3629,7 +3676,7 @@ void InnerWidget::preloadRowsData() {
}
}
bool InnerWidget::chooseCollapsedRow() {
bool InnerWidget::chooseCollapsedRow(Qt::KeyboardModifiers modifiers) {
if (_state != WidgetState::Default) {
return false;
} else if ((_collapsedSelected < 0)
@@ -3643,6 +3690,9 @@ bool InnerWidget::chooseCollapsedRow() {
}
void InnerWidget::switchToFilter(FilterId filterId) {
if (_controller->windowId().type != Window::SeparateType::Primary) {
return;
}
const auto &list = session().data().chatsFilters().list();
const auto filterIt = filterId
? ranges::find(list, filterId, &Data::ChatFilter::id)
@@ -3722,7 +3772,15 @@ bool InnerWidget::chooseHashtag() {
ChosenRow InnerWidget::computeChosenRow() const {
if (_state == WidgetState::Default) {
if (_selected) {
if ((_collapsedSelected >= 0)
&& (_collapsedSelected < _collapsedRows.size())) {
const auto &row = _collapsedRows[_collapsedSelected];
Assert(row->folder != nullptr);
return {
.key = row->folder,
.message = Data::UnreadMessagePosition,
};
} else if (_selected) {
return {
.key = _selected->key(),
.message = Data::UnreadMessagePosition,
@@ -3766,9 +3824,7 @@ bool InnerWidget::isUserpicPressOnWide() const {
bool InnerWidget::chooseRow(
Qt::KeyboardModifiers modifiers,
MsgId pressedTopicRootId) {
if (chooseCollapsedRow()) {
return true;
} else if (chooseHashtag()) {
if (chooseHashtag()) {
return true;
}
const auto modifyChosenRow = [&](

View File

@@ -61,6 +61,8 @@ class FakeRow;
class IndexedList;
class SearchTags;
class SearchEmpty;
class ChatSearchIn;
enum class HashOrCashtag : uchar;
struct ChosenRow {
Key key;
@@ -70,7 +72,7 @@ struct ChosenRow {
bool newWindow : 1 = false;
};
enum class SearchRequestType {
enum class SearchRequestType : uchar {
FromStart,
FromOffset,
PeerFromStart,
@@ -79,6 +81,12 @@ enum class SearchRequestType {
MigratedFromOffset,
};
enum class SearchRequestDelay : uchar {
InCache,
Instant,
Delayed,
};
enum class WidgetState {
Default,
Filtered,
@@ -144,6 +152,7 @@ public:
}
[[nodiscard]] bool hasFilteredResults() const;
void searchRequested(bool loading);
void applySearchState(SearchState state);
[[nodiscard]] auto searchTagsChanges() const
-> rpl::producer<std::vector<Data::ReactionId>>;
@@ -156,15 +165,18 @@ public:
void setLoadMoreCallback(Fn<void()> callback);
void setLoadMoreFilteredCallback(Fn<void()> callback);
[[nodiscard]] rpl::producer<> listBottomReached() const;
[[nodiscard]] rpl::producer<> cancelSearchFromUserRequests() const;
[[nodiscard]] auto changeSearchTabRequests() const
-> rpl::producer<ChatSearchTab>;
[[nodiscard]] rpl::producer<> cancelSearchRequests() const;
[[nodiscard]] rpl::producer<> cancelSearchFromRequests() const;
[[nodiscard]] rpl::producer<> changeSearchFromRequests() const;
[[nodiscard]] rpl::producer<ChosenRow> chosenRow() const;
[[nodiscard]] rpl::producer<> updated() const;
[[nodiscard]] rpl::producer<int> scrollByDeltaRequests() const;
[[nodiscard]] rpl::producer<Ui::ScrollToRequest> mustScrollTo() const;
[[nodiscard]] rpl::producer<Ui::ScrollToRequest> dialogMoved() const;
[[nodiscard]] rpl::producer<> searchMessages() const;
[[nodiscard]] rpl::producer<SearchRequestDelay> searchRequests() const;
[[nodiscard]] rpl::producer<QString> completeHashtagRequests() const;
[[nodiscard]] rpl::producer<> refreshHashtagsRequests() const;
@@ -240,7 +252,7 @@ private:
void repaintCollapsedFolderRow(not_null<Data::Folder*> folder);
void refreshWithCollapsedRows(bool toTop = false);
bool needCollapsedRowsRefresh() const;
bool chooseCollapsedRow();
bool chooseCollapsedRow(Qt::KeyboardModifiers modifiers);
void switchToFilter(FilterId filterId);
bool chooseHashtag();
ChosenRow computeChosenRow() const;
@@ -339,10 +351,10 @@ private:
[[nodiscard]] int filteredIndex(int y) const;
[[nodiscard]] int filteredHeight(int till = -1) const;
[[nodiscard]] int peerSearchOffset() const;
[[nodiscard]] int searchTagsOffset() const;
[[nodiscard]] int searchInChatOffset() const;
[[nodiscard]] int searchedOffset() const;
[[nodiscard]] int searchInChatSkip() const;
[[nodiscard]] int hashtagsOffset() const;
void paintCollapsedRows(
Painter &p,
@@ -358,38 +370,38 @@ private:
void paintSearchTags(
Painter &p,
const Ui::PaintContext &context) const;
void paintSearchInChat(
Painter &p,
const Ui::PaintContext &context) const;
void paintSearchInPeer(
Painter &p,
not_null<PeerData*> peer,
Ui::PeerUserpicView &userpic,
int top,
const Ui::Text::String &text) const;
void paintSearchInSaved(
Painter &p,
int top,
const Ui::Text::String &text) const;
void paintSearchInReplies(
Painter &p,
int top,
const Ui::Text::String &text) const;
void paintSearchInTopic(
Painter &p,
const Ui::PaintContext &context,
not_null<Data::ForumTopic*> topic,
Ui::PeerUserpicView &userpic,
int top,
const Ui::Text::String &text) const;
template <typename PaintUserpic>
void paintSearchInFilter(
Painter &p,
PaintUserpic paintUserpic,
int top,
const style::icon *icon,
const Ui::Text::String &text) const;
void refreshSearchInChatLabel();
//void paintSearchInChat(
// Painter &p,
// const Ui::PaintContext &context) const;
//void paintSearchInPeer(
// Painter &p,
// not_null<PeerData*> peer,
// Ui::PeerUserpicView &userpic,
// int top,
// const Ui::Text::String &text) const;
//void paintSearchInSaved(
// Painter &p,
// int top,
// const Ui::Text::String &text) const;
//void paintSearchInReplies(
// Painter &p,
// int top,
// const Ui::Text::String &text) const;
//void paintSearchInTopic(
// Painter &p,
// const Ui::PaintContext &context,
// not_null<Data::ForumTopic*> topic,
// Ui::PeerUserpicView &userpic,
// int top,
// const Ui::Text::String &text) const;
//template <typename PaintUserpic>
//void paintSearchInFilter(
// Painter &p,
// PaintUserpic paintUserpic,
// int top,
// const style::icon *icon,
// const Ui::Text::String &text) const;
void updateSearchIn();
void repaintSearchResult(int index);
Ui::VideoUserpic *validateVideoUserpic(not_null<Row*> row);
@@ -415,7 +427,7 @@ private:
void savePinnedOrder();
bool pinnedShiftAnimationCallback(crl::time now);
void handleChatListEntryRefreshes();
void moveCancelSearchButtons();
void moveSearchIn();
void dragPinnedFromTouch();
void saveChatsFilterScrollState(FilterId filterId);
@@ -490,19 +502,22 @@ private:
WidgetState _state = WidgetState::Default;
std::unique_ptr<ChatSearchIn> _searchIn;
rpl::event_stream<ChatSearchTab> _changeSearchTabRequests;
rpl::event_stream<> _cancelSearchRequests;
rpl::event_stream<> _cancelSearchFromRequests;
rpl::event_stream<> _changeSearchFromRequests;
object_ptr<Ui::RpWidget> _loadingAnimation = { nullptr };
object_ptr<SearchEmpty> _searchEmpty = { nullptr };
SearchState _searchEmptyState;
object_ptr<Ui::FlatLabel> _empty = { nullptr };
object_ptr<Ui::IconButton> _cancelSearchFromUser;
rpl::event_stream<> _cancelSearch;
Ui::DraggingScrollManager _draggingScroll;
SearchState _searchState;
HashOrCashtag _searchHashOrCashtag = {};
History *_searchInMigrated = nullptr;
PeerData *_searchFromShown = nullptr;
mutable Ui::PeerUserpicView _searchFromUserUserpic;
Ui::Text::String _searchFromUserText;
std::unique_ptr<SearchTags> _searchTags;
int _searchTagsLeft = 0;
@@ -522,7 +537,7 @@ private:
rpl::event_stream<Ui::ScrollToRequest> _mustScrollTo;
rpl::event_stream<Ui::ScrollToRequest> _dialogMoved;
rpl::event_stream<> _searchMessages;
rpl::event_stream<SearchRequestDelay> _searchRequests;
rpl::event_stream<QString> _completeHashtagRequests;
rpl::event_stream<> _refreshHashtagsRequests;
@@ -540,6 +555,7 @@ private:
bool _savedSublists = false;
bool _searchLoading = false;
bool _searchWaiting = false;
base::unique_qptr<Ui::PopupMenu> _menu;

View File

@@ -10,7 +10,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_folder.h"
#include "data/data_forum_topic.h"
#include "data/data_saved_sublist.h"
#include "dialogs/ui/chat_search_tabs.h"
#include "dialogs/ui/chat_search_in.h"
#include "history/history.h"
namespace Dialogs {

View File

@@ -9,7 +9,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "base/qt/qt_key_modifiers.h"
#include "base/options.h"
#include "dialogs/ui/chat_search_tabs.h"
#include "dialogs/ui/chat_search_in.h"
#include "dialogs/ui/dialogs_stories_content.h"
#include "dialogs/ui/dialogs_stories_list.h"
#include "dialogs/ui/dialogs_suggestions.h"
@@ -95,6 +95,7 @@ namespace {
constexpr auto kSearchPerPage = 50;
constexpr auto kStoriesExpandDuration = crl::time(200);
constexpr auto kSearchRequestDelay = crl::time(900);
base::options::toggle OptionForumHideChatsList({
.id = kOptionForumHideChatsList,
@@ -324,9 +325,9 @@ Widget::Widget(
_scroll->scrollToY(st + _inner->st()->height);
}
}, lifetime());
_inner->searchMessages(
) | rpl::start_with_next([=] {
searchRequested();
_inner->searchRequests(
) | rpl::start_with_next([=](SearchRequestDelay delay) {
searchRequested(delay);
}, lifetime());
_inner->completeHashtagRequests(
) | rpl::start_with_next([=](const QString &tag) {
@@ -336,7 +337,23 @@ Widget::Widget(
) | rpl::start_with_next([=] {
searchCursorMoved();
}, lifetime());
_inner->cancelSearchFromUserRequests(
_inner->changeSearchTabRequests(
) | rpl::filter([=](ChatSearchTab tab) {
return _searchState.tab != tab;
}) | rpl::start_with_next([=](ChatSearchTab tab) {
auto copy = _searchState;
copy.tab = tab;
applySearchState(std::move(copy));
}, lifetime());
_inner->cancelSearchRequests(
) | rpl::start_with_next([=] {
cancelSearch({
.forceFullCancel = true,
.jumpBackToSearchedChat = true,
});
controller->widget()->setInnerFocus();
}, lifetime());
_inner->cancelSearchFromRequests(
) | rpl::start_with_next([=] {
auto copy = _searchState;
copy.fromPeer = nullptr;
@@ -345,10 +362,9 @@ Widget::Widget(
}
applySearchState(std::move(copy));
}, lifetime());
_inner->cancelSearchRequests(
_inner->changeSearchFromRequests(
) | rpl::start_with_next([=] {
setInnerFocus(true);
applySearchState({});
showSearchFrom();
}, lifetime());
_inner->chosenRow(
) | rpl::start_with_next([=](const ChosenRow &row) {
@@ -406,7 +422,9 @@ Widget::Widget(
}, lifetime());
}
_cancelSearch->setClickedCallback([this] { cancelSearch(); });
_cancelSearch->setClickedCallback([this] {
cancelSearch({ .jumpBackToSearchedChat = true });
});
_jumpToDate->entity()->setClickedCallback([this] { showCalendar(); });
_chooseFromUser->entity()->setClickedCallback([this] { showSearchFrom(); });
rpl::single(rpl::empty) | rpl::then(
@@ -544,6 +562,8 @@ void Widget::chosenRow(const ChosenRow &row) {
if (topicJump) {
if (controller()->shownForum().current() == topicJump->forum()) {
controller()->closeForum();
} else if (row.newWindow) {
controller()->showInNewWindow(Window::SeparateId(topicJump));
} else {
if (!controller()->adaptive().isOneColumn()) {
controller()->showForum(
@@ -557,11 +577,17 @@ void Widget::chosenRow(const ChosenRow &row) {
}
return;
} else if (const auto topic = row.key.topic()) {
session().data().saveViewAsMessages(topic->forum(), false);
controller()->showThread(
topic,
row.message.fullId.msg,
Window::SectionShow::Way::ClearStack);
if (row.newWindow) {
controller()->showInNewWindow(
Window::SeparateId(topic),
row.message.fullId.msg);
} else {
session().data().saveViewAsMessages(topic->forum(), false);
controller()->showThread(
topic,
row.message.fullId.msg,
Window::SectionShow::Way::ClearStack);
}
} else if (history
&& row.userpicClick
&& (row.message.fullId.msg == ShowAtUnreadMsgId)
@@ -577,16 +603,19 @@ void Widget::chosenRow(const ChosenRow &row) {
const auto forum = history->peer->forum();
if (controller()->shownForum().current() == forum) {
controller()->closeForum();
return;
}
controller()->showForum(
forum,
Window::SectionShow().withChildColumn());
if (forum->channel()->viewForumAsMessages()) {
controller()->showThread(
history,
ShowAtUnreadMsgId,
Window::SectionShow::Way::ClearStack);
} else if (row.newWindow) {
controller()->showInNewWindow(
Window::SeparateId(Window::SeparateType::Forum, history));
} else {
controller()->showForum(
forum,
Window::SectionShow().withChildColumn());
if (forum->channel()->viewForumAsMessages()) {
controller()->showThread(
history,
ShowAtUnreadMsgId,
Window::SectionShow::Way::ClearStack);
}
}
return;
} else if (history) {
@@ -612,6 +641,12 @@ void Widget::chosenRow(const ChosenRow &row) {
return;
}
}
if (row.newWindow) {
controller()->showInNewWindow(Window::SeparateId(
Window::SeparateType::Archive,
&session()));
return;
}
controller()->openFolder(folder);
hideChildList();
}
@@ -675,7 +710,7 @@ void Widget::setupMoreChatsBar() {
controller()->activeChatsFilter(
) | rpl::start_with_next([=](FilterId id) {
storiesToggleExplicitExpand(false);
const auto cancelled = cancelSearch(true);
const auto cancelled = cancelSearch({ .forceFullCancel = true });
const auto guard = gsl::finally([&] {
if (cancelled) {
controller()->content()->dialogsCancelled();
@@ -878,13 +913,15 @@ void Widget::setupStories() {
Core::App().settings().setStoriesClickTooltipHidden(true);
Core::App().saveSettingsDelayed();
};
_stories->setShowTooltip(
parentWidget(),
rpl::combine(
Core::App().settings().storiesClickTooltipHiddenValue(),
shownValue(),
!rpl::mappers::_1 && rpl::mappers::_2),
hideTooltip);
InvokeQueued(_stories.get(), [=] {
_stories->setShowTooltip(
controller()->content(),
rpl::combine(
Core::App().settings().storiesClickTooltipHiddenValue(),
shownValue(),
!rpl::mappers::_1 && rpl::mappers::_2),
hideTooltip);
});
}
_storiesContents.fire(Stories::ContentForSession(
@@ -1096,9 +1133,6 @@ void Widget::updateControlsVisibility(bool fast) {
updateJumpToDateVisibility(fast);
updateSearchFromVisibility(fast);
}
if (_searchTabs) {
_searchTabs->show();
}
if (_connecting) {
_connecting->setForceHidden(false);
}
@@ -1153,7 +1187,7 @@ bool Widget::cancelSearchByMouseBack() {
return _searchHasFocus
&& !_searchSuggestionsLocked
&& !_searchState.inChat
&& cancelSearch();
&& cancelSearch({ .jumpBackToSearchedChat = true });
}
void Widget::processSearchFocusChange() {
@@ -1242,102 +1276,6 @@ void Widget::updateSuggestions(anim::type animated) {
}
}
void Widget::updateSearchTabs() {
const auto has = _searchState.inChat || _searchingHashtag;
if (!has) {
if (_searchTabs) {
_searchTabs = nullptr;
updateControlsGeometry();
}
return;
} else if (!_searchTabs) {
const auto savedSession = &session();
const auto markedTextContext = [=](Fn<void()> repaint) {
return Core::MarkedTextContext{
.session = savedSession,
.customEmojiRepaint = std::move(repaint),
};
};
_searchTabs = std::make_unique<ChatSearchTabs>(
this,
_searchState.tab,
std::move(markedTextContext));
_searchTabs->setVisible(!_showAnimation);
_searchTabs->tabChanges(
) | rpl::filter([=](ChatSearchTab tab) {
return (_searchState.tab != tab);
}) | rpl::start_with_next([=](ChatSearchTab tab) {
auto copy = _searchState;
copy.tab = tab;
applySearchState(std::move(copy));
}, _searchTabs->lifetime());
}
const auto sublist = _searchState.inChat.sublist();
const auto topic = _searchState.inChat.topic();
const auto peer = _searchState.inChat.owningHistory()
? _searchState.inChat.owningHistory()->peer.get()
: _openedForum
? _openedForum->channel().get()
: nullptr;
const auto topicShortLabel = !topic
? TextWithEntities()
: topic->iconId()
? Ui::Text::SingleCustomEmoji(
Data::SerializeCustomEmojiId(topic->iconId()))
: Ui::Text::SingleCustomEmoji(Data::TopicIconEmojiEntity({
.title = (topic->isGeneral()
? Data::ForumGeneralIconTitle()
: topic->title()),
.colorId = (topic->isGeneral()
? Data::ForumGeneralIconColor(st::windowSubTextFg->c)
: topic->colorId()),
}));
const auto peerShortLabel = peer
? Ui::Text::SingleCustomEmoji(
session().data().customEmojiManager().peerUserpicEmojiData(
peer,
{},
true))
: sublist
? Ui::Text::SingleCustomEmoji(
session().data().customEmojiManager().peerUserpicEmojiData(
sublist->peer(),
{},
true))
: TextWithEntities();
const auto myShortLabel = DefaultShortLabel(ChatSearchTab::MyMessages);
const auto publicShortLabel = _searchingHashtag
? DefaultShortLabel(ChatSearchTab::PublicPosts)
: TextWithEntities();
if ((_searchState.tab == ChatSearchTab::ThisTopic
&& !_searchState.inChat.topic())
|| (_searchState.tab == ChatSearchTab::ThisPeer
&& !_searchState.inChat
&& !_openedForum)
|| (_searchState.tab == ChatSearchTab::PublicPosts
&& !_searchingHashtag)) {
_searchState.tab = _searchState.inChat.topic()
? ChatSearchTab::ThisTopic
: (_searchState.inChat.owningHistory()
|| _searchState.inChat.sublist())
? ChatSearchTab::ThisPeer
: ChatSearchTab::MyMessages;
}
const auto peerTabType = (peer && peer->isBroadcast())
? ChatSearchPeerTabType::Channel
: (peer && (peer->isChat() || peer->isMegagroup()))
? ChatSearchPeerTabType::Group
: ChatSearchPeerTabType::Chat;
_searchTabs->setTabShortLabels({
{ ChatSearchTab::ThisTopic, topicShortLabel },
{ ChatSearchTab::ThisPeer, peerShortLabel },
{ ChatSearchTab::MyMessages, myShortLabel },
{ ChatSearchTab::PublicPosts, publicShortLabel },
}, _searchState.tab, peerTabType);
updateControlsGeometry();
}
void Widget::changeOpenedSubsection(
FnMut<void()> change,
bool fromRight,
@@ -1388,7 +1326,7 @@ void Widget::changeOpenedFolder(Data::Folder *folder, anim::type animated) {
return;
}
changeOpenedSubsection([&] {
cancelSearch(true);
cancelSearch({ .forceFullCancel = true });
closeChildList(anim::type::instant);
controller()->closeForum();
_openedFolder = folder;
@@ -1442,7 +1380,7 @@ void Widget::changeOpenedForum(Data::Forum *forum, anim::type animated) {
return;
}
changeOpenedSubsection([&] {
cancelSearch(true);
cancelSearch({ .forceFullCancel = true });
closeChildList(anim::type::instant);
_openedForum = forum;
_searchState.tab = forum
@@ -1925,14 +1863,22 @@ void Widget::slideFinished() {
}
void Widget::escape() {
if (!cancelSearch()) {
if (controller()->shownForum().current()) {
controller()->closeForum();
if (!cancelSearch({ .jumpBackToSearchedChat = true })) {
if (const auto forum = controller()->shownForum().current()) {
const auto id = controller()->windowId();
const auto initial = id.forum();
if (!initial) {
controller()->closeForum();
} else if (initial != forum) {
controller()->showForum(initial);
}
} else if (controller()->openedFolder().current()) {
controller()->closeFolder();
if (!controller()->windowId().folder()) {
controller()->closeFolder();
}
} else if (controller()->activeChatEntryCurrent().key) {
controller()->content()->dialogsCancelled();
} else {
} else if (controller()->isPrimary()) {
const auto filters = &session().data().chatsFilters();
const auto &list = filters->list();
const auto first = list.empty() ? FilterId() : list.front().id();
@@ -2003,7 +1949,7 @@ void Widget::loadMoreBlockedByDate() {
session().api().requestMoreBlockedByDateDialogs();
}
bool Widget::search(bool inCache) {
bool Widget::search(bool inCache, SearchRequestDelay delay) {
_processingSearch = true;
const auto guard = gsl::finally([&] {
_processingSearch = false;
@@ -2032,7 +1978,7 @@ bool Widget::search(bool inCache) {
return true;
} else if (inCache) {
const auto success = _singleMessageSearch.lookup(query, [=] {
searchRequested();
searchRequested(delay);
});
if (!success) {
return false;
@@ -2150,6 +2096,9 @@ bool Widget::search(bool inCache) {
}).send();
_searchQueries.emplace(_searchRequest, _searchQuery);
}
_inner->searchRequested(true);
} else {
_inner->searchRequested(false);
}
const auto peerQuery = Api::ConvertPeerSearchQuery(query);
if (searchForPeersRequired(peerQuery)) {
@@ -2201,20 +2150,25 @@ bool Widget::searchForPeersRequired(const QString &query) const {
return _searchState.filterChatsList()
&& !_openedForum
&& !query.isEmpty()
&& !IsHashtagSearchQuery(query);
&& (IsHashOrCashtagSearchQuery(query) == HashOrCashtag::None);
}
bool Widget::searchForTopicsRequired(const QString &query) const {
return _searchState.filterChatsList()
&& _openedForum
&& !query.isEmpty()
&& !IsHashtagSearchQuery(query)
&& (IsHashOrCashtagSearchQuery(query) == HashOrCashtag::None)
&& !_openedForum->topicsList()->loaded();
}
void Widget::searchRequested() {
if (!search(true)) {
_searchTimer.callOnce(AutoSearchTimeout);
void Widget::searchRequested(SearchRequestDelay delay) {
if (search(true, delay)) {
return;
} else if (delay == SearchRequestDelay::Instant) {
_searchTimer.cancel();
search();
} else {
_searchTimer.callOnce(kSearchRequestDelay);
}
}
@@ -2269,10 +2223,11 @@ void Widget::searchTopics() {
}
void Widget::searchMore() {
if (_searchRequest || _searchInHistoryRequest) {
if (_searchRequest
|| _searchInHistoryRequest
|| _searchTimer.isActive()) {
return;
}
if (!_searchFull) {
} else if (!_searchFull) {
if (const auto peer = searchInPeer()) {
auto &histories = session().data().histories();
const auto topic = searchInTopic();
@@ -2724,17 +2679,19 @@ void Widget::updateCancelSearch() {
QString Widget::validateSearchQuery() {
const auto query = currentSearchQuery();
if (_searchState.tab == ChatSearchTab::PublicPosts) {
_searchingHashtag = true;
if (_searchHashOrCashtag == HashOrCashtag::None) {
_searchHashOrCashtag = HashOrCashtag::Hashtag;
}
const auto fixed = FixHashtagSearchQuery(
query,
currentSearchQueryCursorPosition());
currentSearchQueryCursorPosition(),
_searchHashOrCashtag);
if (fixed.text != query) {
setSearchQuery(fixed.text, fixed.cursorPosition);
}
return fixed.text;
} else if (_searchingHashtag != IsHashtagSearchQuery(query)) {
_searchingHashtag = !_searchingHashtag;
updateSearchTabs();
} else {
_searchHashOrCashtag = IsHashOrCashtagSearchQuery(query);
}
return query;
}
@@ -2772,6 +2729,9 @@ void Widget::updateForceDisplayWide() {
void Widget::showForum(
not_null<Data::Forum*> forum,
const Window::SectionShow &params) {
if (_openedForum == forum) {
return;
}
const auto nochat = !controller()->mainSectionShown();
if (!params.childColumn
|| (Core::App().settings().dialogsWidthRatio(nochat) == 0.)
@@ -2780,7 +2740,7 @@ void Widget::showForum(
changeOpenedForum(forum, params.animated);
return;
}
cancelSearch(true);
cancelSearch({ .forceFullCancel = true });
openChildList(forum, params);
}
@@ -2911,6 +2871,9 @@ bool Widget::applySearchState(SearchState state) {
}
hideChildList();
}
if (state.inChat && _layout == Layout::Main) {
controller()->closeFolder();
}
// Adjust state to be consistent.
if (const auto peer = state.inChat.peer()) {
@@ -2928,11 +2891,12 @@ bool Widget::applySearchState(SearchState state) {
state.fromPeer = nullptr;
}
if (state.tab == ChatSearchTab::PublicPosts
&& !IsHashtagSearchQuery(state.query)) {
&& IsHashOrCashtagSearchQuery(state.query) == HashOrCashtag::None) {
state.tab = (_openedForum && !state.inChat)
? ChatSearchTab::ThisPeer
: ChatSearchTab::MyMessages;
} else if (!state.inChat && !_searchTabs) {
} else if (!state.inChat
&& _searchHashOrCashtag == HashOrCashtag::None) {
state.tab = (forum || _openedForum)
? ChatSearchTab::ThisPeer
: ChatSearchTab::MyMessages;
@@ -2966,6 +2930,20 @@ bool Widget::applySearchState(SearchState state) {
return false;
}
if ((state.tab == ChatSearchTab::ThisTopic
&& !state.inChat.topic())
|| (state.tab == ChatSearchTab::ThisPeer
&& !state.inChat
&& !_openedForum)
|| (state.tab == ChatSearchTab::PublicPosts
&& _searchHashOrCashtag == HashOrCashtag::None)) {
state.tab = state.inChat.topic()
? ChatSearchTab::ThisTopic
: (state.inChat.owningHistory() || state.inChat.sublist())
? ChatSearchTab::ThisPeer
: ChatSearchTab::MyMessages;
}
const auto migrateFrom = (peer && !topic)
? peer->migrateFrom()
: nullptr;
@@ -2979,7 +2957,6 @@ bool Widget::applySearchState(SearchState state) {
}
if (inChatChanged) {
controller()->setSearchInChat(_searchState.inChat);
updateSearchTabs();
}
if (queryChanged || inChatChanged) {
updateCancelSearch();
@@ -3004,10 +2981,6 @@ bool Widget::applySearchState(SearchState state) {
_peerSearchQuery = QString();
}
if (_searchState.inChat && _layout == Layout::Main) {
controller()->closeFolder();
}
if (_searchState.query != currentSearchQuery()) {
setSearchQuery(_searchState.query);
}
@@ -3334,9 +3307,6 @@ void Widget::updateControlsGeometry() {
if (_forumRequestsBar) {
_forumRequestsBar->resizeToWidth(barw);
}
if (_searchTabs) {
_searchTabs->resizeToWidth(barw);
}
_updateScrollGeometryCached = [=] {
const auto moreChatsBarTop = expandedStoriesTop
+ ((!_stories || _stories->isHidden()) ? 0 : _aboveScrollAdded);
@@ -3358,13 +3328,8 @@ void Widget::updateControlsGeometry() {
if (_forumReportBar) {
_forumReportBar->bar().move(0, forumReportTop);
}
const auto searchTabsTop = forumReportTop
const auto scrollTop = forumReportTop
+ (_forumReportBar ? _forumReportBar->bar().height() : 0);
if (_searchTabs) {
_searchTabs->move(0, searchTabsTop);
}
const auto scrollTop = searchTabsTop
+ (_searchTabs ? _searchTabs->height() : 0);
const auto scrollHeight = height() - scrollTop - bottomSkip;
const auto wasScrollHeight = _scroll->height();
_scroll->setGeometry(0, scrollTop, scrollWidth, scrollHeight);
@@ -3656,10 +3621,11 @@ void Widget::setSearchQuery(const QString &query, int cursorPosition) {
}
}
bool Widget::cancelSearch(bool forceFullCancel) {
bool Widget::cancelSearch(CancelSearchOptions options) {
cancelSearchRequest();
auto updatedState = _searchState;
const auto clearingQuery = !updatedState.query.isEmpty();
const auto forceFullCancel = options.forceFullCancel;
auto clearingInChat = (forceFullCancel || !clearingQuery)
&& (updatedState.inChat
|| updatedState.fromPeer
@@ -3668,7 +3634,9 @@ bool Widget::cancelSearch(bool forceFullCancel) {
updatedState.query = QString();
}
if (clearingInChat) {
if (updatedState.inChat && controller()->adaptive().isOneColumn()) {
if (options.jumpBackToSearchedChat
&& updatedState.inChat
&& controller()->adaptive().isOneColumn()) {
if (const auto thread = updatedState.inChat.thread()) {
controller()->showThread(thread);
} else {
@@ -3700,7 +3668,11 @@ bool Widget::cancelSearch(bool forceFullCancel) {
_inner->clearFilter();
applySearchState(std::move(updatedState));
if (_suggestions && clearSearchFocus) {
const auto clearLockedFocus = !_searchHasFocus;
setInnerFocus(true);
if (clearLockedFocus) {
processSearchFocusChange();
}
}
updateForceDisplayWide();
return clearingQuery || clearingInChat || clearSearchFocus;

View File

@@ -57,6 +57,7 @@ namespace Window {
class SessionController;
class ConnectionState;
struct SectionShow;
struct SeparateId;
} // namespace Window
namespace Dialogs::Stories {
@@ -74,10 +75,12 @@ class FakeRow;
class Key;
struct ChosenRow;
class InnerWidget;
enum class SearchRequestType;
enum class SearchRequestType : uchar;
enum class SearchRequestDelay : uchar;
class Suggestions;
class ChatSearchTabs;
class ChatSearchIn;
enum class ChatSearchTab : uchar;
enum class HashOrCashtag : uchar;
class Widget final : public Window::AbstractSectionWidget {
public:
@@ -131,7 +134,6 @@ public:
bool floatPlayerHandleWheelEvent(QEvent *e) override;
QRect floatPlayerAvailableRect() override;
bool cancelSearch(bool forceFullCancel = false);
bool cancelSearchByMouseBack();
QVariant inputMethodQuery(Qt::InputMethodQuery query) const override;
@@ -157,8 +159,8 @@ private:
[[nodiscard]] QString currentSearchQuery() const;
[[nodiscard]] int currentSearchQueryCursorPosition() const;
void clearSearchField();
void searchRequested();
bool search(bool inCache = false);
void searchRequested(SearchRequestDelay delay);
bool search(bool inCache = false, SearchRequestDelay after = {});
void searchTopics();
void searchMore();
@@ -255,13 +257,18 @@ private:
void updateScrollUpPosition();
void updateLockUnlockPosition();
void updateSuggestions(anim::type animated);
void updateSearchTabs();
void processSearchFocusChange();
[[nodiscard]] bool redirectToSearchPossible() const;
[[nodiscard]] bool redirectKeyToSearch(QKeyEvent *e) const;
[[nodiscard]] bool redirectImeToSearch() const;
struct CancelSearchOptions {
bool forceFullCancel = false;
bool jumpBackToSearchedChat = false;
};
bool cancelSearch(CancelSearchOptions options);
MTP::Sender _api;
bool _dragInScroll = false;
@@ -294,7 +301,6 @@ private:
QPointer<InnerWidget> _inner;
std::unique_ptr<Suggestions> _suggestions;
std::vector<std::unique_ptr<Suggestions>> _hidingSuggestions;
std::unique_ptr<ChatSearchTabs> _searchTabs;
class BottomButton;
object_ptr<BottomButton> _updateTelegram = { nullptr };
object_ptr<BottomButton> _loadMoreChats = { nullptr };
@@ -310,7 +316,7 @@ private:
object_ptr<Ui::JumpDownButton> _scrollToTop;
bool _scrollToTopIsShown = false;
bool _forumSearchRequested = false;
bool _searchingHashtag = false;
HashOrCashtag _searchHashOrCashtag = {};
Data::Folder *_openedFolder = nullptr;
Data::Forum *_openedForum = nullptr;

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