Compare commits

...

153 Commits

Author SHA1 Message Date
John Preston
4505a2bf2d Beta version 5.9.2.
- Fix some jump-to-message highlightings.
- Fix some long round video messages sending.
- Fix possible crash in global photos / videos.
- Fix streaming starting for some large video files.
- Fix possible drop of top peers / recent chats cache.
- Fix possible crashes in audio on macOS (rollback OpenAL).
2024-12-24 10:44:15 +04:00
John Preston
a314380b08 Rollback OpenAL on macOS. 2024-12-23 22:51:33 +04:00
John Preston
0c07a015c6 Always show VIEW DISCUSSION from comments. 2024-12-23 22:46:43 +04:00
John Preston
a0c7697280 Fix affiliate program ending confirmation box.
Fixes #28780.
2024-12-23 22:46:42 +04:00
John Preston
04023da723 Highlight search query part in separate window chat. 2024-12-23 21:29:40 +04:00
John Preston
13ea045055 Fix collision of dice_sticker last frames. 2024-12-23 21:29:40 +04:00
John Preston
f03351d112 Allow filter gifts by "In Stock". 2024-12-23 21:29:40 +04:00
John Preston
48d9f10f5b Improve message part highlighting. 2024-12-23 21:29:39 +04:00
John Preston
2b53df98cd Fix long message parts highlighting in topics. 2024-12-23 21:29:34 +04:00
John Preston
99a7a13218 Always send video messages as not "big" files. 2024-12-23 21:27:12 +04:00
John Preston
2d2d4ac002 Reduce video message bitrate so they always fit. 2024-12-23 21:27:12 +04:00
John Preston
d12e8023e3 Fix scheduled replies loading. 2024-12-23 21:27:12 +04:00
John Preston
17181cee8f Fix possible crash in Forward folders switch. 2024-12-23 21:27:12 +04:00
23rd
a74ee911b3 Removed chats filters strip from forward box while search. 2024-12-23 11:02:09 +04:00
Ilya Fedin
b0b37172ce Fix the naming of TitleControlsLayout static methods 2024-12-23 11:01:35 +04:00
Pavel Zolotarevskiy
cccf048e3f Don't export a duplicate "text" field on star gift
Fixes #28781
2024-12-21 22:32:12 +04:00
John Preston
82e890746b Fix possible crash in global media search. 2024-12-20 21:07:20 +04:00
John Preston
188d65d700 Improve streaming of large files. 2024-12-20 21:07:20 +04:00
John Preston
4569f93e70 Fix username disappearance in My Profile. 2024-12-20 21:07:20 +04:00
23rd
bcd1d8461f Added ability to open context menu for active account from main menu. 2024-12-20 19:33:06 +03:00
23rd
183a9139f9 Moved out ability to mark as read all chats from hidden shortcut. 2024-12-20 19:33:06 +03:00
23rd
80a1e6ecf3 Fixed ability to mark as read all chats for wrong account. 2024-12-20 19:33:06 +03:00
23rd
aa1f8cfb8f Fixed display at least one injected sponsored message. 2024-12-20 15:53:43 +03:00
23rd
8060691f3d Fixed preview of chats filters in filter link box when window is small. 2024-12-19 15:54:26 +03:00
Ilya Fedin
bf26de495a Adapt to TitleControlsLayout change 2024-12-19 15:59:25 +04:00
Ilya Fedin
73b3f7e298 Adapt to TitleControlsOnLeft change 2024-12-19 15:59:25 +04:00
Ilya Fedin
22191649aa Update lib_base and lib_ui 2024-12-19 15:59:25 +04:00
bitxer
0557907310 Enhance experimental setting description 2024-12-19 15:49:24 +04:00
bitxer
0f283c484d Added experimental settings to prefer ipv6 when it is available 2024-12-19 15:49:24 +04:00
John Preston
e33ca9d316 Fix top/recent peers cache write error. 2024-12-19 13:51:47 +04:00
John Preston
f93f4c72f7 Beta version 5.9.1.
- Add global media overview tabs in chats search.
- Add main menu "My Profile" with my stories and gifts access.
- Highlight some of search query on result message open.
- Fix highlighting quotes from bottom parts of long messages.
- Allow forward and reply bars together.
- Make gift price categories scrollable.
- Auto-send deep-link /start in existing bot chats.
2024-12-18 19:12:12 +04:00
23rd
f0b9bc10c2 Added fade effect to price categories in star gift box. 2024-12-18 17:39:46 +03:00
John Preston
f583879aee Update OpenAL to 1.24.1 on Windows/macOS. 2024-12-18 18:22:54 +04:00
23rd
7ea6c6c84b Fixed width of username label with button for QR in profiles. 2024-12-18 16:21:07 +03:00
23rd
2532a0ff59 Moved out to single place ministars creation in top of box. 2024-12-18 15:47:57 +03:00
23rd
c3f354826d Fixed text color of custom icon in some phrases with links. 2024-12-18 15:01:57 +03:00
23rd
6d1e421ad7 Fixed color of strikeout format when spellcheck underline is present. 2024-12-18 11:54:26 +03:00
23rd
29b0055e39 Slightly improved some phrases on error while add participant to chat. 2024-12-18 11:03:44 +03:00
Ilya Fedin
2cb20fe342 Switch macOS packaged action to latest ffmpeg 2024-12-18 10:53:43 +04:00
Ilya Fedin
fc97fa4415 Fix snap action 2024-12-18 09:56:23 +04:00
Ilya Fedin
876a50f759 Update OpenAL to 1.24.1 on Linux 2024-12-18 09:55:58 +04:00
Ilya Fedin
eb0d2868f5 Expand "always run in background" behavior from GNOME/Pantheon to all Linux
Right now it checks the title controls layout that is typically set only by gtk based DEs and KDE matching the GNOME's and Pantheon's defaults.

There are more and more reports about window manager not to supporting both tray and minimization out of the box and title controls layout seem to typically be either unset or set to nothing leaving users with no way to run tdesktop with no window open.
2024-12-18 09:55:35 +04:00
John Preston
e215d5bc64 Fix build with Xcode. 2024-12-17 21:27:08 +04:00
John Preston
3f0d687656 Add cache for global media search requests. 2024-12-17 21:17:14 +04:00
John Preston
d59eb8e731 Support global media in chats search. 2024-12-17 21:17:14 +04:00
John Preston
04e9eed88d Reuse filters slider scroll for search. 2024-12-17 21:17:14 +04:00
John Preston
5072e95f16 Support folders strip touch-screen scroll. 2024-12-17 21:17:14 +04:00
John Preston
a2b8366477 Show forward original date in context menu. 2024-12-17 21:17:14 +04:00
John Preston
e9a6bee046 Fix sending old topic messages. 2024-12-17 21:13:29 +04:00
John Preston
080a8d7ee5 Fix "Open" miniapp button antialiasing. 2024-12-17 21:13:29 +04:00
John Preston
f94fd3118b Add "My Profile" instead of "My Stories". 2024-12-17 21:13:29 +04:00
John Preston
8ddb13d6e2 Show verified/premium badge in chat preview. 2024-12-17 21:13:29 +04:00
John Preston
c6cf8be8d4 Redesign gift visibility toggle. 2024-12-17 21:13:29 +04:00
John Preston
e92270a9ab Add "View Discussion" button to third column. 2024-12-17 21:13:29 +04:00
John Preston
65d6636a41 Add special toast title for anonymous stars. 2024-12-17 21:13:29 +04:00
John Preston
4701badb2a Highlight text in bottom of a long bubble. 2024-12-17 21:13:29 +04:00
John Preston
3565215c81 Highlight word from search query. 2024-12-17 21:13:29 +04:00
John Preston
3957fea5e4 Send start in bots auto in existing bot chats. 2024-12-17 21:13:29 +04:00
John Preston
10f1ae152d Fix crash in sending games from inline bots.
Regression was introduced in 2d1fb0562d.
2024-12-17 21:13:29 +04:00
John Preston
eb29b6bffe Allow forward+reply, options in single box. 2024-12-17 21:13:29 +04:00
John Preston
d157eb0b6e Enter selects from-row in reply-in-another-chat. 2024-12-17 21:13:29 +04:00
John Preston
0045eb4598 Make price categories scrollable. 2024-12-17 21:13:27 +04:00
23rd
b61c66c385 Fixed display of title widgets in separate panel while show animation. 2024-12-16 06:46:36 +03:00
23rd
56d6c4eb30 Fixed mouse track on right button for bots when there is unread badge. 2024-12-16 05:45:56 +03:00
23rd
a6030d708d Fixed display of unread state in chats filters strip after reorder. 2024-12-16 05:45:56 +03:00
23rd
683c3c4f36 Fixed Escape hotkey in info sections with search field. 2024-12-16 05:45:56 +03:00
23rd
bd084f9181 Fixed blink of submenu in profile section on section destroy. 2024-12-16 05:45:56 +03:00
23rd
fef133bf0a Fixed incorrect action type of userpic change with image from clipboard.
Fixed #28731.
2024-12-16 05:45:56 +03:00
23rd
15c226e6cf Removed confusing lock state from button in earn out section. 2024-12-16 05:45:56 +03:00
23rd
84f111d641 Fixed unreachable bottom button from contact media in some cases. 2024-12-16 05:45:55 +03:00
23rd
18c1e7ac60 Removed animation cache from dialogs widget on instant clear search. 2024-12-16 05:45:55 +03:00
Daniel Novomeský
ee6dbdced6 Update libjxl and libheif on Linux 2024-12-14 17:50:51 +04:00
Daniel Novomeský
cb443d797d Update kimageformats submodule 2024-12-14 17:50:51 +04:00
Daniel Novomeský
5a6497ec70 Upgrade libheif to 1.18.2, upgrade libjxl to 0.11.1 2024-12-14 17:50:51 +04:00
Andrey Egorov
893ca8bcbd Residence country instead of Citizenship 2024-12-06 18:29:17 +04:00
John Preston
f91e4c8b69 Version 5.9.
- Affiliate programs for bots.
- Add option to show folder tags in chats list.
2024-12-04 19:03:17 +04:00
Ilya Fedin
f7c777d07d Add branding colors to metainfo 2024-12-04 19:03:11 +04:00
John Preston
12a8e8616c Fix giveaway sticker badge. 2024-12-04 13:51:20 +04:00
John Preston
2fbf7e8504 Fix build with Xcode. 2024-12-04 13:51:20 +04:00
John Preston
6864e6d5bf Improve phrase for revoked starref link. 2024-12-04 13:51:20 +04:00
John Preston
09b4e0e21b Use nice format for numbers. 2024-12-04 13:51:20 +04:00
John Preston
f381005184 Add "New" badge for affiliate programs. 2024-12-04 13:51:20 +04:00
John Preston
42a2de4bf0 Add a NEW badge to "Earn Stars". 2024-12-04 13:51:20 +04:00
John Preston
1fd1e34844 Add link icon to connected starref programs. 2024-12-04 13:51:20 +04:00
John Preston
64dbbd7d09 Green badges for commissions. 2024-12-04 13:51:20 +04:00
John Preston
c137e577dc Allow sorting suggested starref programs. 2024-12-04 13:51:20 +04:00
John Preston
f592a9202f Improve arrow down in choose recipient box. 2024-12-04 13:51:20 +04:00
John Preston
cdc24d2e57 Fix states in joined/suggested lists. 2024-12-04 13:51:20 +04:00
John Preston
b8bf3f6520 Allow changing the recipients. 2024-12-04 13:51:20 +04:00
John Preston
82cec83d87 "Add {bot}" button in existing starrefs list. 2024-12-04 13:51:20 +04:00
John Preston
1e14667006 Add affiliate program point to bot info. 2024-12-04 13:51:20 +04:00
John Preston
6539d14852 Create/Update confirm box for the starref. 2024-12-04 13:51:20 +04:00
John Preston
400df0f980 Add referral link preview to the box. 2024-12-04 13:51:19 +04:00
John Preston
4cafb3f966 Make nicer footer in starref boxes. 2024-12-04 13:51:19 +04:00
John Preston
d8892c4eb4 Nice referral link icon in the box. 2024-12-04 13:51:19 +04:00
John Preston
1ebd25e76e Start/Update/End toasts for starref programs. 2024-12-04 13:51:19 +04:00
John Preston
ca89aa8377 Confirm box for revoke link / end program. 2024-12-04 13:51:19 +04:00
John Preston
ad6272bfe5 Show correct error on stopped starref. 2024-12-04 13:51:19 +04:00
John Preston
b401c37c39 Correctly show commission in stars stats. 2024-12-04 13:51:19 +04:00
John Preston
552dd318cd Implement nice starref start button. 2024-12-04 13:51:19 +04:00
John Preston
a97880132a Respect appconfig starref restrictions. 2024-12-04 13:51:19 +04:00
John Preston
89058c63c8 Check appconfig start ref prefixes. 2024-12-04 13:51:19 +04:00
John Preston
747e417809 Apply connected programs in realtime. 2024-12-04 13:51:19 +04:00
John Preston
fd26e1618c Add "Affiliate programs" to Manage Channel. 2024-12-04 13:51:19 +04:00
John Preston
86ea760011 Show list of programs in View Existing. 2024-12-04 13:51:19 +04:00
John Preston
824237deb3 Nice starref link box. 2024-12-04 13:51:19 +04:00
John Preston
ca8c70cc95 Fix creating default starref program. 2024-12-04 13:51:19 +04:00
John Preston
46fcc695a5 Add starref entry point to my/bot stars page. 2024-12-04 13:51:19 +04:00
John Preston
0e866a0266 Nice starref join confirmation box. 2024-12-04 13:51:19 +04:00
John Preston
63c36f5907 Implement nice limited duration slider. 2024-12-04 13:51:19 +04:00
John Preston
5299500d78 Implement nice commission slider. 2024-12-04 13:51:19 +04:00
John Preston
3b3d1aa9cc Update some icons for starref sections. 2024-12-04 13:51:19 +04:00
John Preston
62d2346471 Initial starref programs list implementation. 2024-12-04 13:51:19 +04:00
John Preston
1e15764bb9 Initial starref setup section implementation. 2024-12-04 13:51:19 +04:00
John Preston
a6bfd35f1a Add phrases for star referrals. 2024-12-04 13:51:18 +04:00
John Preston
3296efe46b Use correct format for double formatting. 2024-12-04 13:51:18 +04:00
John Preston
51ddfbc340 Update API scheme to layer 195. 2024-12-04 13:51:18 +04:00
23rd
42142d819a Added support of media with spoiler to export to JSON. 2024-12-04 11:48:07 +03:00
23rd
bbf9d523a6 Fixed incorrect calculation of title width in sponsored messages. 2024-12-04 11:48:07 +03:00
23rd
721877e10a Removed redundant special tab mode from PeerListBox. 2024-12-04 11:48:07 +03:00
23rd
a9e95a128f Fixed display of filters tabs on some cases of first activating. 2024-12-04 11:48:07 +03:00
John Preston
2b920eaa87 Use new Vazirmatn repository URL.
Co-authored-by: ilya-fedin <fedin-ilja2010@ya.ru>
2024-12-04 10:21:37 +04:00
Amir Hossein "Amiria" Maher
a3ec759e62 Update from 'vazir' to 'vazirmatn' 2024-12-04 10:21:37 +04:00
Ilya Fedin
3661442acd Add more screenshots to metainfo 2024-12-04 10:14:58 +04:00
Ilya Fedin
f232d329c5 Add transparency to the preview image 2024-12-04 10:14:18 +04:00
Ilya Fedin
ac5cf3bd80 Update summary in metainfo and comment in desktop file 2024-12-04 10:11:39 +04:00
Ilya Fedin
ac13ac7a2c Remove "Desktop" from application name on Linux 2024-12-04 10:08:04 +04:00
John Preston
0bccb35cb0 Use "Open" non-upper-case. 2024-12-03 17:26:46 +04:00
23rd
18d9484ab1 Removed Ui::show from LocalStorageBox. 2024-12-03 17:26:46 +04:00
23rd
fe7c06bc84 Added Enter shortcut to box for adding or removing of shared filter. 2024-12-03 17:26:46 +04:00
23rd
b6fb3bbf1d Fixed build of shortcuts settings included with another Session declare. 2024-12-03 17:26:46 +04:00
23rd
927d7a3aeb Renamed sessions_box to settings_active_sessions. 2024-12-03 17:26:46 +04:00
23rd
979973745b Fixed build of notifications type included with another Session declare. 2024-12-03 17:26:46 +04:00
23rd
afab863f11 Fixed non-closed last tag for inline buttons in HTML export. 2024-12-03 17:26:46 +04:00
23rd
168162c174 Fixed color of float button from sponsored message bar in new window. 2024-12-03 17:26:46 +04:00
23rd
2b122087c4 Re-fixed focus capture from compose search widget. 2024-12-03 17:26:46 +04:00
23rd
043d97cfdf Moved out SearchFieldController to td_ui. 2024-12-03 17:26:46 +04:00
GitHub Action
794818953d Update User-Agent for DNS to Chrome 131.0.0.0. 2024-12-03 17:22:11 +04:00
Ilya Fedin
783570fe9f Update Qt 6.8.0 -> 6.8.1 2024-12-03 12:15:46 +04:00
John Preston
b1e2a4243e Fix join requests list for legacy groups. 2024-11-29 20:18:31 +04:00
John Preston
b347308137 Show bot app name in title. 2024-11-29 20:18:31 +04:00
John Preston
b3c8a79946 Use langpack-ed about for Verification Codes. 2024-11-29 20:18:31 +04:00
23rd
9822c56f1a Removed display of right button for bots when there is unread badge. 2024-11-29 16:03:42 +03:00
23rd
cdd7ff5c6d Fixed count of current size for non-thumbed media with long bottom info. 2024-11-29 15:56:31 +03:00
23rd
5aba2f25cc Fixed drawing of currency icon with non-default scale in profile. 2024-11-29 15:56:31 +03:00
John Preston
96398daa78 Beta version 5.8.5: Fix build with Xcode. 2024-11-29 11:25:29 +04:00
John Preston
61ceb66415 Beta version 5.8.5.
- Fix pinned chats in folders.
- Fix emoji in folder tags.
- Fix several crashes.
2024-11-29 10:52:14 +04:00
John Preston
b4f173cdb3 Fix possible crash in ads preloading. 2024-11-29 10:33:29 +04:00
John Preston
03e4592082 Fix search in group/channel requests list. 2024-11-29 10:33:29 +04:00
John Preston
cf2dbe50a1 Fix crash in narrow column reactions view. 2024-11-29 10:33:29 +04:00
John Preston
e5bb5b75fe Fix crash in webview teardown on Windows. 2024-11-29 10:33:29 +04:00
23rd
cffce47eb1 Fixed emoji in chats filter tags. 2024-11-29 08:31:49 +03:00
23rd
ca0adba6cf Fixed pinned chats in chats filters.
Regression was introduced in e3465da979.
2024-11-28 22:08:22 +03:00
287 changed files with 9094 additions and 1741 deletions

View File

@@ -69,7 +69,7 @@ jobs:
run: |
brew update
brew upgrade || true
brew install ada-url autoconf automake boost cmake ffmpeg@6 libtool openal-soft openh264 openssl opus ninja pkg-config python qt yasm xz
brew install ada-url autoconf automake boost cmake ffmpeg libtool openal-soft openh264 openssl opus ninja pkg-config python qt yasm xz
sudo xcode-select -s /Applications/Xcode.app/Contents/Developer
xcodebuild -version > CACHE_KEY.txt

View File

@@ -57,14 +57,14 @@ jobs:
sudo iptables -P FORWARD ACCEPT
sudo snap install --classic snapcraft
sudo usermod -aG lxd $USER
sudo snap run lxd init --auto
sudo snap run lxd waitready
sudo lxd init --auto
sudo lxd waitready
- name: Free up some disk space.
uses: jlumbroso/free-disk-space@54081f138730dfa15788a46383842cd2f914a1be
- name: Telegram Desktop snap build.
run: sg lxd -c 'snap run snapcraft --verbosity=debug'
run: sudo -u $USER snap run snapcraft --verbosity=debug
- name: Move artifact.
if: env.UPLOAD_ARTIFACT == 'true'

View File

@@ -58,7 +58,7 @@ Version **1.8.15** was the last that supports older systems
* Guideline Support Library ([MIT License](https://github.com/Microsoft/GSL/blob/master/LICENSE))
* Range-v3 ([Boost License](https://github.com/ericniebler/range-v3/blob/master/LICENSE.txt))
* Open Sans font ([Apache License 2.0](http://www.apache.org/licenses/LICENSE-2.0.html))
* Vazir font ([SIL Open Font License 1.1](https://github.com/rastikerdar/vazir-font/blob/master/OFL.txt))
* Vazirmatn font ([SIL Open Font License 1.1](https://github.com/rastikerdar/vazirmatn/blob/master/OFL.txt))
* Emoji alpha codes ([MIT License](https://github.com/emojione/emojione/blob/master/extras/alpha-codes/LICENSE.md))
* xxHash ([BSD License](https://github.com/Cyan4973/xxHash/blob/dev/LICENSE))
* QR Code generator ([MIT License](https://github.com/nayuki/QR-Code-generator#license))

View File

@@ -322,8 +322,6 @@ PRIVATE
boxes/send_gif_with_caption_box.h
boxes/send_files_box.cpp
boxes/send_files_box.h
boxes/sessions_box.cpp
boxes/sessions_box.h
boxes/share_box.cpp
boxes/share_box.h
boxes/star_gift_box.cpp
@@ -470,6 +468,7 @@ PRIVATE
core/sandbox.h
core/shortcuts.cpp
core/shortcuts.h
core/stars_amount.h
core/ui_integration.cpp
core/ui_integration.h
core/update_checker.cpp
@@ -919,6 +918,12 @@ PRIVATE
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/bot/starref/info_bot_starref_common.cpp
info/bot/starref/info_bot_starref_common.h
info/bot/starref/info_bot_starref_join_widget.cpp
info/bot/starref/info_bot_starref_join_widget.h
info/bot/starref/info_bot_starref_setup_widget.cpp
info/bot/starref/info_bot_starref_setup_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
@@ -941,6 +946,12 @@ PRIVATE
info/downloads/info_downloads_provider.h
info/downloads/info_downloads_widget.cpp
info/downloads/info_downloads_widget.h
info/global_media/info_global_media_widget.cpp
info/global_media/info_global_media_widget.h
info/global_media/info_global_media_inner_widget.cpp
info/global_media/info_global_media_inner_widget.h
info/global_media/info_global_media_provider.cpp
info/global_media/info_global_media_provider.h
info/media/info_media_buttons.h
info/media/info_media_common.cpp
info/media/info_media_common.h
@@ -1405,6 +1416,8 @@ PRIVATE
settings/cloud_password/settings_cloud_password_manage.h
settings/cloud_password/settings_cloud_password_start.cpp
settings/cloud_password/settings_cloud_password_start.h
settings/settings_active_sessions.cpp
settings/settings_active_sessions.h
settings/settings_advanced.cpp
settings/settings_advanced.h
settings/settings_blocked_peers.cpp
@@ -1566,8 +1579,6 @@ PRIVATE
ui/item_text_options.cpp
ui/item_text_options.h
ui/resize_area.h
ui/search_field_controller.cpp
ui/search_field_controller.h
ui/unread_badge.cpp
ui/unread_badge.h
window/main_window.cpp

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 78 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 655 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 820 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 586 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 699 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 718 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 505 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 763 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 963 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 691 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 615 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 517 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 959 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 791 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 654 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

@@ -18,6 +18,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_menu_activate" = "Use this account";
"lng_menu_set_status" = "Set Emoji Status";
"lng_menu_change_status" = "Change Emoji Status";
"lng_menu_my_profile" = "My Profile";
"lng_menu_my_stories" = "My Stories";
"lng_menu_my_groups" = "My Groups";
"lng_menu_my_channels" = "My Channels";
@@ -288,6 +289,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_error_cant_add_member" = "Sorry, you can't add the bot to this group. Ask a group admin to do it.";
"lng_error_cant_add_bot" = "Sorry, this bot can't be added to groups.";
"lng_error_cant_add_admin_invite" = "You can't add this user as an admin because they are not a member of this group and you are not allowed to add them.";
"lng_error_you_blocked_user" = "Sorry, you can't add this user or bot to groups because you've blocked them. Please unblock to proceed.";
"lng_error_add_admin_not_member" = "You can't add this user as an admin because they are not a member of this group and you are not allowed to add them.";
"lng_error_user_admin_invalid" = "You can't ban this user because they are an admin in this group and you are not allowed to demote them.";
"lng_error_channel_bots_too_much" = "Sorry, this channel has too many bots.";
"lng_error_group_bots_too_much" = "There are too many bots in this group. Please remove some of the bots you're not using first.";
"lng_error_cant_add_admin_unban" = "Sorry, you can't add this user as an admin because they are in the Removed Users list and you can't unban them.";
"lng_error_cant_ban_admin" = "You can't ban this user because they are an admin in this group and you are not allowed to demote them.";
"lng_error_cant_reply_other" = "This message can't be replied in another chat.";
@@ -1545,6 +1551,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_manage_channel_title" = "Manage Channel";
"lng_manage_bot_title" = "Manage Bot";
"lng_manage_peer_recent_actions" = "Recent actions";
"lng_manage_peer_star_ref" = "Affiliate programs";
"lng_manage_peer_members" = "Members";
"lng_manage_peer_subscribers" = "Subscribers";
"lng_manage_peer_administrators" = "Administrators";
@@ -1618,11 +1625,99 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_manage_peer_bot_balance" = "Balance";
"lng_manage_peer_bot_balance_currency" = "Toncoin";
"lng_manage_peer_bot_balance_credits" = "Stars";
"lng_manage_peer_bot_star_ref" = "Affiliate Program";
"lng_manage_peer_bot_star_ref_off" = "Off";
"lng_manage_peer_bot_star_ref_about" = "Share a link to {bot} with your friends and earn {amount} of their spending there.";
"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";
"lng_manage_peer_bot_about" = "Use {bot} to manage this bot.";
"lng_star_ref_title" = "Affiliate Program";
"lng_star_ref_about" = "Reward those who help grow your user base.";
"lng_star_ref_share_title" = "Share revenue with affiliates";
"lng_star_ref_share_about" = "Set the commission for revenue generated by users referred to you.";
"lng_star_ref_launch_title" = "Launch your affiliate program";
"lng_star_ref_launch_about" = "Telegram will feature your program for millions of potential affiliates.";
"lng_star_ref_let_title" = "Let affiliate promote you";
"lng_star_ref_let_about" = "Affiliates will share your referral link with their audience.";
"lng_star_ref_commission_title" = "Commission";
"lng_star_ref_commission_about" = "Define the percentage of star revenue your affiliates earn for referring users to your bot.";
"lng_star_ref_duration_title" = "Duration";
"lng_star_ref_duration_about" = "Set the duration for which affiliates will earn commissions from referred users.";
"lng_star_ref_existing_title" = "View existing programs";
"lng_star_ref_existing_about" = "Explore what other mini apps offer.";
"lng_star_ref_add_bot" = "Add {bot}";
"lng_star_ref_end" = "End Affiliate Program";
"lng_star_ref_start" = "Start Affiliate Program";
"lng_star_ref_start_disabled" = "Available in {time}";
"lng_star_ref_start_info" = "By creating an affiliate program, you agree to the {terms} of Affiliate Programs.";
"lng_star_ref_update" = "Update Affiliate Program";
"lng_star_ref_update_info" = "By updating an affiliate program, you agree to the {terms} of Affiliate Programs.";
"lng_star_ref_button_link" = "terms and conditions";
"lng_star_ref_tos_url" = "https://telegram.org/tos/mini-apps";
"lng_star_ref_warning_title" = "Warning";
"lng_star_ref_warning_text" = "Once you start the affiliate program, you won't be able to decrease its commission or duration. You can only increase these parameters or end the program, which will disable all previously distributed referral links.";
"lng_star_ref_warning_change" = "This change is irreversible. You won't be able to reduce commission or duration. You can only increase these parameters or end the program, which will disable all previously shared referral links.";
"lng_star_ref_warning_start" = "Start";
"lng_star_ref_warning_update" = "Update";
"lng_star_ref_warning_if_end" = "If you end your affiliate program:";
"lng_star_ref_warning_if_end1" = "Any referral links already shared will be disabled in **24** hours.";
"lng_star_ref_warning_if_end2" = "All participating affiliates will be notified.";
"lng_star_ref_warning_if_end3" = "You will be able to start a new affiliate program only in **24** hours.";
"lng_star_ref_warning_end" = "End Anyway";
"lng_star_ref_created_title" = "Affiliate program started";
"lng_star_ref_created_text" = "Any Telegram user, channel owner or mini app developer can now join your program.";
"lng_star_ref_updated_title" = "Affiliate program updated";
"lng_star_ref_updated_text" = "Any Telegram user, channel owner or mini app developer can join your program.";
"lng_star_ref_ended_title" = "Affiliate program ended";
"lng_star_ref_ended_text" = "Participating affiliates have been notified. All referral links will be disabled in **24** hours.";
"lng_star_ref_list_title" = "Affiliate Programs";
"lng_star_ref_list_about_channel" = "Promote mini apps to your subscribers and earn a share of their revenue in Stars.";
"lng_star_ref_list_text" = "Earn a commission each time a user who first accessed a mini app through your referral link spends **Stars** within it.";
"lng_star_ref_list_my" = "My Programs";
"lng_star_ref_list_my_open" = "Open App";
"lng_star_ref_list_my_copy" = "Copy Link";
"lng_star_ref_list_my_leave" = "Leave";
"lng_star_ref_list_subtitle" = "Programs";
"lng_star_ref_sort_text" = "Sort by {sort}";
"lng_star_ref_sort_profitability" = "Profitability";
"lng_star_ref_sort_date" = "Date";
"lng_star_ref_sort_revenue" = "Revenue";
"lng_star_ref_reliable_title" = "Reliable";
"lng_star_ref_reliable_about" = "Receive guaranteed commissions for spending by users you refer.";
"lng_star_ref_transparent_title" = "Transparent";
"lng_star_ref_transparent_about" = "Track your commissions from referred users in real time.";
"lng_star_ref_simple_title" = "Simple";
"lng_star_ref_simple_about" = "Choose a mini app below, get your referral link, and start earning Stars.";
"lng_star_ref_duration_forever" = "Forever";
"lng_star_ref_one_about" = "{app} will share {amount} of the revenue from each user you refer to it {duration}.";
"lng_star_ref_one_about_for_forever" = "for **lifetime**";
"lng_star_ref_one_about_for_months#one" = "for **{count} month**";
"lng_star_ref_one_about_for_months#other" = "for **{count} months**";
"lng_star_ref_one_about_for_years#one" = "for **{count} year**";
"lng_star_ref_one_about_for_years#other" = "for **{count} years**";
"lng_star_ref_one_daily_revenue" = "Daily revenue per user: {amount}";
"lng_star_ref_one_join" = "Join Program";
"lng_star_ref_one_join_text" = "By joining this program, you agree to the {terms} of Affiliate Programs.";
"lng_star_ref_joined_title" = "Program joined";
"lng_star_ref_joined_text" = "You can now copy the referral link.";
"lng_star_ref_link_title" = "Referral Link";
"lng_star_ref_link_about_channel" = "Share this link with your subscribers to earn a {amount} commission on their spending in {app} {duration}.";
"lng_star_ref_link_about_user" = "Share this link with your friends to earn a {amount} commission on their spending in {app} {duration}.";
"lng_star_ref_link_about_bot" = "Share this link with your users to earn a {amount} commission on their spending in {app} {duration}.";
"lng_star_ref_link_recipient" = "Commissions will be sent to:";
"lng_star_ref_link_copy" = "Copy Link";
"lng_star_ref_link_copy_none" = "No one have opened {app} through this link.";
"lng_star_ref_link_copy_users#one" = "{count} user have opened {app} through this link.";
"lng_star_ref_link_copy_users#other" = "{count} users have opened {app} through this link.";
"lng_star_ref_link_copied_title" = "Link copied to clipboard";
"lng_star_ref_link_copied_text" = "Share this link and earn {amount} of what people who use it spend in {app}!";
"lng_star_ref_stopped" = "This affiliate link is no longer active.";
"lng_star_ref_revoke_title" = "Revoke Link";
"lng_star_ref_revoke_text" = "Are you sure you want to revoke the link of {bot}?";
"lng_star_ref_revoked_title" = "Link removed";
"lng_star_ref_revoked_text" = "It will no longer work.";
"lng_manage_discussion_group" = "Discussion";
"lng_manage_discussion_group_add" = "Add a group";
@@ -2435,11 +2530,14 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"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_earn_title" = "Earn Stars";
"lng_credits_summary_earn_about" = "Distribute links to mini apps and earn a share of their revenue in Stars.";
"lng_credits_summary_history_tab_full" = "All Transactions";
"lng_credits_summary_history_tab_in" = "Incoming";
"lng_credits_summary_history_tab_out" = "Outgoing";
"lng_credits_summary_history_entry_inner_in" = "In-App Purchase";
"lng_credits_summary_balance" = "Balance";
"lng_credits_commission" = "{amount} commission";
"lng_credits_more_options" = "More Options";
"lng_credits_balance_me" = "your balance";
"lng_credits_buy_button" = "Buy More Stars";
@@ -2502,6 +2600,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_credits_box_history_entry_via_premium_bot" = "Premium Bot";
"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_reason_star_ref" = "Affiliate Program";
"lng_credits_box_history_entry_affiliate" = "Affiliate";
"lng_credits_box_history_entry_miniapp" = "Mini App";
"lng_credits_box_history_entry_referred" = "Referred User";
"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";
@@ -3080,6 +3182,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_gift_stars_sold_out" = "sold out";
"lng_gift_stars_tabs_all" = "All Gifts";
"lng_gift_stars_tabs_limited" = "Limited";
"lng_gift_stars_tabs_in_stock" = "In Stock";
"lng_gift_send_title" = "Send a Gift";
"lng_gift_send_message" = "Enter Message";
"lng_gift_send_anonymous" = "Hide My Name";
@@ -3096,11 +3199,14 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_gift_visible_hint" = "This gift is visible to visitors of your page.";
"lng_gift_availability" = "Availability";
"lng_gift_from_hidden" = "Hidden User";
"lng_gift_visibility" = "Visibility";
"lng_gift_visibility_shown" = "Visible on your page";
"lng_gift_visibility_hidden" = "Not visible on your page";
"lng_gift_visibility_show" = "show";
"lng_gift_visibility_hide" = "hide";
"lng_gift_availability_left#one" = "{count} of {amount} left";
"lng_gift_availability_left#other" = "{count} of {amount} left";
"lng_gift_availability_none" = "None of {amount} left";
"lng_gift_display_on_page" = "Display on my Page";
"lng_gift_display_on_page_hide" = "Hide from my Page";
"lng_gift_convert_to_stars#one" = "Convert to {count} Star";
"lng_gift_convert_to_stars#other" = "Convert to {count} Stars";
"lng_gift_convert_sure_title" = "Convert Gift to Stars";
@@ -3376,6 +3482,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_replies_no_comments" = "No comments here yet...";
"lng_verification_codes" = "Verification Codes";
"lng_verification_codes_about" = "Third-party services, like websites and stores, can send verification codes to your phone number via Telegram instead of SMS. Such codes will appear in this chat.\n\nIf you didn't request any codes — don't worry! Most likely, someone made a mistake when entering their number.";
"lng_archived_name" = "Archived chats";
"lng_archived_add" = "Archive";
@@ -3550,6 +3657,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_context_mark_read_sure" = "Are you sure you want to mark all chats from this folder as read?";
"lng_context_mark_read_all" = "Mark all chats as read";
"lng_context_mark_read_all_sure" = "Are you sure you want to mark all chats as read?";
"lng_context_mark_read_all_sure_2" = "**This action cannot be undone.**";
"lng_context_mark_read_mentions_all" = "Mark all mentions as read";
"lng_context_mark_read_reactions_all" = "Read all reactions";
"lng_context_archive_expand" = "Expand";
@@ -3709,6 +3817,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_paid_react_agree_link" = "Terms of Service";
"lng_paid_react_toast#one" = "Star Sent!";
"lng_paid_react_toast#other" = "Stars Sent!";
"lng_paid_react_toast_anonymous#one" = "Star sent anonymously!";
"lng_paid_react_toast_anonymous#other" = "Stars sent anonymously!";
"lng_paid_react_toast_text#one" = "You reacted with **{count} Star**.";
"lng_paid_react_toast_text#other" = "You reacted with **{count} Stars**.";
"lng_paid_react_undo" = "Undo";
@@ -4815,6 +4925,17 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_forward_show_captions" = "Show captions";
"lng_forward_change_recipient" = "Change recipient";
"lng_forward_sender_names_removed" = "Sender names removed";
"lng_forward_header_short" = "Forward";
"lng_forward_action_show_sender" = "Show Sender Name";
"lng_forward_action_show_senders" = "Show Sender Names";
"lng_forward_action_hide_sender" = "Hide Sender Name";
"lng_forward_action_hide_senders" = "Hide Sender Names";
"lng_forward_action_show_caption" = "Show Caption";
"lng_forward_action_show_captions" = "Show Captions";
"lng_forward_action_hide_caption" = "Hide Caption";
"lng_forward_action_hide_captions" = "Hide Captions";
"lng_forward_action_change_recipient" = "Change Recipient";
"lng_forward_action_remove" = "Do Not Forward";
"lng_passport_title" = "Telegram Passport";
"lng_passport_request1" = "{bot} requests access to your personal data";
@@ -5718,6 +5839,13 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_recent_chats" = "Chats";
"lng_recent_channels" = "Channels";
"lng_recent_apps" = "Apps";
"lng_all_photos" = "Photos";
"lng_all_videos" = "Videos";
"lng_all_downloads" = "Downloads";
"lng_all_links" = "Links";
"lng_all_files" = "Files";
"lng_all_music" = "Music";
"lng_all_voice" = "Voice";
"lng_channels_none_title" = "No channels yet...";
"lng_channels_none_about" = "You are not currently subscribed to any channels.";
"lng_channels_your_title" = "Channels you joined";

View File

@@ -29,6 +29,7 @@
<file alias="search.tgs">../../animations/search.tgs</file>
<file alias="noresults.tgs">../../animations/noresults.tgs</file>
<file alias="hello_status.tgs">../../animations/hello_status.tgs</file>
<file alias="starref_link.tgs">../../animations/starref_link.tgs</file>
<file alias="dice_idle.tgs">../../animations/dice/dice_idle.tgs</file>
<file alias="dart_idle.tgs">../../animations/dice/dart_idle.tgs</file>

View File

@@ -4,6 +4,7 @@
<file alias="art/bg_thumbnail.png">../../art/bg_thumbnail.png</file>
<file alias="art/bg_initial.jpg">../../art/bg_initial.jpg</file>
<file alias="art/business_logo.png">../../art/business_logo.png</file>
<file alias="art/affiliate_logo.png">../../art/affiliate_logo.png</file>
<file alias="art/logo_256.png">../../art/logo_256.png</file>
<file alias="art/logo_256_no_margin.png">../../art/logo_256_no_margin.png</file>
<file alias="art/themeimage.jpg">../../art/themeimage.jpg</file>

View File

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

View File

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

View File

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

View File

@@ -8,6 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "api/api_chat_filters.h"
#include "apiwrap.h"
#include "base/event_filter.h"
#include "boxes/peer_list_box.h"
#include "boxes/premium_limits_box.h"
#include "boxes/filters/edit_filter_links.h" // FilterChatStatusText
@@ -139,7 +140,8 @@ void InitFilterLinkHeader(
Ui::FilterLinkHeaderType type,
const QString &title,
const QString &iconEmoji,
rpl::producer<int> count) {
rpl::producer<int> count,
bool horizontalFilters) {
const auto icon = Ui::LookupFilterIcon(
Ui::LookupFilterIconByEmoji(
iconEmoji
@@ -153,7 +155,7 @@ void InitFilterLinkHeader(
.badge = (type == Ui::FilterLinkHeaderType::AddingChats
? std::move(count)
: rpl::single(0)),
.horizontalFilters = Core::App().settings().chatFiltersHorizontal(),
.horizontalFilters = horizontalFilters,
});
const auto widget = header.widget;
widget->resizeToWidth(st::boxWideWidth);
@@ -548,6 +550,26 @@ void ShowImportToast(
strong->showToast(std::move(text));
}
void HandleEnterInBox(not_null<Ui::BoxContent*> box) {
const auto isEnter = [=](not_null<QEvent*> event) {
if (event->type() == QEvent::KeyPress) {
if (const auto k = static_cast<QKeyEvent*>(event.get())) {
return (k->key() == Qt::Key_Enter)
|| (k->key() == Qt::Key_Return);
}
}
return false;
};
base::install_event_filter(box, [=](not_null<QEvent*> event) {
if (isEnter(event)) {
box->triggerButton(0);
return base::EventFilterResult::Cancel;
}
return base::EventFilterResult::Continue;
});
}
void ProcessFilterInvite(
base::weak_ptr<Window::SessionController> weak,
const QString &slug,
@@ -572,6 +594,8 @@ void ProcessFilterInvite(
title,
std::move(peers),
std::move(already));
const auto horizontalFilters = !strong->enoughSpaceForFilters()
|| Core::App().settings().chatFiltersHorizontal();
const auto raw = controller.get();
auto initBox = [=](not_null<PeerListBox*> box) {
box->setStyle(st::filterInviteBox);
@@ -588,7 +612,7 @@ void ProcessFilterInvite(
});
InitFilterLinkHeader(box, [=](int min, int max, int addedTop) {
raw->adjust(min, max, addedTop);
}, type, title, iconEmoji, rpl::duplicate(badge));
}, type, title, iconEmoji, rpl::duplicate(badge), horizontalFilters);
raw->setRealContentHeight(box->heightValue());
@@ -610,6 +634,8 @@ void ProcessFilterInvite(
box->addButton(std::move(owned));
HandleEnterInBox(box);
struct State {
bool importing = false;
};
@@ -798,6 +824,8 @@ void ProcessFilterRemove(
title,
std::move(suggest),
std::move(all));
const auto horizontalFilters = !strong->enoughSpaceForFilters()
|| Core::App().settings().chatFiltersHorizontal();
const auto raw = controller.get();
auto initBox = [=](not_null<PeerListBox*> box) {
box->setStyle(st::filterInviteBox);
@@ -809,7 +837,7 @@ void ProcessFilterRemove(
});
InitFilterLinkHeader(box, [=](int min, int max, int addedTop) {
raw->adjust(min, max, addedTop);
}, type, title, iconEmoji, rpl::single(0));
}, type, title, iconEmoji, rpl::single(0), horizontalFilters);
auto owned = Ui::FilterLinkProcessButton(
box,
@@ -829,6 +857,8 @@ void ProcessFilterRemove(
box->addButton(std::move(owned));
HandleEnterInBox(box);
raw->selectedValue(
) | rpl::start_with_next([=](
base::flat_set<not_null<PeerData*>> &&peers) {

View File

@@ -207,32 +207,12 @@ void ConfirmSubscriptionBox(
Ui::AddSkip(content);
Ui::AddSkip(content);
{
const auto widget = Ui::CreateChild<Ui::RpWidget>(content);
using ColoredMiniStars = Ui::Premium::ColoredMiniStars;
const auto stars = widget->lifetime().make_state<ColoredMiniStars>(
widget,
false,
Ui::Premium::MiniStars::Type::BiStars);
stars->setColorOverride(Ui::Premium::CreditsIconGradientStops());
widget->resize(
st::boxWideWidth - photoSize,
photoSize * 2);
content->sizeValue(
) | rpl::start_with_next([=](const QSize &size) {
widget->moveToLeft(photoSize / 2, 0);
const auto starsRect = Rect(widget->size());
stars->setPosition(starsRect.topLeft());
stars->setSize(starsRect.size());
widget->lower();
}, widget->lifetime());
widget->paintRequest(
) | rpl::start_with_next([=](const QRect &r) {
auto p = QPainter(widget);
p.fillRect(r, Qt::transparent);
stars->paint(p);
}, widget->lifetime());
}
Settings::AddMiniStars(
content,
Ui::CreateChild<Ui::RpWidget>(content),
photoSize,
box->width(),
2.);
box->addRow(
object_ptr<Ui::CenterWrap<Ui::FlatLabel>>(

View File

@@ -74,7 +74,16 @@ constexpr auto kTransactionsLimit = 100;
}).value;
const auto stargift = tl.data().vstargift();
const auto reaction = tl.data().is_reaction();
const auto incoming = (int64(tl.data().vstars().v) >= 0);
const auto amount = Data::FromTL(tl.data().vstars());
const auto starrefAmount = tl.data().vstarref_amount()
? Data::FromTL(*tl.data().vstarref_amount())
: StarsAmount();
const auto starrefCommission
= tl.data().vstarref_commission_permille().value_or_empty();
const auto starrefBarePeerId = tl.data().vstarref_peer()
? peerFromMTP(*tl.data().vstarref_peer()).value
: 0;
const auto incoming = (amount >= StarsAmount());
const auto saveActorId = (reaction || !extended.empty()) && incoming;
return Data::CreditsHistoryEntry{
.id = qs(tl.data().vid()),
@@ -83,7 +92,7 @@ constexpr auto kTransactionsLimit = 100;
.date = base::unixtime::parse(tl.data().vdate().v),
.photoId = photo ? photo->id : 0,
.extended = std::move(extended),
.credits = tl.data().vstars().v,
.credits = Data::FromTL(tl.data().vstars()),
.bareMsgId = uint64(tl.data().vmsg_id().value_or_empty()),
.barePeerId = saveActorId ? peer->id.value : barePeerId,
.bareGiveawayMsgId = uint64(
@@ -92,6 +101,9 @@ constexpr auto kTransactionsLimit = 100;
? owner->processDocument(stargift->data().vsticker())->id
: 0),
.bareActorId = saveActorId ? barePeerId : uint64(0),
.starrefAmount = starrefAmount,
.starrefCommission = starrefCommission,
.starrefRecipientId = starrefBarePeerId,
.peerType = tl.data().vpeer().match([](const HistoryPeerTL &) {
return Data::CreditsHistoryEntry::PeerType::Peer;
}, [](const MTPDstarsTransactionPeerPlayMarket &) {
@@ -181,7 +193,7 @@ constexpr auto kTransactionsLimit = 100;
return Data::CreditsStatusSlice{
.list = std::move(entries),
.subscriptions = std::move(subscriptions),
.balance = status.data().vbalance().v,
.balance = Data::FromTL(status.data().vbalance()),
.subscriptionsMissingBalance
= status.data().vsubscriptions_missing_balance().value_or_empty(),
.allLoaded = !status.data().vnext_offset().has_value()
@@ -268,8 +280,8 @@ void CreditsStatus::request(
_peer->isSelf() ? MTP_inputPeerSelf() : _peer->input
)).done([=](const TLResult &result) {
_requestId = 0;
const auto balance = result.data().vbalance().v;
_peer->session().credits().apply(_peer->id, balance);
const auto &balance = result.data().vbalance();
_peer->session().credits().apply(_peer->id, Data::FromTL(balance));
if (const auto onstack = done) {
onstack(StatusFromTL(result, _peer));
}
@@ -348,7 +360,9 @@ rpl::producer<not_null<PeerData*>> PremiumPeerBot(
const auto api = lifetime.make_state<MTP::Sender>(&session->mtp());
api->request(MTPcontacts_ResolveUsername(
MTP_string(username)
MTP_flags(0),
MTP_string(username),
MTP_string()
)).done([=](const MTPcontacts_ResolvedPeer &result) {
session->data().processUsers(result.data().vusers());
session->data().processChats(result.data().vchats());
@@ -380,12 +394,13 @@ rpl::producer<rpl::no_value, QString> CreditsEarnStatistics::request() {
)).done([=](const MTPpayments_StarsRevenueStats &result) {
const auto &data = result.data();
const auto &status = data.vstatus().data();
using Data::FromTL;
_data = Data::CreditsEarnStatistics{
.revenueGraph = StatisticalGraphFromTL(
data.vrevenue_graph()),
.currentBalance = status.vcurrent_balance().v,
.availableBalance = status.vavailable_balance().v,
.overallRevenue = status.voverall_revenue().v,
.currentBalance = FromTL(status.vcurrent_balance()),
.availableBalance = FromTL(status.vavailable_balance()),
.overallRevenue = FromTL(status.voverall_revenue()),
.usdRate = data.vusd_rate().v,
.isWithdrawalEnabled = status.is_withdrawal_enabled(),
.nextWithdrawalAt = status.vnext_withdrawal_at()

View File

@@ -74,12 +74,17 @@ const FoundMessages &MessagesSearchMerged::messages() const {
return _concatedFound;
}
const MessagesSearch::Request &MessagesSearchMerged::request() const {
return _request;
}
void MessagesSearchMerged::clear() {
_concatedFound = {};
_migratedFirstFound = {};
}
void MessagesSearchMerged::search(const Request &search) {
_request = search;
if (_migratedSearch) {
_waitingForTotal = true;
_migratedSearch->searchMessages(search);

View File

@@ -31,6 +31,7 @@ public:
void searchMore();
[[nodiscard]] const FoundMessages &messages() const;
[[nodiscard]] const Request &request() const;
[[nodiscard]] rpl::producer<> newFounds() const;
[[nodiscard]] rpl::producer<> nextFounds() const;
@@ -39,6 +40,7 @@ private:
void addFound(const FoundMessages &data);
MessagesSearch _apiSearch;
Request _request;
std::optional<MessagesSearch> _migratedSearch;
FoundMessages _migratedFirstFound;

View File

@@ -181,7 +181,9 @@ std::optional<HistoryItem*> SingleMessageSearch::performLookupByUsername(
ready();
};
_requestId = _session->api().request(MTPcontacts_ResolveUsername(
MTP_string(username)
MTP_flags(0),
MTP_string(username),
MTP_string()
)).done([=](const MTPcontacts_ResolvedPeer &result) {
result.match([&](const MTPDcontacts_resolvedPeer &data) {
_session->data().processUsers(data.vusers());

View File

@@ -756,19 +756,32 @@ rpl::producer<Ui::WhoReadContent> WhoReacted(
const style::WhoRead &st) {
return WhoReacted(item, reaction, context, st, nullptr);
}
rpl::producer<Ui::WhoReadContent> WhenEdited(
[[nodiscard]] rpl::producer<Ui::WhoReadContent> WhenDate(
not_null<PeerData*> author,
TimeId date) {
TimeId date,
Ui::WhoReadType type) {
return rpl::single(Ui::WhoReadContent{
.participants = { Ui::WhoReadParticipant{
.name = author->name(),
.date = FormatReadDate(date, QDateTime::currentDateTime()),
.id = author->id.value,
} },
.type = Ui::WhoReadType::Edited,
.type = type,
.fullReadCount = 1,
});
}
rpl::producer<Ui::WhoReadContent> WhenEdited(
not_null<PeerData*> author,
TimeId date) {
return WhenDate(author, date, Ui::WhoReadType::Edited);
}
rpl::producer<Ui::WhoReadContent> WhenOriginal(
not_null<PeerData*> author,
TimeId date) {
return WhenDate(author, date, Ui::WhoReadType::Original);
}
} // namespace Api

View File

@@ -64,5 +64,8 @@ struct WhoReadList {
[[nodiscard]] rpl::producer<Ui::WhoReadContent> WhenEdited(
not_null<PeerData*> author,
TimeId date);
[[nodiscard]] rpl::producer<Ui::WhoReadContent> WhenOriginal(
not_null<PeerData*> author,
TimeId date);
} // namespace Api

View File

@@ -3219,6 +3219,31 @@ void ApiWrap::sharedMediaDone(
}
}
mtpRequestId ApiWrap::requestGlobalMedia(
Storage::SharedMediaType type,
const QString &query,
int32 offsetRate,
Data::MessagePosition offsetPosition,
Fn<void(Api::GlobalMediaResult)> done) {
auto prepared = Api::PrepareGlobalMediaRequest(
_session,
offsetRate,
offsetPosition,
type,
query);
if (!prepared) {
done({});
return 0;
}
return request(
std::move(*prepared)
).done([=](const Api::SearchRequestResult &result) {
done(Api::ParseGlobalMediaResult(_session, result));
}).fail([=] {
done({});
}).send();
}
void ApiWrap::sendAction(const SendAction &action) {
if (!action.options.scheduled
&& !action.options.shortcutId

View File

@@ -59,6 +59,7 @@ class Show;
namespace Api {
struct SearchResult;
struct GlobalMediaResult;
class Updates;
class Authorizations;
@@ -288,6 +289,12 @@ public:
Storage::SharedMediaType type,
MsgId messageId,
SliceType slice);
mtpRequestId requestGlobalMedia(
Storage::SharedMediaType type,
const QString &query,
int32 offsetRate,
Data::MessagePosition offsetPosition,
Fn<void(Api::GlobalMediaResult)> done);
void readFeaturedSetDelayed(uint64 setId);
@@ -509,6 +516,10 @@ private:
MsgId topicRootId,
SharedMediaType type,
Api::SearchResult &&parsed);
void globalMediaDone(
SharedMediaType type,
FullMsgId messageId,
Api::GlobalMediaResult &&parsed);
void sendSharedContact(
const QString &phone,
@@ -672,6 +683,17 @@ private:
};
base::flat_set<HistoryRequest> _historyRequests;
struct GlobalMediaRequest {
SharedMediaType mediaType = {};
FullMsgId aroundId;
SliceType sliceType = {};
friend inline auto operator<=>(
const GlobalMediaRequest&,
const GlobalMediaRequest&) = default;
};
base::flat_set<GlobalMediaRequest> _globalMediaRequests;
std::unique_ptr<DialogsLoadState> _dialogsLoadState;
TimeId _dialogsLoadTill = 0;
rpl::variable<bool> _dialogsLoadMayBlockByDate = false;

View File

@@ -262,10 +262,16 @@ void ShowAddParticipantsError(
return tr::lng_bot_already_in_group(tr::now);
} else if (error == u"BOT_GROUPS_BLOCKED"_q) {
return tr::lng_error_cant_add_bot(tr::now);
} else if (error == u"ADMINS_TOO_MUCH"_q) {
return ((chat->isChat() || chat->isMegagroup())
? tr::lng_error_admin_limit
: tr::lng_error_admin_limit_channel)(tr::now);
} else if (error == u"YOU_BLOCKED_USER"_q) {
return tr::lng_error_you_blocked_user(tr::now);
} else if (error == u"CHAT_ADMIN_INVITE_REQUIRED"_q) {
return tr::lng_error_add_admin_not_member(tr::now);
} else if (error == u"USER_ADMIN_INVALID"_q) {
return tr::lng_error_user_admin_invalid(tr::now);
} else if (error == u"BOTS_TOO_MUCH"_q) {
return (chat->isChannel()
? tr::lng_error_channel_bots_too_much
: tr::lng_error_group_bots_too_much)(tr::now);
}
return tr::lng_failed_add_participant(tr::now);
}();

View File

@@ -29,6 +29,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "lang/lang_keys.h"
#include "main/main_session.h"
#include "settings/settings_common.h"
#include "ui/chat/chats_filter_tag.h"
#include "ui/effects/animation_value_f.h"
#include "ui/effects/animations.h"
#include "ui/effects/panel_animation.h"
@@ -544,31 +545,18 @@ void EditFilterBox(
colors->width(),
h);
}, preview->lifetime());
const auto previewColor = preview->lifetime().make_state<QColor>();
const auto previewTag = preview->lifetime().make_state<QImage>();
const auto previewAlpha = preview->lifetime().make_state<float64>(1);
preview->paintRequest() | rpl::start_with_next([=] {
auto p = QPainter(preview);
p.fillRect(preview->rect(), Qt::transparent);
const auto &font = st::dialogRowFilterTagFont;
const auto text = name->getLastText().toUpper();
p.setFont(font);
p.setOpacity(*previewAlpha);
const auto roundedWidth = font->width(text) + font->spacew * 3;
const auto size = previewTag->size() / style::DevicePixelRatio();
const auto rect = QRect(
preview->width() - roundedWidth - st::boxRowPadding.right(),
(st::normalFont->height - font->height) / 2,
roundedWidth,
font->height);
const auto pen = QPen(*previewColor);
p.setPen(Qt::NoPen);
p.setBrush(anim::with_alpha(pen.color(), .15));
{
auto hq = PainterHighQualityEnabler(p);
const auto radius = font->height / 3.;
p.drawRoundedRect(rect, radius, radius);
}
p.setPen(pen);
p.drawText(rect, text, style::al_center);
preview->width() - size.width() - st::boxRowPadding.right(),
(st::normalFont->height - size.height()) / 2,
size.width(),
size.height());
p.drawImage(rect.topLeft(), *previewTag);
if (p.opacity() < 1) {
p.setOpacity(1. - p.opacity());
p.setFont(st::normalFont);
@@ -580,10 +568,6 @@ void EditFilterBox(
}
}, preview->lifetime());
name->changes() | rpl::start_with_next([=] {
preview->update();
}, preview->lifetime());
const auto side = st::userpicBuilderEmojiAccentColorSize;
const auto line = colors->add(
Ui::CreateSkipWidget(colors, side),
@@ -594,6 +578,13 @@ void EditFilterBox(
const auto palette = [](int i) {
return Ui::EmptyUserpic::UserpicColor(i).color2;
};
name->changes() | rpl::start_with_next([=] {
*previewTag = Ui::ChatsFilterTag(
name->getLastText().toUpper(),
palette(state->colorIndex.current())->c,
false);
preview->update();
}, preview->lifetime());
for (auto i = 0; i < kColorsCount; ++i) {
const auto button = Ui::CreateChild<UserpicBuilder::CircleButton>(
line);
@@ -605,7 +596,10 @@ void EditFilterBox(
const auto color = palette(i);
button->setBrush(color);
if (progress == 1) {
*previewColor = color->c;
*previewTag = Ui::ChatsFilterTag(
name->getLastText().toUpper(),
color->c,
false);
if (i == kNoTag) {
*previewAlpha = 0.;
}
@@ -628,7 +622,10 @@ void EditFilterBox(
buttons[was]->setSelectedProgress(1. - progress);
}
buttons[now]->setSelectedProgress(progress);
*previewColor = anim::color(c1, c2, progress);
*previewTag = Ui::ChatsFilterTag(
name->getLastText().toUpper(),
anim::color(c1, c2, progress),
false);
*previewAlpha = anim::interpolateF(a1, a2, progress);
preview->update();
}, 0., 1., st::universalDuration);

View File

@@ -47,6 +47,7 @@ void GiftCreditsBox(
const auto content = box->setPinnedToTopContent(
object_ptr<Ui::VerticalLayout>(box));
Ui::AddSkip(content);
Ui::AddSkip(content);
Ui::AddSkip(content);
const auto &stUser = st::premiumGiftsUserpicButton;
@@ -58,39 +59,19 @@ void GiftCreditsBox(
Ui::AddSkip(content);
Ui::AddSkip(content);
{
const auto widget = Ui::CreateChild<Ui::RpWidget>(content);
using ColoredMiniStars = Ui::Premium::ColoredMiniStars;
const auto stars = widget->lifetime().make_state<ColoredMiniStars>(
widget,
false,
Ui::Premium::MiniStars::Type::BiStars);
stars->setColorOverride(Ui::Premium::CreditsIconGradientStops());
widget->resize(
st::boxWidth - stUser.photoSize,
stUser.photoSize * 2);
content->sizeValue(
) | rpl::start_with_next([=](const QSize &size) {
widget->moveToLeft(stUser.photoSize / 2, 0);
const auto starsRect = Rect(widget->size());
stars->setPosition(starsRect.topLeft());
stars->setSize(starsRect.size());
widget->lower();
}, widget->lifetime());
widget->paintRequest(
) | rpl::start_with_next([=](const QRect &r) {
auto p = QPainter(widget);
p.fillRect(r, Qt::transparent);
stars->paint(p);
}, widget->lifetime());
}
Settings::AddMiniStars(
content,
Ui::CreateChild<Ui::RpWidget>(content),
stUser.photoSize,
box->width(),
2.);
{
Ui::AddSkip(content);
const auto arrow = Ui::Text::SingleCustomEmoji(
peer->owner().customEmojiManager().registerInternalEmoji(
st::topicButtonArrow,
st::channelEarnLearnArrowMargins,
false));
true));
auto link = tr::lng_credits_box_history_entry_gift_about_link(
lt_emoji,
rpl::single(arrow),
@@ -122,7 +103,7 @@ void GiftCreditsBox(
Main::MakeSessionShow(box->uiShow(), &peer->session()),
box->verticalLayout(),
peer,
0,
StarsAmount(),
[=] { gifted(); box->uiShow()->hideLayer(); },
tr::lng_credits_summary_options_subtitle(),
{});

View File

@@ -237,7 +237,7 @@ void AddTableRow(
valueMargins);
}
object_ptr<Ui::RpWidget> MakeStarGiftStarsValue(
[[nodiscard]] object_ptr<Ui::RpWidget> MakeStarGiftStarsValue(
not_null<QWidget*> parent,
not_null<Window::SessionNavigation*> controller,
const Data::CreditsHistoryEntry &entry,
@@ -255,8 +255,8 @@ object_ptr<Ui::RpWidget> MakeStarGiftStarsValue(
auto star = session->data().customEmojiManager().creditsEmoji();
const auto label = Ui::CreateChild<Ui::FlatLabel>(
raw,
rpl::single(
star.append(' ' + Lang::FormatCountDecimal(entry.credits))),
rpl::single(star.append(
' ' + Lang::FormatStarsAmountDecimal(entry.credits))),
st::giveawayGiftCodeValue,
st::defaultPopupMenu,
std::move(makeContext));
@@ -302,6 +302,62 @@ object_ptr<Ui::RpWidget> MakeStarGiftStarsValue(
return result;
}
[[nodiscard]] object_ptr<Ui::RpWidget> MakeVisibilityTableValue(
not_null<QWidget*> parent,
not_null<Window::SessionNavigation*> controller,
bool savedToProfile,
Fn<void(bool)> toggleVisibility) {
auto result = object_ptr<Ui::RpWidget>(parent);
const auto raw = result.data();
const auto label = Ui::CreateChild<Ui::FlatLabel>(
raw,
(savedToProfile
? tr::lng_gift_visibility_shown()
: tr::lng_gift_visibility_hidden()),
st::giveawayGiftCodeValue,
st::defaultPopupMenu);
const auto toggle = Ui::CreateChild<Ui::RoundButton>(
raw,
(savedToProfile
? tr::lng_gift_visibility_hide()
: tr::lng_gift_visibility_show()),
st::starGiftSmallButton);
toggle->setTextTransform(Ui::RoundButton::TextTransform::NoTransform);
toggle->setClickedCallback([=] {
toggleVisibility(!savedToProfile);
});
rpl::combine(
raw->widthValue(),
toggle->widthValue()
) | rpl::start_with_next([=](int width, int toggleWidth) {
const auto toggleSkip = toggleWidth
? (st::normalFont->spacew + toggleWidth)
: 0;
label->resizeToNaturalWidth(width - toggleSkip);
label->moveToLeft(0, 0, width);
if (toggle) {
toggle->moveToLeft(
label->width() + st::normalFont->spacew,
(st::giveawayGiftCodeValue.style.font->ascent
- st::starGiftSmallButton.style.font->ascent),
width);
}
}, label->lifetime());
label->heightValue() | rpl::start_with_next([=](int height) {
raw->resize(
raw->width(),
height + st::giveawayGiftCodeValueMargin.bottom());
}, raw->lifetime());
label->setAttribute(Qt::WA_TransparentForMouseEvents);
return result;
}
not_null<Ui::FlatLabel*> AddTableRow(
not_null<Ui::TableLayout*> table,
rpl::producer<QString> label,
@@ -1035,6 +1091,7 @@ void AddStarGiftTable(
not_null<Window::SessionNavigation*> controller,
not_null<Ui::VerticalLayout*> container,
const Data::CreditsHistoryEntry &entry,
Fn<void(bool)> toggleVisibility,
Fn<void()> convertToStars) {
auto table = container->add(
object_ptr<Ui::TableLayout>(
@@ -1072,9 +1129,15 @@ void AddStarGiftTable(
rpl::single(Ui::Text::WithEntities(
langDateTime(entry.lastSaleDate))));
}
if (!entry.date.isNull()) {
AddTableRow(
table,
tr::lng_gift_link_label_date(),
rpl::single(Ui::Text::WithEntities(langDateTime(entry.date))));
}
const auto marginWithButton = st::giveawayGiftCodeValueMargin
- QMargins(0, 0, 0, st::giveawayGiftCodeValueMargin.bottom());
{
const auto margin = st::giveawayGiftCodeValueMargin
- QMargins(0, 0, 0, st::giveawayGiftCodeValueMargin.bottom());
AddTableRow(
table,
tr::lng_gift_link_label_value(),
@@ -1083,13 +1146,18 @@ void AddStarGiftTable(
controller,
entry,
std::move(convertToStars)),
margin);
marginWithButton);
}
if (!entry.date.isNull()) {
if (toggleVisibility) {
AddTableRow(
table,
tr::lng_gift_link_label_date(),
rpl::single(Ui::Text::WithEntities(langDateTime(entry.date))));
tr::lng_gift_visibility(),
MakeVisibilityTableValue(
table,
controller,
entry.savedToProfile,
std::move(toggleVisibility)),
marginWithButton);
}
if (entry.limitedCount > 0) {
auto amount = rpl::single(TextWithEntities{
@@ -1146,9 +1214,43 @@ void AddCreditsHistoryEntryTable(
st::giveawayGiftCodeTableMargin);
const auto peerId = PeerId(entry.barePeerId);
const auto actorId = PeerId(entry.bareActorId);
const auto starrefRecipientId = PeerId(entry.starrefRecipientId);
const auto session = &controller->session();
if (actorId || peerId) {
auto text = entry.in
if (entry.starrefCommission) {
if (entry.starrefAmount) {
AddTableRow(
table,
tr::lng_star_ref_commission_title(),
rpl::single(TextWithEntities{
QString::number(entry.starrefCommission / 10.) + '%' }));
} else {
AddTableRow(
table,
tr::lng_gift_link_label_reason(),
tr::lng_credits_box_history_entry_reason_star_ref(
Ui::Text::WithEntities));
}
}
if (starrefRecipientId && entry.starrefAmount) {
AddTableRow(
table,
tr::lng_credits_box_history_entry_affiliate(),
controller,
starrefRecipientId);
}
if (peerId && entry.starrefCommission) {
AddTableRow(
table,
(entry.starrefAmount
? tr::lng_credits_box_history_entry_referred
: tr::lng_credits_box_history_entry_miniapp)(),
controller,
peerId);
}
if (actorId || (!entry.starrefCommission && peerId)) {
auto text = entry.starrefCommission
? tr::lng_credits_box_history_entry_referred()
: entry.in
? tr::lng_credits_box_history_entry_peer_in()
: tr::lng_credits_box_history_entry_peer();
AddTableRow(
@@ -1229,7 +1331,7 @@ void AddCreditsHistoryEntryTable(
tr::lng_gift_link_label_gift(),
tr::lng_gift_stars_title(
lt_count,
rpl::single(float64(entry.credits)),
rpl::single(entry.credits.value()),
Ui::Text::RichLangValue));
}
{

View File

@@ -58,6 +58,7 @@ void AddStarGiftTable(
not_null<Window::SessionNavigation*> controller,
not_null<Ui::VerticalLayout*> container,
const Data::CreditsHistoryEntry &entry,
Fn<void(bool)> toggleVisibility,
Fn<void()> convertToStars);
void AddCreditsHistoryEntryTable(
not_null<Window::SessionNavigation*> controller,

View File

@@ -7,7 +7,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#include "boxes/local_storage_box.h"
#include "boxes/abstract_box.h"
#include "ui/wrap/vertical_layout.h"
#include "ui/wrap/slide_wrap.h"
#include "ui/widgets/labels.h"
@@ -21,8 +20,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "storage/cache/storage_cache_database.h"
#include "data/data_session.h"
#include "lang/lang_keys.h"
#include "mainwindow.h"
#include "main/main_session.h"
#include "window/window_session_controller.h"
#include "styles/style_layers.h"
#include "styles/style_boxes.h"
@@ -282,19 +281,19 @@ LocalStorageBox::LocalStorageBox(
_timeLimit = settings.totalTimeLimit;
}
void LocalStorageBox::Show(not_null<::Main::Session*> session) {
void LocalStorageBox::Show(not_null<Window::SessionController*> controller) {
auto shared = std::make_shared<object_ptr<LocalStorageBox>>(
Box<LocalStorageBox>(session, CreateTag()));
Box<LocalStorageBox>(&controller->session(), CreateTag()));
const auto weak = shared->data();
rpl::combine(
session->data().cache().statsOnMain(),
session->data().cacheBigFile().statsOnMain()
controller->session().data().cache().statsOnMain(),
controller->session().data().cacheBigFile().statsOnMain()
) | rpl::start_with_next([=](
Database::Stats &&stats,
Database::Stats &&statsBig) {
weak->update(std::move(stats), std::move(statsBig));
if (auto &strong = *shared) {
Ui::show(std::move(strong));
controller->uiShow()->show(std::move(strong));
}
}, weak->lifetime());
}

View File

@@ -14,6 +14,10 @@ namespace Main {
class Session;
} // namespace Main
namespace Window {
class SessionController;
} // namespace Window
namespace Storage {
namespace Cache {
class Database;
@@ -40,7 +44,7 @@ public:
not_null<Main::Session*> session,
CreateTag);
static void Show(not_null<Main::Session*> session);
static void Show(not_null<Window::SessionController*> controller);
protected:
void prepare() override;

View File

@@ -120,6 +120,9 @@ void PeerListBox::createMultiSelect() {
content()->submitted();
});
_select->entity()->setQueryChangedCallback([=](const QString &query) {
if (_customQueryChangedCallback) {
_customQueryChangedCallback(query);
}
searchQueryChanged(query);
});
_select->entity()->setItemRemovedCallback([=](uint64 itemId) {
@@ -138,6 +141,10 @@ void PeerListBox::createMultiSelect() {
_select->moveToLeft(0, 0);
}
void PeerListBox::appendQueryChangedCallback(Fn<void(QString)> callback) {
_customQueryChangedCallback = std::move(callback);
}
void PeerListBox::setAddedTopScrollSkip(int skip) {
_addedTopScrollSkip = skip;
_scrollBottomFixed = false;
@@ -217,19 +224,7 @@ void PeerListBox::keyPressEvent(QKeyEvent *e) {
void PeerListBox::searchQueryChanged(const QString &query) {
scrollToY(0);
const auto isEmpty = content()->searchQueryChanged(query);
if (_specialTabsMode.enabled) {
const auto was = _specialTabsMode.searchIsActive;
_specialTabsMode.searchIsActive = !isEmpty;
if (was != _specialTabsMode.searchIsActive) {
if (_specialTabsMode.searchIsActive) {
_specialTabsMode.topSkip = _addedTopScrollSkip;
setAddedTopScrollSkip(0);
} else {
setAddedTopScrollSkip(_specialTabsMode.topSkip);
}
}
}
content()->searchQueryChanged(query);
}
void PeerListBox::resizeEvent(QResizeEvent *e) {
@@ -486,7 +481,7 @@ void PeerListBox::addSelectItem(
void PeerListBox::addSelectItem(
uint64 itemId,
const QString &text,
Ui::MultiSelect::PaintRoundImage paintUserpic,
PaintRoundImageCallback paintUserpic,
anim::type animated) {
if (!_select) {
createMultiSelect();
@@ -561,13 +556,8 @@ rpl::producer<int> PeerListBox::multiSelectHeightValue() const {
return _select ? _select->heightValue() : rpl::single(0);
}
void PeerListBox::setSpecialTabMode(bool value) {
content()->setIgnoreHiddenRowsOnSearch(value);
if (value) {
_specialTabsMode.enabled = true;
} else {
_specialTabsMode = {};
}
rpl::producer<> PeerListBox::noSearchSubmits() const {
return content()->noSearchSubmits();
}
PeerListRow::PeerListRow(not_null<PeerData*> peer)
@@ -1291,6 +1281,9 @@ void PeerListContent::clearAllContent() {
= _normalizedSearchQuery
= _mentionHighlight
= QString();
if (_controller->hasComplexSearch()) {
_controller->search(QString());
}
}
void PeerListContent::convertRowToSearchResult(not_null<PeerListRow*> row) {
@@ -2079,7 +2072,7 @@ void PeerListContent::checkScrollForPreload() {
}
}
PeerListContent::IsEmpty PeerListContent::searchQueryChanged(QString query) {
void PeerListContent::searchQueryChanged(QString query) {
const auto searchWordsList = TextUtilities::PrepareSearchWords(query);
const auto normalizedQuery = searchWordsList.join(' ');
if (_ignoreHiddenRowsOnSearch && !normalizedQuery.isEmpty()) {
@@ -2136,7 +2129,6 @@ PeerListContent::IsEmpty PeerListContent::searchQueryChanged(QString query) {
}
refreshRows();
}
return _normalizedSearchQuery.isEmpty();
}
std::unique_ptr<PeerListState> PeerListContent::saveState() const {
@@ -2211,6 +2203,9 @@ bool PeerListContent::submitted() {
_controller->rowClicked(row);
return true;
}
} else {
_noSearchSubmits.fire({});
return true;
}
return false;
}

View File

@@ -652,8 +652,7 @@ public:
[[nodiscard]] bool hasPressed() const;
void clearSelection();
using IsEmpty = bool;
IsEmpty searchQueryChanged(QString query);
void searchQueryChanged(QString query);
bool submitted();
PeerListRowId updateFromParentDrag(QPoint globalPosition);
@@ -713,7 +712,7 @@ public:
update();
}
std::unique_ptr<PeerListState> saveState() const;
[[nodiscard]] std::unique_ptr<PeerListState> saveState() const;
void restoreState(std::unique_ptr<PeerListState> state);
void showRowMenu(
@@ -721,10 +720,14 @@ public:
bool highlightRow,
Fn<void(not_null<Ui::PopupMenu*>)> destroyed);
auto scrollToRequests() const {
[[nodiscard]] auto scrollToRequests() const {
return _scrollToRequests.events();
}
[[nodiscard]] auto noSearchSubmits() const {
return _noSearchSubmits.events();
}
~PeerListContent();
protected:
@@ -891,6 +894,8 @@ private:
object_ptr<Ui::FlatLabel> _searchLoading = { nullptr };
object_ptr<Ui::RpWidget> _loadingAnimation = { nullptr };
rpl::event_stream<> _noSearchSubmits;
std::vector<std::unique_ptr<PeerListRow>> _searchRows;
base::Timer _repaintByStatus;
base::unique_qptr<Ui::PopupMenu> _contextMenu;
@@ -1107,8 +1112,7 @@ public:
[[nodiscard]] std::vector<PeerListRowId> collectSelectedIds();
[[nodiscard]] std::vector<not_null<PeerData*>> collectSelectedRows();
[[nodiscard]] rpl::producer<int> multiSelectHeightValue() const;
void setSpecialTabMode(bool value);
[[nodiscard]] rpl::producer<> noSearchSubmits() const;
void peerListSetTitle(rpl::producer<QString> title) override {
setTitle(std::move(title));
@@ -1133,6 +1137,8 @@ public:
void showFinished() override;
void appendQueryChangedCallback(Fn<void(QString)>);
protected:
void prepare() override;
void setInnerFocus() override;
@@ -1170,16 +1176,10 @@ private:
object_ptr<Ui::SlideWrap<Ui::MultiSelect>> _select = { nullptr };
const std::shared_ptr<Main::SessionShow> _show;
Fn<void(QString)> _customQueryChangedCallback;
std::unique_ptr<PeerListController> _controller;
Fn<void(PeerListBox*)> _init;
bool _scrollBottomFixed = false;
int _addedTopScrollSkip = 0;
struct SpecialTabsMode final {
bool enabled = false;
bool searchIsActive = false;
int topSkip = 0;
};
SpecialTabsMode _specialTabsMode;
};

View File

@@ -46,6 +46,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#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/bot/starref/info_bot_starref_join_widget.h"
#include "info/bot/starref/info_bot_starref_setup_widget.h"
#include "info/channel_statistics/boosts/info_boosts_widget.h"
#include "info/channel_statistics/earn/earn_format.h"
#include "info/channel_statistics/earn/earn_icons.h"
@@ -60,6 +62,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/controls/emoji_button.h"
#include "ui/controls/userpic_button.h"
#include "ui/effects/premium_graphics.h"
#include "ui/new_badges.h"
#include "ui/rect.h"
#include "ui/rp_widget.h"
#include "ui/vertical_list.h"
@@ -78,6 +81,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "styles/style_chat_helpers.h"
#include "styles/style_layers.h"
#include "styles/style_menu_icons.h"
#include "styles/style_settings.h"
#include "styles/style_boxes.h"
#include "styles/style_info.h"
@@ -358,6 +362,7 @@ private:
void fillBotUsernamesButton();
void fillBotCurrencyButton();
void fillBotCreditsButton();
void fillBotAffiliateProgram();
void fillBotEditIntroButton();
void fillBotEditCommandsButton();
void fillBotEditSettingsButton();
@@ -1181,6 +1186,7 @@ void Controller::fillManageSection() {
fillBotUsernamesButton();
fillBotCurrencyButton();
fillBotCreditsButton();
fillBotAffiliateProgram();
fillBotEditIntroButton();
fillBotEditCommandsButton();
fillBotEditSettingsButton();
@@ -1238,6 +1244,9 @@ void Controller::fillManageSection() {
&& (channel->isBroadcast() || channel->isGigagroup());
const auto hasRecentActions = isChannel
&& (channel->hasAdminRights() || channel->amCreator());
const auto hasStarRef = Info::BotStarRef::Join::Allowed(_peer)
&& isChannel
&& channel->canPostMessages();
const auto canEditStickers = isChannel && channel->canEditStickers();
const auto canDeleteChannel = isChannel && channel->canDelete();
const auto canEditColorIndex = isChannel && channel->canEditEmoji();
@@ -1420,10 +1429,21 @@ void Controller::fillManageSection() {
AddButtonWithCount(
_controls.buttonsLayout,
tr::lng_manage_peer_recent_actions(),
rpl::single(QString()), //Empty count.
rpl::single(QString()), // Empty count.
std::move(callback),
{ &st::menuIconGroupLog });
}
if (hasStarRef) {
auto callback = [=] {
_navigation->showSection(Info::BotStarRef::Join::Make(_peer));
};
AddButtonWithCount(
_controls.buttonsLayout,
tr::lng_manage_peer_star_ref(),
rpl::single(QString()), // Empty count.
std::move(callback),
{ .icon = &st::menuIconStarRefShare, .newBadge = true });
}
if (canEditStickers || canDeleteChannel) {
::AddSkip(_controls.buttonsLayout);
@@ -1664,7 +1684,7 @@ void Controller::fillBotCreditsButton() {
auto &lifetime = _controls.buttonsLayout->lifetime();
const auto state = lifetime.make_state<State>();
if (const auto balance = _peer->session().credits().balance(_peer->id)) {
state->balance = Lang::FormatCountDecimal(balance);
state->balance = Lang::FormatStarsAmountDecimal(balance);
}
const auto wrap = _controls.buttonsLayout->add(
@@ -1689,7 +1709,7 @@ void Controller::fillBotCreditsButton() {
if (data.balance) {
wrap->toggle(true, anim::type::normal);
}
state->balance = Lang::FormatCountDecimal(data.balance);
state->balance = Lang::FormatStarsAmountDecimal(data.balance);
});
}
{
@@ -1711,6 +1731,35 @@ void Controller::fillBotCreditsButton() {
}
void Controller::fillBotAffiliateProgram() {
Expects(_isBot);
if (!Info::BotStarRef::Setup::Allowed(_peer)) {
return;
}
const auto user = _peer->asUser();
auto label = user->session().changes().peerFlagsValue(
user,
Data::PeerUpdate::Flag::StarRefProgram
) | rpl::map([=] {
const auto commission = user->botInfo
? user->botInfo->starRefProgram.commission
: 0;
return commission
? Info::BotStarRef::FormatCommission(commission)
: tr::lng_manage_peer_bot_star_ref_off(tr::now);
});
AddButtonWithCount(
_controls.buttonsLayout,
tr::lng_manage_peer_bot_star_ref(),
std::move(label),
[controller = _navigation->parentController(), user] {
controller->showSection(Info::BotStarRef::Setup::Make(user));
},
{ .icon = &st::menuIconSharing, .newBadge = true });
}
void Controller::fillBotEditIntroButton() {
Expects(_isBot);
@@ -2227,7 +2276,9 @@ void Controller::saveHistoryVisibility() {
void Controller::toggleBotManager(const QString &command) {
const auto controller = _navigation->parentController();
_api.request(MTPcontacts_ResolveUsername(
MTP_string(kBotManagerUsername.utf16())
MTP_flags(0),
MTP_string(kBotManagerUsername.utf16()),
MTP_string()
)).done([=](const MTPcontacts_ResolvedPeer &result) {
_peer->owner().processUsers(result.data().vusers());
_peer->owner().processChats(result.data().vchats());
@@ -2501,6 +2552,13 @@ object_ptr<Ui::SettingsButton> EditPeerInfoBox::CreateButton(
st.button);
const auto button = result.data();
button->addClickHandler(callback);
const auto badge = descriptor.newBadge
? Ui::NewBadge::CreateNewBadge(
button,
tr::lng_premium_summary_new_badge()).get()
: nullptr;
if (descriptor) {
AddButtonIcon(
button,
@@ -2509,7 +2567,7 @@ object_ptr<Ui::SettingsButton> EditPeerInfoBox::CreateButton(
}
auto labelText = rpl::combine(
std::move(text),
rpl::duplicate(text),
std::move(count),
button->widthValue()
) | rpl::map([&st](const QString &text, const QString &count, int width) {
@@ -2524,11 +2582,40 @@ object_ptr<Ui::SettingsButton> EditPeerInfoBox::CreateButton(
: count;
});
if (badge) {
rpl::combine(
std::move(text),
rpl::duplicate(labelText),
button->widthValue()
) | rpl::start_with_next([=](
const QString &text,
const QString &label,
int width) {
const auto space = st.button.style.font->spacew;
const auto left = st.button.padding.left()
+ st.button.style.font->width(text)
+ space;
const auto right = st.labelPosition.x()
+ st.label.style.font->width(label)
+ (space * 2);
const auto available = width - left - right;
badge->setVisible(available >= badge->width());
if (!badge->isHidden()) {
const auto top = st.button.padding.top()
+ st.button.style.font->ascent
- st::settingsPremiumNewBadge.style.font->ascent
- st::settingsPremiumNewBadgePadding.top();
badge->moveToLeft(left, top, width);
}
}, badge->lifetime());
}
const auto label = Ui::CreateChild<Ui::FlatLabel>(
button,
std::move(labelText),
st.label);
label->setAttribute(Qt::WA_TransparentForMouseEvents);
label->show();
rpl::combine(
button->widthValue(),

View File

@@ -202,10 +202,11 @@ void Controller::prepare() {
const auto session = &_to->session();
auto above = object_ptr<Ui::VerticalLayout>((QWidget*)nullptr);
above->add(
CreateBoostReplaceUserpics(
CreateUserpicsTransfer(
above.data(),
_selectedPeers.value(),
_to),
_to,
UserpicsTransferType::BoostReplace),
st::boxRowPadding + st::boostReplaceUserpicsPadding);
above->add(
object_ptr<Ui::FlatLabel>(
@@ -366,10 +367,11 @@ object_ptr<Ui::BoxContent> ReassignBoostSingleBox(
});
box->verticalLayout()->insert(
0,
CreateBoostReplaceUserpics(
CreateUserpicsTransfer(
box,
rpl::single(std::vector{ peer }),
to),
to,
UserpicsTransferType::BoostReplace),
st::boxRowPadding + st::boostReplaceUserpicsPadding);
});
@@ -536,10 +538,11 @@ object_ptr<Ui::BoxContent> ReassignBoostsBox(
return Box<PeerListBox>(std::move(controller), std::move(initBox));
}
object_ptr<Ui::RpWidget> CreateBoostReplaceUserpics(
object_ptr<Ui::RpWidget> CreateUserpicsTransfer(
not_null<Ui::RpWidget*> parent,
rpl::producer<std::vector<not_null<PeerData*>>> from,
not_null<PeerData*> to) {
not_null<PeerData*> to,
UserpicsTransferType type) {
struct State {
std::vector<not_null<PeerData*>> from;
std::vector<std::unique_ptr<Ui::UserpicButton>> buttons;
@@ -640,13 +643,18 @@ object_ptr<Ui::RpWidget> CreateBoostReplaceUserpics(
button->render(&q, position, QRegion(), QWidget::DrawChildren);
}
state->painting = false;
const auto boosting = (type == UserpicsTransferType::BoostReplace);
const auto last = state->buttons.back().get();
const auto back = boosting ? last : right;
const auto add = st::boostReplaceIconAdd;
const auto skip = st::boostReplaceIconSkip;
const auto w = st::boostReplaceIcon.width() + 2 * skip;
const auto h = st::boostReplaceIcon.height() + 2 * skip;
const auto x = last->x() + last->width() - w + add.x();
const auto y = last->y() + last->height() - h + add.y();
const auto &icon = boosting
? st::boostReplaceIcon
: st::starrefJoinIcon;
const auto skip = boosting ? st::boostReplaceIconSkip : 0;
const auto w = icon.width() + 2 * skip;
const auto h = icon.height() + 2 * skip;
const auto x = back->x() + back->width() - w + add.x();
const auto y = back->y() + back->height() - h + add.y();
auto brush = QLinearGradient(QPointF(x + w, y + h), QPointF(x, y));
brush.setStops(Ui::Premium::ButtonGradientStops());
@@ -654,7 +662,7 @@ object_ptr<Ui::RpWidget> CreateBoostReplaceUserpics(
pen.setWidthF(stroke);
q.setPen(pen);
q.drawEllipse(x - half, y - half, w + stroke, h + stroke);
st::boostReplaceIcon.paint(q, x + skip, y + skip, outerw);
icon.paint(q, x + skip, y + skip, outerw);
const auto size = st::boostReplaceArrow.size();
st::boostReplaceArrow.paint(

View File

@@ -53,10 +53,15 @@ object_ptr<Ui::BoxContent> ReassignBoostsBox(
Fn<void(std::vector<int> slots, int groups, int channels)> reassign,
Fn<void()> cancel);
[[nodiscard]] object_ptr<Ui::RpWidget> CreateBoostReplaceUserpics(
enum class UserpicsTransferType {
BoostReplace,
StarRefJoin,
};
[[nodiscard]] object_ptr<Ui::RpWidget> CreateUserpicsTransfer(
not_null<Ui::RpWidget*> parent,
rpl::producer<std::vector<not_null<PeerData*>>> from,
not_null<PeerData*> to);
not_null<PeerData*> to,
UserpicsTransferType type);
[[nodiscard]] object_ptr<Ui::RpWidget> CreateUserpicsWithMoreBadge(
not_null<Ui::RpWidget*> parent,

View File

@@ -57,6 +57,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/text/format_values.h"
#include "ui/text/text_utilities.h"
#include "ui/toast/toast.h"
#include "ui/ui_utility.h"
#include "ui/vertical_list.h"
#include "ui/widgets/fields/input_field.h"
#include "ui/widgets/buttons.h"
@@ -71,11 +72,14 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "styles/style_premium.h"
#include "styles/style_settings.h"
#include <QtWidgets/QApplication>
namespace Ui {
namespace {
constexpr auto kPriceTabAll = 0;
constexpr auto kPriceTabLimited = -1;
constexpr auto kPriceTabInStock = -2;
constexpr auto kGiftMessageLimit = 255;
constexpr auto kSentToastDuration = 3 * crl::time(1000);
@@ -156,6 +160,10 @@ private:
return is(now) || is(now.addDays(1)) || is(now.addDays(-1));
}
[[nodiscard]] bool IsSoldOut(const Api::StarGift &info) {
return info.limitedCount && !info.limitedLeft;
}
PreviewDelegate::PreviewDelegate(
not_null<QWidget*> parent,
not_null<Ui::ChatStyle*> st,
@@ -554,6 +562,8 @@ void PreviewWrap::paintEvent(QPaintEvent *e) {
return simple(tr::lng_gift_stars_tabs_all(tr::now));
} else if (price == kPriceTabLimited) {
return simple(tr::lng_gift_stars_tabs_limited(tr::now));
} else if (price == kPriceTabInStock) {
return simple(tr::lng_gift_stars_tabs_in_stock(tr::now));
}
auto &manager = session->data().customEmojiManager();
auto result = Text::String();
@@ -589,18 +599,33 @@ struct GiftPriceTabs {
struct State {
rpl::variable<std::vector<int>> prices;
rpl::variable<int> priceTab = kPriceTabAll;
rpl::variable<int> fullWidth;
std::vector<Button> buttons;
int dragx = 0;
int pressx = 0;
float64 dragscroll = 0.;
float64 scroll = 0.;
int scrollMax = 0;
int selected = -1;
int pressed = -1;
int active = -1;
};
const auto state = raw->lifetime().make_state<State>();
const auto scroll = [=] {
return QPoint(int(base::SafeRound(state->scroll)), 0);
};
state->prices = std::move(
gifts
) | rpl::map([](const std::vector<GiftTypeStars> &gifts) {
auto result = std::vector<int>();
result.push_back(kPriceTabAll);
auto special = 1;
auto same = true;
auto sameKey = 0;
auto hasNonSoldOut = false;
auto hasSoldOut = false;
auto hasLimited = false;
for (const auto &gift : gifts) {
if (same) {
const auto key = gift.info.stars
@@ -611,10 +636,13 @@ struct GiftPriceTabs {
same = false;
}
}
if (gift.info.limitedCount
&& (result.size() < 2 || result[1] != kPriceTabLimited)) {
result.insert(begin(result) + 1, kPriceTabLimited);
if (IsSoldOut(gift.info)) {
hasSoldOut = true;
} else {
hasNonSoldOut = true;
}
if (gift.info.limitedCount) {
hasLimited = true;
}
if (!ranges::contains(result, gift.info.stars)) {
result.push_back(gift.info.stars);
@@ -623,6 +651,12 @@ struct GiftPriceTabs {
if (same) {
return std::vector<int>();
}
if (hasSoldOut && hasNonSoldOut) {
result.insert(begin(result) + (special++), kPriceTabInStock);
}
if (hasLimited) {
result.insert(begin(result) + (special++), kPriceTabLimited);
}
ranges::sort(begin(result) + 1, end(result));
return result;
});
@@ -681,6 +715,9 @@ struct GiftPriceTabs {
button.geometry = QRect(QPoint(x, y), r.size());
x += r.width() + st::giftBoxTabSkip;
}
state->fullWidth = x
- st::giftBoxTabSkip
+ st::giftBoxTabsMargin.right();
const auto height = state->buttons.empty()
? 0
: (y
@@ -690,13 +727,35 @@ struct GiftPriceTabs {
raw->update();
}, raw->lifetime());
rpl::combine(
raw->widthValue(),
state->fullWidth.value()
) | rpl::start_with_next([=](int outer, int inner) {
state->scrollMax = std::max(0, inner - outer);
}, raw->lifetime());
raw->setMouseTracking(true);
raw->events() | rpl::start_with_next([=](not_null<QEvent*> e) {
const auto type = e->type();
switch (type) {
case QEvent::Leave: setSelected(-1); break;
case QEvent::MouseMove: {
const auto position = static_cast<QMouseEvent*>(e.get())->pos();
const auto me = static_cast<QMouseEvent*>(e.get());
const auto mousex = me->pos().x();
const auto drag = QApplication::startDragDistance();
if (state->dragx > 0) {
state->scroll = std::clamp(
state->dragscroll + state->dragx - mousex,
0.,
state->scrollMax * 1.);
raw->update();
break;
} else if (state->pressx > 0
&& std::abs(state->pressx - mousex) > drag) {
state->dragx = state->pressx;
state->dragscroll = state->scroll;
}
const auto position = me->pos() + scroll();
for (auto i = 0, c = int(state->buttons.size()); i != c; ++i) {
if (state->buttons[i].geometry.contains(position)) {
setSelected(i);
@@ -704,17 +763,32 @@ struct GiftPriceTabs {
}
}
} break;
case QEvent::Wheel: {
const auto me = static_cast<QWheelEvent*>(e.get());
state->scroll = std::clamp(
state->scroll - Ui::ScrollDeltaF(me).x(),
0.,
state->scrollMax * 1.);
raw->update();
} break;
case QEvent::MouseButtonPress: {
const auto me = static_cast<QMouseEvent*>(e.get());
if (me->button() != Qt::LeftButton) {
break;
}
const auto position = me->pos();
for (auto i = 0, c = int(state->buttons.size()); i != c; ++i) {
if (state->buttons[i].geometry.contains(position)) {
setActive(i);
break;
}
state->pressed = state->selected;
state->pressx = me->pos().x();
} break;
case QEvent::MouseButtonRelease: {
const auto me = static_cast<QMouseEvent*>(e.get());
if (me->button() != Qt::LeftButton) {
break;
}
const auto dragx = std::exchange(state->dragx, 0);
const auto pressed = std::exchange(state->pressed, -1);
state->pressx = 0;
if (!dragx && pressed >= 0 && state->selected == pressed) {
setActive(pressed);
}
} break;
}
@@ -724,8 +798,9 @@ struct GiftPriceTabs {
auto p = QPainter(raw);
auto hq = PainterHighQualityEnabler(p);
const auto padding = st::giftBoxTabPadding;
const auto shift = -scroll();
for (const auto &button : state->buttons) {
const auto geometry = button.geometry;
const auto geometry = button.geometry.translated(shift);
if (button.active) {
p.setBrush(st::giftBoxTabBgActive);
p.setPen(Qt::NoPen);
@@ -740,6 +815,14 @@ struct GiftPriceTabs {
.availableWidth = button.text.maxWidth(),
});
}
{
const auto &icon = st::defaultEmojiSuggestions;
const auto w = icon.fadeRight.width();
const auto &c = st::boxDividerBg->c;
const auto r = QRect(0, 0, w, raw->height());
icon.fadeRight.fill(p, r.translated(raw->width() - w, 0), c);
icon.fadeLeft.fill(p, r, c);
}
}, raw->lifetime());
return {
@@ -875,7 +958,7 @@ void SoldOutBox(
Data::CreditsHistoryEntry{
.firstSaleDate = base::unixtime::parse(gift.info.firstSaleDate),
.lastSaleDate = base::unixtime::parse(gift.info.lastSaleDate),
.credits = uint64(gift.info.stars),
.credits = StarsAmount(gift.info.stars),
.bareGiftStickerId = gift.info.document->id,
.peerType = Data::CreditsHistoryEntry::PeerType::Peer,
.limitedCount = gift.info.limitedCount,
@@ -1128,9 +1211,7 @@ void SendGiftBox(
button->setClickedCallback([=] {
const auto star = std::get_if<GiftTypeStars>(&descriptor);
if (star
&& star->info.limitedCount
&& !star->info.limitedLeft) {
if (star && IsSoldOut(star->info)) {
window->show(Box(SoldOutBox, window, *star));
} else {
window->show(
@@ -1235,6 +1316,8 @@ void AddBlock(
gifts.erase(ranges::remove_if(gifts, [&](const GiftTypeStars &gift) {
return (price == kPriceTabLimited)
? (!gift.info.limitedCount)
: (price == kPriceTabInStock)
? IsSoldOut(gift.info)
: (price && gift.info.stars != price);
}), end(gifts));
return GiftsDescriptor{
@@ -1270,32 +1353,12 @@ void GiftBox(
AddSkip(content);
AddSkip(content);
{
const auto widget = CreateChild<RpWidget>(content);
using ColoredMiniStars = Premium::ColoredMiniStars;
const auto stars = widget->lifetime().make_state<ColoredMiniStars>(
widget,
false,
Premium::MiniStars::Type::BiStars);
stars->setColorOverride(Premium::CreditsIconGradientStops());
widget->resize(
st::boxWidth - stUser.photoSize,
stUser.photoSize * 2);
content->sizeValue(
) | rpl::start_with_next([=](const QSize &size) {
widget->moveToLeft((size.width() - widget->width()) / 2, 0);
const auto starsRect = Rect(widget->size());
stars->setPosition(starsRect.topLeft());
stars->setSize(starsRect.size());
widget->lower();
}, widget->lifetime());
widget->paintRequest(
) | rpl::start_with_next([=](const QRect &r) {
auto p = QPainter(widget);
p.fillRect(r, Qt::transparent);
stars->paint(p);
}, widget->lifetime());
}
Settings::AddMiniStars(
content,
Ui::CreateChild<Ui::RpWidget>(content),
stUser.photoSize,
box->width(),
2.);
AddSkip(content);
AddSkip(box->verticalLayout());

View File

@@ -1612,7 +1612,7 @@ void Panel::initLayout() {
#ifndef Q_OS_MAC
_controls->wrap.raise();
Ui::Platform::TitleControlsLayoutChanged(
_controls->controls.layout().changes(
) | rpl::start_with_next([=] {
// _menuToggle geometry depends on _controls arrangement.
crl::on_main(widget(), [=] { updateControlsGeometry(); });

View File

@@ -899,7 +899,9 @@ void GifsListWidget::searchForGifs(const QString &query) {
if (!_searchBot && !_searchBotRequestId) {
const auto username = session().serverConfig().gifSearchUsername;
_searchBotRequestId = _api.request(MTPcontacts_ResolveUsername(
MTP_string(username)
MTP_flags(0),
MTP_string(username),
MTP_string()
)).done([=](const MTPcontacts_ResolvedPeer &result) {
auto &data = result.data();
session().data().processUsers(data.vusers());

View File

@@ -31,7 +31,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "boxes/edit_privacy_box.h"
#include "boxes/premium_preview_box.h"
#include "boxes/sticker_set_box.h"
#include "boxes/sessions_box.h"
#include "boxes/star_gift_box.h"
#include "boxes/language_box.h"
#include "passport/passport_form_controller.h"
@@ -51,6 +50,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "window/window_peer_menu.h"
#include "window/themes/window_theme_editor_box.h" // GenerateSlug.
#include "payments/payments_checkout_process.h"
#include "settings/settings_active_sessions.h"
#include "settings/settings_credits.h"
#include "settings/settings_credits_graphics.h"
#include "settings/settings_information.h"
@@ -63,6 +63,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "settings/settings_premium.h"
#include "mainwidget.h"
#include "main/main_account.h"
#include "main/main_app_config.h"
#include "main/main_domain.h"
#include "main/main_session.h"
#include "main/main_session_settings.h"
@@ -556,8 +557,19 @@ bool ResolveUsernameOrPhone(
? ResolveType::Profile
: ResolveType::Default;
auto startToken = params.value(u"start"_q);
auto referral = params.value(u"ref"_q);
if (!startToken.isEmpty()) {
resolveType = ResolveType::BotStart;
if (referral.isEmpty()) {
const auto appConfig = &controller->session().appConfig();
const auto &prefixes = appConfig->startRefPrefixes();
for (const auto &prefix : prefixes) {
if (startToken.startsWith(prefix)) {
referral = startToken.mid(prefix.size());
break;
}
}
}
} else if (params.contains(u"startgroup"_q)) {
resolveType = ResolveType::AddToGroup;
startToken = params.value(u"startgroup"_q);
@@ -613,6 +625,7 @@ bool ResolveUsernameOrPhone(
}
: Window::RepliesByLinkInfo{ v::null },
.resolveType = resolveType,
.referral = referral,
.startToken = startToken,
.startAdminRights = adminRights,
.startAutoSubmit = myContext.botStartAutoSubmit,

View File

@@ -0,0 +1,99 @@
/*
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/basic_types.h"
inline constexpr auto kOneStarInNano = int64(1'000'000'000);
class StarsAmount {
public:
StarsAmount() = default;
explicit StarsAmount(int64 whole) : _whole(whole) {}
StarsAmount(int64 whole, int64 nano) : _whole(whole), _nano(nano) {
normalize();
}
[[nodiscard]] int64 whole() const {
return _whole;
}
[[nodiscard]] int64 nano() const {
return _nano;
}
[[nodiscard]] double value() const {
return double(_whole) + double(_nano) / kOneStarInNano;
}
[[nodiscard]] bool empty() const {
return !_whole && !_nano;
}
[[nodiscard]] inline bool operator!() const {
return empty();
}
[[nodiscard]] inline explicit operator bool() const {
return !empty();
}
inline StarsAmount &operator+=(StarsAmount other) {
_whole += other._whole;
_nano += other._nano;
normalize();
return *this;
}
inline StarsAmount &operator-=(StarsAmount other) {
_whole -= other._whole;
_nano -= other._nano;
normalize();
return *this;
}
inline StarsAmount &operator*=(int64 multiplier) {
_whole *= multiplier;
_nano *= multiplier;
normalize();
return *this;
}
friend inline auto operator<=>(StarsAmount, StarsAmount) = default;
friend inline bool operator==(StarsAmount, StarsAmount) = default;
[[nodiscard]] StarsAmount abs() const {
return (_whole < 0) ? StarsAmount(-_whole, -_nano) : *this;
}
private:
int64 _whole = 0;
int64 _nano = 0;
void normalize() {
if (_nano < 0) {
const auto shifts = (-_nano + kOneStarInNano - 1)
/ kOneStarInNano;
_nano += shifts * kOneStarInNano;
_whole -= shifts;
} else if (_nano >= kOneStarInNano) {
const auto shifts = _nano / kOneStarInNano;
_nano -= shifts * kOneStarInNano;
_whole += shifts;
}
}
};
[[nodiscard]] inline StarsAmount operator+(StarsAmount a, StarsAmount b) {
return a += b;
}
[[nodiscard]] inline StarsAmount operator-(StarsAmount a, StarsAmount b) {
return a -= b;
}
[[nodiscard]] inline StarsAmount operator*(StarsAmount a, int64 b) {
return a *= b;
}

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 = 5008004;
constexpr auto AppVersionStr = "5.8.4";
constexpr auto AppVersion = 5009002;
constexpr auto AppVersionStr = "5.9.2";
constexpr auto AppBetaVersion = true;
constexpr auto AppAlphaVersion = TDESKTOP_ALPHA_VERSION;

View File

@@ -19,6 +19,11 @@ constexpr auto kReloadThreshold = 60 * crl::time(1000);
} // namespace
StarsAmount FromTL(const MTPStarsAmount &value) {
const auto &data = value.data();
return StarsAmount(data.vamount().v, data.vnanos().v);
}
Credits::Credits(not_null<Main::Session*> session)
: _session(session)
, _reload([=] { load(true); }) {
@@ -27,7 +32,7 @@ Credits::Credits(not_null<Main::Session*> session)
Credits::~Credits() = default;
void Credits::apply(const MTPDupdateStarsBalance &data) {
apply(data.vbalance().v);
apply(FromTL(data.vbalance()));
}
rpl::producer<float64> Credits::rateValue(
@@ -65,13 +70,13 @@ rpl::producer<bool> Credits::loadedValue() const {
) | rpl::then(_loadedChanges.events() | rpl::map_to(true));
}
uint64 Credits::balance() const {
StarsAmount Credits::balance() const {
return _nonLockedBalance.current();
}
uint64 Credits::balance(PeerId peerId) const {
StarsAmount Credits::balance(PeerId peerId) const {
const auto it = _cachedPeerBalances.find(peerId);
return (it != _cachedPeerBalances.end()) ? it->second : 0;
return (it != _cachedPeerBalances.end()) ? it->second : StarsAmount();
}
uint64 Credits::balanceCurrency(PeerId peerId) const {
@@ -79,17 +84,19 @@ uint64 Credits::balanceCurrency(PeerId peerId) const {
return (it != _cachedPeerCurrencyBalances.end()) ? it->second : 0;
}
rpl::producer<uint64> Credits::balanceValue() const {
rpl::producer<StarsAmount> Credits::balanceValue() const {
return _nonLockedBalance.value();
}
void Credits::updateNonLockedValue() {
_nonLockedBalance = (_balance >= _locked) ? (_balance - _locked) : 0;
_nonLockedBalance = (_balance >= _locked)
? (_balance - _locked)
: StarsAmount();
}
void Credits::lock(int count) {
void Credits::lock(StarsAmount count) {
Expects(loaded());
Expects(count >= 0);
Expects(count >= StarsAmount(0));
Expects(_locked + count <= _balance);
_locked += count;
@@ -97,8 +104,8 @@ void Credits::lock(int count) {
updateNonLockedValue();
}
void Credits::unlock(int count) {
Expects(count >= 0);
void Credits::unlock(StarsAmount count) {
Expects(count >= StarsAmount(0));
Expects(_locked >= count);
_locked -= count;
@@ -106,12 +113,12 @@ void Credits::unlock(int count) {
updateNonLockedValue();
}
void Credits::withdrawLocked(int count) {
Expects(count >= 0);
void Credits::withdrawLocked(StarsAmount count) {
Expects(count >= StarsAmount(0));
Expects(_locked >= count);
_locked -= count;
apply(_balance >= count ? (_balance - count) : 0);
apply(_balance >= count ? (_balance - count) : StarsAmount(0));
invalidate();
}
@@ -119,7 +126,7 @@ void Credits::invalidate() {
_reload.call();
}
void Credits::apply(uint64 balance) {
void Credits::apply(StarsAmount balance) {
_balance = balance;
updateNonLockedValue();
@@ -129,7 +136,7 @@ void Credits::apply(uint64 balance) {
}
}
void Credits::apply(PeerId peerId, uint64 balance) {
void Credits::apply(PeerId peerId, StarsAmount balance) {
_cachedPeerBalances[peerId] = balance;
}

View File

@@ -17,30 +17,32 @@ class Session;
namespace Data {
[[nodiscard]] StarsAmount FromTL(const MTPStarsAmount &value);
class Credits final {
public:
explicit Credits(not_null<Main::Session*> session);
~Credits();
void load(bool force = false);
void apply(uint64 balance);
void apply(PeerId peerId, uint64 balance);
void apply(StarsAmount balance);
void apply(PeerId peerId, StarsAmount balance);
[[nodiscard]] bool loaded() const;
[[nodiscard]] rpl::producer<bool> loadedValue() const;
[[nodiscard]] uint64 balance() const;
[[nodiscard]] uint64 balance(PeerId peerId) const;
[[nodiscard]] rpl::producer<uint64> balanceValue() const;
[[nodiscard]] StarsAmount balance() const;
[[nodiscard]] StarsAmount balance(PeerId peerId) const;
[[nodiscard]] rpl::producer<StarsAmount> balanceValue() const;
[[nodiscard]] rpl::producer<float64> rateValue(
not_null<PeerData*> ownedBotOrChannel);
void applyCurrency(PeerId peerId, uint64 balance);
[[nodiscard]] uint64 balanceCurrency(PeerId peerId) const;
void lock(int count);
void unlock(int count);
void withdrawLocked(int count);
void lock(StarsAmount count);
void unlock(StarsAmount count);
void withdrawLocked(StarsAmount count);
void invalidate();
void apply(const MTPDupdateStarsBalance &data);
@@ -52,12 +54,12 @@ private:
std::unique_ptr<Api::CreditsStatus> _loader;
base::flat_map<PeerId, uint64> _cachedPeerBalances;
base::flat_map<PeerId, StarsAmount> _cachedPeerBalances;
base::flat_map<PeerId, uint64> _cachedPeerCurrencyBalances;
uint64 _balance = 0;
uint64 _locked = 0;
rpl::variable<uint64> _nonLockedBalance;
StarsAmount _balance;
StarsAmount _locked;
rpl::variable<StarsAmount> _nonLockedBalance;
rpl::event_stream<> _loadedChanges;
crl::time _lastLoaded = 0;
float64 _rate = 0.;

View File

@@ -85,7 +85,7 @@ QByteArray RecentPeers::serialize() const {
auto stream = Serialize::ByteArrayWriter(size);
stream
<< quint32(AppVersion)
<< quint32(_list.size());
<< quint32(count);
for (const auto &peer : list) {
Serialize::writePeer(stream, peer);
}

View File

@@ -156,10 +156,11 @@ void SponsoredMessages::inject(
if (blockIt == end(history->blocks)) {
return;
}
const auto messages = [&]() -> const std::vector<ViewPtr> & {
const auto messages = [&]() -> const std::vector<ViewPtr>& {
return (*blockIt)->messages;
};
auto lastViewIt = ranges::find(messages(), lastView, &ViewPtr::get);
auto appendAtLeastToEnd = false;
while ((summaryBetween < list.postsBetween)
|| (summaryHeight < betweenHeight)) {
lastViewIt++;
@@ -168,6 +169,10 @@ void SponsoredMessages::inject(
if (blockIt != end(history->blocks)) {
lastViewIt = begin(messages());
} else {
if (!list.injectedCount) {
appendAtLeastToEnd = true;
break;
}
return;
}
}
@@ -182,17 +187,24 @@ void SponsoredMessages::inject(
entryIt->itemFullId = FullMsgId(
history->peer->id,
_session->data().nextLocalMessageId());
const auto makedMessage = history->makeMessage(
entryIt->itemFullId.msg,
entryIt->sponsored.from,
entryIt->sponsored.textWithEntities,
(*lastViewIt)->data());
entryIt->item.reset(makedMessage.get());
history->addNewInTheMiddle(
makedMessage.get(),
std::distance(begin(history->blocks), blockIt),
std::distance(begin(messages()), lastViewIt) + 1);
messages().back().get()->setPendingResize();
if (appendAtLeastToEnd) {
entryIt->item.reset(history->addSponsoredMessage(
entryIt->itemFullId.msg,
entryIt->sponsored.from,
entryIt->sponsored.textWithEntities));
} else {
const auto makedMessage = history->makeMessage(
entryIt->itemFullId.msg,
entryIt->sponsored.from,
entryIt->sponsored.textWithEntities,
(*lastViewIt)->data());
entryIt->item.reset(makedMessage.get());
history->addNewInTheMiddle(
makedMessage.get(),
std::distance(begin(history->blocks), blockIt),
std::distance(begin(messages()), lastViewIt) + 1);
messages().back().get()->setPendingResize();
}
list.injectedCount++;
}
}

View File

@@ -264,7 +264,7 @@ QByteArray TopPeers::serialize() const {
stream
<< quint32(AppVersion)
<< quint32(_disabled ? 1 : 0)
<< quint32(_list.size());
<< quint32(count);
for (const auto &top : list) {
Serialize::writePeer(stream, top.peer);
stream << SerializeRating(top.rating);

View File

@@ -92,27 +92,28 @@ struct PeerUpdate {
BusinessDetails = (1ULL << 30),
Birthday = (1ULL << 31),
PersonalChannel = (1ULL << 32),
StarRefProgram = (1ULL << 33),
// For chats and channels
InviteLinks = (1ULL << 33),
Members = (1ULL << 34),
Admins = (1ULL << 35),
BannedUsers = (1ULL << 36),
Rights = (1ULL << 37),
PendingRequests = (1ULL << 38),
Reactions = (1ULL << 39),
InviteLinks = (1ULL << 34),
Members = (1ULL << 35),
Admins = (1ULL << 36),
BannedUsers = (1ULL << 37),
Rights = (1ULL << 38),
PendingRequests = (1ULL << 39),
Reactions = (1ULL << 40),
// For channels
ChannelAmIn = (1ULL << 40),
StickersSet = (1ULL << 41),
EmojiSet = (1ULL << 42),
ChannelLinkedChat = (1ULL << 43),
ChannelLocation = (1ULL << 44),
Slowmode = (1ULL << 45),
GroupCall = (1ULL << 46),
ChannelAmIn = (1ULL << 41),
StickersSet = (1ULL << 42),
EmojiSet = (1ULL << 43),
ChannelLinkedChat = (1ULL << 44),
ChannelLocation = (1ULL << 45),
Slowmode = (1ULL << 46),
GroupCall = (1ULL << 47),
// For iteration
LastUsedBit = (1ULL << 46),
LastUsedBit = (1ULL << 47),
};
using Flags = base::flags<Flag>;
friend inline constexpr auto is_flag_type(Flag) { return true; }

View File

@@ -716,7 +716,7 @@ bool ChatFilters::applyChange(ChatFilter &filter, ChatFilter &&updated) {
}
if (pinnedChanged) {
const auto filterList = _owner->chatsFilters().chatsList(id);
filterList->pinned()->applyList(wasFilter.pinned());
filterList->pinned()->applyList(filter.pinned());
}
if (chatlistChanged) {
_isChatlistChanged.fire_copy(id);

View File

@@ -57,12 +57,15 @@ struct CreditsHistoryEntry final {
QDateTime lastSaleDate;
PhotoId photoId = 0;
std::vector<CreditsHistoryMedia> extended;
uint64 credits = 0;
StarsAmount credits;
uint64 bareMsgId = 0;
uint64 barePeerId = 0;
uint64 bareGiveawayMsgId = 0;
uint64 bareGiftStickerId = 0;
uint64 bareActorId = 0;
StarsAmount starrefAmount;
int starrefCommission = 0;
uint64 starrefRecipientId = 0;
PeerType peerType;
QDateTime subscriptionUntil;
QDateTime successDate;
@@ -89,7 +92,7 @@ struct CreditsStatusSlice final {
using OffsetToken = QString;
std::vector<CreditsHistoryEntry> list;
std::vector<SubscriptionEntry> subscriptions;
uint64 balance = 0;
StarsAmount balance;
uint64 subscriptionsMissingBalance = 0;
bool allLoaded = false;
OffsetToken token;

View File

@@ -7,22 +7,21 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#pragma once
#include "core/stars_amount.h"
#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;
StarsAmount currentBalance;
StarsAmount availableBalance;
StarsAmount overallRevenue;
float64 usdRate = 0.;
bool isWithdrawalEnabled = false;
QDateTime nextWithdrawalAt;

View File

@@ -55,7 +55,8 @@ MTPInputReplyTo ReplyToForMTP(
? replyingToTopic->rootId()
: Data::ForumTopic::kGeneralId)
: (to ? to->topicRootId() : Data::ForumTopic::kGeneralId);
const auto replyToTopicId = to
const auto replyToTopicId = (to
&& (to->history() != history || to->id != replyingToTopicId))
? to->topicRootId()
: replyingToTopicId;
const auto external = replyTo.messageId

View File

@@ -2220,7 +2220,7 @@ void MessageReactions::scheduleSendPaid(
_paid->scheduledPrivacySet = anonymous.has_value();
}
if (count > 0) {
_item->history()->session().credits().lock(count);
_item->history()->session().credits().lock(StarsAmount(count));
}
_item->history()->owner().reactions().schedulePaid(_item);
}
@@ -2233,7 +2233,8 @@ void MessageReactions::cancelScheduledPaid() {
if (_paid) {
if (_paid->scheduledFlag) {
if (const auto amount = int(_paid->scheduled)) {
_item->history()->session().credits().unlock(amount);
_item->history()->session().credits().unlock(
StarsAmount(amount));
}
_paid->scheduled = 0;
_paid->scheduledFlag = 0;
@@ -2296,9 +2297,9 @@ void MessageReactions::finishPaidSending(
if (const auto amount = send.count) {
const auto credits = &_item->history()->session().credits();
if (success) {
credits->withdrawLocked(amount);
credits->withdrawLocked(StarsAmount(amount));
} else {
credits->unlock(amount);
credits->unlock(StarsAmount(amount));
}
}
}

View File

@@ -86,7 +86,9 @@ constexpr auto SpecialMsgIdShift = EndStoryMsgId.bare;
constexpr auto ShowAtTheEndMsgId = MsgId(SpecialMsgIdShift + 1);
constexpr auto SwitchAtTopMsgId = MsgId(SpecialMsgIdShift + 2);
constexpr auto ShowAndStartBotMsgId = MsgId(SpecialMsgIdShift + 4);
constexpr auto ShowAndMaybeStartBotMsgId = MsgId(SpecialMsgIdShift + 5);
constexpr auto ShowForChooseMessagesMsgId = MsgId(SpecialMsgIdShift + 6);
constexpr auto kSearchQueryOffsetHint = -1;
static_assert(SpecialMsgIdShift + 0xFF < 0);
static_assert(-(SpecialMsgIdShift + 0xFF) > ServerMaxMsgId);
@@ -221,4 +223,13 @@ struct hash<FullStoryId> {
}
};
template <>
struct hash<FullMsgId> {
size_t operator()(FullMsgId value) const {
return QtPrivate::QHashCombine().operator()(
std::hash<BareId>()(value.peer.value),
value.msg.bare);
}
};
} // namespace std

View File

@@ -26,6 +26,109 @@ constexpr auto kDefaultSearchTimeoutMs = crl::time(200);
} // namespace
MTPMessagesFilter PrepareSearchFilter(Storage::SharedMediaType type) {
using Type = Storage::SharedMediaType;
switch (type) {
case Type::Photo:
return MTP_inputMessagesFilterPhotos();
case Type::Video:
return MTP_inputMessagesFilterVideo();
case Type::PhotoVideo:
return MTP_inputMessagesFilterPhotoVideo();
case Type::MusicFile:
return MTP_inputMessagesFilterMusic();
case Type::File:
return MTP_inputMessagesFilterDocument();
case Type::VoiceFile:
return MTP_inputMessagesFilterVoice();
case Type::RoundVoiceFile:
return MTP_inputMessagesFilterRoundVoice();
case Type::RoundFile:
return MTP_inputMessagesFilterRoundVideo();
case Type::GIF:
return MTP_inputMessagesFilterGif();
case Type::Link:
return MTP_inputMessagesFilterUrl();
case Type::ChatPhoto:
return MTP_inputMessagesFilterChatPhotos();
case Type::Pinned:
return MTP_inputMessagesFilterPinned();
}
return MTP_inputMessagesFilterEmpty();
}
std::optional<GlobalMediaRequest> PrepareGlobalMediaRequest(
not_null<Main::Session*> session,
int32 offsetRate,
Data::MessagePosition offsetPosition,
Storage::SharedMediaType type,
const QString &query) {
const auto filter = PrepareSearchFilter(type);
if (query.isEmpty() && filter.type() == mtpc_inputMessagesFilterEmpty) {
return std::nullopt;
}
const auto minDate = 0;
const auto maxDate = 0;
const auto folderId = 0;
const auto limit = offsetPosition.fullId.peer
? kSharedMediaLimit
: kFirstSharedMediaLimit;
return MTPmessages_SearchGlobal(
MTP_flags(MTPmessages_SearchGlobal::Flag::f_folder_id), // No archive
MTP_int(folderId),
MTP_string(query),
filter,
MTP_int(minDate),
MTP_int(maxDate),
MTP_int(offsetRate),
(offsetPosition.fullId.peer
? session->data().peer(PeerId(offsetPosition.fullId.peer))->input
: MTP_inputPeerEmpty()),
MTP_int(offsetPosition.fullId.msg),
MTP_int(limit));
}
GlobalMediaResult ParseGlobalMediaResult(
not_null<Main::Session*> session,
const MTPmessages_Messages &data) {
auto result = GlobalMediaResult();
auto messages = (const QVector<MTPMessage>*)nullptr;
data.match([&](const MTPDmessages_messagesNotModified &) {
}, [&](const auto &data) {
session->data().processUsers(data.vusers());
session->data().processChats(data.vchats());
messages = &data.vmessages().v;
});
data.match([&](const MTPDmessages_messagesNotModified &) {
}, [&](const MTPDmessages_messages &data) {
result.fullCount = data.vmessages().v.size();
}, [&](const MTPDmessages_messagesSlice &data) {
result.fullCount = data.vcount().v;
result.offsetRate = data.vnext_rate().value_or_empty();
}, [&](const MTPDmessages_channelMessages &data) {
result.fullCount = data.vcount().v;
});
data.match([&](const MTPDmessages_channelMessages &data) {
LOG(("API Error: received messages.channelMessages when "
"no channel was passed! (ParseSearchResult)"));
}, [](const auto &) {});
const auto addType = NewMessageType::Existing;
result.messageIds.reserve(messages->size());
for (const auto &message : *messages) {
const auto item = session->data().addNewMessage(
message,
MessageFlags(),
addType);
if (item) {
result.messageIds.push_back(item->position());
}
}
return result;
}
std::optional<SearchRequest> PrepareSearchRequest(
not_null<PeerData*> peer,
MsgId topicRootId,
@@ -33,36 +136,7 @@ std::optional<SearchRequest> PrepareSearchRequest(
const QString &query,
MsgId messageId,
Data::LoadDirection direction) {
const auto filter = [&] {
using Type = Storage::SharedMediaType;
switch (type) {
case Type::Photo:
return MTP_inputMessagesFilterPhotos();
case Type::Video:
return MTP_inputMessagesFilterVideo();
case Type::PhotoVideo:
return MTP_inputMessagesFilterPhotoVideo();
case Type::MusicFile:
return MTP_inputMessagesFilterMusic();
case Type::File:
return MTP_inputMessagesFilterDocument();
case Type::VoiceFile:
return MTP_inputMessagesFilterVoice();
case Type::RoundVoiceFile:
return MTP_inputMessagesFilterRoundVoice();
case Type::RoundFile:
return MTP_inputMessagesFilterRoundVideo();
case Type::GIF:
return MTP_inputMessagesFilterGif();
case Type::Link:
return MTP_inputMessagesFilterUrl();
case Type::ChatPhoto:
return MTP_inputMessagesFilterChatPhotos();
case Type::Pinned:
return MTP_inputMessagesFilterPinned();
}
return MTP_inputMessagesFilterEmpty();
}();
const auto filter = PrepareSearchFilter(type);
if (query.isEmpty() && filter.type() == mtpc_inputMessagesFilterEmpty) {
return std::nullopt;
}

View File

@@ -19,6 +19,7 @@ class Session;
namespace Data {
enum class LoadDirection : char;
struct MessagePosition;
} // namespace Data
namespace Api {
@@ -36,6 +37,27 @@ using HistoryResult = SearchResult;
using HistoryRequest = MTPmessages_GetHistory;
using HistoryRequestResult = MTPmessages_Messages;
using GlobalMediaRequest = MTPmessages_SearchGlobal;
struct GlobalMediaResult {
std::vector<Data::MessagePosition> messageIds;
int32 offsetRate = 0;
int fullCount = 0;
};
[[nodiscard]] MTPMessagesFilter PrepareSearchFilter(
Storage::SharedMediaType type);
[[nodiscard]] std::optional<GlobalMediaRequest> PrepareGlobalMediaRequest(
not_null<Main::Session*> session,
int32 offsetRate,
Data::MessagePosition offsetPosition,
Storage::SharedMediaType type,
const QString &query);
[[nodiscard]] GlobalMediaResult ParseGlobalMediaResult(
not_null<Main::Session*> session,
const MTPmessages_Messages &data);
[[nodiscard]] std::optional<SearchRequest> PrepareSearchRequest(
not_null<PeerData*> peer,
MsgId topicRootId,

View File

@@ -1404,10 +1404,6 @@ void Session::forgetPassportCredentials() {
_passportCredentials = nullptr;
}
QString Session::nameSortKey(const QString &name) const {
return TextUtilities::RemoveAccents(name).toLower();
}
void Session::setupMigrationViewer() {
session().changes().peerUpdates(
PeerUpdate::Flag::Migration

View File

@@ -115,8 +115,6 @@ public:
return *_session;
}
[[nodiscard]] QString nameSortKey(const QString &name) const;
[[nodiscard]] Groups &groups() {
return _groups;
}

View File

@@ -36,6 +36,8 @@ using Options = base::flags<Option>;
namespace Data {
struct FileOrigin;
struct UploadState {
explicit UploadState(int64 size) : size(size) {
}
@@ -58,8 +60,6 @@ constexpr auto kVoiceMessageCacheTag = uint8(0x03);
constexpr auto kVideoMessageCacheTag = uint8(0x04);
constexpr auto kAnimationCacheTag = uint8(0x05);
struct FileOrigin;
} // namespace Data
struct MessageGroupId {
@@ -342,3 +342,29 @@ enum class MediaWebPageFlag : uint8 {
};
inline constexpr bool is_flag_type(MediaWebPageFlag) { return true; }
using MediaWebPageFlags = base::flags<MediaWebPageFlag>;
namespace Data {
enum class ForwardOptions {
PreserveInfo,
NoSenderNames,
NoNamesAndCaptions,
};
struct ForwardDraft {
MessageIdsList ids;
ForwardOptions options = ForwardOptions::PreserveInfo;
friend inline auto operator<=>(
const ForwardDraft&,
const ForwardDraft&) = default;
};
using ForwardDrafts = base::flat_map<MsgId, ForwardDraft>;
struct ResolvedForwardDraft {
HistoryItemsList items;
ForwardOptions options = ForwardOptions::PreserveInfo;
};
} // namespace Data

View File

@@ -205,6 +205,16 @@ void UserData::setBusinessDetails(Data::BusinessDetails details) {
session().changes().peerUpdated(this, UpdateFlag::BusinessDetails);
}
void UserData::setStarRefProgram(StarRefProgram program) {
const auto info = botInfo.get();
if (info && info->starRefProgram != program) {
info->starRefProgram = program;
session().changes().peerUpdated(
this,
Data::PeerUpdate::Flag::StarRefProgram);
}
}
ChannelId UserData::personalChannelId() const {
return _personalChannelId;
}
@@ -600,6 +610,8 @@ void ApplyUserUpdate(not_null<UserData*> user, const MTPDuserFull &update) {
}
if (const auto info = user->botInfo.get()) {
info->canManageEmojiStatus = update.is_bot_can_manage_emoji_status();
user->setStarRefProgram(
Data::ParseStarRefProgram(update.vstarref_program()));
}
if (const auto pinned = update.vpinned_msg_id()) {
SetTopPinnedMessageId(user, pinned->v);
@@ -719,4 +731,19 @@ void ApplyUserUpdate(not_null<UserData*> user, const MTPDuserFull &update) {
user->fullUpdated();
}
StarRefProgram ParseStarRefProgram(const MTPStarRefProgram *program) {
if (!program) {
return {};
}
auto result = StarRefProgram();
const auto &data = program->data();
result.commission = data.vcommission_permille().v;
result.durationMonths = data.vduration_months().value_or_empty();
result.revenuePerUser = data.vdaily_revenue_per_user()
? Data::FromTL(*data.vdaily_revenue_per_user())
: StarsAmount();
result.endDate = data.vend_date().value_or_empty();
return result;
}
} // namespace Data

View File

@@ -7,6 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#pragma once
#include "core/stars_amount.h"
#include "data/data_birthday.h"
#include "data/data_peer.h"
#include "data/data_chat_participant_status.h"
@@ -19,6 +20,17 @@ struct BotCommand;
struct BusinessDetails;
} // namespace Data
struct StarRefProgram {
StarsAmount revenuePerUser;
TimeId endDate = 0;
ushort commission = 0;
uint8 durationMonths = 0;
friend inline constexpr bool operator==(
StarRefProgram,
StarRefProgram) = default;
};
struct BotInfo {
BotInfo();
@@ -44,6 +56,8 @@ struct BotInfo {
ChatAdminRights groupAdminRights;
ChatAdminRights channelAdminRights;
StarRefProgram starRefProgram;
int version = 0;
int descriptionVersion = 0;
int activeUsers = 0;
@@ -206,6 +220,8 @@ public:
[[nodiscard]] const Data::BusinessDetails &businessDetails() const;
void setBusinessDetails(Data::BusinessDetails details);
void setStarRefProgram(StarRefProgram program);
[[nodiscard]] ChannelId personalChannelId() const;
[[nodiscard]] MsgId personalChannelMessageId() const;
void setPersonalChannel(ChannelId channelId, MsgId messageId);
@@ -253,4 +269,7 @@ namespace Data {
void ApplyUserUpdate(not_null<UserData*> user, const MTPDuserFull &update);
[[nodiscard]] StarRefProgram ParseStarRefProgram(
const MTPStarRefProgram *program);
} // namespace Data

View File

@@ -689,7 +689,11 @@ recentPeersSpecialName: PeerListItem(recentPeersItem) {
namePosition: point(64px, 19px);
}
dialogsTabsScroll: ScrollArea(defaultScrollArea) {
barHidden: true;
}
dialogsSearchTabs: SettingsSlider(defaultSettingsSlider) {
padding: 8px;
height: 33px;
barTop: 30px;
barSkip: 0px;
@@ -707,7 +711,6 @@ dialogsSearchTabs: SettingsSlider(defaultSettingsSlider) {
rippleBgActive: lightButtonBgOver;
ripple: defaultRippleAnimation;
}
dialogsSearchTabsPadding: 8px;
chatsFiltersTabs: SettingsSlider(dialogsSearchTabs) {
rippleBottomSkip: 0px;

View File

@@ -63,6 +63,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "window/window_controller.h"
#include "window/window_session_controller.h"
#include "window/window_peer_menu.h"
#include "ui/chat/chats_filter_tag.h"
#include "ui/effects/ripple_animation.h"
#include "ui/effects/loading_element.h"
#include "ui/widgets/multi_select.h"
@@ -129,7 +130,9 @@ constexpr auto kPreviewPostsLimit = 3;
if (const auto history = row->key().history()) {
if (const auto user = history->peer->asUser()) {
if (user->botInfo && user->botInfo->hasMainApp) {
return user;
if (!history->unreadCount() && !history->unreadMark()) {
return user;
}
}
}
}
@@ -315,6 +318,9 @@ InnerWidget::InnerWidget(
session().settings().archiveCollapsedChanges() | rpl::map_to(false),
session().data().chatsFilters().changed() | rpl::map_to(true)
) | rpl::start_with_next([=](bool refreshHeight) {
if (refreshHeight) {
_chatsFilterTags.clear();
}
if (refreshHeight && _filterId) {
// Height of the main list will be refreshed in other way.
_shownList->updateHeights(_narrowRatio);
@@ -1216,8 +1222,7 @@ void InnerWidget::paintEvent(QPaintEvent *e) {
const auto it = _rightButtons.find(user->id);
if (it == _rightButtons.end()) {
auto rightButton = RightButton();
const auto text
= tr::lng_profile_open_app_short(tr::now).toUpper();
const auto text = tr::lng_profile_open_app_short(tr::now);
rightButton.text.setText(st::dialogRowOpenBotTextStyle, text);
const auto size = QSize(
rightButton.text.maxWidth()
@@ -1944,7 +1949,10 @@ const std::vector<Key> &InnerWidget::pinnedChatsOrder() const {
}
void InnerWidget::checkReorderPinnedStart(QPoint localPosition) {
if (!_pressed || _dragging || _state != WidgetState::Default) {
if (!_pressed
|| _dragging
|| (_state != WidgetState::Default)
|| _pressedBotApp) {
return;
} else if (qAbs(localPosition.y() - _dragStart.y())
< style::ConvertScale(kStartReorderThreshold)) {
@@ -4274,34 +4282,12 @@ QImage *InnerWidget::cacheChatsFilterTag(
if (roundedText.isEmpty() || colorIndex < 0) {
return nullptr;
}
const auto &roundedFont = st::dialogRowFilterTagFont;
const auto roundedWidth = roundedFont->width(roundedText)
+ roundedFont->spacew * 3;
const auto rect = QRect(0, 0, roundedWidth, roundedFont->height);
auto cache = QImage(
rect.size() * style::DevicePixelRatio(),
QImage::Format_ARGB32_Premultiplied);
cache.setDevicePixelRatio(style::DevicePixelRatio());
cache.fill(Qt::transparent);
{
auto p = QPainter(&cache);
const auto pen = QPen(active
? st::dialogsBgActive
: Ui::EmptyUserpic::UserpicColor(colorIndex).color2);
p.setPen(Qt::NoPen);
p.setBrush(active
? st::dialogsTextFgActive->c
: anim::with_alpha(pen.color(), .15));
{
auto hq = PainterHighQualityEnabler(p);
const auto radius = roundedFont->height / 3.;
p.drawRoundedRect(rect, radius, radius);
}
p.setPen(pen);
p.setFont(roundedFont);
p.drawText(rect, roundedText, style::al_center);
}
return &_chatsFilterTags.emplace(key, std::move(cache)).first->second;
return &_chatsFilterTags.emplace(
key,
Ui::ChatsFilterTag(
std::move(roundedText),
Ui::EmptyUserpic::UserpicColor(colorIndex).color2->c,
active)).first->second;
}
bool InnerWidget::chooseHashtag() {

View File

@@ -121,8 +121,12 @@ struct EntryState {
FilterId filterId = 0;
FullReplyTo currentReplyTo;
friend inline auto operator<=>(EntryState, EntryState) noexcept
= default;
friend inline auto operator<=>(
const EntryState&,
const EntryState&) = default;
friend inline bool operator==(
const EntryState&,
const EntryState&) = default;
};
struct SearchState {

View File

@@ -696,16 +696,19 @@ void Widget::chosenRow(const ChosenRow &row) {
}
return;
} else if (const auto topic = row.key.topic()) {
auto params = Window::SectionShow(
Window::SectionShow::Way::ClearStack);
params.highlightPart.text = _searchState.query;
if (!params.highlightPart.empty()) {
params.highlightPartOffsetHint = kSearchQueryOffsetHint;
}
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);
controller()->showThread(topic, row.message.fullId.msg, params);
}
} else if (history
&& row.userpicClick
@@ -742,13 +745,16 @@ void Widget::chosenRow(const ChosenRow &row) {
const auto showAtMsgId = controller()->uniqueChatsInSearchResults()
? ShowAtUnreadMsgId
: row.message.fullId.msg;
auto params = Window::SectionShow(
Window::SectionShow::Way::ClearStack);
params.highlightPart.text = _searchState.query;
if (!params.highlightPart.empty()) {
params.highlightPartOffsetHint = kSearchQueryOffsetHint;
}
if (row.newWindow) {
controller()->showInNewWindow(peer, showAtMsgId);
} else {
controller()->showThread(
history,
showAtMsgId,
Window::SectionShow::Way::ClearStack);
controller()->showThread(history, showAtMsgId, params);
hideChildList();
}
} else if (const auto folder = row.key.folder()) {
@@ -1421,6 +1427,9 @@ void Widget::updateSuggestions(anim::type animated) {
controller(),
TopPeersContent(&session()),
RecentPeersContent(&session()));
_suggestions->clearSearchQueryRequests() | rpl::start_with_next([=] {
setSearchQuery(QString());
}, _suggestions->lifetime());
_searchSuggestionsLocked = false;
rpl::merge(
@@ -2928,7 +2937,11 @@ void Widget::updateCancelSearch() {
QString Widget::validateSearchQuery() {
const auto query = currentSearchQuery();
if (_searchState.tab == ChatSearchTab::PublicPosts) {
if (!_subsectionTopBar
&& _suggestions
&& _suggestions->consumeSearchQuery(query)) {
return QString();
} else if (_searchState.tab == ChatSearchTab::PublicPosts) {
if (_searchHashOrCashtag == HashOrCashtag::None) {
_searchHashOrCashtag = HashOrCashtag::Hashtag;
}
@@ -3265,6 +3278,9 @@ bool Widget::applySearchState(SearchState state) {
_openedForum && _searchState.inChat);
}
if (!_searchState.inChat && _searchState.query.isEmpty()) {
if (!_widthAnimationCache.isNull()) {
stopWidthAnimation();
}
setInnerFocus();
} else if (!_subsectionTopBar) {
_search->setFocus();
@@ -3923,9 +3939,18 @@ void Widget::setSearchQuery(const QString &query, int cursorPosition) {
}
bool Widget::cancelSearch(CancelSearchOptions options) {
const auto clearingSuggestionsQuery = _suggestions
&& _suggestions->consumeSearchQuery(QString());
if (clearingSuggestionsQuery) {
setSearchQuery(QString());
if (!options.forceFullCancel) {
return true;
}
}
cancelSearchRequest();
auto updatedState = _searchState;
const auto clearingQuery = !updatedState.query.isEmpty();
const auto clearingQuery = clearingSuggestionsQuery
|| !updatedState.query.isEmpty();
const auto forceFullCancel = options.forceFullCancel;
auto clearingInChat = (forceFullCancel || !clearingQuery)
&& (updatedState.inChat

View File

@@ -144,9 +144,7 @@ int PaintBadges(
int pinnedIconTop,
bool narrow) {
auto initial = right;
if (const auto used = PaintRightButton(p, context)) {
return used - st::dialogsUnreadPadding;
} else if (badgesState.unread
if (badgesState.unread
&& !badgesState.unreadCounter
&& context.st->unreadMarkDiameter > 0) {
const auto d = context.st->unreadMarkDiameter;
@@ -188,6 +186,8 @@ int PaintBadges(
: QString::number(badgesState.unreadCounter);
const auto badge = PaintUnreadBadge(p, counter, right, top, st);
right -= badge.width() + st.padding;
} else if (const auto used = PaintRightButton(p, context)) {
return used - st::dialogsUnreadPadding;
} else if (displayPinnedIcon) {
const auto &icon = ThreeStateIcon(
st::dialogsPinnedIcon,

View File

@@ -23,10 +23,16 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_user.h"
#include "dialogs/ui/chat_search_empty.h"
#include "history/history.h"
#include "info/downloads/info_downloads_widget.h"
#include "info/media/info_media_widget.h"
#include "info/info_controller.h"
#include "info/info_memento.h"
#include "info/info_wrap_widget.h"
#include "inline_bots/bot_attach_web_view.h"
#include "lang/lang_keys.h"
#include "main/main_session.h"
#include "settings/settings_common.h"
#include "storage/storage_shared_media.h"
#include "ui/boxes/confirm_box.h"
#include "ui/effects/ripple_animation.h"
#include "ui/text/text_utilities.h"
@@ -42,6 +48,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/delayed_activation.h"
#include "ui/dynamic_thumbnails.h"
#include "ui/painter.h"
#include "ui/search_field_controller.h"
#include "ui/unread_badge_paint.h"
#include "ui/ui_utility.h"
#include "window/window_separate_id.h"
@@ -60,6 +67,7 @@ constexpr auto kCollapsedChannelsCount = 5;
constexpr auto kProbablyMaxChannels = 1000;
constexpr auto kCollapsedAppsCount = 5;
constexpr auto kProbablyMaxApps = 100;
constexpr auto kSearchQueryDelay = crl::time(900);
class RecentRow final : public PeerListRow {
public:
@@ -194,7 +202,7 @@ RecentRow::RecentRow(not_null<PeerData*> peer)
if (user->botInfo && user->botInfo->hasMainApp) {
return std::make_unique<Ui::Text::String>(
st::dialogRowOpenBotTextStyle,
tr::lng_profile_open_app_short(tr::now).toUpper());
tr::lng_profile_open_app_short(tr::now));
}
}
return nullptr;
@@ -261,7 +269,7 @@ bool RecentRow::refreshBadge() {
}
QSize RecentRow::rightActionSize() const {
if (_mainAppText) {
if (_mainAppText && _badgeSize.isEmpty()) {
return QSize(
_mainAppText->maxWidth() + _mainAppText->minHeight(),
st::dialogRowOpenBotHeight);
@@ -270,7 +278,7 @@ QSize RecentRow::rightActionSize() const {
}
QMargins RecentRow::rightActionMargins() const {
if (_mainAppText) {
if (_mainAppText && _badgeSize.isEmpty()) {
return QMargins(
0,
st::dialogRowOpenBotRecentTop,
@@ -292,13 +300,14 @@ void RecentRow::rightActionPaint(
int outerWidth,
bool selected,
bool actionSelected) {
if (_mainAppText) {
if (_mainAppText && _badgeSize.isEmpty()) {
const auto size = RecentRow::rightActionSize();
p.setPen(Qt::NoPen);
p.setBrush(actionSelected
? st::activeButtonBgOver
: st::activeButtonBg);
const auto radius = size.height() / 2;
auto hq = PainterHighQualityEnabler(p);
p.drawRoundedRect(QRect(QPoint(x, y), size), radius, radius);
if (_actionRipple) {
_actionRipple->paint(p, x, y, outerWidth);
@@ -335,13 +344,13 @@ void RecentRow::rightActionPaint(
}
bool RecentRow::rightActionDisabled() const {
return !_mainAppText;
return !_mainAppText || !_badgeSize.isEmpty();
}
void RecentRow::rightActionAddRipple(
QPoint point,
Fn<void()> updateCallback) {
if (!_mainAppText) {
if (!_mainAppText || !_badgeSize.isEmpty()) {
return;
}
if (!_actionRipple) {
@@ -1291,7 +1300,23 @@ Suggestions::Suggestions(
RecentPeersList recentPeers)
: RpWidget(parent)
, _controller(controller)
, _tabs(std::make_unique<Ui::SettingsSlider>(this, st::dialogsSearchTabs))
, _tabsScroll(
std::make_unique<Ui::ScrollArea>(this, st::dialogsTabsScroll, true))
, _tabs(
_tabsScroll->setOwnedWidget(
object_ptr<Ui::SettingsSlider>(this, st::dialogsSearchTabs)))
, _tabKeys{
{ Tab::Chats },
{ Tab::Channels },
{ Tab::Apps },
{ Tab::Media, MediaType::Photo },
{ Tab::Media, MediaType::Video },
{ Tab::Downloads },
{ Tab::Media, MediaType::Link },
{ Tab::Media, MediaType::File },
{ Tab::Media, MediaType::MusicFile },
{ Tab::Media, MediaType::RoundVoiceFile },
}
, _chatsScroll(std::make_unique<Ui::ElasticScroll>(this))
, _chatsContent(
_chatsScroll->setOwnedWidget(object_ptr<Ui::VerticalLayout>(this)))
@@ -1312,8 +1337,8 @@ Suggestions::Suggestions(
, _appsContent(
_appsScroll->setOwnedWidget(object_ptr<Ui::VerticalLayout>(this)))
, _recentApps(setupRecentApps())
, _popularApps(setupPopularApps()) {
, _popularApps(setupPopularApps())
, _searchQueryTimer([=] { applySearchQuery(); }) {
setupTabs();
setupChats();
setupChannels();
@@ -1323,10 +1348,46 @@ Suggestions::Suggestions(
Suggestions::~Suggestions() = default;
void Suggestions::setupTabs() {
_tabsScroll->setCustomWheelProcess([=](not_null<QWheelEvent*> e) {
const auto pixelDelta = e->pixelDelta();
const auto angleDelta = e->angleDelta();
if (std::abs(pixelDelta.x()) + std::abs(angleDelta.x())) {
return false;
}
const auto y = pixelDelta.y() ? pixelDelta.y() : angleDelta.y();
_tabsScroll->scrollToX(_tabsScroll->scrollLeft() - y);
return true;
});
const auto scrollToIndex = [=](int index, anim::type type) {
const auto to = index
? (_tabs->centerOfSection(index) - _tabsScroll->width() / 2)
: 0;
_tabsScrollAnimation.stop();
if (type == anim::type::instant) {
_tabsScroll->scrollToX(to);
} else {
_tabsScrollAnimation.start(
[=](float64 v) { _tabsScroll->scrollToX(v); },
_tabsScroll->scrollLeft(),
std::min(to, _tabsScroll->scrollLeftMax()),
st::defaultTabsSlider.duration);
}
};
rpl::single(-1) | rpl::then(
_tabs->sectionActivated()
) | rpl::combine_previous(
) | rpl::start_with_next([=](int was, int index) {
if (was != index) {
scrollToIndex(index, anim::type::normal);
}
}, _tabs->lifetime());
const auto shadow = Ui::CreateChild<Ui::PlainShadow>(this);
shadow->lower();
_tabs->move(st::dialogsSearchTabsPadding, 0);
_tabsScroll->move(0, 0);
_tabs->move(0, 0);
rpl::combine(
widthValue(),
_tabs->heightValue()
@@ -1335,20 +1396,37 @@ void Suggestions::setupTabs() {
shadow->setGeometry(0, height - line, width, line);
}, shadow->lifetime());
shadow->showOn(_tabs->shownValue());
shadow->showOn(_tabsScroll->shownValue());
_tabs->setSections({
tr::lng_recent_chats(tr::now),
tr::lng_recent_channels(tr::now),
tr::lng_recent_apps(tr::now),
});
const auto labels = base::flat_map<Key, QString>{
{ Key{ Tab::Chats }, tr::lng_recent_chats(tr::now) },
{ Key{ Tab::Channels }, tr::lng_recent_channels(tr::now) },
{ Key{ Tab::Apps }, tr::lng_recent_apps(tr::now) },
{ Key{ Tab::Media, MediaType::Photo }, tr::lng_all_photos(tr::now) },
{ Key{ Tab::Media, MediaType::Video }, tr::lng_all_videos(tr::now) },
{ Key{ Tab::Downloads }, tr::lng_all_downloads(tr::now) },
{ Key{ Tab::Media, MediaType::Link }, tr::lng_all_links(tr::now) },
{ Key{ Tab::Media, MediaType::File }, tr::lng_all_files(tr::now) },
{
Key{ Tab::Media, MediaType::MusicFile },
tr::lng_all_music(tr::now),
},
{
Key{ Tab::Media, MediaType::RoundVoiceFile },
tr::lng_all_voice(tr::now),
},
};
auto sections = std::vector<QString>();
for (const auto key : _tabKeys) {
const auto i = labels.find(key);
Assert(i != end(labels));
sections.push_back(i->second);
}
_tabs->setSections(sections);
_tabs->sectionActivated(
) | rpl::start_with_next([=](int section) {
switchTab(section == 2
? Tab::Apps
: section
? Tab::Channels
: Tab::Chats);
Assert(section >= 0 && section < _tabKeys.size());
switchTab(_tabKeys[section]);
}, _tabs->lifetime());
}
@@ -1418,7 +1496,7 @@ void Suggestions::setupChats() {
_chatsScroll->viewportEvent(e);
}, _topPeers->lifetime());
_chatsScroll->setVisible(_tab.current() == Tab::Chats);
_chatsScroll->setVisible(_key.current().tab == Tab::Chats);
_chatsScroll->setCustomTouchProcess(_recent->processTouch);
}
@@ -1452,7 +1530,7 @@ void Suggestions::setupChannels() {
rpl::mappers::_1 + rpl::mappers::_2 == 0),
anim::type::instant);
_channelsScroll->setVisible(_tab.current() == Tab::Channels);
_channelsScroll->setVisible(_key.current().tab == Tab::Channels);
_channelsScroll->setCustomTouchProcess([=](not_null<QTouchEvent*> e) {
const auto myChannels = _myChannels->processTouch(e);
const auto recommendations = _recommendations->processTouch(e);
@@ -1469,7 +1547,7 @@ void Suggestions::setupApps() {
_popularApps->wrap->toggle(count > 0, anim::type::instant);
}, _popularApps->wrap->lifetime());
_appsScroll->setVisible(_tab.current() == Tab::Apps);
_appsScroll->setVisible(_key.current().tab == Tab::Apps);
_appsScroll->setCustomTouchProcess([=](not_null<QTouchEvent*> e) {
const auto recentApps = _recentApps->processTouch(e);
const auto popularApps = _popularApps->processTouch(e);
@@ -1478,12 +1556,11 @@ void Suggestions::setupApps() {
}
void Suggestions::selectJump(Qt::Key direction, int pageSize) {
switch (_tab.current()) {
switch (_key.current().tab) {
case Tab::Chats: selectJumpChats(direction, pageSize); return;
case Tab::Channels: selectJumpChannels(direction, pageSize); return;
case Tab::Apps: selectJumpApps(direction, pageSize); return;
}
Unexpected("Tab in Suggestions::selectJump.");
}
void Suggestions::selectJumpChats(Qt::Key direction, int pageSize) {
@@ -1661,7 +1738,7 @@ void Suggestions::selectJumpApps(Qt::Key direction, int pageSize) {
}
void Suggestions::chooseRow() {
switch (_tab.current()) {
switch (_key.current().tab) {
case Tab::Chats:
if (!_topPeers->chooseRow()) {
_recent->choose();
@@ -1680,10 +1757,49 @@ void Suggestions::chooseRow() {
}
}
bool Suggestions::consumeSearchQuery(const QString &query) {
using Type = MediaType;
const auto key = _key.current();
const auto tab = key.tab;
const auto type = (key.tab == Tab::Media) ? key.mediaType : Type::kCount;
if (tab != Tab::Downloads
&& type != Type::File
&& type != Type::Link
&& type != Type::MusicFile) {
return false;
} else if (_searchQuery == query) {
return false;
}
_searchQuery = query;
_persist = !_searchQuery.isEmpty();
if (query.isEmpty() || tab == Tab::Downloads) {
_searchQueryTimer.cancel();
applySearchQuery();
} else {
_searchQueryTimer.callOnce(kSearchQueryDelay);
}
return true;
}
void Suggestions::applySearchQuery() {
const auto key = _key.current();
const auto controller = _mediaLists[key].wrap->controller();
const auto search = controller->searchFieldController();
if (search->query() != _searchQuery) {
search->setQuery(_searchQuery);
}
}
rpl::producer<> Suggestions::clearSearchQueryRequests() const {
return _clearSearchQueryRequests.events();
}
Data::Thread *Suggestions::updateFromParentDrag(QPoint globalPosition) {
return (_tab.current() == Tab::Chats)
? updateFromChatsDrag(globalPosition)
: updateFromChannelsDrag(globalPosition);
switch (_key.current().tab) {
case Tab::Chats: return updateFromChatsDrag(globalPosition);
case Tab::Channels: return updateFromChannelsDrag(globalPosition);
}
return nullptr;
}
Data::Thread *Suggestions::updateFromChatsDrag(QPoint globalPosition) {
@@ -1744,39 +1860,71 @@ void Suggestions::hide(anim::type animated, Fn<void()> finish) {
}
}
void Suggestions::switchTab(Tab tab) {
const auto was = _tab.current();
if (was == tab) {
void Suggestions::switchTab(Key key) {
const auto was = _key.current();
if (was == key) {
return;
}
_tab = tab;
consumeSearchQuery(QString());
_key = key;
_persist = false;
_clearSearchQueryRequests.fire({});
if (_tabs->isHidden()) {
return;
}
startSlideAnimation(was, tab);
startSlideAnimation(was, key);
}
void Suggestions::startSlideAnimation(Tab was, Tab now) {
if (!_slideAnimation.animating()) {
_slideLeft = (was == Tab::Chats || now == Tab::Chats)
? Ui::GrabWidget(_chatsScroll.get())
: Ui::GrabWidget(_channelsScroll.get());
_slideLeftTop = (was == Tab::Chats || now == Tab::Chats)
? _chatsScroll->y()
: _channelsScroll->y();
_slideRight = (was == Tab::Apps || now == Tab::Apps)
? Ui::GrabWidget(_appsScroll.get())
: Ui::GrabWidget(_channelsScroll.get());
_slideRightTop = (was == Tab::Apps || now == Tab::Apps)
? _appsScroll->y()
: _channelsScroll->y();
_chatsScroll->hide();
_channelsScroll->hide();
_appsScroll->hide();
void Suggestions::ensureContent(Key key) {
if (key.tab != Tab::Downloads && key.tab != Tab::Media) {
return;
}
const auto from = (now > was) ? 0. : 1.;
const auto to = (now > was) ? 1. : 0.;
auto &list = _mediaLists[key];
if (list.wrap) {
return;
}
const auto self = _controller->session().user();
const auto memento = (key.tab == Tab::Downloads)
? Info::Downloads::Make(self)
: std::make_shared<Info::Memento>(
self,
Info::Section(key.mediaType, Info::Section::Type::GlobalMedia));
list.wrap = Ui::CreateChild<Info::WrapWidget>(
this,
_controller,
Info::Wrap::Search,
memento.get());
list.wrap->show();
updateControlsGeometry();
}
void Suggestions::startSlideAnimation(Key was, Key now) {
ensureContent(now);
const auto wasIndex = ranges::find(_tabKeys, was);
const auto nowIndex = ranges::find(_tabKeys, now);
if (!_slideAnimation.animating()) {
const auto find = [&](Key key) -> not_null<QWidget*> {
switch (key.tab) {
case Tab::Chats: return _chatsScroll.get();
case Tab::Channels: return _channelsScroll.get();
case Tab::Apps: return _appsScroll.get();
}
return _mediaLists[key].wrap;
};
auto left = find(was);
auto right = find(now);
if (wasIndex > nowIndex) {
std::swap(left, right);
}
_slideLeft = Ui::GrabWidget(left);
_slideLeftTop = left->y();
_slideRight = Ui::GrabWidget(right);
_slideRightTop = right->y();
left->hide();
right->hide();
}
const auto from = (nowIndex > wasIndex) ? 0. : 1.;
const auto to = (nowIndex > wasIndex) ? 1. : 0.;
_slideAnimation.start([=] {
update();
if (!_slideAnimation.animating() && !_shownAnimation.animating()) {
@@ -1807,10 +1955,13 @@ void Suggestions::startShownAnimation(bool shown, Fn<void()> finish) {
resize(now, height());
}
}
_tabs->hide();
_tabsScroll->hide();
_chatsScroll->hide();
_channelsScroll->hide();
_appsScroll->hide();
for (const auto &[key, list] : _mediaLists) {
list.wrap->hide();
}
_slideAnimation.stop();
}
@@ -1822,11 +1973,14 @@ void Suggestions::finishShow() {
_shownAnimation.stop();
_cache = QPixmap();
_tabs->show();
const auto tab = _tab.current();
_chatsScroll->setVisible(tab == Tab::Chats);
_channelsScroll->setVisible(tab == Tab::Channels);
_appsScroll->setVisible(tab == Tab::Apps);
_tabsScroll->show();
const auto key = _key.current();
_chatsScroll->setVisible(key == Key{ Tab::Chats });
_channelsScroll->setVisible(key == Key{ Tab::Channels });
_appsScroll->setVisible(key == Key{ Tab::Apps });
for (const auto &[mediaKey, list] : _mediaLists) {
list.wrap->setVisible(key == mediaKey);
}
}
float64 Suggestions::shownOpacity() const {
@@ -1846,7 +2000,7 @@ void Suggestions::paintEvent(QPaintEvent *e) {
p.drawPixmap(0, (opacity - 1.) * slide, _cache);
} else if (!_slideLeft.isNull()) {
const auto slide = st::topPeers.height + st::searchedBarHeight;
const auto right = (_tab.current() == Tab::Channels);
const auto right = (_key.current().tab == Tab::Channels);
const auto progress = _slideAnimation.value(right ? 1. : 0.);
p.setOpacity(1. - progress);
p.drawPixmap(
@@ -1862,18 +2016,39 @@ void Suggestions::paintEvent(QPaintEvent *e) {
}
void Suggestions::resizeEvent(QResizeEvent *e) {
const auto w = std::max(width(), st::columnMinimalWidthLeft);
_tabs->resizeToWidth(w);
const auto tabs = _tabs->height();
updateControlsGeometry();
}
_chatsScroll->setGeometry(0, tabs, w, height() - tabs);
void Suggestions::updateControlsGeometry() {
const auto w = std::max(width(), st::columnMinimalWidthLeft);
_tabs->fitWidthToSections();
const auto tabs = _tabs->height();
_tabsScroll->setGeometry(0, 0, w, tabs);
const auto content = QRect(0, tabs, w, height() - tabs);
_chatsScroll->setGeometry(content);
_chatsContent->resizeToWidth(w);
_channelsScroll->setGeometry(0, tabs, w, height() - tabs);
_channelsScroll->setGeometry(content);
_channelsContent->resizeToWidth(w);
_appsScroll->setGeometry(0, tabs, w, height() - tabs);
_appsScroll->setGeometry(content);
_appsContent->resizeToWidth(w);
const auto expanding = false;
for (const auto &[key, list] : _mediaLists) {
const auto full = !list.wrap->scrollBottomSkip();
const auto additionalScroll = (full ? st::boxRadius : 0);
const auto height = content.height() - (full ? 0 : st::boxRadius);
const auto wrapGeometry = QRect{ 0, tabs, w, height};
list.wrap->updateGeometry(
wrapGeometry,
expanding,
additionalScroll,
content.height());
}
}
auto Suggestions::setupRecentPeers(RecentPeersList recentPeers)
@@ -2027,8 +2202,8 @@ auto Suggestions::setupRecommendations() -> std::unique_ptr<ObjectList> {
_persist = true;
}, list->lifetime());
_tab.value() | rpl::filter(
rpl::mappers::_1 == Tab::Channels
_key.value() | rpl::filter(
rpl::mappers::_1 == Key{ Tab::Channels }
) | rpl::start_with_next([=] {
controller->load();
}, list->lifetime());
@@ -2142,8 +2317,8 @@ auto Suggestions::setupPopularApps() -> std::unique_ptr<ObjectList> {
_persist = true;
}, list->lifetime());
_tab.value() | rpl::filter(
rpl::mappers::_1 == Tab::Apps
_key.value() | rpl::filter(
rpl::mappers::_1 == Key{ Tab::Apps }
) | rpl::start_with_next([=] {
controller->load();
}, list->lifetime());

View File

@@ -8,6 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#pragma once
#include "base/object_ptr.h"
#include "base/timer.h"
#include "dialogs/ui/top_peers_strip.h"
#include "ui/effects/animations.h"
#include "ui/rp_widget.h"
@@ -18,12 +19,21 @@ namespace Data {
class Thread;
} // namespace Data
namespace Info {
class WrapWidget;
} // namespace Info
namespace Main {
class Session;
} // namespace Main
namespace Storage {
enum class SharedMediaType : signed char;
} // namespace Storage
namespace Ui {
class BoxContent;
class ScrollArea;
class ElasticScroll;
class SettingsSlider;
class VerticalLayout;
@@ -55,6 +65,9 @@ public:
void selectJump(Qt::Key direction, int pageSize = 0);
void chooseRow();
bool consumeSearchQuery(const QString &query);
[[nodiscard]] rpl::producer<> clearSearchQueryRequests() const;
[[nodiscard]] Data::Thread *updateFromParentDrag(QPoint globalPosition);
void dragLeft();
@@ -96,10 +109,13 @@ public:
class ObjectListController;
private:
using MediaType = Storage::SharedMediaType;
enum class Tab : uchar {
Chats,
Channels,
Apps,
Media,
Downloads,
};
enum class JumpResult : uchar {
NotApplied,
@@ -107,6 +123,14 @@ private:
AppliedAndOut,
};
struct Key {
Tab tab = Tab::Chats;
MediaType mediaType = {};
friend inline auto operator<=>(Key, Key) = default;
friend inline bool operator==(Key, Key) = default;
};
struct ObjectList {
not_null<Ui::SlideWrap<PeerListContent>*> wrap;
rpl::variable<int> count;
@@ -118,6 +142,11 @@ private:
rpl::event_stream<not_null<PeerData*>> chosen;
};
struct MediaList {
Info::WrapWidget *wrap = nullptr;
rpl::variable<int> count;
};
void paintEvent(QPaintEvent *e) override;
void resizeEvent(QResizeEvent *e) override;
@@ -160,17 +189,23 @@ private:
SearchEmptyIcon icon,
rpl::producer<QString> text);
void switchTab(Tab tab);
void switchTab(Key key);
void startShownAnimation(bool shown, Fn<void()> finish);
void startSlideAnimation(Tab was, Tab now);
void startSlideAnimation(Key was, Key now);
void ensureContent(Key key);
void finishShow();
void handlePressForChatPreview(PeerId id, Fn<void(bool)> callback);
void updateControlsGeometry();
void applySearchQuery();
const not_null<Window::SessionController*> _controller;
const std::unique_ptr<Ui::SettingsSlider> _tabs;
rpl::variable<Tab> _tab = Tab::Chats;
const std::unique_ptr<Ui::ScrollArea> _tabsScroll;
const not_null<Ui::SettingsSlider*> _tabs;
Ui::Animations::Simple _tabsScrollAnimation;
const std::vector<Key> _tabKeys;
rpl::variable<Key> _key;
const std::unique_ptr<Ui::ElasticScroll> _chatsScroll;
const not_null<Ui::VerticalLayout*> _chatsContent;
@@ -200,6 +235,11 @@ private:
const std::unique_ptr<ObjectList> _recentApps;
const std::unique_ptr<ObjectList> _popularApps;
base::flat_map<Key, MediaList> _mediaLists;
rpl::event_stream<> _clearSearchQueryRequests;
QString _searchQuery;
base::Timer _searchQueryTimer;
Ui::Animations::Simple _shownAnimation;
Fn<void()> _showFinished;
bool _hidden = false;

View File

@@ -920,6 +920,7 @@ StoriesSlice ParseStoriesSlice(
auto content = photo
? ParsePhoto(*photo, suggestedPath)
: Photo();
content.spoilered = data.is_spoiler();
media.content = content;
}, [&](const MTPDmessageMediaDocument &data) {
const auto document = data.vdocument();
@@ -948,6 +949,7 @@ StoriesSlice ParseStoriesSlice(
date,
extension);
content.thumb.file.suggestedPath = path + "_thumb.jpg";
content.spoilered = data.is_spoiler();
media.content = content;
}, [&](const auto &data) {
media.content = UnsupportedMedia();
@@ -1329,6 +1331,7 @@ Media ParseMedia(
+ "photos/"
+ PreparePhotoFileName(++context.photos, date))
: Photo();
content.spoilered = data.is_spoiler();
if (const auto ttl = data.vttl_seconds()) {
result.ttl = ttl->v;
content.image.file = File();
@@ -1349,6 +1352,7 @@ Media ParseMedia(
result.ttl = ttl->v;
content.file = File();
}
content.spoilered = data.is_spoiler();
result.content = content;
}, [&](const MTPDmessageMediaWebPage &data) {
// Ignore web pages.

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