Compare commits

...

377 Commits

Author SHA1 Message Date
John Preston
af35beefc2 Beta version 5.12.5: Fix build with Qt 6. 2025-03-14 17:23:54 +04:00
John Preston
532258bea8 Beta version 5.12.5.
- Support swipe navigation on touchscreens.
- Close main menu by swipe navigation.
2025-03-14 17:06:42 +04:00
Ilya Fedin
5b257293eb Update patches on Linux 2025-03-14 16:50:58 +04:00
John Preston
8e6d23ddd6 Support tg://settings/phone_privacy links. 2025-03-14 16:40:50 +04:00
John Preston
6d5d61c842 Label 'Add users' for Remove Fee. 2025-03-14 16:40:50 +04:00
John Preston
9d2e2a1739 Fix processing of video qualities.
Regression was introduced in b618d1e56a.
2025-03-14 16:40:49 +04:00
John Preston
19e2642ec1 Improve paid messages in groups layout. 2025-03-14 16:40:49 +04:00
John Preston
f9df522b41 Show error not-found on comment link click. 2025-03-14 16:40:49 +04:00
John Preston
b3e5c4a4aa Fix send button size on chat switch. 2025-03-14 16:40:49 +04:00
John Preston
06ed6c81a7 Remove 'delete also for' in Deleted Accounts. 2025-03-14 16:40:44 +04:00
John Preston
d1e4dbb603 Fix animated emoji in folder name. 2025-03-14 16:40:44 +04:00
John Preston
abe8079296 Allow temp-set of wearable as a status. 2025-03-14 16:40:44 +04:00
John Preston
033cb2790c Remove debug widget in the info section. 2025-03-14 16:03:58 +04:00
John Preston
188edce258 Support swipe-back from main menu. 2025-03-14 15:03:27 +04:00
John Preston
34858b36c1 Fix swipe gestures on touchscreens. 2025-03-14 14:36:13 +04:00
Ilya Fedin
044ef3447c Add the missing 5.12.4 changelog part 2025-03-14 11:14:28 +04:00
Ilya Fedin
2660439160 Fix Platform::ApplicationIconName for snap
Looks like it broke through rebases
2025-03-14 10:22:24 +04:00
John Preston
3f6f96cfb4 Fix typo in lcms2 build commands for Windows. 2025-03-12 21:15:30 +04:00
John Preston
99a1c98ae0 Beta version 5.12.4.
- Touchpad swipe back to go back in navigation history.
- Touchpad swipe between folders if they're horizontal.

Out-of-the-box monochrome tray icon on Linux
with GTK icon recolorization support.

Files telegram{,{,-attention,-mute}-panel} are renamed
to org.telegram.desktop{,{,-attention,-mute}-symbolic} for
unified icon lookup in multiple (sometimes sandboxed) builds
and GTK icon recolorization support.

Snap package got icon theming support,
the icon names are snap.telegram-desktop.{,{,-attention,-mute}-symbolic}
(notice the name ends with a dot in case of no suffix!)
2025-03-12 18:26:46 +04:00
John Preston
fd718dfd5c Allow more Info sections swipe-back. 2025-03-12 18:02:47 +04:00
Ilya Fedin
31cb2f1999 Switch tray to symbolic icons
This allows to rely on GTK recoloring rather than requiring the user to have an icon theme
2025-03-12 14:54:41 +04:00
Ilya Fedin
a6a8e32be7 Install app icon in a spec-compilant way 2025-03-12 14:54:41 +04:00
Ilya Fedin
594bb8a76b Support icon themes in snap 2025-03-12 14:54:41 +04:00
Ilya Fedin
228bbc1e8e Always use app ID based icon name
This allows to unify it between flatpak and non-flatpak builds and corresponds to what most other GUI apps on Linux do
2025-03-12 14:54:41 +04:00
Ilya Fedin
b14c2878b3 Revert "Move icon name getter to cross-platform header"
This reverts commit d9318c9935.
2025-03-12 14:54:41 +04:00
Ilya Fedin
57f10040e1 Get rid of XEmbed mentions
The code goes through Qt abstractions which not necessarily go through XEmbed
2025-03-12 14:53:43 +04:00
Ilya Fedin
0fb67c78a9 Add a method to calculate instance hash to Launcher 2025-03-12 14:53:21 +04:00
John Preston
ff0f7f49da Fix new swipes on Windows. 2025-03-12 14:02:04 +04:00
23rd
9682e37547 Added shortcut to open chat preview from dialogs. 2025-03-12 11:59:20 +04:00
23rd
7197d9480b Added ability to swipe between chats filters. 2025-03-12 11:59:20 +04:00
23rd
3315c9c7f4 Added ability to open main menu with swipe-to-back. 2025-03-12 11:59:20 +04:00
23rd
f57eff4195 Added ability to mirror icon from widget for swipe-to-back. 2025-03-12 11:59:20 +04:00
23rd
f0c8e48403 Added ability to keep ratio of swipe within range. 2025-03-12 11:59:20 +04:00
23rd
de87bed375 Moved out data for swipe handler from history view to ui controls. 2025-03-12 11:59:20 +04:00
23rd
5cdfaab2db Moved out swipe handler from history view to ui controls. 2025-03-12 11:59:20 +04:00
23rd
acb0b029b9 Added ability to switch tab in tabbed selector with swipe. 2025-03-12 11:59:20 +04:00
23rd
4c74cbbbe9 Added ability to set swipe back widget on opposite side. 2025-03-12 11:59:20 +04:00
23rd
ad64e068db Allowed swipe-to-back in side info section. 2025-03-12 11:59:20 +04:00
23rd
6bed3f3f09 Increased speed of swipe-to-back. 2025-03-12 11:59:20 +04:00
23rd
d02e55da06 Added ability to swipe-to-back to all info sections with narrow type. 2025-03-12 11:59:20 +04:00
23rd
d662a8f2b9 Added ability to swipe-to-back to other sections. 2025-03-12 11:59:20 +04:00
23rd
13a93102a5 Added ability to swipe-to-back to admin log section. 2025-03-12 11:59:20 +04:00
23rd
0d6a1e6610 Added ability to swipe-to-back to replies section. 2025-03-12 11:59:20 +04:00
23rd
05cd9eebb8 Added ability to swipe-to-back to dialogs widget. 2025-03-12 11:59:20 +04:00
23rd
d93d8ab1cc Added ability to provide ElasticScroll to SetupSwipeHandler. 2025-03-12 11:59:12 +04:00
23rd
540fa0e669 Added ability to swipe-to-back to history widget. 2025-03-12 04:36:34 +03:00
23rd
17a10cf6bb Added initial ability to handle history view swipe in both directions. 2025-03-12 04:36:24 +03:00
John Preston
11d0f9db03 Try fixing crashes on Linux. 2025-03-10 13:32:36 +04:00
John Preston
f024ceecdd Version 5.12.3.
- Fix a couple more crashes.
- Fix gift disappearing on unpin.
- Fix country emoji for Fragment numbers.
2025-03-10 11:58:20 +04:00
John Preston
08c07a0785 Don't remove last gift on unpin if all loaded. 2025-03-10 11:57:20 +04:00
John Preston
b843f91b3c Support 'FT' "country" flag emoji. 2025-03-10 11:52:42 +04:00
23rd
4b2c5b3321 Removed chat filters strip from folders and forums in separated windows. 2025-03-10 11:20:33 +04:00
23rd
2b43f2682a Fixed open of locked chats filter with shortcuts to open near filters. 2025-03-10 11:20:28 +04:00
23rd
7da0124286 Moved out lambda to jump to near chats filters to static function. 2025-03-10 11:18:43 +04:00
23rd
feaeef6482 Fixed repaint of send button when it has slowmode countdown. 2025-03-10 11:11:10 +04:00
23rd
15bcfeec1d Added pause to animation of custom emoji in status info. 2025-03-10 11:11:10 +04:00
23rd
5fb002ab4c Added ability to specify time when export data. 2025-03-10 11:11:10 +04:00
23rd
f56ddbb1e0 Added recursive animation to lock hint while recording voice. 2025-03-10 11:11:10 +04:00
John Preston
6bd2a7c962 Fix possible crash in MediaGenericTextPart. 2025-03-10 11:00:49 +04:00
John Preston
9d591ae806 Fix infinite recursion in channel reactions edit. 2025-03-10 10:59:39 +04:00
Ilya Fedin
7d30e3913c Add lcms2 dependency for Qt6 on Windows 2025-03-10 10:58:10 +04:00
Kaiyang Wu
19d7dd7aa3 fix(integration_linux): include core_settings.h with Qt version <= 6.5.0
Signed-off-by: Kaiyang Wu <self@origincode.me>
2025-03-09 20:33:20 +04:00
John Preston
45444253fd Version 5.12.2.
- Fix some crashes.
2025-03-09 09:44:47 +04:00
John Preston
308ade6a7e Fix crash in some custom emoji cases. 2025-03-09 06:55:25 +04:00
John Preston
fc67a801e3 Version 5.12.1: Fix build with Xcode. 2025-03-08 07:09:15 +04:00
John Preston
6a3657ca87 Version 5.12.1.
- Fix a crash in some chat switchings.
- Fix crashes in empty repaint callbacks.
2025-03-08 07:06:37 +04:00
John Preston
0537c5f273 Fix crashes in empty repaint callbacks. 2025-03-08 07:05:45 +04:00
John Preston
cc4a5f30b6 Fix a crash in some chat switchings. 2025-03-07 23:56:17 +04:00
John Preston
b0d7c3e9b1 Revert "Re-enable ffmpeg optimizations on Linux"
This reverts commit bd28ac6e1f.

It fails to link in Release mode with LTO.
2025-03-07 22:20:18 +04:00
John Preston
3bf7c44fc9 Version 5.12.
- Set a fee for incoming messages from unknown users.
- Set a fee for messages in groups or channel comments.
- Show some information about who's messaging you.
- Pin gifts on your profile.
2025-03-07 20:50:29 +04:00
John Preston
4ff4e63a11 Fix sending paid intro sticker. 2025-03-07 19:03:41 +04:00
John Preston
72a35ba58b Show new-peer-info photo/name change. 2025-03-07 19:03:41 +04:00
John Preston
b6a31979f2 Add the same enter/leave check to ListWidget. 2025-03-07 19:03:40 +04:00
John Preston
7c710e22cc Always process mouse input in active window.
Fixes #29008.
2025-03-07 19:03:40 +04:00
John Preston
ab58e7a225 Show common groups userpics in new-peer-info. 2025-03-07 19:03:40 +04:00
John Preston
c9fb97cd7c Simplify marked text context logic. 2025-03-07 19:03:40 +04:00
John Preston
789f3e1584 Add non-official account info icon. 2025-03-07 19:03:39 +04:00
John Preston
0fc8229be1 Initial new peer information display. 2025-03-07 19:03:39 +04:00
John Preston
a1e555267e Fix sending invite links to paid. 2025-03-07 19:03:39 +04:00
John Preston
0ac88c0cb5 Show special blank chat for paid messages. 2025-03-07 19:03:39 +04:00
John Preston
d43a6da62b Fix sending bot commands in paid chats. 2025-03-07 19:03:39 +04:00
John Preston
940455f786 Preload gifts for the gift to user layer. 2025-03-07 19:03:39 +04:00
John Preston
0f74456f30 Support gifts pinning. 2025-03-07 19:03:39 +04:00
John Preston
7840fa6d90 Add context menu for gifts in list. 2025-03-07 19:03:39 +04:00
John Preston
95ccc99fee Hide reply in notification for paid peers. 2025-03-07 19:03:39 +04:00
23rd
7b0a156bba Added lottie icon when have no enough info for earn stats. 2025-03-07 19:03:39 +04:00
23rd
0d8ae7bb37 Renamed creditsAmount in invoice for premium gift with represented name. 2025-03-07 19:03:39 +04:00
23rd
9491cff1df Fixed display credits in list of gift options. 2025-03-07 19:03:39 +04:00
23rd
51dc5d6e37 Fixed discount calculation for gifts options with different currencies. 2025-03-07 19:03:39 +04:00
John Preston
f4c739ab92 Improve transactions history for new stuff. 2025-03-07 19:03:39 +04:00
John Preston
0dd8ae3d77 Update submodules. 2025-03-07 19:03:39 +04:00
John Preston
7d2878d81c Support gifting premium for stars. 2025-03-07 19:03:39 +04:00
23rd
bd70a05861 Removed button to turn off sponsored messages in megagroups. 2025-03-07 19:03:39 +04:00
23rd
0605c7b2bc Added ability to display possible currency earn in megagroups in future. 2025-03-07 19:03:39 +04:00
23rd
8e83a55143 Added ability to request earn stats without currency earn in megagroups. 2025-03-07 19:03:39 +04:00
John Preston
4ab4eb8ef2 Pause voice in pay-to-send chats. 2025-03-07 19:03:39 +04:00
John Preston
d1e6150874 Don't suggest userpics to paid-restricted. 2025-03-07 19:03:38 +04:00
John Preston
4121c99f36 Allow folders submenu to have a scroll. 2025-03-07 19:03:38 +04:00
John Preston
827040f487 Use paid messages values from appConfig. 2025-03-07 19:03:38 +04:00
John Preston
9032489786 Add pays-me status bar in chat. 2025-03-07 19:03:38 +04:00
John Preston
8ea7bd4913 Simplify paid message button labeling. 2025-03-07 19:03:38 +04:00
John Preston
97b021efaf Star-count button in multi-Forward/CreatePoll. 2025-03-07 19:03:38 +04:00
John Preston
b3f9a77ba7 Star-count button in SendFilesBox/ShareBox. 2025-03-07 19:03:38 +04:00
John Preston
63fdc1f876 Update API scheme on layer 200. 2025-03-07 19:03:38 +04:00
John Preston
17cf354c58 Support custom send button for paid. 2025-03-07 19:03:38 +04:00
John Preston
c6fd8bcb99 Edit paid messages exceptions. 2025-03-07 19:03:38 +04:00
John Preston
1684465e04 Add sending paid stories replies. 2025-03-07 19:03:38 +04:00
John Preston
fe2df96953 Improve paid peer-box multi-send. 2025-03-07 19:03:38 +04:00
John Preston
ee9d0cfd99 Update API scheme, disable scheduled paid. 2025-03-07 19:03:38 +04:00
John Preston
7b7e18e752 Support paid sending in ShareBox. 2025-03-07 19:03:38 +04:00
John Preston
928be4151b Update API scheme, parse premium gifts for stars. 2025-03-07 19:03:38 +04:00
John Preston
37dd648686 Implement paid location sending. 2025-03-07 19:03:38 +04:00
John Preston
93a590774e Send paid stickers/gifs/inline-results. 2025-03-07 19:03:38 +04:00
John Preston
22b99b6d3e Send paid shared contacts. 2025-03-07 19:03:37 +04:00
John Preston
101d626d4f Support paid files-with-comment and polls. 2025-03-07 19:03:37 +04:00
John Preston
3633c19208 Update API scheme, improve service messages. 2025-03-07 19:03:37 +04:00
John Preston
e302f328f7 Don't cut confirming emoji status. 2025-03-07 19:03:37 +04:00
John Preston
f74ba95e95 Don't track caption in fullscreen video view.
Fixes #28981.
2025-03-07 19:03:37 +04:00
John Preston
c38982d286 Simplify paid stars check. 2025-03-07 19:03:37 +04:00
John Preston
fe9bac096b Refresh balance after paid message sending. 2025-03-07 19:03:37 +04:00
John Preston
5b809c4fc6 Show paid stars information above a message. 2025-03-07 19:03:37 +04:00
John Preston
4729e51e14 Improve phrases for paid messages. 2025-03-07 19:03:37 +04:00
John Preston
960cf7a34b Update API scheme, new paid. 2025-03-07 19:03:37 +04:00
John Preston
852ab19760 Update API scheme, track stars-per-message. 2025-03-07 19:03:37 +04:00
John Preston
2e45d9fc6b Allow replacing default shortcuts. 2025-03-07 19:03:37 +04:00
John Preston
74b71b92b6 Update API scheme on layer 200. 2025-03-07 19:03:37 +04:00
John Preston
bbc14ba74f Allow sending paid messages. 2025-03-07 19:03:37 +04:00
John Preston
45c7829cd8 Track stars-per-message for users and channels. 2025-03-07 19:03:37 +04:00
John Preston
f2aa3afbbb Allow editing charge-for-message privacy. 2025-03-07 19:03:37 +04:00
John Preston
909b01241b Update API scheme to layer 200. 2025-03-07 19:03:36 +04:00
Ilya Fedin
abb58c58a0 QPlatformKeyMapper -> QKeyMapper 2025-03-06 17:54:54 +04:00
Ilya Fedin
700e10d32c Use flat_map::remove in clearFrom{Topic,Item}
Now that notification closing happens in destructor, the iterator is no more needed
2025-03-06 17:30:26 +04:00
Ilya Fedin
4ac48d0e4a Fix clearFromTopic on Linux
Looks like it was broken since addition
2025-03-06 16:41:34 +04:00
Ilya Fedin
345b2cb835 Move notification closing to NotificationData destruction 2025-03-04 14:17:43 +04:00
Ilya Fedin
b962309498 Move hints.lookup_value() out of xdg_notifications_notifications_call_notify
Or it gets executed after hints.end() which clears hints
2025-03-04 12:28:37 +04:00
Eugene
e99cb9bfb8 Ensure policy check before creating Zone.Identifier for downloaded files on Windows 2025-03-04 10:47:31 +04:00
Ilya Fedin
9e12e18f90 Clean up unnecessary calls to Manager::Private::clearNotification
It was added to replicate NotificationData::close but lots of places call it after the notification is already cleared
2025-03-03 18:40:22 +04:00
Ilya Fedin
66fc9b38df Fix a race condition with asynchronous notification sending 2025-03-03 18:40:06 +04:00
Ilya Fedin
5dbe429e6b Fix NotificationData initialization 2025-03-03 18:39:55 +04:00
GitHub Action
b2481ea6c1 Update User-Agent for DNS to Chrome 133.0.0.0. 2025-03-03 18:37:13 +04:00
Ilya Fedin
86a294ce4b Subscribe to XdgNotifications signals on Manager initialization 2025-03-01 09:08:10 +04:00
Ilya Fedin
a8d1eadfbf Turn NotificationData into a struct 2025-02-28 13:20:53 +04:00
Ilya Fedin
b07d3c5403 Decouple GNotification from NotificationData 2025-02-28 12:05:09 +04:00
Ilya Fedin
892db55ae1 Get rid of NotificationData::init 2025-02-28 11:12:08 +04:00
Ilya Fedin
93615fef65 Revert "Check whether notification image has alpha channel"
This reverts commit cee593c423.

Avatars couldn't be opaque anyway while this simplifies porting out of NotificationData
2025-02-28 11:12:08 +04:00
Ilya Fedin
87452706ef Remove unused has_weak_ptr from Manager::Private 2025-02-28 11:12:08 +04:00
Ilya Fedin
3569615b21 Use gi::cstring for notification actions 2025-02-28 11:12:08 +04:00
Ilya Fedin
86f7d09d31 Pass notification icon name inline 2025-02-28 11:12:08 +04:00
Ilya Fedin
d5d1254393 Destroy NotificationData signal connections with rpl::lifetme 2025-02-28 11:12:08 +04:00
davidholio
4df90cfb9e Make sure caption items can only be interacted if not in video fullscreen. 2025-02-25 11:40:05 +04:00
Ilya Fedin
ec6862d31a Communicate PiP window margins to the OS 2025-02-25 10:15:33 +04:00
Ilya Fedin
6f23010382 Fix IconGraphic::counterSlice for Window::WithSmallCounter 2025-02-24 13:25:59 +04:00
Ilya Fedin
c672f105d3 IconGraphic::isCounterNeeded helper for Linux tray 2025-02-24 13:25:59 +04:00
Ilya Fedin
e60d501e4a Have a state struct in Linux tray 2025-02-24 13:25:59 +04:00
jovaska
0d7175058b Fix compilation with ffmpeg-4.x 2025-02-24 13:15:37 +04:00
Ilya Fedin
3b0bd9d1d1 Remove duplicate entry in qt snap part 2025-02-20 17:48:37 +04:00
Ilya Fedin
bd28ac6e1f Re-enable ffmpeg optimizations on Linux
They were disabled due to -Ofast but Dockerfile is switched to -O3 since a long time
2025-02-20 11:34:55 +04:00
Ilya Fedin
0c2d00c792 More clean up in qt snap part 2025-02-20 11:34:47 +04:00
Ilya Fedin
140ba653b9 More clean up in libjxl snap part 2025-02-20 11:34:33 +04:00
Ilya Fedin
f64f008f77 Remove fmt from snap
It's not really needed for a long time
2025-02-20 11:34:24 +04:00
Ilya Fedin
a6315bef05 Move GNotiftcation action handlers to Manager 2025-02-19 14:42:11 +04:00
Ilya Fedin
f810d7c82a Fix spaces on end of lines 2025-02-18 21:26:38 +04:00
Ilya Fedin
cf61dedc79 Simplify GNotification actions 2025-02-18 21:21:37 +04:00
Ilya Fedin
2ab9587f5f Don't wrap QByteArray into std::shared_ptr
This has no sense as QByteArray is CoW
2025-02-18 21:20:53 +04:00
Ilya Fedin
4950b52359 Don't set CMAKE_EXPORT_COMPILE_COMMANDS via cmake.configureSettings
It's controlled by cmake.exportCompileCommandsFile and defaults to true
2025-02-14 20:19:29 +04:00
Nikolai Nechaev
03af444735 Notifications: stop fading in before starting to fade out
When a notification is to start hiding (i.e., fade out), it is supposed
to start fading out from the maximum opacity, even if it was not fully
restored (which only happens if the cursor passed through the
notification too quickly). Thus, call `.stop()` for the previous
animation, if any, before `.start()`ing the next animation.
2025-02-14 20:17:02 +04:00
Nikolai Nechaev
7f6221b409 Manager::startAllHiding: don't treat fading in notifications specially
Previously, `Window::Notifications::Default::Manager` would not start
hiding notifications that are fading in when other notifications should
hide. This would lead to some notifications never hiding, e.g., when the
cursor passes through the notification too quickly and there was not
enough time for the notification to fade in completely.

Also renamed `Widget::isShowing` -> `Widget::isFadingIn` for clarity.

Fixes #28811.
2025-02-14 20:17:02 +04:00
John Preston
ef859d77e9 Version 5.11.1.
- Fix arbitrary cropping support in the image editor.
- Fix crash on round video message playback.
- Fix complex custom keyboard shortcuts.
2025-02-13 21:24:40 +04:00
John Preston
0fd752657a Fix build with latest Visual Studio. 2025-02-13 21:24:40 +04:00
23rd
f1451a1de3 Added ripple animation to button for gift to channel in history widget. 2025-02-13 21:02:44 +04:00
John Preston
fa96f25683 Fix webview with old webkitgtk. 2025-02-13 21:01:28 +04:00
John Preston
9e447383df Support Shift+.. shortcuts on Linux. 2025-02-13 21:01:16 +04:00
John Preston
5e762be32b Remove redundant new progress in round videos. 2025-02-13 20:20:47 +04:00
John Preston
75de81a3ab Update lib_webview. 2025-02-13 20:20:43 +04:00
John Preston
d26b64a5bb Fix crash in video messages. 2025-02-13 20:20:40 +04:00
John Preston
cb8d40eaf2 Fix recording shortcuts on Windows. 2025-02-13 20:20:34 +04:00
John Preston
ded0936bc4 Fix arbitrary aspect in photo editor crop. 2025-02-13 20:20:14 +04:00
John Preston
16830a410c Improve gift-to-channel button conditions. 2025-02-13 20:20:07 +04:00
John Preston
9f79dda463 Version 5.11.
- Copy video links at current time.
- Set custom video covers when posting to channels.
- Send paid reactions from the name of your channels.
2025-02-13 10:37:16 +04:00
John Preston
15dc7c74d7 Make emoji status in chat bubbles clickable. 2025-02-12 18:27:21 +04:00
John Preston
f9abef9e05 Update API scheme on layer 199. 2025-02-12 17:24:24 +04:00
John Preston
ba84499f00 Send paid reactions from channels. 2025-02-12 17:24:24 +04:00
John Preston
79ce24222a Prepare code for paid reactions from channels. 2025-02-12 17:24:24 +04:00
John Preston
6cb9264864 Update API scheme to layer 199. 2025-02-12 17:24:24 +04:00
John Preston
247a070405 Prioritize last used playback timestamp. 2025-02-12 17:24:24 +04:00
John Preston
e05bb75b8a Allow sending videos with covers. 2025-02-12 17:24:24 +04:00
John Preston
6a415cf232 Support on-hover autoplay and position save. 2025-02-12 17:24:24 +04:00
John Preston
e8034189df Use video covers in messages. 2025-02-12 17:24:24 +04:00
John Preston
107f329b4f Update API scheme on layer 198. 2025-02-12 17:24:24 +04:00
John Preston
999a13358e Allow sharing video at a timestamp. 2025-02-12 17:24:24 +04:00
John Preston
2077f51084 Open video from ?t= links. 2025-02-12 17:24:24 +04:00
John Preston
1e77a3df20 Fix file reference video cover. 2025-02-12 17:24:24 +04:00
John Preston
141a291523 Start video from required timestamp. 2025-02-12 17:24:24 +04:00
John Preston
cb03d5a9d3 Show video_timestamp progress line. 2025-02-12 17:24:24 +04:00
John Preston
b618d1e56a Parse and store video_timestamp. 2025-02-12 17:24:24 +04:00
John Preston
23ae638512 Parse video cover photos. 2025-02-12 17:24:23 +04:00
John Preston
eda749d7cb Show correct shortcut text on macOS. 2025-02-12 17:00:39 +04:00
John Preston
08d0186e53 Beta version 5.10.8.
- Edit keyboard shortcuts in Settings > Chat Settings.
- Hide controls in calls with incoming video.
- Fix several crashes.
2025-02-12 11:45:58 +04:00
John Preston
443981ba31 Fix reordering of various-height pinned chats.
Fixes #28948.
2025-02-12 11:39:26 +04:00
John Preston
f3ed7c5e19 Fix crash in Instant View in some themes.
Fixes #28604.
2025-02-12 11:39:26 +04:00
John Preston
dd2378b591 Hide call controls in one-on-one video. 2025-02-12 11:39:26 +04:00
John Preston
b885779365 Support location picker on Linux. 2025-02-12 11:39:26 +04:00
John Preston
32b95f0d9a Improve forum outlines in stories/folders. 2025-02-12 11:39:26 +04:00
John Preston
2b8eec8666 Allow fast bot mode without support mode. 2025-02-12 11:39:26 +04:00
23rd
3f24627f54 Removed reactions list for anonymous non-owner admins. 2025-02-10 23:48:09 +03:00
23rd
0c4bca312e Removed ability to set wallpaper to chat with require-premium user. 2025-02-10 22:52:25 +03:00
23rd
e36afc675e Removed ability to set TTL to chat with require-premium user. 2025-02-10 22:52:00 +03:00
23rd
d754014321 Added missed label about similar channels to box for doubled limits. 2025-02-10 22:38:42 +03:00
23rd
9f73242cc5 Improved width of voice waveform. 2025-02-10 21:22:57 +03:00
23rd
fae9649773 Added log to catch possible api errors when disable calls for account. 2025-02-10 19:42:26 +03:00
23rd
4b43f4cbec Fixed color update of right buttons in chats list on palette change. 2025-02-10 10:49:53 +04:00
23rd
b61befa210 Fixed color update of badges in chat filters strip on palette change. 2025-02-10 10:49:53 +04:00
23rd
5e2bc337bc Fixed preview display of personal channel on change of last message. 2025-02-10 10:49:53 +04:00
23rd
af1608cbfa Added rpl filter to exclude incomplete sizes. 2025-02-10 10:49:53 +04:00
John Preston
9aa80976ff Fix viewing gifts from the list. 2025-02-10 10:46:49 +04:00
John Preston
c9cfe9e90f Allow filtering users gifts. 2025-02-10 10:46:49 +04:00
John Preston
482e337762 Move shortcuts settings lower. 2025-02-10 10:46:49 +04:00
John Preston
a0821f5a01 Handle additional phone call discard reason. 2025-02-10 10:46:49 +04:00
John Preston
5ebdf3ed39 Save custom shortcuts to disk. 2025-02-10 10:46:49 +04:00
John Preston
86096db02d Apply shortcuts changes to actions. 2025-02-10 10:46:49 +04:00
John Preston
a93e01b896 Don't show Non-Unique status without Upgrade. 2025-02-10 10:46:49 +04:00
John Preston
0585e72c35 Section for shortcuts editing. 2025-02-10 10:46:48 +04:00
John Preston
c82fbefcfc Don't show Send a Gift to service users. 2025-02-10 10:46:48 +04:00
John Preston
23542a1db1 Fix boost group phrases. 2025-02-10 10:46:48 +04:00
John Preston
1bf50d60d8 Fix possible crash in stories. 2025-02-10 10:46:48 +04:00
John Preston
8596b0309e Fix bot chat scroll shift on state restore.
Fixes #28085.
2025-02-10 10:46:48 +04:00
John Preston
7f6e871b26 Fix crash in emoji color picker. 2025-02-10 10:46:48 +04:00
John Preston
8912d4d55a Fix weather widget in stories. 2025-02-10 10:46:48 +04:00
John Preston
7ac849ab12 Don't update mouse in non-active window. 2025-02-10 10:46:48 +04:00
John Preston
4cab699b04 Use larger unique gift preview. 2025-02-10 10:46:48 +04:00
John Preston
03aa05e4d2 Remove creation of telesco.pe links. 2025-02-10 10:46:48 +04:00
John Preston
428a3cf0ce Improve phrases for forward-from-saved. 2025-02-10 10:46:48 +04:00
John Preston
1c8b165a64 Show quantity of issued unique gifts. 2025-02-10 10:46:48 +04:00
John Preston
1869071ef7 Sort/Filter channel gifts by non-admins. 2025-02-10 10:46:48 +04:00
John Preston
33ca5ee39f Limited before In Stock. 2025-02-10 10:46:48 +04:00
John Preston
e46d5a86d3 Don't auto-submit /start on non-loaded history. 2025-02-10 10:46:48 +04:00
John Preston
2729bcac3b Fix multi-line description in gift box. 2025-02-10 10:46:48 +04:00
John Preston
d60ce41fa9 Add a gift button to channel bottom. 2025-02-10 10:46:48 +04:00
John Preston
958db945f3 Fix bot verify badge position. 2025-02-10 10:46:48 +04:00
John Preston
057f906ca4 Remove bot verification from saved messages. 2025-02-10 10:46:48 +04:00
23rd
8df7a45e29 Added animated arrow to toggle of business hours. 2025-02-10 10:46:47 +04:00
23rd
e4af1570cb Replaced link button for switch of business hours with small round one. 2025-02-10 10:46:47 +04:00
23rd
049ebf9027 Fixed display of credits icon in table row from gift box. 2025-02-10 10:46:47 +04:00
23rd
f2f0c7df92 Extended context menu for outgoing gifts. 2025-02-10 10:46:47 +04:00
Anton Ryzhov
bee4118513 Export media sizes 2025-02-09 12:22:37 +04:00
Ilya Fedin
bf48025d12 Update Qt 6.8.1 -> 6.8.2 2025-02-02 15:37:34 +04:00
Ilya Fedin
5e624605cf Don't print "Choosing Qt $version" multiple times 2025-02-02 15:37:34 +04:00
Ilya Fedin
026490acc6 Add geoclue and geocode-glib to snap 2025-02-02 15:37:34 +04:00
Ilya Fedin
709ce3adb8 Set UID in docker run 2025-02-01 12:54:55 +04:00
Ilya Fedin
a8d23489c4 Set up gcc toolset environment manually 2025-02-01 12:52:22 +04:00
Ilya Fedin
296df113e3 Implement getting geolocation via geoclue/geocode-glib on Linux 2025-02-01 12:51:02 +04:00
23rd
97c4e79e96 Improved title for separated windows with shared media. 2025-01-30 13:59:53 +03:00
23rd
f89bac7781 Improved handle of existing separated windows for shared media. 2025-01-30 13:59:53 +03:00
23rd
1d8a7f8fd3 Removed redundant template from Info::Media::AddCountedButton. 2025-01-30 13:59:35 +03:00
23rd
ef9f7ab27a Moved process of separated window for shared media to correspond file. 2025-01-30 13:59:00 +03:00
23rd
a676138745 Added ability to open shared media in window with Ctrl+Click. 2025-01-30 12:16:31 +03:00
23rd
7442ea7a16 Added ability to open single typed shared media in window for topics. 2025-01-30 12:04:44 +03:00
23rd
0089cad740 Replaced default shortcut to open chat menu with Ctrl+\. 2025-01-30 11:42:12 +03:00
23rd
cf1fa718a8 Improved conflict handle between IV shortcuts and Shortcuts. 2025-01-30 11:39:56 +03:00
23rd
d6ba6ac41e Added initial ability to open single typed shared media in window. 2025-01-30 11:01:04 +03:00
23rd
bbdd5feaa4 Fixed display of video userpic in short box on Retina. 2025-01-30 11:01:04 +03:00
Ilya Fedin
678527254b Add development facilities to the CentOS Dockerfile 2025-01-29 15:27:25 +01:00
John Preston
46bf7781aa Version 5.10.7: Haptic on Linux. 2025-01-27 23:43:57 +04:00
John Preston
99c547b625 Version 5.10.7: Fix gifts scrolling. 2025-01-27 23:41:07 +04:00
John Preston
6976e97de3 Fix possible crash in font selection box. 2025-01-27 23:41:07 +04:00
23rd
f8e3e70273 Added peer userpic to gift service messages. 2025-01-27 21:10:44 +03:00
23rd
23c9f7a957 Added entities support to title from ServiceBoxContent. 2025-01-27 21:10:44 +03:00
23rd
2ca763cc77 Made less strict condition to show unique status of gift in gift box. 2025-01-27 21:10:44 +03:00
John Preston
2adc811351 Version 5.10.7: Fix build. 2025-01-27 20:31:01 +04:00
23rd
be18be4a86 Added volume slider to settings box for rtmp live stream. 2025-01-27 18:35:58 +03:00
John Preston
8d7abb1b8a Version 5.10.7.
- Fix some phrases for collectible gifts.
- Fix freeze in sound reencoding on macOS and Linux.
2025-01-27 19:27:11 +04:00
John Preston
1cc5988c40 Improve gifts phrases. 2025-01-27 19:25:17 +04:00
John Preston
da426ae03b Introduce fast-buttons-bots for support mode. 2025-01-27 19:25:17 +04:00
John Preston
3cebd6d923 Fix deadlock in audio for notification repacking. 2025-01-27 18:10:38 +04:00
John Preston
f1019c8ca4 Update Qt patches in Linux build. 2025-01-27 18:07:42 +04:00
John Preston
edfb7b6b24 Add delay for anim::Disabled tabbed panel hide. 2025-01-27 18:07:42 +04:00
23rd
157a928f5a Added shortcut to open chat menu from dialogs, chat info and history. 2025-01-27 18:07:42 +04:00
Ilya Fedin
5f5a2a3ef2 Check high contrast theme for auto dark mode on Windows 2025-01-27 15:01:11 +01:00
Ilya Fedin
8534cf3756 Extract system dark mode from QPalette 2025-01-27 15:01:11 +01:00
Ilya Fedin
39b90092ff Revert "Fallback to portal on Linux if QStyleHints::colorScheme is unknown"
This reverts commit a88f48cd93.
2025-01-27 15:01:11 +01:00
John Preston
d3142ebe6d Version 5.10.6: Fix build. 2025-01-26 14:00:45 +04:00
John Preston
d914c6be2e Version 5.10.6.
- Fix crash in opening users gifts section.
- Fix unintended sound in reaction notifications.
- Fix disabling notifications sound on macOS and Linux.
2025-01-26 12:48:57 +04:00
John Preston
05c3e968df Remove 'SOON' badge for tradable feature. 2025-01-26 11:34:45 +04:00
John Preston
7756cce123 Fix viewing gifts from list of saved. 2025-01-26 11:34:44 +04:00
John Preston
8287d717f8 Fix notifications without sounds on macOS. 2025-01-26 11:34:32 +04:00
23rd
db9e60b4b5 Added amount of total voters to poll results with multiple answers.
Fixed #28879.
2025-01-26 08:49:21 +03:00
23rd
28a79bfccb Improved sort of received star gifts. 2025-01-25 20:18:42 +03:00
23rd
321490e528 Fixed ability to moderate messages from channel authors. 2025-01-25 19:32:49 +03:00
23rd
6f752357d7 Fixed ability to moderate messages from topbar in sections. 2025-01-25 19:32:49 +03:00
Ilya Fedin
e1f71baed6 Move native notifications option check logic to cross platform code 2025-01-25 18:33:13 +04:00
John Preston
df377cd5bb Version 5.10.5.
- Count all unread topics as a single chat in archive unread counter.
- Fix common groups section in bots profiles.
- Fix crash on scheduling messages from Share box.
- Fix silent notifications on macOS.
- Add support for 'sound-file' notification hint on Linux.
2025-01-24 22:23:27 +04:00
John Preston
691a0acdab Allow sound-without-toast again on macOS/Linux. 2025-01-24 22:23:27 +04:00
John Preston
5e2eda6af3 Fix webpage preview glitch. 2025-01-24 22:12:21 +04:00
23rd
17cdc2b585 Fixed search shortcut in section for comments. 2025-01-24 21:09:30 +04:00
23rd
c08266f81b Fixed reward label for point details widget in credits statistics. 2025-01-24 21:09:28 +04:00
23rd
f90a4db569 Respected pause reason in mini stars from section for main settings. 2025-01-24 21:09:27 +04:00
John Preston
22ec7a6d75 Fix notification sounds on macOS. 2025-01-24 21:08:54 +04:00
John Preston
1b16a84810 Return common groups section to bots.
Fixes #28886.
2025-01-24 20:22:16 +04:00
John Preston
d778276f5d Reuse views in gifts lists. 2025-01-24 13:40:57 +04:00
John Preston
e3030a168f Fix crash in share box scheduling. 2025-01-24 12:13:35 +04:00
John Preston
dfd07a4f4f Count all topics as one chat for outer badges. 2025-01-24 12:02:28 +04:00
John Preston
f9fc65d7de Fix webapp window top in some cases. 2025-01-24 11:14:06 +04:00
Ilya Fedin
46cf7db242 Add support for sound-file notification hint on Linux 2025-01-24 11:02:38 +04:00
John Preston
2b94cffe7e Allow getting name or path for sound. 2025-01-23 12:13:01 +04:00
John Preston
2e74ad6fbe Use common code for local sound disk cache. 2025-01-23 11:58:20 +04:00
John Preston
df7dc1583d Try settings window title for miniapps. 2025-01-23 10:55:59 +04:00
John Preston
88e80b4fae Version 5.10.4: Fix build with GCC. 2025-01-22 23:56:52 +04:00
John Preston
aea90f4b65 Version 5.10.4.
- Add messages search in channel comments.
- Allow sending your unique gifts to blockchain.
- Add option for running in backround on Linux.
- Fix possible freeze on quit on macOS.
2025-01-22 22:22:20 +04:00
John Preston
80db076f38 Improve channel gifts viewing. 2025-01-22 22:13:29 +04:00
John Preston
27bba8250a Add special phrase for unique hidden gift. 2025-01-22 22:13:29 +04:00
John Preston
3fb0fa6892 Swap confirm and password boxes on export. 2025-01-22 22:13:28 +04:00
John Preston
c3195cfcbe Improve convert-to-stars message in channel gift. 2025-01-22 22:13:28 +04:00
John Preston
51661a872c Fix channel unique gift transfer. 2025-01-22 22:13:28 +04:00
John Preston
64706ea103 Add confirmation for go-to-Fragment. 2025-01-22 22:13:28 +04:00
John Preston
ec69d557dc Add channel gifts filter. 2025-01-22 22:13:28 +04:00
John Preston
addd37fb1f Make channel gifts wearing / transferring. 2025-01-22 22:13:28 +04:00
John Preston
9dc947ecb6 Simplify run-in-background option selection. 2025-01-22 22:13:28 +04:00
John Preston
7d74d3da3a Remove dnd/focus query code on macOS. 2025-01-22 22:13:28 +04:00
John Preston
aa0c56876c Use sound in native notifications on macOS. 2025-01-22 22:13:28 +04:00
John Preston
37c7b0c6d1 Allow exporting unique gifts to blockchain. 2025-01-22 22:13:28 +04:00
John Preston
0d07d238bc Allow converting channel gifts to stars. 2025-01-22 22:13:28 +04:00
John Preston
fa4e74ffef Show and edit channel gifts notify settings. 2025-01-22 22:13:28 +04:00
John Preston
c22d76e5be Show only channel userpic in RTMP livestreams. 2025-01-22 22:13:28 +04:00
John Preston
18850ebd83 Gradient on premium gift discount badge. 2025-01-22 22:13:28 +04:00
John Preston
17abef95eb Build FFmpeg with WAV encoder/muxer. 2025-01-22 22:13:28 +04:00
John Preston
d135151477 Use native sound support in macOS notifications. 2025-01-22 22:13:28 +04:00
John Preston
07fd9b3074 Show nice collectible tooltip on wearing. 2025-01-22 22:13:27 +04:00
John Preston
0523ae705a Fix invisible chat type filter. 2025-01-22 22:13:27 +04:00
John Preston
9db2502cd0 Preserve formatting when editing links.
Fixes #28170.
2025-01-22 22:13:27 +04:00
John Preston
a174119877 Update API scheme on layer 198. 2025-01-22 22:13:27 +04:00
John Preston
569dd19932 Fix build on Xcode. 2025-01-22 22:13:27 +04:00
John Preston
530e2a1feb Support background-run options on Linux. 2025-01-22 22:13:27 +04:00
John Preston
de732ba692 Update icons for collectible wearing. 2025-01-22 22:13:27 +04:00
John Preston
c6649e84a6 Update patches for Qt 5.15. 2025-01-22 22:13:27 +04:00
John Preston
e3517aceab Improve stars in collectible emoji status. 2025-01-22 22:13:27 +04:00
John Preston
6d7abd1718 Don't use double-badge in profile, use two. 2025-01-22 22:13:27 +04:00
John Preston
e9e493707b Show left / sold gifts counts. 2025-01-22 22:13:27 +04:00
John Preston
c25adf8b57 Use a custom animated emoji for collectible status. 2025-01-22 22:13:27 +04:00
John Preston
d2be10cd4e Use "Display on my Page" big button. 2025-01-22 22:13:27 +04:00
John Preston
006ecf9a56 Fix giveaway results message sticker. 2025-01-22 22:13:27 +04:00
John Preston
a53cc52241 Ask for premium to wear a collectible. 2025-01-22 22:13:26 +04:00
John Preston
961d283325 Put on / Take off collectible wearable. 2025-01-22 22:13:26 +04:00
John Preston
4e46529eb6 Show gift on collectible status click. 2025-01-22 22:13:26 +04:00
John Preston
81001e04e9 Partially implement viewing channel gifts. 2025-01-22 22:13:26 +04:00
John Preston
2fd174ab9c Partially implement sending gifts to channels. 2025-01-22 22:13:26 +04:00
John Preston
6ff5e221ea Support similar bots section. 2025-01-22 22:13:26 +04:00
John Preston
232077b919 Allow wearing unique gifts as status. 2025-01-22 22:13:26 +04:00
John Preston
fecddb5203 Allow wearing collectibles from emoji status. 2025-01-22 22:13:26 +04:00
John Preston
d0132c0f7b Update API scheme to layer 198. 2025-01-22 22:13:26 +04:00
John Preston
37d32b32f8 Show gifts inside media view in stories. 2025-01-22 22:13:26 +04:00
John Preston
51213b499f Start showing gifts from stories. 2025-01-22 22:13:25 +04:00
John Preston
dcb98ce0fb WebPage open button doesn't inflate the preview. 2025-01-22 22:13:25 +04:00
John Preston
428e90a844 Add unique gifts link preview. 2025-01-22 22:13:25 +04:00
John Preston
017535cf7b Handle t.me/nft/slug links. 2025-01-22 22:13:25 +04:00
John Preston
409389a994 Add gift menu with Share and Transfer. 2025-01-22 22:13:25 +04:00
John Preston
ba34d92cd3 Update API scheme to layer 197. 2025-01-22 22:13:25 +04:00
23rd
b412ee258d Respected first day of week from system in calendar box. 2025-01-22 11:23:08 +03:00
23rd
f1ffe2a641 Added icon to webview buttons from bot keyboards. 2025-01-22 10:21:22 +03:00
23rd
a13ca95894 Added initial implementation of search in section for comments. 2025-01-18 01:10:28 +03:00
23rd
0989a80a57 Added ability to provide top msg id to api message search class. 2025-01-18 01:10:28 +03:00
23rd
d72c15e9d3 Added ability to set wallpaper for bots. 2025-01-17 22:08:48 +03:00
23rd
7084cf9526 Removed ability to set wallpaper for deleted accounts. 2025-01-17 22:08:48 +03:00
23rd
67cc0ef75c Removed call button from history with deleted account. 2025-01-17 22:08:48 +03:00
23rd
75714cc358 Fixed caption for personal photo in media viewer when it's single photo. 2025-01-17 22:08:48 +03:00
23rd
75e454f3fd Fixed blinking of message preview from user's personal channel. 2025-01-17 20:17:22 +03:00
23rd
97afb4e01a Added shortcut to section of public photo settings to self userpic. 2025-01-17 17:55:22 +03:00
23rd
8fcbf43410 Fixed display of reloaded boost statistics after buying of giveaway. 2025-01-17 16:52:30 +03:00
23rd
15a834b883 Fixed text color in badges from giveaway message stickers. 2025-01-17 16:37:25 +03:00
23rd
3d8396e586 Made less strict condition to show button of removed users in chat edit. 2025-01-17 16:28:11 +03:00
23rd
3257fd364a Replaced hardcoded threshold of gigagroup conversion with server value. 2025-01-17 16:09:36 +03:00
23rd
d690af99fc Fixed ripple color for top buttons in section of affiliate programs. 2025-01-17 15:45:59 +03:00
23rd
1c28495162 Fixed boost bubble position on resize container. 2025-01-17 12:46:21 +03:00
23rd
ec82d5674f Slightly improved style of section for boost statistics. 2025-01-14 20:15:11 +03:00
23rd
4a22e76bdb Fixed opening of chat export progress from history peer menu. 2025-01-14 20:15:11 +03:00
23rd
527be95618 Improved style of labels in top bar for chat export. 2025-01-14 20:15:11 +03:00
23rd
41985c0a5f Fixed ability to cancel of loading video as part of grouped media. 2025-01-09 22:52:33 +03:00
23rd
522e45ce92 Removed trial transcribe button from long voice messages. 2025-01-09 22:52:33 +03:00
545 changed files with 19371 additions and 6353 deletions

32
.devcontainer.json Normal file
View File

@@ -0,0 +1,32 @@
{
"name": "CentOS",
"image": "tdesktop:centos_env",
"customizations": {
"vscode": {
"settings": {
"C_Cpp.intelliSenseEngine": "disabled",
"clangd.arguments": [
"--compile-commands-dir=${workspaceFolder}/out"
],
"cmake.generator": "Ninja Multi-Config",
"cmake.buildDirectory": "${workspaceFolder}/out"
},
"extensions": [
"ms-vscode.cpptools-extension-pack",
"llvm-vs-code-extensions.vscode-clangd",
"TheQtCompany.qt",
"ms-python.python",
"ms-azuretools.vscode-docker",
"eamodio.gitlens"
]
}
},
"capAdd": [
"SYS_PTRACE"
],
"securityOpt": [
"seccomp=unconfined"
],
"workspaceMount": "source=${localWorkspaceFolder},target=/usr/src/tdesktop,type=bind,consistency=cached",
"workspaceFolder": "/usr/src/tdesktop"
}

View File

@@ -83,6 +83,7 @@ jobs:
fi
docker run --rm \
-u $(id -u) \
-v $PWD:/usr/src/tdesktop \
-e CONFIG=Debug \
tdesktop:centos_env \
@@ -114,8 +115,8 @@ jobs:
if: env.UPLOAD_ARTIFACT == 'true'
run: |
cd $REPO_NAME/out/Debug
sudo mkdir artifact
sudo mv {Telegram,Updater} artifact/
mkdir artifact
mv {Telegram,Updater} artifact/
- uses: actions/upload-artifact@v4
if: env.UPLOAD_ARTIFACT == 'true'
name: Upload artifact.

1
.gitignore vendored
View File

@@ -19,6 +19,7 @@ Release/
ipch/
.vs/
.vscode/
.cache/
/Telegram/log.txt
/Telegram/data

View File

@@ -919,6 +919,8 @@ PRIVATE
history/history_unread_things.h
history/history_view_highlight_manager.cpp
history/history_view_highlight_manager.h
history/history_view_swipe_back_session.cpp
history/history_view_swipe_back_session.h
history/history_widget.cpp
history/history_widget.h
info/bot/earn/info_bot_earn_list.cpp
@@ -959,6 +961,7 @@ PRIVATE
info/global_media/info_global_media_inner_widget.h
info/global_media/info_global_media_provider.cpp
info/global_media/info_global_media_provider.h
info/media/info_media_buttons.cpp
info/media/info_media_buttons.h
info/media/info_media_common.cpp
info/media/info_media_common.h
@@ -1014,8 +1017,8 @@ PRIVATE
info/saved/info_saved_sublists_widget.h
info/settings/info_settings_widget.cpp
info/settings/info_settings_widget.h
info/similar_channels/info_similar_channels_widget.cpp
info/similar_channels/info_similar_channels_widget.h
info/similar_peers/info_similar_peers_widget.cpp
info/similar_peers/info_similar_peers_widget.h
info/statistics/info_statistics_common.h
info/statistics/info_statistics_inner_widget.cpp
info/statistics/info_statistics_inner_widget.h
@@ -1135,6 +1138,8 @@ PRIVATE
media/audio/media_audio_loader.h
media/audio/media_audio_loaders.cpp
media/audio/media_audio_loaders.h
media/audio/media_audio_local_cache.cpp
media/audio/media_audio_local_cache.h
media/audio/media_audio_track.cpp
media/audio/media_audio_track.h
media/audio/media_child_ffmpeg_loader.cpp
@@ -1203,6 +1208,8 @@ PRIVATE
media/streaming/media_streaming_video_track.h
media/view/media_view_group_thumbs.cpp
media/view/media_view_group_thumbs.h
media/view/media_view_open_common.cpp
media/view/media_view_open_common.h
media/view/media_view_overlay_opengl.cpp
media/view/media_view_overlay_opengl.h
media/view/media_view_overlay_raster.cpp
@@ -1221,7 +1228,6 @@ PRIVATE
media/view/media_view_playback_controls.h
media/view/media_view_playback_progress.cpp
media/view/media_view_playback_progress.h
media/view/media_view_open_common.h
media/system_media_controls_manager.h
media/system_media_controls_manager.cpp
menu/menu_antispam_validator.cpp
@@ -1471,6 +1477,8 @@ PRIVATE
settings/settings_privacy_security.h
settings/settings_scale_preview.cpp
settings/settings_scale_preview.h
settings/settings_shortcuts.cpp
settings/settings_shortcuts.h
settings/settings_type.h
settings/settings_websites.cpp
settings/settings_websites.h
@@ -2055,14 +2063,16 @@ if (LINUX AND DESKTOP_APP_USE_PACKAGED)
configure_file("../lib/xdg/org.telegram.desktop.metainfo.xml" "${CMAKE_CURRENT_BINARY_DIR}/org.telegram.desktop.metainfo.xml" @ONLY)
generate_appdata_changelog(Telegram "${CMAKE_SOURCE_DIR}/changelog.txt" "${CMAKE_CURRENT_BINARY_DIR}/org.telegram.desktop.metainfo.xml")
install(TARGETS Telegram RUNTIME DESTINATION "${CMAKE_INSTALL_BINDIR}" BUNDLE DESTINATION "${CMAKE_INSTALL_BINDIR}")
install(FILES "Resources/art/icon16.png" DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/icons/hicolor/16x16/apps" RENAME "telegram.png")
install(FILES "Resources/art/icon32.png" DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/icons/hicolor/32x32/apps" RENAME "telegram.png")
install(FILES "Resources/art/icon48.png" DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/icons/hicolor/48x48/apps" RENAME "telegram.png")
install(FILES "Resources/art/icon64.png" DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/icons/hicolor/64x64/apps" RENAME "telegram.png")
install(FILES "Resources/art/icon128.png" DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/icons/hicolor/128x128/apps" RENAME "telegram.png")
install(FILES "Resources/art/icon256.png" DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/icons/hicolor/256x256/apps" RENAME "telegram.png")
install(FILES "Resources/art/icon512.png" DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/icons/hicolor/512x512/apps" RENAME "telegram.png")
install(FILES "Resources/icons/tray_monochrome.svg" DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/icons/hicolor/symbolic/apps" RENAME "telegram-symbolic.svg")
install(FILES "Resources/art/icon16.png" DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/icons/hicolor/16x16/apps" RENAME "org.telegram.desktop.png")
install(FILES "Resources/art/icon32.png" DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/icons/hicolor/32x32/apps" RENAME "org.telegram.desktop.png")
install(FILES "Resources/art/icon48.png" DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/icons/hicolor/48x48/apps" RENAME "org.telegram.desktop.png")
install(FILES "Resources/art/icon64.png" DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/icons/hicolor/64x64/apps" RENAME "org.telegram.desktop.png")
install(FILES "Resources/art/icon128.png" DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/icons/hicolor/128x128/apps" RENAME "org.telegram.desktop.png")
install(FILES "Resources/art/icon256.png" DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/icons/hicolor/256x256/apps" RENAME "org.telegram.desktop.png")
install(FILES "Resources/art/icon512.png" DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/icons/hicolor/512x512/apps" RENAME "org.telegram.desktop.png")
install(FILES "Resources/icons/tray_monochrome.svg" DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/icons/hicolor/symbolic/apps" RENAME "org.telegram.desktop-symbolic.svg")
install(FILES "Resources/icons/tray_monochrome_attention.svg" DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/icons/hicolor/symbolic/apps" RENAME "org.telegram.desktop-attention-symbolic.svg")
install(FILES "Resources/icons/tray_monochrome_mute.svg" DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/icons/hicolor/symbolic/apps" RENAME "org.telegram.desktop-mute-symbolic.svg")
install(FILES "../lib/xdg/org.telegram.desktop.desktop" DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/applications")
install(FILES "${CMAKE_CURRENT_BINARY_DIR}/org.telegram.desktop.service" DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/dbus-1/services")
install(FILES "${CMAKE_CURRENT_BINARY_DIR}/org.telegram.desktop.metainfo.xml" DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/metainfo")

View File

@@ -1,6 +1,7 @@
// This is a list of your own shortcuts for Telegram Desktop
// You can see full list of commands in the 'shortcuts-default.json' file
// Place a null value instead of a command string to switch the shortcut off
// You can also edit them in Settings > Chat Settings > Keyboard Shortcuts.
[
// {

Binary file not shown.

After

Width:  |  Height:  |  Size: 663 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 761 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 311 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 578 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 829 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 730 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 716 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 447 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 671 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1004 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 370 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 712 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 926 B

View File

@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="16px" height="16px" viewBox="0 0 16 16" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<g id="plane" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<path d="M1.3311718,6.36592184 C5.3576954,4.67244493 8.04267511,3.5560013 9.38611094,3.01659096 C13.2218932,1.47646481 14.0189359,1.2089284 14.5384372,1.2 C14.6526967,1.19815119 14.9081723,1.22548649 15.0736587,1.35511219 C15.2133922,1.4645656 15.2518384,1.61242159 15.2702362,1.71619544 C15.288634,1.81996929 15.3115436,2.05636876 15.2933322,2.24108442 C15.0854698,4.34939964 14.1860526,9.46572464 13.7284802,11.8270738 C13.5348641,12.8262491 13.1536281,13.1612675 12.7845475,13.1940535 C11.9824498,13.265305 11.3733733,12.6823476 10.5965026,12.190753 C9.3808532,11.4215044 8.69408865,10.9426448 7.51409044,10.1920004 C6.15039834,9.32450079 7.03442319,8.84770795 7.81158733,8.06849502 C8.01497489,7.86457129 11.5490353,4.7615061 11.6174372,4.48000946 C11.625992,4.44480359 11.6339313,4.31357282 11.5531696,4.24427815 C11.472408,4.17498349 11.3532107,4.19867957 11.2671947,4.21752527 C11.1452695,4.24423848 9.20325394,5.48334063 5.44114787,7.93483171 C4.88991321,8.30022994 4.39062196,8.47826423 3.94327414,8.46893456 C3.45010907,8.45864936 2.50145729,8.19975808 1.79623221,7.97846422 C0.931244952,7.70703829 0.243770289,7.56353344 0.303633888,7.10256824 C0.334814555,6.86246904 0.677327192,6.61692024 1.3311718,6.36592184 Z" id="Path-3" fill="#FFFFFF"></path>
</g>
<circle class="error" fill="#f23c34" cx="3.9" cy="12.7" r="2.2"/>
</svg>

After

Width:  |  Height:  |  Size: 1.6 KiB

View File

@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="16px" height="16px" viewBox="0 0 16 16" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<g id="plane" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<path d="M1.3311718,6.36592184 C5.3576954,4.67244493 8.04267511,3.5560013 9.38611094,3.01659096 C13.2218932,1.47646481 14.0189359,1.2089284 14.5384372,1.2 C14.6526967,1.19815119 14.9081723,1.22548649 15.0736587,1.35511219 C15.2133922,1.4645656 15.2518384,1.61242159 15.2702362,1.71619544 C15.288634,1.81996929 15.3115436,2.05636876 15.2933322,2.24108442 C15.0854698,4.34939964 14.1860526,9.46572464 13.7284802,11.8270738 C13.5348641,12.8262491 13.1536281,13.1612675 12.7845475,13.1940535 C11.9824498,13.265305 11.3733733,12.6823476 10.5965026,12.190753 C9.3808532,11.4215044 8.69408865,10.9426448 7.51409044,10.1920004 C6.15039834,9.32450079 7.03442319,8.84770795 7.81158733,8.06849502 C8.01497489,7.86457129 11.5490353,4.7615061 11.6174372,4.48000946 C11.625992,4.44480359 11.6339313,4.31357282 11.5531696,4.24427815 C11.472408,4.17498349 11.3532107,4.19867957 11.2671947,4.21752527 C11.1452695,4.24423848 9.20325394,5.48334063 5.44114787,7.93483171 C4.88991321,8.30022994 4.39062196,8.47826423 3.94327414,8.46893456 C3.45010907,8.45864936 2.50145729,8.19975808 1.79623221,7.97846422 C0.931244952,7.70703829 0.243770289,7.56353344 0.303633888,7.10256824 C0.334814555,6.86246904 0.677327192,6.61692024 1.3311718,6.36592184 Z" id="Path-3" fill="#FFFFFF"></path>
</g>
<circle fill="#888888" cx="3.9" cy="12.7" r="2.2"/>
</svg>

After

Width:  |  Height:  |  Size: 1.6 KiB

View File

@@ -604,6 +604,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_settings_update_fail" = "Update check failed :(";
"lng_settings_workmode_tray" = "Show tray icon";
"lng_settings_workmode_window" = "Show taskbar icon";
"lng_settings_window_close" = "When window closed";
"lng_settings_run_in_background" = "Run in the background";
"lng_settings_quit_on_close" = "Quit the application";
"lng_settings_close_to_taskbar" = "Close to taskbar";
"lng_settings_monochrome_icon" = "Use monochrome icon";
"lng_settings_window_system" = "Window title bar";
@@ -639,6 +642,47 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_settings_chat_quick_action_react" = "Send reaction with double click";
"lng_settings_chat_corner_reaction" = "Reaction button on messages";
"lng_settings_shortcuts" = "Keyboard shortcuts";
"lng_shortcuts_reset" = "Reset to default";
"lng_shortcuts_recording" = "Recording...";
"lng_shortcuts_add_another" = "Add another";
"lng_shortcuts_close" = "Close the window";
"lng_shortcuts_lock" = "Lock the application";
"lng_shortcuts_minimize" = "Minimize the window";
"lng_shortcuts_quit" = "Quit the application";
"lng_shortcuts_media_play" = "Play the media";
"lng_shortcuts_media_pause" = "Pause the media";
"lng_shortcuts_media_play_pause" = "Toggle media playback";
"lng_shortcuts_media_stop" = "Stop media playback";
"lng_shortcuts_media_previous" = "Previous track";
"lng_shortcuts_media_next" = "Next track";
"lng_shortcuts_search" = "Search messages";
"lng_shortcuts_chat_previous" = "Previous chat";
"lng_shortcuts_chat_next" = "Next chat";
"lng_shortcuts_chat_first" = "First chat";
"lng_shortcuts_chat_last" = "Last chat";
"lng_shortcuts_chat_self" = "Saved Messages";
"lng_shortcuts_chat_pinned_n" = "Pinned chat #{index}";
"lng_shortcuts_show_account_n" = "Account #{index}";
"lng_shortcuts_show_all_chats" = "All Chats folder";
"lng_shortcuts_show_folder_n" = "Folder #{index}";
"lng_shortcuts_show_folder_last" = "Last folder";
"lng_shortcuts_folder_next" = "Next folder";
"lng_shortcuts_folder_previous" = "Previous folder";
"lng_shortcuts_scheduled" = "Scheduled messages";
"lng_shortcuts_archive" = "Archived chats";
"lng_shortcuts_contacts" = "Contacts list";
"lng_shortcuts_just_send" = "Just send";
"lng_shortcuts_silent_send" = "Silent send";
"lng_shortcuts_schedule" = "Schedule";
"lng_shortcuts_read_chat" = "Mark chat as read";
"lng_shortcuts_archive_chat" = "Archive chat";
"lng_shortcuts_media_fullscreen" = "Toggle video fullscreen";
"lng_shortcuts_show_chat_menu" = "Show chat menu";
"lng_shortcuts_show_chat_preview" = "Show chat preview";
"lng_settings_chat_reactions_title" = "Quick Reaction";
"lng_settings_chat_reactions_subtitle" = "Choose your favorite reaction";
"lng_settings_chat_message_reply_from" = "Bob Harris";
@@ -1175,6 +1219,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_edit_privacy_contacts" = "My contacts";
"lng_edit_privacy_close_friends" = "Close friends";
"lng_edit_privacy_contacts_and_premium" = "Contacts & Premium";
"lng_edit_privacy_paid" = "Paid";
"lng_edit_privacy_contacts_and_miniapps" = "Contacts & Mini Apps";
"lng_edit_privacy_nobody" = "Nobody";
"lng_edit_privacy_premium" = "Premium users";
@@ -1313,6 +1358,13 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_messages_privacy_premium_about" = "Subscribe now to change this setting and get access to other exclusive features of Telegram Premium.";
"lng_messages_privacy_premium" = "Only subscribers of {link} can select this option.";
"lng_messages_privacy_premium_link" = "Telegram Premium";
"lng_messages_privacy_charge" = "Charge for messages";
"lng_messages_privacy_charge_about" = "Charge a fee for messages from people outside your contacts or those you haven't messaged first.";
"lng_messages_privacy_price" = "Set your price per message";
"lng_messages_privacy_price_about" = "You will receive {percent} of the selected fee ({amount}) for each incoming message.";
"lng_messages_privacy_exceptions" = "Exceptions";
"lng_messages_privacy_remove_fee" = "Remove Fee";
"lng_messages_privacy_remove_about" = "Add users or entire groups who won't be charged for sending messages to you.";
"lng_self_destruct_title" = "Account self-destruction";
"lng_self_destruct_description" = "If you don't come online at least once within this period, your account will be deleted along with all groups, messages and contacts.";
@@ -1346,6 +1398,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_profile_common_groups#other" = "{count} groups in common";
"lng_profile_similar_channels#one" = "{count} similar channel";
"lng_profile_similar_channels#other" = "{count} similar channels";
"lng_profile_similar_bots#one" = "{count} similar bot";
"lng_profile_similar_bots#other" = "{count} similar bots";
"lng_profile_saved_messages#one" = "{count} saved message";
"lng_profile_saved_messages#other" = "{count} saved messages";
"lng_profile_peer_gifts#one" = "{count} gift";
@@ -2020,16 +2074,28 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_action_gift_sent" = "You sent a gift for {cost}";
"lng_action_gift_unique_sent" = "You sent a unique collectible item";
"lng_action_gift_upgraded" = "{user} turned the gift from you into a unique collectible";
"lng_action_gift_upgraded_channel" = "{user} turned this gift to {channel} into a unique collectible";
"lng_action_gift_upgraded_self_channel" = "You turned this gift to {channel} into a unique collectible";
"lng_action_gift_upgraded_mine" = "You turned the gift from {user} into a unique collectible";
"lng_action_gift_upgraded_self" = "You turned this gift into a unique collectible";
"lng_action_gift_transferred" = "{user} transferred you a gift";
"lng_action_gift_transferred_channel" = "{user} transferred a gift to {channel}";
"lng_action_gift_transferred_unknown" = "Someone transferred you a gift";
"lng_action_gift_transferred_unknown_channel" = "Someone transferred a gift to {channel}";
"lng_action_gift_transferred_self" = "You transferred a unique collectible";
"lng_action_gift_transferred_self_channel" = "You transferred a gift to {channel}";
"lng_action_gift_transferred_mine" = "You transferred a gift to {user}";
"lng_action_gift_received_anonymous" = "Unknown user sent you a gift for {cost}";
"lng_action_gift_sent_channel" = "{user} sent a gift to {name} for {cost}";
"lng_action_gift_sent_self_channel" = "You sent a gift to {name} for {cost}";
"lng_action_gift_self_bought" = "You bought a gift for {cost}";
"lng_action_gift_self_subtitle" = "Saved Gift";
"lng_action_gift_self_about#one" = "Display this gift on your page or convert it to **{count}** Star.";
"lng_action_gift_self_about#other" = "Display this gift on your page or convert it to **{count}** Stars.";
"lng_action_gift_self_about_unique" = "You can display this gift on your page or turn it into unique collectible and send to others.";
"lng_action_gift_channel_about#one" = "Display this gift in channel's Gifts or convert it to **{count}** Star.";
"lng_action_gift_channel_about#other" = "Display this gift in channel's Gifts or convert it to **{count}** Stars.";
"lng_action_gift_channel_about_unique" = "You can display this gift in channel's Gifts or turn it into unique collectible.";
"lng_action_gift_for_stars#one" = "{count} Star";
"lng_action_gift_for_stars#other" = "{count} Stars";
"lng_action_gift_got_subtitle" = "Gift from {user}";
@@ -2038,6 +2104,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_action_gift_got_upgradable_text" = "Upgrade this gift to a unique collectible.";
"lng_action_gift_got_gift_text" = "You can keep this gift on your page.";
"lng_action_gift_can_remove_text" = "You can remove this gift from your page.";
"lng_action_gift_got_gift_channel" = "You can keep this gift in channel's Gifts.";
"lng_action_gift_can_remove_channel" = "You can remove this gift from channel's Gifts.";
"lng_action_gift_sent_subtitle" = "Gift for {user}";
"lng_action_gift_sent_text#one" = "{user} can display this gift on their page or convert it to {count} Star.";
"lng_action_gift_sent_text#other" = "{user} can display this gift on their page or convert it to {count} Stars.";
@@ -2099,6 +2167,15 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_action_boost_apply#other" = "{from} boosted the group {count} times";
"lng_action_set_chat_intro" = "{from} added the message below for all empty chats. How?";
"lng_action_payment_refunded" = "{peer} refunded {amount}";
"lng_action_paid_message_sent#one" = "You paid {count} Star to {action}";
"lng_action_paid_message_sent#other" = "You paid {count} Stars to {action}";
"lng_action_paid_message_one" = "send a message";
"lng_action_paid_message_some#one" = "send {count} message";
"lng_action_paid_message_some#other" = "send {count} messages";
"lng_action_paid_message_got#one" = "You received {count} Star from {name}";
"lng_action_paid_message_got#other" = "You received {count} Stars from {name}";
"lng_you_paid_stars#one" = "You paid {count} Star.";
"lng_you_paid_stars#other" = "You paid {count} Stars.";
"lng_similar_channels_title" = "Similar channels";
"lng_similar_channels_view_all" = "View all";
@@ -2108,9 +2185,23 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_similar_channels_premium_all_link" = "Telegram Premium";
"lng_similar_channels_show_more" = "Show more channels";
"lng_similar_bots_title" = "Similar bots";
"lng_similar_bots_premium_all#one" = "Subscribe to {link} to unlock up to **{count}** similar bot.";
"lng_similar_bots_premium_all#other" = "Subscribe to {link} to unlock up to **{count}** similar bots.";
"lng_similar_bots_show_more" = "Show more bots";
"lng_peer_gifts_title" = "Gifts";
"lng_peer_gifts_about" = "These gifts were sent to {user} by other users.";
"lng_peer_gifts_about_mine" = "These gifts were sent to you by other users. Click on a gift to convert it to Stars or change its privacy settings.";
"lng_peer_gifts_notify" = "Notify About New Gifts";
"lng_peer_gifts_notify_enabled" = "You will receive a message from Telegram when your channel receives a gift.";
"lng_peer_gifts_filter_by_value" = "Sort by Value";
"lng_peer_gifts_filter_by_date" = "Sort by Date";
"lng_peer_gifts_filter_unlimited" = "Unlimited";
"lng_peer_gifts_filter_limited" = "Limited";
"lng_peer_gifts_filter_unique" = "Unique";
"lng_peer_gifts_filter_saved" = "Displayed";
"lng_peer_gifts_filter_unsaved" = "Hidden";
"lng_premium_gift_duration_months#one" = "for {count} month";
"lng_premium_gift_duration_months#other" = "for {count} months";
@@ -2373,6 +2464,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_group_stickers_add" = "Choose sticker set";
"lng_group_emoji" = "Select Emoji Pack";
"lng_group_emoji_description" = "Choose an emoji pack that will be available to all members within the group.";
"lng_collectible_emoji" = "Collectibles";
"lng_premium" = "Premium";
"lng_premium_free" = "Free";
@@ -2517,6 +2609,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_premium_double_limits_subtitle_accounts" = "Connected Accounts";
"lng_premium_double_limits_about_accounts#one" = "Connect {count} account with different mobile numbers";
"lng_premium_double_limits_about_accounts#other" = "Connect {count} accounts with different mobile numbers";
"lng_premium_double_limits_subtitle_similar_channels" = "Similar Channel";
"lng_premium_double_limits_about_similar_channels#one" = "View up to {count} similar channel";
"lng_premium_double_limits_about_similar_channels#other" = "View up to {count} similar channels";
//
"lng_premium_gift_title" = "Gift Telegram Premium";
@@ -2586,6 +2682,12 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_credits_summary_history_entry_inner_in" = "In-App Purchase";
"lng_credits_summary_balance" = "Balance";
"lng_credits_commission" = "{amount} commission";
"lng_credits_paid_messages_fee#one" = "Fee for {count} Message";
"lng_credits_paid_messages_fee#other" = "Fee for {count} Messages";
"lng_credits_paid_messages_fee_about" = "You receive {percent} of the price that you charge for each incoming message. {link}";
"lng_credits_paid_messages_fee_about_link" = "Change Fee {emoji}";
"lng_credits_paid_messages_full" = "Full Price";
"lng_credits_premium_gift_duration" = "Duration";
"lng_credits_more_options" = "More Options";
"lng_credits_balance_me" = "your balance";
"lng_credits_buy_button" = "Buy More Stars";
@@ -2699,6 +2801,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"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_star_gift" = "Buy **Stars** to send gifts to {user} and other contacts.";
"lng_credits_small_balance_for_message" = "Buy **Stars** to send messages to {user}.";
"lng_credits_small_balance_for_messages" = "Buy **Stars** to send messages.";
"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}";
@@ -2960,11 +3064,17 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_boost_group_needs_level_emoji#one" = "Your group needs to reach **Level {count}** to set emoji pack.";
"lng_boost_group_needs_level_emoji#other" = "Your group needs to reach **Level {count}** to set emoji pack.";
"lng_boost_channel_title_wear" = "Wear Item";
"lng_boost_channel_needs_level_wear#one" = "Your channel needs **Level {count}** to wear collectibles.";
"lng_boost_channel_needs_level_wear#other" = "Your channel needs **Level {count}** to wear collectibles.";
"lng_boost_channel_ask" = "Ask your **Premium** subscribers to boost your channel with this link:";
"lng_boost_channel_ask_button" = "Copy Link";
"lng_boost_channel_or" = "or";
"lng_boost_channel_gifting" = "Boost your channel by gifting your subscribers Telegram Premium. {link}";
"lng_boost_channel_gifting_link" = "Get boosts >";
//"lng_boost_channel_or" = "or";
//"lng_boost_channel_gifting" = "Boost your channel by gifting your subscribers Telegram Premium. {link}";
//"lng_boost_channel_gifting_link" = "Get boosts >";
"lng_boost_group_ask" = "Ask your **Premium** members to boost your group with this link:";
//"lng_boost_group_gifting" = "Boost your group by gifting your members Telegram Premium. {link}";
"lng_feature_stories#one" = "**{count}** Story Per Day";
"lng_feature_stories#other" = "**{count}** Stories Per Day";
@@ -3226,6 +3336,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_gift_premium_about" = "Give {name} access to exclusive features with Telegram Premium. {features}";
"lng_gift_premium_features" = "See Features >";
"lng_gift_premium_label" = "Premium";
"lng_gift_premium_by_stars" = "or {amount}";
"lng_gift_stars_subtitle" = "Gift Stars";
"lng_gift_stars_about" = "Give {name} gifts that can be kept on your profile or converted to Stars. {link}";
"lng_gift_stars_link" = "What are Stars >";
@@ -3237,12 +3348,22 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_gift_send_title" = "Send a Gift";
"lng_gift_send_message" = "Enter Message";
"lng_gift_send_anonymous" = "Hide My Name";
"lng_gift_send_pay_with_stars" = "Pay with {amount}";
"lng_gift_send_stars_balance" = "Your balance is {amount}. {link}";
"lng_gift_send_stars_balance_link" = "Get More Stars >";
"lng_gift_send_anonymous_self" = "Hide my name and message from visitors to my profile.";
"lng_gift_send_anonymous_about" = "You can hide your name and message from visitors to {user}'s profile. {recipient} will still see your name and message.";
"lng_gift_send_anonymous_about_paid" = "You can hide your name from visitors to {user}'s profile. {recipient} will still see your name.";
"lng_gift_send_anonymous_about_channel" = "You can hide your name and message from all visitors of this channel except its admins.";
"lng_gift_send_unique" = "Make Unique for {price}";
"lng_gift_send_unique_about" = "Enable this to let {user} turn your gift into a unique collectible. {link}";
"lng_gift_send_unique_about_channel" = "Enable this to let the admins of {name} turn your gift into a unique collectible. {link}";
"lng_gift_send_unique_link" = "Learn More >";
"lng_gift_send_premium_about" = "Only {user} will see your message.";
"lng_gift_send_limited_sold#one" = "{count} sold";
"lng_gift_send_limited_sold#other" = "{count} sold";
"lng_gift_send_limited_left#one" = "{count} left";
"lng_gift_send_limited_left#other" = "{count} left";
"lng_gift_send_button" = "Send a Gift for {cost}";
"lng_gift_send_button_self" = "Buy a Gift for {cost}";
"lng_gift_sent_title" = "Gift Sent!";
@@ -3254,20 +3375,26 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_gift_price_unique" = "Unique";
"lng_gift_view_unpack" = "Unpack";
"lng_gift_anonymous_hint" = "Only you can see the sender's name.";
"lng_gift_anonymous_hint_channel" = "Only admins of this channel can see the sender's name.";
"lng_gift_hidden_hint" = "This gift is hidden. Only you can see it.";
"lng_gift_visible_hint" = "This gift is visible to visitors of your page.";
"lng_gift_hidden_unique" = "This gift is not displayed on your page.";
"lng_gift_visible_hint" = "This gift is visible on your page.";
"lng_gift_hidden_hint_channel" = "This gift is hidden from visitors of your channel.";
"lng_gift_visible_hint_channel" = "This gift is visible in your channel's Gifts.";
"lng_gift_in_blockchain" = "This gift is in TON blockchain. {link}";
"lng_gift_in_blockchain_link" = "View >";
"lng_gift_visible_hide" = "Hide >";
"lng_gift_show_on_page" = "Display on my Page";
"lng_gift_show_on_channel" = "Display in channel's Gifts";
"lng_gift_availability" = "Availability";
"lng_gift_from_hidden" = "Hidden User";
"lng_gift_visibility" = "Visibility";
"lng_gift_visibility_shown" = "Visible on your page";
"lng_gift_visibility_hidden" = "Not visible on your page";
"lng_gift_visibility_show" = "show";
"lng_gift_visibility_hide" = "hide";
"lng_gift_self_status" = "buy yourself a gift";
"lng_gift_self_title" = "Buy a Gift";
"lng_gift_self_about" = "Buy yourself a gift to display on your page or reserve for later.\n\nLimited-edition gifts upgraded to collectibles can be gifted to others later.";
"lng_gift_channel_title" = "Send a Gift";
"lng_gift_channel_about" = "Select a gift to show appreciation for {name}.";
"lng_gift_unique_owner" = "Owner";
"lng_gift_unique_owner_change" = "change";
"lng_gift_unique_address_copied" = "Address copied to clipboard.";
"lng_gift_unique_status" = "Status";
"lng_gift_unique_status_non" = "Non-Unique";
"lng_gift_unique_status_upgrade" = "upgrade";
@@ -3276,6 +3403,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_gift_unique_backdrop" = "Backdrop";
"lng_gift_unique_symbol" = "Symbol";
"lng_gift_unique_rarity" = "Only {percent} of such collectibles have this attribute.";
"lng_gift_unique_availability_label" = "Quantity";
"lng_gift_unique_availability#one" = "{count} of {amount} issued";
"lng_gift_unique_availability#other" = "{count} of {amount} issued";
"lng_gift_unique_info" = "Gifted to {recipient} on {date}.";
@@ -3291,14 +3419,21 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_gift_convert_sure_title" = "Convert Gift to Stars";
"lng_gift_convert_sure_confirm#one" = "Do you want to convert this gift from {user} to **{count} Star**?";
"lng_gift_convert_sure_confirm#other" = "Do you want to convert this gift from {user} to **{count} Stars**?";
"lng_gift_convert_sure_confirm_channel#one" = "Do you want to convert this gift to {channel} to **{count} Star**?";
"lng_gift_convert_sure_confirm_channel#other" = "Do you want to convert this gift to {channel} to **{count} Stars**?";
"lng_gift_convert_sure_limit#one" = "Conversion is available for the next **{count} day**.";
"lng_gift_convert_sure_limit#other" = "Conversion is available for the next **{count} days**.";
"lng_gift_convert_sure_caution" = "This action cannot be undone. This will permanently destroy the gift.";
"lng_gift_convert_sure" = "Convert";
"lng_gift_display_done" = "The gift is now shown on your profile page.";
"lng_gift_display_done_channel" = "The gift is now shown in channel's Gifts.";
"lng_gift_display_done_hide" = "The gift is now hidden from your profile page.";
"lng_gift_display_done_hide_channel" = "The gift is now hidden from channel's Gifts.";
"lng_gift_pinned_done" = "The gift will always be shown on top.";
"lng_gift_got_stars#one" = "You got **{count} Star** for this gift.";
"lng_gift_got_stars#other" = "You got **{count} Stars** for this gift.";
"lng_gift_channel_got#one" = "Channel got **{count} Star** for this gift.";
"lng_gift_channel_got#other" = "Channel got **{count} Stars** for this gift.";
"lng_gift_sold_out_title" = "Sold Out!";
"lng_gift_sold_out_text#one" = "All {count} gift was already sold.";
"lng_gift_sold_out_text#other" = "All {count} gifts were already sold.";
@@ -3309,6 +3444,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_gift_upgrade_about" = "Turn your gift into a unique collectible\nthat you can transfer or auction.";
"lng_gift_upgrade_preview_title" = "Make Unique";
"lng_gift_upgrade_preview_about" = "Let {name} turn your gift into a unique collectible.";
"lng_gift_upgrade_preview_about_channel" = "Let the admins of {name} turn your gift into a unique collectible.";
"lng_gift_upgrade_unique_title" = "Unique";
"lng_gift_upgrade_unique_about" = "Get a unique number, model, backdrop and symbol for your gift.";
"lng_gift_upgrade_transferable_title" = "Transferable";
@@ -3328,6 +3464,12 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_gift_transferred_about" = "{name} was successfully transferred to {recipient}.";
"lng_gift_transfer_title" = "Transfer {name}";
"lng_gift_transfer_via_blockchain" = "Send via Blockchain";
"lng_gift_transfer_password_title" = "Two-step verification";
"lng_gift_transfer_password_description" = "Please enter your password to transfer.";
"lng_gift_transfer_password_about" = "You can withdraw only if you have:";
"lng_gift_transfer_confirm_title" = "Manage with Fragment";
"lng_gift_transfer_confirm_text" = "You can use Fragment, a third-party service, to transfer {name} to your TON account. After that, you can manage it as an NFT with any TON wallet outside Telegram.\n\nYou can also move such NFTs back to your Telegram account via Fragment.";
"lng_gift_transfer_confirm_button" = "Open Fragment";
"lng_gift_transfer_unlocks_days#one" = "unlocks in {count} day";
"lng_gift_transfer_unlocks_days#other" = "unlocks in {count} days";
"lng_gift_transfer_unlocks_hours#one" = "unlocks in {count} hour";
@@ -3344,6 +3486,22 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_gift_transfer_sure_for" = "Do you want to transfer ownership of {name} to {recipient} for {price}?";
"lng_gift_transfer_button" = "Transfer";
"lng_gift_transfer_button_for" = "Transfer for {price}";
"lng_gift_transfer_wear" = "Wear";
"lng_gift_transfer_take_off" = "Take Off";
"lng_gift_menu_show" = "Show";
"lng_gift_menu_hide" = "Hide";
"lng_gift_wear_title" = "Wear {name}";
"lng_gift_wear_about" = "and get these benefits:";
"lng_gift_wear_badge_title" = "Radiant Badge";
"lng_gift_wear_badge_about" = "The glittering icon of this item will be displayed next to your name.";
"lng_gift_wear_badge_about_channel" = "The glittering icon of this item will be displayed next to channel's name.";
"lng_gift_wear_proof_title" = "Proof of Ownership";
"lng_gift_wear_proof_about" = "Clicking the icon of this item next to your name will show its info and owner.";
"lng_gift_wear_proof_about_channel" = "Clicking the icon of this item next to channel's name will show its info and owner.";
"lng_gift_wear_start" = "Start Wearing";
"lng_gift_wear_subscribe" = "Subscribe to {link} to wear collectibles.";
"lng_gift_wear_start_toast" = "You put on {name}";
"lng_gift_wear_end_toast" = "You took off {name}";
"lng_accounts_limit_title" = "Limit Reached";
"lng_accounts_limit1#one" = "You have reached the limit of **{count}** connected account.";
@@ -3485,6 +3643,22 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_new_contact_from_request_group" = "{user} is an admin of {name}, a group you requested to join.";
"lng_new_contact_about_status" = "This account uses {emoji} as a custom status next to its\nname. Such emoji statuses are available to all\nsubscribers of {link}.";
"lng_new_contact_about_status_link" = "Telegram Premium";
"lng_new_contact_not_contact" = "Not a contact";
"lng_new_contact_phone_number" = "Phone number";
"lng_new_contact_registration" = "Registration";
"lng_new_contact_common_groups" = "Common groups";
"lng_new_contact_groups#one" = "{count} group {emoji} {arrow}";
"lng_new_contact_groups#other" = "{count} groups {emoji} {arrow}";
"lng_new_contact_not_official" = "Not an official account";
"lng_new_contact_updated_name" = "User updated name {when}";
"lng_new_contact_updated_photo" = "User updated photo {when}";
"lng_new_contact_updated_now" = "less than an hour ago";
"lng_new_contact_updated_hours#one" = "{count} hour ago";
"lng_new_contact_updated_hours#other" = "{count} hours ago";
"lng_new_contact_updated_days#one" = "{count} day ago";
"lng_new_contact_updated_days#other" = "{count} days ago";
"lng_new_contact_updated_months#one" = "{count} month ago";
"lng_new_contact_updated_months#other" = "{count} months ago";
"lng_from_request_title_channel" = "Response to your join request";
"lng_from_request_title_group" = "Response to your join request";
"lng_from_request_body" = "You received this message because you requested to join {name} on {date}.";
@@ -3513,6 +3687,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_send_anonymous_ph" = "Send anonymously...";
"lng_story_reply_ph" = "Reply privately...";
"lng_story_comment_ph" = "Comment story...";
"lng_message_paid_ph" = "Message for {amount}";
"lng_send_text_no" = "Text not allowed.";
"lng_send_text_no_about" = "The admins of this group only allow sending {types}.";
"lng_send_text_type_and_last" = "{types} and {last}";
@@ -3759,6 +3934,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_choose_image" = "Choose an image";
"lng_choose_file" = "Choose a file";
"lng_choose_files" = "Choose Files";
"lng_choose_cover" = "Choose video cover";
"lng_choose_cover_bad" = "Can't use this file as a caption.";
"lng_game_tag" = "Game";
"lng_context_new_window" = "Open in new window";
@@ -3875,6 +4052,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_context_read_show" = "show when";
"lng_context_edit_shortcut" = "Edit Shortcut";
"lng_context_delete_shortcut" = "Delete Quick Reply";
"lng_context_gift_send" = "Send Another Gift";
"lng_add_tag_about" = "Tag this message with an emoji for quick search.";
"lng_subscribe_tag_about" = "Organize your Saved Messages with tags. {link}";
@@ -3903,6 +4081,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_context_disable_spoiler" = "Remove Spoiler";
"lng_context_make_paid" = "Make This Content Paid";
"lng_context_change_price" = "Change Price";
"lng_context_edit_cover" = "Edit Cover";
"lng_context_clear_cover" = "Clear Cover";
"lng_context_mention" = "Mention";
"lng_context_search_from" = "Search messages";
@@ -4032,6 +4212,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_reply_cant_forward" = "Sorry, you can't reply to a message that was sent before the group was upgraded to a supergroup. Do you wish to forward it and add your comment?";
"lng_share_title" = "Share to";
"lng_share_at_time_title" = "Share at {time} to";
"lng_share_copy_link" = "Copy share link";
"lng_share_confirm" = "Send";
"lng_share_wrong_user" = "This game was opened from a different user.";
@@ -4155,7 +4336,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_media_save_progress" = "{ready} of {total} {mb}";
"lng_mediaview_save_as" = "Save As...";
"lng_mediaview_copy" = "Copy";
"lng_mediaview_copy_frame" = "Copy Frame";
"lng_mediaview_forward" = "Forward";
"lng_mediaview_share_at_time" = "Share at {time}";
"lng_mediaview_delete" = "Delete";
"lng_mediaview_save_to_profile" = "Post to Profile";
"lng_mediaview_pin_story_done" = "Story pinned";
@@ -4666,6 +4849,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_rights_boosts_no_restrict" = "Do not restrict boosters";
"lng_rights_boosts_about" = "Turn this on to always allow users who boosted your group to send messages and media.";
"lng_rights_boosts_about_on" = "Choose how many boosts a user must give to the group to bypass restrictions on sending messages.";
"lng_rights_charge_stars" = "Charge Stars for Messages";
"lng_rights_charge_stars_about" = "If you turn this on, regular members of the group will have to pay Stars to send messages.";
"lng_rights_charge_price" = "Set price per message";
"lng_rights_charge_price_about" = "Your group will receive {percent} of the selected fee ({amount}) for each incoming message.";
"lng_slowmode_enabled" = "Slow Mode is active.\nYou can send your next message in {left}.";
"lng_slowmode_no_many" = "Slow mode is enabled. You can't send more than one message at a time.";
@@ -4673,6 +4860,28 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_slowmode_seconds#one" = "{count} second";
"lng_slowmode_seconds#other" = "{count} seconds";
"lng_payment_confirm_title" = "Confirm payment";
"lng_payment_confirm_text#one" = "{name} charges **{count}** Star per message.";
"lng_payment_confirm_text#other" = "{name} charges **{count}** Stars per message.";
"lng_payment_confirm_amount#one" = "**{count}** Star";
"lng_payment_confirm_amount#other" = "**{count}** Stars";
"lng_payment_confirm_users#one" = "You selected **{count}** user who charge Stars for messages.";
"lng_payment_confirm_users#other" = "You selected **{count}** users who charge Stars for messages.";
"lng_payment_confirm_chats#one" = "You selected **{count}** chat where you pay Stars for messages.";
"lng_payment_confirm_chats#other" = "You selected **{count}** chats where you pay Stars for messages.";
"lng_payment_confirm_sure#one" = "Would you like to pay {amount} to send **{count}** message?";
"lng_payment_confirm_sure#other" = "Would you like to pay {amount} to send **{count}** messages?";
"lng_payment_confirm_dont_ask" = "Don't ask me again";
"lng_payment_confirm_button#one" = "Pay for {count} Message";
"lng_payment_confirm_button#other" = "Pay for {count} Messages";
"lng_payment_bar_text" = "{name} must pay {cost} for each message to you.";
"lng_payment_bar_button" = "Remove Fee";
"lng_payment_refund_title" = "Remove Fee";
"lng_payment_refund_text" = "Are you sure you want to allow {name} to message you for free?";
"lng_payment_refund_also#one" = "Refund already paid {count} Star";
"lng_payment_refund_also#other" = "Refund already paid {count} Stars";
"lng_payment_refund_confirm" = "Confirm";
"lng_rights_gigagroup_title" = "Broadcast group";
"lng_rights_gigagroup_convert" = "Convert to Broadcast Group";
"lng_rights_gigagroup_about" = "Broadcast groups can have over 200,000 members, but only admins can send messages in them.";
@@ -4804,6 +5013,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_send_non_premium_message_toast" = "**{user}** only accepts messages from contacts and {link} subscribers.";
"lng_send_non_premium_message_toast_link" = "Telegram Premium";
"lng_send_charges_stars_text" = "{user} charges {amount} for each message.";
"lng_send_charges_stars_go" = "Buy Stars";
"lng_exceptions_list_title" = "Exceptions";
"lng_removed_list_title" = "Removed users";
@@ -5520,6 +5732,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_view_button_iv" = "Instant View";
"lng_view_button_stickerset" = "View stickers";
"lng_view_button_emojipack" = "View emoji";
"lng_view_button_collectible" = "View collectible";
"lng_sponsored_hide_ads" = "Hide";
"lng_sponsored_title" = "What are sponsored messages?";
@@ -5836,6 +6049,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_boosts_list_tab_gifts#other" = "{count} Gifts";
"lng_boosts_prepaid_giveaway_title" = "Prepaid giveaways";
"lng_boosts_prepaid_giveaway_title_subtext" = "Select a giveaway you already paid for to set it up.";
"lng_boosts_prepaid_giveaway_single" = "Prepaid giveaway";
"lng_boosts_prepaid_giveaway_quantity#one" = "{count} Telegram Premium";
"lng_boosts_prepaid_giveaway_quantity#other" = "{count} Telegram Premium";
@@ -5895,6 +6109,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_channel_earn_learn_coin_link" = "https://telegram.org/blog/monetization-for-channels";
"lng_channel_earn_chart_top_hours" = "Ad impressions";
"lng_channel_earn_chart_revenue" = "Ad rewards";
"lng_channel_earn_chart_overriden_detail_credits" = "Rewards in Stars";
"lng_channel_earn_chart_overriden_detail_currency" = "Rewards in TON";
"lng_channel_earn_chart_overriden_detail_usd" = "Rewards in USD";
"lng_channel_earn_currency_history" = "TON Transactions";

View File

@@ -24,6 +24,8 @@
<file alias="icons/settings/star.svg">../../icons/settings/star.svg</file>
<file alias="icons/settings/starmini.svg">../../icons/settings/starmini.svg</file>
<file alias="icons/tray/monochrome.svg">../../icons/tray_monochrome.svg</file>
<file alias="icons/tray/monochrome_attention.svg">../../icons/tray_monochrome_attention.svg</file>
<file alias="icons/tray/monochrome_mute.svg">../../icons/tray_monochrome_mute.svg</file>
<file alias="topic_icons/blue.svg">../../art/topic_icons/blue.svg</file>
<file alias="topic_icons/yellow.svg">../../art/topic_icons/yellow.svg</file>
<file alias="topic_icons/violet.svg">../../art/topic_icons/violet.svg</file>

View File

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

View File

@@ -44,8 +44,8 @@ IDI_ICON1 ICON "..\\art\\icon256.ico"
//
VS_VERSION_INFO VERSIONINFO
FILEVERSION 5,10,3,0
PRODUCTVERSION 5,10,3,0
FILEVERSION 5,12,5,0
PRODUCTVERSION 5,12,5,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.10.3.0"
VALUE "FileVersion", "5.12.5.0"
VALUE "LegalCopyright", "Copyright (C) 2014-2025"
VALUE "ProductName", "Telegram Desktop"
VALUE "ProductVersion", "5.10.3.0"
VALUE "ProductVersion", "5.12.5.0"
END
END
BLOCK "VarFileInfo"

View File

@@ -35,8 +35,8 @@ LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
//
VS_VERSION_INFO VERSIONINFO
FILEVERSION 5,10,3,0
PRODUCTVERSION 5,10,3,0
FILEVERSION 5,12,5,0
PRODUCTVERSION 5,12,5,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.10.3.0"
VALUE "FileVersion", "5.12.5.0"
VALUE "LegalCopyright", "Copyright (C) 2014-2025"
VALUE "ProductName", "Telegram Desktop"
VALUE "ProductVersion", "5.10.3.0"
VALUE "ProductVersion", "5.12.5.0"
END
END
BLOCK "VarFileInfo"

View File

@@ -217,7 +217,11 @@ void Authorizations::toggleCallsDisabled(uint64 hash, bool disabled) {
MTP_bool(disabled)
)).done([=] {
_toggleCallsDisabledRequests.remove(hash);
}).fail([=] {
}).fail([=](const MTP::Error &error) {
LOG(("API Error: toggle calls %1. Hash: %2. %3.")
.arg(disabled ? u"disabled"_q : u"enabled"_q)
.arg(hash)
.arg(error.type()));
_toggleCallsDisabledRequests.remove(hash);
}).send();
_toggleCallsDisabledRequests.emplace(hash, id);

View File

@@ -149,18 +149,14 @@ void InitFilterLinkHeader(
iconEmoji
).value_or(Ui::FilterIcon::Custom)).active;
const auto isStatic = title.isStatic;
const auto makeContext = [=](Fn<void()> repaint) {
return Core::MarkedTextContext{
.session = &box->peerListUiShow()->session(),
.customEmojiRepaint = std::move(repaint),
.customEmojiLoopLimit = isStatic ? -1 : 0,
};
};
auto header = Ui::MakeFilterLinkHeader(box, {
.type = type,
.title = TitleText(type)(tr::now),
.about = AboutText(type, title.text),
.makeAboutContext = makeContext,
.aboutContext = Core::TextContext({
.session = &box->peerListUiShow()->session(),
.customEmojiLoopLimit = isStatic ? -1 : 0,
}),
.folderTitle = title.text,
.folderIcon = icon,
.badge = (type == Ui::FilterLinkHeaderType::AddingChats
@@ -560,16 +556,12 @@ void ShowImportToast(
text.append('\n').append(phrase(tr::now, lt_count, added));
}
const auto isStatic = title.isStatic;
const auto makeContext = [=](not_null<QWidget*> widget) {
return Core::MarkedTextContext{
.session = &strong->session(),
.customEmojiRepaint = [=] { widget->update(); },
.customEmojiLoopLimit = isStatic ? -1 : 0,
};
};
strong->showToast({
.text = std::move(text),
.textContext = makeContext,
.textContext = Core::TextContext({
.session = &strong->session(),
.customEmojiLoopLimit = isStatic ? -1 : 0,
})
});
}
@@ -640,18 +632,14 @@ void ProcessFilterInvite(
raw->setRealContentHeight(box->heightValue());
const auto isStatic = title.isStatic;
const auto makeContext = [=](Fn<void()> update) {
return Core::MarkedTextContext{
.session = &strong->session(),
.customEmojiRepaint = update,
.customEmojiLoopLimit = isStatic ? -1 : 0,
};
};
auto owned = Ui::FilterLinkProcessButton(
box,
type,
title.text,
makeContext,
Core::TextContext({
.session = &strong->session(),
.customEmojiLoopLimit = isStatic ? -1 : 0,
}),
std::move(badge));
const auto button = owned.data();
@@ -873,18 +861,14 @@ void ProcessFilterRemove(
}, type, title, iconEmoji, rpl::single(0), horizontalFilters);
const auto isStatic = title.isStatic;
const auto makeContext = [=](Fn<void()> update) {
return Core::MarkedTextContext{
.session = &strong->session(),
.customEmojiRepaint = update,
.customEmojiLoopLimit = isStatic ? -1 : 0,
};
};
auto owned = Ui::FilterLinkProcessButton(
box,
type,
title.text,
makeContext,
Core::TextContext({
.session = &strong->session(),
.customEmojiLoopLimit = isStatic ? -1 : 0,
}),
std::move(badge));
const auto button = owned.data();

View File

@@ -211,11 +211,10 @@ void ApplyBotsList(
Data::PeerUpdate::Flag::FullInfo);
}
[[nodiscard]] ChatParticipants::Channels ParseSimilar(
[[nodiscard]] ChatParticipants::Peers ParseSimilarChannels(
not_null<Main::Session*> session,
const MTPmessages_Chats &chats) {
auto result = ChatParticipants::Channels();
std::vector<not_null<ChannelData*>>();
auto result = ChatParticipants::Peers();
chats.match([&](const auto &data) {
const auto &list = data.vchats().v;
result.list.reserve(list.size());
@@ -234,10 +233,29 @@ void ApplyBotsList(
return result;
}
[[nodiscard]] ChatParticipants::Channels ParseSimilar(
[[nodiscard]] ChatParticipants::Peers ParseSimilarChannels(
not_null<ChannelData*> channel,
const MTPmessages_Chats &chats) {
return ParseSimilar(&channel->session(), chats);
return ParseSimilarChannels(&channel->session(), chats);
}
[[nodiscard]] ChatParticipants::Peers ParseSimilarBots(
not_null<Main::Session*> session,
const MTPusers_Users &users) {
auto result = ChatParticipants::Peers();
users.match([&](const auto &data) {
const auto &list = data.vusers().v;
result.list.reserve(list.size());
for (const auto &user : list) {
result.list.push_back(session->data().processUser(user));
}
if constexpr (MTPDusers_usersSlice::Is<decltype(data)>()) {
if (session->premiumPossible()) {
result.more = data.vcount().v - data.vusers().v.size();
}
}
});
return result;
}
} // namespace
@@ -782,52 +800,65 @@ void ChatParticipants::unblock(
_kickRequests.emplace(kick, requestId);
}
void ChatParticipants::loadSimilarChannels(not_null<ChannelData*> channel) {
if (!channel->isBroadcast()) {
return;
} else if (const auto i = _similar.find(channel); i != end(_similar)) {
void ChatParticipants::loadSimilarPeers(not_null<PeerData*> peer) {
if (const auto i = _similar.find(peer); i != end(_similar)) {
if (i->second.requestId
|| !i->second.channels.more
|| !channel->session().premium()) {
|| !i->second.peers.more
|| !peer->session().premium()) {
return;
}
}
using Flag = MTPchannels_GetChannelRecommendations::Flag;
_similar[channel].requestId = _api.request(
MTPchannels_GetChannelRecommendations(
MTP_flags(Flag::f_channel),
channel->inputChannel)
).done([=](const MTPmessages_Chats &result) {
auto &similar = _similar[channel];
similar.requestId = 0;
auto parsed = ParseSimilar(channel, result);
if (similar.channels == parsed) {
return;
}
similar.channels = std::move(parsed);
if (const auto history = channel->owner().historyLoaded(channel)) {
if (const auto item = history->joinedMessageInstance()) {
history->owner().requestItemResize(item);
if (const auto channel = peer->asBroadcast()) {
using Flag = MTPchannels_GetChannelRecommendations::Flag;
_similar[peer].requestId = _api.request(
MTPchannels_GetChannelRecommendations(
MTP_flags(Flag::f_channel),
channel->inputChannel)
).done([=](const MTPmessages_Chats &result) {
auto &similar = _similar[channel];
similar.requestId = 0;
auto parsed = ParseSimilarChannels(channel, result);
if (similar.peers == parsed) {
return;
}
}
_similarLoaded.fire_copy(channel);
}).send();
similar.peers = std::move(parsed);
if (const auto history = channel->owner().historyLoaded(channel)) {
if (const auto item = history->joinedMessageInstance()) {
history->owner().requestItemResize(item);
}
}
_similarLoaded.fire_copy(channel);
}).send();
} else if (const auto bot = peer->asBot()) {
_similar[peer].requestId = _api.request(
MTPbots_GetBotRecommendations(bot->inputUser)
).done([=](const MTPusers_Users &result) {
auto &similar = _similar[peer];
similar.requestId = 0;
auto parsed = ParseSimilarBots(&peer->session(), result);
if (similar.peers == parsed) {
return;
}
similar.peers = std::move(parsed);
_similarLoaded.fire_copy(peer);
}).send();
}
}
auto ChatParticipants::similar(not_null<ChannelData*> channel)
-> const Channels & {
const auto i = channel->isBroadcast()
? _similar.find(channel)
auto ChatParticipants::similar(not_null<PeerData*> peer)
-> const Peers & {
const auto i = (peer->isBroadcast() || peer->isBot())
? _similar.find(peer)
: end(_similar);
if (i != end(_similar)) {
return i->second.channels;
return i->second.peers;
}
static const auto empty = Channels();
static const auto empty = Peers();
return empty;
}
auto ChatParticipants::similarLoaded() const
-> rpl::producer<not_null<ChannelData*>> {
-> rpl::producer<not_null<PeerData*>> {
return _similarLoaded.events();
}
@@ -841,15 +872,15 @@ void ChatParticipants::loadRecommendations() {
MTP_inputChannelEmpty())
).done([=](const MTPmessages_Chats &result) {
_recommendations.requestId = 0;
auto parsed = ParseSimilar(_session, result);
_recommendations.channels = std::move(parsed);
_recommendations.channels.more = 0;
auto parsed = ParseSimilarChannels(_session, result);
_recommendations.peers = std::move(parsed);
_recommendations.peers.more = 0;
_recommendationsLoaded = true;
}).send();
}
const ChatParticipants::Channels &ChatParticipants::recommendations() const {
return _recommendations.channels;
const ChatParticipants::Peers &ChatParticipants::recommendations() const {
return _recommendations.peers;
}
rpl::producer<> ChatParticipants::recommendationsLoaded() const {

View File

@@ -138,27 +138,27 @@ public:
not_null<ChannelData*> channel,
not_null<PeerData*> participant);
void loadSimilarChannels(not_null<ChannelData*> channel);
void loadSimilarPeers(not_null<PeerData*> peer);
struct Channels {
std::vector<not_null<ChannelData*>> list;
struct Peers {
std::vector<not_null<PeerData*>> list;
int more = 0;
friend inline bool operator==(
const Channels &,
const Channels &) = default;
const Peers &,
const Peers &) = default;
};
[[nodiscard]] const Channels &similar(not_null<ChannelData*> channel);
[[nodiscard]] const Peers &similar(not_null<PeerData*> peer);
[[nodiscard]] auto similarLoaded() const
-> rpl::producer<not_null<ChannelData*>>;
-> rpl::producer<not_null<PeerData*>>;
void loadRecommendations();
[[nodiscard]] const Channels &recommendations() const;
[[nodiscard]] const Peers &recommendations() const;
[[nodiscard]] rpl::producer<> recommendationsLoaded() const;
private:
struct SimilarChannels {
Channels channels;
struct SimilarPeers {
Peers peers;
mtpRequestId requestId = 0;
};
@@ -186,10 +186,10 @@ private:
not_null<PeerData*>>;
base::flat_map<KickRequest, mtpRequestId> _kickRequests;
base::flat_map<not_null<ChannelData*>, SimilarChannels> _similar;
rpl::event_stream<not_null<ChannelData*>> _similarLoaded;
base::flat_map<not_null<PeerData*>, SimilarPeers> _similar;
rpl::event_stream<not_null<PeerData*>> _similarLoaded;
SimilarChannels _recommendations;
SimilarPeers _recommendations;
rpl::variable<bool> _recommendationsLoaded = false;
};

View File

@@ -25,6 +25,7 @@ struct SendOptions {
TimeId scheduled = 0;
BusinessShortcutId shortcutId = 0;
EffectId effectId = 0;
int starsApproved = 0;
bool silent = false;
bool handleSupportSwitch = false;
bool invertCaption = false;
@@ -74,6 +75,7 @@ struct MessageToSend {
struct RemoteFileInfo {
MTPInputFile file;
std::optional<MTPInputFile> thumb;
std::optional<MTPInputPhoto> videoCover;
std::vector<MTPInputDocument> attachedStickers;
};

View File

@@ -90,7 +90,13 @@ constexpr auto kTransactionsLimit = 100;
? peerFromMTP(*tl.data().vstarref_peer()).value
: 0;
const auto incoming = (amount >= StarsAmount());
const auto saveActorId = (reaction || !extended.empty()) && incoming;
const auto paidMessagesCount
= tl.data().vpaid_messages().value_or_empty();
const auto premiumMonthsForStars
= tl.data().vpremium_gift_months().value_or_empty();
const auto saveActorId = (reaction
|| !extended.empty()
|| paidMessagesCount) && incoming;
const auto parsedGift = stargift
? FromTL(&peer->session(), *stargift)
: std::optional<Data::StarGift>();
@@ -110,9 +116,9 @@ constexpr auto kTransactionsLimit = 100;
.bareGiftStickerId = giftStickerId,
.bareActorId = saveActorId ? barePeerId : uint64(0),
.uniqueGift = parsedGift ? parsedGift->unique : nullptr,
.starrefAmount = starrefAmount,
.starrefCommission = starrefCommission,
.starrefRecipientId = starrefBarePeerId,
.starrefAmount = paidMessagesCount ? StarsAmount() : starrefAmount,
.starrefCommission = paidMessagesCount ? 0 : starrefCommission,
.starrefRecipientId = paidMessagesCount ? 0 : starrefBarePeerId,
.peerType = tl.data().vpeer().match([](const HistoryPeerTL &) {
return Data::CreditsHistoryEntry::PeerType::Peer;
}, [](const MTPDstarsTransactionPeerPlayMarket &) {
@@ -138,9 +144,15 @@ constexpr auto kTransactionsLimit = 100;
? base::unixtime::parse(tl.data().vtransaction_date()->v)
: QDateTime(),
.successLink = qs(tl.data().vtransaction_url().value_or_empty()),
.paidMessagesCount = paidMessagesCount,
.paidMessagesAmount = (paidMessagesCount
? starrefAmount
: StarsAmount()),
.paidMessagesCommission = paidMessagesCount ? starrefCommission : 0,
.starsConverted = int(nonUniqueGift
? nonUniqueGift->vconvert_stars().v
: 0),
.premiumMonthsForStars = premiumMonthsForStars,
.floodSkip = int(tl.data().vfloodskip_number().value_or(0)),
.converted = stargift && incoming,
.stargift = stargift.has_value(),
@@ -513,4 +525,12 @@ void EditCreditsSubscription(
)).done(done).fail([=](const MTP::Error &e) { fail(e.type()); }).send();
}
MTPInputSavedStarGift InputSavedStarGiftId(const Data::SavedStarGiftId &id) {
return id.isUser()
? MTP_inputSavedStarGiftUser(MTP_int(id.userMessageId().bare))
: MTP_inputSavedStarGiftChat(
id.chat()->input,
MTP_long(id.chatSavedId()));
}
} // namespace Api

View File

@@ -12,6 +12,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_credits_earn.h"
#include "mtproto/sender.h"
namespace Data {
class SavedStarGiftId;
} // namespace Data
namespace Main {
class Session;
} // namespace Main
@@ -116,4 +120,7 @@ void EditCreditsSubscription(
Fn<void()> done,
Fn<void(QString)> fail);
[[nodiscard]] MTPInputSavedStarGift InputSavedStarGiftId(
const Data::SavedStarGiftId &id);
} // namespace Api

View File

@@ -276,14 +276,22 @@ mtpRequestId EditTextMessage(
takeFileReference = [=] { return photo->fileReference(); };
} else if (const auto document = media->document()) {
using Flag = MTPDinputMediaDocument::Flag;
const auto videoCover = media->videoCover();
const auto videoTimestamp = media->videoTimestamp();
const auto flags = Flag()
| (media->ttlSeconds() ? Flag::f_ttl_seconds : Flag())
| (spoilered ? Flag::f_spoiler : Flag());
| (spoilered ? Flag::f_spoiler : Flag())
| (videoTimestamp ? Flag::f_video_timestamp : Flag())
| (videoCover ? Flag::f_video_cover : Flag());
takeInputMedia = [=] {
return MTP_inputMediaDocument(
MTP_flags(flags),
document->mtpInput(),
(videoCover
? videoCover->mtpInput()
: MTPInputPhoto()),
MTP_int(media->ttlSeconds()),
MTP_int(videoTimestamp),
MTPstring()); // query
};
takeFileReference = [=] { return document->fileReference(); };

View File

@@ -13,6 +13,32 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
namespace Api {
PeerId ParsePaidReactionShownPeer(
not_null<Main::Session*> session,
const MTPPaidReactionPrivacy &value) {
return value.match([&](const MTPDpaidReactionPrivacyDefault &) {
return session->userPeerId();
}, [](const MTPDpaidReactionPrivacyAnonymous &) {
return PeerId();
}, [&](const MTPDpaidReactionPrivacyPeer &data) {
return data.vpeer().match([&](const MTPDinputPeerSelf &) {
return session->userPeerId();
}, [](const MTPDinputPeerUser &data) {
return peerFromUser(data.vuser_id());
}, [](const MTPDinputPeerChat &data) {
return peerFromChat(data.vchat_id());
}, [](const MTPDinputPeerChannel &data) {
return peerFromChannel(data.vchannel_id());
}, [](const MTPDinputPeerUserFromMessage &data) -> PeerId {
Unexpected("From message peer in ParsePaidReactionShownPeer.");
}, [](const MTPDinputPeerChannelFromMessage &data) -> PeerId {
Unexpected("From message peer in ParsePaidReactionShownPeer.");
}, [](const MTPDinputPeerEmpty &) -> PeerId {
Unexpected("Empty peer in ParsePaidReactionShownPeer.");
});
});
}
GlobalPrivacy::GlobalPrivacy(not_null<ApiWrap*> api)
: _session(&api->session())
, _api(&api->instance()) {
@@ -88,7 +114,8 @@ void GlobalPrivacy::updateHideReadTime(bool hide) {
archiveAndMuteCurrent(),
unarchiveOnNewMessageCurrent(),
hide,
newRequirePremiumCurrent());
newRequirePremiumCurrent(),
newChargeStarsCurrent());
}
bool GlobalPrivacy::hideReadTimeCurrent() const {
@@ -99,14 +126,6 @@ rpl::producer<bool> GlobalPrivacy::hideReadTime() const {
return _hideReadTime.value();
}
void GlobalPrivacy::updateNewRequirePremium(bool value) {
update(
archiveAndMuteCurrent(),
unarchiveOnNewMessageCurrent(),
hideReadTimeCurrent(),
value);
}
bool GlobalPrivacy::newRequirePremiumCurrent() const {
return _newRequirePremium.current();
}
@@ -115,27 +134,46 @@ rpl::producer<bool> GlobalPrivacy::newRequirePremium() const {
return _newRequirePremium.value();
}
void GlobalPrivacy::loadPaidReactionAnonymous() {
if (_paidReactionAnonymousLoaded) {
int GlobalPrivacy::newChargeStarsCurrent() const {
return _newChargeStars.current();
}
rpl::producer<int> GlobalPrivacy::newChargeStars() const {
return _newChargeStars.value();
}
void GlobalPrivacy::updateMessagesPrivacy(
bool requirePremium,
int chargeStars) {
update(
archiveAndMuteCurrent(),
unarchiveOnNewMessageCurrent(),
hideReadTimeCurrent(),
requirePremium,
chargeStars);
}
void GlobalPrivacy::loadPaidReactionShownPeer() {
if (_paidReactionShownPeerLoaded) {
return;
}
_paidReactionAnonymousLoaded = true;
_paidReactionShownPeerLoaded = true;
_api.request(MTPmessages_GetPaidReactionPrivacy(
)).done([=](const MTPUpdates &result) {
_session->api().applyUpdates(result);
}).send();
}
void GlobalPrivacy::updatePaidReactionAnonymous(bool value) {
_paidReactionAnonymous = value;
void GlobalPrivacy::updatePaidReactionShownPeer(PeerId shownPeer) {
_paidReactionShownPeer = shownPeer;
}
bool GlobalPrivacy::paidReactionAnonymousCurrent() const {
return _paidReactionAnonymous.current();
PeerId GlobalPrivacy::paidReactionShownPeerCurrent() const {
return _paidReactionShownPeer.current();
}
rpl::producer<bool> GlobalPrivacy::paidReactionAnonymous() const {
return _paidReactionAnonymous.value();
rpl::producer<PeerId> GlobalPrivacy::paidReactionShownPeer() const {
return _paidReactionShownPeer.value();
}
void GlobalPrivacy::updateArchiveAndMute(bool value) {
@@ -143,7 +181,8 @@ void GlobalPrivacy::updateArchiveAndMute(bool value) {
value,
unarchiveOnNewMessageCurrent(),
hideReadTimeCurrent(),
newRequirePremiumCurrent());
newRequirePremiumCurrent(),
newChargeStarsCurrent());
}
void GlobalPrivacy::updateUnarchiveOnNewMessage(
@@ -152,14 +191,16 @@ void GlobalPrivacy::updateUnarchiveOnNewMessage(
archiveAndMuteCurrent(),
value,
hideReadTimeCurrent(),
newRequirePremiumCurrent());
newRequirePremiumCurrent(),
newChargeStarsCurrent());
}
void GlobalPrivacy::update(
bool archiveAndMute,
UnarchiveOnNewMessage unarchiveOnNewMessage,
bool hideReadTime,
bool newRequirePremium) {
bool newRequirePremium,
int newChargeStars) {
using Flag = MTPDglobalPrivacySettings::Flag;
_api.request(_requestId).cancel();
@@ -178,35 +219,44 @@ void GlobalPrivacy::update(
| (hideReadTime ? Flag::f_hide_read_marks : Flag())
| ((newRequirePremium && newRequirePremiumAllowed)
? Flag::f_new_noncontact_peers_require_premium
: Flag());
: Flag())
| Flag::f_noncontact_peers_paid_stars;
_requestId = _api.request(MTPaccount_SetGlobalPrivacySettings(
MTP_globalPrivacySettings(MTP_flags(flags))
MTP_globalPrivacySettings(
MTP_flags(flags),
MTP_long(newChargeStars))
)).done([=](const MTPGlobalPrivacySettings &result) {
_requestId = 0;
apply(result);
}).fail([=](const MTP::Error &error) {
_requestId = 0;
if (error.type() == u"PREMIUM_ACCOUNT_REQUIRED"_q) {
update(archiveAndMute, unarchiveOnNewMessage, hideReadTime, {});
update(
archiveAndMute,
unarchiveOnNewMessage,
hideReadTime,
false,
0);
}
}).send();
_archiveAndMute = archiveAndMute;
_unarchiveOnNewMessage = unarchiveOnNewMessage;
_hideReadTime = hideReadTime;
_newRequirePremium = newRequirePremium;
_newChargeStars = newChargeStars;
}
void GlobalPrivacy::apply(const MTPGlobalPrivacySettings &data) {
data.match([&](const MTPDglobalPrivacySettings &data) {
_archiveAndMute = data.is_archive_and_mute_new_noncontact_peers();
_unarchiveOnNewMessage = data.is_keep_archived_unmuted()
? UnarchiveOnNewMessage::None
: data.is_keep_archived_folders()
? UnarchiveOnNewMessage::NotInFoldersUnmuted
: UnarchiveOnNewMessage::AnyUnmuted;
_hideReadTime = data.is_hide_read_marks();
_newRequirePremium = data.is_new_noncontact_peers_require_premium();
});
void GlobalPrivacy::apply(const MTPGlobalPrivacySettings &settings) {
const auto &data = settings.data();
_archiveAndMute = data.is_archive_and_mute_new_noncontact_peers();
_unarchiveOnNewMessage = data.is_keep_archived_unmuted()
? UnarchiveOnNewMessage::None
: data.is_keep_archived_folders()
? UnarchiveOnNewMessage::NotInFoldersUnmuted
: UnarchiveOnNewMessage::AnyUnmuted;
_hideReadTime = data.is_hide_read_marks();
_newRequirePremium = data.is_new_noncontact_peers_require_premium();
_newChargeStars = data.vnoncontact_peers_paid_stars().value_or_empty();
}
} // namespace Api

View File

@@ -23,6 +23,10 @@ enum class UnarchiveOnNewMessage {
AnyUnmuted,
};
[[nodiscard]] PeerId ParsePaidReactionShownPeer(
not_null<Main::Session*> session,
const MTPPaidReactionPrivacy &value);
class GlobalPrivacy final {
public:
explicit GlobalPrivacy(not_null<ApiWrap*> api);
@@ -45,23 +49,28 @@ public:
[[nodiscard]] bool hideReadTimeCurrent() const;
[[nodiscard]] rpl::producer<bool> hideReadTime() const;
void updateNewRequirePremium(bool value);
[[nodiscard]] bool newRequirePremiumCurrent() const;
[[nodiscard]] rpl::producer<bool> newRequirePremium() const;
void loadPaidReactionAnonymous();
void updatePaidReactionAnonymous(bool value);
[[nodiscard]] bool paidReactionAnonymousCurrent() const;
[[nodiscard]] rpl::producer<bool> paidReactionAnonymous() const;
[[nodiscard]] int newChargeStarsCurrent() const;
[[nodiscard]] rpl::producer<int> newChargeStars() const;
void updateMessagesPrivacy(bool requirePremium, int chargeStars);
void loadPaidReactionShownPeer();
void updatePaidReactionShownPeer(PeerId shownPeer);
[[nodiscard]] PeerId paidReactionShownPeerCurrent() const;
[[nodiscard]] rpl::producer<PeerId> paidReactionShownPeer() const;
private:
void apply(const MTPGlobalPrivacySettings &data);
void apply(const MTPGlobalPrivacySettings &settings);
void update(
bool archiveAndMute,
UnarchiveOnNewMessage unarchiveOnNewMessage,
bool hideReadTime,
bool newRequirePremium);
bool newRequirePremium,
int newChargeStars);
const not_null<Main::Session*> _session;
MTP::Sender _api;
@@ -72,9 +81,10 @@ private:
rpl::variable<bool> _showArchiveAndMute = false;
rpl::variable<bool> _hideReadTime = false;
rpl::variable<bool> _newRequirePremium = false;
rpl::variable<bool> _paidReactionAnonymous = false;
rpl::variable<int> _newChargeStars = 0;
rpl::variable<PeerId> _paidReactionShownPeer = false;
std::vector<Fn<void()>> _callbacks;
bool _paidReactionAnonymousLoaded = false;
bool _paidReactionShownPeerLoaded = false;
};

View File

@@ -111,7 +111,8 @@ MTPInputMedia PrepareUploadedDocument(
| (info.thumb ? Flag::f_thumb : Flag())
| (item->groupId() ? Flag::f_nosound_video : Flag())
| (info.attachedStickers.empty() ? Flag::f_stickers : Flag())
| (ttlSeconds ? Flag::f_ttl_seconds : Flag());
| (ttlSeconds ? Flag::f_ttl_seconds : Flag())
| (info.videoCover ? Flag::f_video_cover : Flag());
const auto document = item->media()->document();
return MTP_inputMediaUploadedDocument(
MTP_flags(flags),
@@ -121,6 +122,8 @@ MTPInputMedia PrepareUploadedDocument(
ComposeSendingDocumentAttributes(document),
MTP_vector<MTPInputDocument>(
ranges::to<QVector<MTPInputDocument>>(info.attachedStickers)),
info.videoCover.value_or(MTPInputPhoto()),
MTP_int(0), // video_timestamp
MTP_int(ttlSeconds));
}

View File

@@ -103,6 +103,7 @@ void MessagesSearch::searchRequest() {
_requestId = _history->session().api().request(MTPmessages_Search(
MTP_flags((fromPeer ? Flag::f_from_id : Flag())
| (savedPeer ? Flag::f_saved_peer_id : Flag())
| (_request.topMsgId ? Flag::f_top_msg_id : Flag())
| (_request.tags.empty() ? Flag() : Flag::f_saved_reaction)),
_history->peer->input,
MTP_string(_request.query),
@@ -111,7 +112,7 @@ void MessagesSearch::searchRequest() {
MTP_vector_from_range(_request.tags | ranges::views::transform(
Data::ReactionToMTP
)),
MTPint(), // top_msg_id
MTP_int(_request.topMsgId), // top_msg_id
MTP_inputMessagesFilterEmpty(),
MTP_int(0), // min_date
MTP_int(0), // max_date

View File

@@ -32,6 +32,7 @@ public:
QString query;
PeerData *from = nullptr;
std::vector<Data::ReactionId> tags;
MsgId topMsgId;
friend inline bool operator==(
const Request &,

View File

@@ -64,6 +64,10 @@ MessagesSearchMerged::MessagesSearchMerged(not_null<History*> history)
}
}
void MessagesSearchMerged::disableMigrated() {
_migratedSearch = std::nullopt;
}
void MessagesSearchMerged::addFound(const FoundMessages &data) {
for (const auto &message : data.messages) {
_concatedFound.messages.push_back(message);

View File

@@ -29,6 +29,7 @@ public:
void clear();
void search(const Request &search);
void searchMore();
void disableMigrated();
[[nodiscard]] const FoundMessages &messages() const;
[[nodiscard]] const Request &request() const;

View File

@@ -37,7 +37,7 @@ Polls::Polls(not_null<ApiWrap*> api)
void Polls::create(
const PollData &data,
const SendAction &action,
SendAction action,
Fn<void()> done,
Fn<void()> fail) {
_session->api().sendAction(action);
@@ -59,6 +59,9 @@ void Polls::create(
history->startSavingCloudDraft(topicRootId);
}
const auto silentPost = ShouldSendSilent(peer, action.options);
const auto starsPaid = std::min(
peer->starsPerMessageChecked(),
action.options.starsApproved);
if (silentPost) {
sendFlags |= MTPmessages_SendMedia::Flag::f_silent;
}
@@ -71,6 +74,10 @@ void Polls::create(
if (action.options.effectId) {
sendFlags |= MTPmessages_SendMedia::Flag::f_effect;
}
if (starsPaid) {
action.options.starsApproved -= starsPaid;
sendFlags |= MTPmessages_SendMedia::Flag::f_allow_paid_stars;
}
const auto sendAs = action.options.sendAs;
if (sendAs) {
sendFlags |= MTPmessages_SendMedia::Flag::f_send_as;
@@ -93,7 +100,8 @@ void Polls::create(
MTP_int(action.options.scheduled),
(sendAs ? sendAs->input : MTP_inputPeerEmpty()),
Data::ShortcutIdToMTP(_session, action.options.shortcutId),
MTP_long(action.options.effectId)
MTP_long(action.options.effectId),
MTP_long(starsPaid)
), [=](const MTPUpdates &result, const MTP::Response &response) {
if (clearCloudDraft) {
history->finishSavingCloudDraft(

View File

@@ -27,7 +27,7 @@ public:
void create(
const PollData &data,
const SendAction &action,
SendAction action,
Fn<void()> done,
Fn<void()> fail);
void sendVotes(

View File

@@ -11,6 +11,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "api/api_text_entities.h"
#include "apiwrap.h"
#include "base/random.h"
#include "data/data_channel.h"
#include "data/data_document.h"
#include "data/data_peer.h"
#include "data/data_peer_values.h"
@@ -377,15 +378,15 @@ const Data::PremiumSubscriptionOptions &Premium::subscriptionOptions() const {
return _subscriptionOptions;
}
rpl::producer<> Premium::somePremiumRequiredResolved() const {
return _somePremiumRequiredResolved.events();
rpl::producer<> Premium::someMessageMoneyRestrictionsResolved() const {
return _someMessageMoneyRestrictionsResolved.events();
}
void Premium::resolvePremiumRequired(not_null<UserData*> user) {
_resolvePremiumRequiredUsers.emplace(user);
if (!_premiumRequiredRequestScheduled
&& _resolvePremiumRequestedUsers.empty()) {
_premiumRequiredRequestScheduled = true;
void Premium::resolveMessageMoneyRestrictions(not_null<UserData*> user) {
_resolveMessageMoneyRequiredUsers.emplace(user);
if (!_messageMoneyRequestScheduled
&& _resolveMessageMoneyRequestedUsers.empty()) {
_messageMoneyRequestScheduled = true;
crl::on_main(_session, [=] {
requestPremiumRequiredSlice();
});
@@ -393,50 +394,65 @@ void Premium::resolvePremiumRequired(not_null<UserData*> user) {
}
void Premium::requestPremiumRequiredSlice() {
_premiumRequiredRequestScheduled = false;
if (!_resolvePremiumRequestedUsers.empty()
|| _resolvePremiumRequiredUsers.empty()) {
_messageMoneyRequestScheduled = false;
if (!_resolveMessageMoneyRequestedUsers.empty()
|| _resolveMessageMoneyRequiredUsers.empty()) {
return;
}
constexpr auto kPerRequest = 100;
auto users = MTP_vector_from_range(_resolvePremiumRequiredUsers
auto users = MTP_vector_from_range(_resolveMessageMoneyRequiredUsers
| ranges::views::transform(&UserData::inputUser));
if (users.v.size() > kPerRequest) {
auto shortened = users.v;
shortened.resize(kPerRequest);
users = MTP_vector<MTPInputUser>(std::move(shortened));
const auto from = begin(_resolvePremiumRequiredUsers);
_resolvePremiumRequestedUsers = { from, from + kPerRequest };
_resolvePremiumRequiredUsers.erase(from, from + kPerRequest);
const auto from = begin(_resolveMessageMoneyRequiredUsers);
_resolveMessageMoneyRequestedUsers = { from, from + kPerRequest };
_resolveMessageMoneyRequiredUsers.erase(from, from + kPerRequest);
} else {
_resolvePremiumRequestedUsers
= base::take(_resolvePremiumRequiredUsers);
_resolveMessageMoneyRequestedUsers
= base::take(_resolveMessageMoneyRequiredUsers);
}
const auto finish = [=](const QVector<MTPBool> &list) {
constexpr auto me = UserDataFlag::MeRequiresPremiumToWrite;
constexpr auto known = UserDataFlag::RequirePremiumToWriteKnown;
constexpr auto mask = me | known;
const auto finish = [=](const QVector<MTPRequirementToContact> &list) {
auto index = 0;
for (const auto &user : base::take(_resolvePremiumRequestedUsers)) {
const auto require = (index < list.size())
&& mtpIsTrue(list[index++]);
user->setFlags((user->flags() & ~mask)
| known
| (require ? me : UserDataFlag()));
for (const auto &user : base::take(_resolveMessageMoneyRequestedUsers)) {
const auto set = [&](bool requirePremium, int stars) {
using Flag = UserDataFlag;
constexpr auto me = Flag::RequiresPremiumToWrite;
constexpr auto known = Flag::MessageMoneyRestrictionsKnown;
constexpr auto hasPrem = Flag::HasRequirePremiumToWrite;
constexpr auto hasStars = Flag::HasStarsPerMessage;
user->setStarsPerMessage(stars);
user->setFlags((user->flags() & ~(me | hasPrem | hasStars))
| known
| (requirePremium ? (me | hasPrem) : Flag())
| (stars ? hasStars : Flag()));
};
if (index >= list.size()) {
set(false, 0);
continue;
}
list[index++].match([&](const MTPDrequirementToContactEmpty &) {
set(false, 0);
}, [&](const MTPDrequirementToContactPremium &) {
set(true, 0);
}, [&](const MTPDrequirementToContactPaidMessages &data) {
set(false, data.vstars_amount().v);
});
}
if (!_premiumRequiredRequestScheduled
&& !_resolvePremiumRequiredUsers.empty()) {
_premiumRequiredRequestScheduled = true;
if (!_messageMoneyRequestScheduled
&& !_resolveMessageMoneyRequiredUsers.empty()) {
_messageMoneyRequestScheduled = true;
crl::on_main(_session, [=] {
requestPremiumRequiredSlice();
});
}
_somePremiumRequiredResolved.fire({});
_someMessageMoneyRestrictionsResolved.fire({});
};
_session->api().request(
MTPusers_GetIsPremiumRequiredToContact(std::move(users))
).done([=](const MTPVector<MTPBool> &result) {
MTPusers_GetRequirementsToContact(std::move(users))
).done([=](const MTPVector<MTPRequirementToContact> &result) {
finish(result.v);
}).fail([=] {
finish({});
@@ -463,10 +479,14 @@ rpl::producer<rpl::no_value, QString> PremiumGiftCodeOptions::request() {
for (const auto &tlOption : result.v) {
const auto &data = tlOption.data();
tlMapOptions[data.vusers().v].push_back(tlOption);
if (qs(data.vcurrency()) == Ui::kCreditsCurrency) {
continue;
}
const auto token = Token{ data.vusers().v, data.vmonths().v };
_stores[token] = Store{
.amount = data.vamount().v,
.currency = qs(data.vcurrency()),
.product = qs(data.vstore_product().value_or_empty()),
.quantity = data.vstore_quantity().value_or_empty(),
};
@@ -475,14 +495,14 @@ rpl::producer<rpl::no_value, QString> PremiumGiftCodeOptions::request() {
}
}
for (const auto &[amount, tlOptions] : tlMapOptions) {
if (amount == 1 && _optionsForOnePerson.currency.isEmpty()) {
_optionsForOnePerson.currency = qs(
tlOptions.front().data().vcurrency());
if (amount == 1 && _optionsForOnePerson.currencies.empty()) {
for (const auto &option : tlOptions) {
_optionsForOnePerson.months.push_back(
option.data().vmonths().v);
_optionsForOnePerson.totalCosts.push_back(
option.data().vamount().v);
_optionsForOnePerson.currencies.push_back(
qs(option.data().vcurrency()));
}
}
_subscriptionOptions[amount] = GiftCodesFromTL(tlOptions);
@@ -509,7 +529,7 @@ rpl::producer<rpl::no_value, QString> PremiumGiftCodeOptions::applyPrepaid(
_api.request(MTPpayments_LaunchPrepaidGiveaway(
_peer->input,
MTP_long(prepaidId),
invoice.creditsAmount
invoice.giveawayCredits
? Payments::InvoiceCreditsGiveawayToTL(invoice)
: Payments::InvoicePremiumGiftCodeGiveawayToTL(invoice)
)).done([=](const MTPUpdates &result) {
@@ -540,7 +560,7 @@ Payments::InvoicePremiumGiftCode PremiumGiftCodeOptions::invoice(
const auto token = Token{ users, months };
const auto &store = _stores[token];
return Payments::InvoicePremiumGiftCode{
.currency = _optionsForOnePerson.currency,
.currency = store.currency,
.storeProduct = store.product,
.randomId = randomId,
.amount = store.amount,
@@ -553,14 +573,15 @@ Payments::InvoicePremiumGiftCode PremiumGiftCodeOptions::invoice(
std::vector<GiftOptionData> PremiumGiftCodeOptions::optionsForPeer() const {
auto result = std::vector<GiftOptionData>();
if (!_optionsForOnePerson.currency.isEmpty()) {
if (!_optionsForOnePerson.currencies.empty()) {
const auto count = int(_optionsForOnePerson.months.size());
result.reserve(count);
for (auto i = 0; i != count; ++i) {
Assert(i < _optionsForOnePerson.totalCosts.size());
Assert(i < _optionsForOnePerson.currencies.size());
result.push_back({
.cost = _optionsForOnePerson.totalCosts[i],
.currency = _optionsForOnePerson.currency,
.currency = _optionsForOnePerson.currencies[i],
.months = _optionsForOnePerson.months[i],
});
}
@@ -581,7 +602,7 @@ Data::PremiumSubscriptionOptions PremiumGiftCodeOptions::options(int amount) {
MTP_int(_optionsForOnePerson.months[i]),
MTPstring(),
MTPint(),
MTP_string(_optionsForOnePerson.currency),
MTP_string(_optionsForOnePerson.currencies[i]),
MTP_long(_optionsForOnePerson.totalCosts[i] * amount)));
}
_subscriptionOptions[amount] = GiftCodesFromTL(tlOptions);
@@ -694,28 +715,38 @@ rpl::producer<rpl::no_value, QString> SponsoredToggle::setToggled(bool v) {
};
}
RequirePremiumState ResolveRequiresPremiumToWrite(
MessageMoneyRestriction ResolveMessageMoneyRestrictions(
not_null<PeerData*> peer,
History *maybeHistory) {
const auto user = peer->asUser();
if (!user
|| !user->someRequirePremiumToWrite()
|| user->session().premium()) {
return RequirePremiumState::No;
} else if (user->requirePremiumToWriteKnown()) {
return user->meRequiresPremiumToWrite()
? RequirePremiumState::Yes
: RequirePremiumState::No;
} else if (user->flags() & UserDataFlag::MutualContact) {
return RequirePremiumState::No;
} else if (!maybeHistory) {
return RequirePremiumState::Unknown;
if (const auto channel = peer->asChannel()) {
return {
.starsPerMessage = channel->starsPerMessageChecked(),
.known = true,
};
}
const auto user = peer->asUser();
if (!user) {
return { .known = true };
} else if (user->messageMoneyRestrictionsKnown()) {
return {
.starsPerMessage = user->starsPerMessageChecked(),
.premiumRequired = (user->requiresPremiumToWrite()
&& !user->session().premium()),
.known = true,
};
} else if (user->hasStarsPerMessage()) {
return {};
} else if (!user->hasRequirePremiumToWrite()) {
return { .known = true };
} else if (user->flags() & UserDataFlag::MutualContact) {
return { .known = true };
} else if (!maybeHistory) {
return {};
}
const auto update = [&](bool require) {
using Flag = UserDataFlag;
constexpr auto known = Flag::RequirePremiumToWriteKnown;
constexpr auto me = Flag::MeRequiresPremiumToWrite;
constexpr auto known = Flag::MessageMoneyRestrictionsKnown;
constexpr auto me = Flag::RequiresPremiumToWrite;
user->setFlags((user->flags() & ~me)
| known
| (require ? me : Flag()));
@@ -727,16 +758,19 @@ RequirePremiumState ResolveRequiresPremiumToWrite(
const auto item = view->data();
if (!item->out() && !item->isService()) {
update(false);
return RequirePremiumState::No;
return { .known = true };
}
}
}
if (user->isContact() // Here we know, that we're not in his contacts.
&& maybeHistory->loadedAtTop() // And no incoming messages.
&& maybeHistory->loadedAtBottom()) {
update(true);
return {
.premiumRequired = !user->session().premium(),
.known = true,
};
}
return RequirePremiumState::Unknown;
return {};
}
rpl::producer<DocumentData*> RandomHelloStickerValue(
@@ -805,8 +839,14 @@ std::optional<Data::StarGift> FromTL(
auto result = Data::StarGift{
.id = uint64(data.vid().v),
.unique = std::make_shared<Data::UniqueGift>(Data::UniqueGift{
.id = data.vid().v,
.slug = qs(data.vslug()),
.title = qs(data.vtitle()),
.ownerId = peerFromUser(UserId(data.vowner_id().v)),
.ownerAddress = qs(data.vowner_address().value_or_empty()),
.ownerName = qs(data.vowner_name().value_or_empty()),
.ownerId = (data.vowner_id()
? peerFromMTP(*data.vowner_id())
: PeerId()),
.number = data.vnum().v,
.model = *model,
.pattern = *pattern,
@@ -829,9 +869,9 @@ std::optional<Data::StarGift> FromTL(
});
}
std::optional<Data::UserStarGift> FromTL(
not_null<UserData*> to,
const MTPuserStarGift &gift) {
std::optional<Data::SavedStarGift> FromTL(
not_null<PeerData*> to,
const MTPsavedStarGift &gift) {
const auto session = &to->session();
const auto &data = gift.data();
auto parsed = FromTL(session, data.vgift());
@@ -841,8 +881,12 @@ std::optional<Data::UserStarGift> FromTL(
unique->starsForTransfer = data.vtransfer_stars().value_or(-1);
unique->exportAt = data.vcan_export_at().value_or_empty();
}
return Data::UserStarGift{
using Id = Data::SavedStarGiftId;
return Data::SavedStarGift{
.info = std::move(*parsed),
.manageId = (to->isUser()
? Id::User(data.vmsg_id().value_or_empty())
: Id::Chat(to, data.vsaved_id().value_or_empty())),
.message = (data.vmessage()
? TextWithEntities{
.text = qs(data.vmessage()->data().vtext()),
@@ -855,12 +899,12 @@ std::optional<Data::UserStarGift> FromTL(
.starsUpgradedBySender = int64(
data.vupgrade_stars().value_or_empty()),
.fromId = (data.vfrom_id()
? peerFromUser(data.vfrom_id()->v)
? peerFromMTP(*data.vfrom_id())
: PeerId()),
.messageId = data.vmsg_id().value_or_empty(),
.date = data.vdate().v,
.upgradable = data.is_can_upgrade(),
.anonymous = data.is_name_hidden(),
.pinned = data.is_pinned_to_top(),
.hidden = data.is_unsaved(),
.mine = to->isSelf(),
};
@@ -910,11 +954,9 @@ Data::UniqueGiftOriginalDetails FromTL(
auto result = Data::UniqueGiftOriginalDetails();
result.date = data.vdate().v;
result.senderId = data.vsender_id()
? peerFromUser(
UserId(data.vsender_id().value_or_empty()))
? peerFromMTP(*data.vsender_id())
: PeerId();
result.recipientId = peerFromUser(
UserId(data.vrecipient_id().v));
result.recipientId = peerFromMTP(data.vrecipient_id());
result.message = data.vmessage()
? ParseTextWithEntities(session, *data.vmessage())
: TextWithEntities();

View File

@@ -116,8 +116,9 @@ public:
[[nodiscard]] auto subscriptionOptions() const
-> const Data::PremiumSubscriptionOptions &;
[[nodiscard]] rpl::producer<> somePremiumRequiredResolved() const;
void resolvePremiumRequired(not_null<UserData*> user);
[[nodiscard]] auto someMessageMoneyRestrictionsResolved() const
-> rpl::producer<>;
void resolveMessageMoneyRestrictions(not_null<UserData*> user);
private:
void reloadPromo();
@@ -166,10 +167,10 @@ private:
Data::PremiumSubscriptionOptions _subscriptionOptions;
rpl::event_stream<> _somePremiumRequiredResolved;
base::flat_set<not_null<UserData*>> _resolvePremiumRequiredUsers;
base::flat_set<not_null<UserData*>> _resolvePremiumRequestedUsers;
bool _premiumRequiredRequestScheduled = false;
rpl::event_stream<> _someMessageMoneyRestrictionsResolved;
base::flat_set<not_null<UserData*>> _resolveMessageMoneyRequiredUsers;
base::flat_set<not_null<UserData*>> _resolveMessageMoneyRequestedUsers;
bool _messageMoneyRequestScheduled = false;
};
@@ -208,6 +209,7 @@ private:
};
struct Store final {
uint64 amount = 0;
QString currency;
QString product;
int quantity = 0;
};
@@ -218,7 +220,7 @@ private:
struct {
std::vector<int> months;
std::vector<int64> totalCosts;
QString currency;
std::vector<QString> currencies;
} _optionsForOnePerson;
std::vector<int> _availablePresets;
@@ -244,12 +246,20 @@ private:
};
enum class RequirePremiumState {
Unknown,
Yes,
No,
struct MessageMoneyRestriction {
int starsPerMessage = 0;
bool premiumRequired = false;
bool known = false;
explicit operator bool() const {
return starsPerMessage != 0 || premiumRequired;
}
friend inline bool operator==(
const MessageMoneyRestriction &,
const MessageMoneyRestriction &) = default;
};
[[nodiscard]] RequirePremiumState ResolveRequiresPremiumToWrite(
[[nodiscard]] MessageMoneyRestriction ResolveMessageMoneyRestrictions(
not_null<PeerData*> peer,
History *maybeHistory);
@@ -259,9 +269,9 @@ enum class RequirePremiumState {
[[nodiscard]] std::optional<Data::StarGift> FromTL(
not_null<Main::Session*> session,
const MTPstarGift &gift);
[[nodiscard]] std::optional<Data::UserStarGift> FromTL(
not_null<UserData*> to,
const MTPuserStarGift &gift);
[[nodiscard]] std::optional<Data::SavedStarGift> FromTL(
not_null<PeerData*> to,
const MTPsavedStarGift &gift);
[[nodiscard]] Data::UniqueGiftModel FromTL(
not_null<Main::Session*> session,

View File

@@ -26,7 +26,7 @@ Data::PremiumSubscriptionOption CreateSubscriptionOption(
}();
return {
.duration = Ui::FormatTTL(months * 86400 * 31),
.discount = discount
.discount = (discount > 0)
? QString::fromUtf8("\xe2\x88\x92%1%").arg(discount)
: QString(),
.costPerMonth = Ui::FillAmountAndCurrency(

View File

@@ -24,15 +24,26 @@ template<typename Option>
if (tlOpts.isEmpty()) {
return {};
}
auto monthlyAmountPerCurrency = base::flat_map<QString, int>();
auto result = Data::PremiumSubscriptionOptions();
const auto monthlyAmount = [&] {
const auto monthlyAmount = [&](const QString &currency) -> int {
const auto it = monthlyAmountPerCurrency.find(currency);
if (it != end(monthlyAmountPerCurrency)) {
return it->second;
}
const auto &min = ranges::min_element(
tlOpts,
ranges::less(),
[](const Option &o) { return o.data().vamount().v; }
[&](const Option &o) {
return currency == qs(o.data().vcurrency())
? o.data().vamount().v
: std::numeric_limits<int64_t>::max();
}
)->data();
return min.vamount().v / float64(min.vmonths().v);
}();
const auto monthly = min.vamount().v / float64(min.vmonths().v);
monthlyAmountPerCurrency.emplace(currency, monthly);
return monthly;
};
result.reserve(tlOpts.size());
for (const auto &tlOption : tlOpts) {
const auto &option = tlOption.data();
@@ -45,7 +56,7 @@ template<typename Option>
const auto currency = qs(option.vcurrency());
result.push_back(CreateSubscriptionOption(
months,
monthlyAmount,
monthlyAmount(currency),
amount,
currency,
botUrl));

View File

@@ -95,7 +95,9 @@ void SendSimpleMedia(SendAction action, MTPInputMedia inputMedia) {
const auto messagePostAuthor = peer->isBroadcast()
? session->user()->name()
: QString();
const auto starsPaid = std::min(
peer->starsPerMessageChecked(),
action.options.starsApproved);
if (action.options.scheduled) {
flags |= MessageFlag::IsOrWasScheduled;
sendFlags |= MTPmessages_SendMedia::Flag::f_schedule_date;
@@ -111,6 +113,10 @@ void SendSimpleMedia(SendAction action, MTPInputMedia inputMedia) {
flags |= MessageFlag::InvertMedia;
sendFlags |= MTPmessages_SendMedia::Flag::f_invert_media;
}
if (starsPaid) {
action.options.starsApproved -= starsPaid;
sendFlags |= MTPmessages_SendMedia::Flag::f_allow_paid_stars;
}
auto &histories = history->owner().histories();
histories.sendPreparedMessage(
@@ -129,7 +135,8 @@ void SendSimpleMedia(SendAction action, MTPInputMedia inputMedia) {
MTP_int(action.options.scheduled),
(sendAs ? sendAs->input : MTP_inputPeerEmpty()),
Data::ShortcutIdToMTP(session, action.options.shortcutId),
MTP_long(action.options.effectId)
MTP_long(action.options.effectId),
MTP_long(starsPaid)
), [=](const MTPUpdates &result, const MTP::Response &response) {
}, [=](const MTP::Error &error, const MTP::Response &response) {
api->sendMessageFail(error, peer, randomId);
@@ -160,7 +167,7 @@ void SendExistingMedia(
? (*localMessageId)
: session->data().nextLocalMessageId());
const auto randomId = base::RandomValue<uint64>();
const auto &action = message.action;
auto &action = message.action;
auto flags = NewMessageFlags(peer);
auto sendFlags = MTPmessages_SendMedia::Flags(0);
@@ -190,7 +197,9 @@ void SendExistingMedia(
sendFlags |= MTPmessages_SendMedia::Flag::f_entities;
}
const auto captionText = caption.text;
const auto starsPaid = std::min(
peer->starsPerMessageChecked(),
action.options.starsApproved);
if (action.options.scheduled) {
flags |= MessageFlag::IsOrWasScheduled;
sendFlags |= MTPmessages_SendMedia::Flag::f_schedule_date;
@@ -206,6 +215,10 @@ void SendExistingMedia(
flags |= MessageFlag::InvertMedia;
sendFlags |= MTPmessages_SendMedia::Flag::f_invert_media;
}
if (starsPaid) {
action.options.starsApproved -= starsPaid;
sendFlags |= MTPmessages_SendMedia::Flag::f_allow_paid_stars;
}
session->data().registerMessageRandomId(randomId, newId);
@@ -216,6 +229,7 @@ void SendExistingMedia(
.replyTo = action.replyTo,
.date = NewMessageDate(action.options),
.shortcutId = action.options.shortcutId,
.starsPaid = starsPaid,
.postAuthor = NewMessagePostAuthor(action),
.effectId = action.options.effectId,
}, media, caption);
@@ -240,7 +254,8 @@ void SendExistingMedia(
MTP_int(action.options.scheduled),
(sendAs ? sendAs->input : MTP_inputPeerEmpty()),
Data::ShortcutIdToMTP(session, action.options.shortcutId),
MTP_long(action.options.effectId)
MTP_long(action.options.effectId),
MTP_long(starsPaid)
), [=](const MTPUpdates &result, const MTP::Response &response) {
}, [=](const MTP::Error &error, const MTP::Response &response) {
if (error.code() == 400
@@ -272,7 +287,9 @@ void SendExistingDocument(
return MTP_inputMediaDocument(
MTP_flags(0),
document->mtpInput(),
MTPInputPhoto(), // video_cover
MTPint(), // ttl_seconds
MTPint(), // video_timestamp
MTPstring()); // query
};
SendExistingMedia(
@@ -339,7 +356,7 @@ bool SendDice(MessageToSend &message) {
message.action.generateLocal = true;
const auto &action = message.action;
auto &action = message.action;
api->sendAction(action);
const auto newId = FullMsgId(
@@ -378,6 +395,13 @@ bool SendDice(MessageToSend &message) {
flags |= MessageFlag::InvertMedia;
sendFlags |= MTPmessages_SendMedia::Flag::f_invert_media;
}
const auto starsPaid = std::min(
peer->starsPerMessageChecked(),
action.options.starsApproved);
if (starsPaid) {
action.options.starsApproved -= starsPaid;
sendFlags |= MTPmessages_SendMedia::Flag::f_allow_paid_stars;
}
session->data().registerMessageRandomId(randomId, newId);
@@ -388,6 +412,7 @@ bool SendDice(MessageToSend &message) {
.replyTo = action.replyTo,
.date = NewMessageDate(action.options),
.shortcutId = action.options.shortcutId,
.starsPaid = starsPaid,
.postAuthor = NewMessagePostAuthor(action),
.effectId = action.options.effectId,
}, TextWithEntities(), MTP_messageMediaDice(
@@ -409,7 +434,8 @@ bool SendDice(MessageToSend &message) {
MTP_int(action.options.scheduled),
(sendAs ? sendAs->input : MTP_inputPeerEmpty()),
Data::ShortcutIdToMTP(session, action.options.shortcutId),
MTP_long(action.options.effectId)
MTP_long(action.options.effectId),
MTP_long(starsPaid)
), [=](const MTPUpdates &result, const MTP::Response &response) {
}, [=](const MTP::Error &error, const MTP::Response &response) {
api->sendMessageFail(error, peer, randomId, newId);
@@ -547,9 +573,12 @@ void SendConfirmedFile(
using Flag = MTPDmessageMediaDocument::Flag;
return MTP_messageMediaDocument(
MTP_flags(Flag::f_document
| (file->spoiler ? Flag::f_spoiler : Flag())),
| (file->spoiler ? Flag::f_spoiler : Flag())
| (file->videoCover ? Flag::f_video_cover : Flag())),
file->document,
MTPVector<MTPDocument>(), // alt_documents
file->videoCover ? file->videoCover->photo : MTPPhoto(),
MTPint(), // video_timestamp
MTPint());
} else if (file->type == SendMediaType::Audio) {
const auto ttlSeconds = file->to.options.ttlSeconds;
@@ -557,9 +586,12 @@ void SendConfirmedFile(
return MTP_messageMediaDocument(
MTP_flags(Flag::f_document
| Flag::f_voice
| (ttlSeconds ? Flag::f_ttl_seconds : Flag())),
| (ttlSeconds ? Flag::f_ttl_seconds : Flag())
| (file->videoCover ? Flag::f_video_cover : Flag())),
file->document,
MTPVector<MTPDocument>(), // alt_documents
file->videoCover ? file->videoCover->photo : MTPPhoto(),
MTPint(), // video_timestamp
MTP_int(ttlSeconds));
} else if (file->type == SendMediaType::Round) {
using Flag = MTPDmessageMediaDocument::Flag;
@@ -571,6 +603,8 @@ void SendConfirmedFile(
| (file->spoiler ? Flag::f_spoiler : Flag())),
file->document,
MTPVector<MTPDocument>(), // alt_documents
MTPPhoto(), // video_cover
MTPint(), // video_timestamp
MTP_int(ttlSeconds));
} else {
Unexpected("Type in sendFilesConfirmed.");
@@ -600,6 +634,9 @@ void SendConfirmedFile(
.replyTo = file->to.replyTo,
.date = NewMessageDate(file->to.options),
.shortcutId = file->to.options.shortcutId,
.starsPaid = std::min(
history->peer->starsPerMessageChecked(),
file->to.options.starsApproved),
.postAuthor = NewMessagePostAuthor(action),
.groupedId = groupId,
.effectId = file->to.options.effectId,

View File

@@ -1219,7 +1219,8 @@ void Updates::applyUpdatesNoPtsCheck(const MTPUpdates &updates) {
MTPint(), // quick_reply_shortcut_id
MTPlong(), // effect
MTPFactCheck(),
MTPint()), // report_delivery_until_date
MTPint(), // report_delivery_until_date
MTPlong()), // paid_message_stars
MessageFlags(),
NewMessageType::Unread);
} break;
@@ -1257,7 +1258,8 @@ void Updates::applyUpdatesNoPtsCheck(const MTPUpdates &updates) {
MTPint(), // quick_reply_shortcut_id
MTPlong(), // effect
MTPFactCheck(),
MTPint()), // report_delivery_until_date
MTPint(), // report_delivery_until_date
MTPlong()), // paid_message_stars
MessageFlags(),
NewMessageType::Unread);
} break;
@@ -2709,8 +2711,8 @@ void Updates::feedUpdate(const MTPUpdate &update) {
case mtpc_updatePaidReactionPrivacy: {
const auto &data = update.c_updatePaidReactionPrivacy();
_session->api().globalPrivacy().updatePaidReactionAnonymous(
mtpIsTrue(data.vprivate()));
_session->api().globalPrivacy().updatePaidReactionShownPeer(
Api::ParsePaidReactionShownPeer(_session, data.vprivate()));
} break;
}

View File

@@ -210,6 +210,7 @@ MTPInputPrivacyKey KeyToTL(UserPrivacy::Key key) {
case Key::About: return MTP_inputPrivacyKeyAbout();
case Key::Birthday: return MTP_inputPrivacyKeyBirthday();
case Key::GiftsAutoSave: return MTP_inputPrivacyKeyStarGiftsAutoSave();
case Key::NoPaidMessages: return MTP_inputPrivacyKeyNoPaidMessages();
}
Unexpected("Key in Api::UserPrivacy::KetToTL.");
}
@@ -241,6 +242,8 @@ std::optional<UserPrivacy::Key> TLToKey(mtpTypeId type) {
case mtpc_inputPrivacyKeyBirthday: return Key::Birthday;
case mtpc_privacyKeyStarGiftsAutoSave:
case mtpc_inputPrivacyKeyStarGiftsAutoSave: return Key::GiftsAutoSave;
case mtpc_privacyKeyNoPaidMessages:
case mtpc_inputPrivacyKeyNoPaidMessages: return Key::NoPaidMessages;
}
return std::nullopt;
}

View File

@@ -32,6 +32,7 @@ public:
About,
Birthday,
GiftsAutoSave,
NoPaidMessages,
};
enum class Option {
Everyone,

View File

@@ -142,6 +142,16 @@ void ShowChannelsLimitBox(not_null<PeerData*> peer) {
action.replaceMediaOf);
}
[[nodiscard]] QString FormatVideoTimestamp(TimeId seconds) {
const auto minutes = seconds / 60;
const auto hours = minutes / 60;
return hours
? u"%1h%2m%3s"_q.arg(hours).arg(minutes % 60).arg(seconds % 60)
: minutes
? u"%1m%2s"_q.arg(minutes).arg(seconds % 60)
: QString::number(seconds);
}
} // namespace
ApiWrap::ApiWrap(not_null<Main::Session*> session)
@@ -513,6 +523,7 @@ void ApiWrap::sendMessageFail(
uint64 randomId,
FullMsgId itemId) {
const auto show = ShowForPeer(peer);
const auto paidStarsPrefix = u"ALLOW_PAYMENT_REQUIRED_"_q;
if (show && error == u"PEER_FLOOD"_q) {
show->showBox(
Ui::MakeInformBox(
@@ -567,6 +578,19 @@ void ApiWrap::sendMessageFail(
if (show) {
show->showToast(tr::lng_error_schedule_limit(tr::now));
}
} else if (error.startsWith(paidStarsPrefix)) {
if (show) {
show->showToast(
u"Payment requirements changed. Please, try again."_q);
}
if (const auto stars = error.mid(paidStarsPrefix.size()).toInt()) {
if (const auto user = peer->asUser()) {
user->setStarsPerMessage(stars);
} else if (const auto channel = peer->asChannel()) {
channel->setStarsPerMessage(stars);
}
}
peer->updateFull();
}
if (const auto item = _session->data().message(itemId)) {
Assert(randomId != 0);
@@ -717,7 +741,8 @@ void ApiWrap::finalizeMessageDataRequest(
QString ApiWrap::exportDirectMessageLink(
not_null<HistoryItem*> item,
bool inRepliesContext,
bool forceNonPublicLink) {
bool forceNonPublicLink,
std::optional<TimeId> videoTimestamp) {
Expects(item->history()->peer->isChannel());
const auto itemId = item->fullId();
@@ -769,19 +794,6 @@ QString ApiWrap::exportDirectMessageLink(
: linkThreadId
? (QString::number(linkThreadId.bare) + '/' + post)
: post);
if (linkChannel->hasUsername()
&& !forceNonPublicLink
&& !linkChannel->isMegagroup()
&& !linkCommentId
&& !linkThreadId) {
if (const auto media = item->media()) {
if (const auto document = media->document()) {
if (document->isVideoMessage()) {
return u"https://telesco.pe/"_q + query;
}
}
}
}
return session().createInternalLinkFull(query);
};
if (forceNonPublicLink) {
@@ -803,7 +815,14 @@ QString ApiWrap::exportDirectMessageLink(
_unlikelyMessageLinks.emplace_or_assign(itemId, link);
}
}).send();
return current;
const auto addTimestamp = channel->hasUsername()
&& !inRepliesContext
&& videoTimestamp.has_value();
const auto addedSeparator = (current.indexOf('?') >= 0) ? '&' : '?';
const auto addedTimestamp = addTimestamp
? (addedSeparator + u"t="_q + FormatVideoTimestamp(*videoTimestamp))
: QString();
return current + addedTimestamp;
}
QString ApiWrap::exportDirectStoryLink(not_null<Data::Story*> story) {
@@ -1772,7 +1791,7 @@ void ApiWrap::joinChannel(not_null<ChannelData*> channel) {
_channelAmInRequests.emplace(channel, requestId);
using Flag = ChannelDataFlag;
chatParticipants().loadSimilarChannels(channel);
chatParticipants().loadSimilarPeers(channel);
channel->setFlags(channel->flags() | Flag::SimilarExpanded);
}
}
@@ -3293,7 +3312,7 @@ void ApiWrap::finishForwarding(const SendAction &action) {
void ApiWrap::forwardMessages(
Data::ResolvedForwardDraft &&draft,
const SendAction &action,
SendAction action,
FnMut<void()> &&successCallback) {
Expects(!draft.items.empty());
@@ -3368,9 +3387,17 @@ void ApiWrap::forwardMessages(
const auto requestType = Data::Histories::RequestType::Send;
const auto idsCopy = localIds;
const auto scheduled = action.options.scheduled;
const auto starsPaid = std::min(
action.options.starsApproved,
int(ids.size() * peer->starsPerMessageChecked()));
auto oneFlags = sendFlags;
if (starsPaid) {
action.options.starsApproved -= starsPaid;
oneFlags |= SendFlag::f_allow_paid_stars;
}
histories.sendRequest(history, requestType, [=](Fn<void()> finish) {
history->sendRequestId = request(MTPmessages_ForwardMessages(
MTP_flags(sendFlags),
MTP_flags(oneFlags),
forwardFrom->input,
MTP_vector<MTPint>(ids),
MTP_vector<MTPlong>(randomIds),
@@ -3378,7 +3405,9 @@ void ApiWrap::forwardMessages(
MTP_int(topMsgId),
MTP_int(action.options.scheduled),
(sendAs ? sendAs->input : MTP_inputPeerEmpty()),
Data::ShortcutIdToMTP(_session, action.options.shortcutId)
Data::ShortcutIdToMTP(_session, action.options.shortcutId),
MTPint(), // video_timestamp
MTP_long(starsPaid)
)).done([=](const MTPUpdates &result) {
if (!scheduled) {
this->updates().checkForSentToScheduled(result);
@@ -3423,6 +3452,7 @@ void ApiWrap::forwardMessages(
.replyTo = { .topicRootId = topMsgId },
.date = NewMessageDate(action.options),
.shortcutId = action.options.shortcutId,
.starsPaid = action.options.starsApproved,
.postAuthor = NewMessagePostAuthor(action),
// forwarded messages don't have effects
@@ -3516,6 +3546,7 @@ void ApiWrap::sendSharedContact(
.replyTo = action.replyTo,
.date = NewMessageDate(action.options),
.shortcutId = action.options.shortcutId,
.starsPaid = action.options.starsApproved,
.postAuthor = NewMessagePostAuthor(action),
.effectId = action.options.effectId,
}, TextWithEntities(), MTP_messageMediaContact(
@@ -3572,6 +3603,18 @@ void ApiWrap::editMedia(
file.path,
file.content,
std::move(file.information),
(file.videoCover
? std::make_unique<FileLoadTask>(
&session(),
file.videoCover->path,
file.videoCover->content,
std::move(file.videoCover->information),
nullptr,
SendMediaType::Photo,
to,
TextWithTags(),
false)
: nullptr),
type,
to,
caption,
@@ -3613,6 +3656,19 @@ void ApiWrap::sendFiles(
file.path,
file.content,
std::move(file.information),
(file.videoCover
? std::make_unique<FileLoadTask>(
&session(),
file.videoCover->path,
file.videoCover->content,
std::move(file.videoCover->information),
nullptr,
SendMediaType::Photo,
to,
TextWithTags(),
false,
nullptr)
: nullptr),
uploadWithType,
to,
caption,
@@ -3638,11 +3694,13 @@ void ApiWrap::sendFile(
auto caption = TextWithTags();
const auto spoiler = false;
const auto information = nullptr;
const auto videoCover = nullptr;
_fileLoader->addTask(std::make_unique<FileLoadTask>(
&session(),
QString(),
fileContent,
information,
videoCover,
type,
to,
caption,
@@ -3849,6 +3907,14 @@ void ApiWrap::sendMessage(MessageToSend &&message) {
sendFlags |= MTPmessages_SendMessage::Flag::f_effect;
mediaFlags |= MTPmessages_SendMedia::Flag::f_effect;
}
const auto starsPaid = std::min(
peer->starsPerMessageChecked(),
action.options.starsApproved);
if (starsPaid) {
action.options.starsApproved -= starsPaid;
sendFlags |= MTPmessages_SendMessage::Flag::f_allow_paid_stars;
mediaFlags |= MTPmessages_SendMedia::Flag::f_allow_paid_stars;
}
lastMessage = history->addNewLocalMessage({
.id = newId.msg,
.flags = flags,
@@ -3856,6 +3922,7 @@ void ApiWrap::sendMessage(MessageToSend &&message) {
.replyTo = action.replyTo,
.date = NewMessageDate(action.options),
.shortcutId = action.options.shortcutId,
.starsPaid = starsPaid,
.postAuthor = NewMessagePostAuthor(action),
.effectId = action.options.effectId,
}, sending, media);
@@ -3904,7 +3971,8 @@ void ApiWrap::sendMessage(MessageToSend &&message) {
MTP_int(action.options.scheduled),
(sendAs ? sendAs->input : MTP_inputPeerEmpty()),
mtpShortcut,
MTP_long(action.options.effectId)
MTP_long(action.options.effectId),
MTP_long(starsPaid)
), done, fail);
} else {
histories.sendPreparedMessage(
@@ -3922,7 +3990,8 @@ void ApiWrap::sendMessage(MessageToSend &&message) {
MTP_int(action.options.scheduled),
(sendAs ? sendAs->input : MTP_inputPeerEmpty()),
mtpShortcut,
MTP_long(action.options.effectId)
MTP_long(action.options.effectId),
MTP_long(starsPaid)
), done, fail);
}
isFirst = false;
@@ -3979,7 +4048,7 @@ void ApiWrap::sendBotStart(
void ApiWrap::sendInlineResult(
not_null<UserData*> bot,
not_null<InlineBots::Result*> data,
const SendAction &action,
SendAction action,
std::optional<MsgId> localMessageId,
Fn<void(bool)> done) {
sendAction(action);
@@ -4019,6 +4088,13 @@ void ApiWrap::sendInlineResult(
if (action.options.hideViaBot) {
sendFlags |= SendFlag::f_hide_via;
}
const auto starsPaid = std::min(
peer->starsPerMessageChecked(),
action.options.starsApproved);
if (starsPaid) {
action.options.starsApproved -= starsPaid;
sendFlags |= SendFlag::f_allow_paid_stars;
}
const auto sendAs = action.options.sendAs;
if (sendAs) {
@@ -4033,6 +4109,7 @@ void ApiWrap::sendInlineResult(
.replyTo = action.replyTo,
.date = NewMessageDate(action.options),
.shortcutId = action.options.shortcutId,
.starsPaid = starsPaid,
.viaBotId = ((bot && !action.options.hideViaBot)
? peerToUser(bot->id)
: UserId()),
@@ -4056,7 +4133,8 @@ void ApiWrap::sendInlineResult(
MTP_string(data->getId()),
MTP_int(action.options.scheduled),
(sendAs ? sendAs->input : MTP_inputPeerEmpty()),
Data::ShortcutIdToMTP(_session, action.options.shortcutId)
Data::ShortcutIdToMTP(_session, action.options.shortcutId),
MTP_long(starsPaid)
), [=](const MTPUpdates &result, const MTP::Response &response) {
history->finishSavingCloudDraft(
topicRootId,
@@ -4136,16 +4214,29 @@ void ApiWrap::uploadAlbumMedia(
return;
}
const auto &fields = document->c_document();
const auto mtpCover = data.vvideo_cover();
const auto cover = (mtpCover && mtpCover->type() == mtpc_photo)
? &(mtpCover->c_photo())
: (const MTPDphoto*)nullptr;
using Flag = MTPDinputMediaDocument::Flag;
const auto flags = Flag()
| (data.vttl_seconds() ? Flag::f_ttl_seconds : Flag())
| (spoiler ? Flag::f_spoiler : Flag());
| (spoiler ? Flag::f_spoiler : Flag())
| (data.vvideo_timestamp() ? Flag::f_video_timestamp : Flag())
| (cover ? Flag::f_video_cover : Flag());
const auto media = MTP_inputMediaDocument(
MTP_flags(flags),
MTP_inputDocument(
fields.vid(),
fields.vaccess_hash(),
fields.vfile_reference()),
(cover
? MTP_inputPhoto(
cover->vid(),
cover->vaccess_hash(),
cover->vfile_reference())
: MTPInputPhoto()),
MTP_int(data.vvideo_timestamp().value_or_empty()),
MTP_int(data.vttl_seconds().value_or_empty()),
MTPstring()); // query
sendAlbumWithUploaded(item, groupId, media);
@@ -4175,6 +4266,7 @@ void ApiWrap::sendMediaWithRandomId(
Fn<void(bool)> done) {
const auto history = item->history();
const auto replyTo = item->replyTo();
const auto peer = history->peer;
auto caption = item->originalText();
TextUtilities::Trim(caption);
@@ -4184,6 +4276,12 @@ void ApiWrap::sendMediaWithRandomId(
Api::ConvertOption::SkipLocal);
const auto updateRecentStickers = Api::HasAttachedStickers(media);
const auto starsPaid = std::min(
peer->starsPerMessageChecked(),
options.starsApproved);
if (starsPaid) {
options.starsApproved -= starsPaid;
}
using Flag = MTPmessages_SendMedia::Flag;
const auto flags = Flag(0)
@@ -4196,10 +4294,10 @@ void ApiWrap::sendMediaWithRandomId(
| (options.sendAs ? Flag::f_send_as : Flag(0))
| (options.shortcutId ? Flag::f_quick_reply_shortcut : Flag(0))
| (options.effectId ? Flag::f_effect : Flag(0))
| (options.invertCaption ? Flag::f_invert_media : Flag(0));
| (options.invertCaption ? Flag::f_invert_media : Flag(0))
| (starsPaid ? Flag::f_allow_paid_stars : Flag(0));
auto &histories = history->owner().histories();
const auto peer = history->peer;
const auto itemId = item->fullId();
histories.sendPreparedMessage(
history,
@@ -4223,7 +4321,8 @@ void ApiWrap::sendMediaWithRandomId(
MTP_int(options.scheduled),
(options.sendAs ? options.sendAs->input : MTP_inputPeerEmpty()),
Data::ShortcutIdToMTP(_session, options.shortcutId),
MTP_long(options.effectId)
MTP_long(options.effectId),
MTP_long(starsPaid)
), [=](const MTPUpdates &result, const MTP::Response &response) {
if (done) done(true);
if (updateRecentStickers) {
@@ -4242,7 +4341,7 @@ void ApiWrap::sendMultiPaidMedia(
Expects(album->options.price > 0);
const auto groupId = album->groupId;
const auto &options = album->options;
auto &options = album->options;
const auto randomId = album->items.front().randomId;
auto medias = album->items | ranges::view::transform([](
const SendingAlbum::Item &part) {
@@ -4252,6 +4351,7 @@ void ApiWrap::sendMultiPaidMedia(
const auto history = item->history();
const auto replyTo = item->replyTo();
const auto peer = history->peer;
auto caption = item->originalText();
TextUtilities::Trim(caption);
@@ -4259,6 +4359,12 @@ void ApiWrap::sendMultiPaidMedia(
_session,
caption.entities,
Api::ConvertOption::SkipLocal);
const auto starsPaid = std::min(
peer->starsPerMessageChecked(),
options.starsApproved);
if (starsPaid) {
options.starsApproved -= starsPaid;
}
using Flag = MTPmessages_SendMedia::Flag;
const auto flags = Flag(0)
@@ -4271,10 +4377,10 @@ void ApiWrap::sendMultiPaidMedia(
| (options.sendAs ? Flag::f_send_as : Flag(0))
| (options.shortcutId ? Flag::f_quick_reply_shortcut : Flag(0))
| (options.effectId ? Flag::f_effect : Flag(0))
| (options.invertCaption ? Flag::f_invert_media : Flag(0));
| (options.invertCaption ? Flag::f_invert_media : Flag(0))
| (starsPaid ? Flag::f_allow_paid_stars : Flag(0));
auto &histories = history->owner().histories();
const auto peer = history->peer;
const auto itemId = item->fullId();
album->sent = true;
histories.sendPreparedMessage(
@@ -4297,7 +4403,8 @@ void ApiWrap::sendMultiPaidMedia(
MTP_int(options.scheduled),
(options.sendAs ? options.sendAs->input : MTP_inputPeerEmpty()),
Data::ShortcutIdToMTP(_session, options.shortcutId),
MTP_long(options.effectId)
MTP_long(options.effectId),
MTP_long(starsPaid)
), [=](const MTPUpdates &result, const MTP::Response &response) {
if (const auto album = _sendingAlbums.take(groupId)) {
const auto copy = (*album)->items;
@@ -4387,6 +4494,12 @@ void ApiWrap::sendAlbumIfReady(not_null<SendingAlbum*> album) {
const auto history = sample->history();
const auto replyTo = sample->replyTo();
const auto sendAs = album->options.sendAs;
const auto starsPaid = std::min(
history->peer->starsPerMessageChecked() * int(medias.size()),
album->options.starsApproved);
if (starsPaid) {
album->options.starsApproved -= starsPaid;
}
using Flag = MTPmessages_SendMultiMedia::Flag;
const auto flags = Flag(0)
| (replyTo ? Flag::f_reply_to : Flag(0))
@@ -4399,7 +4512,8 @@ void ApiWrap::sendAlbumIfReady(not_null<SendingAlbum*> album) {
? Flag::f_quick_reply_shortcut
: Flag(0))
| (album->options.effectId ? Flag::f_effect : Flag(0))
| (album->options.invertCaption ? Flag::f_invert_media : Flag(0));
| (album->options.invertCaption ? Flag::f_invert_media : Flag(0))
| (starsPaid ? Flag::f_allow_paid_stars : Flag(0));
auto &histories = history->owner().histories();
const auto peer = history->peer;
album->sent = true;
@@ -4415,7 +4529,8 @@ void ApiWrap::sendAlbumIfReady(not_null<SendingAlbum*> album) {
MTP_int(album->options.scheduled),
(sendAs ? sendAs->input : MTP_inputPeerEmpty()),
Data::ShortcutIdToMTP(_session, album->options.shortcutId),
MTP_long(album->options.effectId)
MTP_long(album->options.effectId),
MTP_long(starsPaid)
), [=](const MTPUpdates &result, const MTP::Response &response) {
_sendingAlbums.remove(groupId);
}, [=](const MTP::Error &error, const MTP::Response &response) {

View File

@@ -166,7 +166,8 @@ public:
QString exportDirectMessageLink(
not_null<HistoryItem*> item,
bool inRepliesContext,
bool forceNonPublicLink = false);
bool forceNonPublicLink = false,
std::optional<TimeId> videoTimestamp = {});
QString exportDirectStoryLink(not_null<Data::Story*> item);
void requestContacts();
@@ -305,7 +306,7 @@ public:
void finishForwarding(const SendAction &action);
void forwardMessages(
Data::ResolvedForwardDraft &&draft,
const SendAction &action,
SendAction action,
FnMut<void()> &&successCallback = nullptr);
void shareContact(
const QString &phone,
@@ -367,7 +368,7 @@ public:
void sendInlineResult(
not_null<UserData*> bot,
not_null<InlineBots::Result*> data,
const SendAction &action,
SendAction action,
std::optional<MsgId> localMessageId,
Fn<void(bool)> done = nullptr);
void sendMessageFail(

View File

@@ -1078,7 +1078,9 @@ void BackgroundPreviewBox::updateServiceBg(const std::vector<QColor> &bg) {
? tr::lng_background_other_group(tr::now)
: forChannel()
? tr::lng_background_other_channel(tr::now)
: (_forPeer && !_fromMessageId)
: (_forPeer
&& !_fromMessageId
&& !_forPeer->starsPerMessageChecked())
? tr::lng_background_other_info(
tr::now,
lt_user,

View File

@@ -1122,3 +1122,10 @@ profileQrBackgroundRadius: 12px;
profileQrIcon: icon{{ "qr_mini", windowActiveTextFg }};
profileQrBackgroundMargins: margins(36px, 12px, 36px, 12px);
profileQrBackgroundPadding: margins(0px, 24px, 0px, 24px);
foldersMenu: PopupMenu(popupMenuWithIcons) {
maxHeight: 320px;
menu: Menu(menuWithIcons) {
itemPadding: margins(54px, 8px, 44px, 8px);
}
}

View File

@@ -172,13 +172,6 @@ void ChangeFilterById(
const auto account = not_null(&history->session().account());
if (const auto controller = Core::App().windowFor(account)) {
const auto isStatic = name.isStatic;
const auto textContext = [=](not_null<QWidget*> widget) {
return Core::MarkedTextContext{
.session = &history->session(),
.customEmojiRepaint = [=] { widget->update(); },
.customEmojiLoopLimit = isStatic ? -1 : 0,
};
};
controller->showToast({
.text = (add
? tr::lng_filters_toast_add
@@ -189,7 +182,10 @@ void ChangeFilterById(
lt_folder,
Ui::Text::Wrapped(name.text, EntityType::Bold),
Ui::Text::WithEntities),
.textContext = textContext,
.textContext = Core::TextContext({
.session = &history->session(),
.customEmojiLoopLimit = isStatic ? -1 : 0,
}),
});
}
}).fail([=](const MTP::Error &error) {
@@ -290,19 +286,18 @@ void FillChooseFilterMenu(
const auto title = filter.title();
auto item = base::make_unique_q<FilterAction>(
menu.get(),
st::foldersMenu,
menu->st().menu,
Ui::Menu::CreateAction(
menu.get(),
Ui::Text::FixAmpersandInAction(title.text.text),
std::move(callback)),
contains ? &st::mediaPlayerMenuCheck : nullptr,
contains ? &st::mediaPlayerMenuCheck : nullptr);
const auto context = Core::MarkedTextContext{
item->setMarkedText(title.text, QString(), Core::TextContext({
.session = &history->session(),
.customEmojiRepaint = [raw = item.get()] { raw->update(); },
.repaint = [raw = item.get()] { raw->update(); },
.customEmojiLoopLimit = title.isStatic ? -1 : 0,
};
item->setMarkedText(title.text, QString(), context);
}));
item->setIcon(Icon(showColors ? filter : filter.withColorIndex({})));
const auto action = menu->addAction(std::move(item));

View File

@@ -817,13 +817,15 @@ CreatePollBox::CreatePollBox(
not_null<Window::SessionController*> controller,
PollData::Flags chosen,
PollData::Flags disabled,
rpl::producer<int> starsRequired,
Api::SendType sendType,
SendMenu::Details sendMenuDetails)
: _controller(controller)
, _chosen(chosen)
, _disabled(disabled)
, _sendType(sendType)
, _sendMenuDetails([result = sendMenuDetails] { return result; }) {
, _sendMenuDetails([result = sendMenuDetails] { return result; })
, _starsRequired(std::move(starsRequired)) {
}
rpl::producer<CreatePollBox::Result> CreatePollBox::submitRequests() const {
@@ -1226,10 +1228,11 @@ object_ptr<Ui::RpWidget> CreatePollBox::setupContent() {
_sendMenuDetails());
};
const auto submit = addButton(
(isNormal
? tr::lng_polls_create_button()
: tr::lng_schedule_button()),
tr::lng_polls_create_button(),
[=] { isNormal ? send({}) : schedule(); });
submit->setText(PaidSendButtonText(_starsRequired.value(), isNormal
? tr::lng_polls_create_button()
: tr::lng_schedule_button()));
const auto sendMenuDetails = [=] {
collectError();
return (*error) ? SendMenu::Details() : _sendMenuDetails();

View File

@@ -42,6 +42,7 @@ public:
not_null<Window::SessionController*> controller,
PollData::Flags chosen,
PollData::Flags disabled,
rpl::producer<int> starsRequired,
Api::SendType sendType,
SendMenu::Details sendMenuDetails);
@@ -76,6 +77,7 @@ private:
const PollData::Flags _disabled = PollData::Flags();
const Api::SendType _sendType = Api::SendType();
const Fn<SendMenu::Details()> _sendMenuDetails;
rpl::variable<int> _starsRequired;
base::unique_qptr<ChatHelpers::TabbedPanel> _emojiPanel;
Fn<void()> _setInnerFocus;
Fn<rpl::producer<bool>()> _dataIsValidValue;

View File

@@ -271,7 +271,8 @@ void DeleteMessagesBox::prepare() {
appendDetails({
tr::lng_delete_for_me_chat_hint(tr::now, lt_count, count)
});
} else if (!peer->isSelf()) {
} else if (!peer->isSelf()
&& (!peer->isUser() || !peer->asUser()->isInaccessible())) {
if (const auto user = peer->asUser(); user && user->isBot()) {
_revokeForBot = true;
}

View File

@@ -467,13 +467,16 @@ void EditCaptionBox::rebuildPreview() {
}
} else {
const auto &file = _preparedList.files.front();
const auto isVideoFile = file.isVideoFile();
const auto media = Ui::SingleMediaPreview::Create(
this,
st::defaultComposeControls,
gifPaused,
file,
[] { return true; },
[=](Ui::AttachActionType type) {
return (type != Ui::AttachActionType::EditCover)
|| isVideoFile;
},
Ui::AttachControls::Type::EditOnly);
_isPhoto = (media && media->isPhoto());
const auto withCheckbox = _isPhoto && CanBeCompressed(_albumType);
@@ -719,7 +722,7 @@ void EditCaptionBox::setupPhotoEditorEventHandler() {
controller->uiShow(),
&_preparedList.files.front(),
st::sendMediaPreviewSize,
[=] { rebuildPreview(); });
[=](bool ok) { if (ok) rebuildPreview(); });
} else {
EditPhotoImage(_controller, _photoMedia, hasSpoiler(), [=](
Ui::PreparedList &&list) {

View File

@@ -12,7 +12,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/effects/premium_graphics.h"
#include "ui/layers/generic_box.h"
#include "ui/widgets/checkbox.h"
#include "ui/widgets/continuous_sliders.h"
#include "ui/widgets/shadow.h"
#include "ui/text/format_values.h"
#include "ui/text/text_utilities.h"
#include "ui/toast/toast.h"
#include "ui/wrap/slide_wrap.h"
@@ -21,6 +23,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "history/history.h"
#include "boxes/peer_list_controllers.h"
#include "settings/settings_premium.h"
#include "settings/settings_privacy_controllers.h"
#include "settings/settings_privacy_security.h"
#include "calls/calls_instance.h"
#include "lang/lang_keys.h"
@@ -42,6 +45,8 @@ namespace {
constexpr auto kPremiumsRowId = PeerId(FakeChatId(BareId(1))).value;
constexpr auto kMiniAppsRowId = PeerId(FakeChatId(BareId(2))).value;
constexpr auto kStarsMin = 1;
constexpr auto kDefaultChargeStars = 10;
using Exceptions = Api::UserPrivacy::Exceptions;
@@ -452,6 +457,143 @@ auto PrivacyExceptionsBoxController::createRow(not_null<History*> history)
return result;
}
[[nodiscard]] object_ptr<Ui::RpWidget> MakeChargeStarsSlider(
QWidget *parent,
not_null<const style::MediaSlider*> sliderStyle,
not_null<const style::FlatLabel*> labelStyle,
int valuesCount,
Fn<int(int)> valueByIndex,
int value,
int maxValue,
Fn<void(int)> valueProgress,
Fn<void(int)> valueFinished) {
auto result = object_ptr<Ui::VerticalLayout>(parent);
const auto raw = result.data();
const auto labels = raw->add(object_ptr<Ui::RpWidget>(raw));
const auto min = Ui::CreateChild<Ui::FlatLabel>(
raw,
QString::number(kStarsMin),
*labelStyle);
const auto max = Ui::CreateChild<Ui::FlatLabel>(
raw,
QString::number(maxValue),
*labelStyle);
const auto current = Ui::CreateChild<Ui::FlatLabel>(
raw,
QString::number(value),
*labelStyle);
min->setTextColorOverride(st::windowSubTextFg->c);
max->setTextColorOverride(st::windowSubTextFg->c);
const auto slider = raw->add(object_ptr<Ui::MediaSliderWheelless>(
raw,
*sliderStyle));
labels->resize(
labels->width(),
current->height() + st::defaultVerticalListSkip);
struct State {
int indexMin = 0;
int index = 0;
};
const auto state = raw->lifetime().make_state<State>();
const auto updateByIndex = [=] {
const auto outer = labels->width();
const auto minWidth = min->width();
const auto maxWidth = max->width();
const auto currentWidth = current->width();
if (minWidth + maxWidth + currentWidth > outer) {
return;
}
min->moveToLeft(0, 0, outer);
max->moveToRight(0, 0, outer);
current->moveToLeft((outer - current->width()) / 2, 0, outer);
};
const auto updateByValue = [=](int value) {
current->setText(
tr::lng_action_gift_for_stars(tr::now, lt_count, value));
state->index = 0;
auto maxIndex = valuesCount - 1;
while (state->index < maxIndex) {
const auto mid = (state->index + maxIndex) / 2;
const auto midValue = valueByIndex(mid);
if (midValue == value) {
state->index = mid;
break;
} else if (midValue < value) {
state->index = mid + 1;
} else {
maxIndex = mid - 1;
}
}
updateByIndex();
};
const auto progress = [=](int value) {
updateByValue(value);
valueProgress(value);
};
const auto finished = [=](int value) {
updateByValue(value);
valueFinished(value);
};
style::PaletteChanged() | rpl::start_with_next([=] {
min->setTextColorOverride(st::windowSubTextFg->c);
max->setTextColorOverride(st::windowSubTextFg->c);
}, raw->lifetime());
updateByValue(value);
state->indexMin = 0;
slider->setPseudoDiscrete(
valuesCount,
valueByIndex,
value,
progress,
finished,
state->indexMin);
slider->resize(slider->width(), sliderStyle->seekSize.height());
raw->widthValue() | rpl::start_with_next([=](int width) {
labels->resizeToWidth(width);
updateByIndex();
}, slider->lifetime());
return result;
}
void EditNoPaidMessagesExceptions(
not_null<Window::SessionController*> window,
const Api::UserPrivacy::Rule &value) {
auto controller = std::make_unique<PrivacyExceptionsBoxController>(
&window->session(),
tr::lng_messages_privacy_remove_fee(),
value.always,
std::optional<SpecialRowType>());
auto initBox = [=, controller = controller.get()](
not_null<PeerListBox*> box) {
box->addButton(tr::lng_settings_save(), [=] {
auto copy = value;
auto &setTo = copy.always;
setTo.peers = box->collectSelectedRows();
setTo.premiums = false;
setTo.miniapps = false;
auto &removeFrom = copy.never;
for (const auto peer : setTo.peers) {
removeFrom.peers.erase(
ranges::remove(removeFrom.peers, peer),
end(removeFrom.peers));
}
window->session().api().userPrivacy().save(
Api::UserPrivacy::Key::NoPaidMessages,
copy);
box->closeBox();
});
box->addButton(tr::lng_cancel(), [=] { box->closeBox(); });
};
window->show(
Box<PeerListBox>(std::move(controller), std::move(initBox)));
}
} // namespace
bool EditPrivacyController::hasOption(Option option) const {
@@ -812,19 +954,27 @@ void EditMessagesPrivacyBox(
constexpr auto kOptionAll = 0;
constexpr auto kOptionPremium = 1;
constexpr auto kOptionCharge = 2;
const auto session = &controller->session();
const auto allowed = [=] {
return controller->session().premium()
|| controller->session().appConfig().newRequirePremiumFree();
return session->premium()
|| session->appConfig().newRequirePremiumFree();
};
const auto privacy = &controller->session().api().globalPrivacy();
const auto privacy = &session->api().globalPrivacy();
const auto inner = box->verticalLayout();
inner->add(object_ptr<Ui::PlainShadow>(box));
Ui::AddSkip(inner, st::messagePrivacyTopSkip);
Ui::AddSubsectionTitle(inner, tr::lng_messages_privacy_subtitle());
const auto group = std::make_shared<Ui::RadiobuttonGroup>(
privacy->newRequirePremiumCurrent() ? kOptionPremium : kOptionAll);
(!allowed()
? kOptionAll
: privacy->newRequirePremiumCurrent()
? kOptionPremium
: privacy->newChargeStarsCurrent()
? kOptionCharge
: kOptionAll));
inner->add(
object_ptr<Ui::Radiobutton>(
inner,
@@ -846,6 +996,92 @@ void EditMessagesPrivacyBox(
0,
st::messagePrivacyBottomSkip));
Ui::AddDividerText(inner, tr::lng_messages_privacy_about());
const auto available = session->appConfig().paidMessagesAvailable();
const auto charged = available
? inner->add(
object_ptr<Ui::Radiobutton>(
inner,
group,
kOptionCharge,
tr::lng_messages_privacy_charge(tr::now),
st::messagePrivacyCheck),
st::settingsSendTypePadding + style::margins(
0,
st::messagePrivacyBottomSkip,
0,
st::messagePrivacyBottomSkip))
: nullptr;
struct State {
rpl::variable<int> stars;
};
const auto state = std::make_shared<State>();
const auto savedValue = privacy->newChargeStarsCurrent();
if (available) {
Ui::AddDividerText(inner, tr::lng_messages_privacy_charge_about());
const auto chargeWrap = inner->add(
object_ptr<Ui::SlideWrap<Ui::VerticalLayout>>(
inner,
object_ptr<Ui::VerticalLayout>(inner)));
const auto chargeInner = chargeWrap->entity();
Ui::AddSkip(chargeInner);
state->stars = SetupChargeSlider(
chargeInner,
session->user(),
savedValue);
Ui::AddSkip(chargeInner);
Ui::AddSubsectionTitle(
chargeInner,
tr::lng_messages_privacy_exceptions());
const auto key = Api::UserPrivacy::Key::NoPaidMessages;
session->api().userPrivacy().reload(key);
auto label = session->api().userPrivacy().value(
key
) | rpl::map([=](const Api::UserPrivacy::Rule &value) {
using namespace Settings;
const auto always = ExceptionUsersCount(value.always.peers);
return always
? tr::lng_edit_privacy_exceptions_count(
tr::now,
lt_count,
always)
: tr::lng_edit_privacy_exceptions_add(tr::now);
});
const auto exceptions = Settings::AddButtonWithLabel(
chargeInner,
tr::lng_messages_privacy_remove_fee(),
std::move(label),
st::settingsButtonNoIcon);
const auto shower = exceptions->lifetime().make_state<rpl::lifetime>();
exceptions->setClickedCallback([=] {
*shower = session->api().userPrivacy().value(
key
) | rpl::take(
1
) | rpl::start_with_next([=](const Api::UserPrivacy::Rule &value) {
EditNoPaidMessagesExceptions(controller, value);
});
});
Ui::AddSkip(chargeInner);
Ui::AddDividerText(
chargeInner,
tr::lng_messages_privacy_remove_about());
using namespace rpl::mappers;
chargeWrap->toggleOn(group->value() | rpl::map(_1 == kOptionCharge));
chargeWrap->finishAnimating();
}
using WeakToast = base::weak_ptr<Ui::Toast::Instance>;
const auto toast = std::make_shared<WeakToast>();
const auto showToast = [=] {
@@ -875,19 +1111,20 @@ void EditMessagesPrivacyBox(
}),
});
};
if (!allowed()) {
CreateRadiobuttonLock(restricted, st::messagePrivacyCheck);
if (charged) {
CreateRadiobuttonLock(charged, st::messagePrivacyCheck);
}
group->setChangedCallback([=](int value) {
if (value == kOptionPremium) {
if (value == kOptionPremium || value == kOptionCharge) {
group->setValue(kOptionAll);
showToast();
}
});
}
Ui::AddDividerText(inner, tr::lng_messages_privacy_about());
if (!allowed()) {
Ui::AddSkip(inner);
Settings::AddButtonWithIcon(
inner,
@@ -907,8 +1144,12 @@ void EditMessagesPrivacyBox(
} else {
box->addButton(tr::lng_settings_save(), [=] {
if (allowed()) {
privacy->updateNewRequirePremium(
group->current() == kOptionPremium);
const auto value = group->current();
const auto premiumRequired = (value == kOptionPremium);
const auto chargeStars = (value == kOptionCharge)
? state->stars.current()
: 0;
privacy->updateMessagesPrivacy(premiumRequired, chargeStars);
box->closeBox();
} else {
showToast();
@@ -919,3 +1160,78 @@ void EditMessagesPrivacyBox(
});
}
}
rpl::producer<int> SetupChargeSlider(
not_null<Ui::VerticalLayout*> container,
not_null<PeerData*> peer,
int savedValue) {
struct State {
rpl::variable<int> stars;
};
const auto group = !peer->isUser();
const auto state = container->lifetime().make_state<State>();
const auto chargeStars = savedValue ? savedValue : kDefaultChargeStars;
state->stars = chargeStars;
Ui::AddSubsectionTitle(container, group
? tr::lng_rights_charge_price()
: tr::lng_messages_privacy_price());
auto values = std::vector<int>();
const auto maxStars = peer->session().appConfig().paidMessageStarsMax();
if (chargeStars < kStarsMin) {
values.push_back(chargeStars);
}
for (auto i = kStarsMin; i < std::min(100, maxStars); ++i) {
values.push_back(i);
}
for (auto i = 100; i < std::min(1000, maxStars); i += 10) {
if (i < chargeStars + 10 && chargeStars < i) {
values.push_back(chargeStars);
}
values.push_back(i);
}
for (auto i = 1000; i < maxStars + 1; i += 100) {
if (i < chargeStars + 100 && chargeStars < i) {
values.push_back(chargeStars);
}
values.push_back(i);
}
const auto valuesCount = int(values.size());
const auto setStars = [=](int value) {
state->stars = value;
};
container->add(
MakeChargeStarsSlider(
container,
&st::settingsScale,
&st::settingsScaleLabel,
valuesCount,
[=](int index) { return values[index]; },
chargeStars,
maxStars,
setStars,
setStars),
st::boxRowPadding);
const auto skip = 2 * st::defaultVerticalListSkip;
Ui::AddSkip(container, skip);
auto dollars = state->stars.value() | rpl::map([=](int stars) {
const auto ratio = peer->session().appConfig().starsWithdrawRate();
const auto dollars = int(base::SafeRound(stars * ratio));
return '~' + Ui::FillAmountAndCurrency(dollars, u"USD"_q);
});
const auto percent = peer->session().appConfig().paidMessageCommission();
Ui::AddDividerText(
container,
(group
? tr::lng_rights_charge_price_about
: tr::lng_messages_privacy_price_about)(
lt_percent,
rpl::single(QString::number(percent / 10.) + '%'),
lt_amount,
std::move(dollars)));
return state->stars.value();
}

View File

@@ -169,3 +169,8 @@ private:
void EditMessagesPrivacyBox(
not_null<Ui::GenericBox*> box,
not_null<Window::SessionController*> controller);
[[nodiscard]] rpl::producer<int> SetupChargeSlider(
not_null<Ui::VerticalLayout*> container,
not_null<PeerData*> peer,
int savedValue);

View File

@@ -441,13 +441,10 @@ void EditFilterBox(
using namespace Window;
return window->isGifPausedAtLeastFor(GifPauseReason::Layer);
};
name->setCustomTextContext([=](Fn<void()> repaint) {
return std::any(Core::MarkedTextContext{
.session = session,
.customEmojiRepaint = std::move(repaint),
.customEmojiLoopLimit = value ? -1 : 0,
});
}, [paused] {
name->setCustomTextContext(Core::TextContext({
.session = session,
.customEmojiLoopLimit = value ? -1 : 0,
}), [paused] {
return On(PowerSaving::kEmojiChat) || paused();
}, [paused] {
return On(PowerSaving::kChatSpoiler) || paused();
@@ -609,10 +606,7 @@ void EditFilterBox(
float64 alpha = 1.;
};
const auto tag = preview->lifetime().make_state<TagState>();
tag->context.textContext = Core::MarkedTextContext{
.session = session,
.customEmojiRepaint = [] {},
};
tag->context.textContext = Core::TextContext({ .session = session });
preview->paintRequest() | rpl::start_with_next([=] {
auto p = QPainter(preview);
p.setOpacity(tag->alpha);

View File

@@ -163,10 +163,10 @@ ExceptionRow::ExceptionRow(
st::defaultTextStyle,
filters,
kMarkupTextOptions,
Core::MarkedTextContext{
Core::TextContext({
.session = &history->session(),
.customEmojiRepaint = repaint,
});
.repaint = repaint,
}));
} else if (peer()->isSelf()) {
setCustomStatus(tr::lng_saved_forward_here(tr::now));
}

View File

@@ -537,13 +537,6 @@ void LinkController::addHeader(not_null<Ui::VerticalLayout*> container) {
verticalLayout->add(std::move(icon.widget));
const auto isStatic = _filterTitle.isStatic;
const auto makeContext = [=](Fn<void()> update) {
return Core::MarkedTextContext{
.session = &_window->session(),
.customEmojiRepaint = update,
.customEmojiLoopLimit = isStatic ? -1 : 0,
};
};
verticalLayout->add(
object_ptr<Ui::CenterWrap<>>(
verticalLayout,
@@ -559,7 +552,10 @@ void LinkController::addHeader(not_null<Ui::VerticalLayout*> container) {
Ui::Text::WithEntities)),
st::settingsFilterDividerLabel,
st::defaultPopupMenu,
makeContext)),
Core::TextContext({
.session = &_window->session(),
.customEmojiLoopLimit = isStatic ? -1 : 0,
}))),
st::filterLinkDividerLabelPadding);
verticalLayout->geometryValue(

View File

@@ -9,6 +9,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "api/api_credits.h"
#include "boxes/peer_list_controllers.h"
#include "core/ui_integration.h" // TextContext.
#include "data/data_peer.h"
#include "data/data_session.h"
#include "data/data_user.h"
@@ -67,14 +68,9 @@ void GiftCreditsBox(
2.);
{
Ui::AddSkip(content);
const auto arrow = Ui::Text::SingleCustomEmoji(
peer->owner().customEmojiManager().registerInternalEmoji(
st::topicButtonArrow,
st::channelEarnLearnArrowMargins,
true));
auto link = tr::lng_credits_box_history_entry_gift_about_link(
lt_emoji,
rpl::single(arrow),
rpl::single(Ui::Text::IconEmoji(&st::textMoreIconEmoji)),
Ui::Text::RichLangValue
) | rpl::map([](TextWithEntities text) {
return Ui::Text::Link(
@@ -92,7 +88,7 @@ void GiftCreditsBox(
lt_link,
std::move(link),
Ui::Text::RichLangValue),
{ .session = &peer->session() },
Core::TextContext({ .session = &peer->session() }),
st::creditsBoxAbout)),
st::boxRowPadding);
}

File diff suppressed because it is too large Load Diff

View File

@@ -13,6 +13,10 @@ namespace Api {
struct GiftCode;
} // namespace Api
namespace ChatHelpers {
class Show;
} // namespace ChatHelpers
namespace Data {
struct Boost;
struct CreditsHistoryEntry;
@@ -21,6 +25,14 @@ struct GiveawayResults;
struct SubscriptionEntry;
} // namespace Data
namespace Main {
class Session;
} // namespace Main
namespace Settings {
struct CreditsEntryBoxStyleOverrides;
} // namespace Settings
namespace Ui {
class GenericBox;
class VerticalLayout;
@@ -54,29 +66,37 @@ void ResolveGiveawayInfo(
std::optional<Data::GiveawayStart> start,
std::optional<Data::GiveawayResults> results);
[[nodiscard]] QString TonAddressUrl(
not_null<Main::Session*> session,
const QString &address);
void AddStarGiftTable(
not_null<Window::SessionNavigation*> controller,
std::shared_ptr<ChatHelpers::Show> show,
not_null<Ui::VerticalLayout*> container,
Settings::CreditsEntryBoxStyleOverrides st,
const Data::CreditsHistoryEntry &entry,
Fn<void(bool)> toggleVisibility,
Fn<void()> convertToStars,
Fn<void()> startUpgrade);
void AddCreditsHistoryEntryTable(
not_null<Window::SessionNavigation*> controller,
std::shared_ptr<ChatHelpers::Show> show,
not_null<Ui::VerticalLayout*> container,
Settings::CreditsEntryBoxStyleOverrides st,
const Data::CreditsHistoryEntry &entry);
void AddSubscriptionEntryTable(
not_null<Window::SessionNavigation*> controller,
std::shared_ptr<ChatHelpers::Show> show,
not_null<Ui::VerticalLayout*> container,
Settings::CreditsEntryBoxStyleOverrides st,
const Data::SubscriptionEntry &s);
void AddSubscriberEntryTable(
not_null<Window::SessionNavigation*> controller,
std::shared_ptr<ChatHelpers::Show> show,
not_null<Ui::VerticalLayout*> container,
Settings::CreditsEntryBoxStyleOverrides st,
not_null<PeerData*> peer,
TimeId date);
void AddCreditsBoostTable(
not_null<Window::SessionNavigation*> controller,
std::shared_ptr<ChatHelpers::Show> show,
not_null<Ui::VerticalLayout*> container,
Settings::CreditsEntryBoxStyleOverrides st,
const Data::Boost &boost);

View File

@@ -72,6 +72,16 @@ ModerateOptions CalculateModerateOptions(const HistoryItemsList &items) {
if (peer != item->history()->peer) {
return {};
}
{
const auto author = item->author();
if (author == peer) {
return {};
} else if (const auto channel = author->asChannel()) {
if (channel->linkedChat() == peer) {
return {};
}
}
}
if (!item->suggestBanReport()) {
result.allCanBan = false;
}
@@ -443,10 +453,7 @@ void CreateModerateMessagesBox(
) | rpl::start_with_next([=](const TextWithEntities &text) {
raw->setMarkedText(
Ui::Text::Link(text, u"internal:"_q),
Core::MarkedTextContext{
.session = session,
.customEmojiRepaint = [=] { raw->update(); },
});
Core::TextContext({ .session = session }));
}, label->lifetime());
Ui::AddSkip(inner);

View File

@@ -1154,8 +1154,7 @@ RecoverBox::RecoverBox(
rpl::single(Ui::Text::WrapEmailPattern(pattern)),
Ui::Text::WithEntities),
st::termsContent,
st::defaultPopupMenu,
[=](Fn<void()> update) { return CommonTextContext{ std::move(update) }; })
st::defaultPopupMenu)
, _closeParent(std::move(closeParent)) {
_patternLabel->setAttribute(Qt::WA_TransparentForMouseEvents);
if (_cloudFields.pendingResetDate != 0 || !session) {

View File

@@ -873,6 +873,7 @@ void PeerListRow::paintUserpic(
} else if (const auto callback = generatePaintUserpicCallback(false)) {
callback(p, x, y, outerWidth, st.photoSize);
}
paintUserpicOverlay(p, st, x, y, outerWidth);
}
// Emulates Ui::RoundImageCheckbox::paint() in a checked state.
@@ -912,7 +913,13 @@ void PeerListRow::paintDisabledCheckUserpic(
p.setPen(userpicBorderPen);
p.setBrush(Qt::NoBrush);
p.drawEllipse(userpicEllipse);
if (peer()->forum()) {
const auto radius = userpicDiameter
* Ui::ForumUserpicRadiusMultiplier();
p.drawRoundedRect(userpicEllipse, radius, radius);
} else {
p.drawEllipse(userpicEllipse);
}
p.setPen(iconBorderPen);
p.setBrush(st.disabledCheckFg);

View File

@@ -95,6 +95,13 @@ public:
[[nodiscard]] virtual QString generateShortName();
[[nodiscard]] virtual auto generatePaintUserpicCallback(
bool forceRound) -> PaintRoundImageCallback;
virtual void paintUserpicOverlay(
Painter &p,
const style::PeerListItem &st,
int x,
int y,
int outerWidth) {
}
[[nodiscard]] virtual auto generateNameFirstLetters() const
-> const base::flat_set<QChar> &;

View File

@@ -8,7 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "boxes/peer_list_controllers.h"
#include "api/api_chat_participants.h"
#include "api/api_premium.h"
#include "api/api_premium.h" // MessageMoneyRestriction.
#include "base/random.h"
#include "boxes/filters/edit_filter_chats_list.h"
#include "settings/settings_premium.h"
@@ -41,6 +41,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "history/history.h"
#include "history/history_item.h"
#include "dialogs/dialogs_main_list.h"
#include "payments/ui/payments_reaction_box.h"
#include "ui/effects/outline_segments.h"
#include "ui/wrap/slide_wrap.h"
#include "window/window_separate_id.h"
@@ -275,40 +276,71 @@ bool PeerListGlobalSearchController::isLoading() {
return _timer.isActive() || _requestId;
}
struct RecipientRow::Restriction {
Api::MessageMoneyRestriction value;
RestrictionBadgeCache cache;
};
RecipientRow::RecipientRow(
not_null<PeerData*> peer,
const style::PeerListItem *maybeLockedSt,
History *maybeHistory)
: PeerListRow(peer)
, _maybeHistory(maybeHistory)
, _resolvePremiumRequired(maybeLockedSt != nullptr) {
if (maybeLockedSt
&& (Api::ResolveRequiresPremiumToWrite(peer, maybeHistory)
== Api::RequirePremiumState::Yes)) {
_lockedSt = maybeLockedSt;
, _maybeLockedSt(maybeLockedSt) {
if (_maybeLockedSt) {
setRestriction(Api::ResolveMessageMoneyRestrictions(
peer,
maybeHistory));
}
}
PaintRoundImageCallback RecipientRow::generatePaintUserpicCallback(
bool forceRound) {
auto result = PeerListRow::generatePaintUserpicCallback(forceRound);
if (const auto st = _lockedSt) {
return [=](Painter &p, int x, int y, int outerWidth, int size) {
result(p, x, y, outerWidth, size);
PaintPremiumRequiredLock(p, st, x, y, outerWidth, size);
};
Api::MessageMoneyRestriction RecipientRow::restriction() const {
return _restriction
? _restriction->value
: Api::MessageMoneyRestriction();
}
void RecipientRow::setRestriction(Api::MessageMoneyRestriction restriction) {
if (!restriction) {
_restriction = nullptr;
return;
} else if (!_restriction) {
_restriction = std::make_unique<Restriction>();
}
_restriction->value = restriction;
}
void RecipientRow::paintUserpicOverlay(
Painter &p,
const style::PeerListItem &st,
int x,
int y,
int outerWidth) {
if (const auto &r = _restriction) {
PaintRestrictionBadge(
p,
_maybeLockedSt,
r->value.starsPerMessage,
r->cache,
x,
y,
outerWidth,
st.photoSize);
}
return result;
}
bool RecipientRow::refreshLock(
not_null<const style::PeerListItem*> maybeLockedSt) {
if (const auto user = peer()->asUser()) {
const auto locked = _resolvePremiumRequired
&& (Api::ResolveRequiresPremiumToWrite(user, _maybeHistory)
== Api::RequirePremiumState::Yes);
if (this->locked() != locked) {
setLocked(locked ? maybeLockedSt.get() : nullptr);
using Restriction = Api::MessageMoneyRestriction;
const auto r = _maybeLockedSt
? Api::ResolveMessageMoneyRestrictions(
user,
_maybeHistory)
: Restriction();
if ((_restriction ? _restriction->value : Restriction()) != r) {
setRestriction(r);
return true;
}
}
@@ -318,22 +350,30 @@ bool RecipientRow::refreshLock(
void RecipientRow::preloadUserpic() {
PeerListRow::preloadUserpic();
if (!_resolvePremiumRequired) {
if (!_maybeLockedSt) {
return;
} else if (Api::ResolveRequiresPremiumToWrite(peer(), _maybeHistory)
== Api::RequirePremiumState::Unknown) {
const auto user = peer()->asUser();
user->session().api().premium().resolvePremiumRequired(user);
}
const auto peer = this->peer();
const auto known = Api::ResolveMessageMoneyRestrictions(
peer,
_maybeHistory).known;
if (known) {
return;
} else if (const auto user = peer->asUser()) {
const auto api = &user->session().api();
api->premium().resolveMessageMoneyRestrictions(user);
} else if (const auto group = peer->asChannel()) {
group->updateFull();
}
}
void TrackPremiumRequiredChanges(
void TrackMessageMoneyRestrictionsChanges(
not_null<PeerListController*> controller,
rpl::lifetime &lifetime) {
const auto session = &controller->session();
rpl::merge(
Data::AmPremiumValue(session) | rpl::to_empty,
session->api().premium().somePremiumRequiredResolved()
session->api().premium().someMessageMoneyRestrictionsResolved()
) | rpl::start_with_next([=] {
const auto st = &controller->computeListSt().item;
const auto delegate = controller->delegate();
@@ -726,7 +766,7 @@ std::unique_ptr<PeerListRow> ContactsBoxController::createRow(
return std::make_unique<PeerListRow>(user);
}
RecipientPremiumRequiredError WritePremiumRequiredError(
RecipientMoneyRestrictionError WriteMoneyRestrictionError(
not_null<UserData*> user) {
return {
.text = tr::lng_send_non_premium_message_toast(
@@ -759,7 +799,7 @@ ChooseRecipientBoxController::ChooseRecipientBoxController(
, _session(args.session)
, _callback(std::move(args.callback))
, _filter(std::move(args.filter))
, _premiumRequiredError(std::move(args.premiumRequiredError)) {
, _moneyRestrictionError(std::move(args.moneyRestrictionError)) {
}
Main::Session &ChooseRecipientBoxController::session() const {
@@ -769,14 +809,17 @@ Main::Session &ChooseRecipientBoxController::session() const {
void ChooseRecipientBoxController::prepareViewHook() {
delegate()->peerListSetTitle(tr::lng_forward_choose());
if (_premiumRequiredError) {
TrackPremiumRequiredChanges(this, lifetime());
if (_moneyRestrictionError) {
TrackMessageMoneyRestrictionsChanges(this, lifetime());
}
}
bool ChooseRecipientBoxController::showLockedError(
not_null<PeerListRow*> row) {
return RecipientRow::ShowLockedError(this, row, _premiumRequiredError);
return RecipientRow::ShowLockedError(
this,
row,
_moneyRestrictionError);
}
void ChooseRecipientBoxController::rowClicked(not_null<PeerListRow*> row) {
@@ -836,8 +879,9 @@ void ChooseRecipientBoxController::rowClicked(not_null<PeerListRow*> row) {
bool RecipientRow::ShowLockedError(
not_null<PeerListController*> controller,
not_null<PeerListRow*> row,
Fn<RecipientPremiumRequiredError(not_null<UserData*>)> error) {
if (!static_cast<RecipientRow*>(row.get())->locked()) {
Fn<RecipientMoneyRestrictionError(not_null<UserData*>)> error) {
const auto recipient = static_cast<RecipientRow*>(row.get());
if (!recipient->restriction().premiumRequired) {
return false;
}
::Settings::ShowPremiumPromoToast(
@@ -860,15 +904,15 @@ auto ChooseRecipientBoxController::createRow(
: ((peer->isBroadcast() && !Data::CanSendAnything(peer))
|| peer->isRepliesChat()
|| peer->isVerifyCodes()
|| (peer->isUser() && (_premiumRequiredError
? !peer->asUser()->canSendIgnoreRequirePremium()
|| (peer->isUser() && (_moneyRestrictionError
? !peer->asUser()->canSendIgnoreMoneyRestrictions()
: !Data::CanSendAnything(peer))));
if (skip) {
return nullptr;
}
auto result = std::make_unique<Row>(
history,
_premiumRequiredError ? &computeListSt().item : nullptr);
_moneyRestrictionError ? &computeListSt().item : nullptr);
return result;
}
@@ -1093,25 +1137,61 @@ auto ChooseTopicBoxController::createRow(not_null<Data::ForumTopic*> topic)
return skip ? nullptr : std::make_unique<Row>(topic);
};
void PaintPremiumRequiredLock(
void PaintRestrictionBadge(
Painter &p,
not_null<const style::PeerListItem*> st,
int stars,
RestrictionBadgeCache &cache,
int x,
int y,
int outerWidth,
int size) {
auto hq = PainterHighQualityEnabler(p);
const auto paletteVersion = style::PaletteVersion();
const auto good = !cache.badge.isNull()
&& (cache.stars == stars)
&& (cache.paletteVersion == paletteVersion);
const auto &check = st->checkbox.check;
auto pen = check.border->p;
pen.setWidthF(check.width);
p.setPen(pen);
p.setBrush(st::premiumButtonBg2);
const auto &icon = st::stickersPremiumLock;
const auto width = icon.width();
const auto height = icon.height();
const auto rect = QRect(
QPoint(x + size - width, y + size - height),
icon.size());
p.drawEllipse(rect);
icon.paintInCenter(p, rect);
const auto add = check.width;
if (!good) {
cache.stars = stars;
cache.paletteVersion = paletteVersion;
if (stars) {
const auto text = (stars >= 1000)
? (QString::number(stars / 1000) + 'K')
: QString::number(stars);
cache.badge = Ui::GenerateSmallBadgeImage(
text,
st::paidReactTopStarIcon,
check.bgActive->c,
st::premiumButtonFg->c,
&check);
} else {
auto hq = PainterHighQualityEnabler(p);
const auto &icon = st::stickersPremiumLock;
const auto width = icon.width();
const auto height = icon.height();
const auto rect = QRect(
QPoint(x + size - width, y + size - height),
icon.size());
const auto added = QMargins(add, add, add, add);
const auto ratio = style::DevicePixelRatio();
cache.badge = QImage(
(rect + added).size() * ratio,
QImage::Format_ARGB32_Premultiplied);
cache.badge.setDevicePixelRatio(ratio);
cache.badge.fill(Qt::transparent);
const auto inner = QRect(add, add, rect.width(), rect.height());
auto q = QPainter(&cache.badge);
auto pen = check.border->p;
pen.setWidthF(check.width);
q.setPen(pen);
q.setBrush(st::premiumButtonBg2);
q.drawEllipse(inner);
icon.paintInCenter(q, inner);
}
}
const auto cached = cache.badge.size() / cache.badge.devicePixelRatio();
const auto left = x + size + add - cached.width();
const auto top = stars ? (y - add) : (y + size + add - cached.height());
p.drawImage(left, top, cache.badge);
}

View File

@@ -19,6 +19,10 @@ namespace style {
struct PeerListItem;
} // namespace style
namespace Api {
struct MessageMoneyRestriction;
} // namespace Api
namespace Data {
class Thread;
class Forum;
@@ -93,13 +97,28 @@ private:
};
struct RecipientPremiumRequiredError {
struct RecipientMoneyRestrictionError {
TextWithEntities text;
};
[[nodiscard]] RecipientPremiumRequiredError WritePremiumRequiredError(
[[nodiscard]] RecipientMoneyRestrictionError WriteMoneyRestrictionError(
not_null<UserData*> user);
struct RestrictionBadgeCache {
int paletteVersion = 0;
int stars = 0;
QImage badge;
};
void PaintRestrictionBadge(
Painter &p,
not_null<const style::PeerListItem*> st,
int stars,
RestrictionBadgeCache &cache,
int x,
int y,
int outerWidth,
int size);
class RecipientRow : public PeerListRow {
public:
explicit RecipientRow(
@@ -112,30 +131,33 @@ public:
[[nodiscard]] static bool ShowLockedError(
not_null<PeerListController*> controller,
not_null<PeerListRow*> row,
Fn<RecipientPremiumRequiredError(not_null<UserData*>)> error);
Fn<RecipientMoneyRestrictionError(not_null<UserData*>)> error);
[[nodiscard]] History *maybeHistory() const {
return _maybeHistory;
}
[[nodiscard]] bool locked() const {
return _lockedSt != nullptr;
}
void setLocked(const style::PeerListItem *lockedSt) {
_lockedSt = lockedSt;
}
PaintRoundImageCallback generatePaintUserpicCallback(
bool forceRound) override;
void paintUserpicOverlay(
Painter &p,
const style::PeerListItem &st,
int x,
int y,
int outerWidth) override;
void preloadUserpic() override;
[[nodiscard]] Api::MessageMoneyRestriction restriction() const;
void setRestriction(Api::MessageMoneyRestriction restriction);
private:
struct Restriction;
History *_maybeHistory = nullptr;
const style::PeerListItem *_lockedSt = nullptr;
bool _resolvePremiumRequired = false;
const style::PeerListItem *_maybeLockedSt = nullptr;
std::shared_ptr<Restriction> _restriction;
};
void TrackPremiumRequiredChanges(
void TrackMessageMoneyRestrictionsChanges(
not_null<PeerListController*> controller,
rpl::lifetime &lifetime);
@@ -261,8 +283,8 @@ struct ChooseRecipientArgs {
FnMut<void(not_null<Data::Thread*>)> callback;
Fn<bool(not_null<Data::Thread*>)> filter;
using PremiumRequiredError = RecipientPremiumRequiredError;
Fn<PremiumRequiredError(not_null<UserData*>)> premiumRequiredError;
using MoneyRestrictionError = RecipientMoneyRestrictionError;
Fn<MoneyRestrictionError(not_null<UserData*>)> moneyRestrictionError;
};
class ChooseRecipientBoxController
@@ -290,8 +312,8 @@ private:
const not_null<Main::Session*> _session;
FnMut<void(not_null<Data::Thread*>)> _callback;
Fn<bool(not_null<Data::Thread*>)> _filter;
Fn<RecipientPremiumRequiredError(
not_null<UserData*>)> _premiumRequiredError;
Fn<RecipientMoneyRestrictionError(
not_null<UserData*>)> _moneyRestrictionError;
};
@@ -371,11 +393,3 @@ private:
Fn<bool(not_null<Data::ForumTopic*>)> _filter;
};
void PaintPremiumRequiredLock(
Painter &p,
not_null<const style::PeerListItem*> st,
int x,
int y,
int outerWidth,
int size);

View File

@@ -9,10 +9,12 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "api/api_chat_participants.h"
#include "api/api_invite_links.h"
#include "api/api_premium.h"
#include "boxes/peers/edit_participant_box.h"
#include "boxes/peers/edit_peer_type_box.h"
#include "boxes/peers/replace_boost_box.h"
#include "boxes/max_invite_box.h"
#include "chat_helpers/message_field.h"
#include "lang/lang_keys.h"
#include "data/data_channel.h"
#include "data/data_chat.h"
@@ -22,6 +24,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_changes.h"
#include "data/data_peer_values.h"
#include "history/history.h"
#include "history/history_item_helpers.h"
#include "dialogs/dialogs_indexed_list.h"
#include "ui/boxes/confirm_box.h"
#include "ui/boxes/show_or_premium_box.h"
@@ -52,16 +55,39 @@ constexpr auto kUserpicsLimit = 3;
class ForbiddenRow final : public PeerListRow {
public:
ForbiddenRow(not_null<PeerData*> peer, bool locked);
ForbiddenRow(
not_null<PeerData*> peer,
not_null<const style::PeerListItem*> lockSt,
bool locked);
PaintRoundImageCallback generatePaintUserpicCallback(
bool forceRound) override;
Api::MessageMoneyRestriction restriction() const;
void setRestriction(Api::MessageMoneyRestriction restriction);
void preloadUserpic() override;
void paintUserpicOverlay(
Painter &p,
const style::PeerListItem &st,
int x,
int y,
int outerWidth) override;
bool refreshLock();
private:
struct Restriction {
Api::MessageMoneyRestriction value;
RestrictionBadgeCache cache;
};
const bool _locked = false;
const not_null<const style::PeerListItem*> _lockSt;
QImage _disabledFrame;
InMemoryKey _userpicKey;
int _paletteVersion = 0;
std::shared_ptr<Restriction> _restriction;
};
@@ -81,6 +107,9 @@ public:
[[nodiscard]] rpl::producer<int> selectedValue() const {
return _selected.value();
}
[[nodiscard]] rpl::producer<int> starsToSend() const {
return _starsToSend.value();
}
void send(
std::vector<not_null<PeerData*>> list,
@@ -89,10 +118,16 @@ public:
private:
void appendRow(not_null<UserData*> user);
[[nodiscard]] std::unique_ptr<PeerListRow> createRow(
[[nodiscard]] std::unique_ptr<ForbiddenRow> createRow(
not_null<UserData*> user) const;
[[nodiscard]] bool canInvite(not_null<PeerData*> peer) const;
void send(
std::vector<not_null<PeerData*>> list,
Ui::ShowPtr show,
Fn<void()> close,
Api::SendOptions options);
void setSimpleCover();
void setComplexCover();
@@ -101,8 +136,11 @@ private:
const std::vector<not_null<UserData*>> &_users;
const bool _can = false;
rpl::variable<int> _selected;
rpl::variable<int> _starsToSend;
bool _sending = false;
rpl::lifetime _paymentCheckLifetime;
};
base::flat_set<not_null<UserData*>> GetAlreadyInFromPeer(PeerData *peer) {
@@ -256,11 +294,17 @@ Main::Session &InviteForbiddenController::session() const {
return _peer->session();
}
ForbiddenRow::ForbiddenRow(not_null<PeerData*> peer, bool locked)
ForbiddenRow::ForbiddenRow(
not_null<PeerData*> peer,
not_null<const style::PeerListItem*> lockSt,
bool locked)
: PeerListRow(peer)
, _locked(locked) {
, _locked(locked)
, _lockSt(lockSt) {
if (_locked) {
setCustomStatus(tr::lng_invite_status_disabled(tr::now));
} else {
setRestriction(Api::ResolveMessageMoneyRestrictions(peer, nullptr));
}
}
@@ -339,6 +383,76 @@ PaintRoundImageCallback ForbiddenRow::generatePaintUserpicCallback(
};
}
Api::MessageMoneyRestriction ForbiddenRow::restriction() const {
return _restriction
? _restriction->value
: Api::MessageMoneyRestriction();
}
void ForbiddenRow::setRestriction(Api::MessageMoneyRestriction restriction) {
if (!restriction || !restriction.starsPerMessage) {
_restriction = nullptr;
return;
} else if (!_restriction) {
_restriction = std::make_unique<Restriction>();
}
_restriction->value = restriction;
}
void ForbiddenRow::paintUserpicOverlay(
Painter &p,
const style::PeerListItem &st,
int x,
int y,
int outerWidth) {
if (const auto &r = _restriction) {
PaintRestrictionBadge(
p,
_lockSt,
r->value.starsPerMessage,
r->cache,
x,
y,
outerWidth,
st.photoSize);
}
}
bool ForbiddenRow::refreshLock() {
if (_locked) {
return false;
} else if (const auto user = peer()->asUser()) {
using Restriction = Api::MessageMoneyRestriction;
auto r = Api::ResolveMessageMoneyRestrictions(user, nullptr);
if (!r || !r.starsPerMessage) {
r = Restriction();
}
if ((_restriction ? _restriction->value : Restriction()) != r) {
setRestriction(r);
return true;
}
}
return false;
}
void ForbiddenRow::preloadUserpic() {
PeerListRow::preloadUserpic();
const auto peer = this->peer();
const auto known = Api::ResolveMessageMoneyRestrictions(
peer,
nullptr).known;
if (known) {
return;
} else if (const auto user = peer->asUser()) {
const auto api = &user->session().api();
api->premium().resolveMessageMoneyRestrictions(user);
} else if (const auto group = peer->asChannel()) {
group->updateFull();
}
}
void InviteForbiddenController::setSimpleCover() {
delegate()->peerListSetTitle(
_can ? tr::lng_profile_add_via_link() : tr::lng_via_link_cant());
@@ -435,6 +549,30 @@ void InviteForbiddenController::setComplexCover() {
}
void InviteForbiddenController::prepare() {
session().api().premium().someMessageMoneyRestrictionsResolved(
) | rpl::start_with_next([=] {
auto stars = 0;
const auto process = [&](not_null<PeerListRow*> raw) {
const auto row = static_cast<ForbiddenRow*>(raw.get());
if (row->refreshLock()) {
delegate()->peerListUpdateRow(raw);
}
if (const auto r = row->restriction()) {
stars += r.starsPerMessage;
}
};
auto count = delegate()->peerListFullRowsCount();
for (auto i = 0; i != count; ++i) {
process(delegate()->peerListRowAt(i));
}
_starsToSend = stars;
count = delegate()->peerListSearchRowsCount();
for (auto i = 0; i != count; ++i) {
process(delegate()->peerListSearchRowAt(i));
}
}, lifetime());
if (session().premium()
|| (_forbidden.premiumAllowsInvite.empty()
&& _forbidden.premiumAllowsWrite.empty())) {
@@ -464,6 +602,11 @@ void InviteForbiddenController::rowClicked(not_null<PeerListRow*> row) {
const auto checked = row->checked();
delegate()->peerListSetRowChecked(row, !checked);
_selected = _selected.current() + (checked ? -1 : 1);
const auto r = static_cast<ForbiddenRow*>(row.get())->restriction();
if (r.starsPerMessage) {
_starsToSend = _starsToSend.current()
+ (checked ? -r.starsPerMessage : r.starsPerMessage);
}
}
void InviteForbiddenController::appendRow(not_null<UserData*> user) {
@@ -473,6 +616,9 @@ void InviteForbiddenController::appendRow(not_null<UserData*> user) {
delegate()->peerListAppendRow(std::move(row));
if (canInvite(user)) {
delegate()->peerListSetRowChecked(raw, true);
if (const auto r = raw->restriction()) {
_starsToSend = _starsToSend.current() + r.starsPerMessage;
}
}
}
}
@@ -481,7 +627,64 @@ void InviteForbiddenController::send(
std::vector<not_null<PeerData*>> list,
Ui::ShowPtr show,
Fn<void()> close) {
if (_sending || list.empty()) {
send(list, show, close, {});
}
void InviteForbiddenController::send(
std::vector<not_null<PeerData*>> list,
Ui::ShowPtr show,
Fn<void()> close,
Api::SendOptions options) {
if (list.empty()) {
return;
}
_paymentCheckLifetime.destroy();
const auto withPaymentApproved = [=](int approved) {
auto copy = options;
copy.starsApproved = approved;
send(list, show, close, copy);
};
const auto messagesCount = 1;
const auto alreadyApproved = options.starsApproved;
auto paid = std::vector<not_null<PeerData*>>();
auto waiting = base::flat_set<not_null<PeerData*>>();
auto totalStars = 0;
for (const auto &peer : list) {
const auto details = ComputePaymentDetails(peer, messagesCount);
if (!details) {
waiting.emplace(peer);
} else if (details->stars > 0) {
totalStars += details->stars;
paid.push_back(peer);
}
}
if (!waiting.empty()) {
session().changes().peerUpdates(
Data::PeerUpdate::Flag::FullInfo
) | rpl::start_with_next([=](const Data::PeerUpdate &update) {
if (waiting.contains(update.peer)) {
withPaymentApproved(alreadyApproved);
}
}, _paymentCheckLifetime);
if (!session().credits().loaded()) {
session().credits().loadedValue(
) | rpl::filter(
rpl::mappers::_1
) | rpl::take(1) | rpl::start_with_next([=] {
withPaymentApproved(alreadyApproved);
}, _paymentCheckLifetime);
}
return;
} else if (totalStars > alreadyApproved) {
const auto sessionShow = Main::MakeSessionShow(show, &session());
ShowSendPaidConfirm(sessionShow, paid, SendPaymentDetails{
.messages = messagesCount,
.stars = totalStars,
}, [=] { withPaymentApproved(totalStars); });
return;
} else if (_sending) {
return;
}
_sending = true;
@@ -492,12 +695,18 @@ void InviteForbiddenController::send(
if (link.isEmpty()) {
return false;
}
auto full = options;
auto &api = _peer->session().api();
auto options = Api::SendOptions();
for (const auto &to : list) {
auto copy = full;
copy.starsApproved = std::min(
to->starsPerMessageChecked(),
full.starsApproved);
full.starsApproved -= copy.starsApproved;
const auto history = to->owner().history(to);
auto message = Api::MessageToSend(
Api::SendAction(history, options));
Api::SendAction(history, copy));
message.textWithTags = { link };
message.action.clearDraft = false;
api.sendMessage(std::move(message));
@@ -542,10 +751,11 @@ void InviteForbiddenController::send(
}
}
std::unique_ptr<PeerListRow> InviteForbiddenController::createRow(
std::unique_ptr<ForbiddenRow> InviteForbiddenController::createRow(
not_null<UserData*> user) const {
const auto locked = _can && !canInvite(user);
return std::make_unique<ForbiddenRow>(user, locked);
const auto lockSt = &computeListSt().item;
return std::make_unique<ForbiddenRow>(user, lockSt, locked);
}
} // namespace
@@ -584,8 +794,8 @@ void AddParticipantsBoxController::subscribeToMigration() {
}
void AddParticipantsBoxController::rowClicked(not_null<PeerListRow*> row) {
const auto premiumRequiredError = WritePremiumRequiredError;
if (RecipientRow::ShowLockedError(this, row, premiumRequiredError)) {
const auto moneyRestrictionError = WriteMoneyRestrictionError;
if (RecipientRow::ShowLockedError(this, row, moneyRestrictionError)) {
return;
}
const auto &serverConfig = session().serverConfig();
@@ -614,7 +824,7 @@ void AddParticipantsBoxController::itemDeselectedHook(
void AddParticipantsBoxController::prepareViewHook() {
updateTitle();
TrackPremiumRequiredChanges(this, lifetime());
TrackMessageMoneyRestrictionsChanges(this, lifetime());
}
int AddParticipantsBoxController::alreadyInCount() const {
@@ -929,12 +1139,15 @@ bool ChatInviteForbidden(
) | rpl::start_with_next([=](bool has) {
box->clearButtons();
if (has) {
box->addButton(tr::lng_via_link_send(), [=] {
const auto send = box->addButton(tr::lng_via_link_send(), [=] {
weak->send(
box->collectSelectedRows(),
box->uiShow(),
crl::guard(box, [=] { box->closeBox(); }));
});
send->setText(PaidSendButtonText(
weak->starsToSend(),
tr::lng_via_link_send()));
}
box->addButton(tr::lng_create_group_skip(), [=] {
box->closeBox();

View File

@@ -265,7 +265,7 @@ struct IconSelector {
const auto manager = &controller->session().data().customEmojiManager();
auto factory = [=](DocumentId id, Fn<void()> repaint)
-> std::unique_ptr<Ui::Text::CustomEmoji> {
-> std::unique_ptr<Ui::Text::CustomEmoji> {
const auto tag = Data::CustomEmojiManager::SizeTag::Large;
if (id == kDefaultIconId) {
return std::make_unique<DefaultIconEmoji>(
@@ -288,7 +288,7 @@ struct IconSelector {
.show = controller->uiShow(),
.mode = EmojiListWidget::Mode::TopicIcon,
.paused = Window::PausedIn(controller, PauseReason::Layer),
.customRecentList = recent(),
.customRecentList = DocumentListToRecent(recent()),
.customRecentFactory = std::move(factory),
.st = &st::reactPanelEmojiPan,
}),
@@ -297,7 +297,7 @@ struct IconSelector {
icons->requestDefaultIfUnknown();
icons->defaultUpdates(
) | rpl::start_with_next([=] {
selector->provideRecent(recent());
selector->provideRecent(DocumentListToRecent(recent()));
}, selector->lifetime());
placeFooter(selector->createFooter());

View File

@@ -15,7 +15,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "boxes/background_box.h"
#include "boxes/stickers_box.h"
#include "chat_helpers/compose/compose_show.h"
#include "core/ui_integration.h" // Core::MarkedTextContext.
#include "core/ui_integration.h" // TextContext
#include "data/stickers/data_custom_emoji.h"
#include "data/stickers/data_stickers.h"
#include "data/data_changes.h"
@@ -165,7 +165,7 @@ private:
const uint32 _level;
const TextWithEntities _icon;
const Core::MarkedTextContext _context;
const Ui::Text::MarkedContext _context;
Ui::Text::String _text;
bool _minimal = false;
@@ -313,9 +313,11 @@ PreviewWrap::PreviewWrap(
WebPageCollage(),
nullptr, // iv
nullptr, // stickerSet
nullptr, // uniqueGift
0, // duration
QString(), // author
false, // hasLargeMedia
false, // photoIsVideoCover
0)) // pendingTill
, _theme(theme)
, _style(style)
@@ -464,7 +466,10 @@ LevelBadge::LevelBadge(
st::settingsLevelBadgeLock,
QMargins(0, st::settingsLevelBadgeLockSkip, 0, 0),
false)))
, _context({ .session = session }) {
, _context(Core::TextContext({
.session = session,
.repaint = [this] { update(); },
})) {
updateText();
}
@@ -525,7 +530,7 @@ void LevelBadge::paintEvent(QPaintEvent *e) {
struct SetValues {
uint8 colorIndex = 0;
DocumentId backgroundEmojiId = 0;
DocumentId statusId = 0;
EmojiStatusId statusId;
TimeId statusUntil = 0;
bool statusChanged = false;
};
@@ -807,7 +812,7 @@ int ColorSelector::resizeGetHeight(int newWidth) {
const auto state = right->lifetime().make_state<State>();
state->panel.someCustomChosen(
) | rpl::start_with_next([=](EmojiStatusPanel::CustomChosen chosen) {
emojiIdChosen(chosen.id);
emojiIdChosen(chosen.id.documentId);
}, raw->lifetime());
std::move(colorIndexValue) | rpl::start_with_next([=](uint8 index) {
@@ -871,13 +876,12 @@ int ColorSelector::resizeGetHeight(int newWidth) {
const auto customTextColor = [=] {
return style->coloredValues(false, state->index).name;
};
const auto controller = show->resolveWindow(
ChatHelpers::WindowUsage::PremiumPromo);
const auto controller = show->resolveWindow();
if (controller) {
state->panel.show({
.controller = controller,
.button = right,
.ensureAddedEmojiId = state->emojiId,
.ensureAddedEmojiId = { state->emojiId },
.customTextColor = customTextColor,
.backgroundEmojiMode = true,
});
@@ -901,8 +905,8 @@ int ColorSelector::resizeGetHeight(int newWidth) {
not_null<Ui::RpWidget*> parent,
std::shared_ptr<ChatHelpers::Show> show,
not_null<ChannelData*> channel,
rpl::producer<DocumentId> statusIdValue,
Fn<void(DocumentId,TimeId)> statusIdChosen,
rpl::producer<EmojiStatusId> statusIdValue,
Fn<void(EmojiStatusId,TimeId)> statusIdChosen,
bool group) {
const auto button = ButtonStyleWithRightEmoji(
parent,
@@ -924,20 +928,20 @@ int ColorSelector::resizeGetHeight(int newWidth) {
struct State {
EmojiStatusPanel panel;
std::unique_ptr<Ui::Text::CustomEmoji> emoji;
DocumentId statusId = 0;
EmojiStatusId statusId;
};
const auto state = right->lifetime().make_state<State>();
state->panel.someCustomChosen(
) | rpl::start_with_next([=](EmojiStatusPanel::CustomChosen chosen) {
statusIdChosen(chosen.id, chosen.until);
statusIdChosen({ chosen.id }, chosen.until);
}, raw->lifetime());
const auto session = &show->session();
std::move(statusIdValue) | rpl::start_with_next([=](DocumentId id) {
std::move(statusIdValue) | rpl::start_with_next([=](EmojiStatusId id) {
state->statusId = id;
state->emoji = id
? session->data().customEmojiManager().create(
id,
Data::EmojiStatusCustomId(id),
[=] { right->update(); })
: nullptr;
right->resize(
@@ -985,13 +989,12 @@ int ColorSelector::resizeGetHeight(int newWidth) {
}, right->lifetime());
raw->setClickedCallback([=] {
const auto controller = show->resolveWindow(
ChatHelpers::WindowUsage::PremiumPromo);
const auto controller = show->resolveWindow();
if (controller) {
state->panel.show({
.controller = controller,
.button = right,
.ensureAddedEmojiId = state->statusId,
.ensureAddedEmojiId = { state->statusId },
.channelStatusMode = true,
});
}
@@ -1183,7 +1186,7 @@ void EditPeerColorBox(
struct State {
rpl::variable<uint8> index;
rpl::variable<DocumentId> emojiId;
rpl::variable<DocumentId> statusId;
rpl::variable<EmojiStatusId> statusId;
TimeId statusUntil = 0;
bool statusChanged = false;
bool changing = false;
@@ -1263,8 +1266,7 @@ void EditPeerColorBox(
{ &st::menuBlueIconWallpaper }
);
button->setClickedCallback([=] {
const auto usage = ChatHelpers::WindowUsage::PremiumPromo;
if (const auto strong = show->resolveWindow(usage)) {
if (const auto strong = show->resolveWindow()) {
show->show(Box<BackgroundBox>(strong, channel));
}
});
@@ -1321,7 +1323,7 @@ void EditPeerColorBox(
show,
channel,
state->statusId.value(),
[=](DocumentId id, TimeId until) {
[=](EmojiStatusId id, TimeId until) {
state->statusId = id;
state->statusUntil = until;
state->statusChanged = true;
@@ -1471,8 +1473,7 @@ void CheckBoostLevel(
return;
}
const auto openStatistics = [=] {
if (const auto controller = show->resolveWindow(
ChatHelpers::WindowUsage::PremiumPromo)) {
if (const auto controller = show->resolveWindow()) {
controller->showSection(Info::Boosts::Make(peer));
}
};

View File

@@ -219,6 +219,33 @@ void SaveSlowmodeSeconds(
api->registerModifyRequest(key, requestId);
}
void SaveStarsPerMessage(
not_null<ChannelData*> channel,
int starsPerMessage,
Fn<void()> done) {
const auto api = &channel->session().api();
const auto key = Api::RequestKey("stars_per_message", channel->id);
const auto requestId = api->request(MTPchannels_UpdatePaidMessagesPrice(
channel->inputChannel,
MTP_long(starsPerMessage)
)).done([=](const MTPUpdates &result) {
api->clearModifyRequest(key);
api->applyUpdates(result);
channel->setStarsPerMessage(starsPerMessage);
done();
}).fail([=](const MTP::Error &error) {
api->clearModifyRequest(key);
if (error.type() != u"CHAT_NOT_MODIFIED"_q) {
return;
}
channel->setStarsPerMessage(starsPerMessage);
done();
}).send();
api->registerModifyRequest(key, requestId);
}
void SaveBoostsUnrestrict(
not_null<ChannelData*> channel,
int boostsUnrestrict,
@@ -271,6 +298,7 @@ void ShowEditPermissions(
channel,
result.boostsUnrestrict,
close);
SaveStarsPerMessage(channel, result.starsPerMessage, close);
}
};
auto done = [=](EditPeerPermissionsBoxResult result) {
@@ -282,7 +310,9 @@ void ShowEditPermissions(
const auto saveFor = peer->migrateToOrMe();
const auto chat = saveFor->asChat();
if (!chat
|| (!result.slowmodeSeconds && !result.boostsUnrestrict)) {
|| (!result.slowmodeSeconds
&& !result.boostsUnrestrict
&& !result.starsPerMessage)) {
save(saveFor, result);
return;
}
@@ -1244,7 +1274,9 @@ void Controller::fillManageSection() {
? channel->canViewMembers()
: chat->amIn();
const auto canViewKicked = isChannel
&& (channel->isBroadcast() || channel->isGigagroup());
&& (channel->isMegagroup()
? (channel->isBroadcast() || channel->isGigagroup())
: true);
const auto hasRecentActions = isChannel
&& (channel->hasAdminRights() || channel->amCreator());
const auto hasStarRef = Info::BotStarRef::Join::Allowed(_peer)
@@ -2692,3 +2724,9 @@ bool EditPeerInfoBox::Available(not_null<PeerData*> peer) {
return false;
}
}
void ShowEditChatPermissions(
not_null<Window::SessionNavigation*> navigation,
not_null<PeerData*> peer) {
ShowEditPermissions(navigation, peer);
}

View File

@@ -56,3 +56,7 @@ private:
not_null<PeerData*> _peer;
};
void ShowEditChatPermissions(
not_null<Window::SessionNavigation*> navigation,
not_null<PeerData*> peer);

View File

@@ -15,7 +15,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "boxes/peer_list_controllers.h"
#include "boxes/share_box.h"
#include "core/application.h"
#include "core/ui_integration.h" // Core::MarkedTextContext.
#include "core/ui_integration.h" // TextContext
#include "data/components/credits.h"
#include "data/data_changes.h"
#include "data/data_channel.h"
@@ -740,10 +740,10 @@ void Controller::setupAboveJoinedWidget() {
{ QString::number(current.subscription.credits) },
Ui::Text::WithEntities),
kMarkupTextOptions,
Core::MarkedTextContext{
Core::TextContext({
.session = &session(),
.customEmojiRepaint = [=] { widget->update(); },
});
.repaint = [=] { widget->update(); },
}));
auto &lifetime = widget->lifetime();
const auto rateValue = lifetime.make_state<rpl::variable<float64>>(
session().credits().rateValue(_peer));
@@ -994,10 +994,7 @@ void Controller::rowClicked(not_null<PeerListRow*> row) {
lt_cost,
{ QString::number(data.subscription.credits) },
Ui::Text::WithEntities),
Core::MarkedTextContext{
.session = session,
.customEmojiRepaint = [=] { subtitle1->update(); },
});
Core::TextContext({ .session = session }));
const auto subtitle2 = box->addRow(
object_ptr<Ui::CenterWrap<Ui::FlatLabel>>(
box,
@@ -1019,7 +1016,8 @@ void Controller::rowClicked(not_null<PeerListRow*> row) {
Ui::AddSkip(content);
Ui::AddSkip(content);
AddSubscriberEntryTable(controller, content, row->peer(), data.date);
const auto show = controller->uiShow();
AddSubscriberEntryTable(show, content, {}, row->peer(), data.date);
Ui::AddSkip(content);
Ui::AddSkip(content);
@@ -1483,8 +1481,12 @@ object_ptr<Ui::BoxContent> ShareInviteLinkBox(
? tr::lng_group_invite_copied(tr::now)
: copied);
};
auto countMessagesCallback = [=](const TextWithTags &comment) {
return 1;
};
auto submitCallback = [=](
std::vector<not_null<Data::Thread*>> &&result,
Fn<bool()> checkPaid,
TextWithTags &&comment,
Api::SendOptions options,
Data::ForwardOptions) {
@@ -1502,6 +1504,8 @@ object_ptr<Ui::BoxContent> ShareInviteLinkBox(
result.size() > 1));
}
return;
} else if (!checkPaid()) {
return;
}
*sending = true;
@@ -1529,7 +1533,7 @@ object_ptr<Ui::BoxContent> ShareInviteLinkBox(
};
auto filterCallback = [](not_null<Data::Thread*> thread) {
if (const auto user = thread->peer()->asUser()) {
if (user->canSendIgnoreRequirePremium()) {
if (user->canSendIgnoreMoneyRestrictions()) {
return true;
}
}
@@ -1538,9 +1542,10 @@ object_ptr<Ui::BoxContent> ShareInviteLinkBox(
auto object = Box<ShareBox>(ShareBox::Descriptor{
.session = session,
.copyCallback = std::move(copyCallback),
.countMessagesCallback = std::move(countMessagesCallback),
.submitCallback = std::move(submitCallback),
.filterCallback = std::move(filterCallback),
.premiumRequiredError = SharePremiumRequiredError(),
.moneyRestrictionError = ShareMessageMoneyRestrictionError(),
});
*box = Ui::MakeWeak(object.data());
return object;

View File

@@ -31,10 +31,12 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "info/profile/info_profile_values.h"
#include "boxes/peers/edit_participants_box.h"
#include "boxes/peers/edit_peer_info_box.h"
#include "boxes/edit_privacy_box.h"
#include "settings/settings_power_saving.h"
#include "window/window_session_controller.h"
#include "window/window_controller.h"
#include "main/main_session.h"
#include "mtproto/mtproto_config.h" // megagroupSizeMax
#include "apiwrap.h"
#include "settings/settings_common.h"
#include "styles/style_layers.h"
@@ -49,7 +51,6 @@ namespace {
constexpr auto kSlowmodeValues = 7;
constexpr auto kBoostsUnrestrictValues = 5;
constexpr auto kSuggestGigagroupThreshold = 199000;
constexpr auto kForceDisableTooltipDuration = 3 * crl::time(1000);
[[nodiscard]] auto Dependencies(PowerSaving::Flags)
@@ -889,11 +890,10 @@ void AddBoostsUnrestrictLabels(
manager->registerInternalEmoji(
st::boostsMessageIcon,
st::boostsMessageIconPadding));
const auto context = Core::MarkedTextContext{
const auto context = Core::TextContext({
.session = session,
.customEmojiRepaint = [] {},
.customEmojiLoopLimit = 1,
};
});
for (auto i = 0; i != kBoostsUnrestrictValues; ++i) {
const auto label = Ui::CreateChild<Ui::FlatLabel>(
labels,
@@ -940,9 +940,7 @@ rpl::producer<int> AddBoostsUnrestrictSlider(
const auto boostsUnrestrict = lifetime.make_state<rpl::variable<int>>(
channel ? channel->boostsUnrestrict() : 0);
container->add(
object_ptr<Ui::BoxContentDivider>(container),
{ 0, st::infoProfileSkip, 0, st::infoProfileSkip });
Ui::AddSkip(container);
auto enabled = boostsUnrestrict->value(
) | rpl::map(_1 > 0);
@@ -1006,19 +1004,20 @@ rpl::producer<int> AddBoostsUnrestrictWrapped(
object_ptr<Ui::SlideWrap<Ui::VerticalLayout>>(
container,
object_ptr<Ui::VerticalLayout>(container)));
wrap->toggleOn(rpl::duplicate(shown), anim::type::normal);
wrap->toggleOn(std::move(shown), anim::type::normal);
wrap->finishAnimating();
auto result = AddBoostsUnrestrictSlider(wrap->entity(), peer);
const auto divider = container->add(
const auto inner = wrap->entity();
auto result = AddBoostsUnrestrictSlider(inner, peer);
const auto skip = st::defaultVerticalListSkip;
const auto divider = inner->add(
object_ptr<Ui::SlideWrap<Ui::BoxContentDivider>>(
container,
object_ptr<Ui::BoxContentDivider>(container),
QMargins{ 0, st::infoProfileSkip, 0, st::infoProfileSkip }));
divider->toggleOn(rpl::combine(
std::move(shown),
rpl::duplicate(result),
!rpl::mappers::_1 || !rpl::mappers::_2));
inner,
object_ptr<Ui::BoxContentDivider>(inner),
QMargins{ 0, skip, 0, skip }));
divider->toggleOn(rpl::duplicate(result) | rpl::map(!rpl::mappers::_1));
divider->finishAnimating();
return result;
@@ -1157,7 +1156,43 @@ void ShowEditPeerPermissionsBox(
rpl::variable<int> slowmodeSeconds;
rpl::variable<int> boostsUnrestrict;
rpl::variable<bool> hasSendRestrictions;
rpl::variable<int> starsPerMessage;
};
const auto state = inner->lifetime().make_state<State>();
const auto channel = peer->asChannel();
const auto available = channel && channel->paidMessagesAvailable();
Ui::AddSkip(inner);
Ui::AddDivider(inner);
auto charging = (Ui::SettingsButton*)nullptr;
if (available) {
Ui::AddSkip(inner);
const auto starsPerMessage = peer->isChannel()
? peer->asChannel()->starsPerMessage()
: 0;
charging = inner->add(object_ptr<Ui::SettingsButton>(
inner,
tr::lng_rights_charge_stars(),
st::settingsButtonNoIcon));
charging->toggleOn(rpl::single(starsPerMessage > 0));
Ui::AddSkip(inner);
Ui::AddDividerText(inner, tr::lng_rights_charge_stars_about());
const auto chargeWrap = inner->add(
object_ptr<Ui::SlideWrap<Ui::VerticalLayout>>(
inner,
object_ptr<Ui::VerticalLayout>(inner)));
chargeWrap->toggleOn(charging->toggledValue());
chargeWrap->finishAnimating();
const auto chargeInner = chargeWrap->entity();
Ui::AddSkip(chargeInner);
state->starsPerMessage = SetupChargeSlider(
chargeInner,
peer,
starsPerMessage);
}
static constexpr auto kSendRestrictions = Flag::EmbedLinks
| Flag::SendGames
| Flag::SendGifs
@@ -1171,7 +1206,6 @@ void ShowEditPeerPermissionsBox(
| Flag::SendVoiceMessages
| Flag::SendFiles
| Flag::SendOther;
const auto state = inner->lifetime().make_state<State>();
state->hasSendRestrictions = ((restrictions & kSendRestrictions) != 0)
|| (peer->isChannel() && peer->asChannel()->slowmodeSeconds() > 0);
state->boostsUnrestrict = AddBoostsUnrestrictWrapped(
@@ -1189,8 +1223,11 @@ void ShowEditPeerPermissionsBox(
});
if (const auto channel = peer->asChannel()) {
constexpr auto kThresholdOffset = int(1000);
const auto threshold = -kThresholdOffset
+ channel->session().serverConfig().megagroupSizeMax;
if (channel->amCreator()
&& channel->membersCount() >= kSuggestGigagroupThreshold) {
&& channel->membersCount() >= threshold) {
AddSuggestGigagroup(
inner,
AboutGigagroupCallback(
@@ -1209,10 +1246,14 @@ void ShowEditPeerPermissionsBox(
const auto boostsUnrestrict = hasRestrictions
? state->boostsUnrestrict.current()
: 0;
const auto starsPerMessage = (charging && charging->toggled())
? state->starsPerMessage.current()
: 0;
done({
restrictions,
slowmodeSeconds,
boostsUnrestrict,
starsPerMessage,
});
});
box->addButton(tr::lng_cancel(), [=] { box->closeBox(); });

View File

@@ -39,6 +39,7 @@ struct EditPeerPermissionsBoxResult final {
ChatRestrictions rights;
int slowmodeSeconds = 0;
int boostsUnrestrict = 0;
int starsPerMessage = 0;
};
void ShowEditPeerPermissionsBox(

View File

@@ -9,6 +9,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "apiwrap.h"
#include "base/event_filter.h"
#include "chat_helpers/emoji_list_widget.h"
#include "chat_helpers/tabbed_panel.h"
#include "chat_helpers/tabbed_selector.h"
#include "core/ui_integration.h"
@@ -362,12 +363,17 @@ object_ptr<Ui::RpWidget> AddReactionsSelector(
const auto customEmojiPaused = [controller = args.controller] {
return controller->isGifPausedAtLeastFor(PauseReason::Layer);
};
auto factory = [=](QStringView data, Fn<void()> update)
-> std::unique_ptr<Ui::Text::CustomEmoji> {
auto simpleContext = Core::TextContext({
.session = session,
.repaint = [=] { raw->update(); },
});
auto context = simpleContext;
context.customEmojiFactory = [=](
QStringView data,
const Ui::Text::MarkedContext &context
) -> std::unique_ptr<Ui::Text::CustomEmoji> {
const auto id = Data::ParseCustomEmojiData(data);
auto result = owner->customEmojiManager().create(
data,
std::move(update));
auto result = Ui::Text::MakeCustomEmoji(data, simpleContext);
if (state->unifiedFactoryOwner->lookupReactionId(id).custom()) {
return std::make_unique<MaybeDisabledEmoji>(
std::move(result),
@@ -376,12 +382,10 @@ object_ptr<Ui::RpWidget> AddReactionsSelector(
using namespace Ui::Text;
return std::make_unique<FirstFrameEmoji>(std::move(result));
};
raw->setCustomTextContext([=](Fn<void()> repaint) {
return std::any(Core::MarkedTextContext{
.session = session,
.customEmojiRepaint = std::move(repaint),
});
}, customEmojiPaused, customEmojiPaused, std::move(factory));
raw->setCustomTextContext(
std::move(context),
customEmojiPaused,
customEmojiPaused);
const auto callback = args.callback;
const auto isCustom = [=](DocumentId id) {
@@ -491,7 +495,8 @@ object_ptr<Ui::RpWidget> AddReactionsSelector(
panelList.erase(
ranges::remove(panelList, paid->selectAnimation->id),
end(panelList));
panel->selector()->provideRecentEmoji(panelList);
panel->selector()->provideRecentEmoji(
ChatHelpers::DocumentListToRecent(panelList));
panel->setDesiredHeightValues(
1.,
st::emojiPanMinHeight / 2,

View File

@@ -106,6 +106,8 @@ PeerShortInfoCover::PeerShortInfoCover(
, _statusStyle(std::make_unique<CustomLabelStyle>(_st.status))
, _status(_widget.get(), std::move(status), _statusStyle->st)
, _roundMask(Images::CornersMask(_st.radius))
, _roundMaskRetina(
Images::CornersMask(_st.radius / style::DevicePixelRatio()))
, _videoPaused(std::move(videoPaused)) {
_widget->setCursor(_cursor);
@@ -190,7 +192,7 @@ void PeerShortInfoCover::paint(QPainter &p) {
if (!frame.isNull()) {
frame = Images::Round(
std::move(frame),
_roundMask,
_roundMaskRetina,
RectPart::TopLeft | RectPart::TopRight);
} else if (_userpicImage.isNull()) {
auto image = QImage(
@@ -226,10 +228,11 @@ void PeerShortInfoCover::paintCoverImage(QPainter &p, const QImage &image) {
const auto top = _widget->height() - fill;
const auto factor = style::DevicePixelRatio();
if (fill > 0) {
const auto t = roundedHeight + _scrollTop;
p.drawImage(
QRect(0, top, roundedWidth, fill),
QRect(0, t, roundedWidth * factor, (roundedWidth - t) * factor),
image,
QRect(0, top * factor, roundedWidth * factor, fill * factor));
QRect(0, t, roundedWidth * factor, (roundedWidth - t) * factor));
}
if (covered <= 0) {
return;
@@ -238,9 +241,9 @@ void PeerShortInfoCover::paintCoverImage(QPainter &p, const QImage &image) {
const auto from = top - rounded;
auto q = QPainter(&_roundedTopImage);
q.drawImage(
QRect(0, 0, roundedWidth, rounded),
QRect(0, 0, roundedWidth * factor, rounded * factor),
image,
QRect(0, from * factor, roundedWidth * factor, rounded * factor));
QRect(0, _scrollTop, roundedWidth * factor, rounded * factor));
q.end();
_roundedTopImage = Images::Round(
std::move(_roundedTopImage),

View File

@@ -123,6 +123,7 @@ private:
object_ptr<Ui::FlatLabel> _additionalStatus = { nullptr };
std::array<QImage, 4> _roundMask;
std::array<QImage, 4> _roundMaskRetina;
QImage _userpicImage;
QImage _roundedTopImage;
QImage _barSmall;

View File

@@ -486,20 +486,23 @@ object_ptr<Ui::BoxContent> PrepareShortInfoBox(
object_ptr<Ui::BoxContent> PrepareShortInfoBox(
not_null<PeerData*> peer,
not_null<Window::SessionNavigation*> navigation,
std::shared_ptr<ChatHelpers::Show> show,
const style::ShortInfoBox *stOverride) {
const auto open = [=] { navigation->showPeerHistory(peer); };
const auto open = [=] {
if (const auto window = show->resolveWindow()) {
window->showPeerHistory(peer);
}
};
const auto videoIsPaused = [=] {
return navigation->parentController()->isGifPausedAtLeastFor(
Window::GifPauseReason::Layer);
return show->paused(Window::GifPauseReason::Layer);
};
auto menuFiller = [=](Ui::Menu::MenuCallback addAction) {
const auto controller = navigation->parentController();
const auto peerSeparateId = Window::SeparateId(peer);
if (controller->windowId() != peerSeparateId) {
const auto window = show->resolveWindow();
if (window && window->windowId() != peerSeparateId) {
addAction(tr::lng_context_new_window(tr::now), [=] {
Ui::PreventDelayedActivation();
controller->showInNewWindow(peer);
window->showInNewWindow(peer);
}, &st::menuIconNewWindow);
}
};
@@ -511,6 +514,13 @@ object_ptr<Ui::BoxContent> PrepareShortInfoBox(
stOverride);
}
object_ptr<Ui::BoxContent> PrepareShortInfoBox(
not_null<PeerData*> peer,
not_null<Window::SessionNavigation*> navigation,
const style::ShortInfoBox *stOverride) {
return PrepareShortInfoBox(peer, navigation->uiShow(), stOverride);
}
rpl::producer<QString> PrepareShortInfoStatus(not_null<PeerData*> peer) {
return StatusValue(peer);
}

View File

@@ -16,6 +16,10 @@ struct ShortInfoCover;
struct ShortInfoBox;
} // namespace style
namespace ChatHelpers {
class Show;
} // namespace ChatHelpers
namespace Ui::Menu {
struct MenuCallback;
} // namespace Ui::Menu
@@ -42,6 +46,11 @@ struct PreparedShortInfoUserpic {
Fn<void(Ui::Menu::MenuCallback)> menuFiller,
const style::ShortInfoBox *stOverride = nullptr);
[[nodiscard]] object_ptr<Ui::BoxContent> PrepareShortInfoBox(
not_null<PeerData*> peer,
std::shared_ptr<ChatHelpers::Show> show,
const style::ShortInfoBox *stOverride = nullptr);
[[nodiscard]] object_ptr<Ui::BoxContent> PrepareShortInfoBox(
not_null<PeerData*> peer,
not_null<Window::SessionNavigation*> navigation,

View File

@@ -1139,8 +1139,7 @@ void PreviewBox(
button->resizeToWidth(width);
if (!descriptor.fromSettings) {
button->setClickedCallback([=] {
const auto window = show->resolveWindow(
ChatHelpers::WindowUsage::PremiumPromo);
const auto window = show->resolveWindow();
if (!window) {
return;
}
@@ -1527,6 +1526,18 @@ void DoubledLimitsPreviewBox(
Main::Domain::kPremiumMaxAccounts,
till,
});
{
const auto premium = limits.similarChannelsPremium();
entries.push_back({
tr::lng_premium_double_limits_subtitle_similar_channels(),
tr::lng_premium_double_limits_about_similar_channels(
lt_count,
rpl::single(float64(premium)),
Ui::Text::RichLangValue),
limits.similarChannelsDefault(),
premium,
});
}
Ui::Premium::ShowListBox(
box,
st::defaultPremiumLimits,

View File

@@ -9,7 +9,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 "core/ui_integration.h" // TextContext
#include "data/components/credits.h"
#include "data/data_credits.h"
#include "data/data_photo.h"
@@ -511,32 +511,28 @@ TextWithEntities CreditsEmoji(not_null<Main::Session*> session) {
}
TextWithEntities CreditsEmojiSmall(not_null<Main::Session*> session) {
return Ui::Text::SingleCustomEmoji(
session->data().customEmojiManager().registerInternalEmoji(
st::starIconSmall,
st::starIconSmallPadding,
true),
return Ui::Text::IconEmoji(
&st::starIconEmoji,
QString(QChar(0x2B50)));
}
not_null<FlatLabel*> SetButtonMarkedLabel(
not_null<RpWidget*> button,
rpl::producer<TextWithEntities> text,
Fn<std::any(Fn<void()> update)> context,
Text::MarkedContext context,
const style::FlatLabel &st,
const style::color *textFg) {
const auto buttonLabel = Ui::CreateChild<Ui::FlatLabel>(
button,
rpl::single(QString()),
st);
context.repaint = [=] { buttonLabel->update(); };
rpl::duplicate(
text
) | rpl::filter([=](const TextWithEntities &text) {
return !text.text.isEmpty();
}) | rpl::start_with_next([=](const TextWithEntities &text) {
buttonLabel->setMarkedText(
text,
context([=] { buttonLabel->update(); }));
buttonLabel->setMarkedText(text, context);
}, buttonLabel->lifetime());
if (textFg) {
buttonLabel->setTextColorOverride((*textFg)->c);
@@ -565,15 +561,12 @@ not_null<FlatLabel*> SetButtonMarkedLabel(
not_null<Main::Session*> session,
const style::FlatLabel &st,
const style::color *textFg) {
return SetButtonMarkedLabel(button, text, [=](Fn<void()> update) {
return Core::MarkedTextContext{
.session = session,
.customEmojiRepaint = update,
};
}, st, textFg);
return SetButtonMarkedLabel(button, text, Core::TextContext({
.session = session,
}), st, textFg);
}
void SendStarGift(
void SendStarsForm(
not_null<Main::Session*> session,
std::shared_ptr<Payments::CreditsFormData> data,
Fn<void(std::optional<QString>)> done) {

View File

@@ -41,7 +41,7 @@ void SendCreditsBox(
not_null<FlatLabel*> SetButtonMarkedLabel(
not_null<RpWidget*> button,
rpl::producer<TextWithEntities> text,
Fn<std::any(Fn<void()> update)> context,
Text::MarkedContext context,
const style::FlatLabel &st,
const style::color *textFg = nullptr);
@@ -52,7 +52,7 @@ not_null<FlatLabel*> SetButtonMarkedLabel(
const style::FlatLabel &st,
const style::color *textFg = nullptr);
void SendStarGift(
void SendStarsForm(
not_null<Main::Session*> session,
std::shared_ptr<Payments::CreditsFormData> data,
Fn<void(std::optional<QString>)> done);

View File

@@ -59,9 +59,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "window/window_session_controller.h"
#include "core/application.h"
#include "core/core_settings.h"
#include "styles/style_layers.h"
#include "styles/style_boxes.h"
#include "styles/style_chat_helpers.h"
#include "styles/style_layers.h"
#include <QtCore/QMimeData>
@@ -242,7 +242,7 @@ SendFilesBox::Block::Block(
int till,
Fn<bool()> gifPaused,
SendFilesWay way,
Fn<bool()> canToggleSpoiler)
Fn<bool(const Ui::PreparedFile &, Ui::AttachActionType)> actionAllowed)
: _items(items)
, _from(from)
, _till(till) {
@@ -260,7 +260,9 @@ SendFilesBox::Block::Block(
st,
my,
way,
std::move(canToggleSpoiler));
[=](int index, Ui::AttachActionType type) {
return actionAllowed((*_items)[from + index], type);
});
_preview.reset(preview);
} else {
const auto media = Ui::SingleMediaPreview::Create(
@@ -268,7 +270,9 @@ SendFilesBox::Block::Block(
st,
gifPaused,
first,
std::move(canToggleSpoiler));
[=](Ui::AttachActionType type) {
return actionAllowed((*_items)[from], type);
});
if (media) {
_isSingleMedia = true;
_preview.reset(media);
@@ -344,6 +348,38 @@ rpl::producer<int> SendFilesBox::Block::itemModifyRequest() const {
}
}
rpl::producer<int> SendFilesBox::Block::itemEditCoverRequest() const {
using namespace rpl::mappers;
const auto preview = _preview.get();
const auto from = _from;
if (_isAlbum) {
const auto album = static_cast<Ui::AlbumPreview*>(preview);
return album->thumbEditCoverRequested() | rpl::map(_1 + from);
} else if (_isSingleMedia) {
const auto media = static_cast<Ui::SingleMediaPreview*>(preview);
return media->editCoverRequests() | rpl::map_to(from);
} else {
return rpl::never<int>();
}
}
rpl::producer<int> SendFilesBox::Block::itemClearCoverRequest() const {
using namespace rpl::mappers;
const auto preview = _preview.get();
const auto from = _from;
if (_isAlbum) {
const auto album = static_cast<Ui::AlbumPreview*>(preview);
return album->thumbClearCoverRequested() | rpl::map(_1 + from);
} else if (_isSingleMedia) {
const auto media = static_cast<Ui::SingleMediaPreview*>(preview);
return media->clearCoverRequests() | rpl::map_to(from);
} else {
return rpl::never<int>();
}
}
rpl::producer<> SendFilesBox::Block::orderUpdated() const {
if (_isAlbum) {
const auto album = static_cast<Ui::AlbumPreview*>(_preview.get());
@@ -678,6 +714,18 @@ void SendFilesBox::openDialogToAddFileToAlbum() {
crl::guard(this, callback));
}
void SendFilesBox::refreshMessagesCount() {
const auto way = _sendWay.current();
const auto withCaption = _list.canAddCaption(
way.groupFiles() && way.sendImagesAsPhotos(),
way.sendImagesAsPhotos());
const auto withComment = !withCaption
&& _caption
&& !_caption->isHidden()
&& !_caption->getTextWithTags().text.isEmpty();
_messagesCount = _list.files.size() + (withComment ? 1 : 0);
}
void SendFilesBox::refreshButtons() {
clearButtons();
@@ -686,6 +734,15 @@ void SendFilesBox::refreshButtons() {
? tr::lng_send_button()
: tr::lng_create_group_next()),
[=] { send({}); });
refreshMessagesCount();
const auto perMessage = _captionToPeer
? _captionToPeer->starsPerMessageChecked()
: 0;
if (perMessage > 0) {
_send->setText(PaidSendButtonText(_messagesCount.value(
) | rpl::map(rpl::mappers::_1 * perMessage)));
}
if (_sendType == Api::SendType::Normal) {
SendMenu::SetupMenuAndShortcuts(
_send,
@@ -802,10 +859,9 @@ void SendFilesBox::refreshPriceTag() {
QString(),
st::paidTagLabel);
std::move(text) | rpl::start_with_next([=](TextWithEntities &&text) {
label->setMarkedText(text, Core::MarkedTextContext{
label->setMarkedText(text, Core::TextContext({
.session = session,
.customEmojiRepaint = [=] { label->update(); },
});
}));
}, label->lifetime());
label->show();
label->sizeValue() | rpl::start_with_next([=](QSize size) {
@@ -1008,7 +1064,16 @@ void SendFilesBox::pushBlock(int from, int till) {
till,
gifPaused,
_sendWay.current(),
[=] { return !hasPrice(); });
[=](const Ui::PreparedFile &file, Ui::AttachActionType type) {
return (type == Ui::AttachActionType::ToggleSpoiler)
? !hasPrice()
: (type == Ui::AttachActionType::EditCover)
? (file.isVideoFile()
&& _captionToPeer
&& (_captionToPeer->isBroadcast()
|| _captionToPeer->isSelf()))
: (file.videoCover != nullptr);
});
auto &block = _blocks.back();
const auto widget = _inner->add(
block.takeWidget(),
@@ -1129,7 +1194,79 @@ void SendFilesBox::pushBlock(int from, int till) {
show,
&_list.files[index],
st::sendMediaPreviewSize,
[=] { refreshAllAfterChanges(from); });
[=](bool ok) { if (ok) refreshAllAfterChanges(from); });
}, widget->lifetime());
block.itemEditCoverRequest(
) | rpl::start_with_next([=, show = _show](int index) {
applyBlockChanges();
const auto replace = [=](Ui::PreparedList list) {
if (list.files.empty()) {
return;
}
auto &entry = _list.files[index];
const auto video = entry.information
? std::get_if<Ui::PreparedFileInformation::Video>(
&entry.information->media)
: nullptr;
if (!video) {
return;
}
auto old = std::shared_ptr<Ui::PreparedFile>(
std::move(entry.videoCover));
entry.videoCover = std::make_unique<Ui::PreparedFile>(
std::move(list.files.front()));
Editor::OpenWithPreparedFile(
this,
show,
entry.videoCover.get(),
st::sendMediaPreviewSize,
crl::guard(this, [=](bool ok) {
if (!ok) {
_list.files[index].videoCover = old
? std::make_unique<Ui::PreparedFile>(
std::move(*old))
: nullptr;
}
refreshAllAfterChanges(from);
}),
video->thumbnail.size());
};
const auto checkResult = [=](const Ui::PreparedList &list) {
if (list.files.empty()) {
return true;
}
if (list.files.front().type != Ui::PreparedFile::Type::Photo) {
show->showToast(tr::lng_choose_cover_bad(tr::now));
return false;
}
return true;
};
const auto callback = [=](FileDialog::OpenResult &&result) {
const auto premium = _show->session().premium();
FileDialogCallback(
std::move(result),
checkResult,
replace,
premium,
show);
};
FileDialog::GetOpenPath(
this,
tr::lng_choose_cover(tr::now),
FileDialog::ImagesFilter(),
crl::guard(this, callback));
}, widget->lifetime());
block.itemClearCoverRequest(
) | rpl::start_with_next([=](int index) {
applyBlockChanges();
refreshAllAfterChanges(from, [&] {
auto &entry = _list.files[index];
entry.videoCover = nullptr;
});
}, widget->lifetime());
block.orderUpdated() | rpl::start_with_next([=]{
@@ -1334,6 +1471,7 @@ void SendFilesBox::setupCaption() {
_caption->changes()
) | rpl::start_with_next([=] {
checkCharsLimitation();
refreshMessagesCount();
}, _caption->lifetime());
}

View File

@@ -153,7 +153,9 @@ private:
int till,
Fn<bool()> gifPaused,
Ui::SendFilesWay way,
Fn<bool()> canToggleSpoiler);
Fn<bool(
const Ui::PreparedFile &,
Ui::AttachActionType)> actionAllowed);
Block(Block &&other) = default;
Block &operator=(Block &&other) = default;
@@ -164,6 +166,8 @@ private:
[[nodiscard]] rpl::producer<int> itemDeleteRequest() const;
[[nodiscard]] rpl::producer<int> itemReplaceRequest() const;
[[nodiscard]] rpl::producer<int> itemModifyRequest() const;
[[nodiscard]] rpl::producer<int> itemEditCoverRequest() const;
[[nodiscard]] rpl::producer<int> itemClearCoverRequest() const;
[[nodiscard]] rpl::producer<> orderUpdated() const;
void setSendWay(Ui::SendFilesWay way);
@@ -242,6 +246,7 @@ private:
void addPreparedAsyncFile(Ui::PreparedFile &&file);
void checkCharsLimitation();
void refreshMessagesCount();
[[nodiscard]] Fn<MenuDetails()> prepareSendMenuDetails(
const SendFilesBoxDescriptor &descriptor);
@@ -257,6 +262,7 @@ private:
Ui::PreparedList _list;
std::optional<int> _removingIndex;
rpl::variable<int> _messagesCount;
SendFilesLimits _limits = {};
Fn<MenuDetails()> _sendMenuDetails;

View File

@@ -53,6 +53,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "main/main_session.h"
#include "core/application.h"
#include "core/core_settings.h"
#include "styles/style_calls.h"
#include "styles/style_layers.h"
#include "styles/style_boxes.h"
#include "styles/style_menu_icons.h"
@@ -117,12 +118,13 @@ private:
Ui::RoundImageCheckbox checkbox;
Ui::Text::String name;
Ui::Animations::Simple nameActive;
bool locked = false;
Api::MessageMoneyRestriction restriction;
RestrictionBadgeCache badgeCache;
};
void invalidateCache();
bool showLockedError(not_null<Chat*> chat);
void refreshLockedRows();
void refreshRestrictedRows();
[[nodiscard]] int displayedChatsCount() const;
[[nodiscard]] not_null<Data::Thread*> chatThread(
@@ -131,7 +133,7 @@ private:
void paintChat(Painter &p, not_null<Chat*> chat, int index);
void updateChat(not_null<PeerData*> peer);
void updateChatName(not_null<Chat*> chat);
void initChatLocked(not_null<Chat*> chat);
void initChatRestriction(not_null<Chat*> chat);
void repaintChat(not_null<PeerData*> peer);
int chatIndex(not_null<PeerData*> peer) const;
void repaintChatAtIndex(int index);
@@ -197,16 +199,16 @@ ShareBox::ShareBox(QWidget*, Descriptor &&descriptor)
, _api(&_descriptor.session->mtp())
, _select(
this,
(_descriptor.stMultiSelect
? *_descriptor.stMultiSelect
(_descriptor.st.multiSelect
? *_descriptor.st.multiSelect
: st::defaultMultiSelect),
tr::lng_participant_filter())
, _comment(
this,
object_ptr<Ui::InputField>(
this,
(_descriptor.stComment
? *_descriptor.stComment
(_descriptor.st.comment
? *_descriptor.st.comment
: st::shareComment),
Ui::InputField::Mode::MultiLine,
tr::lng_photos_comment()),
@@ -251,7 +253,7 @@ void ShareBox::prepareCommentField() {
.session = _descriptor.session,
.show = Main::MakeSessionShow(show, _descriptor.session),
.field = field,
.fieldStyle = _descriptor.stLabel,
.fieldStyle = _descriptor.st.label,
});
}
field->setSubmitSettings(Core::App().settings().sendSubmitWay());
@@ -276,7 +278,9 @@ void ShareBox::prepare() {
_select->resizeToWidth(st::boxWideWidth);
Ui::SendPendingMoveResizeEvents(_select);
setTitle(tr::lng_share_title());
setTitle(_descriptor.titleOverride
? std::move(_descriptor.titleOverride)
: tr::lng_share_title());
_inner = setInnerWidget(
object_ptr<Inner>(this, _descriptor, uiShow()),
@@ -509,9 +513,19 @@ void ShareBox::keyPressEvent(QKeyEvent *e) {
SendMenu::Details ShareBox::sendMenuDetails() const {
const auto selected = _inner->selected();
const auto type = ranges::all_of(
selected | ranges::views::transform(&Data::Thread::peer),
HistoryView::CanScheduleUntilOnline)
const auto hasPaid = [&] {
for (const auto &thread : selected) {
if (thread->peer()->starsPerMessageChecked()) {
return true;
}
}
return false;
}();
const auto type = hasPaid
? SendMenu::Type::SilentOnly
: ranges::all_of(
selected | ranges::views::transform(&Data::Thread::peer),
HistoryView::CanScheduleUntilOnline)
? SendMenu::Type::ScheduledToUser
: (selected.size() == 1 && selected.front()->peer()->isSelf())
? SendMenu::Type::Reminder
@@ -561,6 +575,9 @@ void ShareBox::showMenu(not_null<Ui::RpWidget*> parent) {
submit(action.options);
return;
}
const auto st = _descriptor.st.scheduleBox
? *_descriptor.st.scheduleBox
: HistoryView::ScheduleBoxStyleArgs();
uiShow()->showBox(
HistoryView::PrepareScheduleBox(
this,
@@ -569,7 +586,7 @@ void ShareBox::showMenu(not_null<Ui::RpWidget*> parent) {
[=](Api::SendOptions options) { submit(options); },
action.options,
HistoryView::DefaultScheduleTime(),
_descriptor.scheduleBoxStyle));
st));
});
_menu->setForcedVerticalOrigin(Ui::PopupMenu::VerticalOrigin::Bottom);
const auto result = FillSendMenu(
@@ -603,6 +620,9 @@ void ShareBox::createButtons() {
showMenu(send);
}
}, send->lifetime());
send->setText(PaidSendButtonText(
_starsToSend.value(),
tr::lng_share_confirm()));
} else if (_descriptor.copyCallback) {
addButton(_copyLinkText.value(), [=] { copyLink(); });
}
@@ -646,6 +666,73 @@ void ShareBox::innerSelectedChanged(
}
void ShareBox::submit(Api::SendOptions options) {
_submitLifetime.destroy();
auto threads = _inner->selected();
const auto weak = Ui::MakeWeak(this);
const auto field = _comment->entity();
auto comment = field->getTextWithAppliedMarkdown();
const auto checkPaid = [=] {
if (!_descriptor.countMessagesCallback) {
return true;
}
const auto withPaymentApproved = crl::guard(weak, [=](int approved) {
auto copy = options;
copy.starsApproved = approved;
submit(copy);
});
const auto messagesCount = _descriptor.countMessagesCallback(
comment);
const auto alreadyApproved = options.starsApproved;
auto paid = std::vector<not_null<PeerData*>>();
auto waiting = base::flat_set<not_null<PeerData*>>();
auto totalStars = 0;
for (const auto &thread : threads) {
const auto peer = thread->peer();
const auto details = ComputePaymentDetails(peer, messagesCount);
if (!details) {
waiting.emplace(peer);
} else if (details->stars > 0) {
totalStars += details->stars;
paid.push_back(peer);
}
}
if (!waiting.empty()) {
_descriptor.session->changes().peerUpdates(
Data::PeerUpdate::Flag::FullInfo
) | rpl::start_with_next([=](const Data::PeerUpdate &update) {
if (waiting.contains(update.peer)) {
withPaymentApproved(alreadyApproved);
}
}, _submitLifetime);
if (!_descriptor.session->credits().loaded()) {
_descriptor.session->credits().loadedValue(
) | rpl::filter(
rpl::mappers::_1
) | rpl::take(1) | rpl::start_with_next([=] {
withPaymentApproved(alreadyApproved);
}, _submitLifetime);
}
return false;
} else if (totalStars > alreadyApproved) {
const auto show = uiShow();
const auto session = _descriptor.session;
const auto sessionShow = Main::MakeSessionShow(show, session);
const auto scheduleBoxSt = _descriptor.st.scheduleBox.get();
ShowSendPaidConfirm(sessionShow, paid, SendPaymentDetails{
.messages = messagesCount,
.stars = totalStars,
}, [=] { withPaymentApproved(totalStars); }, PaidConfirmStyles{
.label = (scheduleBoxSt
? scheduleBoxSt->chooseDateTimeArgs.labelStyle
: nullptr),
.checkbox = _descriptor.st.checkbox,
});
return false;
}
return true;
};
if (const auto onstack = _descriptor.submitCallback) {
const auto forwardOptions = (_forwardOptions.captionsCount
&& _forwardOptions.dropCaptions)
@@ -654,8 +741,9 @@ void ShareBox::submit(Api::SendOptions options) {
? Data::ForwardOptions::NoSenderNames
: Data::ForwardOptions::PreserveInfo;
onstack(
_inner->selected(),
_comment->entity()->getTextWithAppliedMarkdown(),
std::move(threads),
checkPaid,
std::move(comment),
options,
forwardOptions);
}
@@ -675,9 +763,23 @@ void ShareBox::selectedChanged() {
_comment->toggle(_hasSelected, anim::type::normal);
_comment->resizeToWidth(st::boxWideWidth);
}
computeStarsCount();
update();
}
void ShareBox::computeStarsCount() {
auto perMessage = 0;
for (const auto &thread : _inner->selected()) {
perMessage += thread->peer()->starsPerMessageChecked();
}
const auto messagesCount = _descriptor.countMessagesCallback
? _descriptor.countMessagesCallback(_comment
? _comment->entity()->getTextWithTags()
: TextWithTags())
: 0;
_starsToSend = perMessage * messagesCount;
}
void ShareBox::scrollTo(Ui::ScrollToRequest request) {
scrollToY(request.ymin, request.ymax);
//auto scrollTop = scrollArea()->scrollTop(), scrollBottom = scrollTop + scrollArea()->height();
@@ -704,7 +806,9 @@ ShareBox::Inner::Inner(
: RpWidget(parent)
, _descriptor(descriptor)
, _show(std::move(show))
, _st(_descriptor.st ? *_descriptor.st : st::shareBoxList)
, _st(_descriptor.st.peerList
? *_descriptor.st.peerList
: st::shareBoxList)
, _defaultChatsIndexed(
std::make_unique<Dialogs::IndexedList>(
Dialogs::SortMode::Add))
@@ -713,13 +817,13 @@ ShareBox::Inner::Inner(
_rowHeight = st::shareRowHeight;
setAttribute(Qt::WA_OpaquePaintEvent);
if (_descriptor.premiumRequiredError) {
if (_descriptor.moneyRestrictionError) {
const auto session = _descriptor.session;
rpl::merge(
Data::AmPremiumValue(session) | rpl::to_empty,
session->api().premium().somePremiumRequiredResolved()
session->api().premium().someMessageMoneyRestrictionsResolved()
) | rpl::start_with_next([=] {
refreshLockedRows();
refreshRestrictedRows();
}, lifetime());
}
@@ -780,38 +884,36 @@ void ShareBox::Inner::invalidateCache() {
}
bool ShareBox::Inner::showLockedError(not_null<Chat*> chat) {
if (!chat->locked) {
if (!chat->restriction.premiumRequired) {
return false;
}
::Settings::ShowPremiumPromoToast(
Main::MakeSessionShow(_show, _descriptor.session),
ChatHelpers::ResolveWindowDefault(),
_descriptor.premiumRequiredError(chat->peer->asUser()).text,
_descriptor.moneyRestrictionError(chat->peer->asUser()).text,
u"require_premium"_q);
return true;
}
void ShareBox::Inner::refreshLockedRows() {
void ShareBox::Inner::refreshRestrictedRows() {
auto changed = false;
for (const auto &[peer, data] : _dataMap) {
const auto history = data->history;
const auto locked = (Api::ResolveRequiresPremiumToWrite(
const auto restriction = Api::ResolveMessageMoneyRestrictions(
history->peer,
history
) == Api::RequirePremiumState::Yes);
if (data->locked != locked) {
data->locked = locked;
history);
if (data->restriction != restriction) {
data->restriction = restriction;
changed = true;
}
}
for (const auto &data : d_byUsernameFiltered) {
const auto history = data->history;
const auto locked = (Api::ResolveRequiresPremiumToWrite(
const auto restriction = Api::ResolveMessageMoneyRestrictions(
history->peer,
history
) == Api::RequirePremiumState::Yes);
if (data->locked != locked) {
data->locked = locked;
history);
if (data->restriction != restriction) {
data->restriction = restriction;
changed = true;
}
}
@@ -878,14 +980,14 @@ void ShareBox::Inner::updateChatName(not_null<Chat*> chat) {
chat->name.setText(_st.item.nameStyle, text, Ui::NameTextOptions());
}
void ShareBox::Inner::initChatLocked(not_null<Chat*> chat) {
if (_descriptor.premiumRequiredError) {
void ShareBox::Inner::initChatRestriction(not_null<Chat*> chat) {
if (_descriptor.moneyRestrictionError) {
const auto history = chat->history;
if (Api::ResolveRequiresPremiumToWrite(
const auto restriction = Api::ResolveMessageMoneyRestrictions(
history->peer,
history
) == Api::RequirePremiumState::Yes) {
chat->locked = true;
history);
if (restriction || restriction.known) {
chat->restriction = restriction;
}
}
}
@@ -1011,14 +1113,15 @@ void ShareBox::Inner::loadProfilePhotos() {
void ShareBox::Inner::preloadUserpic(not_null<Dialogs::Entry*> entry) {
entry->chatListPreloadData();
const auto history = entry->asHistory();
if (!_descriptor.premiumRequiredError || !history) {
if (!_descriptor.moneyRestrictionError || !history) {
return;
} else if (Api::ResolveRequiresPremiumToWrite(
history->peer,
history
) == Api::RequirePremiumState::Unknown) {
const auto user = history->peer->asUser();
_descriptor.session->api().premium().resolvePremiumRequired(user);
} else if (!Api::ResolveMessageMoneyRestrictions(
history->peer,
history).known) {
if (const auto user = history->peer->asUser()) {
const auto api = &_descriptor.session->api();
api->premium().resolveMessageMoneyRestrictions(user);
}
}
}
@@ -1041,7 +1144,7 @@ auto ShareBox::Inner::getChat(not_null<Dialogs::Row*> row)
repaintChat(peer);
}));
updateChatName(i->second.get());
initChatLocked(i->second.get());
initChatRestriction(i->second.get());
row->attached = i->second.get();
return i->second.get();
}
@@ -1075,10 +1178,12 @@ void ShareBox::Inner::paintChat(
auto photoTop = st::sharePhotoTop;
chat->checkbox.paint(p, x + photoLeft, y + photoTop, outerWidth);
if (chat->locked) {
PaintPremiumRequiredLock(
if (chat->restriction) {
PaintRestrictionBadge(
p,
&_st.item,
chat->restriction.starsPerMessage,
chat->badgeCache,
x + photoLeft,
y + photoTop,
outerWidth,
@@ -1433,7 +1538,7 @@ void ShareBox::Inner::peopleReceived(
_st.item,
[=] { repaintChat(peer); }));
updateChatName(d_byUsernameFiltered.back().get());
initChatLocked(d_byUsernameFiltered.back().get());
initChatRestriction(d_byUsernameFiltered.back().get());
}
}
};
@@ -1486,22 +1591,34 @@ ChatHelpers::ForwardedMessagePhraseArgs CreateForwardedMessagePhraseArgs(
};
}
ShareBox::CountMessagesCallback ShareBox::DefaultForwardCountMessages(
not_null<History*> history,
MessageIdsList msgIds) {
return [=](const TextWithTags &comment) {
const auto items = history->owner().idsToItems(msgIds);
return int(items.size()) + (comment.empty() ? 0 : 1);
};
}
ShareBox::SubmitCallback ShareBox::DefaultForwardCallback(
std::shared_ptr<Ui::Show> show,
not_null<History*> history,
MessageIdsList msgIds) {
MessageIdsList msgIds,
std::optional<TimeId> videoTimestamp) {
struct State final {
base::flat_set<mtpRequestId> requests;
};
const auto state = std::make_shared<State>();
return [=](
std::vector<not_null<Data::Thread*>> &&result,
TextWithTags &&comment,
Fn<bool()> checkPaid,
TextWithTags comment,
Api::SendOptions options,
Data::ForwardOptions forwardOptions) {
if (!state->requests.empty()) {
return; // Share clicked already.
}
const auto items = history->owner().idsToItems(msgIds);
const auto existingIds = history->owner().itemsToIds(items);
if (existingIds.empty() || result.empty()) {
@@ -1514,6 +1631,8 @@ ShareBox::SubmitCallback ShareBox::DefaultForwardCallback(
if (error.error) {
show->showBox(MakeSendErrorBox(error, result.size() > 1));
return;
} else if (!checkPaid()) {
return;
}
using Flag = MTPmessages_ForwardMessages::Flag;
@@ -1525,6 +1644,9 @@ ShareBox::SubmitCallback ShareBox::DefaultForwardCallback(
: Flag(0))
| ((forwardOptions == Data::ForwardOptions::NoNamesAndCaptions)
? Flag::f_drop_media_captions
: Flag(0))
| (videoTimestamp.has_value()
? Flag::f_video_timestamp
: Flag(0));
auto mtpMsgIds = QVector<MTPint>();
mtpMsgIds.reserve(existingIds.size());
@@ -1559,6 +1681,12 @@ ShareBox::SubmitCallback ShareBox::DefaultForwardCallback(
: topicRootId;
const auto peer = thread->peer();
const auto threadHistory = thread->owningHistory();
const auto starsPaid = std::min(
peer->starsPerMessageChecked(),
options.starsApproved);
if (starsPaid) {
options.starsApproved -= starsPaid;
}
histories.sendRequest(threadHistory, requestType, [=](
Fn<void()> finish) {
const auto session = &threadHistory->session();
@@ -1570,7 +1698,8 @@ ShareBox::SubmitCallback ShareBox::DefaultForwardCallback(
: Flag(0))
| (options.shortcutId
? Flag::f_quick_reply_shortcut
: Flag(0));
: Flag(0))
| (starsPaid ? Flag::f_allow_paid_stars : Flag());
threadHistory->sendRequestId = api.request(
MTPmessages_ForwardMessages(
MTP_flags(sendFlags),
@@ -1581,7 +1710,9 @@ ShareBox::SubmitCallback ShareBox::DefaultForwardCallback(
MTP_int(topMsgId),
MTP_int(options.scheduled),
MTP_inputPeerEmpty(), // send_as
Data::ShortcutIdToMTP(session, options.shortcutId)
Data::ShortcutIdToMTP(session, options.shortcutId),
MTP_int(videoTimestamp.value_or(0)),
MTP_long(starsPaid)
)).done([=](const MTPUpdates &updates, mtpRequestId reqId) {
threadHistory->session().api().applyUpdates(updates);
state->requests.remove(reqId);
@@ -1596,7 +1727,11 @@ ShareBox::SubmitCallback ShareBox::DefaultForwardCallback(
}
finish();
}).fail([=](const MTP::Error &error) {
if (error.type() == u"VOICE_MESSAGES_FORBIDDEN"_q) {
const auto type = error.type();
if (type.startsWith(u"ALLOW_PAYMENT_REQUIRED_"_q)) {
show->showToast(u"Payment requirements changed. "
"Please, try again."_q);
} else if (type == u"VOICE_MESSAGES_FORBIDDEN"_q) {
show->showToast(
tr::lng_restricted_send_voice_messages(
tr::now,
@@ -1612,9 +1747,38 @@ ShareBox::SubmitCallback ShareBox::DefaultForwardCallback(
};
}
ShareBoxStyleOverrides DarkShareBoxStyle() {
using namespace HistoryView;
const auto schedule = [&] {
auto date = Ui::ChooseDateTimeStyleArgs();
date.labelStyle = &st::groupCallBoxLabel;
date.dateFieldStyle = &st::groupCallScheduleDateField;
date.timeFieldStyle = &st::groupCallScheduleTimeField;
date.separatorStyle = &st::callMuteButtonLabel;
date.atStyle = &st::callMuteButtonLabel;
date.calendarStyle = &st::groupCallCalendarColors;
auto st = ScheduleBoxStyleArgs();
st.topButtonStyle = &st::groupCallMenuToggle;
st.popupMenuStyle = &st::groupCallPopupMenu;
st.chooseDateTimeArgs = std::move(date);
return st;
};
return {
.multiSelect = &st::groupCallMultiSelect,
.comment = &st::groupCallShareBoxComment,
.peerList = &st::groupCallShareBoxList,
.label = &st::groupCallField,
.checkbox = &st::groupCallCheckbox,
.scheduleBox = std::make_shared<ScheduleBoxStyleArgs>(schedule()),
};
}
void FastShareMessage(
std::shared_ptr<Main::SessionShow> show,
not_null<HistoryItem*> item) {
not_null<HistoryItem*> item,
ShareBoxStyleOverrides st) {
const auto history = item->history();
const auto owner = &history->owner();
const auto session = &history->session();
@@ -1663,7 +1827,7 @@ void FastShareMessage(
const auto requiresInline = item->requiresSendInlineRight();
auto filterCallback = [=](not_null<Data::Thread*> thread) {
if (const auto user = thread->peer()->asUser()) {
if (user->canSendIgnoreRequirePremium()) {
if (user->canSendIgnoreMoneyRestrictions()) {
return true;
}
}
@@ -1678,43 +1842,54 @@ void FastShareMessage(
show->show(Box<ShareBox>(ShareBox::Descriptor{
.session = session,
.copyCallback = std::move(copyLinkCallback),
.countMessagesCallback = ShareBox::DefaultForwardCountMessages(
history,
msgIds),
.submitCallback = ShareBox::DefaultForwardCallback(
show,
history,
msgIds),
.filterCallback = std::move(filterCallback),
.st = st,
.forwardOptions = {
.sendersCount = ItemsForwardSendersCount(items),
.captionsCount = ItemsForwardCaptionsCount(items),
.show = !hasOnlyForcedForwardedInfo,
},
.premiumRequiredError = SharePremiumRequiredError(),
.moneyRestrictionError = ShareMessageMoneyRestrictionError(),
}), Ui::LayerOption::CloseOther);
}
void FastShareMessage(
not_null<Window::SessionController*> controller,
not_null<HistoryItem*> item) {
FastShareMessage(controller->uiShow(), item);
not_null<HistoryItem*> item,
ShareBoxStyleOverrides st) {
FastShareMessage(controller->uiShow(), item, st);
}
void FastShareLink(
not_null<Window::SessionController*> controller,
const QString &url) {
FastShareLink(controller->uiShow(), url);
const QString &url,
ShareBoxStyleOverrides st) {
FastShareLink(controller->uiShow(), url, st);
}
void FastShareLink(
std::shared_ptr<Main::SessionShow> show,
const QString &url) {
const QString &url,
ShareBoxStyleOverrides st) {
const auto box = std::make_shared<QPointer<Ui::BoxContent>>();
const auto sending = std::make_shared<bool>();
auto copyCallback = [=] {
QGuiApplication::clipboard()->setText(url);
show->showToast(tr::lng_background_link_copied(tr::now));
};
auto countMessagesCallback = [=](const TextWithTags &comment) {
return 1;
};
auto submitCallback = [=](
std::vector<not_null<::Data::Thread*>> &&result,
Fn<bool()> checkPaid,
TextWithTags &&comment,
Api::SendOptions options,
::Data::ForwardOptions) {
@@ -1731,6 +1906,8 @@ void FastShareLink(
MakeSendErrorBox(error, result.size() > 1));
}
return;
} else if (!checkPaid()) {
return;
}
*sending = true;
@@ -1758,7 +1935,7 @@ void FastShareLink(
};
auto filterCallback = [](not_null<::Data::Thread*> thread) {
if (const auto user = thread->peer()->asUser()) {
if (user->canSendIgnoreRequirePremium()) {
if (user->canSendIgnoreMoneyRestrictions()) {
return true;
}
}
@@ -1768,15 +1945,17 @@ void FastShareLink(
Box<ShareBox>(ShareBox::Descriptor{
.session = &show->session(),
.copyCallback = std::move(copyCallback),
.countMessagesCallback = std::move(countMessagesCallback),
.submitCallback = std::move(submitCallback),
.filterCallback = std::move(filterCallback),
.premiumRequiredError = SharePremiumRequiredError(),
.st = st,
.moneyRestrictionError = ShareMessageMoneyRestrictionError(),
}),
Ui::LayerOption::KeepOther,
anim::type::normal);
}
auto SharePremiumRequiredError()
-> Fn<RecipientPremiumRequiredError(not_null<UserData*>)> {
return WritePremiumRequiredError;
auto ShareMessageMoneyRestrictionError()
-> Fn<RecipientMoneyRestrictionError(not_null<UserData*>)> {
return WriteMoneyRestrictionError;
}

View File

@@ -61,58 +61,78 @@ class PopupMenu;
class ShareBox;
void FastShareMessage(
std::shared_ptr<Main::SessionShow> show,
not_null<HistoryItem*> item);
void FastShareMessage(
not_null<Window::SessionController*> controller,
not_null<HistoryItem*> item);
void FastShareLink(
not_null<Window::SessionController*> controller,
const QString &url);
void FastShareLink(
std::shared_ptr<Main::SessionShow> show,
const QString &url);
struct ShareBoxStyleOverrides {
const style::MultiSelect *multiSelect = nullptr;
const style::InputField *comment = nullptr;
const style::PeerList *peerList = nullptr;
const style::InputField *label = nullptr;
const style::Checkbox *checkbox = nullptr;
std::shared_ptr<HistoryView::ScheduleBoxStyleArgs> scheduleBox;
};
[[nodiscard]] ShareBoxStyleOverrides DarkShareBoxStyle();
struct RecipientPremiumRequiredError;
[[nodiscard]] auto SharePremiumRequiredError()
-> Fn<RecipientPremiumRequiredError(not_null<UserData*>)>;
void FastShareMessage(
std::shared_ptr<Main::SessionShow> show,
not_null<HistoryItem*> item,
ShareBoxStyleOverrides st = {});
void FastShareMessage(
not_null<Window::SessionController*> controller,
not_null<HistoryItem*> item,
ShareBoxStyleOverrides st = {});
void FastShareLink(
not_null<Window::SessionController*> controller,
const QString &url,
ShareBoxStyleOverrides st = {});
void FastShareLink(
std::shared_ptr<Main::SessionShow> show,
const QString &url,
ShareBoxStyleOverrides st = {});
struct RecipientMoneyRestrictionError;
[[nodiscard]] auto ShareMessageMoneyRestrictionError()
-> Fn<RecipientMoneyRestrictionError(not_null<UserData*>)>;
class ShareBox final : public Ui::BoxContent {
public:
using CopyCallback = Fn<void()>;
using CountMessagesCallback = Fn<int(const TextWithTags&)>;
using SubmitCallback = Fn<void(
std::vector<not_null<Data::Thread*>>&&,
Fn<bool()> checkPaid,
TextWithTags&&,
Api::SendOptions,
Data::ForwardOptions)>;
using FilterCallback = Fn<bool(not_null<Data::Thread*>)>;
[[nodiscard]] static auto DefaultForwardCountMessages(
not_null<History*> history,
MessageIdsList msgIds) -> CountMessagesCallback;
[[nodiscard]] static SubmitCallback DefaultForwardCallback(
std::shared_ptr<Ui::Show> show,
not_null<History*> history,
MessageIdsList msgIds);
MessageIdsList msgIds,
std::optional<TimeId> videoTimestamp = {});
struct Descriptor {
not_null<Main::Session*> session;
CopyCallback copyCallback;
CountMessagesCallback countMessagesCallback;
SubmitCallback submitCallback;
FilterCallback filterCallback;
object_ptr<Ui::RpWidget> bottomWidget = { nullptr };
rpl::producer<QString> copyLinkText;
const style::MultiSelect *stMultiSelect = nullptr;
const style::InputField *stComment = nullptr;
const style::PeerList *st = nullptr;
const style::InputField *stLabel = nullptr;
rpl::producer<QString> titleOverride;
ShareBoxStyleOverrides st;
std::optional<TimeId> videoTimestamp;
struct {
int sendersCount = 0;
int captionsCount = 0;
bool show = false;
} forwardOptions;
HistoryView::ScheduleBoxStyleArgs scheduleBoxStyle;
using PremiumRequiredError = RecipientPremiumRequiredError;
Fn<PremiumRequiredError(not_null<UserData*>)> premiumRequiredError;
using MoneyRestrictionError = RecipientMoneyRestrictionError;
Fn<MoneyRestrictionError(
not_null<UserData*>)> moneyRestrictionError;
};
ShareBox(QWidget*, Descriptor &&descriptor);
@@ -137,6 +157,7 @@ private:
void needSearchByUsername();
void applyFilterUpdate(const QString &query);
void selectedChanged();
void computeStarsCount();
void createButtons();
int getTopScrollSkip() const;
int getBottomScrollSkip() const;
@@ -168,6 +189,7 @@ private:
bool _hasSelected = false;
rpl::variable<QString> _copyLinkText;
rpl::variable<int> _starsToSend;
base::Timer _searchTimer;
QString _peopleQuery;
@@ -183,5 +205,6 @@ private:
PeopleQueries _peopleQueries;
Ui::Animations::Simple _scrollAnimation;
rpl::lifetime _submitLifetime;
};

File diff suppressed because it is too large Load Diff

View File

@@ -7,6 +7,12 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#pragma once
#include "data/data_star_gift.h"
namespace ChatHelpers {
class Show;
} // namespace ChatHelpers
namespace Data {
struct UniqueGift;
struct GiftCode;
@@ -17,6 +23,11 @@ namespace Payments {
enum class CheckoutResult;
} // namespace Payments
namespace Settings {
struct GiftWearBoxStyleOverride;
struct CreditsEntryBoxStyleOverrides;
} // namespace Settings
namespace Window {
class SessionController;
} // namespace Window
@@ -27,6 +38,7 @@ class CustomEmoji;
namespace Ui {
class PopupMenu;
class GenericBox;
class VerticalLayout;
@@ -41,6 +53,16 @@ void AddUniqueGiftCover(
not_null<VerticalLayout*> container,
rpl::producer<Data::UniqueGift> data,
rpl::producer<QString> subtitleOverride = nullptr);
void AddWearGiftCover(
not_null<VerticalLayout*> container,
const Data::UniqueGift &data,
not_null<PeerData*> peer);
void ShowUniqueGiftWearBox(
std::shared_ptr<ChatHelpers::Show> show,
not_null<PeerData*> peer,
const Data::UniqueGift &gift,
Settings::GiftWearBoxStyleOverride st);
struct PatternPoint {
QPointF position;
@@ -63,8 +85,8 @@ struct StarGiftUpgradeArgs {
not_null<Window::SessionController*> controller;
base::required<uint64> stargiftId;
Fn<void(bool)> ready;
not_null<UserData*> user;
MsgId itemId = 0;
not_null<PeerData*> peer;
Data::SavedStarGiftId savedId;
int cost = 0;
bool canAddSender = false;
bool canAddComment = false;
@@ -73,7 +95,10 @@ struct StarGiftUpgradeArgs {
};
void ShowStarGiftUpgradeBox(StarGiftUpgradeArgs &&args);
void AddUniqueCloseButton(not_null<GenericBox*> box);
void AddUniqueCloseButton(
not_null<GenericBox*> box,
Settings::CreditsEntryBoxStyleOverrides st,
Fn<void(not_null<PopupMenu*>)> fillMenu = nullptr);
void RequestStarsFormAndSubmit(
not_null<Window::SessionController*> window,
@@ -83,6 +108,6 @@ void RequestStarsFormAndSubmit(
void ShowGiftTransferredToast(
base::weak_ptr<Window::SessionController> weak,
not_null<PeerData*> to,
const MTPUpdates &result);
const Data::UniqueGift &gift);
} // namespace Ui

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