Compare commits

...

407 Commits

Author SHA1 Message Date
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
John Preston
48fb0f3b1e Version 5.10.3: Fix edit of album caption. 2025-01-09 22:54:57 +04:00
John Preston
798093c58f Merge remote-tracking branch 'origin/dev' into dev 2025-01-09 22:54:43 +04:00
doliv Debian fisso
e795dad616 Fixed crash sending files in restricted permissions Group Chat 2025-01-09 22:49:40 +04:00
John Preston
c3587ff46f Fix years in affiliate program creation. 2025-01-09 22:38:18 +04:00
John Preston
dc9fa9ccf2 Add boost group item in topic groups. 2025-01-09 22:26:22 +04:00
John Preston
97c259d928 Improve "... boosted group" phrases. 2025-01-09 22:19:34 +04:00
John Preston
afffdd5bbf Don't auto-send /start in blocked bots. 2025-01-09 22:00:49 +04:00
John Preston
199e7a1d46 Fix elision of localized Verification Codes name. 2025-01-09 21:24:14 +04:00
John Preston
a1aa315187 Version 5.10.3.
- Fix a crash in legacy group opening.
2025-01-09 18:54:09 +04:00
John Preston
8c56fddc55 Fix crash in 5.10.2. 2025-01-09 18:53:25 +04:00
John Preston
277d76df3e Version 5.10.2: Update Qt patches on Linux. 2025-01-08 17:42:51 +04:00
John Preston
1ac33d30bd Version 5.10.2: Improve gifts layout. 2025-01-08 17:36:45 +04:00
23rd
658cb438f8 Added spoiler entity to email pattern in recover box. 2025-01-08 14:25:40 +03:00
23rd
2b71625ffe Moved out part of common code for cloud password to td_ui. 2025-01-08 14:25:40 +03:00
23rd
2b13fc9a24 Slightly simplified Intro::Step::setDescriptionText. 2025-01-08 14:25:40 +03:00
23rd
9e18964e7f Added spoiler entity to email pattern in intro and cloud password. 2025-01-08 14:25:40 +03:00
23rd
43dfe559a6 Added simple context for marked text. 2025-01-08 10:58:56 +03:00
23rd
aab7ba264c Added ability to open peers in window with middle button from contacts. 2025-01-07 23:45:23 +03:00
23rd
b7162b5fad Added ability to open recent peers in window with middle button. 2025-01-07 23:45:23 +03:00
23rd
ce4a081155 Added initial ability to handle middle button in peer lists. 2025-01-07 23:45:23 +03:00
23rd
5df2a048e1 Fixed ability to copy selected text with presented compose search. 2025-01-07 23:12:49 +03:00
23rd
1b6a7fafa8 Added ability to instantly delete account in test DC. 2025-01-07 23:12:49 +03:00
23rd
2ab725e5e1 Added patch for colors of strikeout format to Linux. 2025-01-07 23:12:49 +03:00
John Preston
88a310a86e Version 5.10.2: Hide unique gift userpic. 2025-01-07 21:31:06 +04:00
John Preston
86319be256 Version 5.10.2.
- Fix double verification badge in profiles.
2025-01-07 21:23:42 +04:00
John Preston
9d68ef6421 Fix bot verification showing second check. 2025-01-07 20:59:52 +04:00
John Preston
2bb1c5d39b Show verify badge on Verifications Codes. 2025-01-07 20:59:17 +04:00
John Preston
3aa15c979d Version 5.10.1.
- Show "Boost group the send messages" information.
- Fix several gifts bugs and glitches.
- Fix several crashes.
2025-01-06 21:42:23 +04:00
John Preston
c062ba3426 Fix possible crash in sticker click. 2025-01-06 21:42:23 +04:00
John Preston
343560225c Fix crash in QR code copy on scale < 100. 2025-01-06 21:42:23 +04:00
John Preston
e0dd77f0c3 Fix possible crash in forward box. 2025-01-06 21:42:23 +04:00
John Preston
92ff07f723 Fix possible crash in message translation. 2025-01-06 21:42:23 +04:00
John Preston
a23dca080a Always show "View" button in gifts. 2025-01-06 21:42:23 +04:00
John Preston
6844f88567 Improve gift layout. 2025-01-06 21:42:23 +04:00
John Preston
e6060ea277 Improve gift corner badge display. 2025-01-06 21:42:23 +04:00
John Preston
549de7fa54 Show both verify and status emoji in opened chat. 2025-01-06 21:42:23 +04:00
John Preston
ecf9faa21d Fix usernames and QR overlap. 2025-01-06 21:42:23 +04:00
John Preston
a87ebd41e7 Fix reactions for call messages. 2025-01-06 21:42:23 +04:00
John Preston
183dd40f39 Improve gift phrases usage. 2025-01-06 21:42:23 +04:00
John Preston
c722c5c46f Improve gift pattern transparency. 2025-01-06 21:42:23 +04:00
John Preston
865200db5e Improve custom verification icon display. 2025-01-06 21:42:23 +04:00
John Preston
c3e15de759 Don't show Downloads if it's empty in search. 2025-01-06 21:42:23 +04:00
John Preston
44bfdbdc83 Add global search chat type filter. 2025-01-06 21:42:23 +04:00
John Preston
5f10c1875c Ask for boosts to unlock group restrictions. 2025-01-06 21:42:23 +04:00
23rd
a7ae7a8cda Slightly improved fade effect in price categories from star gift box. 2025-01-06 20:40:17 +03:00
23rd
706f142a98 Slightly improved ripple animation for button to gift credits. 2025-01-06 20:40:17 +03:00
23rd
08df3b2dff Removed unused badge in giveaway box. 2025-01-06 20:40:17 +03:00
23rd
14672ff145 Excluded cases of divisions by zero in dates from statistics charts. 2025-01-06 20:40:17 +03:00
Nikolai Nechaev
7fcd84d08e Fix handling of notification disappearing under cursor
Previously, notifications disappearing under cursor (e.g., because closed
manually or open from another device) did not notify the manager
properly, as the leaveEventHook was not triggered. This could lead to
notifications staying around when not supposed to (see #28813).

This commit fixes that by explicitly notifying manager when the
notification widget disappears under the cursor.

Fixes #28813.
2025-01-06 09:51:09 +04:00
dependabot[bot]
12f8686326 Bump jinja2 from 3.1.4 to 3.1.5 in /Telegram/build/docker/centos_env
Bumps [jinja2](https://github.com/pallets/jinja) from 3.1.4 to 3.1.5.
- [Release notes](https://github.com/pallets/jinja/releases)
- [Changelog](https://github.com/pallets/jinja/blob/main/CHANGES.rst)
- [Commits](https://github.com/pallets/jinja/compare/3.1.4...3.1.5)

---
updated-dependencies:
- dependency-name: jinja2
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-01-06 09:48:00 +04:00
John Preston
aa445adfff Fix possible crash in ListWidget destructor. 2025-01-04 22:00:38 +04:00
John Preston
603aa5db5f Version 5.10: Fix build with GCC. 2025-01-02 12:23:07 +04:00
John Preston
c34289036f Version 5.10: Show folder tags premium promo. 2025-01-02 11:25:08 +04:00
John Preston
5b6bec775b Version 5.10: Fix build with Xcode. 2025-01-02 11:02:33 +04:00
John Preston
3b0fe3043f Version 5.10.
- Collectible Gifts.
- Reactions for Service Messages.
- Verification from Third Parties.
- Custom Emoji in Folder Names.
2025-01-02 10:54:50 +04:00
GitHub Action
c99165891f Update copyright year to 2025. 2025-01-02 10:48:21 +04:00
John Preston
4938b18f9d Fix display of gifts from bots. 2025-01-02 10:47:37 +04:00
John Preston
8895b4e8a3 Implement buying gifts for myself. 2024-12-31 21:40:18 +04:00
John Preston
a7321c9beb Return native verify icon to the right. 2024-12-31 17:18:59 +04:00
John Preston
c23b533704 Hide shared media layer when jumping to message. 2024-12-31 16:44:22 +04:00
John Preston
de34c75788 Support custom emoji in folder menus. 2024-12-31 16:29:10 +04:00
John Preston
06341efe0d Allow disabling animations in folder emoji. 2024-12-31 15:41:13 +04:00
John Preston
c810005f86 Don't parse empty messages in channels. 2024-12-31 13:44:52 +04:00
John Preston
cdedf283ac Show correct topic buttons in admin log. 2024-12-31 13:12:16 +04:00
John Preston
acfd92e2e6 Display emoji correctly in folder tags. 2024-12-31 13:12:16 +04:00
John Preston
51b81dba87 Fix animated side buttons with locks. 2024-12-31 13:12:16 +04:00
John Preston
7f6dfcf52f Improve new gift transactions a bit. 2024-12-31 13:12:16 +04:00
John Preston
92582d8434 Implement refunded upgraded gift view. 2024-12-31 13:12:16 +04:00
John Preston
e2bff474db Show upgraded gift from old "View" button. 2024-12-31 13:12:15 +04:00
John Preston
4f702e12b7 Improve upgrade/transfer toasts. 2024-12-31 13:12:15 +04:00
John Preston
083400d1c2 Implement unique gift transfer. 2024-12-31 13:12:15 +04:00
John Preston
7491337bfd Show forward original date for edited items. 2024-12-31 13:12:15 +04:00
John Preston
d6b833fbb2 Add icons for gift upgrading. 2024-12-31 13:12:15 +04:00
John Preston
2113a2b634 Implement nice unique gifts in the list. 2024-12-31 13:12:15 +04:00
John Preston
5df632264f Allow pay for upgrade when sending. 2024-12-31 13:12:15 +04:00
John Preston
42c350243a Implement unique gift view box. 2024-12-31 13:12:15 +04:00
John Preston
522ca3b04a Pause gift view ministars in an inactive window. 2024-12-31 13:12:15 +04:00
John Preston
2d53ec5d34 Implement unique gift view in chat. 2024-12-31 13:12:15 +04:00
John Preston
13d2f70c3a Implement upgraded unique gifts. 2024-12-31 13:12:15 +04:00
John Preston
a87d19998e Support bot verifications without modify access. 2024-12-31 13:12:15 +04:00
John Preston
6ddf241293 Update API scheme on layer 196. 2024-12-31 13:12:15 +04:00
John Preston
e43ec6c4ea Add unique gift phrases. 2024-12-31 13:12:15 +04:00
John Preston
5f3db95cbd Parse unique gift fields. 2024-12-31 13:12:15 +04:00
John Preston
d874829b06 Start animating emoji in filter titles. 2024-12-31 13:12:15 +04:00
John Preston
6cfbccd955 Handle report_delivery_until_date. 2024-12-25 11:09:35 +04:00
John Preston
0d821c3630 Implement simple bot verification management. 2024-12-25 11:09:35 +04:00
John Preston
b61e3b580d Return chat type icons. 2024-12-25 11:09:35 +04:00
John Preston
5c301353ec Improve verified badge display. 2024-12-25 11:09:35 +04:00
John Preston
0363421862 Apply server side bot verifications. 2024-12-25 11:09:35 +04:00
John Preston
6f18b9b691 Proof-of-concept custom verify badges. 2024-12-25 11:09:35 +04:00
John Preston
35e40be550 Support service messages reactions. 2024-12-25 11:09:35 +04:00
John Preston
c1528f532e Update API scheme to layer 196. 2024-12-25 11:09:35 +04:00
587 changed files with 25194 additions and 7313 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

2
LEGAL
View File

@@ -1,7 +1,7 @@
This file is part of Telegram Desktop,
the official desktop application for the Telegram messaging service.
Copyright (c) 2014-2024 The Telegram Desktop Authors.
Copyright (c) 2014-2025 The Telegram Desktop Authors.
Telegram Desktop is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by

View File

@@ -246,6 +246,8 @@ PRIVATE
boxes/peers/prepare_short_info_box.h
boxes/peers/replace_boost_box.cpp
boxes/peers/replace_boost_box.h
boxes/peers/verify_peers_box.cpp
boxes/peers/verify_peers_box.h
boxes/about_box.cpp
boxes/about_box.h
boxes/about_sponsored_box.cpp
@@ -330,6 +332,8 @@ PRIVATE
boxes/sticker_set_box.h
boxes/stickers_box.cpp
boxes/stickers_box.h
boxes/transfer_gift_box.cpp
boxes/transfer_gift_box.h
boxes/translate_box.cpp
boxes/translate_box.h
boxes/url_auth_box.cpp
@@ -626,6 +630,7 @@ PRIVATE
data/data_shared_media.h
data/data_sparse_ids.cpp
data/data_sparse_ids.h
data/data_star_gift.h
data/data_statistics.h
data/data_stories.cpp
data/data_stories.h
@@ -796,6 +801,8 @@ PRIVATE
history/view/media/history_view_story_mention.h
history/view/media/history_view_theme_document.cpp
history/view/media/history_view_theme_document.h
history/view/media/history_view_unique_gift.cpp
history/view/media/history_view_unique_gift.h
history/view/media/history_view_userpic_suggestion.cpp
history/view/media/history_view_userpic_suggestion.h
history/view/media/history_view_web_page.cpp
@@ -952,6 +959,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
@@ -1007,8 +1015,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
@@ -1128,6 +1136,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
@@ -1196,6 +1206,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
@@ -1214,7 +1226,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
@@ -1402,8 +1413,6 @@ PRIVATE
settings/business/settings_recipients_helper.h
settings/business/settings_working_hours.cpp
settings/business/settings_working_hours.h
settings/cloud_password/settings_cloud_password_common.cpp
settings/cloud_password/settings_cloud_password_common.h
settings/cloud_password/settings_cloud_password_email.cpp
settings/cloud_password/settings_cloud_password_email.h
settings/cloud_password/settings_cloud_password_email_confirm.cpp
@@ -1416,6 +1425,8 @@ PRIVATE
settings/cloud_password/settings_cloud_password_manage.h
settings/cloud_password/settings_cloud_password_start.cpp
settings/cloud_password/settings_cloud_password_start.h
settings/cloud_password/settings_cloud_password_step.cpp
settings/cloud_password/settings_cloud_password_step.h
settings/settings_active_sessions.cpp
settings/settings_active_sessions.h
settings/settings_advanced.cpp
@@ -1464,6 +1475,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

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 500 B

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: 811 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 868 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

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

@@ -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,46 @@ 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_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 +1218,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 +1357,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 +1397,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";
@@ -1628,11 +1681,42 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_manage_peer_bot_star_ref" = "Affiliate Program";
"lng_manage_peer_bot_star_ref_off" = "Off";
"lng_manage_peer_bot_star_ref_about" = "Share a link to {bot} with your friends and earn {amount} of their spending there.";
"lng_manage_peer_bot_verify" = "Verify Accounts";
"lng_manage_peer_bot_edit_intro" = "Edit Intro";
"lng_manage_peer_bot_edit_commands" = "Edit Commands";
"lng_manage_peer_bot_edit_settings" = "Change Bot Settings";
"lng_manage_peer_bot_about" = "Use {bot} to manage this bot.";
"lng_bot_verify_title" = "Choose Chat to Verify";
"lng_bot_verify_bot_title" = "Verify Bot";
"lng_bot_verify_bot_text" = "Do you want to verify {name} with your verification mark and description?";
"lng_bot_verify_bot_about" = "You can customize your description for each bot.";
"lng_bot_verify_bot_submit" = "Verify Bot";
"lng_bot_verify_bot_sent" = "{name} has been notified and will receive your verification mark and description upon accepting.";
"lng_bot_verify_bot_remove" = "This bot is already verified by you. Do you want to remove verification?";
"lng_bot_verify_user_title" = "Verify User";
"lng_bot_verify_user_text" = "Do you want to verify {name} with your verification mark and description?";
"lng_bot_verify_user_about" = "You can customize your description for each account.";
"lng_bot_verify_user_submit" = "Verify User";
"lng_bot_verify_user_sent" = "{name} has been notified and will receive your verification mark and description upon accepting.";
"lng_bot_verify_user_remove" = "This account is already verified by you. Do you want to remove verification?";
"lng_bot_verify_channel_title" = "Verify Channel";
"lng_bot_verify_channel_text" = "Do you want to verify {name} with your verification mark and description?";
"lng_bot_verify_channel_about" = "You can customize your description for each channel.";
"lng_bot_verify_channel_submit" = "Verify Channel";
"lng_bot_verify_channel_sent" = "{name} has been notified and will receive your verification mark and description upon accepting.";
"lng_bot_verify_channel_remove" = "This channel is already verified by you. Do you want to remove verification?";
"lng_bot_verify_group_title" = "Verify Group";
"lng_bot_verify_group_text" = "Do you want to verify {name} with your verification mark and description?";
"lng_bot_verify_group_about" = "You can customize your description for each group.";
"lng_bot_verify_group_submit" = "Verify Group";
"lng_bot_verify_group_sent" = "{name} has been notified and will receive your verification mark and description upon accepting.";
"lng_bot_verify_group_remove" = "This group is already verified by you. Do you want to remove verification?";
"lng_bot_verify_description_label" = "Description";
"lng_bot_verify_remove_title" = "Remove verification";
"lng_bot_verify_remove_submit" = "Remove";
"lng_bot_verify_remove_done" = "You've removed this verification.";
"lng_star_ref_title" = "Affiliate Program";
"lng_star_ref_about" = "Reward those who help grow your user base.";
"lng_star_ref_share_title" = "Share revenue with affiliates";
@@ -1985,21 +2069,50 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_action_proximity_distance_km#other" = "{count} km";
"lng_action_webview_data_done" = "Data from the \"{text}\" button was transferred to the bot.";
"lng_action_gift_received" = "{user} sent you a gift for {cost}";
"lng_action_gift_unique_received" = "{user} sent you a unique collectible item";
"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}";
"lng_action_gift_got_stars_text#one" = "Display this gift on your page or convert it to **{count}** Star.";
"lng_action_gift_got_stars_text#other" = "Display this gift on your page or convert it to **{count}** Stars.";
"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.";
"lng_action_gift_sent_upgradable" = "{user} can upgrade this gift to a unique collectible.";
"lng_action_gift_premium_months#one" = "{count} Month Premium";
"lng_action_gift_premium_months#other" = "{count} Months Premium";
"lng_action_gift_premium_about" = "Subscription for exclusive Telegram features.";
"lng_action_gift_refunded" = "This gift was downgraded because a request to refund the payment related to this gift was made, and the money was returned.";
"lng_action_suggested_photo_me" = "You suggested this photo for {user}'s Telegram profile.";
"lng_action_suggested_photo" = "{user} suggests this photo for your Telegram profile.";
"lng_action_suggested_photo_button" = "View Photo";
@@ -2048,10 +2161,20 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_action_giveaway_results_credits#other" = "{count} winners of the giveaway were randomly selected by Telegram and received their prize.";
"lng_action_giveaway_results_credits_some" = "Some winners of the giveaway were randomly selected by Telegram and received their prize.";
"lng_action_giveaway_results_none" = "No winners of the giveaway could be selected.";
"lng_action_boost_apply_me" = "You boosted the group";
"lng_action_boost_apply#one" = "{from} boosted the group";
"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";
@@ -2061,9 +2184,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";
@@ -2326,6 +2463,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";
@@ -2403,6 +2541,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_premium_summary_button" = "Subscribe for {cost} per month";
"lng_premium_summary_new_badge" = "NEW";
"lng_soon_badge" = "Soon";
"lng_premium_success" = "You've successfully subscribed to Telegram Premium!";
"lng_premium_unavailable" = "This feature requires subscription to **Telegram Premium**.\n\nUnfortunately, **Telegram Premium** is not available in your region.";
@@ -2469,6 +2608,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";
@@ -2538,6 +2681,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";
@@ -2576,6 +2725,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_credits_summary_in_toast_about#other" = "**{count}** Stars added to your balance.";
"lng_credits_box_history_entry_peer" = "Recipient";
"lng_credits_box_history_entry_peer_in" = "From";
"lng_credits_box_history_entry_gift_from" = "Gift From";
"lng_credits_box_history_entry_via" = "Via";
"lng_credits_box_history_entry_play_market" = "Play Store";
"lng_credits_box_history_entry_app_store" = "App Store";
@@ -2585,6 +2735,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_credits_box_history_entry_giveaway_name" = "Received Prize";
"lng_credits_box_history_entry_gift_sent" = "Sent Gift";
"lng_credits_box_history_entry_gift_converted" = "Converted Gift";
"lng_credits_box_history_entry_gift_transfer" = "Gift Transfer";
"lng_credits_box_history_entry_gift_unavailable" = "Unavailable";
"lng_credits_box_history_entry_gift_sold_out" = "This gift has sold out";
"lng_credits_box_history_entry_gift_out_about" = "With Stars, **{user}** will be able to unlock content and services on Telegram.\n{link}";
@@ -2612,6 +2763,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_credits_box_history_entry_about_link" = "here";
"lng_credits_box_history_entry_reaction_name" = "Star Reaction";
"lng_credits_box_history_entry_subscription" = "Monthly subscription fee";
"lng_credits_box_history_entry_gift_upgrade" = "Collectible Upgrade";
"lng_credits_subscription_section" = "My subscriptions";
"lng_credits_box_subscription_title" = "Subscription";
@@ -2648,6 +2800,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}";
@@ -2909,11 +3063,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";
@@ -3175,6 +3335,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 >";
@@ -3186,24 +3347,69 @@ 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!";
"lng_gift_sent_about#one" = "You spent **{count}** Star from your balance.";
"lng_gift_sent_about#other" = "You spent **{count}** Stars from your balance.";
"lng_gift_limited_of_one" = "unique";
"lng_gift_limited_of_count" = "1 of {amount}";
"lng_gift_collectible_tag" = "gift";
"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_address_copied" = "Address copied to clipboard.";
"lng_gift_unique_status" = "Status";
"lng_gift_unique_status_non" = "Non-Unique";
"lng_gift_unique_status_upgrade" = "upgrade";
"lng_gift_unique_number" = "Collectible #{index}";
"lng_gift_unique_model" = "Model";
"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}.";
"lng_gift_unique_info_sender" = "Gifted by {from} to {recipient} on {date}.";
"lng_gift_unique_info_sender_comment" = "Gifted by {from} to {recipient} on {date} with the comment \"{text}\".";
"lng_gift_unique_info_reciever" = "Gifted to {recipient} on {date}.";
"lng_gift_unique_info_reciever_comment" = "Gifted to {recipient} on {date} with the comment \"{text}\".";
"lng_gift_availability_left#one" = "{count} of {amount} left";
"lng_gift_availability_left#other" = "{count} of {amount} left";
"lng_gift_availability_none" = "None of {amount} left";
@@ -3212,20 +3418,89 @@ 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.";
"lng_gift_send_small" = "send a gift";
"lng_gift_sell_small#one" = "sell for {count} Star";
"lng_gift_sell_small#other" = "sell for {count} Stars";
"lng_gift_upgrade_title" = "Upgrade Gift";
"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";
"lng_gift_upgrade_transferable_about" = "Send your upgraded gift to any of your friends on Telegram.";
"lng_gift_upgrade_tradable_title" = "Tradable";
"lng_gift_upgrade_tradable_about" = "Sell or auction your gift on third-party NFT marketplaces.";
"lng_gift_upgrade_button" = "Upgrade for {price}";
"lng_gift_upgrade_free" = "Upgrade for Free";
"lng_gift_upgrade_confirm" = "Confirm";
"lng_gift_upgrade_add_my" = "Add my name to the gift";
"lng_gift_upgrade_add_my_comment" = "Add my name and comment";
"lng_gift_upgrade_add_sender" = "Add sender's name to the gift";
"lng_gift_upgrade_add_comment" = "Add sender's name and comment";
"lng_gift_upgraded_title" = "Gift Upgraded";
"lng_gift_upgraded_about" = "Your gift {name} now has unique attributes and can be transferred to others";
"lng_gift_transferred_title" = "Gift Transferred";
"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";
"lng_gift_transfer_unlocks_hours#other" = "unlocks in {count} hours";
"lng_gift_transfer_unlocks_title" = "Unlocking in progress";
"lng_gift_transfer_unlocks_about" = "{when}, you'll be able to send this collectible to any TON blockchain address outside Telegram for sale or auction.";
"lng_gift_transfer_unlocks_when_days#one" = "In {count} day";
"lng_gift_transfer_unlocks_when_days#other" = "In {count} days";
"lng_gift_transfer_unlocks_when_hours#one" = "In {count} hour";
"lng_gift_transfer_unlocks_when_hours#other" = "In {count} hours";
"lng_gift_transfer_unlocks_update_title" = "Update required";
"lng_gift_transfer_unlocks_update_about" = "Please update your Telegram application to the latest version.";
"lng_gift_transfer_sure" = "Do you want to transfer ownership of {name} to {recipient}?";
"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.";
@@ -3367,6 +3642,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}.";
@@ -3395,6 +3686,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}";
@@ -3641,6 +3933,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";
@@ -3757,6 +4051,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}";
@@ -3785,6 +4080,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";
@@ -3914,6 +4211,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.";
@@ -4029,11 +4327,17 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_search_messages_from" = "Show messages from";
"lng_search_messages_n_of_amount" = "{n} of {amount}";
"lng_search_messages_none" = "No results";
"lng_search_filter_all" = "All chats";
"lng_search_filter_private" = "Private chats";
"lng_search_filter_group" = "Group chats";
"lng_search_filter_channel" = "Channels";
"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";
@@ -4544,6 +4848,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.";
@@ -4551,6 +4859,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.";
@@ -4682,6 +5012,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";
@@ -5237,6 +5570,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_filters_edit" = "Edit Folder";
"lng_filters_setup_menu" = "Edit Folders";
"lng_filters_new_name" = "Folder name";
"lng_filters_enable_animations" = "Enable animations";
"lng_filters_disable_animations" = "Disable animations";
"lng_filters_add_chats" = "Add Chats";
"lng_filters_remove_chats" = "Add Chats to Exclude";
"lng_filters_include" = "Included chats";
@@ -5396,6 +5731,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?";
@@ -5712,6 +6048,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";
@@ -5771,6 +6108,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";
@@ -5879,6 +6217,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_search_tab_no_results_text" = "There were no results for \"{query}\".";
"lng_search_tab_no_results_retry" = "Try another hashtag.";
"lng_search_tab_by_hashtag" = "Enter a hashtag to find messages containing it.";
"lng_search_tab_try_in_all" = "Search in All Messages";
"lng_contact_details_button" = "View Contact";
"lng_contact_details_title" = "Contact details";

View File

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

View File

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

View File

@@ -35,8 +35,8 @@ LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
//
VS_VERSION_INFO VERSIONINFO
FILEVERSION 5,9,2,0
PRODUCTVERSION 5,9,2,0
FILEVERSION 5,12,2,0
PRODUCTVERSION 5,12,2,0
FILEFLAGSMASK 0x3fL
#ifdef _DEBUG
FILEFLAGS 0x1L
@@ -53,10 +53,10 @@ BEGIN
BEGIN
VALUE "CompanyName", "Telegram FZ-LLC"
VALUE "FileDescription", "Telegram Desktop Updater"
VALUE "FileVersion", "5.9.2.0"
VALUE "LegalCopyright", "Copyright (C) 2014-2024"
VALUE "FileVersion", "5.12.2.0"
VALUE "LegalCopyright", "Copyright (C) 2014-2025"
VALUE "ProductName", "Telegram Desktop"
VALUE "ProductVersion", "5.9.2.0"
VALUE "ProductVersion", "5.12.2.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

@@ -7,6 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#include "api/api_chat_filters.h"
#include "api/api_text_entities.h"
#include "apiwrap.h"
#include "base/event_filter.h"
#include "boxes/peer_list_box.h"
@@ -14,6 +15,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "boxes/filters/edit_filter_links.h" // FilterChatStatusText
#include "core/application.h"
#include "core/core_settings.h"
#include "core/ui_integration.h"
#include "data/data_channel.h"
#include "data/data_chat.h"
#include "data/data_chat_filters.h"
@@ -25,6 +27,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/boxes/confirm_box.h"
#include "ui/controls/filter_link_header.h"
#include "ui/text/text_utilities.h"
#include "ui/toast/toast.h"
#include "ui/widgets/buttons.h"
#include "ui/filter_icons.h"
#include "ui/vertical_list.h"
@@ -49,7 +52,7 @@ public:
ToggleChatsController(
not_null<Window::SessionController*> window,
ToggleAction action,
const QString &title,
Data::ChatFilterTitle title,
std::vector<not_null<PeerData*>> chats,
std::vector<not_null<PeerData*>> additional);
@@ -75,7 +78,6 @@ private:
Ui::RpWidget *_addedBottomWidget = nullptr;
ToggleAction _action = ToggleAction::Adding;
QString _filterTitle;
base::flat_set<not_null<PeerData*>> _checkable;
std::vector<not_null<PeerData*>> _chats;
std::vector<not_null<PeerData*>> _additional;
@@ -106,9 +108,9 @@ private:
[[nodiscard]] TextWithEntities AboutText(
Ui::FilterLinkHeaderType type,
const QString &title) {
TextWithEntities title) {
using Type = Ui::FilterLinkHeaderType;
auto boldTitle = Ui::Text::Bold(title);
auto boldTitle = Ui::Text::Wrapped(title, EntityType::Bold);
return (type == Type::AddingFilter)
? tr::lng_filters_by_link_sure(
tr::now,
@@ -138,19 +140,24 @@ void InitFilterLinkHeader(
not_null<PeerListBox*> box,
Fn<void(int minHeight, int maxHeight, int addedTopHeight)> adjust,
Ui::FilterLinkHeaderType type,
const QString &title,
const QString &iconEmoji,
Data::ChatFilterTitle title,
QString iconEmoji,
rpl::producer<int> count,
bool horizontalFilters) {
const auto icon = Ui::LookupFilterIcon(
Ui::LookupFilterIconByEmoji(
iconEmoji
).value_or(Ui::FilterIcon::Custom)).active;
const auto isStatic = title.isStatic;
auto header = Ui::MakeFilterLinkHeader(box, {
.type = type,
.title = TitleText(type)(tr::now),
.about = AboutText(type, title),
.folderTitle = title,
.about = AboutText(type, title.text),
.aboutContext = Core::TextContext({
.session = &box->peerListUiShow()->session(),
.customEmojiLoopLimit = isStatic ? -1 : 0,
}),
.folderTitle = title.text,
.folderIcon = icon,
.badge = (type == Ui::FilterLinkHeaderType::AddingChats
? std::move(count)
@@ -248,12 +255,11 @@ void ImportInvite(
ToggleChatsController::ToggleChatsController(
not_null<Window::SessionController*> window,
ToggleAction action,
const QString &title,
Data::ChatFilterTitle title,
std::vector<not_null<PeerData*>> chats,
std::vector<not_null<PeerData*>> additional)
: _window(window)
, _action(action)
, _filterTitle(title)
, _chats(std::move(chats))
, _additional(std::move(additional)) {
setStyleOverrides(&st::filterLinkChatsList);
@@ -529,7 +535,7 @@ void ShowImportError(
void ShowImportToast(
base::weak_ptr<Window::SessionController> weak,
const QString &title,
Data::ChatFilterTitle title,
Ui::FilterLinkHeaderType type,
int added) {
const auto strong = weak.get();
@@ -540,14 +546,23 @@ void ShowImportToast(
const auto phrase = created
? tr::lng_filters_added_title
: tr::lng_filters_updated_title;
auto text = Ui::Text::Bold(phrase(tr::now, lt_folder, title));
auto text = Ui::Text::Wrapped(
phrase(tr::now, lt_folder, title.text, Ui::Text::WithEntities),
EntityType::Bold);
if (added > 0) {
const auto phrase = created
? tr::lng_filters_added_also
: tr::lng_filters_updated_also;
text.append('\n').append(phrase(tr::now, lt_count, added));
}
strong->showToast(std::move(text));
const auto isStatic = title.isStatic;
strong->showToast({
.text = std::move(text),
.textContext = Core::TextContext({
.session = &strong->session(),
.customEmojiLoopLimit = isStatic ? -1 : 0,
})
});
}
void HandleEnterInBox(not_null<Ui::BoxContent*> box) {
@@ -574,8 +589,8 @@ void ProcessFilterInvite(
base::weak_ptr<Window::SessionController> weak,
const QString &slug,
FilterId filterId,
const QString &title,
const QString &iconEmoji,
Data::ChatFilterTitle title,
QString iconEmoji,
std::vector<not_null<PeerData*>> peers,
std::vector<not_null<PeerData*>> already) {
const auto strong = weak.get();
@@ -616,10 +631,15 @@ void ProcessFilterInvite(
raw->setRealContentHeight(box->heightValue());
const auto isStatic = title.isStatic;
auto owned = Ui::FilterLinkProcessButton(
box,
type,
title,
title.text,
Core::TextContext({
.session = &strong->session(),
.customEmojiLoopLimit = isStatic ? -1 : 0,
}),
std::move(badge));
const auto button = owned.data();
@@ -720,7 +740,7 @@ void CheckFilterInvite(
if (!strong) {
return;
}
auto title = QString();
auto title = Data::ChatFilterTitle();
auto iconEmoji = QString();
auto filterId = FilterId();
auto peers = std::vector<not_null<PeerData*>>();
@@ -739,7 +759,8 @@ void CheckFilterInvite(
return result;
};
result.match([&](const MTPDchatlists_chatlistInvite &data) {
title = qs(data.vtitle());
title.text = ParseTextWithEntities(session, data.vtitle());
title.isStatic = data.is_title_noanimate();
iconEmoji = data.vemoticon().value_or_empty();
peers = parseList(data.vpeers());
}, [&](const MTPDchatlists_chatlistInviteAlready &data) {
@@ -804,8 +825,8 @@ void ProcessFilterUpdate(
void ProcessFilterRemove(
base::weak_ptr<Window::SessionController> weak,
const QString &title,
const QString &iconEmoji,
Data::ChatFilterTitle title,
QString iconEmoji,
std::vector<not_null<PeerData*>> all,
std::vector<not_null<PeerData*>> suggest,
Fn<void(std::vector<not_null<PeerData*>>)> done) {
@@ -839,10 +860,15 @@ void ProcessFilterRemove(
raw->adjust(min, max, addedTop);
}, type, title, iconEmoji, rpl::single(0), horizontalFilters);
const auto isStatic = title.isStatic;
auto owned = Ui::FilterLinkProcessButton(
box,
type,
title,
title.text,
Core::TextContext({
.session = &strong->session(),
.customEmojiLoopLimit = isStatic ? -1 : 0,
}),
std::move(badge));
const auto button = owned.data();

View File

@@ -17,6 +17,7 @@ class SessionController;
namespace Data {
class ChatFilter;
struct ChatFilterTitle;
} // namespace Data
namespace Api {
@@ -36,8 +37,8 @@ void ProcessFilterUpdate(
void ProcessFilterRemove(
base::weak_ptr<Window::SessionController> weak,
const QString &title,
const QString &iconEmoji,
Data::ChatFilterTitle title,
QString iconEmoji,
std::vector<not_null<PeerData*>> all,
std::vector<not_null<PeerData*>> suggest,
Fn<void(std::vector<not_null<PeerData*>>)> done);

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

@@ -7,6 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#include "api/api_credits.h"
#include "api/api_premium.h"
#include "api/api_statistics_data_deserialize.h"
#include "api/api_updates.h"
#include "apiwrap.h"
@@ -73,6 +74,11 @@ constexpr auto kTransactionsLimit = 100;
return PeerId(0);
}).value;
const auto stargift = tl.data().vstargift();
const auto nonUniqueGift = stargift
? stargift->match([&](const MTPDstarGift &data) {
return &data;
}, [](const auto &) { return (const MTPDstarGift*)nullptr; })
: nullptr;
const auto reaction = tl.data().is_reaction();
const auto amount = Data::FromTL(tl.data().vstars());
const auto starrefAmount = tl.data().vstarref_amount()
@@ -84,7 +90,17 @@ 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>();
const auto giftStickerId = parsedGift ? parsedGift->document->id : 0;
return Data::CreditsHistoryEntry{
.id = qs(tl.data().vid()),
.title = qs(tl.data().vtitle().value_or_empty()),
@@ -97,13 +113,12 @@ constexpr auto kTransactionsLimit = 100;
.barePeerId = saveActorId ? peer->id.value : barePeerId,
.bareGiveawayMsgId = uint64(
tl.data().vgiveaway_post_id().value_or_empty()),
.bareGiftStickerId = (stargift
? owner->processDocument(stargift->data().vsticker())->id
: 0),
.bareGiftStickerId = giftStickerId,
.bareActorId = saveActorId ? barePeerId : uint64(0),
.starrefAmount = starrefAmount,
.starrefCommission = starrefCommission,
.starrefRecipientId = starrefBarePeerId,
.uniqueGift = parsedGift ? parsedGift->unique : nullptr,
.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 &) {
@@ -129,12 +144,19 @@ constexpr auto kTransactionsLimit = 100;
? base::unixtime::parse(tl.data().vtransaction_date()->v)
: QDateTime(),
.successLink = qs(tl.data().vtransaction_url().value_or_empty()),
.starsConverted = int(stargift
? stargift->data().vconvert_stars().v
.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(),
.giftUpgraded = tl.data().is_stargift_upgrade(),
.reaction = tl.data().is_reaction(),
.refunded = tl.data().is_refund(),
.pending = tl.data().is_pending(),
@@ -503,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);
@@ -601,7 +622,7 @@ auto PremiumGiftCodeOptions::requestStarGifts()
_giftsHash = data.vhash().v;
const auto &list = data.vgifts().v;
const auto session = &_peer->session();
auto gifts = std::vector<StarGift>();
auto gifts = std::vector<Data::StarGift>();
gifts.reserve(list.size());
for (const auto &gift : list) {
if (auto parsed = FromTL(session, gift)) {
@@ -620,7 +641,8 @@ auto PremiumGiftCodeOptions::requestStarGifts()
};
}
const std::vector<StarGift> &PremiumGiftCodeOptions::starGifts() const {
auto PremiumGiftCodeOptions::starGifts() const
-> const std::vector<Data::StarGift> & {
return _gifts;
}
@@ -693,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()));
@@ -726,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(
@@ -758,41 +793,100 @@ rpl::producer<DocumentData*> RandomHelloStickerValue(
}) | rpl::take(1) | rpl::map(random));
}
std::optional<StarGift> FromTL(
std::optional<Data::StarGift> FromTL(
not_null<Main::Session*> session,
const MTPstarGift &gift) {
const auto &data = gift.data();
const auto document = session->data().processDocument(
data.vsticker());
const auto remaining = data.vavailability_remains();
const auto total = data.vavailability_total();
if (!document->sticker()) {
return {};
}
return StarGift{
.id = uint64(data.vid().v),
.stars = int64(data.vstars().v),
.starsConverted = int64(data.vconvert_stars().v),
.document = document,
.limitedLeft = remaining.value_or_empty(),
.limitedCount = total.value_or_empty(),
.firstSaleDate = data.vfirst_sale_date().value_or_empty(),
.lastSaleDate = data.vlast_sale_date().value_or_empty(),
.birthday = data.is_birthday(),
};
return gift.match([&](const MTPDstarGift &data) {
const auto document = session->data().processDocument(
data.vsticker());
const auto remaining = data.vavailability_remains();
const auto total = data.vavailability_total();
if (!document->sticker()) {
return std::optional<Data::StarGift>();
}
return std::optional<Data::StarGift>(Data::StarGift{
.id = uint64(data.vid().v),
.stars = int64(data.vstars().v),
.starsConverted = int64(data.vconvert_stars().v),
.starsToUpgrade = int64(data.vupgrade_stars().value_or_empty()),
.document = document,
.limitedLeft = remaining.value_or_empty(),
.limitedCount = total.value_or_empty(),
.firstSaleDate = data.vfirst_sale_date().value_or_empty(),
.lastSaleDate = data.vlast_sale_date().value_or_empty(),
.upgradable = data.vupgrade_stars().has_value(),
.birthday = data.is_birthday(),
});
}, [&](const MTPDstarGiftUnique &data) {
const auto total = data.vavailability_total().v;
auto model = std::optional<Data::UniqueGiftModel>();
auto pattern = std::optional<Data::UniqueGiftPattern>();
for (const auto &attribute : data.vattributes().v) {
attribute.match([&](const MTPDstarGiftAttributeModel &data) {
model = FromTL(session, data);
}, [&](const MTPDstarGiftAttributePattern &data) {
pattern = FromTL(session, data);
}, [&](const MTPDstarGiftAttributeBackdrop &data) {
}, [&](const MTPDstarGiftAttributeOriginalDetails &data) {
});
}
if (!model
|| !model->document->sticker()
|| !pattern
|| !pattern->document->sticker()) {
return std::optional<Data::StarGift>();
}
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()),
.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,
}),
.document = model->document,
.limitedLeft = (total - data.vavailability_issued().v),
.limitedCount = total,
};
const auto unique = result.unique.get();
for (const auto &attribute : data.vattributes().v) {
attribute.match([&](const MTPDstarGiftAttributeModel &data) {
}, [&](const MTPDstarGiftAttributePattern &data) {
}, [&](const MTPDstarGiftAttributeBackdrop &data) {
unique->backdrop = FromTL(data);
}, [&](const MTPDstarGiftAttributeOriginalDetails &data) {
unique->originalDetails = FromTL(session, data);
});
}
return std::make_optional(result);
});
}
std::optional<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());
if (!parsed) {
return {};
} else if (const auto unique = parsed->unique.get()) {
unique->starsForTransfer = data.vtransfer_stars().value_or(-1);
unique->exportAt = data.vcan_export_at().value_or_empty();
}
return 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()),
@@ -802,15 +896,71 @@ std::optional<UserStarGift> FromTL(
}
: TextWithEntities()),
.starsConverted = int64(data.vconvert_stars().value_or_empty()),
.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(),
};
}
Data::UniqueGiftModel FromTL(
not_null<Main::Session*> session,
const MTPDstarGiftAttributeModel &data) {
auto result = Data::UniqueGiftModel{
.document = session->data().processDocument(data.vdocument()),
};
result.name = qs(data.vname());
result.rarityPermille = data.vrarity_permille().v;
return result;
}
Data::UniqueGiftPattern FromTL(
not_null<Main::Session*> session,
const MTPDstarGiftAttributePattern &data) {
auto result = Data::UniqueGiftPattern{
.document = session->data().processDocument(data.vdocument()),
};
result.document->overrideEmojiUsesTextColor(true);
result.name = qs(data.vname());
result.rarityPermille = data.vrarity_permille().v;
return result;
}
Data::UniqueGiftBackdrop FromTL(const MTPDstarGiftAttributeBackdrop &data) {
auto result = Data::UniqueGiftBackdrop();
result.name = qs(data.vname());
result.rarityPermille = data.vrarity_permille().v;
result.centerColor = Ui::ColorFromSerialized(
data.vcenter_color());
result.edgeColor = Ui::ColorFromSerialized(
data.vedge_color());
result.patternColor = Ui::ColorFromSerialized(
data.vpattern_color());
result.textColor = Ui::ColorFromSerialized(
data.vtext_color());
return result;
}
Data::UniqueGiftOriginalDetails FromTL(
not_null<Main::Session*> session,
const MTPDstarGiftAttributeOriginalDetails &data) {
auto result = Data::UniqueGiftOriginalDetails();
result.date = data.vdate().v;
result.senderId = data.vsender_id()
? peerFromMTP(*data.vsender_id())
: PeerId();
result.recipientId = peerFromMTP(data.vrecipient_id());
result.message = data.vmessage()
? ParseTextWithEntities(session, *data.vmessage())
: TextWithEntities();
return result;
}
} // namespace Api

View File

@@ -8,6 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#pragma once
#include "data/data_premium_subscription_option.h"
#include "data/data_star_gift.h"
#include "mtproto/sender.h"
class History;
@@ -73,34 +74,6 @@ struct GiftOptionData {
int months = 0;
};
struct StarGift {
uint64 id = 0;
int64 stars = 0;
int64 starsConverted = 0;
not_null<DocumentData*> document;
int limitedLeft = 0;
int limitedCount = 0;
TimeId firstSaleDate = 0;
TimeId lastSaleDate = 0;
bool birthday = false;
friend inline bool operator==(
const StarGift &,
const StarGift &) = default;
};
struct UserStarGift {
StarGift info;
TextWithEntities message;
int64 starsConverted = 0;
PeerId fromId = 0;
MsgId messageId = 0;
TimeId date = 0;
bool anonymous = false;
bool hidden = false;
bool mine = false;
};
class Premium final {
public:
explicit Premium(not_null<ApiWrap*> api);
@@ -143,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();
@@ -193,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;
};
@@ -223,7 +197,7 @@ public:
[[nodiscard]] bool giveawayGiftsPurchaseAvailable() const;
[[nodiscard]] rpl::producer<rpl::no_value, QString> requestStarGifts();
[[nodiscard]] const std::vector<StarGift> &starGifts() const;
[[nodiscard]] const std::vector<Data::StarGift> &starGifts() const;
private:
struct Token final {
@@ -235,6 +209,7 @@ private:
};
struct Store final {
uint64 amount = 0;
QString currency;
QString product;
int quantity = 0;
};
@@ -245,7 +220,7 @@ private:
struct {
std::vector<int> months;
std::vector<int64> totalCosts;
QString currency;
std::vector<QString> currencies;
} _optionsForOnePerson;
std::vector<int> _availablePresets;
@@ -253,7 +228,7 @@ private:
base::flat_map<Token, Store> _stores;
int32 _giftsHash = 0;
std::vector<StarGift> _gifts;
std::vector<Data::StarGift> _gifts;
MTP::Sender _api;
@@ -271,23 +246,43 @@ 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);
[[nodiscard]] rpl::producer<DocumentData*> RandomHelloStickerValue(
not_null<Main::Session*> session);
[[nodiscard]] std::optional<StarGift> FromTL(
[[nodiscard]] std::optional<Data::StarGift> FromTL(
not_null<Main::Session*> session,
const MTPstarGift &gift);
[[nodiscard]] std::optional<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,
const MTPDstarGiftAttributeModel &data);
[[nodiscard]] Data::UniqueGiftPattern FromTL(
not_null<Main::Session*> session,
const MTPDstarGiftAttributePattern &data);
[[nodiscard]] Data::UniqueGiftBackdrop FromTL(
const MTPDstarGiftAttributeBackdrop &data);
[[nodiscard]] Data::UniqueGiftOriginalDetails FromTL(
not_null<Main::Session*> session,
const MTPDstarGiftAttributeOriginalDetails &data);
} // namespace Api

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

@@ -229,7 +229,7 @@ EntitiesInText EntitiesFromMTP(
}
MTPVector<MTPMessageEntity> EntitiesToMTP(
not_null<Main::Session*> session,
Main::Session *session,
const EntitiesInText &entities,
ConvertOption option) {
auto v = QVector<MTPMessageEntity>();
@@ -283,6 +283,7 @@ MTPVector<MTPMessageEntity> EntitiesToMTP(
v.push_back(MTP_messageEntityMention(offset, length));
} break;
case EntityType::MentionName: {
Assert(session != nullptr);
const auto valid = MentionNameEntity(
session,
offset,
@@ -344,4 +345,14 @@ MTPVector<MTPMessageEntity> EntitiesToMTP(
return MTP_vector<MTPMessageEntity>(std::move(v));
}
TextWithEntities ParseTextWithEntities(
Main::Session *session,
const MTPTextWithEntities &text) {
const auto &data = text.data();
return {
.text = qs(data.vtext()),
.entities = EntitiesFromMTP(session, data.ventities().v),
};
}
} // namespace Api

View File

@@ -25,8 +25,12 @@ enum class ConvertOption {
const QVector<MTPMessageEntity> &entities);
[[nodiscard]] MTPVector<MTPMessageEntity> EntitiesToMTP(
not_null<Main::Session*> session,
Main::Session *session,
const EntitiesInText &entities,
ConvertOption option = ConvertOption::WithLocal);
[[nodiscard]] TextWithEntities ParseTextWithEntities(
Main::Session *session,
const MTPTextWithEntities &text);
} // namespace Api

View File

@@ -1218,7 +1218,9 @@ void Updates::applyUpdatesNoPtsCheck(const MTPUpdates &updates) {
MTP_int(d.vttl_period().value_or_empty()),
MTPint(), // quick_reply_shortcut_id
MTPlong(), // effect
MTPFactCheck()),
MTPFactCheck(),
MTPint(), // report_delivery_until_date
MTPlong()), // paid_message_stars
MessageFlags(),
NewMessageType::Unread);
} break;
@@ -1255,7 +1257,9 @@ void Updates::applyUpdatesNoPtsCheck(const MTPUpdates &updates) {
MTP_int(d.vttl_period().value_or_empty()),
MTPint(), // quick_reply_shortcut_id
MTPlong(), // effect
MTPFactCheck()),
MTPFactCheck(),
MTPint(), // report_delivery_until_date
MTPlong()), // paid_message_stars
MessageFlags(),
NewMessageType::Unread);
} break;
@@ -2707,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);
}
}
@@ -3267,13 +3286,13 @@ void ApiWrap::finishForwarding(const SendAction &action) {
const auto topicRootId = action.replyTo.topicRootId;
auto toForward = history->resolveForwardDraft(topicRootId);
if (!toForward.items.empty()) {
const auto error = GetErrorTextForSending(
const auto error = GetErrorForSending(
history->peer,
{
.topicRootId = topicRootId,
.forward = &toForward.items,
});
if (!error.isEmpty()) {
if (error) {
return;
}
@@ -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

@@ -11,6 +11,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "boxes/filters/edit_filter_box.h"
#include "boxes/premium_limits_box.h"
#include "core/application.h" // primaryWindow
#include "core/ui_integration.h"
#include "data/data_chat_filters.h"
#include "data/data_premium_limits.h"
#include "data/data_session.h"
@@ -22,6 +23,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/painter.h"
#include "ui/rect.h"
#include "ui/text/text_utilities.h" // Ui::Text::Bold
#include "ui/toast/toast.h"
#include "ui/widgets/buttons.h"
#include "ui/widgets/menu/menu_action.h"
#include "ui/widgets/popup_menu.h"
@@ -169,15 +171,22 @@ void ChangeFilterById(
)).done([=, chat = history->peer->name(), name = filter.title()] {
const auto account = not_null(&history->session().account());
if (const auto controller = Core::App().windowFor(account)) {
controller->showToast((add
? tr::lng_filters_toast_add
: tr::lng_filters_toast_remove)(
tr::now,
lt_chat,
Ui::Text::Bold(chat),
lt_folder,
Ui::Text::Bold(name),
Ui::Text::WithEntities));
const auto isStatic = name.isStatic;
controller->showToast({
.text = (add
? tr::lng_filters_toast_add
: tr::lng_filters_toast_remove)(
tr::now,
lt_chat,
Ui::Text::Bold(chat),
lt_folder,
Ui::Text::Wrapped(name.text, EntityType::Bold),
Ui::Text::WithEntities),
.textContext = Core::TextContext({
.session = &history->session(),
.customEmojiLoopLimit = isStatic ? -1 : 0,
}),
});
}
}).fail([=](const MTP::Error &error) {
LOG(("API Error: failed to %1 a dialog to a folder. %2")
@@ -274,19 +283,22 @@ void FillChooseFilterMenu(
};
const auto contains = filter.contains(history);
const auto title = filter.title();
auto item = base::make_unique_q<FilterAction>(
menu.get(),
menu->st().menu,
Ui::Menu::CreateAction(
menu.get(),
Ui::Text::FixAmpersandInAction(filter.title()),
Ui::Text::FixAmpersandInAction(title.text.text),
std::move(callback)),
contains ? &st::mediaPlayerMenuCheck : nullptr,
contains ? &st::mediaPlayerMenuCheck : nullptr);
item->setMarkedText(title.text, QString(), Core::TextContext({
.session = &history->session(),
.customEmojiLoopLimit = title.isStatic ? -1 : 0,
}));
item->setIcon(Icon(showColors ? filter : filter.withColorIndex({})));
const auto &p = st::menuWithIcons.itemPadding;
item->setMinWidth(item->minWidth() + p.left() - p.right() - p.top());
const auto action = menu->addAction(std::move(item));
action->setEnabled(contains
? validator.canRemove(id)

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

@@ -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)
: QString();
});
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

@@ -15,8 +15,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "boxes/premium_limits_box.h"
#include "boxes/premium_preview_box.h"
#include "chat_helpers/emoji_suggestions_widget.h"
#include "chat_helpers/message_field.h"
#include "core/application.h"
#include "core/core_settings.h"
#include "core/ui_integration.h"
#include "data/data_channel.h"
#include "data/data_chat_filters.h"
#include "data/data_peer.h"
@@ -38,6 +40,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/filter_icons.h"
#include "ui/layers/generic_box.h"
#include "ui/painter.h"
#include "ui/power_saving.h"
#include "ui/vertical_list.h"
#include "ui/widgets/buttons.h"
#include "ui/widgets/fields/input_field.h"
@@ -350,13 +353,17 @@ void EditFilterBox(
rpl::variable<bool> hasLinks;
rpl::variable<bool> chatlist;
rpl::variable<bool> creating;
rpl::variable<TextWithEntities> title;
rpl::variable<bool> staticTitle;
rpl::variable<int> colorIndex;
};
const auto owner = &window->session().data();
const auto state = box->lifetime().make_state<State>(State{
.rules = filter,
.chatlist = filter.chatlist(),
.creating = filter.title().isEmpty(),
.creating = filter.title().empty(),
.title = filter.titleText(),
.staticTitle = filter.staticTitle(),
});
state->colorIndex = filter.colorIndex().value_or(kNoTag);
state->links = owner->chatsFilters().chatlistLinks(filter.id()),
@@ -394,32 +401,67 @@ void EditFilterBox(
tr::lng_filters_edit()));
box->setCloseByOutsideClick(false);
const auto session = &window->session();
Data::AmPremiumValue(
&window->session()
session
) | rpl::start_with_next([=] {
box->closeBox();
}, box->lifetime());
const auto content = box->verticalLayout();
const auto current = state->title.current();
const auto name = content->add(
object_ptr<Ui::InputField>(
box,
st::windowFilterNameInput,
tr::lng_filters_new_name(),
filter.title()),
Ui::InputField::Mode::SingleLine,
tr::lng_filters_new_name()),
st::markdownLinkFieldPadding);
InitMessageFieldHandlers(window, name, ChatHelpers::PauseReason::Layer);
name->setTextWithTags({
current.text,
TextUtilities::ConvertEntitiesToTextTags(current.entities),
}, Ui::InputField::HistoryAction::Clear);
name->setMaxLength(kMaxFilterTitleLength);
name->setInstantReplaces(Ui::InstantReplaces::Default());
name->setInstantReplacesEnabled(
Core::App().settings().replaceEmojiValue());
Ui::Emoji::SuggestionsController::Init(
box->getDelegate()->outerContainer(),
name,
&window->session());
const auto nameEditing = box->lifetime().make_state<NameEditing>(
NameEditing{ name });
const auto staticTitle = Ui::CreateChild<Ui::LinkButton>(
name,
QString());
staticTitle->setClickedCallback([=] {
state->staticTitle = !state->staticTitle.current();
});
state->staticTitle.value() | rpl::start_with_next([=](bool value) {
staticTitle->setText(value
? tr::lng_filters_enable_animations(tr::now)
: tr::lng_filters_disable_animations(tr::now));
const auto paused = [=] {
using namespace Window;
return window->isGifPausedAtLeastFor(GifPauseReason::Layer);
};
name->setCustomTextContext(Core::TextContext({
.session = session,
.customEmojiLoopLimit = value ? -1 : 0,
}), [paused] {
return On(PowerSaving::kEmojiChat) || paused();
}, [paused] {
return On(PowerSaving::kChatSpoiler) || paused();
});
name->update();
}, staticTitle->lifetime());
rpl::combine(
staticTitle->widthValue(),
name->widthValue()
) | rpl::start_with_next([=](int inner, int outer) {
staticTitle->moveToRight(
st::windowFilterStaticTitlePosition.x(),
st::windowFilterStaticTitlePosition.y(),
outer);
}, staticTitle->lifetime());
state->creating.value(
) | rpl::filter(!_1) | rpl::start_with_next([=] {
nameEditing->custom = true;
@@ -430,7 +472,13 @@ void EditFilterBox(
if (!nameEditing->settingDefault) {
nameEditing->custom = true;
}
auto entered = name->getTextWithTags();
state->title = TextWithEntities{
std::move(entered.text),
TextUtilities::ConvertTextTagsToEntities(entered.tags),
};
}, name->lifetime());
const auto updateDefaultTitle = [=](const Data::ChatFilter &filter) {
if (nameEditing->custom) {
return;
@@ -443,6 +491,11 @@ void EditFilterBox(
}
};
state->title.value(
) | rpl::start_with_next([=](const TextWithEntities &value) {
staticTitle->setVisible(!value.entities.isEmpty());
}, staticTitle->lifetime());
const auto outer = box->getDelegate()->outerContainer();
CreateIconSelector(
outer,
@@ -545,18 +598,25 @@ void EditFilterBox(
colors->width(),
h);
}, preview->lifetime());
const auto previewTag = preview->lifetime().make_state<QImage>();
const auto previewAlpha = preview->lifetime().make_state<float64>(1);
struct TagState {
Ui::Animations::Simple animation;
Ui::ChatsFilterTagContext context;
QImage frame;
float64 alpha = 1.;
};
const auto tag = preview->lifetime().make_state<TagState>();
tag->context.textContext = Core::TextContext({ .session = session });
preview->paintRequest() | rpl::start_with_next([=] {
auto p = QPainter(preview);
p.setOpacity(*previewAlpha);
const auto size = previewTag->size() / style::DevicePixelRatio();
p.setOpacity(tag->alpha);
const auto size = tag->frame.size() / style::DevicePixelRatio();
const auto rect = QRect(
preview->width() - size.width() - st::boxRowPadding.right(),
(st::normalFont->height - size.height()) / 2,
size.width(),
size.height());
p.drawImage(rect.topLeft(), *previewTag);
p.drawImage(rect.topLeft(), tag->frame);
if (p.opacity() < 1) {
p.setOpacity(1. - p.opacity());
p.setFont(st::normalFont);
@@ -573,16 +633,20 @@ void EditFilterBox(
Ui::CreateSkipWidget(colors, side),
st::boxRowPadding);
auto buttons = std::vector<not_null<UserpicBuilder::CircleButton*>>();
const auto animation
= line->lifetime().make_state<Ui::Animations::Simple>();
const auto palette = [](int i) {
return Ui::EmptyUserpic::UserpicColor(i).color2;
};
name->changes() | rpl::start_with_next([=] {
*previewTag = Ui::ChatsFilterTag(
name->getLastText().toUpper(),
palette(state->colorIndex.current())->c,
false);
const auto upperTitle = [=] {
auto value = state->title.current();
value.text = value.text.toUpper();
return value;
};
state->title.changes(
) | rpl::start_with_next([=] {
tag->context.color = palette(state->colorIndex.current())->c;
tag->frame = Ui::ChatsFilterTag(
upperTitle(),
tag->context);
preview->update();
}, preview->lifetime());
for (auto i = 0; i < kColorsCount; ++i) {
@@ -596,12 +660,12 @@ void EditFilterBox(
const auto color = palette(i);
button->setBrush(color);
if (progress == 1) {
*previewTag = Ui::ChatsFilterTag(
name->getLastText().toUpper(),
color->c,
false);
tag->context.color = color->c;
tag->frame = Ui::ChatsFilterTag(
upperTitle(),
tag->context);
if (i == kNoTag) {
*previewAlpha = 0.;
tag->alpha = 0.;
}
}
buttons.push_back(button);
@@ -616,17 +680,17 @@ void EditFilterBox(
const auto c2 = palette(now);
const auto a1 = (was == kNoTag) ? 0. : 1.;
const auto a2 = (now == kNoTag) ? 0. : 1.;
animation->stop();
animation->start([=](float64 progress) {
tag->animation.stop();
tag->animation.start([=](float64 progress) {
if (was >= 0) {
buttons[was]->setSelectedProgress(1. - progress);
}
buttons[now]->setSelectedProgress(progress);
*previewTag = Ui::ChatsFilterTag(
name->getLastText().toUpper(),
anim::color(c1, c2, progress),
false);
*previewAlpha = anim::interpolateF(a1, a2, progress);
tag->context.color = anim::color(c1, c2, progress);
tag->frame = Ui::ChatsFilterTag(
upperTitle(),
tag->context);
tag->alpha = anim::interpolateF(a1, a2, progress);
preview->update();
}, 0., 1., st::universalDuration);
}
@@ -672,9 +736,11 @@ void EditFilterBox(
}
const auto collect = [=]() -> std::optional<Data::ChatFilter> {
const auto title = name->getLastText().trimmed();
auto title = state->title.current();
const auto staticTitle = !title.entities.isEmpty()
&& state->staticTitle.current();
const auto rules = data->current();
if (title.isEmpty()) {
if (title.empty()) {
name->showError();
box->scrollToY(0);
return {};
@@ -691,7 +757,9 @@ void EditFilterBox(
const auto colorIndex = (rawColorIndex >= kNoTag
? std::nullopt
: std::make_optional(rawColorIndex));
return rules.withTitle(title).withColorIndex(colorIndex);
return rules.withTitle(
{ std::move(title), staticTitle }
).withColorIndex(colorIndex);
};
Ui::AddSubsectionTitle(

View File

@@ -7,6 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#include "boxes/filters/edit_filter_chats_list.h"
#include "core/ui_integration.h"
#include "data/data_chat_filters.h"
#include "data/data_premium_limits.h"
#include "data/data_session.h"
@@ -63,13 +64,27 @@ private:
class ExceptionRow final : public ChatsListBoxController::Row {
public:
explicit ExceptionRow(not_null<History*> history);
ExceptionRow(
not_null<History*> history,
not_null<PeerListDelegate*> delegate);
QString generateName() override;
QString generateShortName() override;
PaintRoundImageCallback generatePaintUserpicCallback(
bool forceRound) override;
void paintStatusText(
Painter &p,
const style::PeerListItem &st,
int x,
int y,
int availableWidth,
int outerWidth,
bool selected) override;
private:
Ui::Text::String _filtersText;
};
class TypeController final : public PeerListController {
@@ -126,15 +141,32 @@ Flag TypeRow::flag() const {
return static_cast<Flag>(id() & 0xFFFF);
}
ExceptionRow::ExceptionRow(not_null<History*> history) : Row(history) {
auto filters = QStringList();
ExceptionRow::ExceptionRow(
not_null<History*> history,
not_null<PeerListDelegate*> delegate)
: Row(history) {
auto filters = TextWithEntities();
for (const auto &filter : history->owner().chatsFilters().list()) {
if (filter.contains(history) && filter.id()) {
filters << filter.title();
if (!filters.empty()) {
filters.append(u", "_q);
}
auto title = filter.title();
filters.append(title.isStatic
? Data::ForceCustomEmojiStatic(std::move(title.text))
: std::move(title.text));
}
}
if (!filters.isEmpty()) {
setCustomStatus(filters.join(", "));
if (!filters.empty()) {
const auto repaint = [=] { delegate->peerListUpdateRow(this); };
_filtersText.setMarkedText(
st::defaultTextStyle,
filters,
kMarkupTextOptions,
Core::TextContext({
.session = &history->session(),
.repaint = repaint,
}));
} else if (peer()->isSelf()) {
setCustomStatus(tr::lng_saved_forward_here(tr::now));
}
@@ -176,6 +208,37 @@ PaintRoundImageCallback ExceptionRow::generatePaintUserpicCallback(
};
}
void ExceptionRow::paintStatusText(
Painter &p,
const style::PeerListItem &st,
int x,
int y,
int availableWidth,
int outerWidth,
bool selected) {
if (_filtersText.isEmpty()) {
Row::paintStatusText(
p,
st,
x,
y,
availableWidth,
outerWidth,
selected);
} else {
p.setPen(selected ? st.statusFgOver : st.statusFg);
_filtersText.draw(p, {
.position = { x, y },
.outerWidth = outerWidth,
.availableWidth = availableWidth,
.palette = &st::defaultTextPalette,
.now = crl::now(),
.pausedEmoji = false,
.elisionLines = 1,
});
}
}
TypeController::TypeController(
not_null<Main::Session*> session,
Flags options,
@@ -418,7 +481,7 @@ void EditFilterChatsListController::prepareViewHook() {
const auto rows = std::make_unique<std::optional<ExceptionRow>[]>(count);
auto i = 0;
for (const auto &history : _peers) {
rows[i++].emplace(history);
rows[i++].emplace(history, delegate());
}
auto pointers = std::vector<ExceptionRow*>();
pointers.reserve(count);
@@ -499,7 +562,7 @@ auto EditFilterChatsListController::createRow(not_null<History*> history)
return nullptr;
}
return history->inChatList()
? std::make_unique<ExceptionRow>(history)
? std::make_unique<ExceptionRow>(history, delegate())
: nullptr;
}

View File

@@ -11,6 +11,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "boxes/peers/edit_peer_invite_link.h" // InviteLinkQrBox.
#include "boxes/peer_list_box.h"
#include "boxes/premium_limits_box.h"
#include "core/ui_integration.h"
#include "data/data_channel.h"
#include "data/data_chat.h"
#include "data/data_chat_filters.h"
@@ -482,7 +483,7 @@ private:
const not_null<Window::SessionController*> _window;
InviteLinkData _data;
QString _filterTitle;
Data::ChatFilterTitle _filterTitle;
base::flat_set<not_null<History*>> _filterChats;
base::flat_map<not_null<PeerData*>, QString> _denied;
rpl::variable<base::flat_set<not_null<PeerData*>>> _selected;
@@ -535,6 +536,7 @@ void LinkController::addHeader(not_null<Ui::VerticalLayout*> container) {
}, verticalLayout->lifetime());
verticalLayout->add(std::move(icon.widget));
const auto isStatic = _filterTitle.isStatic;
verticalLayout->add(
object_ptr<Ui::CenterWrap<>>(
verticalLayout,
@@ -544,9 +546,16 @@ void LinkController::addHeader(not_null<Ui::VerticalLayout*> container) {
? tr::lng_filters_link_no_about(Ui::Text::WithEntities)
: tr::lng_filters_link_share_about(
lt_folder,
rpl::single(Ui::Text::Bold(_filterTitle)),
rpl::single(Ui::Text::Wrapped(
_filterTitle.text,
EntityType::Bold)),
Ui::Text::WithEntities)),
st::settingsFilterDividerLabel)),
st::settingsFilterDividerLabel,
st::defaultPopupMenu,
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,28 +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()> 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

@@ -26,6 +26,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/widgets/labels.h"
#include "ui/wrap/fade_wrap.h"
#include "ui/painter.h"
#include "ui/rect.h"
#include "passport/passport_encryption.h"
#include "passport/passport_panel_edit_contact.h"
#include "settings/settings_privacy_security.h"
@@ -171,8 +172,9 @@ PasscodeBox::PasscodeBox(
bool turningOff)
: _session(session)
, _api(&_session->mtp())
, _textWidth(st::boxWidth - st::boxPadding.left() * 1.5)
, _turningOff(turningOff)
, _about(st::boxWidth - st::boxPadding.left() * 1.5)
, _about(_textWidth)
, _oldPasscode(this, st::defaultInputField, tr::lng_passcode_enter_old())
, _newPasscode(
this,
@@ -193,10 +195,11 @@ PasscodeBox::PasscodeBox(
const CloudFields &fields)
: _session(session)
, _api(mtp)
, _textWidth(st::boxWidth - st::boxPadding.left() * 1.5)
, _turningOff(fields.turningOff)
, _cloudPwd(true)
, _cloudFields(fields)
, _about(st::boxWidth - st::boxPadding.left() * 1.5)
, _about(_textWidth)
, _oldPasscode(this, st::defaultInputField, tr::lng_cloud_password_enter_old())
, _newPasscode(
this,
@@ -274,7 +277,7 @@ void PasscodeBox::prepare() {
: _cloudPwd
? tr::lng_cloud_password_about(tr::now)
: tr::lng_passcode_about(tr::now)));
_aboutHeight = _about.countHeight(st::boxWidth - st::boxPadding.left() * 1.5);
_aboutHeight = _about.countHeight(_textWidth);
const auto onlyCheck = onlyCheckCurrent();
if (onlyCheck) {
_oldPasscode->show();
@@ -382,28 +385,27 @@ void PasscodeBox::paintEvent(QPaintEvent *e) {
Painter p(this);
int32 w = st::boxWidth - st::boxPadding.left() * 1.5;
int32 abouty = (_passwordHint->isHidden() ? ((_reenterPasscode->isHidden() ? (_oldPasscode->y() + (_showRecoverLink && !_hintText.isEmpty() ? st::passcodeTextLine : 0)) : _reenterPasscode->y()) + st::passcodeSkip) : _passwordHint->y()) + _oldPasscode->height() + st::passcodeLittleSkip + st::passcodeAboutSkip;
p.setPen(st::boxTextFg);
_about.drawLeft(p, st::boxPadding.left(), abouty, w, width());
_about.drawLeft(p, st::boxPadding.left(), abouty, _textWidth, width());
if (!_hintText.isEmpty() && _oldError.isEmpty()) {
_hintText.drawLeftElided(p, st::boxPadding.left(), _oldPasscode->y() + _oldPasscode->height() + ((st::passcodeTextLine - st::normalFont->height) / 2), w, width(), 1, style::al_topleft);
_hintText.drawLeftElided(p, st::boxPadding.left(), _oldPasscode->y() + _oldPasscode->height() + ((st::passcodeTextLine - st::normalFont->height) / 2), _textWidth, width(), 1, style::al_topleft);
}
if (!_oldError.isEmpty()) {
p.setPen(st::boxTextFgError);
p.drawText(QRect(st::boxPadding.left(), _oldPasscode->y() + _oldPasscode->height(), w, st::passcodeTextLine), _oldError, style::al_left);
p.drawText(QRect(st::boxPadding.left(), _oldPasscode->y() + _oldPasscode->height(), _textWidth, st::passcodeTextLine), _oldError, style::al_left);
}
if (!_newError.isEmpty()) {
p.setPen(st::boxTextFgError);
p.drawText(QRect(st::boxPadding.left(), _reenterPasscode->y() + _reenterPasscode->height(), w, st::passcodeTextLine), _newError, style::al_left);
p.drawText(QRect(st::boxPadding.left(), _reenterPasscode->y() + _reenterPasscode->height(), _textWidth, st::passcodeTextLine), _newError, style::al_left);
}
if (!_emailError.isEmpty()) {
p.setPen(st::boxTextFgError);
p.drawText(QRect(st::boxPadding.left(), _recoverEmail->y() + _recoverEmail->height(), w, st::passcodeTextLine), _emailError, style::al_left);
p.drawText(QRect(st::boxPadding.left(), _recoverEmail->y() + _recoverEmail->height(), _textWidth, st::passcodeTextLine), _emailError, style::al_left);
}
}
@@ -1141,11 +1143,20 @@ RecoverBox::RecoverBox(
Fn<void()> closeParent)
: _session(session)
, _api(mtp)
, _pattern(st::normalFont->elided(tr::lng_signin_recover_hint(tr::now, lt_recover_email, pattern), st::boxWidth - st::boxPadding.left() * 1.5))
, _textWidth(st::boxWidth - st::boxPadding.left() * 1.5)
, _cloudFields(fields)
, _recoverCode(this, st::defaultInputField, tr::lng_signin_code())
, _noEmailAccess(this, tr::lng_signin_try_password(tr::now))
, _patternLabel(
this,
tr::lng_signin_recover_hint(
lt_recover_email,
rpl::single(Ui::Text::WrapEmailPattern(pattern)),
Ui::Text::WithEntities),
st::termsContent,
st::defaultPopupMenu)
, _closeParent(std::move(closeParent)) {
_patternLabel->setAttribute(Qt::WA_TransparentForMouseEvents);
if (_cloudFields.pendingResetDate != 0 || !session) {
_noEmailAccess.destroy();
} else {
@@ -1176,19 +1187,24 @@ rpl::producer<> RecoverBox::recoveryExpired() const {
return _recoveryExpired.events();
}
void RecoverBox::prepare() {
setTitle(tr::lng_signin_recover_title());
addButton(tr::lng_passcode_submit(), [=] { submit(); });
addButton(tr::lng_cancel(), [=] { closeBox(); });
void RecoverBox::updateHeight() {
setDimensions(
st::boxWidth,
(st::passcodePadding.top()
+ st::passcodePadding.bottom()
+ st::passcodeTextLine
+ _recoverCode->height()
+ _patternLabel->height()
+ st::passcodeTextLine));
}
void RecoverBox::prepare() {
setTitle(tr::lng_signin_recover_title());
addButton(tr::lng_passcode_submit(), [=] { submit(); });
addButton(tr::lng_cancel(), [=] { closeBox(); });
updateHeight();
_recoverCode->changes(
) | rpl::start_with_next([=] {
@@ -1205,23 +1221,42 @@ void RecoverBox::paintEvent(QPaintEvent *e) {
p.setFont(st::normalFont);
p.setPen(st::boxTextFg);
int32 w = st::boxWidth - st::boxPadding.left() * 1.5;
p.drawText(QRect(st::boxPadding.left(), _recoverCode->y() - st::passcodeTextLine - st::passcodePadding.top(), w, st::passcodePadding.top() + st::passcodeTextLine), _pattern, style::al_left);
if (!_error.isEmpty()) {
p.setPen(st::boxTextFgError);
p.drawText(QRect(st::boxPadding.left(), _recoverCode->y() + _recoverCode->height(), w, st::passcodeTextLine), _error, style::al_left);
p.drawText(
QRect(
st::boxPadding.left(),
_recoverCode->y() + _recoverCode->height(),
_textWidth,
st::passcodeTextLine),
_error,
style::al_left);
}
}
void RecoverBox::resizeEvent(QResizeEvent *e) {
BoxContent::resizeEvent(e);
_recoverCode->resize(st::boxWidth - st::boxPadding.left() - st::boxPadding.right(), _recoverCode->height());
_recoverCode->moveToLeft(st::boxPadding.left(), st::passcodePadding.top() + st::passcodePadding.bottom() + st::passcodeTextLine);
_patternLabel->resizeToWidth(_textWidth);
_patternLabel->moveToLeft(
st::boxPadding.left(),
st::passcodePadding.top());
_recoverCode->resize(
st::boxWidth - st::boxPadding.left() - st::boxPadding.right(),
_recoverCode->height());
_recoverCode->moveToLeft(
st::boxPadding.left(),
rect::m::sum::v(st::passcodePadding) + _patternLabel->height());
if (_noEmailAccess) {
_noEmailAccess->moveToLeft(st::boxPadding.left(), _recoverCode->y() + _recoverCode->height() + (st::passcodeTextLine - _noEmailAccess->height()) / 2);
_noEmailAccess->moveToLeft(
st::boxPadding.left(),
rect::bottom(_recoverCode)
+ (st::passcodeTextLine - _noEmailAccess->height()) / 2);
}
updateHeight();
}
void RecoverBox::setInnerFocus() {

View File

@@ -154,6 +154,7 @@ private:
Main::Session *_session = nullptr;
MTP::Sender _api;
const int _textWidth;
QString _pattern;
@@ -219,17 +220,18 @@ private:
void proceedToChange(const QString &code);
void checkSubmitFail(const MTP::Error &error);
void setError(const QString &error);
void updateHeight();
Main::Session *_session = nullptr;
MTP::Sender _api;
const int _textWidth;
mtpRequestId _submitRequest = 0;
QString _pattern;
PasscodeBox::CloudFields _cloudFields;
object_ptr<Ui::InputField> _recoverCode;
object_ptr<Ui::LinkButton> _noEmailAccess;
object_ptr<Ui::FlatLabel> _patternLabel;
Fn<void()> _closeParent;
QString _error;

View File

@@ -788,31 +788,29 @@ int PeerListRow::paintNameIconGetWidth(
|| _isVerifyCodesChat) {
return 0;
}
return _badge.drawGetWidth(
p,
QRect(
return _badge.drawGetWidth(p, {
.peer = peer(),
.rectForName = QRect(
nameLeft,
nameTop,
availableWidth,
st::semiboldFont->height),
nameWidth,
outerWidth,
{
.peer = peer(),
.verified = &(selected
? st::dialogsVerifiedIconOver
: st::dialogsVerifiedIcon),
.premium = &(selected
? st::dialogsPremiumIcon.over
: st::dialogsPremiumIcon.icon),
.scam = &(selected ? st::dialogsScamFgOver : st::dialogsScamFg),
.premiumFg = &(selected
? st::dialogsVerifiedIconBgOver
: st::dialogsVerifiedIconBg),
.customEmojiRepaint = repaint,
.now = now,
.paused = false,
});
.nameWidth = nameWidth,
.outerWidth = outerWidth,
.verified = &(selected
? st::dialogsVerifiedIconOver
: st::dialogsVerifiedIcon),
.premium = &(selected
? st::dialogsPremiumIcon.over
: st::dialogsPremiumIcon.icon),
.scam = &(selected ? st::dialogsScamFgOver : st::dialogsScamFg),
.premiumFg = &(selected
? st::dialogsVerifiedIconBgOver
: st::dialogsVerifiedIconBg),
.customEmojiRepaint = repaint,
.now = now,
.paused = false,
});
}
void PeerListRow::paintStatusText(
@@ -875,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.
@@ -914,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);
@@ -1682,6 +1687,10 @@ void PeerListContent::mousePressReleased(Qt::MouseButton button) {
_controller->rowClicked(row);
}
}
} else if (button == Qt::MiddleButton && pressed == _selected) {
if (auto row = getRow(pressed.index)) {
_controller->rowMiddleClicked(row);
}
}
}

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> &;
@@ -482,6 +489,8 @@ public:
}
virtual void rowClicked(not_null<PeerListRow*> row) = 0;
virtual void rowMiddleClicked(not_null<PeerListRow*> row) {
}
virtual void rowRightActionClicked(not_null<PeerListRow*> row) {
}

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,8 +41,10 @@ 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"
#include "window/window_session_controller.h" // showAddContact()
#include "base/unixtime.h"
#include "styles/style_boxes.h"
@@ -64,6 +66,10 @@ object_ptr<Ui::BoxContent> PrepareContactsBox(
public:
using ContactsBoxController::ContactsBoxController;
[[nodiscard]] rpl::producer<not_null<PeerData*>> wheelClicks() const {
return _wheelClicks.events();
}
protected:
std::unique_ptr<PeerListRow> createRow(
not_null<UserData*> user) override {
@@ -72,6 +78,14 @@ object_ptr<Ui::BoxContent> PrepareContactsBox(
: nullptr;
}
void rowMiddleClicked(
not_null<PeerListRow*> row) override {
_wheelClicks.fire(row->peer());
}
private:
rpl::event_stream<not_null<PeerData*>> _wheelClicks;
};
auto controller = std::make_unique<Controller>(
&sessionController->session());
@@ -100,6 +114,10 @@ object_ptr<Ui::BoxContent> PrepareContactsBox(
online ? &st::contactsSortOnlineIconOver : nullptr);
});
raw->setSortMode(Mode::Online);
raw->wheelClicks() | rpl::start_with_next([=](not_null<PeerData*> p) {
sessionController->showInNewWindow(p);
}, box->lifetime());
};
return Box<PeerListBox>(std::move(controller), std::move(init));
}
@@ -258,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;
}
}
@@ -301,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();
@@ -709,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(
@@ -742,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 {
@@ -752,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) {
@@ -819,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(
@@ -843,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;
}
@@ -1076,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

@@ -2282,6 +2282,8 @@ void ParticipantsBoxSearchController::restoreState(
_allLoaded = my->allLoaded;
_offset = my->offset;
_query = my->query;
_timer.cancel();
_requestId = 0;
if (my->wasLoading) {
searchOnServer();
}

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

@@ -26,6 +26,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "boxes/peers/edit_peer_requests_box.h"
#include "boxes/peers/edit_peer_reactions.h"
#include "boxes/peers/replace_boost_box.h"
#include "boxes/peers/verify_peers_box.h"
#include "boxes/peer_list_controllers.h"
#include "boxes/stickers_box.h"
#include "boxes/username_box.h"
@@ -218,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,
@@ -270,6 +298,7 @@ void ShowEditPermissions(
channel,
result.boostsUnrestrict,
close);
SaveStarsPerMessage(channel, result.starsPerMessage, close);
}
};
auto done = [=](EditPeerPermissionsBoxResult result) {
@@ -281,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;
}
@@ -366,6 +397,7 @@ private:
void fillBotEditIntroButton();
void fillBotEditCommandsButton();
void fillBotEditSettingsButton();
void fillBotVerifyAccounts();
void submitTitle();
void submitDescription();
@@ -1206,6 +1238,7 @@ void Controller::fillManageSection() {
Ui::Text::RichLangValue),
st::boxDividerLabel),
st::defaultBoxDividerLabelPadding));
fillBotVerifyAccounts();
return;
}
@@ -1241,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)
@@ -1796,6 +1831,39 @@ void Controller::fillBotEditSettingsButton() {
{ &st::menuIconSettings });
}
void Controller::fillBotVerifyAccounts() {
Expects(_isBot);
const auto user = _peer->asUser();
const auto wrap = _controls.buttonsLayout->add(
object_ptr<Ui::SlideWrap<Ui::VerticalLayout>>(
_controls.buttonsLayout,
object_ptr<Ui::VerticalLayout>(
_controls.buttonsLayout)));
wrap->toggleOn(rpl::single(
rpl::empty
) | rpl::then(user->owner().botCommandsChanges(
) | rpl::filter(
rpl::mappers::_1 == _peer
) | rpl::to_empty) | rpl::map([=] {
const auto info = user->botInfo.get();
return info && info->verifierSettings;
}));
const auto inner = wrap->entity();
Ui::AddSkip(inner);
AddButtonWithCount(
inner,
tr::lng_manage_peer_bot_verify(),
rpl::never<QString>(),
[controller = _navigation->parentController(), user] {
controller->show(MakeVerifyPeersBox(controller, user));
},
{ &st::menuIconFactcheck });
Ui::AddSkip(inner);
Ui::AddDivider(inner);
}
void Controller::submitTitle() {
Expects(_controls.title != nullptr);
@@ -2656,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"
@@ -26,7 +26,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_user.h"
#include "data/stickers/data_custom_emoji.h"
#include "history/history.h"
#include "history/history_item_helpers.h" // GetErrorTextForSending.
#include "history/history_item_helpers.h" // GetErrorForSending.
#include "history/view/history_view_group_call_bar.h" // GenerateUserpics...
#include "lang/lang_keys.h"
#include "main/main_session.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) {
@@ -1492,29 +1494,18 @@ object_ptr<Ui::BoxContent> ShareInviteLinkBox(
return;
}
const auto error = [&] {
for (const auto thread : result) {
const auto error = GetErrorTextForSending(
thread,
{ .text = &comment });
if (!error.isEmpty()) {
return std::make_pair(error, thread);
}
}
return std::make_pair(QString(), result.front());
}();
if (!error.first.isEmpty()) {
auto text = TextWithEntities();
if (result.size() > 1) {
text.append(
Ui::Text::Bold(error.second->chatListName())
).append("\n\n");
}
text.append(error.first);
const auto errorWithThread = GetErrorForSending(
result,
{ .text = &comment });
if (errorWithThread.error) {
if (*box) {
(*box)->uiShow()->showBox(Ui::MakeInformBox(text));
(*box)->uiShow()->showBox(MakeSendErrorBox(
errorWithThread,
result.size() > 1));
}
return;
} else if (!checkPaid()) {
return;
}
*sending = true;
@@ -1542,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;
}
}
@@ -1551,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,15 @@ 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 context = Core::TextContext({
.session = session,
});
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, context);
if (state->unifiedFactoryOwner->lookupReactionId(id).custom()) {
return std::make_unique<MaybeDisabledEmoji>(
std::move(result),
@@ -376,12 +380,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 +493,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);
}

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