Compare commits

..

164 Commits

Author SHA1 Message Date
John Preston
e6405bc455 Beta version 5.4.2.
- Select audio devices from one-on-one call window.
- Bug fixes and other minor improvements.
- New text layout testing.
2024-08-28 21:50:05 +04:00
John Preston
fed09461ce Fix spaces after newline rendering. 2024-08-28 19:16:55 +04:00
John Preston
6e1ddef4fe Fix strings for group profile colors. 2024-08-28 17:45:29 +04:00
John Preston
76be4a3eb9 Fix build on Windows. 2024-08-28 17:45:18 +04:00
John Preston
997a9e2fe3 Fix text elision under spoiler. 2024-08-28 17:29:43 +04:00
23rd
03790f3da0 Removed include directive for ui_utility from rp_widget header. 2024-08-28 15:15:58 +03:00
23rd
e46a703c7d Removed include directive for ui/integration from ui_utility header. 2024-08-28 14:39:05 +03:00
23rd
ae17acdfd4 Removed include directive for ui_utility from rp_widget header. 2024-08-28 14:38:33 +03:00
23rd
2422c9ce9e Moved out Ui::MakeWeak to separate file. 2024-08-28 14:37:56 +03:00
23rd
7f75d7082a Moved out Ui::CreateChild to separate file. 2024-08-28 14:37:07 +03:00
23rd
4749ab175e Fixed build with Apple clang. 2024-08-28 14:37:07 +03:00
John Preston
e651699e1d Fix thumbnailed document layout. 2024-08-28 14:58:21 +04:00
John Preston
3f4157bab2 Fix crash in unlock with chat in separate window.
Fixes #28306, fixes #28319.
2024-08-28 14:58:21 +04:00
John Preston
a98f559066 Add test_text test app. 2024-08-28 14:58:09 +04:00
John Preston
ffd54c452c Support DirectManipulation in chat preview. 2024-08-28 14:58:02 +04:00
John Preston
61424eeab9 Fix build with Qt 5. 2024-08-28 14:57:49 +04:00
John Preston
c131d6637d Don't force LTR direction on texts. 2024-08-28 14:57:42 +04:00
John Preston
24b0b33f1d Wrap names in LRM/RLM for correct layout. 2024-08-28 14:55:41 +04:00
23rd
675198d361 Fixed gradient in Ui::Premium::BubbleWidget with large ui scale. 2024-08-28 12:38:07 +03:00
23rd
ed667a42ad Moved out MainWidget::clearBotStartToken to static function. 2024-08-28 09:49:53 +03:00
23rd
920484d540 Fixed update of submit way in all opened windows. 2024-08-28 09:32:39 +03:00
23rd
07c5e6542b Replaced Info::Controller with navigation in box for giveaway creation. 2024-08-28 09:10:10 +03:00
23rd
6a43f2e508 Fixed display of round edges in box for giveaway creation. 2024-08-28 09:06:44 +03:00
23rd
ad3e447f08 Added ability to player to go to message for songs from another session. 2024-08-28 08:50:28 +03:00
23rd
1840da1d68 Moved out creation of button for paging of stats lists to single place. 2024-08-27 18:48:19 +03:00
23rd
c82b86cea3 Added ability to convert scale float values. 2024-08-27 18:48:19 +03:00
23rd
0fb383c466 Added ability to block bot from moderate box. 2024-08-27 18:27:15 +03:00
23rd
69fc2f48bf Removed unused include directives from MainWidget. 2024-08-27 18:27:15 +03:00
23rd
7c604fc86a Added ability to send GIF with caption. 2024-08-27 18:27:15 +03:00
23rd
ec7fbb8952 Added ability to provide text with tags when send existing document. 2024-08-22 17:10:17 +03:00
23rd
679350e23d Removed unused simple sendExistingDocument methods from sections. 2024-08-22 17:10:12 +03:00
23rd
f0209c9d6e Removed unused MainWidget::sendExistingDocument. 2024-08-22 17:10:06 +03:00
23rd
5020aec6ec Moved out AddEmojiToggleToField to separate file. 2024-08-22 17:08:25 +03:00
23rd
13737577e7 Fixed updating over state in history widget when edit spoiler of media. 2024-08-21 20:00:48 +03:00
23rd
4ee7d46d78 Improved static infinite radial animation in panel of webview bot. 2024-08-21 19:25:58 +03:00
23rd
d143e32022 Fixed loading of credits history list with offset. 2024-08-21 18:26:24 +03:00
23rd
0a0dab74a1 Added ability to rejoin to subscribed channel after left. 2024-08-21 17:46:36 +03:00
23rd
c6ea91e671 Replaced AdminLog::FilterBox with GenericBox. 2024-08-21 17:18:12 +03:00
23rd
8011adb219 Replaced bool with std::optional in AdminLog::FilterValue. 2024-08-21 17:18:12 +03:00
23rd
6ba5d5f16b Removed old AdminLog::FilterBox. 2024-08-21 17:18:12 +03:00
23rd
d5774830d8 Added ability to provide data to Ui::ExpandablePeerListController. 2024-08-21 17:18:12 +03:00
23rd
eb268102fc Moved out functions to create expandable moderate list to separate file. 2024-08-21 17:18:12 +03:00
23rd
b364c4f23a Moved out graphic of button for expandable moderate list to view. 2024-08-21 17:18:12 +03:00
23rd
91b9266a91 Moved out lambdas to create expandable moderate list to functions. 2024-08-21 17:18:12 +03:00
23rd
1e6236a987 Wrapped filter value labels with EditFlagsControl. 2024-08-21 17:18:12 +03:00
23rd
10ebd7e6ef Moved out AdminLog::FilterValue to separate file. 2024-08-21 17:18:12 +03:00
John Preston
6b83c52c7c Improve top senders rebuild.
Fixes #28296.
2024-08-21 10:54:30 +02:00
John Preston
46304c7a2d Fix build in Release. 2024-08-21 09:35:46 +02:00
John Preston
c3fda41224 Check for local URLs more strictly. 2024-08-21 09:35:46 +02:00
John Preston
a4017e930e Add device selectors to one-on-one calls. 2024-08-21 09:35:46 +02:00
John Preston
95bdb925d5 Fix paddings in text messages disabler. 2024-08-20 16:10:27 +02:00
23rd
a26bae70c7 Added Enter shortcut to EditForumTopicBox for confirmation. 2024-08-19 12:53:42 +03:00
23rd
a38dcb6ee5 Improved color of topic icon in TopicIconView. 2024-08-19 12:02:12 +03:00
23rd
eb27c12117 Added icon and name of contact to deletion confirmation box. 2024-08-18 21:53:12 +03:00
23rd
c03fcf9a23 Added icon and name of topic to deletion confirmation box. 2024-08-18 21:44:34 +03:00
23rd
71f83b5993 Made creation of title with userpic more generic. 2024-08-18 21:44:23 +03:00
23rd
fd2d12d6b1 Removed time shift when reschedule history item. 2024-08-18 20:32:21 +03:00
23rd
50f9f36c3d Fixed padding for history entries in channel earn section. 2024-08-18 13:02:39 +03:00
23rd
f650c679e0 Improved text padding in ShowOrPremiumBox. 2024-08-18 12:41:00 +03:00
23rd
8349cb0dd4 Removed currency column from channel earn section when it's empty. 2024-08-18 12:29:56 +03:00
John Preston
2c1788a63a Version 5.4.1.
- Fix crash when sending Star Reaction in comments.
- Fix loading "Send As" channels in a channel.
- Place Star Reaction always first in list.
- Removed paid Invite Links in groups.
2024-08-17 18:14:54 +02:00
John Preston
eec59611ef Fix leaving forum glitch. 2024-08-17 17:09:49 +02:00
23rd
bfbdf1b935 Removed currency stat from channel earn section when it's not available. 2024-08-16 14:07:55 +03:00
John Preston
f2ea0edc95 Fix link confirmation box in media viewer. 2024-08-16 11:16:35 +02:00
John Preston
754b3a5ae8 Fix bottom text and info layout in albums. 2024-08-16 11:16:18 +02:00
John Preston
62a20ba975 Fix incorrect window activation on message jump.
Fixes #28275.
2024-08-16 10:57:30 +02:00
John Preston
f3dca6efb7 Show correct signature in send-as channel posts. 2024-08-16 10:54:02 +02:00
John Preston
dc1df14a71 Fix send_as appearance in channels. 2024-08-16 09:45:17 +02:00
John Preston
73c018667d Place star reaction always first. 2024-08-16 09:11:25 +02:00
23rd
3905fc7c38 Slightly improved input of credits with large numbers for invite links. 2024-08-15 16:18:33 +03:00
23rd
0c3cabf4ac Removed interface for subscription links from mega groups. 2024-08-15 16:18:33 +03:00
23rd
838d5669ed Replaced api for credits rate. 2024-08-15 16:18:33 +03:00
John Preston
3a7a485dd0 Fix switching profiles in a channel. 2024-08-15 10:45:55 +02:00
John Preston
a4ac00acbd Allow sending star reactions to discussion roots. 2024-08-15 10:14:30 +02:00
John Preston
7d52c13625 Fix focus search on topic open. 2024-08-15 09:48:59 +02:00
John Preston
520de600a0 Fix manifest for Windows Store version. 2024-08-15 08:19:18 +02:00
23rd
4f8e914d53 Added button to media viewer for photo from sponsored messages. 2024-08-15 00:46:58 +03:00
John Preston
79c8c5ec5d Version 5.4.
- Super Channels with post authors' profiles.
- Support favorite channels with Star Reactions.
- Channel Subscriptions with Stars monthly payments.
2024-08-14 23:32:55 +03:00
John Preston
3548aba652 Fix build with GCC. 2024-08-14 23:32:55 +03:00
John Preston
5d1812efc8 Fix build on Windows. 2024-08-14 19:57:38 +02:00
23rd
0477d43fbb Added button to media viewer for media from sponsored messages. 2024-08-14 20:51:01 +03:00
John Preston
73609fe5b2 Fix build on macOS. 2024-08-14 19:02:31 +02:00
John Preston
e027196c8a Don't ask to download CMake on Windows.
The one from Visual Studio works just fine now.
2024-08-14 18:42:52 +02:00
23rd
6d00165e5a Fixed text state of media in sponsored messages. 2024-08-14 18:17:53 +02:00
John Preston
693eaf9262 Allow tonsite:// custom links. 2024-08-14 18:17:53 +02:00
John Preston
3549349ffb Improve layout for the first star reaction. 2024-08-14 18:17:53 +02:00
John Preston
fac20e436d Nice top-up / star sent toasts. 2024-08-14 18:17:53 +02:00
John Preston
63f0feaf04 Change shown top senders only on amount change. 2024-08-14 18:17:52 +02:00
John Preston
3001464f6b Show error on webapp share-to-story. 2024-08-14 18:17:52 +02:00
John Preston
eec9c8a46b Support tg://stars_topup links. 2024-08-14 18:17:52 +02:00
John Preston
80b3754be1 Fix build on Windows. 2024-08-14 18:17:52 +02:00
John Preston
6d61caea4e Update cmake_helpers. 2024-08-14 18:17:52 +02:00
John Preston
b9de12fedb Support 18+/restrictions for messages. 2024-08-14 18:17:52 +02:00
23rd
074dbf41e0 Replaced menu in media viewer with menu for sponsored messages. 2024-08-14 18:17:52 +02:00
23rd
e51b2c0c91 Improved Menu::ShowSponsored to be more generic. 2024-08-14 18:17:52 +02:00
23rd
e4538947c3 Added ability to open media from sponsored messages. 2024-08-14 18:17:52 +02:00
23rd
624e068f2f Added initial ability to display media in sponsored messages. 2024-08-14 18:17:52 +02:00
23rd
30077133d4 Fixed drawing online badge.
Regression was introduced in 66cd27a23e.
2024-08-14 18:17:52 +02:00
John Preston
b0fece2fd0 Provide privacy policy in mini-app. 2024-08-14 18:17:52 +02:00
John Preston
5f8c007a0c Always show Bot Privacy Policy button. 2024-08-14 18:17:52 +02:00
John Preston
a2785867b2 Support correct paid-media-from-bot phrases. 2024-08-14 18:17:52 +02:00
John Preston
9ea495f59d Support group-like channels. 2024-08-14 18:17:52 +02:00
John Preston
24a7e48b75 Fix reactions layout under a media. 2024-08-14 18:17:52 +02:00
John Preston
7ef44fb621 Fix channel reactions list editing. 2024-08-14 18:17:51 +02:00
John Preston
0a1ddddd81 Allow paid reaction even after limit is reached. 2024-08-14 18:17:51 +02:00
John Preston
284f1a5210 Support anonymous paid reactions. 2024-08-14 18:17:51 +02:00
John Preston
37283a7a35 Fix shortcut messages incorrect processing. 2024-08-14 18:17:51 +02:00
John Preston
7f4b540aad Fix crash in poll results collapsing. 2024-08-14 18:17:51 +02:00
John Preston
83f4c53766 Provide unavailable reasons for messages. 2024-08-14 18:17:51 +02:00
John Preston
155f4ea252 Fix build on Windows. 2024-08-14 18:17:51 +02:00
John Preston
f6c071bd18 Fix crash in paid media preview. 2024-08-14 18:17:51 +02:00
John Preston
56959398e2 Update API scheme on layer 186. 2024-08-14 18:17:51 +02:00
23rd
d34d3a796d Added initial graphic of badge to dialog row for subscribed channels. 2024-08-14 18:17:51 +02:00
23rd
dca61541d6 Added ability to emplace badge in dialog row for subscribed channels. 2024-08-14 18:17:51 +02:00
23rd
81a79e2895 Added subscription price info to list of invite links. 2024-08-14 18:17:51 +02:00
23rd
d6541d777f Added box of channel subscriber. 2024-08-14 18:17:51 +02:00
23rd
457301493f Moved out drawing of label for subscription rows to single place. 2024-08-14 18:17:51 +02:00
23rd
4f7364b798 Added ability to cancel and renew subscription from settings section. 2024-08-14 18:17:51 +02:00
23rd
32e8ed93e2 Added ability to display subscription info in ReceiptCreditsBox. 2024-08-14 18:17:51 +02:00
23rd
4760337958 Removed redundant argument from ReceiptCreditsBox. 2024-08-14 18:17:51 +02:00
23rd
f2f85a9083 Replaced icon with custom emoji in list of credits history entries. 2024-08-14 18:17:50 +02:00
23rd
70d5dd8b71 Added initial list of subscriptions to credits settings section. 2024-08-14 18:17:50 +02:00
23rd
ec93a91db2 Added api support for subscriptions list. 2024-08-14 18:17:50 +02:00
23rd
cc3baad377 Added subscription date to credits history entries. 2024-08-14 18:17:50 +02:00
John Preston
d1313f38eb Show right icon in top reactors. 2024-08-14 18:17:50 +02:00
John Preston
49e6c4f552 Don't allow setting star as quick reaction. 2024-08-14 18:17:50 +02:00
John Preston
fb252bb644 Update API scheme on layer 186. 2024-08-14 18:17:50 +02:00
John Preston
78e09f2605 Fix build on Windows. 2024-08-14 18:17:50 +02:00
John Preston
ba4c521d7a Send scheduled paid reactions on quit. 2024-08-14 18:17:50 +02:00
23rd
92f70a0ebb Added handler to subscribe box when balance is too small. 2024-08-14 18:17:50 +02:00
23rd
a13f0cb11e Added ability to subscribe to channel via invite link. 2024-08-14 18:17:50 +02:00
23rd
617ad38a68 Added subscription info to box for details of invite link. 2024-08-14 18:17:50 +02:00
23rd
fb66c85567 Added initial ability to request rate of credits from Credits component. 2024-08-14 18:17:50 +02:00
23rd
05fa2c381a Added initial ability to use credits icon as emoji. 2024-08-14 18:17:50 +02:00
23rd
ae18b4c851 Added special icon to list for invite links with subscription. 2024-08-14 18:17:50 +02:00
23rd
7ed715b01c Added ability to edit invite links with subscription. 2024-08-14 18:17:50 +02:00
23rd
8158c52e82 Added ability to create invite links with subscription. 2024-08-14 18:17:50 +02:00
23rd
44c1109798 Slightly improved include directives in files for invite links. 2024-08-14 18:17:50 +02:00
23rd
ba6bbf54e6 Added initial api support to create invite links with subscription. 2024-08-14 18:17:50 +02:00
23rd
75b26b1a85 Moved bunch of arguments for creation of invite links to structure. 2024-08-14 18:17:50 +02:00
23rd
c8a3b0ab80 Renamed SubscriptionOption with PremiumSubscriptionOption. 2024-08-14 18:17:49 +02:00
John Preston
52a199a362 Make nice paid reaction toast. 2024-08-14 18:17:49 +02:00
John Preston
e89f2b55e8 Support arbitrary content in toasts. 2024-08-14 18:17:49 +02:00
John Preston
ac92e1c99e Start paid reaction toast notification. 2024-08-14 18:17:49 +02:00
John Preston
02610de010 Improve paid reactions box design. 2024-08-14 18:17:49 +02:00
John Preston
273e041935 Update currencies list and rules. 2024-08-14 18:17:49 +02:00
John Preston
474c0838d1 Show "Star Reaction" in stars statistics. 2024-08-14 18:17:49 +02:00
John Preston
afe30da9f4 Add top reactors to paid reaction details. 2024-08-14 18:17:49 +02:00
John Preston
9bb1fa8782 Allow sending paid reactions. 2024-08-14 18:17:49 +02:00
John Preston
bb3fc17489 Allow enabling paid reactions. 2024-08-14 18:17:49 +02:00
John Preston
126fd89bb7 Remove accidentally added files from git. 2024-08-14 18:17:49 +02:00
John Preston
fb8f3ad26c Update API scheme to layer 186. 2024-08-14 18:17:48 +02:00
23rd
cedf161e44 Removed inappropriate phrase from photo editor for profile bot photo. 2024-08-14 18:17:48 +02:00
23rd
89dc18aaea Added mode animations to loading screen from different stats. 2024-08-14 18:17:48 +02:00
23rd
ba99706e75 Fixed indents in qrc file. 2024-08-14 18:17:48 +02:00
23rd
efc8417ab1 Improved text size in list of credits options. 2024-08-14 18:17:48 +02:00
23rd
c4f45c4b7c Fixed resizing of label in section of profile info. 2024-08-14 18:17:48 +02:00
23rd
c985b77a48 Removed event filter from input field in edit peer info box on close. 2024-08-14 18:17:48 +02:00
Ilya Fedin
6148fb9474 Use CMAKE_CXX_COMPILER_FRONTEND_VARIANT 2024-08-14 18:43:27 +04:00
Ilya Fedin
fe86f5d050 Check IV support early 2024-08-13 17:09:15 +04:00
Ilya Fedin
916926bfa6 Fix default export directory in flatpak 2024-08-13 08:45:55 +04:00
Ilya Fedin
9eb15f7b17 Move ada snap part to maintain alphabetical order 2024-08-04 12:16:10 +04:00
Ilya Fedin
2d1b1fbd44 Ensure tdesktop builds after ada in snap 2024-08-04 12:16:10 +04:00
Ilya Fedin
1301c42afa Add ada to macOS packaged action 2024-08-04 12:16:10 +04:00
394 changed files with 10288 additions and 5083 deletions

View File

@@ -69,7 +69,7 @@ jobs:
run: |
brew update
brew upgrade || true
brew install autoconf automake boost cmake ffmpeg@6 openal-soft openh264 openssl opus ninja pkg-config python qt yasm xz
brew install ada-url autoconf automake boost cmake ffmpeg@6 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

@@ -37,6 +37,10 @@ include(cmake/td_scheme.cmake)
include(cmake/td_ui.cmake)
include(cmake/generate_appdata_changelog.cmake)
if (DESKTOP_APP_TEST_APPS)
include(cmake/tests.cmake)
endif()
if (WIN32)
include(cmake/generate_midl.cmake)
generate_midl(Telegram ${src_loc}
@@ -310,6 +314,8 @@ PRIVATE
boxes/self_destruction_box.h
boxes/send_credits_box.cpp
boxes/send_credits_box.h
boxes/send_gif_with_caption_box.cpp
boxes/send_gif_with_caption_box.h
boxes/send_files_box.cpp
boxes/send_files_box.h
boxes/sessions_box.cpp
@@ -475,6 +481,8 @@ PRIVATE
data/business/data_business_info.h
data/business/data_shortcut_messages.cpp
data/business/data_shortcut_messages.h
data/components/credits.cpp
data/components/credits.h
data/components/factchecks.cpp
data/components/factchecks.h
data/components/location_pickers.cpp
@@ -824,6 +832,8 @@ PRIVATE
history/view/history_view_message.cpp
history/view/history_view_message.h
history/view/history_view_object.h
history/view/history_view_paid_reaction_toast.cpp
history/view/history_view_paid_reaction_toast.h
history/view/history_view_pinned_bar.cpp
history/view/history_view_pinned_bar.h
history/view/history_view_pinned_section.cpp
@@ -1236,6 +1246,8 @@ PRIVATE
payments/payments_form.h
payments/payments_non_panel_process.cpp
payments/payments_non_panel_process.h
payments/payments_reaction_process.cpp
payments/payments_reaction_process.h
platform/linux/file_utilities_linux.cpp
platform/linux/file_utilities_linux.h
platform/linux/launcher_linux.cpp
@@ -1468,6 +1480,8 @@ PRIVATE
support/support_preload.h
support/support_templates.cpp
support/support_templates.h
ui/boxes/edit_invite_link_session.cpp
ui/boxes/edit_invite_link_session.h
ui/chat/attach/attach_item_single_file_preview.cpp
ui/chat/attach/attach_item_single_file_preview.h
ui/chat/attach/attach_item_single_media_preview.cpp
@@ -1476,6 +1490,8 @@ PRIVATE
ui/chat/choose_send_as.h
ui/chat/choose_theme_controller.cpp
ui/chat/choose_theme_controller.h
ui/controls/emoji_button_factory.cpp
ui/controls/emoji_button_factory.h
ui/controls/location_picker.cpp
ui/controls/location_picker.h
ui/controls/silent_toggle.cpp
@@ -1501,6 +1517,8 @@ PRIVATE
ui/image/image_location_factory.h
ui/text/format_song_document_name.cpp
ui/text/format_song_document_name.h
ui/widgets/expandable_peer_list.cpp
ui/widgets/expandable_peer_list.h
ui/widgets/label_with_custom_emoji.cpp
ui/widgets/label_with_custom_emoji.h
ui/countryinput.cpp
@@ -1820,7 +1838,7 @@ endif()
set_target_properties(Telegram PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${output_folder})
if (WIN32)
if (WIN32 AND CMAKE_CXX_COMPILER_FRONTEND_VARIANT STREQUAL "MSVC")
target_link_libraries(Telegram
PRIVATE
delayimp
@@ -1917,7 +1935,7 @@ if (NOT DESKTOP_APP_DISABLE_AUTOUPDATE AND NOT build_macstore AND NOT build_wins
base/platform/win/base_windows_safe_library.h
)
target_include_directories(Updater PRIVATE ${lib_base_loc})
if (CMAKE_CXX_COMPILER_ID STREQUAL "MSVC")
if (CMAKE_CXX_COMPILER_FRONTEND_VARIANT STREQUAL "MSVC")
target_link_libraries(Updater
PRIVATE
delayimp

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 214 B

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 426 B

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 912 B

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 240 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 324 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 422 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 349 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 565 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 740 B

View File

@@ -0,0 +1,36 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="72px" height="72px" viewBox="0 0 72 72" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<title>General / menu_incomes</title>
<defs>
<rect id="path-1" x="0" y="0" width="72" height="37"></rect>
<rect id="path-3" x="0" y="0" width="72" height="42"></rect>
</defs>
<g id="General-/-menu_incomes" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="Group-2">
<mask id="mask-2" fill="white">
<use xlink:href="#path-1"></use>
</mask>
<g id="Rectangle"></g>
<g id="Group" mask="url(#mask-2)" stroke="#FFFFFF" stroke-linecap="round" stroke-width="5.4">
<g transform="translate(35.056529, 35.872413) rotate(66.000000) translate(-35.056529, -35.872413) translate(5.056529, 5.872413)" id="Path">
<path d="M7.74068421e-14,27 C7.74068421e-14,27.6514434 0.0207638485,31.2980371 0.0616658298,31.9391554 C1.06104802,47.603977 14.0829009,60 30,60 C46.5685425,60 60,46.5685425 60,30 C60,13.4314575 46.5685425,-1.34265911e-13 30,-1.34265911e-13 C20.9122281,-1.34265911e-13 12.7682399,4.04081874 7.26674545,10.4237463" transform="translate(30.000000, 30.000000) rotate(90.000000) translate(-30.000000, -30.000000) "></path>
</g>
</g>
</g>
<g id="Group-2-Copy" transform="translate(0.000000, 28.000000)">
<mask id="mask-4" fill="white">
<use xlink:href="#path-3"></use>
</mask>
<g id="Rectangle"></g>
<g id="Group" mask="url(#mask-4)" stroke="#FFFFFF" stroke-dasharray="5,14" stroke-linecap="round" stroke-width="5.4">
<g transform="translate(35.056529, 7.872413) rotate(66.000000) translate(-35.056529, -7.872413) translate(-2.745677, -29.929792)" id="Path">
<path d="M7.80220532,34.8022053 C7.80220532,35.4536487 7.82296917,39.1002424 7.86387115,39.7413607 C8.86325334,55.4061823 21.8851062,67.8022053 37.8022053,67.8022053 C54.3707478,67.8022053 67.8022053,54.3707478 67.8022053,37.8022053 C67.8022053,21.2336628 54.3707478,7.80220532 37.8022053,7.80220532 C28.7144334,7.80220532 20.5704452,11.8430241 15.0689508,18.2259516" transform="translate(37.802205, 37.802205) rotate(72.000000) translate(-37.802205, -37.802205) "></path>
</g>
</g>
</g>
<g id="$" transform="translate(23.000000, 16.000000)" fill="#FFFFFF" fill-rule="nonzero">
<path d="M12.034384,39.8 C13.0659026,39.8 14.182808,39.2321492 14.182808,37.869307 L14.182808,35.752184 C20.3489971,35.093477 24,31.5046593 24,26.280431 C24,21.7830518 21.2722063,19.0573675 15.6103152,17.8308096 L10.9570201,16.7859639 C8.04584527,16.1499709 6.62464183,14.8325568 6.62464183,12.9018637 C6.62464183,10.585032 8.64183381,8.90419336 11.8051576,8.90419336 C14.3724928,8.90419336 16.1604585,9.76732673 18.1547278,11.9705882 C19.1633238,13.0154339 19.9426934,13.4015725 20.9971347,13.4015725 C22.2808023,13.4015725 23.2664756,12.5157251 23.2664756,11.198311 C23.2664756,9.92632499 22.5100287,8.54076878 21.226361,7.2914968 C19.530086,5.70151427 17.1653295,4.65666861 14.3,4.29324403 L14.3,1.907979 C14.3,0.56785087 13.2034384,0 12.1489971,0 C11.1174785,0 10,0.545136833 10,1.907979 L10,4.22510192 C4.06303725,4.77023879 0.481375358,8.29091439 0.481375358,13.3334304 C0.481375358,17.7399534 3.20916905,20.6700641 8.43553009,21.8284799 L13.0888252,22.8960396 C16.4813754,23.6910309 17.8796562,24.8721607 17.8796562,26.8709959 C17.8796562,29.460396 15.8395415,31.0958066 12.1948424,31.0958066 C9.46704871,31.0958066 7.19770774,30.073675 5.13467049,27.8704135 C3.96561605,26.7119977 3.32378223,26.4621433 2.45272206,26.4621433 C1.05444126,26.4621433 0,27.3479907 0,28.8698311 C0,30.2099592 0.779369628,31.5955154 2.17765043,32.7993593 C4.01146132,34.457484 6.67679058,35.4796156 9.863037,35.7748981 L9.863037,37.869307 C9.863037,39.2321492 10.9799427,39.8 12.034384,39.8 Z" id="Path"></path>
</g>
<path d="M66.2217021,28.4347599 L72.4528242,38.8199633 C72.7823745,39.3692138 72.6042723,40.0816225 72.0550218,40.4111728 C71.8747737,40.5193217 71.668522,40.5764501 71.4583183,40.5764501 L57.5551143,40.5764501 C56.9145837,40.5764501 56.3953311,40.0571975 56.3953311,39.4166669 C56.3953311,39.2064632 56.4524595,39.0002115 56.5606084,38.8199633 L62.7917304,28.4347599 C63.3600268,27.4875994 64.5885473,27.1804692 65.5357078,27.7487655 C65.817207,27.9176651 66.0528026,28.1532607 66.2217021,28.4347599 Z" id="Triangle" fill="#FFFFFF" transform="translate(64.506716, 33.076450) rotate(168.000000) translate(-64.506716, -33.076450) "></path>
</g>
</svg>

After

Width:  |  Height:  |  Size: 4.6 KiB

View File

@@ -11,6 +11,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"cloud_lng_forwarded_psa_covid" = "COVID-19 Notification from {channel}";
"cloud_lng_tooltip_psa_covid" = "This message provides you with a public service announcement in relation to the ongoing COVID-19 pandemic. Learn more about this initiative at https://telegram.org/blog/coronavirus";
"cloud_lng_topup_purpose_subs" = "Buy **Stars** to keep your channel subscriptions.";
"cloud_lng_passport_in_ar" = "Arabic";
"cloud_lng_passport_in_az" = "Azerbaijani";
"cloud_lng_passport_in_bg" = "Bulgarian";

View File

@@ -1315,6 +1315,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_profile_bot_settings" = "Bot Settings";
"lng_profile_bot_help" = "Bot Help";
"lng_profile_bot_privacy" = "Bot Privacy Policy";
"lng_profile_bot_privacy_url" = "https://telegram.org/privacy-tpa";
"lng_profile_common_groups#one" = "{count} group in common";
"lng_profile_common_groups#other" = "{count} groups in common";
"lng_profile_similar_channels#one" = "{count} similar channel";
@@ -1550,6 +1551,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_manage_peer_reactions_max_slider#other" = "{count} reactions per post";
"lng_manage_peer_reactions_max_about" = "Limit the number of different reactions that can be added to a post, including already published ones.";
"lng_manage_peer_reactions_paid" = "Enable Paid Reactions";
"lng_manage_peer_reactions_paid_about" = "Switch this on to let your subscribers set paid reactions with Telegram Stars, which you will be able to withdraw later as TON. {link}";
"lng_manage_peer_reactions_paid_link" = "Learn more >";
"lng_manage_peer_antispam" = "Aggressive Anti-Spam";
"lng_manage_peer_antispam_about" = "Telegram will filter more spam but may occasionally affect ordinary messages. You can report False Positives in Recent Actions.";
"lng_manage_peer_antispam_not_enough#one" = "Aggressive filtering can be enabled only in groups with more than **{count} member**.";
@@ -1943,6 +1948,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_group_invite_members#other" = "{count} members, among them:";
"lng_channel_invite_private" = "This channel is private.\nPlease join it to continue viewing its content.";
"lng_channel_invite_subscription_button" = "Subscribe";
"lng_channel_invite_subscription_title" = "Subscribe to the Channel";
"lng_channel_invite_subscription_about" = "Do you want to subscribe for {channel} for {price} per month?";
"lng_channel_invite_subscription_terms" = "By subscribing you agree to the {link}.";
"lng_group_invite_create" = "Create an invite link";
"lng_group_invite_about_new" = "Your previous link will be deactivated and we'll generate a new invite link for you.";
"lng_group_invite_copied" = "Invite link copied to clipboard.";
@@ -2017,6 +2027,21 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_group_invite_about_no_approve" = "New users will be able to join the group without being approved by the admins.";
"lng_group_invite_about_approve_channel" = "New users will be able to join the channel only after having been approved by the admins.";
"lng_group_invite_about_no_approve_channel" = "New users will be able to join the channel without being approved by the admins.";
"lng_group_invite_subscription" = "Require Monthly Fee";
"lng_group_invite_subscription_ph" = "Stars Amount per month";
"lng_group_invite_subscription_price" = "~{cost} / month";
"lng_group_invite_subscription_toast" = "Sorry, you cannot change the number of Stars for an already created invite link.";
"lng_group_invite_subscription_about" = "Charge a subscription fee from people joining your channel via this link. {link}";
"lng_group_invite_subscription_about_link" = "Learn more {emoji}";
"lng_group_invite_subscription_about_url" = "https://telegram.org/tos/stars";
"lng_group_invite_subscription_info_subtitle" = "Subscription fee";
"lng_group_invite_subscription_info_title" = "{emoji} {price} / month {multiplier} {total}";
"lng_group_invite_subscription_info_title_none" = "{emoji} {price} / month";
"lng_group_invite_subscription_info_about" = "you get approximately {total} montly";
"lng_group_invite_joined_right" = "per month";
"lng_group_invite_joined_status" = "joined {date}";
"lng_group_invite_joined_row_subscriber" = "Subscriber";
"lng_group_invite_joined_row_date" = "Subscribed";
"lng_group_request_to_join" = "Request to Join";
"lng_group_request_about" = "This group accepts new members only after they are approved by its admins.";
@@ -2352,6 +2377,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_credits_box_out_sure#other" = "Do you want to buy **\"{text}\"** in **{bot}** for **{count} Stars**?";
"lng_credits_box_out_media#one" = "Do you want to unlock {media} in {chat} for **{count} Star**?";
"lng_credits_box_out_media#other" = "Do you want to unlock {media} in {chat} for **{count} Stars**?";
"lng_credits_box_out_media_user#one" = "Do you want to unlock {media} from {user} for **{count} Star**?";
"lng_credits_box_out_media_user#other" = "Do you want to unlock {media} from {user} for **{count} Stars**?";
"lng_credits_box_out_photo" = "a photo";
"lng_credits_box_out_photos#one" = "{count} photo";
"lng_credits_box_out_photos#other" = "{count} photos";
@@ -2366,6 +2393,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_credits_media_done_title" = "Media Unlocked";
"lng_credits_media_done_text#one" = "**{count} Star** transferred to {chat}.";
"lng_credits_media_done_text#other" = "**{count} Stars** transferred to {chat}.";
"lng_credits_media_done_text_user#one" = "**{count} Star** transferred to {user}.";
"lng_credits_media_done_text_user#other" = "**{count} Stars** transferred to {user}.";
"lng_credits_summary_in_toast_title" = "Stars Acquired";
"lng_credits_summary_in_toast_about#one" = "**{count}** Star added to your balance.";
"lng_credits_summary_in_toast_about#other" = "**{count}** Stars added to your balance.";
@@ -2392,10 +2421,42 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_credits_box_history_entry_media" = "Media";
"lng_credits_box_history_entry_about" = "You can dispute this transaction {link}.";
"lng_credits_box_history_entry_about_link" = "here";
"lng_credits_box_history_entry_reaction_name" = "Star Reaction";
"lng_credits_box_history_entry_subscription" = "Monthly subscription fee";
"lng_credits_subscription_section" = "My subscriptions";
"lng_credits_box_subscription_title" = "Subscription";
"lng_credits_subscription_subtitle" = "{emoji} {cost} / month";
"lng_credits_subscriber_subtitle" = "appx. {total} per month";
"lng_credits_subscription_row_to" = "Subscription";
"lng_credits_subscription_row_from" = "Subscribed";
"lng_credits_subscription_row_next_on" = "Renews";
"lng_credits_subscription_row_next_off" = "Expires";
"lng_credits_subscription_row_next_none" = "Expired";
"lng_credits_subscription_on_button" = "Cancel Subscription";
"lng_credits_subscription_on_about" = "If you cancel now, you will still be able to access your subscription until {date}.";
"lng_credits_subscription_off_button" = "Renew Subscription";
"lng_credits_subscription_off_about" = "You have cancelled your subscription.";
"lng_credits_subscription_status_on" = "renews on {date}";
"lng_credits_subscription_status_off" = "expires on {date}";
"lng_credits_subscription_status_none" = "expired on {date}";
"lng_credits_subscription_status_off_right" = "cancelled";
"lng_credits_subscription_status_none_right" = "expired";
"lng_credits_small_balance_title#one" = "{count} Star Needed";
"lng_credits_small_balance_title#other" = "{count} Stars Needed";
"lng_credits_small_balance_about" = "Buy **Stars** and use them on **{bot}** and other miniapps.";
"lng_credits_small_balance_reaction" = "Buy **Stars** and send them to {channel} to support their posts.";
"lng_credits_small_balance_subscribe" = "Buy **Stars** and subscribe to **{channel}** and other channels.";
"lng_credits_small_balance_fallback" = "Buy **Stars** to unlock content and services on Telegram.";
"lng_credits_purchase_blocked" = "Sorry, you can't purchase this item with Telegram Stars.";
"lng_credits_enough" = "You have enough stars at the moment. {link}";
"lng_credits_enough_link" = "Buy anyway";
"lng_credits_gift_title" = "Gift Telegram Stars";
@@ -3053,6 +3114,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_send_text_type_files" = "Files";
"lng_send_text_type_stickers" = "Stickers & GIFs";
"lng_send_text_type_polls" = "Polls";
"lng_send_gif_with_caption" = "Send GIF with caption";
"lng_send_as_title" = "Send message as...";
"lng_send_as_anonymous_admin" = "Anonymous admin";
@@ -3188,6 +3250,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_bot_add_to_side_menu" = "{bot} asks your permission to be added as an option to your main menu so you can access it any time.";
"lng_bot_add_to_side_menu_done" = "Bot added to the main menu.";
"lng_bot_no_scan_qr" = "QR Codes for bots are not supported on Desktop. Please use one of Telegram's mobile apps.";
"lng_bot_no_share_story" = "Sharing to Stories is not supported on Desktop. Please use one of Telegram's mobile apps.";
"lng_bot_click_to_start" = "Click here to use this bot.";
"lng_bot_status_users#one" = "{count} user";
"lng_bot_status_users#other" = "{count} users";
@@ -3412,6 +3475,29 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_paid_about_link_url" = "https://telegram.org/blog/telegram-stars";
"lng_paid_price" = "Unlock for {price}";
"lng_paid_react_title" = "Star Reaction";
"lng_paid_react_about" = "Choose how many **Stars** you want to send to {channel} to support this post.";
"lng_paid_react_already#one" = "You sent **{count} Star** to support this post.";
"lng_paid_react_already#other" = "You sent **{count} Stars** to support this post.";
"lng_paid_react_top_title" = "Top Senders";
"lng_paid_react_send" = "Send {price}";
"lng_paid_react_agree" = "By sending stars, you agree to the {link}.";
"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_text#one" = "You reacted with **{count} Star**.";
"lng_paid_react_toast_text#other" = "You reacted with **{count} Stars**.";
"lng_paid_react_undo" = "Undo";
"lng_paid_react_show_in_top" = "Show me in Top Senders";
"lng_paid_react_anonymous" = "Anonymous";
"lng_sensitive_tag" = "18+";
"lng_sensitive_title" = "18+";
"lng_sensitive_text" = "This media may contain sensitive content suitable only for adults. Do you still want to view it?";
"lng_sensitive_always" = "Always show 18+ media";
"lng_sensitive_view" = "View Anyway";
"lng_sensitive_toast" = "You can update the visibility of sensitive media in **Settings > Chat Settings > Sensitive content**";
"lng_translate_show_original" = "Show Original";
"lng_translate_bar_to" = "Translate to {name}";
"lng_translate_bar_to_other" = "Translate to {name}";
@@ -3516,6 +3602,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_edit_channel_title" = "Edit channel";
"lng_edit_bot_title" = "Edit bot";
"lng_edit_sign_messages" = "Sign messages";
"lng_edit_sign_messages_about" = "Add names of admins to the messages they post.";
"lng_edit_sign_profiles" = "Show authors' profiles";
"lng_edit_sign_profiles_about" = "Add names and photos of admins to the messages they post, linking to their profiles.";
"lng_edit_group" = "Edit group";
"lng_edit_channel_color" = "Change name color";
"lng_edit_channel_level_min" = "Level 1+";
@@ -4256,11 +4345,15 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_admin_log_filter" = "Filter";
"lng_admin_log_filter_title" = "Filter";
"lng_admin_log_filter_all_actions" = "All actions";
"lng_admin_log_filter_actions_type_subtitle" = "Filter actions by type";
"lng_admin_log_filter_actions_member_section" = "Members And Admins";
"lng_admin_log_filter_restrictions" = "New restrictions";
"lng_admin_log_filter_admins_new" = "New admins";
"lng_admin_log_filter_members_new" = "New members";
"lng_admin_log_filter_actions_settings_section" = "Group Settings";
"lng_admin_log_filter_info_group" = "Group info";
"lng_admin_log_filter_info_channel" = "Channel info";
"lng_admin_log_filter_actions_messages_section" = "Messages";
"lng_admin_log_filter_messages_deleted" = "Deleted messages";
"lng_admin_log_filter_messages_edited" = "Edited messages";
"lng_admin_log_filter_messages_pinned" = "Pinned messages";
@@ -4270,6 +4363,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_admin_log_filter_members_removed" = "Leaving members";
"lng_admin_log_filter_topics" = "Topics";
"lng_admin_log_filter_all_admins" = "All users and admins";
"lng_admin_log_filter_actions_admins_subtitle" = "Filter actions by admins";
"lng_admin_log_filter_actions_admins_section" = "Show Actions by All Admins";
"lng_admin_log_about" = "What is this?";
"lng_admin_log_about_text" = "This is a list of all service actions taken by the group's members and admins in the last 48 hours.";
"lng_admin_log_about_text_channel" = "This is a list of all service actions taken by the channel's admins in the last 48 hours.";
@@ -4305,6 +4400,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_admin_log_invites_disabled" = "{from} disabled group invites";
"lng_admin_log_signatures_enabled" = "{from} enabled signatures";
"lng_admin_log_signatures_disabled" = "{from} disabled signatures";
"lng_admin_log_signature_profiles_enabled" = "{from} enabled showing authors' profiles";
"lng_admin_log_signature_profiles_disabled" = "{from} disabled showing authors' profiles";
"lng_admin_log_forwards_enabled" = "{from} allowed content copying";
"lng_admin_log_forwards_disabled" = "{from} restricted content copying";
"lng_admin_log_history_made_hidden" = "{from} made group history hidden for new members";
@@ -4381,6 +4478,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_admin_log_set_profile_background_emoji" = "{from} set channel profile background emoji to {emoji}";
"lng_admin_log_change_profile_background_emoji" = "{from} changed channel profile background emoji from {previous} to {emoji}";
"lng_admin_log_removed_profile_background_emoji" = "{from} removed channel profile background emoji {emoji}";
"lng_admin_log_change_profile_color_group" = "{from} changed group profile color from {previous} to {color}";
"lng_admin_log_set_profile_background_emoji_group" = "{from} set group profile background emoji to {emoji}";
"lng_admin_log_change_profile_background_emoji_group" = "{from} changed group profile background emoji from {previous} to {emoji}";
"lng_admin_log_removed_profile_background_emoji_group" = "{from} removed group profile background emoji {emoji}";
"lng_admin_log_change_wallpaper" = "{from} changed channel wallpaper";
"lng_admin_log_set_status" = "{from} set channel emoji status to {emoji}";
"lng_admin_log_change_status" = "{from} changed channel emoji status from {previous} to {emoji}";
@@ -5179,6 +5280,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_stats_loading" = "Loading stats...";
"lng_stats_loading_subtext" = "Please wait a few moments while we generate your stats.";
"lng_stats_boosts_loading" = "Loading boosts list...";
"lng_stats_boosts_loading_subtext" = "Please wait a few moments while we generate your stats.";
"lng_stats_earn_loading" = "Loading rewards info...";
"lng_stats_earn_loading_subtext" = "Please wait a few moments while we generate your stats.";
"lng_chart_title_member_count" = "Growth";
"lng_chart_title_join" = "Followers";

View File

@@ -11,20 +11,41 @@
<file alias="ttl.tgs">../../animations/ttl.tgs</file>
<file alias="discussion.tgs">../../animations/discussion.tgs</file>
<file alias="stats.tgs">../../animations/stats.tgs</file>
<file alias="stats_boosts.tgs">../../animations/stats_boosts.tgs</file>
<file alias="stats_earn.tgs">../../animations/stats_earn.tgs</file>
<file alias="voice_ttl_idle.tgs">../../animations/voice_ttl_idle.tgs</file>
<file alias="voice_ttl_start.tgs">../../animations/voice_ttl_start.tgs</file>
<file alias="palette.tgs">../../animations/palette.tgs</file>
<file alias="sleep.tgs">../../animations/sleep.tgs</file>
<file alias="greeting.tgs">../../animations/greeting.tgs</file>
<file alias="location.tgs">../../animations/location.tgs</file>
<file alias="robot.tgs">../../animations/robot.tgs</file>
<file alias="writing.tgs">../../animations/writing.tgs</file>
<file alias="hours.tgs">../../animations/hours.tgs</file>
<file alias="phone.tgs">../../animations/phone.tgs</file>
<file alias="greeting.tgs">../../animations/greeting.tgs</file>
<file alias="location.tgs">../../animations/location.tgs</file>
<file alias="robot.tgs">../../animations/robot.tgs</file>
<file alias="writing.tgs">../../animations/writing.tgs</file>
<file alias="hours.tgs">../../animations/hours.tgs</file>
<file alias="phone.tgs">../../animations/phone.tgs</file>
<file alias="chat_link.tgs">../../animations/chat_link.tgs</file>
<file alias="collectible_username.tgs">../../animations/collectible_username.tgs</file>
<file alias="collectible_phone.tgs">../../animations/collectible_phone.tgs</file>
<file alias="search.tgs">../../animations/search.tgs</file>
<file alias="noresults.tgs">../../animations/noresults.tgs</file>
<file alias="dice_idle.tgs">../../animations/dice/dice_idle.tgs</file>
<file alias="dart_idle.tgs">../../animations/dice/dart_idle.tgs</file>
<file alias="bball_idle.tgs">../../animations/dice/bball_idle.tgs</file>
<file alias="fball_idle.tgs">../../animations/dice/fball_idle.tgs</file>
<file alias="slot_0_idle.tgs">../../animations/dice/slot_0_idle.tgs</file>
<file alias="slot_1_idle.tgs">../../animations/dice/slot_1_idle.tgs</file>
<file alias="slot_2_idle.tgs">../../animations/dice/slot_2_idle.tgs</file>
<file alias="slot_back.tgs">../../animations/dice/slot_back.tgs</file>
<file alias="slot_pull.tgs">../../animations/dice/slot_pull.tgs</file>
<file alias="winners.tgs">../../animations/dice/winners.tgs</file>
<file alias="star_reaction_appear.tgs">../../animations/star_reaction/appear.tgs</file>
<file alias="star_reaction_center.tgs">../../animations/star_reaction/center.tgs</file>
<file alias="star_reaction_select.tgs">../../animations/star_reaction/select.tgs</file>
<file alias="star_reaction_toast.tgs">../../animations/star_reaction/toast.tgs</file>
<file alias="star_reaction_effect1.tgs">../../animations/star_reaction/effect1.tgs</file>
<file alias="star_reaction_effect2.tgs">../../animations/star_reaction/effect2.tgs</file>
<file alias="star_reaction_effect3.tgs">../../animations/star_reaction/effect3.tgs</file>
</qresource>
</RCC>

View File

@@ -7,16 +7,6 @@
<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>
<file alias="art/dice_idle.tgs">../../art/dice_idle.tgs</file>
<file alias="art/dart_idle.tgs">../../art/dart_idle.tgs</file>
<file alias="art/bball_idle.tgs">../../art/bball_idle.tgs</file>
<file alias="art/fball_idle.tgs">../../art/fball_idle.tgs</file>
<file alias="art/slot_0_idle.tgs">../../art/slot_0_idle.tgs</file>
<file alias="art/slot_1_idle.tgs">../../art/slot_1_idle.tgs</file>
<file alias="art/slot_2_idle.tgs">../../art/slot_2_idle.tgs</file>
<file alias="art/slot_back.tgs">../../art/slot_back.tgs</file>
<file alias="art/slot_pull.tgs">../../art/slot_pull.tgs</file>
<file alias="art/winners.tgs">../../art/winners.tgs</file>
<file alias="day-blue.tdesktop-theme">../../day-blue.tdesktop-theme</file>
<file alias="night.tdesktop-theme">../../night.tdesktop-theme</file>
<file alias="night-green.tdesktop-theme">../../night-green.tdesktop-theme</file>
@@ -40,6 +30,7 @@
<file alias="topic_icons/red.svg">../../art/topic_icons/red.svg</file>
<file alias="topic_icons/gray.svg">../../art/topic_icons/gray.svg</file>
<file alias="topic_icons/general.svg">../../art/topic_icons/general.svg</file>
<file alias="links_subscription.svg">../../icons/info/edit/links_subscription.svg</file>
</qresource>
<qresource prefix="/icons">
<file alias="calls/hands.lottie">../../icons/calls/hands.lottie</file>

View File

@@ -10,7 +10,7 @@
<Identity Name="TelegramMessengerLLP.TelegramDesktop"
ProcessorArchitecture="ARCHITECTURE"
Publisher="CN=536BC709-8EE1-4478-AF22-F0F0F26FF64A"
Version="5.3.2.0" />
Version="5.4.2.0" />
<Properties>
<DisplayName>Telegram Desktop</DisplayName>
<PublisherDisplayName>Telegram Messenger LLP</PublisherDisplayName>
@@ -37,8 +37,8 @@
<Extensions>
<uap3:Extension Category="windows.protocol">
<uap3:Protocol Name="tg" Parameters="-- &quot;%1&quot;" />
<uap3:Extension Category="windows.protocol">
</uap3:Extension>
<uap3:Extension Category="windows.protocol">
<uap3:Protocol Name="tonsite" Parameters="-- &quot;%1&quot;" />
</uap3:Extension>
<desktop:Extension

View File

@@ -44,8 +44,8 @@ IDI_ICON1 ICON "..\\art\\icon256.ico"
//
VS_VERSION_INFO VERSIONINFO
FILEVERSION 5,3,2,0
PRODUCTVERSION 5,3,2,0
FILEVERSION 5,4,2,0
PRODUCTVERSION 5,4,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.3.2.0"
VALUE "FileVersion", "5.4.2.0"
VALUE "LegalCopyright", "Copyright (C) 2014-2024"
VALUE "ProductName", "Telegram Desktop"
VALUE "ProductVersion", "5.3.2.0"
VALUE "ProductVersion", "5.4.2.0"
END
END
BLOCK "VarFileInfo"

View File

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

View File

@@ -15,6 +15,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "boxes/url_auth_box.h"
#include "boxes/peers/choose_peer_box.h"
#include "lang/lang_keys.h"
#include "chat_helpers/bot_command.h"
#include "core/core_cloud_password.h"
#include "core/click_handler_types.h"
#include "data/data_changes.h"

View File

@@ -26,6 +26,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/widgets/buttons.h"
#include "ui/filter_icons.h"
#include "ui/vertical_list.h"
#include "ui/ui_utility.h"
#include "window/window_session_controller.h"
#include "styles/style_filter_icons.h"
#include "styles/style_layers.h"

View File

@@ -8,26 +8,39 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "api/api_chat_invite.h"
#include "apiwrap.h"
#include "window/window_session_controller.h"
#include "boxes/premium_limits_box.h"
#include "core/application.h"
#include "data/components/credits.h"
#include "data/data_channel.h"
#include "data/data_file_origin.h"
#include "data/data_forum.h"
#include "data/data_photo.h"
#include "data/data_photo_media.h"
#include "data/data_session.h"
#include "data/data_user.h"
#include "info/channel_statistics/boosts/giveaway/boost_badge.h"
#include "info/profile/info_profile_badge.h"
#include "lang/lang_keys.h"
#include "main/main_session.h"
#include "ui/empty_userpic.h"
#include "ui/painter.h"
#include "core/application.h"
#include "data/data_session.h"
#include "data/data_photo.h"
#include "data/data_photo_media.h"
#include "data/data_channel.h"
#include "data/data_forum.h"
#include "data/data_user.h"
#include "data/data_file_origin.h"
#include "settings/settings_credits_graphics.h"
#include "ui/boxes/confirm_box.h"
#include "ui/controls/userpic_button.h"
#include "ui/effects/premium_graphics.h"
#include "ui/effects/premium_stars_colored.h"
#include "ui/empty_userpic.h"
#include "ui/layers/generic_box.h"
#include "ui/painter.h"
#include "ui/rect.h"
#include "ui/text/text_utilities.h"
#include "ui/toast/toast.h"
#include "boxes/premium_limits_box.h"
#include "ui/vertical_list.h"
#include "window/window_session_controller.h"
#include "styles/style_boxes.h"
#include "styles/style_chat_helpers.h"
#include "styles/style_credits.h"
#include "styles/style_info.h"
#include "styles/style_layers.h"
#include "styles/style_premium.h"
namespace Api {
@@ -101,12 +114,237 @@ void SubmitChatInvite(
}).send();
}
void ConfirmSubscriptionBox(
not_null<Ui::GenericBox*> box,
not_null<Main::Session*> session,
const QString &hash,
const MTPDchatInvite *data) {
box->setWidth(st::boxWideWidth);
const auto amount = data->vsubscription_pricing()->data().vamount().v;
const auto formId = data->vsubscription_form_id()->v;
const auto name = qs(data->vtitle());
const auto maybePhoto = session->data().processPhoto(data->vphoto());
const auto photo = maybePhoto->isNull() ? nullptr : maybePhoto.get();
struct State final {
std::shared_ptr<Data::PhotoMedia> photoMedia;
std::unique_ptr<Ui::EmptyUserpic> photoEmpty;
std::optional<MTP::Sender> api;
Ui::RpWidget* saveButton = nullptr;
rpl::variable<bool> loading;
};
const auto state = box->lifetime().make_state<State>();
const auto content = box->verticalLayout();
Ui::AddSkip(content, st::confirmInvitePhotoTop);
const auto userpicWrap = content->add(
object_ptr<Ui::CenterWrap<>>(
content,
object_ptr<Ui::RpWidget>(content)));
const auto userpic = userpicWrap->entity();
const auto photoSize = st::confirmInvitePhotoSize;
userpic->resize(Size(photoSize));
const auto options = Images::Option::RoundCircle;
userpic->paintRequest(
) | rpl::start_with_next([=, small = Data::PhotoSize::Small] {
auto p = QPainter(userpic);
if (state->photoMedia) {
if (const auto image = state->photoMedia->image(small)) {
p.drawPixmap(
0,
0,
image->pix(Size(photoSize), { .options = options }));
}
} else if (state->photoEmpty) {
state->photoEmpty->paintCircle(
p,
0,
0,
userpic->width(),
photoSize);
}
}, userpicWrap->lifetime());
userpicWrap->setAttribute(Qt::WA_TransparentForMouseEvents);
if (photo) {
state->photoMedia = photo->createMediaView();
state->photoMedia->wanted(Data::PhotoSize::Small, Data::FileOrigin());
if (!state->photoMedia->image(Data::PhotoSize::Small)) {
session->downloaderTaskFinished(
) | rpl::start_with_next([=] {
userpic->update();
}, userpicWrap->entity()->lifetime());
}
} else {
state->photoEmpty = std::make_unique<Ui::EmptyUserpic>(
Ui::EmptyUserpic::UserpicColor(0),
name);
}
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());
}
box->addRow(
object_ptr<Ui::CenterWrap<Ui::FlatLabel>>(
box,
object_ptr<Ui::FlatLabel>(
box,
tr::lng_channel_invite_subscription_title(),
st::inviteLinkSubscribeBoxTitle)));
box->addRow(
object_ptr<Ui::CenterWrap<Ui::FlatLabel>>(
box,
object_ptr<Ui::FlatLabel>(
box,
tr::lng_channel_invite_subscription_about(
lt_channel,
rpl::single(Ui::Text::Bold(name)),
lt_price,
tr::lng_credits_summary_options_credits(
lt_count,
rpl::single(amount) | tr::to_count(),
Ui::Text::Bold),
Ui::Text::WithEntities),
st::inviteLinkSubscribeBoxAbout)));
Ui::AddSkip(content);
box->addRow(
object_ptr<Ui::CenterWrap<Ui::FlatLabel>>(
box,
object_ptr<Ui::FlatLabel>(
box,
tr::lng_channel_invite_subscription_terms(
lt_link,
rpl::combine(
tr::lng_paid_react_agree_link(),
tr::lng_group_invite_subscription_about_url()
) | rpl::map([](const QString &text, const QString &url) {
return Ui::Text::Link(text, url);
}),
Ui::Text::RichLangValue),
st::inviteLinkSubscribeBoxTerms)));
{
const auto balance = Settings::AddBalanceWidget(
content,
session->credits().balanceValue(),
true);
session->credits().load(true);
rpl::combine(
balance->sizeValue(),
content->sizeValue()
) | rpl::start_with_next([=](const QSize &, const QSize &) {
balance->moveToRight(
st::creditsHistoryRightSkip * 2,
st::creditsHistoryRightSkip);
balance->update();
}, balance->lifetime());
}
const auto sendCredits = [=, weak = Ui::MakeWeak(box)] {
const auto show = box->uiShow();
const auto buttonWidth = state->saveButton
? state->saveButton->width()
: 0;
state->api->request(
MTPpayments_SendStarsForm(
MTP_flags(0),
MTP_long(formId),
MTP_inputInvoiceChatInviteSubscription(MTP_string(hash)))
).done([=](const MTPpayments_PaymentResult &result) {
state->api = std::nullopt;
state->loading.force_assign(false);
result.match([&](const MTPDpayments_paymentResult &data) {
session->api().applyUpdates(data.vupdates());
}, [](const MTPDpayments_paymentVerificationNeeded &data) {
});
if (weak) {
box->closeBox();
}
}).fail([=](const MTP::Error &error) {
const auto id = error.type();
if (weak) {
state->api = std::nullopt;
}
show->showToast(id);
state->loading.force_assign(false);
}).send();
if (state->saveButton) {
state->saveButton->resizeToWidth(buttonWidth);
}
};
auto confirmText = tr::lng_channel_invite_subscription_button();
state->saveButton = box->addButton(std::move(confirmText), [=] {
if (state->api) {
return;
}
state->api.emplace(&session->mtp());
state->loading.force_assign(true);
const auto done = [=](Settings::SmallBalanceResult result) {
if (result == Settings::SmallBalanceResult::Success
|| result == Settings::SmallBalanceResult::Already) {
sendCredits();
} else {
state->api = std::nullopt;
state->loading.force_assign(false);
}
};
Settings::MaybeRequestBalanceIncrease(
Main::MakeSessionShow(box->uiShow(), session),
amount,
Settings::SmallBalanceSubscription{ .name = name },
done);
});
if (const auto saveButton = state->saveButton) {
using namespace Info::Statistics;
const auto loadingAnimation = InfiniteRadialAnimationWidget(
saveButton,
saveButton->height() / 2,
&st::editStickerSetNameLoading);
AddChildToWidgetCenter(saveButton, loadingAnimation);
loadingAnimation->showOn(
state->loading.value() | rpl::map(rpl::mappers::_1));
}
box->addButton(tr::lng_cancel(), [=] { box->closeBox(); });
}
} // namespace
void CheckChatInvite(
not_null<Window::SessionController*> controller,
const QString &hash,
ChannelData *invitePeekChannel) {
ChannelData *invitePeekChannel,
Fn<void()> loaded) {
const auto session = &controller->session();
const auto weak = base::make_weak(controller);
session->api().checkChatInvite(hash, [=](const MTPChatInvite &result) {
@@ -114,6 +352,9 @@ void CheckChatInvite(
if (!strong) {
return;
}
if (loaded) {
loaded();
}
Core::App().hideMediaView();
const auto show = [&](not_null<PeerData*> chat) {
const auto way = Window::SectionShow::Way::Forward;
@@ -125,11 +366,26 @@ void CheckChatInvite(
};
result.match([=](const MTPDchatInvite &data) {
const auto isGroup = !data.is_broadcast();
const auto box = strong->show(Box<ConfirmInviteBox>(
session,
data,
invitePeekChannel,
[=] { SubmitChatInvite(weak, session, hash, isGroup); }));
const auto hasPricing = !!data.vsubscription_pricing();
const auto canRefulfill = data.is_can_refulfill_subscription();
if (hasPricing
&& !canRefulfill
&& !data.vsubscription_form_id()) {
strong->uiShow()->showToast(
tr::lng_confirm_phone_link_invalid(tr::now));
return;
}
const auto box = (hasPricing && !canRefulfill)
? strong->show(Box(
ConfirmSubscriptionBox,
session,
hash,
&data))
: strong->show(Box<ConfirmInviteBox>(
session,
data,
invitePeekChannel,
[=] { SubmitChatInvite(weak, session, hash, isGroup); }));
if (invitePeekChannel) {
box->boxClosing(
) | rpl::filter([=] {

View File

@@ -38,7 +38,8 @@ namespace Api {
void CheckChatInvite(
not_null<Window::SessionController*> controller,
const QString &hash,
ChannelData *invitePeekChannel = nullptr);
ChannelData *invitePeekChannel = nullptr,
Fn<void()> loaded = nullptr);
} // namespace Api

View File

@@ -94,32 +94,70 @@ constexpr auto kTransactionsLimit = 100;
}, [](const MTPDstarsTransactionPeerAds &) {
return Data::CreditsHistoryEntry::PeerType::Ads;
}),
.refunded = tl.data().is_refund(),
.pending = tl.data().is_pending(),
.failed = tl.data().is_failed(),
.subscriptionUntil = tl.data().vsubscription_period()
? base::unixtime::parse(base::unixtime::now()
+ tl.data().vsubscription_period()->v)
: QDateTime(),
.successDate = tl.data().vtransaction_date()
? base::unixtime::parse(tl.data().vtransaction_date()->v)
: QDateTime(),
.successLink = qs(tl.data().vtransaction_url().value_or_empty()),
.reaction = tl.data().is_reaction(),
.refunded = tl.data().is_refund(),
.pending = tl.data().is_pending(),
.failed = tl.data().is_failed(),
.in = (int64(tl.data().vstars().v) >= 0),
.gift = tl.data().is_gift(),
};
}
[[nodiscard]] Data::SubscriptionEntry SubscriptionFromTL(
const MTPStarsSubscription &tl) {
return Data::SubscriptionEntry{
.id = qs(tl.data().vid()),
.inviteHash = qs(tl.data().vchat_invite_hash().value_or_empty()),
.until = base::unixtime::parse(tl.data().vuntil_date().v),
.subscription = Data::PeerSubscription{
.credits = tl.data().vpricing().data().vamount().v,
.period = tl.data().vpricing().data().vperiod().v,
},
.barePeerId = peerFromMTP(tl.data().vpeer()).value,
.cancelled = tl.data().is_canceled(),
.expired = (base::unixtime::now() > tl.data().vuntil_date().v),
.canRefulfill = tl.data().is_can_refulfill(),
};
}
[[nodiscard]] Data::CreditsStatusSlice StatusFromTL(
const MTPpayments_StarsStatus &status,
not_null<PeerData*> peer) {
peer->owner().processUsers(status.data().vusers());
peer->owner().processChats(status.data().vchats());
const auto &data = status.data();
peer->owner().processUsers(data.vusers());
peer->owner().processChats(data.vchats());
auto entries = std::vector<Data::CreditsHistoryEntry>();
if (const auto history = data.vhistory()) {
entries.reserve(history->v.size());
for (const auto &tl : history->v) {
entries.push_back(HistoryFromTL(tl, peer));
}
}
auto subscriptions = std::vector<Data::SubscriptionEntry>();
if (const auto history = data.vsubscriptions()) {
subscriptions.reserve(history->v.size());
for (const auto &tl : history->v) {
subscriptions.push_back(SubscriptionFromTL(tl));
}
}
return Data::CreditsStatusSlice{
.list = ranges::views::all(
status.data().vhistory().v
) | ranges::views::transform([&](const MTPStarsTransaction &tl) {
return HistoryFromTL(tl, peer);
}) | ranges::to_vector,
.list = std::move(entries),
.subscriptions = std::move(subscriptions),
.balance = status.data().vbalance().v,
.subscriptionsMissingBalance
= status.data().vsubscriptions_missing_balance().value_or_empty(),
.allLoaded = !status.data().vnext_offset().has_value(),
.token = qs(status.data().vnext_offset().value_or_empty()),
.tokenSubscriptions = qs(
status.data().vsubscriptions_next_offset().value_or_empty()),
};
}
@@ -195,10 +233,14 @@ void CreditsStatus::request(
_peer->isSelf() ? MTP_inputPeerSelf() : _peer->input
)).done([=](const TLResult &result) {
_requestId = 0;
done(StatusFromTL(result, _peer));
if (const auto onstack = done) {
onstack(StatusFromTL(result, _peer));
}
}).fail([=] {
_requestId = 0;
done({});
if (const auto onstack = done) {
onstack({});
}
}).send();
}
@@ -220,6 +262,7 @@ void CreditsHistory::request(
}
_requestId = _api.request(MTPpayments_GetStarsTransactions(
MTP_flags(_flags),
MTPstring(), // subscription_id
_peer->isSelf() ? MTP_inputPeerSelf() : _peer->input,
MTP_string(token),
MTP_int(kTransactionsLimit)
@@ -232,6 +275,25 @@ void CreditsHistory::request(
}).send();
}
void CreditsHistory::requestSubscriptions(
const Data::CreditsStatusSlice::OffsetToken &token,
Fn<void(Data::CreditsStatusSlice)> done) {
if (_requestId) {
return;
}
_requestId = _api.request(MTPpayments_GetStarsSubscriptions(
MTP_flags(0),
_peer->isSelf() ? MTP_inputPeerSelf() : _peer->input,
MTP_string(token)
)).done([=](const MTPpayments_StarsStatus &result) {
_requestId = 0;
done(StatusFromTL(result, _peer));
}).fail([=] {
_requestId = 0;
done({});
}).send();
}
Data::CreditTopupOptions CreditsTopupOptions::options() const {
return _options;
}

View File

@@ -60,6 +60,9 @@ public:
void request(
const Data::CreditsStatusSlice::OffsetToken &token,
Fn<void(Data::CreditsStatusSlice)> done);
void requestSubscriptions(
const Data::CreditsStatusSlice::OffsetToken &token,
Fn<void(Data::CreditsStatusSlice)> done);
private:
using HistoryTL = MTPpayments_GetStarsTransactions;

View File

@@ -8,12 +8,12 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "api/api_invite_links.h"
#include "api/api_chat_participants.h"
#include "data/data_peer.h"
#include "data/data_user.h"
#include "data/data_chat.h"
#include "data/data_channel.h"
#include "data/data_session.h"
#include "data/data_changes.h"
#include "data/data_channel.h"
#include "data/data_chat.h"
#include "data/data_peer.h"
#include "data/data_session.h"
#include "data/data_user.h"
#include "main/main_session.h"
#include "base/unixtime.h"
#include "apiwrap.h"
@@ -69,59 +69,46 @@ JoinedByLinkSlice ParseJoinedByLinkSlice(
InviteLinks::InviteLinks(not_null<ApiWrap*> api) : _api(api) {
}
void InviteLinks::create(
not_null<PeerData*> peer,
Fn<void(Link)> done,
const QString &label,
TimeId expireDate,
int usageLimit,
bool requestApproval) {
performCreate(
peer,
std::move(done),
false,
label,
expireDate,
usageLimit,
requestApproval);
void InviteLinks::create(const CreateInviteLinkArgs &args) {
performCreate(args, false);
}
void InviteLinks::performCreate(
not_null<PeerData*> peer,
Fn<void(Link)> done,
bool revokeLegacyPermanent,
const QString &label,
TimeId expireDate,
int usageLimit,
bool requestApproval) {
if (const auto i = _createCallbacks.find(peer)
const CreateInviteLinkArgs &args,
bool revokeLegacyPermanent) {
if (const auto i = _createCallbacks.find(args.peer)
; i != end(_createCallbacks)) {
if (done) {
i->second.push_back(std::move(done));
if (args.done) {
i->second.push_back(std::move(args.done));
}
return;
}
auto &callbacks = _createCallbacks[peer];
if (done) {
callbacks.push_back(std::move(done));
auto &callbacks = _createCallbacks[args.peer];
if (args.done) {
callbacks.push_back(std::move(args.done));
}
const auto requestApproval = !args.subscription && args.requestApproval;
using Flag = MTPmessages_ExportChatInvite::Flag;
_api->request(MTPmessages_ExportChatInvite(
MTP_flags((revokeLegacyPermanent
? Flag::f_legacy_revoke_permanent
: Flag(0))
| (!label.isEmpty() ? Flag::f_title : Flag(0))
| (expireDate ? Flag::f_expire_date : Flag(0))
| ((!requestApproval && usageLimit)
| (!args.label.isEmpty() ? Flag::f_title : Flag(0))
| (args.expireDate ? Flag::f_expire_date : Flag(0))
| ((!requestApproval && args.usageLimit)
? Flag::f_usage_limit
: Flag(0))
| (requestApproval ? Flag::f_request_needed : Flag(0))),
peer->input,
MTP_int(expireDate),
MTP_int(usageLimit),
MTP_string(label)
)).done([=](const MTPExportedChatInvite &result) {
| (requestApproval ? Flag::f_request_needed : Flag(0))
| (args.subscription ? Flag::f_subscription_pricing : Flag(0))),
args.peer->input,
MTP_int(args.expireDate),
MTP_int(args.usageLimit),
MTP_string(args.label),
MTP_starsSubscriptionPricing(
MTP_int(args.subscription.period),
MTP_long(args.subscription.credits))
)).done([=, peer = args.peer](const MTPExportedChatInvite &result) {
const auto callbacks = _createCallbacks.take(peer);
const auto link = prepend(peer, peer->session().user(), result);
if (link && callbacks) {
@@ -129,7 +116,7 @@ void InviteLinks::performCreate(
callback(*link);
}
}
}).fail([=] {
}).fail([=, peer = args.peer] {
_createCallbacks.erase(peer);
}).send();
}
@@ -238,6 +225,15 @@ void InviteLinks::edit(
requestApproval);
}
void InviteLinks::editTitle(
not_null<PeerData*> peer,
not_null<UserData*> admin,
const QString &link,
const QString &label,
Fn<void(Link)> done) {
performEdit(peer, admin, link, done, false, label, 0, 0, false, true);
}
void InviteLinks::performEdit(
not_null<PeerData*> peer,
not_null<UserData*> admin,
@@ -247,7 +243,8 @@ void InviteLinks::performEdit(
const QString &label,
TimeId expireDate,
int usageLimit,
bool requestApproval) {
bool requestApproval,
bool editOnlyTitle) {
const auto key = LinkKey{ peer, link };
if (_deleteCallbacks.contains(key)) {
return;
@@ -272,7 +269,7 @@ void InviteLinks::performEdit(
? Flag::f_request_needed
: Flag(0));
_api->request(MTPmessages_EditExportedChatInvite(
MTP_flags(flags),
MTP_flags(editOnlyTitle ? Flag::f_title : flags),
peer->input,
MTP_string(link),
MTP_int(expireDate),
@@ -344,7 +341,7 @@ void InviteLinks::revokePermanent(
} else if (!admin->isSelf()) {
crl::on_main(&peer->session(), done);
} else {
performCreate(peer, callback, true);
performCreate({ peer, callback }, true);
}
}
@@ -750,6 +747,12 @@ auto InviteLinks::parse(
return std::optional<Link>(Link{
.link = qs(data.vlink()),
.label = qs(data.vtitle().value_or_empty()),
.subscription = data.vsubscription_pricing()
? Data::PeerSubscription{
data.vsubscription_pricing()->data().vamount().v,
data.vsubscription_pricing()->data().vperiod().v,
}
: Data::PeerSubscription(),
.admin = peer->session().data().user(data.vadmin_id()),
.date = data.vdate().v,
.startDate = data.vstart_date().value_or_empty(),

View File

@@ -9,11 +9,14 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
class ApiWrap;
#include "data/data_subscriptions.h"
namespace Api {
struct InviteLink {
QString link;
QString label;
Data::PeerSubscription subscription;
not_null<UserData*> admin;
TimeId date = 0;
TimeId startDate = 0;
@@ -53,6 +56,16 @@ struct InviteLinkUpdate {
not_null<PeerData*> peer,
const MTPmessages_ChatInviteImporters &slice);
struct CreateInviteLinkArgs {
not_null<PeerData*> peer;
Fn<void(InviteLink)> done = nullptr;
QString label;
TimeId expireDate = 0;
int usageLimit = 0;
bool requestApproval = false;
Data::PeerSubscription subscription;
};
class InviteLinks final {
public:
explicit InviteLinks(not_null<ApiWrap*> api);
@@ -61,13 +74,7 @@ public:
using Links = PeerInviteLinks;
using Update = InviteLinkUpdate;
void create(
not_null<PeerData*> peer,
Fn<void(Link)> done = nullptr,
const QString &label = QString(),
TimeId expireDate = 0,
int usageLimit = 0,
bool requestApproval = false);
void create(const CreateInviteLinkArgs &args);
void edit(
not_null<PeerData*> peer,
not_null<UserData*> admin,
@@ -77,6 +84,12 @@ public:
int usageLimit,
bool requestApproval,
Fn<void(Link)> done = nullptr);
void editTitle(
not_null<PeerData*> peer,
not_null<UserData*> admin,
const QString &link,
const QString &label,
Fn<void(Link)> done = nullptr);
void revoke(
not_null<PeerData*> peer,
not_null<UserData*> admin,
@@ -187,15 +200,11 @@ private:
const QString &label = QString(),
TimeId expireDate = 0,
int usageLimit = 0,
bool requestApproval = false);
bool requestApproval = false,
bool editOnlyTitle = false);
void performCreate(
not_null<PeerData*> peer,
Fn<void(Link)> done,
bool revokeLegacyPermanent,
const QString &label = QString(),
TimeId expireDate = 0,
int usageLimit = 0,
bool requestApproval = false);
const CreateInviteLinkArgs &args,
bool revokeLegacyPermanent);
void requestJoinedFirstSlice(LinkKey key);
[[nodiscard]] std::optional<JoinedByLinkSlice> lookupJoinedFirstSlice(

View File

@@ -39,9 +39,9 @@ namespace {
};
}
[[nodiscard]] Data::SubscriptionOptions GiftCodesFromTL(
[[nodiscard]] Data::PremiumSubscriptionOptions GiftCodesFromTL(
const QVector<MTPPremiumGiftCodeOption> &tlOptions) {
auto options = SubscriptionOptionsFromTL(tlOptions);
auto options = PremiumSubscriptionOptionsFromTL(tlOptions);
for (auto i = 0; i < options.size(); i++) {
const auto &tlOption = tlOptions[i].data();
const auto perUserText = Ui::FillAmountAndCurrency(
@@ -143,7 +143,7 @@ void Premium::reloadPromo() {
const auto &data = result.data();
_session->data().processUsers(data.vusers());
_subscriptionOptions = SubscriptionOptionsFromTL(
_subscriptionOptions = PremiumSubscriptionOptionsFromTL(
data.vperiod_options().v);
for (const auto &option : data.vperiod_options().v) {
if (option.data().vmonths().v == 1) {
@@ -372,7 +372,7 @@ void Premium::resolveGiveawayInfo(
}).send();
}
const Data::SubscriptionOptions &Premium::subscriptionOptions() const {
const Data::PremiumSubscriptionOptions &Premium::subscriptionOptions() const {
return _subscriptionOptions;
}
@@ -547,7 +547,7 @@ Payments::InvoicePremiumGiftCode PremiumGiftCodeOptions::invoice(
};
}
Data::SubscriptionOptions PremiumGiftCodeOptions::options(int amount) {
Data::PremiumSubscriptionOptions PremiumGiftCodeOptions::options(int amount) {
const auto it = _subscriptionOptions.find(amount);
if (it != end(_subscriptionOptions)) {
return it->second;

View File

@@ -7,7 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#pragma once
#include "data/data_subscription_option.h"
#include "data/data_premium_subscription_option.h"
#include "mtproto/sender.h"
class History;
@@ -106,7 +106,7 @@ public:
Fn<void(GiveawayInfo)> done);
[[nodiscard]] auto subscriptionOptions() const
-> const Data::SubscriptionOptions &;
-> const Data::PremiumSubscriptionOptions &;
[[nodiscard]] rpl::producer<> somePremiumRequiredResolved() const;
void resolvePremiumRequired(not_null<UserData*> user);
@@ -156,7 +156,7 @@ private:
MsgId _giveawayInfoMessageId = 0;
Fn<void(GiveawayInfo)> _giveawayInfoDone;
Data::SubscriptionOptions _subscriptionOptions;
Data::PremiumSubscriptionOptions _subscriptionOptions;
rpl::event_stream<> _somePremiumRequiredResolved;
base::flat_set<not_null<UserData*>> _resolvePremiumRequiredUsers;
@@ -170,7 +170,7 @@ public:
PremiumGiftCodeOptions(not_null<PeerData*> peer);
[[nodiscard]] rpl::producer<rpl::no_value, QString> request();
[[nodiscard]] Data::SubscriptionOptions options(int amount);
[[nodiscard]] Data::PremiumSubscriptionOptions options(int amount);
[[nodiscard]] const std::vector<int> &availablePresets() const;
[[nodiscard]] int monthsFromPreset(int monthsIndex);
[[nodiscard]] Payments::InvoicePremiumGiftCode invoice(
@@ -200,8 +200,9 @@ private:
int quantity = 0;
};
using Amount = int;
using PremiumSubscriptionOptions = Data::PremiumSubscriptionOptions;
const not_null<PeerData*> _peer;
base::flat_map<Amount, Data::SubscriptionOptions> _subscriptionOptions;
base::flat_map<Amount, PremiumSubscriptionOptions> _subscriptionOptions;
struct {
std::vector<int> months;
std::vector<float64> totalCosts;

View File

@@ -13,7 +13,7 @@ namespace Api {
constexpr auto kDiscountDivider = 1.;
Data::SubscriptionOption CreateSubscriptionOption(
Data::PremiumSubscriptionOption CreateSubscriptionOption(
int months,
int monthlyAmount,
int64 amount,

View File

@@ -7,11 +7,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#pragma once
#include "data/data_subscription_option.h"
#include "data/data_premium_subscription_option.h"
namespace Api {
[[nodiscard]] Data::SubscriptionOption CreateSubscriptionOption(
[[nodiscard]] Data::PremiumSubscriptionOption CreateSubscriptionOption(
int months,
int monthlyAmount,
int64 amount,
@@ -19,22 +19,22 @@ namespace Api {
const QString &botUrl);
template<typename Option>
[[nodiscard]] Data::SubscriptionOptions SubscriptionOptionsFromTL(
const QVector<Option> &tlOptions) {
if (tlOptions.isEmpty()) {
[[nodiscard]] auto PremiumSubscriptionOptionsFromTL(
const QVector<Option> &tlOpts) -> Data::PremiumSubscriptionOptions {
if (tlOpts.isEmpty()) {
return {};
}
auto result = Data::SubscriptionOptions();
auto result = Data::PremiumSubscriptionOptions();
const auto monthlyAmount = [&] {
const auto &min = ranges::min_element(
tlOptions,
tlOpts,
ranges::less(),
[](const Option &o) { return o.data().vamount().v; }
)->data();
return min.vamount().v / float64(min.vmonths().v);
}();
result.reserve(tlOptions.size());
for (const auto &tlOption : tlOptions) {
result.reserve(tlOpts.size());
for (const auto &tlOption : tlOpts) {
const auto &option = tlOption.data();
auto botUrl = QString();
if constexpr (!std::is_same_v<Option, MTPPremiumGiftCodeOption>) {

View File

@@ -41,14 +41,14 @@ void InnerFillMessagePostFlags(
const SendOptions &options,
not_null<PeerData*> peer,
MessageFlags &flags) {
const auto anonymousPost = peer->amAnonymous();
if (ShouldSendSilent(peer, options)) {
flags |= MessageFlag::Silent;
}
if (!anonymousPost || options.sendAs) {
if (!peer->amAnonymous()) {
flags |= MessageFlag::HasFromId;
return;
} else if (peer->asMegagroup()) {
}
const auto channel = peer->asBroadcast();
if (!channel) {
return;
}
flags |= MessageFlag::Post;
@@ -57,7 +57,7 @@ void InnerFillMessagePostFlags(
return;
}
flags |= MessageFlag::HasViews;
if (peer->asChannel()->addsSignature()) {
if (channel->addsSignature()) {
flags |= MessageFlag::HasPostAuthor;
}
}
@@ -165,25 +165,15 @@ void SendExistingMedia(
flags |= MessageFlag::HasReplyInfo;
sendFlags |= MTPmessages_SendMedia::Flag::f_reply_to;
}
const auto anonymousPost = peer->amAnonymous();
const auto silentPost = ShouldSendSilent(peer, action.options);
InnerFillMessagePostFlags(action.options, peer, flags);
if (silentPost) {
sendFlags |= MTPmessages_SendMedia::Flag::f_silent;
}
const auto sendAs = action.options.sendAs;
const auto messageFromId = sendAs
? sendAs->id
: anonymousPost
? 0
: session->userPeerId();
if (sendAs) {
sendFlags |= MTPmessages_SendMedia::Flag::f_send_as;
}
const auto messagePostAuthor = peer->isBroadcast()
? session->user()->name()
: QString();
auto caption = TextWithEntities{
message.textWithTags.text,
TextUtilities::ConvertTextTagsToEntities(message.textWithTags.tags)
@@ -219,11 +209,11 @@ void SendExistingMedia(
history->addNewLocalMessage({
.id = newId.msg,
.flags = flags,
.from = messageFromId,
.from = NewMessageFromId(action),
.replyTo = action.replyTo,
.date = HistoryItem::NewMessageDate(action.options),
.date = NewMessageDate(action.options),
.shortcutId = action.options.shortcutId,
.postAuthor = messagePostAuthor,
.postAuthor = NewMessagePostAuthor(action),
.effectId = action.options.effectId,
}, media, caption);
@@ -361,25 +351,15 @@ bool SendDice(MessageToSend &message) {
flags |= MessageFlag::HasReplyInfo;
sendFlags |= MTPmessages_SendMedia::Flag::f_reply_to;
}
const auto anonymousPost = peer->amAnonymous();
const auto silentPost = ShouldSendSilent(peer, action.options);
InnerFillMessagePostFlags(action.options, peer, flags);
if (silentPost) {
sendFlags |= MTPmessages_SendMedia::Flag::f_silent;
}
const auto sendAs = action.options.sendAs;
const auto messageFromId = sendAs
? sendAs->id
: anonymousPost
? 0
: session->userPeerId();
if (sendAs) {
sendFlags |= MTPmessages_SendMedia::Flag::f_send_as;
}
const auto messagePostAuthor = peer->isBroadcast()
? session->user()->name()
: QString();
if (action.options.scheduled) {
flags |= MessageFlag::IsOrWasScheduled;
sendFlags |= MTPmessages_SendMedia::Flag::f_schedule_date;
@@ -401,11 +381,11 @@ bool SendDice(MessageToSend &message) {
history->addNewLocalMessage({
.id = newId.msg,
.flags = flags,
.from = messageFromId,
.from = NewMessageFromId(action),
.replyTo = action.replyTo,
.date = HistoryItem::NewMessageDate(action.options),
.date = NewMessageDate(action.options),
.shortcutId = action.options.shortcutId,
.postAuthor = messagePostAuthor,
.postAuthor = NewMessagePostAuthor(action),
.effectId = action.options.effectId,
}, TextWithEntities(), MTP_messageMediaDice(
MTP_int(0),
@@ -529,7 +509,6 @@ void SendConfirmedFile(
if (file->to.replyTo) {
flags |= MessageFlag::HasReplyInfo;
}
const auto anonymousPost = peer->amAnonymous();
FillMessagePostFlags(action, peer, flags);
if (file->to.options.scheduled) {
flags |= MessageFlag::IsOrWasScheduled;
@@ -551,16 +530,6 @@ void SendConfirmedFile(
if (file->to.options.invertCaption) {
flags |= MessageFlag::InvertMedia;
}
const auto messageFromId = file->to.options.sendAs
? file->to.options.sendAs->id
: anonymousPost
? PeerId()
: session->userPeerId();
const auto messagePostAuthor = peer->isBroadcast()
? session->user()->name()
: QString();
const auto media = MTPMessageMedia([&] {
if (file->type == SendMediaType::Photo) {
using Flag = MTPDmessageMediaPhoto::Flag;
@@ -626,11 +595,11 @@ void SendConfirmedFile(
history->addNewLocalMessage({
.id = newId.msg,
.flags = flags,
.from = messageFromId,
.from = NewMessageFromId(action),
.replyTo = file->to.replyTo,
.date = HistoryItem::NewMessageDate(file->to.options),
.date = NewMessageDate(file->to.options),
.shortcutId = file->to.options.shortcutId,
.postAuthor = messagePostAuthor,
.postAuthor = NewMessagePostAuthor(action),
.groupedId = groupId,
.effectId = file->to.options.effectId,
}, caption, media);

View File

@@ -14,7 +14,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
namespace Api {
namespace {
constexpr auto kRefreshAppConfigTimeout = 3 * crl::time(1000);
constexpr auto kRefreshAppConfigTimeout = crl::time(1);
} // namespace
@@ -24,19 +24,40 @@ SensitiveContent::SensitiveContent(not_null<ApiWrap*> api)
, _appConfigReloadTimer([=] { _session->appConfig().refresh(); }) {
}
void SensitiveContent::reload() {
if (_requestId) {
void SensitiveContent::preload() {
if (!_loaded) {
reload();
}
}
void SensitiveContent::reload(bool force) {
if (_loadRequestId) {
if (force) {
_loadPending = true;
}
return;
}
_requestId = _api.request(MTPaccount_GetContentSettings(
_loaded = true;
_loadRequestId = _api.request(MTPaccount_GetContentSettings(
)).done([=](const MTPaccount_ContentSettings &result) {
_requestId = 0;
result.match([&](const MTPDaccount_contentSettings &data) {
_enabled = data.is_sensitive_enabled();
_canChange = data.is_sensitive_can_change();
});
_loadRequestId = 0;
const auto &data = result.data();
const auto enabled = data.is_sensitive_enabled();
const auto canChange = data.is_sensitive_can_change();
const auto changed = (_enabled.current() != enabled)
|| (_canChange.current() != canChange);
if (changed) {
_enabled = enabled;
_canChange = canChange;
}
if (base::take(_appConfigReloadForce) || changed) {
_appConfigReloadTimer.callOnce(kRefreshAppConfigTimeout);
}
if (base::take(_loadPending)) {
reload();
}
}).fail([=] {
_requestId = 0;
_loadRequestId = 0;
}).send();
}
@@ -57,17 +78,24 @@ void SensitiveContent::update(bool enabled) {
return;
}
using Flag = MTPaccount_SetContentSettings::Flag;
_api.request(_requestId).cancel();
_requestId = _api.request(MTPaccount_SetContentSettings(
_api.request(_saveRequestId).cancel();
if (const auto load = base::take(_loadRequestId)) {
_api.request(load).cancel();
_loadPending = true;
}
const auto finish = [=] {
_saveRequestId = 0;
if (base::take(_loadPending)) {
_appConfigReloadForce = true;
reload(true);
} else {
_appConfigReloadTimer.callOnce(kRefreshAppConfigTimeout);
}
};
_saveRequestId = _api.request(MTPaccount_SetContentSettings(
MTP_flags(enabled ? Flag::f_sensitive_enabled : Flag(0))
)).done([=] {
_requestId = 0;
}).fail([=] {
_requestId = 0;
}).send();
)).done(finish).fail(finish).send();
_enabled = enabled;
_appConfigReloadTimer.callOnce(kRefreshAppConfigTimeout);
}
} // namespace Api

View File

@@ -22,7 +22,8 @@ class SensitiveContent final {
public:
explicit SensitiveContent(not_null<ApiWrap*> api);
void reload();
void preload();
void reload(bool force = false);
void update(bool enabled);
[[nodiscard]] bool enabledCurrent() const;
@@ -32,10 +33,14 @@ public:
private:
const not_null<Main::Session*> _session;
MTP::Sender _api;
mtpRequestId _requestId = 0;
mtpRequestId _loadRequestId = 0;
mtpRequestId _saveRequestId = 0;
rpl::variable<bool> _enabled = false;
rpl::variable<bool> _canChange = false;
base::Timer _appConfigReloadTimer;
bool _appConfigReloadForce = false;
bool _loadPending = false;
bool _loaded = false;
};

View File

@@ -21,6 +21,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "mtproto/mtproto_config.h"
#include "mtproto/mtproto_dc_options.h"
#include "data/business/data_shortcut_messages.h"
#include "data/components/credits.h"
#include "data/components/scheduled_messages.h"
#include "data/components/top_peers.h"
#include "data/notify/data_notify_settings.h"
@@ -2618,7 +2619,7 @@ void Updates::feedUpdate(const MTPUpdate &update) {
case mtpc_updateStarsBalance: {
const auto &data = update.c_updateStarsBalance();
_session->setCredits(data.vbalance().v);
_session->credits().apply(data);
} break;
}

View File

@@ -3282,9 +3282,8 @@ void ApiWrap::forwardMessages(
if (!action.options.scheduled && !action.options.shortcutId) {
histories.readInbox(history);
}
const auto anonymousPost = peer->amAnonymous();
const auto silentPost = ShouldSendSilent(peer, action.options);
const auto sendAs = action.options.sendAs;
const auto silentPost = ShouldSendSilent(peer, action.options);
using SendFlag = MTPmessages_ForwardMessages::Flag;
auto flags = MessageFlags();
@@ -3375,23 +3374,14 @@ void ApiWrap::forwardMessages(
const auto newId = FullMsgId(
peer->id,
_session->data().nextLocalMessageId());
const auto self = _session->user();
const auto messageFromId = sendAs
? sendAs->id
: anonymousPost
? PeerId(0)
: self->id;
const auto messagePostAuthor = peer->isBroadcast()
? self->name()
: QString();
history->addNewLocalMessage({
.id = newId.msg,
.flags = flags,
.from = messageFromId,
.from = NewMessageFromId(action),
.replyTo = { .topicRootId = topMsgId },
.date = HistoryItem::NewMessageDate(action.options),
.date = NewMessageDate(action.options),
.shortcutId = action.options.shortcutId,
.postAuthor = messagePostAuthor,
.postAuthor = NewMessagePostAuthor(action),
// forwarded messages don't have effects
//.effectId = action.options.effectId,
@@ -3466,8 +3456,6 @@ void ApiWrap::sendSharedContact(
const auto newId = FullMsgId(
peer->id,
_session->data().nextLocalMessageId());
const auto anonymousPost = peer->amAnonymous();
auto flags = NewMessageFlags(peer);
if (action.replyTo) {
flags |= MessageFlag::HasReplyInfo;
@@ -3479,22 +3467,14 @@ void ApiWrap::sendSharedContact(
if (action.options.shortcutId) {
flags |= MessageFlag::ShortcutMessage;
}
const auto messageFromId = action.options.sendAs
? action.options.sendAs->id
: anonymousPost
? PeerId()
: _session->userPeerId();
const auto messagePostAuthor = peer->isBroadcast()
? _session->user()->name()
: QString();
const auto item = history->addNewLocalMessage({
.id = newId.msg,
.flags = flags,
.from = messageFromId,
.from = NewMessageFromId(action),
.replyTo = action.replyTo,
.date = HistoryItem::NewMessageDate(action.options),
.date = NewMessageDate(action.options),
.shortcutId = action.options.shortcutId,
.postAuthor = messagePostAuthor,
.postAuthor = NewMessagePostAuthor(action),
.effectId = action.options.effectId,
}, TextWithEntities(), MTP_messageMediaContact(
MTP_string(phone),
@@ -3780,7 +3760,6 @@ void ApiWrap::sendMessage(MessageToSend &&message) {
MTP_string(fields.url),
MTP_int(page->pendingTill)));
}
const auto anonymousPost = peer->amAnonymous();
const auto silentPost = ShouldSendSilent(peer, action.options);
FillMessagePostFlags(action, peer, flags);
if ((exactWebPage && !ignoreWebPage && message.webPage.invert)
@@ -3808,18 +3787,10 @@ void ApiWrap::sendMessage(MessageToSend &&message) {
history->startSavingCloudDraft(draftTopicRootId);
}
const auto sendAs = action.options.sendAs;
const auto messageFromId = sendAs
? sendAs->id
: anonymousPost
? PeerId()
: _session->userPeerId();
if (sendAs) {
sendFlags |= MTPmessages_SendMessage::Flag::f_send_as;
mediaFlags |= MTPmessages_SendMedia::Flag::f_send_as;
}
const auto messagePostAuthor = peer->isBroadcast()
? _session->user()->name()
: QString();
if (action.options.scheduled) {
flags |= MessageFlag::IsOrWasScheduled;
sendFlags |= MTPmessages_SendMessage::Flag::f_schedule_date;
@@ -3837,11 +3808,11 @@ void ApiWrap::sendMessage(MessageToSend &&message) {
lastMessage = history->addNewLocalMessage({
.id = newId.msg,
.flags = flags,
.from = messageFromId,
.from = NewMessageFromId(action),
.replyTo = action.replyTo,
.date = HistoryItem::NewMessageDate(action.options),
.date = NewMessageDate(action.options),
.shortcutId = action.options.shortcutId,
.postAuthor = messagePostAuthor,
.postAuthor = NewMessagePostAuthor(action),
.effectId = action.options.effectId,
}, sending, media);
const auto done = [=](
@@ -3987,7 +3958,6 @@ void ApiWrap::sendInlineResult(
flags |= MessageFlag::HasReplyInfo;
sendFlags |= SendFlag::f_reply_to;
}
const auto anonymousPost = peer->amAnonymous();
const auto silentPost = ShouldSendSilent(peer, action.options);
FillMessagePostFlags(action, peer, flags);
if (silentPost) {
@@ -4006,30 +3976,22 @@ void ApiWrap::sendInlineResult(
}
const auto sendAs = action.options.sendAs;
const auto messageFromId = sendAs
? sendAs->id
: anonymousPost ? PeerId()
: _session->userPeerId();
if (sendAs) {
sendFlags |= MTPmessages_SendInlineBotResult::Flag::f_send_as;
}
const auto messagePostAuthor = peer->isBroadcast()
? _session->user()->name()
: QString();
_session->data().registerMessageRandomId(randomId, newId);
data->addToHistory(history, {
.id = newId.msg,
.flags = flags,
.from = messageFromId,
.from = NewMessageFromId(action),
.replyTo = action.replyTo,
.date = HistoryItem::NewMessageDate(action.options),
.date = NewMessageDate(action.options),
.shortcutId = action.options.shortcutId,
.viaBotId = ((bot && !action.options.hideViaBot)
? peerToUser(bot->id)
: UserId()),
.postAuthor = messagePostAuthor,
.postAuthor = NewMessagePostAuthor(action),
});
history->clearCloudDraft(topicRootId);

View File

@@ -898,9 +898,10 @@ void GroupInfoBox::checkInviteLink() {
channelReady();
} else if (_createdChannel->isFullLoaded() && !_creatingInviteLink) {
_creatingInviteLink = true;
_createdChannel->session().api().inviteLinks().create(
_createdChannel->session().api().inviteLinks().create({
_createdChannel,
crl::guard(this, [=](auto&&) { channelReady(); }));
crl::guard(this, [=](auto&&) { channelReady(); }),
});
} else {
_createdChannel->session().changes().peerUpdates(
_createdChannel,
@@ -1243,7 +1244,7 @@ void SetupChannelBox::mousePressEvent(QMouseEvent *e) {
showToast(tr::lng_create_channel_link_copied(tr::now));
} else if (_channel->isFullLoaded() && !_creatingInviteLink) {
_creatingInviteLink = true;
_channel->session().api().inviteLinks().create(_channel);
_channel->session().api().inviteLinks().create({ _channel });
}
}

View File

@@ -756,6 +756,8 @@ createPollWarningPosition: point(16px, 6px);
createPollCheckboxMargin: margins(22px, 10px, 22px, 10px);
createPollFieldTitlePadding: margins(22px, 7px, 10px, 6px);
sendGifWithCaptionEmojiPosition: point(-30px, 23px);
backgroundCheckbox: Checkbox(defaultCheckbox) {
textFg: msgServiceFg;
textFgActive: msgServiceFg;

View File

@@ -36,6 +36,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/wrap/slide_wrap.h"
#include "ui/wrap/vertical_layout.h"
#include "ui/vertical_list.h"
#include "ui/ui_utility.h"
#include "boxes/abstract_box.h" // Ui::show().
#include "window/window_session_controller.h"
#include "styles/style_layers.h"

View File

@@ -25,6 +25,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "main/main_session.h"
#include "menu/menu_send.h"
#include "ui/controls/emoji_button.h"
#include "ui/controls/emoji_button_factory.h"
#include "ui/rect.h"
#include "ui/text/text_utilities.h"
#include "ui/toast/toast.h"
@@ -37,6 +38,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/wrap/fade_wrap.h"
#include "ui/wrap/slide_wrap.h"
#include "ui/wrap/vertical_layout.h"
#include "ui/ui_utility.h"
#include "window/window_session_controller.h"
#include "styles/style_boxes.h"
#include "styles/style_chat_helpers.h" // defaultComposeFiles.
@@ -54,100 +56,6 @@ constexpr auto kSolutionLimit = 200;
constexpr auto kWarnSolutionLimit = 60;
constexpr auto kErrorLimit = 99;
[[nodiscard]] not_null<Ui::EmojiButton*> AddEmojiToggleToField(
not_null<Ui::InputField*> field,
not_null<Ui::BoxContent*> box,
not_null<Window::SessionController*> controller,
not_null<ChatHelpers::TabbedPanel*> emojiPanel,
QPoint shift) {
const auto emojiToggle = Ui::CreateChild<Ui::EmojiButton>(
field->parentWidget(),
st::defaultComposeFiles.emoji);
const auto fade = Ui::CreateChild<Ui::FadeAnimation>(
emojiToggle,
emojiToggle,
0.5);
{
const auto fadeTarget = Ui::CreateChild<Ui::RpWidget>(emojiToggle);
fadeTarget->resize(emojiToggle->size());
fadeTarget->paintRequest(
) | rpl::start_with_next([=](const QRect &rect) {
auto p = QPainter(fadeTarget);
if (fade->animating()) {
p.fillRect(fadeTarget->rect(), st::boxBg);
}
fade->paint(p);
}, fadeTarget->lifetime());
rpl::single(false) | rpl::then(
field->focusedChanges()
) | rpl::start_with_next([=](bool shown) {
if (shown) {
fade->fadeIn(st::universalDuration);
} else {
fade->fadeOut(st::universalDuration);
}
}, emojiToggle->lifetime());
fade->fadeOut(1);
fade->finish();
}
const auto outer = box->getDelegate()->outerContainer();
const auto allow = [](not_null<DocumentData*>) { return true; };
InitMessageFieldHandlers(
controller,
field,
Window::GifPauseReason::Layer,
allow);
Ui::Emoji::SuggestionsController::Init(
outer,
field,
&controller->session(),
Ui::Emoji::SuggestionsController::Options{
.suggestCustomEmoji = true,
.allowCustomWithoutPremium = allow,
});
const auto updateEmojiPanelGeometry = [=] {
const auto parent = emojiPanel->parentWidget();
const auto global = emojiToggle->mapToGlobal({ 0, 0 });
const auto local = parent->mapFromGlobal(global);
const auto right = local.x() + emojiToggle->width() * 3;
const auto isDropDown = local.y() < parent->height() / 2;
emojiPanel->setDropDown(isDropDown);
if (isDropDown) {
emojiPanel->moveTopRight(
local.y() + emojiToggle->height(),
right);
} else {
emojiPanel->moveBottomRight(local.y(), right);
}
};
rpl::combine(
box->sizeValue(),
field->geometryValue()
) | rpl::start_with_next([=](QSize outer, QRect inner) {
emojiToggle->moveToLeft(
rect::right(inner) + shift.x(),
inner.y() + shift.y());
emojiToggle->update();
}, emojiToggle->lifetime());
emojiToggle->installEventFilter(emojiPanel);
emojiToggle->addClickHandler([=] {
updateEmojiPanelGeometry();
emojiPanel->toggleAnimated();
});
const auto filterCallback = [=](not_null<QEvent*> event) {
if (event->type() == QEvent::Enter) {
updateEmojiPanelGeometry();
}
return base::EventFilterResult::Continue;
};
base::install_event_filter(emojiToggle, filterCallback);
return emojiToggle;
}
class Options {
public:
Options(
@@ -770,7 +678,7 @@ void Options::addEmptyOption() {
_chooseCorrectGroup));
const auto field = _list.back()->field();
if (const auto emojiPanel = _emojiPanel) {
const auto emojiToggle = AddEmojiToggleToField(
const auto emojiToggle = Ui::AddEmojiToggleToField(
field,
_box,
_controller,
@@ -972,7 +880,7 @@ not_null<Ui::InputField*> CreatePollBox::setupQuestion(
emojiPanel->hide();
emojiPanel->selector()->setCurrentPeer(session->user());
const auto emojiToggle = AddEmojiToggleToField(
const auto emojiToggle = Ui::AddEmojiToggleToField(
question,
this,
_controller,

View File

@@ -25,7 +25,7 @@ DownloadPathBox::DownloadPathBox(
, _path(Core::App().settings().downloadPath())
, _pathBookmark(Core::App().settings().downloadPathBookmark())
, _group(std::make_shared<Ui::RadioenumGroup<Directory>>(typeFromPath(_path)))
, _default(Core::App().canReadDefaultDownloadPath(true)
, _default(Core::App().canReadDefaultDownloadPath()
? object_ptr<Ui::Radioenum<Directory>>(
this,
_group,
@@ -149,7 +149,7 @@ void DownloadPathBox::setPathText(const QString &text) {
DownloadPathBox::Directory DownloadPathBox::typeFromPath(
const QString &path) {
if (path.isEmpty()) {
return Core::App().canReadDefaultDownloadPath(true)
return Core::App().canReadDefaultDownloadPath()
? Directory::Downloads
: Directory::Temp;
} else if (path == FileDialog::Tmp()) {

View File

@@ -763,9 +763,6 @@ void EditMessagesPrivacyBox(
lt_link,
link,
Ui::Text::WithEntities),
.st = &st::defaultMultilineToast,
.duration = Ui::Toast::kDefaultDuration * 2,
.multiline = true,
.filter = crl::guard(&controller->session(), [=](
const ClickHandlerPtr &,
Qt::MouseButton button) {

View File

@@ -39,6 +39,7 @@ void GiftCreditsBox(
not_null<Ui::GenericBox*> box,
not_null<PeerData*> peer,
Fn<void()> gifted) {
box->setWidth(st::boxWideWidth);
box->setStyle(st::creditsGiftBox);
box->setNoContentMargin(true);
box->addButton(tr::lng_create_group_back(), [=] { box->closeBox(); });

View File

@@ -23,7 +23,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_media_types.h" // Data::GiveawayStart.
#include "data/data_peer_values.h" // Data::PeerPremiumValue.
#include "data/data_session.h"
#include "data/data_subscription_option.h"
#include "data/data_premium_subscription_option.h"
#include "data/data_user.h"
#include "data/stickers/data_custom_emoji.h"
#include "info/channel_statistics/boosts/giveaway/boost_badge.h" // InfiniteRadialAnimationWidget.
@@ -66,8 +66,8 @@ namespace {
constexpr auto kUserpicsMax = size_t(3);
using GiftOption = Data::SubscriptionOption;
using GiftOptions = Data::SubscriptionOptions;
using GiftOption = Data::PremiumSubscriptionOption;
using GiftOptions = Data::PremiumSubscriptionOptions;
GiftOptions GiftOptionFromTL(const MTPDuserFull &data) {
auto result = GiftOptions();
@@ -75,7 +75,7 @@ GiftOptions GiftOptionFromTL(const MTPDuserFull &data) {
if (!gifts) {
return result;
}
result = Api::SubscriptionOptionsFromTL(gifts->v);
result = Api::PremiumSubscriptionOptionsFromTL(gifts->v);
for (auto &option : result) {
option.costPerMonth = tr::lng_premium_gift_per(
tr::now,
@@ -932,8 +932,8 @@ void ShowAlreadyPremiumToast(
Ui::Text::Link(
Ui::Text::Bold(tr::lng_gift_link_already_link(tr::now))),
Ui::Text::WithEntities),
.duration = 6 * crl::time(1000),
.filter = crl::guard(navigation, shareLink),
.duration = 6 * crl::time(1000),
});
}
@@ -1642,6 +1642,9 @@ void AddCreditsHistoryEntryTable(
not_null<Window::SessionNavigation*> controller,
not_null<Ui::VerticalLayout*> container,
const Data::CreditsHistoryEntry &entry) {
if (!entry) {
return;
}
auto table = container->add(
object_ptr<Ui::TableLayout>(
container,
@@ -1757,3 +1760,56 @@ void AddCreditsHistoryEntryTable(
Ui::Text::Link(entry.successLink, entry.successLink)));
}
}
void AddSubscriptionEntryTable(
not_null<Window::SessionNavigation*> controller,
not_null<Ui::VerticalLayout*> container,
const Data::SubscriptionEntry &s) {
if (!s) {
return;
}
auto table = container->add(
object_ptr<Ui::TableLayout>(
container,
st::giveawayGiftCodeTable),
st::giveawayGiftCodeTableMargin);
const auto peerId = PeerId(s.barePeerId);
AddTableRow(
table,
tr::lng_credits_subscription_row_to(),
controller,
peerId);
if (!s.until.isNull()) {
AddTableRow(
table,
s.expired
? tr::lng_credits_subscription_row_next_none()
: s.cancelled
? tr::lng_credits_subscription_row_next_off()
: tr::lng_credits_subscription_row_next_on(),
rpl::single(Ui::Text::WithEntities(langDateTime(s.until))));
}
}
void AddSubscriberEntryTable(
not_null<Window::SessionNavigation*> controller,
not_null<Ui::VerticalLayout*> container,
not_null<PeerData*> peer,
TimeId date) {
auto table = container->add(
object_ptr<Ui::TableLayout>(
container,
st::giveawayGiftCodeTable),
st::giveawayGiftCodeTableMargin);
AddTableRow(
table,
tr::lng_group_invite_joined_row_subscriber(),
controller,
peer->id);
if (const auto d = base::unixtime::parse(date); !d.isNull()) {
AddTableRow(
table,
tr::lng_group_invite_joined_row_date(),
rpl::single(Ui::Text::WithEntities(langDateTime(d))));
}
}

View File

@@ -19,6 +19,7 @@ namespace Data {
struct CreditsHistoryEntry;
struct GiveawayStart;
struct GiveawayResults;
struct SubscriptionEntry;
} // namespace Data
namespace Ui {
@@ -78,3 +79,13 @@ void AddCreditsHistoryEntryTable(
not_null<Window::SessionNavigation*> controller,
not_null<Ui::VerticalLayout*> container,
const Data::CreditsHistoryEntry &entry);
void AddSubscriptionEntryTable(
not_null<Window::SessionNavigation*> controller,
not_null<Ui::VerticalLayout*> container,
const Data::SubscriptionEntry &s);
void AddSubscriberEntryTable(
not_null<Window::SessionNavigation*> controller,
not_null<Ui::VerticalLayout*> container,
not_null<PeerData*> peer,
TimeId date);

View File

@@ -25,6 +25,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/text/text_options.h"
#include "ui/painter.h"
#include "ui/vertical_list.h"
#include "ui/ui_utility.h"
#include "storage/localstorage.h"
#include "boxes/abstract_box.h"
#include "boxes/premium_preview_box.h"

View File

@@ -94,7 +94,7 @@ void MaxInviteBox::mousePressEvent(QMouseEvent *e) {
showToast(tr::lng_create_channel_link_copied(tr::now));
} else if (_channel->isFullLoaded() && !_creatingInviteLink) {
_creatingInviteLink = true;
_channel->session().api().inviteLinks().create(_channel);
_channel->session().api().inviteLinks().create({ _channel });
}
}
}

View File

@@ -7,6 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#include "boxes/moderate_messages_box.h"
#include "api/api_blocked_peers.h"
#include "api/api_chat_participants.h"
#include "api/api_messages_search.h"
#include "apiwrap.h"
@@ -31,7 +32,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/boxes/confirm_box.h"
#include "ui/controls/userpic_button.h"
#include "ui/effects/ripple_animation.h"
#include "ui/effects/toggle_arrow.h"
#include "ui/layers/generic_box.h"
#include "ui/painter.h"
#include "ui/rect.h"
@@ -39,15 +39,16 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/text/text_utilities.h"
#include "ui/vertical_list.h"
#include "ui/widgets/checkbox.h"
#include "ui/widgets/expandable_peer_list.h"
#include "ui/widgets/participants_check_view.h"
#include "ui/wrap/slide_wrap.h"
#include "ui/ui_utility.h"
#include "styles/style_boxes.h"
#include "styles/style_layers.h"
#include "styles/style_window.h"
namespace {
using Participants = std::vector<not_null<PeerData*>>;
struct ModerateOptions final {
bool allCanBan = false;
bool allCanDelete = false;
@@ -103,117 +104,14 @@ ModerateOptions CalculateModerateOptions(const HistoryItemsList &items) {
};
}
class Button final : public Ui::RippleButton {
public:
Button(not_null<QWidget*> parent, int count);
void setChecked(bool checked);
[[nodiscard]] bool checked() const;
[[nodiscard]] static QSize ComputeSize(int);
private:
void paintEvent(QPaintEvent *event) override;
QImage prepareRippleMask() const override;
QPoint prepareRippleStartPosition() const override;
const int _count;
const QString _text;
bool _checked = false;
Ui::Animations::Simple _animation;
};
Button::Button(not_null<QWidget*> parent, int count)
: RippleButton(parent, st::defaultRippleAnimation)
, _count(count)
, _text(QString::number(std::abs(_count))) {
}
QSize Button::ComputeSize(int count) {
return QSize(
st::moderateBoxExpandHeight
+ st::moderateBoxExpand.width()
+ st::moderateBoxExpandInnerSkip * 4
+ st::moderateBoxExpandFont->width(
QString::number(std::abs(count)))
+ st::moderateBoxExpandToggleSize,
st::moderateBoxExpandHeight);
}
void Button::setChecked(bool checked) {
if (_checked == checked) {
return;
}
_checked = checked;
_animation.stop();
_animation.start(
[=] { update(); },
checked ? 0 : 1,
checked ? 1 : 0,
st::slideWrapDuration);
}
bool Button::checked() const {
return _checked;
}
void Button::paintEvent(QPaintEvent *event) {
auto p = Painter(this);
auto hq = PainterHighQualityEnabler(p);
Ui::RippleButton::paintRipple(p, QPoint());
const auto radius = height() / 2;
p.setPen(Qt::NoPen);
st::moderateBoxExpand.paint(
p,
radius,
(height() - st::moderateBoxExpand.height()) / 2,
width());
const auto innerSkip = st::moderateBoxExpandInnerSkip;
p.setBrush(Qt::NoBrush);
p.setPen(st::boxTextFg);
p.setFont(st::moderateBoxExpandFont);
p.drawText(
QRect(
innerSkip + radius + st::moderateBoxExpand.width(),
0,
width(),
height()),
_text,
style::al_left);
const auto path = Ui::ToggleUpDownArrowPath(
width() - st::moderateBoxExpandToggleSize - radius,
height() / 2,
st::moderateBoxExpandToggleSize,
st::moderateBoxExpandToggleFourStrokes,
_animation.value(_checked ? 1. : 0.));
p.fillPath(path, st::boxTextFg);
}
QImage Button::prepareRippleMask() const {
return Ui::RippleAnimation::RoundRectMask(size(), size().height() / 2);
}
QPoint Button::prepareRippleStartPosition() const {
return mapFromGlobal(QCursor::pos());
}
} // namespace
void CreateModerateMessagesBox(
not_null<Ui::GenericBox*> box,
const HistoryItemsList &items,
Fn<void()> confirmed) {
struct Controller final {
rpl::event_stream<bool> toggleRequestsFromTop;
rpl::event_stream<bool> toggleRequestsFromInner;
rpl::event_stream<bool> checkAllRequests;
Fn<Participants()> collectRequests;
};
using Controller = Ui::ExpandablePeerListController;
const auto [allCanBan, allCanDelete, participants]
= CalculateModerateOptions(items);
const auto inner = box->verticalLayout();
@@ -225,7 +123,12 @@ void CreateModerateMessagesBox(
const auto isSingle = participants.size() == 1;
const auto buttonPadding = isSingle
? QMargins()
: QMargins(0, 0, Button::ComputeSize(participants.size()).width(), 0);
: QMargins(
0,
0,
Ui::ParticipantsCheckView::ComputeSize(
participants.size()).width(),
0);
const auto session = &items.front()->history()->session();
const auto historyPeerId = items.front()->history()->peer->id;
@@ -307,135 +210,6 @@ void CreateModerateMessagesBox(
});
};
const auto createParticipantsList = [&](
not_null<Controller*> controller) {
const auto wrap = inner->add(
object_ptr<Ui::SlideWrap<Ui::VerticalLayout>>(
inner,
object_ptr<Ui::VerticalLayout>(inner)));
wrap->toggle(false, anim::type::instant);
controller->toggleRequestsFromTop.events(
) | rpl::start_with_next([=](bool toggled) {
wrap->toggle(toggled, anim::type::normal);
}, wrap->lifetime());
const auto container = wrap->entity();
Ui::AddSkip(container);
auto &lifetime = wrap->lifetime();
const auto clicks = lifetime.make_state<rpl::event_stream<>>();
const auto checkboxes = ranges::views::all(
participants
) | ranges::views::transform([&](not_null<PeerData*> peer) {
const auto line = container->add(
object_ptr<Ui::AbstractButton>(container));
const auto &st = st::moderateBoxUserpic;
line->resize(line->width(), st.size.height());
const auto userpic = Ui::CreateChild<Ui::UserpicButton>(
line,
peer,
st);
const auto checkbox = Ui::CreateChild<Ui::Checkbox>(
line,
peer->name(),
false,
st::defaultBoxCheckbox);
line->widthValue(
) | rpl::start_with_next([=](int width) {
userpic->moveToLeft(
st::boxRowPadding.left()
+ checkbox->checkRect().width()
+ st::defaultBoxCheckbox.textPosition.x(),
0);
const auto skip = st::defaultBoxCheckbox.textPosition.x();
checkbox->resizeToWidth(width
- rect::right(userpic)
- skip
- st::boxRowPadding.right());
checkbox->moveToLeft(
rect::right(userpic) + skip,
((userpic->height() - checkbox->height()) / 2)
+ st::defaultBoxCheckbox.margin.top());
}, checkbox->lifetime());
userpic->setAttribute(Qt::WA_TransparentForMouseEvents);
checkbox->setAttribute(Qt::WA_TransparentForMouseEvents);
line->setClickedCallback([=] {
checkbox->setChecked(!checkbox->checked());
clicks->fire({});
});
return checkbox;
}) | ranges::to_vector;
clicks->events(
) | rpl::start_with_next([=] {
controller->toggleRequestsFromInner.fire_copy(
ranges::any_of(checkboxes, &Ui::Checkbox::checked));
}, container->lifetime());
controller->checkAllRequests.events(
) | rpl::start_with_next([=](bool checked) {
for (const auto &c : checkboxes) {
c->setChecked(checked);
}
}, container->lifetime());
controller->collectRequests = [=] {
auto result = Participants();
for (auto i = 0; i < checkboxes.size(); i++) {
if (checkboxes[i]->checked()) {
result.push_back(participants[i]);
}
}
return result;
};
};
const auto appendList = [&](
not_null<Ui::Checkbox*> checkbox,
not_null<Controller*> controller) {
if (isSingle) {
const auto p = participants.front();
controller->collectRequests = [=] { return Participants{ p }; };
return;
}
const auto count = int(participants.size());
const auto button = Ui::CreateChild<Button>(inner, count);
button->resize(Button::ComputeSize(count));
const auto overlay = Ui::CreateChild<Ui::AbstractButton>(inner);
checkbox->geometryValue(
) | rpl::start_with_next([=](const QRect &rect) {
overlay->setGeometry(rect);
overlay->raise();
button->moveToRight(
st::moderateBoxExpandRight,
rect.top() + (rect.height() - button->height()) / 2,
box->width());
button->raise();
}, button->lifetime());
controller->toggleRequestsFromInner.events(
) | rpl::start_with_next([=](bool toggled) {
checkbox->setChecked(toggled);
}, checkbox->lifetime());
button->setClickedCallback([=] {
button->setChecked(!button->checked());
controller->toggleRequestsFromTop.fire_copy(button->checked());
});
overlay->setClickedCallback([=] {
checkbox->setChecked(!checkbox->checked());
controller->checkAllRequests.fire_copy(checkbox->checked());
});
createParticipantsList(controller);
};
Ui::AddSkip(inner);
const auto title = box->addRow(
object_ptr<Ui::FlatLabel>(
@@ -457,8 +231,9 @@ void CreateModerateMessagesBox(
false,
st::defaultBoxCheckbox),
st::boxRowPadding + buttonPadding);
const auto controller = box->lifetime().make_state<Controller>();
appendList(report, controller);
const auto controller = box->lifetime().make_state<Controller>(
Controller::Data{ .participants = participants });
Ui::AddExpandablePeerList(report, controller, inner);
handleSubmition(report);
const auto ids = items.front()->from()->owner().itemsToIds(items);
@@ -515,8 +290,9 @@ void CreateModerateMessagesBox(
}, title->lifetime());
}
const auto controller = box->lifetime().make_state<Controller>();
appendList(deleteAll, controller);
const auto controller = box->lifetime().make_state<Controller>(
Controller::Data{ .participants = participants });
Ui::AddExpandablePeerList(deleteAll, controller, inner);
handleSubmition(deleteAll);
handleConfirmation(deleteAll, controller, [=](
@@ -545,8 +321,9 @@ void CreateModerateMessagesBox(
false,
st::defaultBoxCheckbox),
st::boxRowPadding + buttonPadding);
const auto controller = box->lifetime().make_state<Controller>();
appendList(ban, controller);
const auto controller = box->lifetime().make_state<Controller>(
Controller::Data{ .participants = participants });
Ui::AddExpandablePeerList(ban, controller, inner);
handleSubmition(ban);
Ui::AddSkip(inner);
@@ -757,38 +534,24 @@ void DeleteChatBox(not_null<Ui::GenericBox*> box, not_null<PeerData*> peer) {
return base::EventFilterResult::Continue;
});
const auto line = container->add(object_ptr<Ui::RpWidget>(container));
const auto &st = st::mainMenuUserpic;
line->resize(line->width(), st.size.height());
const auto userpic = Ui::CreateChild<Ui::UserpicButton>(
line,
container,
peer,
st);
st::mainMenuUserpic);
userpic->showSavedMessagesOnSelf(true);
const auto label = Ui::CreateChild<Ui::FlatLabel>(
line,
peer->isSelf()
? tr::lng_saved_messages() | Ui::Text::ToBold()
: maybeUser
? tr::lng_profile_delete_conversation() | Ui::Text::ToBold()
: rpl::single(Ui::Text::Bold(peer->name())) | rpl::type_erased(),
box->getDelegate()->style().title);
line->widthValue(
) | rpl::start_with_next([=](int width) {
userpic->moveToLeft(st::boxRowPadding.left(), 0);
const auto skip = st::defaultBoxCheckbox.textPosition.x();
label->resizeToWidth(width
- rect::right(userpic)
- skip
- st::boxRowPadding.right());
label->moveToLeft(
rect::right(userpic) + skip,
((userpic->height() - label->height()) / 2));
}, label->lifetime());
userpic->setAttribute(Qt::WA_TransparentForMouseEvents);
label->setAttribute(Qt::WA_TransparentForMouseEvents);
Ui::IconWithTitle(
container,
userpic,
Ui::CreateChild<Ui::FlatLabel>(
container,
peer->isSelf()
? tr::lng_saved_messages() | Ui::Text::ToBold()
: maybeUser
? tr::lng_profile_delete_conversation() | Ui::Text::ToBold()
: rpl::single(
peer->name()
) | Ui::Text::ToBold() | rpl::type_erased(),
box->getDelegate()->style().title));
Ui::AddSkip(container);
Ui::AddSkip(container);
@@ -829,6 +592,20 @@ void DeleteChatBox(not_null<Ui::GenericBox*> box, not_null<PeerData*> peer) {
st::defaultBoxCheckbox));
}();
const auto maybeBotCheckbox = [&]() -> Ui::Checkbox* {
if (!maybeUser || !maybeUser->isBot()) {
return nullptr;
}
Ui::AddSkip(container);
Ui::AddSkip(container);
return box->addRow(
object_ptr<Ui::Checkbox>(
container,
tr::lng_profile_block_bot(tr::now, Ui::Text::WithEntities),
false,
st::defaultBoxCheckbox));
}();
Ui::AddSkip(container);
auto buttonText = maybeUser
@@ -842,7 +619,11 @@ void DeleteChatBox(not_null<Ui::GenericBox*> box, not_null<PeerData*> peer) {
const auto close = crl::guard(box, [=] { box->closeBox(); });
box->addButton(std::move(buttonText), [=] {
const auto revoke = maybeCheckbox && maybeCheckbox->checked();
const auto stopBot = maybeBotCheckbox && maybeBotCheckbox->checked();
Core::App().closeChatFromWindows(peer);
if (stopBot) {
peer->session().api().blockedPeers().block(peer);
}
// Don't delete old history by default,
// because Android app doesn't.
//

View File

@@ -23,6 +23,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/wrap/slide_wrap.h"
#include "ui/text/text_options.h"
#include "ui/painter.h"
#include "ui/ui_utility.h"
#include "lang/lang_keys.h"
#include "storage/file_download.h"
#include "data/data_peer_values.h"

View File

@@ -12,6 +12,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/wrap/vertical_layout.h"
#include "ui/widgets/multi_select.h"
#include "ui/widgets/scroll_area.h"
#include "ui/ui_utility.h"
#include "main/session/session_show.h"
#include "main/main_session.h"
#include "data/data_session.h"

View File

@@ -21,6 +21,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/boxes/confirm_box.h"
#include "ui/wrap/vertical_layout.h"
#include "ui/wrap/slide_wrap.h"
#include "ui/ui_utility.h"
#include "base/random.h"
#include "base/weak_ptr.h"
#include "api/api_chat_participants.h"

View File

@@ -517,10 +517,13 @@ void InviteForbiddenController::send(
};
const auto sendForFull = [=] {
if (!sendLink()) {
_peer->session().api().inviteLinks().create(_peer, [=](auto) {
if (!sendLink()) {
close();
}
_peer->session().api().inviteLinks().create({
_peer,
[=](auto) {
if (!sendLink()) {
close();
}
},
});
}
};

View File

@@ -19,6 +19,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_forum_topic.h"
#include "data/data_session.h"
#include "data/stickers/data_custom_emoji.h"
#include "base/event_filter.h"
#include "base/random.h"
#include "base/qt_signal_producer.h"
#include "chat_helpers/emoji_list_widget.h"
@@ -482,6 +483,9 @@ void EditForumTopicBox(
state->defaultIcon.current().colorId,
};
}, title->lifetime());
title->submits() | rpl::start_with_next([box] {
box->triggerButton(0);
}, title->lifetime());
if (!topic || !topic->isGeneral()) {
Ui::AddDividerText(top, tr::lng_forum_choose_title_and_icon());

View File

@@ -16,6 +16,10 @@ struct TopicIconDescriptor;
enum class CustomEmojiSizeTag : uchar;
} // namespace Data
namespace Ui::Text {
class CustomEmoji;
} // namespace Ui::Text
namespace Window {
class SessionController;
} // namespace Window

View File

@@ -67,6 +67,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/wrap/padding_wrap.h"
#include "ui/wrap/slide_wrap.h"
#include "ui/wrap/vertical_layout.h"
#include "ui/ui_utility.h"
#include "window/window_session_controller.h"
#include "api/api_invite_links.h"
#include "styles/style_chat_helpers.h"
@@ -318,6 +319,7 @@ private:
std::optional<bool> hiddenPreHistory;
std::optional<bool> forum;
std::optional<bool> signatures;
std::optional<bool> signatureProfiles;
std::optional<bool> noForwards;
std::optional<bool> joinToWrite;
std::optional<bool> requestToJoin;
@@ -408,6 +410,7 @@ private:
std::optional<EditPeerTypeData> _typeDataSavedValue;
std::optional<bool> _forumSavedValue;
std::optional<bool> _signaturesSavedValue;
std::optional<bool> _signatureProfilesSavedValue;
const not_null<Window::SessionNavigation*> _navigation;
const not_null<Ui::BoxContent*> _box;
@@ -620,13 +623,17 @@ object_ptr<Ui::RpWidget> Controller::createTitleEdit() {
local.x() + emojiToggle->width() * 3);
};
base::install_event_filter(container, [=](not_null<QEvent*> event) {
const auto type = event->type();
if (type == QEvent::Move || type == QEvent::Resize) {
crl::on_main(field, [=] { updateEmojiPanelGeometry(); });
}
return base::EventFilterResult::Continue;
});
field->lifetime().make_state<base::unique_qptr<QObject>>([&] {
return base::install_event_filter(container, [=](
not_null<QEvent*> event) {
const auto type = event->type();
if (type == QEvent::Move || type == QEvent::Resize) {
crl::on_main(field, [=] { updateEmojiPanelGeometry(); });
}
return base::EventFilterResult::Continue;
});
}());
field->widthValue() | rpl::start_with_next([=](int width) {
const auto &p = st::editPeerTitleEmojiPosition;
@@ -1051,17 +1058,50 @@ void Controller::fillSignaturesButton() {
return;
}
AddButtonWithText(
const auto signs = AddButtonWithText(
_controls.buttonsLayout,
tr::lng_edit_sign_messages(),
rpl::single(QString()),
[] {},
{ &st::menuIconSigned }
)->toggleOn(rpl::single(channel->addsSignature())
)->toggledValue(
)->toggleOn(rpl::single(channel->addsSignature()));
const auto profiles = _controls.buttonsLayout->add(
object_ptr<Ui::SlideWrap<Ui::SettingsButton>>(
_controls.buttonsLayout,
EditPeerInfoBox::CreateButton(
_controls.buttonsLayout,
tr::lng_edit_sign_profiles(),
rpl::single(QString()),
[] {},
st::manageGroupTopButtonWithText,
{ &st::menuIconProfile })));
profiles->toggleOn(signs->toggledValue());
profiles->finishAnimating();
profiles->entity()->toggleOn(rpl::single(
channel->addsSignature() && channel->signatureProfiles()
))->toggledValue(
) | rpl::start_with_next([=](bool toggled) {
_signatureProfilesSavedValue = toggled;
}, profiles->entity()->lifetime());
signs->toggledValue(
) | rpl::start_with_next([=](bool toggled) {
_signaturesSavedValue = toggled;
if (!toggled) {
_signatureProfilesSavedValue = false;
}
}, _controls.buttonsLayout->lifetime());
Ui::AddSkip(_controls.buttonsLayout);
Ui::AddDividerText(
_controls.buttonsLayout,
rpl::conditional(
signs->toggledValue(),
tr::lng_edit_sign_profiles_about(Ui::Text::WithEntities),
tr::lng_edit_sign_messages_about(Ui::Text::WithEntities)));
Ui::AddSkip(_controls.buttonsLayout);
}
void Controller::fillHistoryVisibilityButton() {
@@ -1219,11 +1259,9 @@ void Controller::fillManageSection() {
}
if (canEditSignatures) {
fillSignaturesButton();
}
if (canEditPreHistoryHidden
} else if (canEditPreHistoryHidden
|| canEditForum
|| canEditColorIndex
|| canEditSignatures
//|| canEditInviteLinks
|| canViewOrEditLinkedChat
|| canEditType) {
@@ -1779,10 +1817,14 @@ bool Controller::validateForum(Saving &to) const {
}
bool Controller::validateSignatures(Saving &to) const {
Expects(_signaturesSavedValue.has_value()
== _signatureProfilesSavedValue.has_value());
if (!_signaturesSavedValue.has_value()) {
return true;
}
to.signatures = _signaturesSavedValue;
to.signatureProfiles = _signatureProfilesSavedValue;
return true;
}
@@ -2215,15 +2257,27 @@ void Controller::saveForum() {
}
void Controller::saveSignatures() {
Expects(_savingData.signatures.has_value()
== _savingData.signatureProfiles.has_value());
const auto channel = _peer->asChannel();
if (!_savingData.signatures
|| !channel
|| *_savingData.signatures == channel->addsSignature()) {
|| ((*_savingData.signatures == channel->addsSignature())
&& (*_savingData.signatureProfiles
== channel->signatureProfiles()))) {
return continueSave();
}
using Flag = MTPchannels_ToggleSignatures::Flag;
_api.request(MTPchannels_ToggleSignatures(
channel->inputChannel,
MTP_bool(*_savingData.signatures)
MTP_flags(Flag()
| (*_savingData.signatures
? Flag::f_signatures_enabled
: Flag())
| (*_savingData.signatureProfiles
? Flag::f_profiles_enabled
: Flag())),
channel->inputChannel
)).done([=](const MTPUpdates &result) {
channel->session().api().applyUpdates(result);
continueSave();

View File

@@ -7,49 +7,58 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#include "boxes/peers/edit_peer_invite_link.h"
#include "core/application.h"
#include "data/data_peer.h"
#include "data/data_user.h"
#include "data/data_channel.h"
#include "data/data_changes.h"
#include "data/data_session.h"
#include "data/data_histories.h"
#include "main/main_session.h"
#include "api/api_invite_links.h"
#include "base/unixtime.h"
#include "apiwrap.h"
#include "base/unixtime.h"
#include "boxes/gift_premium_box.h"
#include "boxes/peer_list_box.h"
#include "boxes/share_box.h"
#include "core/application.h"
#include "core/ui_integration.h" // Core::MarkedTextContext.
#include "data/components/credits.h"
#include "data/data_changes.h"
#include "data/data_channel.h"
#include "data/data_histories.h"
#include "data/data_peer.h"
#include "data/data_session.h"
#include "data/data_user.h"
#include "data/stickers/data_custom_emoji.h"
#include "history/history.h"
#include "history/history_item_helpers.h" // GetErrorTextForSending.
#include "history/view/history_view_group_call_bar.h" // GenerateUserpics...
#include "lang/lang_keys.h"
#include "main/main_session.h"
#include "qr/qr_generate.h"
#include "settings/settings_credits_graphics.h"
#include "ui/boxes/confirm_box.h"
#include "ui/boxes/edit_invite_link.h"
#include "ui/boxes/edit_invite_link_session.h"
#include "ui/controls/invite_link_buttons.h"
#include "ui/controls/invite_link_label.h"
#include "ui/wrap/vertical_layout.h"
#include "ui/wrap/slide_wrap.h"
#include "ui/wrap/padding_wrap.h"
#include "ui/widgets/popup_menu.h"
#include "ui/abstract_button.h"
#include "ui/toast/toast.h"
#include "ui/text/text_utilities.h"
#include "ui/boxes/edit_invite_link.h"
#include "ui/controls/userpic_button.h"
#include "ui/painter.h"
#include "ui/rect.h"
#include "ui/text/format_values.h"
#include "ui/text/text_utilities.h"
#include "ui/toast/toast.h"
#include "ui/vertical_list.h"
#include "boxes/share_box.h"
#include "history/view/history_view_group_call_bar.h" // GenerateUserpics...
#include "history/history_item_helpers.h" // GetErrorTextForSending.
#include "history/history.h"
#include "ui/boxes/confirm_box.h"
#include "boxes/peer_list_box.h"
#include "mainwindow.h"
#include "lang/lang_keys.h"
#include "window/window_session_controller.h"
#include "ui/widgets/popup_menu.h"
#include "ui/wrap/padding_wrap.h"
#include "ui/wrap/slide_wrap.h"
#include "ui/wrap/vertical_layout.h"
#include "window/window_controller.h"
#include "mtproto/sender.h"
#include "qr/qr_generate.h"
#include "intro/intro_qr.h" // TelegramLogoImage
#include "window/window_session_controller.h"
#include "styles/style_boxes.h"
#include "styles/style_layers.h" // st::boxDividerLabel.
#include "styles/style_credits.h"
#include "styles/style_giveaway.h"
#include "styles/style_info.h"
#include "styles/style_layers.h" // st::boxDividerLabel.
#include "styles/style_menu_icons.h"
#include "styles/style_premium.h"
#include <QtGui/QGuiApplication>
#include <QtCore/QMimeData>
#include <QtGui/QGuiApplication>
#include <QtSvg/QSvgRenderer>
namespace {
@@ -72,6 +81,66 @@ void ShowPeerInfoSync(not_null<PeerData*> peer) {
}
}
class SubscriptionRow final : public PeerListRow {
public:
SubscriptionRow(
not_null<PeerData*> peer,
TimeId date,
Data::PeerSubscription subscription);
QSize rightActionSize() const override;
QMargins rightActionMargins() const override;
void rightActionPaint(
Painter &p,
int x,
int y,
int outerWidth,
bool selected,
bool actionSelected) override;
private:
std::optional<Settings::SubscriptionRightLabel> _rightLabel;
};
SubscriptionRow::SubscriptionRow(
not_null<PeerData*> peer,
TimeId date,
Data::PeerSubscription subscription)
: PeerListRow(peer) {
if (subscription) {
_rightLabel = Settings::PaintSubscriptionRightLabelCallback(
&peer->session(),
st::peerListBoxItem,
subscription.credits);
}
setCustomStatus(
tr::lng_group_invite_joined_status(
tr::now,
lt_date,
langDayOfMonthFull(base::unixtime::parse(date).date())));
}
QSize SubscriptionRow::rightActionSize() const {
return _rightLabel ? _rightLabel->size : QSize();
}
QMargins SubscriptionRow::rightActionMargins() const {
return QMargins(0, 0, st::boxRowPadding.right(), 0);
}
void SubscriptionRow::rightActionPaint(
Painter &p,
int x,
int y,
int outerWidth,
bool selected,
bool actionSelected) {
if (_rightLabel) {
return _rightLabel->draw(p, x, y, st::peerListBoxItem.height);
}
}
class RequestedRow final : public PeerListRow {
public:
RequestedRow(not_null<PeerData*> peer, TimeId date);
@@ -604,6 +673,101 @@ void Controller::setupAboveJoinedWidget() {
if (revoked || !current.permanent) {
addHeaderBlock(container);
}
if (current.subscription) {
const auto &st = st::peerListSingleRow.item;
Ui::AddSubsectionTitle(
container,
tr::lng_group_invite_subscription_info_subtitle());
const auto widget = container->add(
CreateSkipWidget(container, st.height));
const auto name = widget->lifetime().make_state<Ui::Text::String>();
auto userpic = QImage(
Size(st.photoSize) * style::DevicePixelRatio(),
QImage::Format_ARGB32_Premultiplied);
{
constexpr auto kGreenIndex = 3;
const auto colors = Ui::EmptyUserpic::UserpicColor(kGreenIndex);
auto emptyUserpic = Ui::EmptyUserpic(colors, {});
userpic.setDevicePixelRatio(style::DevicePixelRatio());
userpic.fill(Qt::transparent);
auto p = QPainter(&userpic);
emptyUserpic.paintCircle(p, 0, 0, st.photoSize, st.photoSize);
auto svg = QSvgRenderer(u":/gui/links_subscription.svg"_q);
const auto size = st.photoSize / 4. * 3.;
const auto r = QRectF(
(st.photoSize - size) / 2.,
(st.photoSize - size) / 2.,
size,
size);
p.setPen(st::historyPeerUserpicFg);
p.setBrush(Qt::NoBrush);
svg.render(&p, r);
}
name->setMarkedText(
st.nameStyle,
current.usage
? tr::lng_group_invite_subscription_info_title(
tr::now,
lt_emoji,
session().data().customEmojiManager().creditsEmoji(),
lt_price,
{ QString::number(current.subscription.credits) },
lt_multiplier,
TextWithEntities{ .text = QString(QChar(0x00D7)) },
lt_total,
{ QString::number(current.usage) },
Ui::Text::WithEntities)
: tr::lng_group_invite_subscription_info_title_none(
tr::now,
lt_emoji,
session().data().customEmojiManager().creditsEmoji(),
lt_price,
{ QString::number(current.subscription.credits) },
Ui::Text::WithEntities),
kMarkupTextOptions,
Core::MarkedTextContext{
.session = &session(),
.customEmojiRepaint = [=] { widget->update(); },
});
auto &lifetime = widget->lifetime();
const auto rateValue = lifetime.make_state<rpl::variable<float64>>(
session().credits().rateValue(_peer));
const auto currency = u"USD"_q;
const auto allCredits = current.subscription.credits * current.usage;
widget->paintRequest(
) | rpl::start_with_next([=] {
auto p = Painter(widget);
p.setBrush(Qt::NoBrush);
p.setPen(st.nameFg);
name->draw(p, {
.position = st.namePosition,
.outerWidth = widget->width() - name->maxWidth(),
.availableWidth = widget->width() - name->maxWidth(),
});
p.drawImage(st.photoPosition, userpic);
const auto rate = rateValue->current();
const auto status = (allCredits <= 0)
? tr::lng_group_invite_no_joined(tr::now)
: (rate > 0)
? tr::lng_group_invite_subscription_info_about(
tr::now,
lt_total,
Ui::FillAmountAndCurrency(allCredits * rate, currency))
: QString();
p.setPen(st.statusFg);
p.setFont(st::contactsStatusFont);
p.drawTextLeft(
st.statusPosition.x(),
st.statusPosition.y(),
widget->width() - st.statusPosition.x(),
status);
}, widget->lifetime());
}
Ui::AddSubsectionTitle(
container,
tr::lng_group_invite_created_by());
@@ -743,6 +907,11 @@ void Controller::appendSlice(const Api::JoinedByLinkSlice &slice) {
_lastUser = user;
auto row = (_role == Role::Requested)
? std::make_unique<RequestedRow>(user.user, user.date)
: (_data.current().subscription)
? std::make_unique<SubscriptionRow>(
user.user,
user.date,
_data.current().subscription)
: std::make_unique<PeerListRow>(user.user);
if (_role != Role::Requested && user.viaFilterLink) {
row->setCustomStatus(
@@ -760,11 +929,117 @@ void Controller::appendSlice(const Api::JoinedByLinkSlice &slice) {
}
void Controller::rowClicked(not_null<PeerListRow*> row) {
ShowPeerInfoSync(row->peer());
if (!_data.current().subscription) {
return ShowPeerInfoSync(row->peer());
}
const auto channel = _peer;
const auto data = _data.current();
const auto show = delegate()->peerListUiShow();
show->showBox(Box([=](not_null<Ui::GenericBox*> box) {
const auto w = Core::App().findWindow(box);
const auto controller = w ? w->sessionController() : nullptr;
if (!controller) {
return;
}
box->setStyle(st::giveawayGiftCodeBox);
box->setNoContentMargin(true);
const auto content = box->verticalLayout();
Ui::AddSkip(content);
Ui::AddSkip(content);
Ui::AddSkip(content);
const auto &stUser = st::boostReplaceUserpic;
const auto session = &row->peer()->session();
content->add(object_ptr<Ui::CenterWrap<>>(
content,
object_ptr<Ui::UserpicButton>(content, channel, stUser))
)->setAttribute(Qt::WA_TransparentForMouseEvents);
Ui::AddSkip(content);
Ui::AddSkip(content);
box->addRow(object_ptr<Ui::CenterWrap<>>(
box,
object_ptr<Ui::FlatLabel>(
box,
tr::lng_credits_box_subscription_title(),
st::creditsBoxAboutTitle)));
Ui::AddSkip(content);
const auto subtitle1 = box->addRow(
object_ptr<Ui::CenterWrap<Ui::FlatLabel>>(
box,
object_ptr<Ui::FlatLabel>(
box,
st::creditsTopupPrice)))->entity();
subtitle1->setMarkedText(
tr::lng_credits_subscription_subtitle(
tr::now,
lt_emoji,
session->data().customEmojiManager().creditsEmoji(),
lt_cost,
{ QString::number(data.subscription.credits) },
Ui::Text::WithEntities),
Core::MarkedTextContext{
.session = session,
.customEmojiRepaint = [=] { subtitle1->update(); },
});
const auto subtitle2 = box->addRow(
object_ptr<Ui::CenterWrap<Ui::FlatLabel>>(
box,
object_ptr<Ui::FlatLabel>(
box,
st::creditsTopupPrice)))->entity();
session->credits().rateValue(
channel
) | rpl::start_with_next([=, currency = u"USD"_q](float64 rate) {
subtitle2->setText(
tr::lng_credits_subscriber_subtitle(
tr::now,
lt_total,
Ui::FillAmountAndCurrency(
data.subscription.credits * rate,
currency)));
}, subtitle2->lifetime());
Ui::AddSkip(content);
Ui::AddSkip(content);
AddSubscriberEntryTable(controller, content, row->peer(), data.date);
Ui::AddSkip(content);
Ui::AddSkip(content);
box->addRow(object_ptr<Ui::CenterWrap<>>(
box,
object_ptr<Ui::FlatLabel>(
box,
tr::lng_credits_box_out_about(
lt_link,
tr::lng_payments_terms_link(
) | Ui::Text::ToLink(
tr::lng_credits_box_out_about_link(tr::now)),
Ui::Text::WithEntities),
st::creditsBoxAboutDivider)));
const auto button = box->addButton(tr::lng_box_ok(), [=] {
box->closeBox();
});
const auto buttonWidth = st::boxWidth
- rect::m::sum::h(st::giveawayGiftCodeBox.buttonPadding);
button->widthValue() | rpl::filter([=] {
return (button->widthNoMargins() != buttonWidth);
}) | rpl::start_with_next([=] {
button->resizeToWidth(buttonWidth);
}, button->lifetime());
}));
}
void Controller::rowRightActionClicked(not_null<PeerListRow*> row) {
if (_role != Role::Requested) {
if (_role != Role::Requested || _data.current().subscription) {
return;
}
delegate()->peerListShowRowMenu(row, true);
@@ -1251,6 +1526,8 @@ object_ptr<Ui::BoxContent> InviteLinkQrBox(
object_ptr<Ui::BoxContent> EditLinkBox(
not_null<PeerData*> peer,
const Api::InviteLink &data) {
constexpr auto kPeriod = 3600 * 24 * 30;
constexpr auto kTestModePeriod = 300;
const auto creating = data.link.isEmpty();
const auto box = std::make_shared<QPointer<Ui::GenericBox>>();
using Fields = Ui::InviteLinkFields;
@@ -1266,13 +1543,25 @@ object_ptr<Ui::BoxContent> EditLinkBox(
};
if (creating) {
Assert(data.admin->isSelf());
peer->session().api().inviteLinks().create(
const auto period = peer->session().isTestMode()
? kTestModePeriod
: kPeriod;
peer->session().api().inviteLinks().create({
peer,
finish,
result.label,
result.expireDate,
result.usageLimit,
result.requestApproval);
result.requestApproval,
{ uint64(result.subscriptionCredits), period },
});
} else if (result.subscriptionCredits) {
peer->session().api().inviteLinks().editTitle(
peer,
data.admin,
result.link,
result.label,
finish);
} else {
peer->session().api().inviteLinks().edit(
peer,
@@ -1287,26 +1576,33 @@ object_ptr<Ui::BoxContent> EditLinkBox(
};
const auto isGroup = !peer->isBroadcast();
const auto isPublic = peer->isChannel() && peer->asChannel()->isPublic();
if (creating) {
auto object = Box(Ui::CreateInviteLinkBox, isGroup, isPublic, done);
*box = Ui::MakeWeak(object.data());
return object;
} else {
auto object = Box(
Ui::EditInviteLinkBox,
Fields{
.link = data.link,
.label = data.label,
.expireDate = data.expireDate,
.usageLimit = data.usageLimit,
.requestApproval = data.requestApproval,
.isGroup = isGroup,
.isPublic = isPublic,
},
done);
*box = Ui::MakeWeak(object.data());
return object;
}
auto object = Box([=](not_null<Ui::GenericBox*> box) {
const auto fill = isGroup
? Fn<Ui::InviteLinkSubscriptionToggle()>(nullptr)
: [=] {
return Ui::FillCreateInviteLinkSubscriptionToggle(box, peer);
};
if (creating) {
Ui::CreateInviteLinkBox(box, fill, isGroup, isPublic, done);
} else {
Ui::EditInviteLinkBox(
box,
fill,
Fields{
.link = data.link,
.label = data.label,
.expireDate = data.expireDate,
.usageLimit = data.usageLimit,
.subscriptionCredits = int(data.subscription.credits),
.requestApproval = data.requestApproval,
.isGroup = isGroup,
.isPublic = isPublic,
},
done);
}
});
*box = Ui::MakeWeak(object.data());
return object;
}
object_ptr<Ui::BoxContent> RevokeLinkBox(

View File

@@ -7,7 +7,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#pragma once
#include "ui/layers/generic_box.h"
template <typename Object>
class object_ptr;
class PeerData;
@@ -22,6 +23,7 @@ class Session;
namespace Ui {
class VerticalLayout;
class Show;
class BoxContent;
} // namespace Ui
[[nodiscard]] bool IsExpiredLink(const Api::InviteLink &data, TimeId now);

View File

@@ -15,10 +15,12 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "main/session/session_show.h"
#include "main/main_session.h"
#include "api/api_invite_links.h"
#include "settings/settings_credits_graphics.h"
#include "ui/wrap/slide_wrap.h"
#include "ui/widgets/buttons.h"
#include "ui/widgets/popup_menu.h"
#include "ui/painter.h"
#include "ui/rect.h"
#include "ui/vertical_list.h"
#include "lang/lang_keys.h"
#include "ui/boxes/confirm_box.h"
@@ -31,6 +33,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "styles/style_layers.h" // st::boxDividerLabel
#include "styles/style_menu_icons.h"
#include <QtSvg/QSvgRenderer>
namespace {
enum class Color {
@@ -39,6 +43,7 @@ enum class Color {
ExpireSoon,
Expired,
Revoked,
Subscription,
Count,
};
@@ -60,8 +65,12 @@ struct InviteLinkAction {
class Row;
using SubscriptionRightLabel = Settings::SubscriptionRightLabel;
class RowDelegate {
public:
virtual std::optional<SubscriptionRightLabel> rightLabel(
int credits) const = 0;
virtual void rowUpdateRow(not_null<Row*> row) = 0;
virtual void rowPaintIcon(
QPainter &p,
@@ -91,6 +100,7 @@ public:
bool forceRound) override;
QSize rightActionSize() const override;
bool rightActionDisabled() const override;
QMargins rightActionMargins() const override;
void rightActionPaint(
Painter &p,
@@ -102,6 +112,7 @@ public:
private:
const not_null<RowDelegate*> _delegate;
std::optional<SubscriptionRightLabel> _rightLabel;
InviteLinkData _data;
QString _status;
float64 _progressTillExpire = 0.;
@@ -137,7 +148,9 @@ private:
[[nodiscard]] Color ComputeColor(
const InviteLinkData &link,
float64 progress) {
return link.revoked
return link.subscription
? Color::Subscription
: link.revoked
? Color::Revoked
: (progress >= 1.)
? Color::Expired
@@ -230,11 +243,13 @@ Row::Row(
, _data(data)
, _progressTillExpire(ComputeProgress(data, now))
, _color(ComputeColor(data, _progressTillExpire)) {
_rightLabel = _delegate->rightLabel(_data.subscription.credits);
setCustomStatus(ComputeStatus(data, now));
}
void Row::update(const InviteLinkData &data, TimeId now) {
_data = data;
_rightLabel = _delegate->rightLabel(_data.subscription.credits);
_progressTillExpire = ComputeProgress(data, now);
_color = ComputeColor(data, _progressTillExpire);
setCustomStatus(ComputeStatus(data, now));
@@ -305,12 +320,22 @@ PaintRoundImageCallback Row::generatePaintUserpicCallback(bool forceRound) {
}
QSize Row::rightActionSize() const {
if (_rightLabel) {
return _rightLabel->size;
}
return QSize(
st::inviteLinkThreeDotsIcon.width(),
st::inviteLinkThreeDotsIcon.height());
}
bool Row::rightActionDisabled() const {
return _rightLabel.has_value();
}
QMargins Row::rightActionMargins() const {
if (_rightLabel) {
return QMargins(0, 0, st::boxRowPadding.right(), 0);
}
return QMargins(
0,
(st::inviteLinkList.item.height - rightActionSize().height()) / 2,
@@ -325,6 +350,9 @@ void Row::rightActionPaint(
int outerWidth,
bool selected,
bool actionSelected) {
if (_rightLabel) {
return _rightLabel->draw(p, x, y, st::inviteLinkList.item.height);
}
(actionSelected
? st::inviteLinkThreeDotsIconOver
: st::inviteLinkThreeDotsIcon).paint(p, x, y, outerWidth);
@@ -354,6 +382,7 @@ public:
not_null<PeerListRow*> row) override;
Main::Session &session() const override;
std::optional<SubscriptionRightLabel> rightLabel(int) const override;
void rowUpdateRow(not_null<Row*> row) override;
void rowPaintIcon(
QPainter &p,
@@ -639,6 +668,17 @@ void LinksController::expiringProgressTimer() {
}
}
std::optional<SubscriptionRightLabel> LinksController::rightLabel(
int credits) const {
if (credits > 0) {
return Settings::PaintSubscriptionRightLabelCallback(
&session(),
st::inviteLinkList.item,
credits);
}
return std::nullopt;
}
void LinksController::rowUpdateRow(not_null<Row*> row) {
delegate()->peerListUpdateRow(row);
}
@@ -659,6 +699,7 @@ void LinksController::rowPaintIcon(
case Color::ExpireSoon: return &st::msgFile4Bg;
case Color::Expired: return &st::msgFile3Bg;
case Color::Revoked: return &st::windowSubTextFg;
case Color::Subscription: return &st::msgFile2Bg;
}
Unexpected("Color in LinksController::rowPaintIcon.");
}();
@@ -676,15 +717,25 @@ void LinksController::rowPaintIcon(
p.setBrush(*bg);
{
auto hq = PainterHighQualityEnabler(p);
auto rect = QRect(0, 0, inner, inner);
if (color == Color::Expiring || color == Color::ExpireSoon) {
rect = rect.marginsRemoved({ stroke, stroke, stroke, stroke });
}
const auto rect = QRect(0, 0, inner, inner)
- ((color == Color::Expiring || color == Color::ExpireSoon)
? Margins(stroke)
: Margins(0));
p.drawEllipse(rect);
}
(color == Color::Revoked
? st::inviteLinkRevokedIcon
: st::inviteLinkIcon).paintInCenter(p, { 0, 0, inner, inner });
if (color == Color::Subscription) {
auto svg = QSvgRenderer(u":/gui/links_subscription.svg"_q);
const auto r = QRect(
(inner - st::inviteLinkSubscriptionSize) / 2,
(inner - st::inviteLinkSubscriptionSize) / 2,
st::inviteLinkSubscriptionSize,
st::inviteLinkSubscriptionSize);
svg.render(&p, r);
} else {
(color == Color::Revoked
? st::inviteLinkRevokedIcon
: st::inviteLinkIcon).paintInCenter(p, { 0, 0, inner, inner });
}
}
p.drawImage(x + skip, y + skip, icon);
if (progress >= 0. && progress < 1.) {

View File

@@ -8,6 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "boxes/peers/edit_peer_permissions_box.h"
#include "lang/lang_keys.h"
#include "history/admin_log/history_admin_log_filter.h"
#include "core/ui_integration.h"
#include "data/stickers/data_custom_emoji.h"
#include "data/data_channel.h"
@@ -55,6 +56,11 @@ constexpr auto kForceDisableTooltipDuration = 3 * crl::time(1000);
return {};
}
[[nodiscard]] auto Dependencies(AdminLog::FilterValue::Flags) {
using Flag = AdminLog::FilterValue::Flag;
return std::vector<std::pair<Flag, Flag>>{};
}
[[nodiscard]] auto NestedRestrictionLabelsList(
Data::RestrictionsSetOptions options)
-> std::vector<NestedEditFlagsLabels<ChatRestrictions>> {
@@ -1432,3 +1438,18 @@ EditFlagsControl<PowerSaving::Flags> CreateEditPowerSaving(
return result;
}
EditFlagsControl<AdminLog::FilterValue::Flags> CreateEditAdminLogFilter(
QWidget *parent,
AdminLog::FilterValue::Flags flags,
bool isChannel) {
auto widget = object_ptr<Ui::VerticalLayout>(parent);
auto descriptor = AdminLog::FilterValueLabels(isChannel);
auto result = CreateEditFlags(
widget.data(),
flags,
std::move(descriptor));
result.widget = std::move(widget);
return result;
}

View File

@@ -7,8 +7,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#pragma once
#include "data/data_chat_participant_status.h"
#include "base/object_ptr.h"
#include "data/data_chat_participant_status.h"
#include "history/admin_log/history_admin_log_filter_value.h"
namespace style {
struct SettingsButton;
@@ -115,3 +116,9 @@ using AdminRightLabel = EditFlagsLabel<ChatAdminRights>;
PowerSaving::Flags flags,
rpl::producer<QString> forceDisabledMessage
) -> EditFlagsControl<PowerSaving::Flags>;
[[nodiscard]] auto CreateEditAdminLogFilter(
QWidget *parent,
AdminLog::FilterValue::Flags flags,
bool isChannel
) -> EditFlagsControl<AdminLog::FilterValue::Flags>;

View File

@@ -29,6 +29,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/widgets/continuous_sliders.h"
#include "ui/widgets/fields/input_field.h"
#include "ui/wrap/slide_wrap.h"
#include "ui/ui_utility.h"
#include "window/window_session_controller.h"
#include "window/window_session_controller_link_info.h"
#include "styles/style_chat_helpers.h"
@@ -282,6 +283,9 @@ void SetupOnlyCustomEmojiField(
const auto offset = size();
if (unifiedId) {
result.text.append('@');
} else if (id.paid()) {
result.text.append(QChar(0x2B50));
unifiedId = reactions->lookupPaid()->selectAnimation->id;
} else {
result.text.append(id.emoji());
const auto i = ranges::find(all, id, &Data::Reaction::id);
@@ -312,6 +316,7 @@ struct ReactionsSelectorArgs {
rpl::producer<QString> title;
std::vector<Data::Reaction> list;
std::vector<Data::ReactionId> selected;
rpl::producer<bool> paid;
Fn<void(std::vector<Data::ReactionId>, bool)> callback;
rpl::producer<ReactionsSelectorState> stateValue;
int customAllowed = 0;
@@ -341,13 +346,18 @@ object_ptr<Ui::RpWidget> AddReactionsSelector(
std::unique_ptr<UnifiedFactoryOwner> unifiedFactoryOwner;
UnifiedFactoryOwner::RecentFactory factory;
base::flat_set<DocumentId> allowed;
std::vector<Data::ReactionId> reactions;
rpl::lifetime focusLifetime;
};
const auto paid = reactions->lookupPaid();
auto normal = reactions->list(Data::Reactions::Type::Active);
normal.push_back(*paid);
const auto state = raw->lifetime().make_state<State>();
state->unifiedFactoryOwner = std::make_unique<UnifiedFactoryOwner>(
session,
reactions->list(Data::Reactions::Type::Active));
normal);
state->factory = state->unifiedFactoryOwner->factory();
state->reactions = std::move(args.selected);
const auto customEmojiPaused = [controller = args.controller] {
return controller->isGifPausedAtLeastFor(PauseReason::Layer);
@@ -396,9 +406,32 @@ object_ptr<Ui::RpWidget> AddReactionsSelector(
state->allowed = std::move(allowed);
raw->rawTextEdit()->update();
}
state->reactions = reactions;
callback(std::move(reactions), hardLimitHit);
}, isCustom, args.customHardLimit);
raw->setTextWithTags(ComposeEmojiList(reactions, args.selected));
const auto applyFromState = [=] {
raw->setTextWithTags(ComposeEmojiList(reactions, state->reactions));
};
applyFromState();
std::move(
args.paid
) | rpl::start_with_next([=](bool paid) {
const auto id = Data::ReactionId::Paid();
if (paid && !ranges::contains(state->reactions, id)) {
state->reactions.insert(begin(state->reactions), id);
applyFromState();
} else if (!paid && ranges::contains(state->reactions, id)) {
state->reactions.erase(
ranges::remove(state->reactions, id),
end(state->reactions));
applyFromState();
}
}, raw->lifetime());
const auto toggle = Ui::CreateChild<Ui::IconButton>(
parent.get(),
st::manageGroupReactions);
using SelectorState = ReactionsSelectorState;
std::move(
@@ -444,10 +477,6 @@ object_ptr<Ui::RpWidget> AddReactionsSelector(
}
}, raw->lifetime());
const auto toggle = Ui::CreateChild<Ui::IconButton>(
parent.get(),
st::manageGroupReactions);
const auto panel = Ui::CreateChild<TabbedPanel>(
args.outer.get(),
args.controller,
@@ -458,8 +487,11 @@ object_ptr<Ui::RpWidget> AddReactionsSelector(
(args.all
? TabbedSelector::Mode::FullReactions
: TabbedSelector::Mode::RecentReactions)));
panel->selector()->provideRecentEmoji(
state->unifiedFactoryOwner->unifiedIdsList());
auto panelList = state->unifiedFactoryOwner->unifiedIdsList();
panelList.erase(
ranges::remove(panelList, paid->selectAnimation->id),
end(panelList));
panel->selector()->provideRecentEmoji(panelList);
panel->setDesiredHeightValues(
1.,
st::emojiPanMinHeight / 2,
@@ -608,15 +640,17 @@ void EditAllowedReactionsBox(
rpl::variable<SelectorState> selectorState;
std::vector<Data::ReactionId> selected;
rpl::variable<int> customCount;
rpl::variable<bool> paidEnabled;
};
const auto allowed = args.allowed;
const auto optionInitial = (allowed.type != AllowedReactionsType::Some)
? Option::All
: allowed.some.empty()
: (allowed.some.empty() && !allowed.paidEnabled)
? Option::None
: Option::Some;
const auto state = box->lifetime().make_state<State>(State{
.option = optionInitial,
.paidEnabled = allowed.paidEnabled,
});
const auto container = box->verticalLayout();
@@ -702,13 +736,19 @@ void EditAllowedReactionsBox(
| ranges::views::transform(&Data::Reaction::id)
| ranges::to_vector)
: allowed.some;
if (allowed.paidEnabled) {
selected.insert(begin(selected), Data::ReactionId::Paid());
}
const auto changed = [=](
std::vector<Data::ReactionId> chosen,
bool hardLimitHit) {
std::vector<Data::ReactionId> chosen,
bool hardLimitHit) {
state->selected = std::move(chosen);
state->customCount = ranges::count_if(
state->selected,
&Data::ReactionId::custom);
state->paidEnabled = ranges::contains(
state->selected,
Data::ReactionId::Paid());
if (hardLimitHit) {
box->uiShow()->showToast(
tr::lng_manage_peer_reactions_limit(tr::now));
@@ -727,6 +767,7 @@ void EditAllowedReactionsBox(
.title = tr::lng_manage_peer_reactions_available_ph(),
.list = all,
.selected = state->selected,
.paid = state->paidEnabled.value(),
.callback = changed,
.stateValue = state->selectorState.value(),
.customAllowed = args.allowedCustomReactions,
@@ -735,7 +776,7 @@ void EditAllowedReactionsBox(
}), st::boxRowPadding);
box->setFocusCallback([=] {
if (!wrap || state->option.current() == Option::Some) {
if (state->option.current() == Option::Some) {
state->selectorState.force_assign(SelectorState::Active);
}
});
@@ -847,6 +888,29 @@ void EditAllowedReactionsBox(
) | rpl::map(rpl::mappers::_1 == SelectorState::Active)));
Ui::AddDividerText(inner, tr::lng_manage_peer_reactions_max_about());
Ui::AddSkip(inner);
const auto paid = inner->add(object_ptr<Ui::SettingsButton>(
inner,
tr::lng_manage_peer_reactions_paid(),
st::manageGroupNoIconButton.button));
paid->toggleOn(state->paidEnabled.value());
paid->toggledValue(
) | rpl::start_with_next([=](bool value) {
state->paidEnabled = value;
}, paid->lifetime());
Ui::AddSkip(inner);
Ui::AddDividerText(
inner,
tr::lng_manage_peer_reactions_paid_about(
lt_link,
tr::lng_manage_peer_reactions_paid_link([=](QString text) {
return Ui::Text::Link(
text,
u"https://telegram.org/tos/stars"_q);
}),
Ui::Text::WithEntities));
}
const auto collect = [=] {
auto result = AllowedReactions();
@@ -856,6 +920,9 @@ void EditAllowedReactionsBox(
: (enabled->toggled())) {
result.some = state->selected;
}
if (!isGroup && enabled->toggled()) {
result.paidEnabled = state->paidEnabled.current();
}
auto some = result.some;
auto simple = all | ranges::views::transform(
&Data::Reaction::id
@@ -907,15 +974,20 @@ void SaveAllowedReactions(
: allowed.some.empty()
? MTP_chatReactionsNone()
: MTP_chatReactionsSome(MTP_vector<MTPReaction>(ids));
const auto editPaidEnabled = peer->isBroadcast();
const auto paidEnabled = editPaidEnabled && allowed.paidEnabled;
const auto maxCount = allowed.maxCount;
peer->session().api().request(MTPmessages_SetChatAvailableReactions(
allowed.maxCount ? MTP_flags(Flag::f_reactions_limit) : MTP_flags(0),
MTP_flags(Flag()
| (maxCount ? Flag::f_reactions_limit : Flag())
| (editPaidEnabled ? Flag::f_paid_enabled : Flag())),
peer->input,
updated,
MTP_int(allowed.maxCount)
MTP_int(maxCount),
MTP_bool(paidEnabled)
)).done([=](const MTPUpdates &result) {
peer->session().api().applyUpdates(result);
auto parsed = Data::Parse(updated);
parsed.maxCount = allowed.maxCount;
auto parsed = Data::Parse(updated, maxCount, paidEnabled);
if (const auto chat = peer->asChat()) {
chat->setAllowedReactions(parsed);
} else if (const auto channel = peer->asChannel()) {

View File

@@ -40,6 +40,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/wrap/slide_wrap.h"
#include "ui/wrap/vertical_layout.h"
#include "ui/widgets/fields/special_fields.h"
#include "ui/ui_utility.h"
#include "window/window_session_controller.h"
#include "settings/settings_common.h"
#include "styles/style_layers.h"

View File

@@ -23,6 +23,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/widgets/buttons.h"
#include "ui/widgets/popup_menu.h"
#include "ui/wrap/vertical_layout_reorder.h"
#include "ui/ui_utility.h"
#include "styles/style_boxes.h" // contactsStatusFont.
#include "styles/style_info.h"
#include "styles/style_layers.h"

View File

@@ -9,6 +9,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/boxes/confirm_box.h"
#include "ui/controls/peer_list_dummy.h"
#include "ui/effects/premium_bubble.h"
#include "ui/effects/premium_graphics.h"
#include "ui/widgets/checkbox.h"
#include "ui/wrap/padding_wrap.h"
@@ -136,6 +137,12 @@ private:
};
[[nodiscard]] Ui::Premium::BubbleType ChooseBubbleType(bool premium) {
return premium
? Ui::Premium::BubbleType::Premium
: Ui::Premium::BubbleType::NoPremium;
}
void InactiveDelegate::peerListSetTitle(rpl::producer<QString> title) {
}
@@ -421,7 +428,7 @@ void SimpleLimitBox(
(descriptor.complexRatio
? descriptor.premiumLimit
: 2 * descriptor.current),
premiumPossible,
ChooseBubbleType(premiumPossible),
descriptor.phrase,
descriptor.icon);
Ui::AddSkip(top, st::premiumLineTextSkip);
@@ -1109,7 +1116,7 @@ void AccountsLimitBox(
: (current > defaultLimit)
? (current + 1)
: (defaultLimit * 2)),
premiumPossible,
ChooseBubbleType(premiumPossible),
std::nullopt,
&st::premiumIconAccounts);
Ui::AddSkip(top, st::premiumLineTextSkip);

View File

@@ -10,6 +10,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "api/api_credits.h"
#include "apiwrap.h"
#include "core/ui_integration.h" // Core::MarkedTextContext.
#include "data/components/credits.h"
#include "data/data_credits.h"
#include "data/data_photo.h"
#include "data/data_session.h"
@@ -80,14 +81,12 @@ struct PaidMediaData {
}
}
const auto bot = item->viaBot();
const auto sender = item->originalSender();
const auto broadcast = (sender && sender->isBroadcast())
? sender
: message->peer.get();
return {
.invoice = invoice,
.item = item,
.peer = broadcast,
.peer = (bot ? bot : sender ? sender : message->peer.get()),
.photos = photos,
.videos = videos,
};
@@ -130,6 +129,16 @@ struct PaidMediaData {
lt_video,
std::move(videosBold),
Ui::Text::WithEntities);
if (const auto user = data.peer->asUser()) {
return tr::lng_credits_box_out_media_user(
lt_count,
rpl::single(form->invoice.amount) | tr::to_count(),
lt_media,
std::move(media),
lt_user,
rpl::single(Ui::Text::Bold(user->shortName())),
Ui::Text::RichLangValue);
}
return tr::lng_credits_box_out_media(
lt_count,
rpl::single(form->invoice.amount) | tr::to_count(),
@@ -301,7 +310,7 @@ void SendCreditsBox(
st::giveawayGiftCodeStartButton.height / 2);
AddChildToWidgetCenter(button.data(), loadingAnimation);
loadingAnimation->showOn(state->confirmButtonBusy.value());
}
}
{
auto buttonText = tr::lng_credits_box_out_confirm(
lt_count,
@@ -361,15 +370,11 @@ void SendCreditsBox(
}
{
session->credits().load(true);
const auto balance = Settings::AddBalanceWidget(
content,
session->creditsValue(),
session->credits().balanceValue(),
false);
const auto api = balance->lifetime().make_state<Api::CreditsStatus>(
session->user());
api->request({}, [=](Data::CreditsStatusSlice slice) {
session->setCredits(slice.balance);
});
rpl::combine(
balance->sizeValue(),
content->sizeValue()

View File

@@ -32,6 +32,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "boxes/premium_limits_box.h"
#include "boxes/premium_preview_box.h"
#include "boxes/send_credits_box.h"
#include "platform/platform_file_utilities.h"
#include "ui/effects/scroll_content_shadow.h"
#include "ui/widgets/fields/number_input.h"
#include "ui/widgets/checkbox.h"
@@ -46,6 +47,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/controls/emoji_button.h"
#include "ui/painter.h"
#include "ui/vertical_list.h"
#include "ui/ui_utility.h"
#include "lottie/lottie_single_player.h"
#include "data/data_channel.h"
#include "data/data_document.h"
@@ -70,7 +72,7 @@ constexpr auto kMaxMessageLength = 4096;
using Ui::SendFilesWay;
[[nodiscard]] inline bool CanAddUrls(const QList<QUrl> &urls) {
return !urls.isEmpty() && ranges::all_of(urls, &QUrl::isLocalFile);
return !urls.isEmpty() && ranges::all_of(urls, Core::UrlIsLocal);
}
[[nodiscard]] bool CanAddFiles(not_null<const QMimeData*> data) {

View File

@@ -0,0 +1,273 @@
/*
This file is part of Telegram Desktop,
the official desktop application for the Telegram messaging service.
For license and copyright information please follow this link:
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#include "boxes/send_gif_with_caption_box.h"
#include "boxes/premium_preview_box.h"
#include "chat_helpers/message_field.h"
#include "chat_helpers/tabbed_panel.h"
#include "chat_helpers/tabbed_selector.h"
#include "core/application.h"
#include "core/core_settings.h"
#include "data/data_document.h"
#include "data/data_document_media.h"
#include "data/data_file_origin.h"
#include "data/data_peer_values.h"
#include "data/data_premium_limits.h"
#include "data/data_session.h"
#include "data/data_user.h"
#include "data/stickers/data_custom_emoji.h"
#include "data/stickers/data_stickers.h"
#include "history/view/controls/history_view_characters_limit.h"
#include "lang/lang_keys.h"
#include "main/main_session.h"
#include "media/clip/media_clip_reader.h"
#include "menu/menu_send.h"
#include "ui/controls/emoji_button.h"
#include "ui/controls/emoji_button_factory.h"
#include "ui/layers/generic_box.h"
#include "ui/rect.h"
#include "ui/vertical_list.h"
#include "ui/widgets/fields/input_field.h"
#include "window/window_controller.h"
#include "window/window_session_controller.h"
#include "styles/style_boxes.h"
#include "styles/style_chat_helpers.h"
#include "styles/style_layers.h"
namespace Ui {
namespace {
[[nodiscard]] not_null<Ui::RpWidget*> AddGifWidget(
not_null<Ui::VerticalLayout*> container,
not_null<DocumentData*> document,
int width) {
struct State final {
std::shared_ptr<Data::DocumentMedia> mediaView;
::Media::Clip::ReaderPointer gif;
rpl::lifetime loadingLifetime;
};
const auto state = container->lifetime().make_state<State>();
state->mediaView = document->createMediaView();
state->mediaView->automaticLoad(Data::FileOriginSavedGifs(), nullptr);
state->mediaView->thumbnailWanted(Data::FileOriginSavedGifs());
state->mediaView->videoThumbnailWanted(Data::FileOriginSavedGifs());
const auto widget = container->add(
Ui::CreateSkipWidget(
container,
document->dimensions.scaled(
width - rect::m::sum::h(st::boxRowPadding),
std::numeric_limits<int>::max(),
Qt::KeepAspectRatio).height()),
st::boxRowPadding);
widget->paintRequest(
) | rpl::start_with_next([=] {
auto p = QPainter(widget);
if (state->gif && state->gif->started()) {
p.drawImage(
0,
0,
state->gif->current({ .frame = widget->size() }, crl::now()));
} else if (const auto thumb = state->mediaView->thumbnail()) {
p.drawImage(
widget->rect(),
thumb->pixNoCache(
widget->size() * style::DevicePixelRatio(),
{ .outer = widget->size() }).toImage());
} else if (const auto thumb = state->mediaView->thumbnailInline()) {
p.drawImage(
widget->rect(),
thumb->pixNoCache(
widget->size() * style::DevicePixelRatio(),
{
.options = Images::Option::Blur,
.outer = widget->size(),
}).toImage());
}
}, widget->lifetime());
const auto updateThumbnail = [=] {
if (document->dimensions.isEmpty()) {
return false;
}
if (!state->mediaView->loaded()) {
return false;
}
const auto callback = [=](::Media::Clip::Notification) {
if (state->gif && state->gif->ready() && !state->gif->started()) {
state->gif->start({ .frame = widget->size() });
}
widget->update();
};
state->gif = ::Media::Clip::MakeReader(
state->mediaView->owner()->location(),
state->mediaView->bytes(),
callback);
return true;
};
if (!updateThumbnail()) {
document->owner().session().downloaderTaskFinished(
) | rpl::start_with_next([=] {
if (updateThumbnail()) {
state->loadingLifetime.destroy();
widget->update();
}
}, state->loadingLifetime);
}
return widget;
}
[[nodiscard]] not_null<Ui::InputField*> AddInputField(
not_null<Ui::GenericBox*> box,
not_null<Window::SessionController*> controller) {
using Limit = HistoryView::Controls::CharactersLimitLabel;
const auto wrap = box->verticalLayout()->add(
object_ptr<Ui::RpWidget>(box),
st::boxRowPadding);
const auto input = Ui::CreateChild<Ui::InputField>(
wrap,
st::defaultComposeFiles.caption,
Ui::InputField::Mode::MultiLine,
tr::lng_photo_caption());
Ui::ResizeFitChild(wrap, input);
struct State final {
base::unique_qptr<ChatHelpers::TabbedPanel> emojiPanel;
base::unique_qptr<Limit> charsLimitation;
};
const auto state = box->lifetime().make_state<State>();
{
const auto container = box->getDelegate()->outerContainer();
using Selector = ChatHelpers::TabbedSelector;
state->emojiPanel = base::make_unique_q<ChatHelpers::TabbedPanel>(
container,
controller,
object_ptr<Selector>(
nullptr,
controller->uiShow(),
Window::GifPauseReason::Layer,
Selector::Mode::EmojiOnly));
const auto emojiPanel = state->emojiPanel.get();
emojiPanel->setDesiredHeightValues(
1.,
st::emojiPanMinHeight / 2,
st::emojiPanMinHeight);
emojiPanel->hide();
emojiPanel->selector()->setCurrentPeer(controller->session().user());
emojiPanel->selector()->emojiChosen(
) | rpl::start_with_next([=](ChatHelpers::EmojiChosen data) {
Ui::InsertEmojiAtCursor(input->textCursor(), data.emoji);
}, input->lifetime());
emojiPanel->selector()->customEmojiChosen(
) | rpl::start_with_next([=](ChatHelpers::FileChosen data) {
const auto info = data.document->sticker();
if (info
&& info->setType == Data::StickersType::Emoji
&& !controller->session().premium()) {
ShowPremiumPreviewBox(
controller,
PremiumFeature::AnimatedEmoji);
} else {
Data::InsertCustomEmoji(input, data.document);
}
}, input->lifetime());
}
const auto emojiButton = Ui::AddEmojiToggleToField(
input,
box,
controller,
state->emojiPanel.get(),
st::sendGifWithCaptionEmojiPosition);
emojiButton->show();
const auto session = &controller->session();
const auto checkCharsLimitation = [=](auto repeat) -> void {
const auto remove = Ui::ComputeFieldCharacterCount(input)
- Data::PremiumLimits(session).captionLengthCurrent();
if (remove > 0) {
if (!state->charsLimitation) {
state->charsLimitation = base::make_unique_q<Limit>(
input,
emojiButton,
style::al_top);
state->charsLimitation->show();
Data::AmPremiumValue(session) | rpl::start_with_next([=] {
repeat(repeat);
}, state->charsLimitation->lifetime());
}
state->charsLimitation->setLeft(remove);
state->charsLimitation->show();
} else {
state->charsLimitation = nullptr;
}
};
input->changes() | rpl::start_with_next([=] {
checkCharsLimitation(checkCharsLimitation);
}, input->lifetime());
return input;
}
} // namespace
void SendGifWithCaptionBox(
not_null<Ui::GenericBox*> box,
not_null<DocumentData*> document,
const SendMenu::Details &details,
Fn<void(Api::SendOptions, TextWithTags)> done) {
const auto window = Core::App().findWindow(box);
const auto controller = window ? window->sessionController() : nullptr;
if (!controller) {
return;
}
box->setTitle(tr::lng_send_gif_with_caption());
box->setWidth(st::boxWidth);
const auto container = box->verticalLayout();
[[maybe_unused]] const auto gifWidget = AddGifWidget(
container,
document,
st::boxWidth);
Ui::AddSkip(container);
const auto input = AddInputField(box, controller);
box->setFocusCallback([=] {
input->setFocus();
});
input->setSubmitSettings(Core::App().settings().sendSubmitWay());
InitMessageField(controller, input, [=](not_null<DocumentData*>) {
return true;
});
const auto send = [=](Api::SendOptions options) {
done(std::move(options), input->getTextWithTags());
};
const auto confirm = box->addButton(
tr::lng_send_button(),
[=] { send({}); });
SendMenu::SetupMenuAndShortcuts(
confirm,
controller->uiShow(),
[=] { return details; },
SendMenu::DefaultCallback(controller->uiShow(), send));
box->addButton(tr::lng_cancel(), [=] {
box->closeBox();
});
input->submits(
) | rpl::start_with_next([=] { send({}); }, input->lifetime());
}
} // namespace Ui

View File

@@ -0,0 +1,30 @@
/*
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
class DocumentData;
namespace Api {
struct SendOptions;
} // namespace Api
namespace SendMenu {
struct Details;
} // namespace SendMenu
namespace Ui {
class GenericBox;
void SendGifWithCaptionBox(
not_null<Ui::GenericBox*> box,
not_null<DocumentData*> document,
const SendMenu::Details &details,
Fn<void(Api::SendOptions, TextWithTags)> done);
} // namespace Ui

View File

@@ -23,6 +23,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/text/text_options.h"
#include "ui/text/text_utilities.h"
#include "ui/painter.h"
#include "ui/ui_utility.h"
#include "chat_helpers/message_field.h"
#include "menu/menu_check_item.h"
#include "menu/menu_send.h"

View File

@@ -155,6 +155,8 @@ callMicrophoneMute: CallButton(callAnswer) {
bg: callIconBg;
outerBg: callMuteRipple;
label: callButtonLabel;
cornerButtonPosition: point(40px, 4px);
cornerButtonBorder: 2px;
}
callMicrophoneUnmute: CallButton(callMicrophoneMute) {
button: IconButton(callButton) {
@@ -181,6 +183,34 @@ callCameraUnmute: CallButton(callMicrophoneUnmute) {
}
}
}
callCornerButtonInner: IconButton {
width: 20px;
height: 20px;
iconPosition: point(-1px, -1px);
rippleAreaPosition: point(0px, 0px);
rippleAreaSize: 20px;
ripple: defaultRippleAnimation;
}
callCornerButton: CallButton(callMicrophoneMute) {
button: IconButton(callCornerButtonInner) {
icon: icon {{ "calls/mini_calls_arrow", callIconFg }};
ripple: RippleAnimation(defaultRippleAnimation) {
color: callMuteRipple;
}
}
bgSize: 20px;
bgPosition: point(0px, 0px);
}
callCornerButtonInactive: CallButton(callMicrophoneUnmute, callCornerButton) {
button: IconButton(callCornerButtonInner) {
icon: icon {{ "calls/mini_calls_arrow", callIconFgActive }};
ripple: RippleAnimation(defaultRippleAnimation) {
color: callIconActiveRipple;
}
}
}
callScreencastOn: CallButton(callMicrophoneMute) {
button: IconButton(callButton) {
icon: icon {{ "calls/calls_present", callIconFg }};
@@ -576,6 +606,18 @@ groupCallMenuAbout: FlatLabel(defaultFlatLabel) {
minWidth: 200px;
maxHeight: 92px;
}
callDeviceSelectionLabel: FlatLabel(defaultSubsectionTitle) {
textFg: groupCallActiveFg;
minWidth: 200px;
maxHeight: 20px;
}
callDeviceSelectionMenu: PopupMenu(groupCallPopupMenu) {
scrollPadding: margins(0px, 3px, 0px, 8px);
menu: Menu(groupCallMenu) {
widthMin: 240px;
itemPadding: margins(17px, 8px, 17px, 7px);
}
}
groupCallRecordingTimerPadding: margins(0px, 4px, 0px, 4px);
groupCallRecordingTimerFont: font(12px);

View File

@@ -1310,6 +1310,19 @@ void Call::toggleScreenSharing(std::optional<QString> uniqueId) {
_videoOutgoing->setState(Webrtc::VideoState::Active);
}
auto Call::playbackDeviceIdValue() const
-> rpl::producer<Webrtc::DeviceResolvedId> {
return _playbackDeviceId.value();
}
rpl::producer<Webrtc::DeviceResolvedId> Call::captureDeviceIdValue() const {
return _captureDeviceId.value();
}
rpl::producer<Webrtc::DeviceResolvedId> Call::cameraDeviceIdValue() const {
return _cameraDeviceId.value();
}
void Call::finish(FinishType type, const MTPPhoneCallDiscardReason &reason) {
Expects(type != FinishType::None);

View File

@@ -31,6 +31,7 @@ enum class AudioState;
namespace Webrtc {
enum class VideoState;
class VideoTrack;
struct DeviceResolvedId;
} // namespace Webrtc
namespace Calls {
@@ -220,6 +221,13 @@ public:
void toggleCameraSharing(bool enabled);
void toggleScreenSharing(std::optional<QString> uniqueId);
[[nodiscard]] auto playbackDeviceIdValue() const
-> rpl::producer<Webrtc::DeviceResolvedId>;
[[nodiscard]] auto captureDeviceIdValue() const
-> rpl::producer<Webrtc::DeviceResolvedId>;
[[nodiscard]] auto cameraDeviceIdValue() const
-> rpl::producer<Webrtc::DeviceResolvedId>;
[[nodiscard]] rpl::lifetime &lifetime() {
return _lifetime;
}

View File

@@ -15,6 +15,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_cloud_file.h"
#include "data/data_changes.h"
#include "calls/group/calls_group_common.h"
#include "calls/ui/calls_device_menu.h"
#include "calls/calls_emoji_fingerprint.h"
#include "calls/calls_signal_bars.h"
#include "calls/calls_userpic.h"
@@ -24,6 +25,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/widgets/call_button.h"
#include "ui/widgets/buttons.h"
#include "ui/widgets/labels.h"
#include "ui/widgets/popup_menu.h"
#include "ui/widgets/shadow.h"
#include "ui/widgets/rp_window.h"
#include "ui/layers/layer_manager.h"
@@ -130,6 +132,7 @@ Panel::Panel(not_null<Call*> call)
initWidget();
initControls();
initLayout();
initMediaDeviceToggles();
showAndActivate();
}
@@ -554,7 +557,6 @@ void Panel::reinitWithCall(Call *call) {
Ui::Toast::Show(widget(), Ui::Toast::Config{
.text = { text },
.st = &st::callErrorToast,
.multiline = true,
});
}, _callLifetime);
@@ -737,6 +739,58 @@ void Panel::initGeometry() {
updateControlsGeometry();
}
void Panel::initMediaDeviceToggles() {
_cameraDeviceToggle = _camera->addCornerButton(
st::callCornerButton,
&st::callCornerButtonInactive);
_audioDeviceToggle = _mute->entity()->addCornerButton(
st::callCornerButton,
&st::callCornerButtonInactive);
_cameraDeviceToggle->setClickedCallback([=] {
showDevicesMenu(_cameraDeviceToggle, {
{ Webrtc::DeviceType::Camera, _call->cameraDeviceIdValue() },
});
});
_audioDeviceToggle->setClickedCallback([=] {
showDevicesMenu(_audioDeviceToggle, {
{ Webrtc::DeviceType::Playback, _call->playbackDeviceIdValue() },
{ Webrtc::DeviceType::Capture, _call->captureDeviceIdValue() },
});
});
}
void Panel::showDevicesMenu(
not_null<QWidget*> button,
std::vector<DeviceSelection> types) {
if (!_call || _devicesMenu) {
return;
}
const auto chosen = [=](Webrtc::DeviceType type, QString id) {
switch (type) {
case Webrtc::DeviceType::Playback:
Core::App().settings().setCallPlaybackDeviceId(id);
break;
case Webrtc::DeviceType::Capture:
Core::App().settings().setCallCaptureDeviceId(id);
break;
case Webrtc::DeviceType::Camera:
Core::App().settings().setCameraDeviceId(id);
break;
}
Core::App().saveSettingsDelayed();
};
_devicesMenu = MakeDeviceSelectionMenu(
widget(),
&Core::App().mediaDevices(),
std::move(types),
chosen);
_devicesMenu->setForcedVerticalOrigin(
Ui::PopupMenu::VerticalOrigin::Bottom);
_devicesMenu->popup(button->mapToGlobal(QPoint())
- QPoint(st::callDeviceSelectionMenu.menu.widthMin / 2, 0));
}
void Panel::refreshOutgoingPreviewInBody(State state) {
const auto inBody = (state != State::Established)
&& (_call->videoOutgoing()->state() != Webrtc::VideoState::Inactive)

View File

@@ -37,6 +37,7 @@ class FadeWrap;
template <typename Widget>
class PaddingWrap;
class RpWindow;
class PopupMenu;
namespace GL {
enum class Backend;
} // namespace GL
@@ -55,6 +56,7 @@ namespace Calls {
class Userpic;
class SignalBars;
class VideoBubble;
struct DeviceSelection;
class Panel final : private Group::Ui::DesktopCapture::ChooseSourceDelegate {
public:
@@ -104,6 +106,7 @@ private:
void initControls();
void reinitWithCall(Call *call);
void initLayout();
void initMediaDeviceToggles();
void initGeometry();
[[nodiscard]] bool handleClose() const;
@@ -126,6 +129,10 @@ private:
void showRemoteLowBattery();
void refreshAnswerHangupRedialLabel();
void showDevicesMenu(
not_null<QWidget*> button,
std::vector<DeviceSelection> types);
[[nodiscard]] QRect incomingFrameGeometry() const;
[[nodiscard]] QRect outgoingFrameGeometry() const;
@@ -156,8 +163,10 @@ private:
Ui::Animations::Simple _hangupShownProgress;
object_ptr<Ui::FadeWrap<Ui::CallButton>> _screencast;
object_ptr<Ui::CallButton> _camera;
Ui::CallButton *_cameraDeviceToggle = nullptr;
base::unique_qptr<Ui::CallButton> _startVideo;
object_ptr<Ui::FadeWrap<Ui::CallButton>> _mute;
Ui::CallButton *_audioDeviceToggle = nullptr;
object_ptr<Ui::FlatLabel> _name;
object_ptr<Ui::FlatLabel> _status;
object_ptr<Ui::RpWidget> _fingerprint = { nullptr };
@@ -170,6 +179,8 @@ private:
int _bodyTop = 0;
int _buttonsTop = 0;
base::unique_qptr<Ui::PopupMenu> _devicesMenu;
base::Timer _updateDurationTimer;
base::Timer _updateOuterRippleTimer;

View File

@@ -7,6 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#pragma once
#include "ui/rect_part.h"
#include "ui/rp_widget.h"
namespace Webrtc {

View File

@@ -16,6 +16,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "lang/lang_hardcoded.h"
#include "boxes/peers/edit_participants_box.h" // SubscribeToMigration.
#include "ui/toast/toast.h"
#include "ui/ui_utility.h"
#include "base/unixtime.h"
#include "core/application.h"
#include "core/core_settings.h"

View File

@@ -33,6 +33,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/text/text_utilities.h"
#include "ui/toast/toast.h"
#include "ui/image/image_prepare.h"
#include "ui/integration.h"
#include "ui/painter.h"
#include "ui/round_rect.h"
#include "info/profile/info_profile_values.h" // Info::Profile::Value.

View File

@@ -645,9 +645,10 @@ void SettingsBox(
shareLink = [=] {
if (!copyLink() && !state->generatingLink) {
state->generatingLink = true;
peer->session().api().inviteLinks().create(
peer->session().api().inviteLinks().create({
peer,
crl::guard(layout, [=](auto&&) { copyLink(); }));
crl::guard(layout, [=](auto&&) { copyLink(); })
});
}
};
}

View File

@@ -16,6 +16,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "media/view/media_view_pip.h"
#include "base/platform/base_platform_info.h"
#include "webrtc/webrtc_video_track.h"
#include "ui/integration.h"
#include "ui/painter.h"
#include "ui/abstract_button.h"
#include "ui/gl/gl_surface.h"

View File

@@ -0,0 +1,250 @@
/*
This file is part of Telegram Desktop,
the official desktop application for the Telegram messaging service.
For license and copyright information please follow this link:
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#include "calls/ui/calls_device_menu.h"
#include "lang/lang_keys.h"
#include "ui/widgets/menu/menu_item_base.h"
#include "ui/widgets/checkbox.h"
#include "ui/widgets/labels.h"
#include "ui/widgets/popup_menu.h"
#include "ui/widgets/scroll_area.h"
#include "ui/wrap/vertical_layout.h"
#include "webrtc/webrtc_device_common.h"
#include "webrtc/webrtc_environment.h"
#include "styles/style_calls.h"
#include "styles/style_layers.h"
namespace Calls {
namespace {
class Subsection final : public Ui::Menu::ItemBase {
public:
Subsection(
not_null<RpWidget*> parent,
const style::Menu &st,
const QString &text);
not_null<QAction*> action() const override;
bool isEnabled() const override;
private:
int contentHeight() const override;
const style::Menu &_st;
const base::unique_qptr<Ui::FlatLabel> _text;
const not_null<QAction*> _dummyAction;
};
class Selector final : public Ui::Menu::ItemBase {
public:
Selector(
not_null<RpWidget*> parent,
const style::Menu &st,
rpl::producer<std::vector<Webrtc::DeviceInfo>> devices,
rpl::producer<Webrtc::DeviceResolvedId> chosen,
Fn<void(QString)> selected);
not_null<QAction*> action() const override;
bool isEnabled() const override;
private:
int contentHeight() const override;
[[nodiscard]] int registerId(const QString &id);
const base::unique_qptr<Ui::ScrollArea> _scroll;
const not_null<Ui::VerticalLayout*> _list;
const not_null<QAction*> _dummyAction;
base::flat_map<QString, int> _ids;
};
Subsection::Subsection(
not_null<RpWidget*> parent,
const style::Menu &st,
const QString &text)
: Ui::Menu::ItemBase(parent, st)
, _st(st)
, _text(base::make_unique_q<Ui::FlatLabel>(
this,
text,
st::callDeviceSelectionLabel))
, _dummyAction(new QAction(parent)) {
setPointerCursor(false);
initResizeHook(parent->sizeValue());
_text->resizeToWidth(st::callDeviceSelectionLabel.minWidth);
_text->moveToLeft(st.itemPadding.left(), st.itemPadding.top());
}
not_null<QAction*> Subsection::action() const {
return _dummyAction;
}
bool Subsection::isEnabled() const {
return false;
}
int Subsection::contentHeight() const {
return _st.itemPadding.top()
+ _text->height()
+ _st.itemPadding.bottom();
}
Selector::Selector(
not_null<RpWidget*> parent,
const style::Menu &st,
rpl::producer<std::vector<Webrtc::DeviceInfo>> devices,
rpl::producer<Webrtc::DeviceResolvedId> chosen,
Fn<void(QString)> selected)
: Ui::Menu::ItemBase(parent, st)
, _scroll(base::make_unique_q<Ui::ScrollArea>(this))
, _list(_scroll->setOwnedWidget(object_ptr<Ui::VerticalLayout>(this)))
, _dummyAction(new QAction(parent)) {
setPointerCursor(false);
initResizeHook(parent->sizeValue());
const auto padding = st.itemPadding;
const auto group = std::make_shared<Ui::RadiobuttonGroup>();
std::move(
chosen
) | rpl::start_with_next([=](Webrtc::DeviceResolvedId id) {
const auto value = id.isDefault() ? 0 : registerId(id.value);
if (!group->hasValue() || group->current() != value) {
group->setValue(value);
}
}, lifetime());
group->setChangedCallback([=](int value) {
if (value == 0) {
selected({});
} else {
for (const auto &[id, index] : _ids) {
if (index == value) {
selected(id);
break;
}
}
}
});
std::move(
devices
) | rpl::start_with_next([=](const std::vector<Webrtc::DeviceInfo> &v) {
while (_list->count()) {
delete _list->widgetAt(0);
}
_list->add(
object_ptr<Ui::Radiobutton>(
_list.get(),
group,
0,
tr::lng_settings_call_device_default(tr::now),
st::groupCallCheckbox,
st::groupCallRadio),
padding);
for (const auto &device : v) {
if (device.inactive) {
continue;
}
_list->add(
object_ptr<Ui::Radiobutton>(
_list.get(),
group,
registerId(device.id),
device.name,
st::groupCallCheckbox,
st::groupCallRadio),
padding);
}
resize(width(), contentHeight());
}, lifetime());
}
not_null<QAction*> Selector::action() const {
return _dummyAction;
}
bool Selector::isEnabled() const {
return false;
}
int Selector::contentHeight() const {
_list->resizeToWidth(width());
if (_list->count() <= 3) {
_scroll->resize(width(), _list->height());
} else {
_scroll->resize(
width(),
3.5 * st::defaultRadio.diameter);
}
return _scroll->height();
}
int Selector::registerId(const QString &id) {
auto &result = _ids[id];
if (!result) {
result = int(_ids.size());
}
return result;
}
void AddDeviceSelection(
not_null<Ui::PopupMenu*> menu,
not_null<Webrtc::Environment*> environment,
DeviceSelection type,
Fn<void(QString)> selected) {
const auto title = [&] {
switch (type.type) {
case Webrtc::DeviceType::Camera:
return tr::lng_settings_call_camera(tr::now);
case Webrtc::DeviceType::Playback:
return tr::lng_settings_call_section_output(tr::now);
case Webrtc::DeviceType::Capture:
return tr::lng_settings_call_section_input(tr::now);
}
Unexpected("Type in AddDeviceSelection.");
}();
menu->addAction(
base::make_unique_q<Subsection>(menu, menu->st().menu, title));
menu->addAction(
base::make_unique_q<Selector>(
menu,
menu->st().menu,
environment->devicesValue(type.type),
std::move(type.chosen),
selected));
}
} // namespace
base::unique_qptr<Ui::PopupMenu> MakeDeviceSelectionMenu(
not_null<Ui::RpWidget*> parent,
not_null<Webrtc::Environment*> environment,
std::vector<DeviceSelection> types,
Fn<void(Webrtc::DeviceType, QString)> choose) {
auto result = base::make_unique_q<Ui::PopupMenu>(
parent,
st::callDeviceSelectionMenu);
const auto raw = result.get();
for (auto type : types) {
if (!raw->empty()) {
raw->addSeparator();
}
const auto selected = [=, type = type.type](QString id) {
choose(type, id);
};
AddDeviceSelection(raw, environment, std::move(type), selected);
}
return result;
}
} // namespace Calls

View File

@@ -0,0 +1,36 @@
/*
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/unique_qptr.h"
namespace Webrtc {
class Environment;
struct DeviceResolvedId;
enum class DeviceType : uchar;
} // namespace Webrtc
namespace Ui {
class RpWidget;
class PopupMenu;
} // namespace Ui
namespace Calls {
struct DeviceSelection {
Webrtc::DeviceType type;
rpl::producer<Webrtc::DeviceResolvedId> chosen;
};
[[nodiscard]] base::unique_qptr<Ui::PopupMenu> MakeDeviceSelectionMenu(
not_null<Ui::RpWidget*> parent,
not_null<Webrtc::Environment*> environment,
std::vector<DeviceSelection> types,
Fn<void(Webrtc::DeviceType, QString)> choose);
} // namespace Calls

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