Merge tag 'v5.11.1' into dev

This commit is contained in:
AlexeyZavar 2025-02-14 00:23:48 +03:00
commit 6163cc8a5a
188 changed files with 4147 additions and 1082 deletions

35
.devcontainer.json Normal file
View file

@ -0,0 +1,35 @@
{
"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",
"cmake.configureSettings": {
"CMAKE_EXPORT_COMPILE_COMMANDS": "ON"
}
},
"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 fi
docker run --rm \ docker run --rm \
-u $(id -u) \
-v $PWD:/usr/src/tdesktop \ -v $PWD:/usr/src/tdesktop \
-e CONFIG=Debug \ -e CONFIG=Debug \
tdesktop:centos_env \ tdesktop:centos_env \
@ -114,8 +115,8 @@ jobs:
if: env.UPLOAD_ARTIFACT == 'true' if: env.UPLOAD_ARTIFACT == 'true'
run: | run: |
cd $REPO_NAME/out/Debug cd $REPO_NAME/out/Debug
sudo mkdir artifact mkdir artifact
sudo mv {Telegram,Updater} artifact/ mv {Telegram,Updater} artifact/
- uses: actions/upload-artifact@v4 - uses: actions/upload-artifact@v4
if: env.UPLOAD_ARTIFACT == 'true' if: env.UPLOAD_ARTIFACT == 'true'
name: Upload artifact. name: Upload artifact.

1
.gitignore vendored
View file

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

View file

@ -1041,6 +1041,7 @@ PRIVATE
info/global_media/info_global_media_inner_widget.h info/global_media/info_global_media_inner_widget.h
info/global_media/info_global_media_provider.cpp info/global_media/info_global_media_provider.cpp
info/global_media/info_global_media_provider.h info/global_media/info_global_media_provider.h
info/media/info_media_buttons.cpp
info/media/info_media_buttons.h info/media/info_media_buttons.h
info/media/info_media_common.cpp info/media/info_media_common.cpp
info/media/info_media_common.h info/media/info_media_common.h
@ -1285,6 +1286,8 @@ PRIVATE
media/streaming/media_streaming_video_track.h media/streaming/media_streaming_video_track.h
media/view/media_view_group_thumbs.cpp media/view/media_view_group_thumbs.cpp
media/view/media_view_group_thumbs.h 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.cpp
media/view/media_view_overlay_opengl.h media/view/media_view_overlay_opengl.h
media/view/media_view_overlay_raster.cpp media/view/media_view_overlay_raster.cpp
@ -1303,7 +1306,6 @@ PRIVATE
media/view/media_view_playback_controls.h media/view/media_view_playback_controls.h
media/view/media_view_playback_progress.cpp media/view/media_view_playback_progress.cpp
media/view/media_view_playback_progress.h 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.h
media/system_media_controls_manager.cpp media/system_media_controls_manager.cpp
menu/menu_antispam_validator.cpp menu/menu_antispam_validator.cpp
@ -1553,6 +1555,8 @@ PRIVATE
settings/settings_privacy_security.h settings/settings_privacy_security.h
settings/settings_scale_preview.cpp settings/settings_scale_preview.cpp
settings/settings_scale_preview.h settings/settings_scale_preview.h
settings/settings_shortcuts.cpp
settings/settings_shortcuts.h
settings/settings_type.h settings/settings_type.h
settings/settings_websites.cpp settings/settings_websites.cpp
settings/settings_websites.h settings/settings_websites.h

View file

@ -1,6 +1,7 @@
// This is a list of your own shortcuts for Telegram Desktop // This is a list of your own shortcuts for Telegram Desktop
// You can see full list of commands in the 'shortcuts-default.json' file // 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 // 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: 447 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 671 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1,004 B

View file

@ -642,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_quick_action_react" = "Send reaction with double click";
"lng_settings_chat_corner_reaction" = "Reaction button on messages"; "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_title" = "Quick Reaction";
"lng_settings_chat_reactions_subtitle" = "Choose your favorite reaction"; "lng_settings_chat_reactions_subtitle" = "Choose your favorite reaction";
"lng_settings_chat_message_reply_from" = "Bob Harris"; "lng_settings_chat_message_reply_from" = "Bob Harris";
@ -2551,6 +2591,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_premium_double_limits_subtitle_accounts" = "Connected Accounts"; "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#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_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"; "lng_premium_gift_title" = "Gift Telegram Premium";
@ -3000,9 +3044,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_boost_channel_ask" = "Ask your **Premium** subscribers to boost your channel with this link:"; "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_ask_button" = "Copy Link";
"lng_boost_channel_or" = "or"; //"lng_boost_channel_or" = "or";
"lng_boost_channel_gifting" = "Boost your channel by gifting your subscribers Telegram Premium. {link}"; //"lng_boost_channel_gifting" = "Boost your channel by gifting your subscribers Telegram Premium. {link}";
"lng_boost_channel_gifting_link" = "Get boosts >"; //"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#one" = "**{count}** Story Per Day";
"lng_feature_stories#other" = "**{count}** Stories Per Day"; "lng_feature_stories#other" = "**{count}** Stories Per Day";
@ -3326,6 +3372,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_gift_unique_backdrop" = "Backdrop"; "lng_gift_unique_backdrop" = "Backdrop";
"lng_gift_unique_symbol" = "Symbol"; "lng_gift_unique_symbol" = "Symbol";
"lng_gift_unique_rarity" = "Only {percent} of such collectibles have this attribute."; "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#one" = "{count} of {amount} issued";
"lng_gift_unique_availability#other" = "{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" = "Gifted to {recipient} on {date}.";
@ -3836,6 +3883,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_choose_image" = "Choose an image"; "lng_choose_image" = "Choose an image";
"lng_choose_file" = "Choose a file"; "lng_choose_file" = "Choose a file";
"lng_choose_files" = "Choose Files"; "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_game_tag" = "Game";
"lng_context_new_window" = "Open in new window"; "lng_context_new_window" = "Open in new window";
@ -3952,6 +4001,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_context_read_show" = "show when"; "lng_context_read_show" = "show when";
"lng_context_edit_shortcut" = "Edit Shortcut"; "lng_context_edit_shortcut" = "Edit Shortcut";
"lng_context_delete_shortcut" = "Delete Quick Reply"; "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_add_tag_about" = "Tag this message with an emoji for quick search.";
"lng_subscribe_tag_about" = "Organize your Saved Messages with tags. {link}"; "lng_subscribe_tag_about" = "Organize your Saved Messages with tags. {link}";
@ -3980,6 +4030,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_context_disable_spoiler" = "Remove Spoiler"; "lng_context_disable_spoiler" = "Remove Spoiler";
"lng_context_make_paid" = "Make This Content Paid"; "lng_context_make_paid" = "Make This Content Paid";
"lng_context_change_price" = "Change Price"; "lng_context_change_price" = "Change Price";
"lng_context_edit_cover" = "Edit Cover";
"lng_context_clear_cover" = "Clear Cover";
"lng_context_mention" = "Mention"; "lng_context_mention" = "Mention";
"lng_context_search_from" = "Search messages"; "lng_context_search_from" = "Search messages";
@ -4109,6 +4161,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_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_title" = "Share to";
"lng_share_at_time_title" = "Share at {time} to";
"lng_share_copy_link" = "Copy share link"; "lng_share_copy_link" = "Copy share link";
"lng_share_confirm" = "Send"; "lng_share_confirm" = "Send";
"lng_share_wrong_user" = "This game was opened from a different user."; "lng_share_wrong_user" = "This game was opened from a different user.";
@ -4232,7 +4285,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_media_save_progress" = "{ready} of {total} {mb}"; "lng_media_save_progress" = "{ready} of {total} {mb}";
"lng_mediaview_save_as" = "Save As..."; "lng_mediaview_save_as" = "Save As...";
"lng_mediaview_copy" = "Copy"; "lng_mediaview_copy" = "Copy";
"lng_mediaview_copy_frame" = "Copy Frame";
"lng_mediaview_forward" = "Forward"; "lng_mediaview_forward" = "Forward";
"lng_mediaview_share_at_time" = "Share at {time}";
"lng_mediaview_delete" = "Delete"; "lng_mediaview_delete" = "Delete";
"lng_mediaview_save_to_profile" = "Post to Profile"; "lng_mediaview_save_to_profile" = "Post to Profile";
"lng_mediaview_pin_story_done" = "Story pinned"; "lng_mediaview_pin_story_done" = "Story pinned";

View file

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

View file

@ -44,8 +44,8 @@ IDI_ICON1 ICON "..\\art\\icon256.ico"
// //
VS_VERSION_INFO VERSIONINFO VS_VERSION_INFO VERSIONINFO
FILEVERSION 5,10,7,0 FILEVERSION 5,11,1,0
PRODUCTVERSION 5,10,7,0 PRODUCTVERSION 5,11,1,0
FILEFLAGSMASK 0x3fL FILEFLAGSMASK 0x3fL
#ifdef _DEBUG #ifdef _DEBUG
FILEFLAGS 0x1L FILEFLAGS 0x1L
@ -62,10 +62,10 @@ BEGIN
BEGIN BEGIN
VALUE "CompanyName", "Radolyn Labs" VALUE "CompanyName", "Radolyn Labs"
VALUE "FileDescription", "AyuGram Desktop" VALUE "FileDescription", "AyuGram Desktop"
VALUE "FileVersion", "5.10.7.0" VALUE "FileVersion", "5.11.1.0"
VALUE "LegalCopyright", "Copyright (C) 2014-2025" VALUE "LegalCopyright", "Copyright (C) 2014-2025"
VALUE "ProductName", "AyuGram Desktop" VALUE "ProductName", "AyuGram Desktop"
VALUE "ProductVersion", "5.10.7.0" VALUE "ProductVersion", "5.11.1.0"
END END
END END
BLOCK "VarFileInfo" BLOCK "VarFileInfo"

View file

@ -35,8 +35,8 @@ LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
// //
VS_VERSION_INFO VERSIONINFO VS_VERSION_INFO VERSIONINFO
FILEVERSION 5,10,7,0 FILEVERSION 5,11,1,0
PRODUCTVERSION 5,10,7,0 PRODUCTVERSION 5,11,1,0
FILEFLAGSMASK 0x3fL FILEFLAGSMASK 0x3fL
#ifdef _DEBUG #ifdef _DEBUG
FILEFLAGS 0x1L FILEFLAGS 0x1L
@ -53,10 +53,10 @@ BEGIN
BEGIN BEGIN
VALUE "CompanyName", "Radolyn Labs" VALUE "CompanyName", "Radolyn Labs"
VALUE "FileDescription", "AyuGram Desktop Updater" VALUE "FileDescription", "AyuGram Desktop Updater"
VALUE "FileVersion", "5.10.7.0" VALUE "FileVersion", "5.11.1.0"
VALUE "LegalCopyright", "Copyright (C) 2014-2025" VALUE "LegalCopyright", "Copyright (C) 2014-2025"
VALUE "ProductName", "AyuGram Desktop" VALUE "ProductName", "AyuGram Desktop"
VALUE "ProductVersion", "5.10.7.0" VALUE "ProductVersion", "5.11.1.0"
END END
END END
BLOCK "VarFileInfo" BLOCK "VarFileInfo"

View file

@ -217,7 +217,11 @@ void Authorizations::toggleCallsDisabled(uint64 hash, bool disabled) {
MTP_bool(disabled) MTP_bool(disabled)
)).done([=] { )).done([=] {
_toggleCallsDisabledRequests.remove(hash); _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); _toggleCallsDisabledRequests.remove(hash);
}).send(); }).send();
_toggleCallsDisabledRequests.emplace(hash, id); _toggleCallsDisabledRequests.emplace(hash, id);

View file

@ -74,6 +74,7 @@ struct MessageToSend {
struct RemoteFileInfo { struct RemoteFileInfo {
MTPInputFile file; MTPInputFile file;
std::optional<MTPInputFile> thumb; std::optional<MTPInputFile> thumb;
std::optional<MTPInputPhoto> videoCover;
std::vector<MTPInputDocument> attachedStickers; std::vector<MTPInputDocument> attachedStickers;
}; };

View file

@ -276,16 +276,22 @@ mtpRequestId EditTextMessage(
takeFileReference = [=] { return photo->fileReference(); }; takeFileReference = [=] { return photo->fileReference(); };
} else if (const auto document = media->document()) { } else if (const auto document = media->document()) {
using Flag = MTPDinputMediaDocument::Flag; using Flag = MTPDinputMediaDocument::Flag;
const auto videoCover = media->videoCover();
const auto videoTimestamp = media->videoTimestamp();
const auto flags = Flag() const auto flags = Flag()
| (media->ttlSeconds() ? Flag::f_ttl_seconds : 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 = [=] { takeInputMedia = [=] {
return MTP_inputMediaDocument( return MTP_inputMediaDocument(
MTP_flags(flags), MTP_flags(flags),
document->mtpInput(), document->mtpInput(),
MTPInputPhoto(), // video_cover (videoCover
? videoCover->mtpInput()
: MTPInputPhoto()),
MTP_int(media->ttlSeconds()), MTP_int(media->ttlSeconds()),
MTPint(), // video_timestamp MTP_int(videoTimestamp),
MTPstring()); // query MTPstring()); // query
}; };
takeFileReference = [=] { return document->fileReference(); }; takeFileReference = [=] { return document->fileReference(); };

View file

@ -13,6 +13,32 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
namespace Api { 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) GlobalPrivacy::GlobalPrivacy(not_null<ApiWrap*> api)
: _session(&api->session()) : _session(&api->session())
, _api(&api->instance()) { , _api(&api->instance()) {
@ -115,27 +141,27 @@ rpl::producer<bool> GlobalPrivacy::newRequirePremium() const {
return _newRequirePremium.value(); return _newRequirePremium.value();
} }
void GlobalPrivacy::loadPaidReactionAnonymous() { void GlobalPrivacy::loadPaidReactionShownPeer() {
if (_paidReactionAnonymousLoaded) { if (_paidReactionShownPeerLoaded) {
return; return;
} }
_paidReactionAnonymousLoaded = true; _paidReactionShownPeerLoaded = true;
_api.request(MTPmessages_GetPaidReactionPrivacy( _api.request(MTPmessages_GetPaidReactionPrivacy(
)).done([=](const MTPUpdates &result) { )).done([=](const MTPUpdates &result) {
_session->api().applyUpdates(result); _session->api().applyUpdates(result);
}).send(); }).send();
} }
void GlobalPrivacy::updatePaidReactionAnonymous(bool value) { void GlobalPrivacy::updatePaidReactionShownPeer(PeerId shownPeer) {
_paidReactionAnonymous = value; _paidReactionShownPeer = shownPeer;
} }
bool GlobalPrivacy::paidReactionAnonymousCurrent() const { PeerId GlobalPrivacy::paidReactionShownPeerCurrent() const {
return _paidReactionAnonymous.current(); return _paidReactionShownPeer.current();
} }
rpl::producer<bool> GlobalPrivacy::paidReactionAnonymous() const { rpl::producer<PeerId> GlobalPrivacy::paidReactionShownPeer() const {
return _paidReactionAnonymous.value(); return _paidReactionShownPeer.value();
} }
void GlobalPrivacy::updateArchiveAndMute(bool value) { void GlobalPrivacy::updateArchiveAndMute(bool value) {

View file

@ -23,6 +23,10 @@ enum class UnarchiveOnNewMessage {
AnyUnmuted, AnyUnmuted,
}; };
[[nodiscard]] PeerId ParsePaidReactionShownPeer(
not_null<Main::Session*> session,
const MTPPaidReactionPrivacy &value);
class GlobalPrivacy final { class GlobalPrivacy final {
public: public:
explicit GlobalPrivacy(not_null<ApiWrap*> api); explicit GlobalPrivacy(not_null<ApiWrap*> api);
@ -49,10 +53,10 @@ public:
[[nodiscard]] bool newRequirePremiumCurrent() const; [[nodiscard]] bool newRequirePremiumCurrent() const;
[[nodiscard]] rpl::producer<bool> newRequirePremium() const; [[nodiscard]] rpl::producer<bool> newRequirePremium() const;
void loadPaidReactionAnonymous(); void loadPaidReactionShownPeer();
void updatePaidReactionAnonymous(bool value); void updatePaidReactionShownPeer(PeerId shownPeer);
[[nodiscard]] bool paidReactionAnonymousCurrent() const; [[nodiscard]] PeerId paidReactionShownPeerCurrent() const;
[[nodiscard]] rpl::producer<bool> paidReactionAnonymous() const; [[nodiscard]] rpl::producer<PeerId> paidReactionShownPeer() const;
private: private:
void apply(const MTPGlobalPrivacySettings &data); void apply(const MTPGlobalPrivacySettings &data);
@ -72,9 +76,9 @@ private:
rpl::variable<bool> _showArchiveAndMute = false; rpl::variable<bool> _showArchiveAndMute = false;
rpl::variable<bool> _hideReadTime = false; rpl::variable<bool> _hideReadTime = false;
rpl::variable<bool> _newRequirePremium = false; rpl::variable<bool> _newRequirePremium = false;
rpl::variable<bool> _paidReactionAnonymous = false; rpl::variable<PeerId> _paidReactionShownPeer = false;
std::vector<Fn<void()>> _callbacks; 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()) | (info.thumb ? Flag::f_thumb : Flag())
| (item->groupId() ? Flag::f_nosound_video : Flag()) | (item->groupId() ? Flag::f_nosound_video : Flag())
| (info.attachedStickers.empty() ? Flag::f_stickers : 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(); const auto document = item->media()->document();
return MTP_inputMediaUploadedDocument( return MTP_inputMediaUploadedDocument(
MTP_flags(flags), MTP_flags(flags),
@ -121,7 +122,7 @@ MTPInputMedia PrepareUploadedDocument(
ComposeSendingDocumentAttributes(document), ComposeSendingDocumentAttributes(document),
MTP_vector<MTPInputDocument>( MTP_vector<MTPInputDocument>(
ranges::to<QVector<MTPInputDocument>>(info.attachedStickers)), ranges::to<QVector<MTPInputDocument>>(info.attachedStickers)),
MTPInputPhoto(), // video_cover info.videoCover.value_or(MTPInputPhoto()),
MTP_int(0), // video_timestamp MTP_int(0), // video_timestamp
MTP_int(ttlSeconds)); MTP_int(ttlSeconds));
} }

View file

@ -549,10 +549,11 @@ void SendConfirmedFile(
using Flag = MTPDmessageMediaDocument::Flag; using Flag = MTPDmessageMediaDocument::Flag;
return MTP_messageMediaDocument( return MTP_messageMediaDocument(
MTP_flags(Flag::f_document 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, file->document,
MTPVector<MTPDocument>(), // alt_documents MTPVector<MTPDocument>(), // alt_documents
MTPPhoto(), // video_cover file->videoCover ? file->videoCover->photo : MTPPhoto(),
MTPint(), // video_timestamp MTPint(), // video_timestamp
MTPint()); MTPint());
} else if (file->type == SendMediaType::Audio) { } else if (file->type == SendMediaType::Audio) {
@ -561,10 +562,11 @@ void SendConfirmedFile(
return MTP_messageMediaDocument( return MTP_messageMediaDocument(
MTP_flags(Flag::f_document MTP_flags(Flag::f_document
| Flag::f_voice | Flag::f_voice
| (ttlSeconds ? Flag::f_ttl_seconds : Flag())), | (ttlSeconds ? Flag::f_ttl_seconds : Flag())
| (file->videoCover ? Flag::f_video_cover : Flag())),
file->document, file->document,
MTPVector<MTPDocument>(), // alt_documents MTPVector<MTPDocument>(), // alt_documents
MTPPhoto(), // video_cover file->videoCover ? file->videoCover->photo : MTPPhoto(),
MTPint(), // video_timestamp MTPint(), // video_timestamp
MTP_int(ttlSeconds)); MTP_int(ttlSeconds));
} else if (file->type == SendMediaType::Round) { } else if (file->type == SendMediaType::Round) {

View file

@ -2717,8 +2717,8 @@ void Updates::feedUpdate(const MTPUpdate &update) {
case mtpc_updatePaidReactionPrivacy: { case mtpc_updatePaidReactionPrivacy: {
const auto &data = update.c_updatePaidReactionPrivacy(); const auto &data = update.c_updatePaidReactionPrivacy();
_session->api().globalPrivacy().updatePaidReactionAnonymous( _session->api().globalPrivacy().updatePaidReactionShownPeer(
mtpIsTrue(data.vprivate())); Api::ParsePaidReactionShownPeer(_session, data.vprivate()));
} break; } break;
} }

View file

@ -148,6 +148,16 @@ void ShowChannelsLimitBox(not_null<PeerData*> peer) {
action.replaceMediaOf); 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 } // namespace
ApiWrap::ApiWrap(not_null<Main::Session*> session) ApiWrap::ApiWrap(not_null<Main::Session*> session)
@ -740,7 +750,8 @@ void ApiWrap::finalizeMessageDataRequest(
QString ApiWrap::exportDirectMessageLink( QString ApiWrap::exportDirectMessageLink(
not_null<HistoryItem*> item, not_null<HistoryItem*> item,
bool inRepliesContext, bool inRepliesContext,
bool forceNonPublicLink) { bool forceNonPublicLink,
std::optional<TimeId> videoTimestamp) {
Expects(item->history()->peer->isChannel()); Expects(item->history()->peer->isChannel());
const auto itemId = item->fullId(); const auto itemId = item->fullId();
@ -792,19 +803,6 @@ QString ApiWrap::exportDirectMessageLink(
: linkThreadId : linkThreadId
? (QString::number(linkThreadId.bare) + '/' + post) ? (QString::number(linkThreadId.bare) + '/' + post)
: 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); return session().createInternalLinkFull(query);
}; };
if (forceNonPublicLink) { if (forceNonPublicLink) {
@ -826,7 +824,14 @@ QString ApiWrap::exportDirectMessageLink(
_unlikelyMessageLinks.emplace_or_assign(itemId, link); _unlikelyMessageLinks.emplace_or_assign(itemId, link);
} }
}).send(); }).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) { QString ApiWrap::exportDirectStoryLink(not_null<Data::Story*> story) {
@ -3624,6 +3629,18 @@ void ApiWrap::editMedia(
file.path, file.path,
file.content, file.content,
std::move(file.information), 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, type,
to, to,
caption, caption,
@ -3665,6 +3682,19 @@ void ApiWrap::sendFiles(
file.path, file.path,
file.content, file.content,
std::move(file.information), 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, uploadWithType,
to, to,
caption, caption,
@ -3690,11 +3720,13 @@ void ApiWrap::sendFile(
auto caption = TextWithTags(); auto caption = TextWithTags();
const auto spoiler = false; const auto spoiler = false;
const auto information = nullptr; const auto information = nullptr;
const auto videoCover = nullptr;
_fileLoader->addTask(std::make_unique<FileLoadTask>( _fileLoader->addTask(std::make_unique<FileLoadTask>(
&session(), &session(),
QString(), QString(),
fileContent, fileContent,
information, information,
videoCover,
type, type,
to, to,
caption, caption,
@ -4209,19 +4241,30 @@ void ApiWrap::uploadAlbumMedia(
return; return;
} }
const auto &fields = document->c_document(); 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; using Flag = MTPDinputMediaDocument::Flag;
const auto flags = Flag() const auto flags = Flag()
| (data.vttl_seconds() ? Flag::f_ttl_seconds : 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( const auto media = MTP_inputMediaDocument(
MTP_flags(flags), MTP_flags(flags),
MTP_inputDocument( MTP_inputDocument(
fields.vid(), fields.vid(),
fields.vaccess_hash(), fields.vaccess_hash(),
fields.vfile_reference()), fields.vfile_reference()),
MTPInputPhoto(), // video_cover (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()), MTP_int(data.vttl_seconds().value_or_empty()),
MTPint(), // video_timestamp
MTPstring()); // query MTPstring()); // query
sendAlbumWithUploaded(item, groupId, media); sendAlbumWithUploaded(item, groupId, media);
} break; } break;

View file

@ -166,7 +166,8 @@ public:
QString exportDirectMessageLink( QString exportDirectMessageLink(
not_null<HistoryItem*> item, not_null<HistoryItem*> item,
bool inRepliesContext, bool inRepliesContext,
bool forceNonPublicLink = false); bool forceNonPublicLink = false,
std::optional<TimeId> videoTimestamp = {});
QString exportDirectStoryLink(not_null<Data::Story*> item); QString exportDirectStoryLink(not_null<Data::Story*> item);
void requestContacts(); void requestContacts();

View file

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

View file

@ -44,6 +44,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/basic_click_handlers.h" // UrlClickHandler::Open. #include "ui/basic_click_handlers.h" // UrlClickHandler::Open.
#include "ui/boxes/boost_box.h" // StartFireworks. #include "ui/boxes/boost_box.h" // StartFireworks.
#include "ui/controls/userpic_button.h" #include "ui/controls/userpic_button.h"
#include "ui/effects/credits_graphics.h"
#include "ui/effects/premium_graphics.h" #include "ui/effects/premium_graphics.h"
#include "ui/effects/premium_stars_colored.h" #include "ui/effects/premium_stars_colored.h"
#include "ui/effects/premium_top_bar.h" #include "ui/effects/premium_top_bar.h"
@ -405,21 +406,14 @@ void AddTableRow(
auto result = object_ptr<Ui::RpWidget>(table); auto result = object_ptr<Ui::RpWidget>(table);
const auto raw = result.data(); const auto raw = result.data();
const auto session = &show->session(); const auto star = Ui::CreateSingleStarWidget(
const auto makeContext = [session](Fn<void()> update) { raw,
return Core::MarkedTextContext{ table->st().defaultValue.style.font->height);
.session = session,
.customEmojiRepaint = std::move(update),
};
};
auto star = session->data().customEmojiManager().creditsEmoji();
const auto label = Ui::CreateChild<Ui::FlatLabel>( const auto label = Ui::CreateChild<Ui::FlatLabel>(
raw, raw,
rpl::single(star.append( Lang::FormatStarsAmountDecimal(entry.credits),
' ' + Lang::FormatStarsAmountDecimal(entry.credits))),
table->st().defaultValue, table->st().defaultValue,
st::defaultPopupMenu, st::defaultPopupMenu);
std::move(makeContext));
const auto convert = convertToStars const auto convert = convertToStars
? Ui::CreateChild<Ui::RoundButton>( ? Ui::CreateChild<Ui::RoundButton>(
@ -430,7 +424,8 @@ void AddTableRow(
table->st().smallButton) table->st().smallButton)
: nullptr; : nullptr;
if (convert) { if (convert) {
convert->setTextTransform(Ui::RoundButton::TextTransform::NoTransform); using namespace Ui;
convert->setTextTransform(RoundButton::TextTransform::NoTransform);
convert->setClickedCallback(std::move(convertToStars)); convert->setClickedCallback(std::move(convertToStars));
} }
rpl::combine( rpl::combine(
@ -440,11 +435,13 @@ void AddTableRow(
const auto convertSkip = convertWidth const auto convertSkip = convertWidth
? (st::normalFont->spacew + convertWidth) ? (st::normalFont->spacew + convertWidth)
: 0; : 0;
label->resizeToNaturalWidth(width - convertSkip); const auto labelLeft = rect::right(star) + st::normalFont->spacew;
label->moveToLeft(0, 0, width); label->resizeToNaturalWidth(width - convertSkip - labelLeft);
star->moveToLeft(0, 0, width);
label->moveToLeft(labelLeft, 0, width);
if (convert) { if (convert) {
convert->moveToLeft( convert->moveToLeft(
label->width() + st::normalFont->spacew, rect::right(label) + st::normalFont->spacew,
(table->st().defaultValue.style.font->ascent (table->st().defaultValue.style.font->ascent
- table->st().smallButton.style.font->ascent), - table->st().smallButton.style.font->ascent),
width); width);
@ -1490,10 +1487,15 @@ void AddStarGiftTable(
auto amount = rpl::single(TextWithEntities{ auto amount = rpl::single(TextWithEntities{
Lang::FormatCountDecimal(entry.limitedCount) Lang::FormatCountDecimal(entry.limitedCount)
}); });
const auto count = unique
? (entry.limitedCount - entry.limitedLeft)
: entry.limitedLeft;
AddTableRow( AddTableRow(
table, table,
tr::lng_gift_availability(), (unique
((!unique && !entry.limitedLeft) ? tr::lng_gift_unique_availability_label()
: tr::lng_gift_availability()),
((!unique && !count)
? tr::lng_gift_availability_none( ? tr::lng_gift_availability_none(
lt_amount, lt_amount,
std::move(amount), std::move(amount),
@ -1502,12 +1504,12 @@ void AddStarGiftTable(
? tr::lng_gift_unique_availability ? tr::lng_gift_unique_availability
: tr::lng_gift_availability_left)( : tr::lng_gift_availability_left)(
lt_count_decimal, lt_count_decimal,
rpl::single(entry.limitedLeft * 1.), rpl::single(count * 1.),
lt_amount, lt_amount,
std::move(amount), std::move(amount),
Ui::Text::WithEntities))); Ui::Text::WithEntities)));
} }
if (!unique && !entry.soldOutInfo) { if (!unique && !entry.soldOutInfo && startUpgrade) {
AddTableRow( AddTableRow(
table, table,
tr::lng_gift_unique_status(), tr::lng_gift_unique_status(),

View file

@ -922,7 +922,13 @@ void PeerListRow::paintDisabledCheckUserpic(
p.setPen(userpicBorderPen); p.setPen(userpicBorderPen);
p.setBrush(Qt::NoBrush); 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.setPen(iconBorderPen);
p.setBrush(st.disabledCheckFg); p.setBrush(st.disabledCheckFg);

View file

@ -317,6 +317,7 @@ PreviewWrap::PreviewWrap(
0, // duration 0, // duration
QString(), // author QString(), // author
false, // hasLargeMedia false, // hasLargeMedia
false, // photoIsVideoCover
0)) // pendingTill 0)) // pendingTill
, _theme(theme) , _theme(theme)
, _style(style) , _style(style)

View file

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

View file

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

View file

@ -1526,6 +1526,18 @@ void DoubledLimitsPreviewBox(
Main::Domain::kPremiumMaxAccounts, Main::Domain::kPremiumMaxAccounts,
till, till,
}); });
{
const auto premium = limits.similarChannelsPremium();
entries.push_back({
tr::lng_premium_double_limits_subtitle_similar_channels(),
tr::lng_premium_double_limits_about_similar_channels(
lt_count,
rpl::single(float64(premium)),
Ui::Text::RichLangValue),
limits.similarChannelsDefault(),
premium,
});
}
Ui::Premium::ShowListBox( Ui::Premium::ShowListBox(
box, box,
st::defaultPremiumLimits, st::defaultPremiumLimits,

View file

@ -250,7 +250,7 @@ SendFilesBox::Block::Block(
int till, int till,
Fn<bool()> gifPaused, Fn<bool()> gifPaused,
SendFilesWay way, SendFilesWay way,
Fn<bool()> canToggleSpoiler) Fn<bool(const Ui::PreparedFile &, Ui::AttachActionType)> actionAllowed)
: _items(items) : _items(items)
, _from(from) , _from(from)
, _till(till) { , _till(till) {
@ -268,7 +268,9 @@ SendFilesBox::Block::Block(
st, st,
my, my,
way, way,
std::move(canToggleSpoiler)); [=](int index, Ui::AttachActionType type) {
return actionAllowed((*_items)[from + index], type);
});
_preview.reset(preview); _preview.reset(preview);
} else { } else {
const auto media = Ui::SingleMediaPreview::Create( const auto media = Ui::SingleMediaPreview::Create(
@ -276,7 +278,9 @@ SendFilesBox::Block::Block(
st, st,
gifPaused, gifPaused,
first, first,
std::move(canToggleSpoiler)); [=](Ui::AttachActionType type) {
return actionAllowed((*_items)[from], type);
});
if (media) { if (media) {
_isSingleMedia = true; _isSingleMedia = true;
_preview.reset(media); _preview.reset(media);
@ -352,6 +356,38 @@ rpl::producer<int> SendFilesBox::Block::itemModifyRequest() const {
} }
} }
rpl::producer<int> SendFilesBox::Block::itemEditCoverRequest() const {
using namespace rpl::mappers;
const auto preview = _preview.get();
const auto from = _from;
if (_isAlbum) {
const auto album = static_cast<Ui::AlbumPreview*>(preview);
return album->thumbEditCoverRequested() | rpl::map(_1 + from);
} else if (_isSingleMedia) {
const auto media = static_cast<Ui::SingleMediaPreview*>(preview);
return media->editCoverRequests() | rpl::map_to(from);
} else {
return rpl::never<int>();
}
}
rpl::producer<int> SendFilesBox::Block::itemClearCoverRequest() const {
using namespace rpl::mappers;
const auto preview = _preview.get();
const auto from = _from;
if (_isAlbum) {
const auto album = static_cast<Ui::AlbumPreview*>(preview);
return album->thumbClearCoverRequested() | rpl::map(_1 + from);
} else if (_isSingleMedia) {
const auto media = static_cast<Ui::SingleMediaPreview*>(preview);
return media->clearCoverRequests() | rpl::map_to(from);
} else {
return rpl::never<int>();
}
}
rpl::producer<> SendFilesBox::Block::orderUpdated() const { rpl::producer<> SendFilesBox::Block::orderUpdated() const {
if (_isAlbum) { if (_isAlbum) {
const auto album = static_cast<Ui::AlbumPreview*>(_preview.get()); const auto album = static_cast<Ui::AlbumPreview*>(_preview.get());
@ -1046,7 +1082,16 @@ void SendFilesBox::pushBlock(int from, int till) {
till, till,
gifPaused, gifPaused,
_sendWay.current(), _sendWay.current(),
[=] { return !hasPrice(); }); [=](const Ui::PreparedFile &file, Ui::AttachActionType type) {
return (type == Ui::AttachActionType::ToggleSpoiler)
? !hasPrice()
: (type == Ui::AttachActionType::EditCover)
? (file.isVideoFile()
&& _captionToPeer
&& (_captionToPeer->isBroadcast()
|| _captionToPeer->isSelf()))
: (file.videoCover != nullptr);
});
auto &block = _blocks.back(); auto &block = _blocks.back();
const auto widget = _inner->add( const auto widget = _inner->add(
block.takeWidget(), block.takeWidget(),
@ -1167,7 +1212,79 @@ void SendFilesBox::pushBlock(int from, int till) {
show, show,
&_list.files[index], &_list.files[index],
st::sendMediaPreviewSize, st::sendMediaPreviewSize,
[=] { refreshAllAfterChanges(from); }); [=](bool ok) { if (ok) refreshAllAfterChanges(from); });
}, widget->lifetime());
block.itemEditCoverRequest(
) | rpl::start_with_next([=, show = _show](int index) {
applyBlockChanges();
const auto replace = [=](Ui::PreparedList list) {
if (list.files.empty()) {
return;
}
auto &entry = _list.files[index];
const auto video = entry.information
? std::get_if<Ui::PreparedFileInformation::Video>(
&entry.information->media)
: nullptr;
if (!video) {
return;
}
auto old = std::shared_ptr<Ui::PreparedFile>(
std::move(entry.videoCover));
entry.videoCover = std::make_unique<Ui::PreparedFile>(
std::move(list.files.front()));
Editor::OpenWithPreparedFile(
this,
show,
entry.videoCover.get(),
st::sendMediaPreviewSize,
crl::guard(this, [=](bool ok) {
if (!ok) {
_list.files[index].videoCover = old
? std::make_unique<Ui::PreparedFile>(
std::move(*old))
: nullptr;
}
refreshAllAfterChanges(from);
}),
video->thumbnail.size());
};
const auto checkResult = [=](const Ui::PreparedList &list) {
if (list.files.empty()) {
return true;
}
if (list.files.front().type != Ui::PreparedFile::Type::Photo) {
show->showToast(tr::lng_choose_cover_bad(tr::now));
return false;
}
return true;
};
const auto callback = [=](FileDialog::OpenResult &&result) {
const auto premium = _show->session().premium();
FileDialogCallback(
std::move(result),
checkResult,
replace,
premium,
show);
};
FileDialog::GetOpenPath(
this,
tr::lng_choose_cover(tr::now),
FileDialog::ImagesFilter(),
crl::guard(this, callback));
}, widget->lifetime());
block.itemClearCoverRequest(
) | rpl::start_with_next([=](int index) {
applyBlockChanges();
refreshAllAfterChanges(from, [&] {
auto &entry = _list.files[index];
entry.videoCover = nullptr;
});
}, widget->lifetime()); }, widget->lifetime());
block.orderUpdated() | rpl::start_with_next([=]{ block.orderUpdated() | rpl::start_with_next([=]{

View file

@ -153,7 +153,9 @@ private:
int till, int till,
Fn<bool()> gifPaused, Fn<bool()> gifPaused,
Ui::SendFilesWay way, Ui::SendFilesWay way,
Fn<bool()> canToggleSpoiler); Fn<bool(
const Ui::PreparedFile &,
Ui::AttachActionType)> actionAllowed);
Block(Block &&other) = default; Block(Block &&other) = default;
Block &operator=(Block &&other) = default; Block &operator=(Block &&other) = default;
@ -164,6 +166,8 @@ private:
[[nodiscard]] rpl::producer<int> itemDeleteRequest() const; [[nodiscard]] rpl::producer<int> itemDeleteRequest() const;
[[nodiscard]] rpl::producer<int> itemReplaceRequest() const; [[nodiscard]] rpl::producer<int> itemReplaceRequest() const;
[[nodiscard]] rpl::producer<int> itemModifyRequest() const; [[nodiscard]] rpl::producer<int> itemModifyRequest() const;
[[nodiscard]] rpl::producer<int> itemEditCoverRequest() const;
[[nodiscard]] rpl::producer<int> itemClearCoverRequest() const;
[[nodiscard]] rpl::producer<> orderUpdated() const; [[nodiscard]] rpl::producer<> orderUpdated() const;
void setSendWay(Ui::SendFilesWay way); void setSendWay(Ui::SendFilesWay way);

View file

@ -282,7 +282,9 @@ void ShareBox::prepare() {
_select->resizeToWidth(st::boxWideWidth); _select->resizeToWidth(st::boxWideWidth);
Ui::SendPendingMoveResizeEvents(_select); Ui::SendPendingMoveResizeEvents(_select);
setTitle(tr::lng_share_title()); setTitle(_descriptor.titleOverride
? std::move(_descriptor.titleOverride)
: tr::lng_share_title());
_inner = setInnerWidget( _inner = setInnerWidget(
object_ptr<Inner>(this, _descriptor, uiShow()), object_ptr<Inner>(this, _descriptor, uiShow()),
@ -1500,7 +1502,8 @@ ChatHelpers::ForwardedMessagePhraseArgs CreateForwardedMessagePhraseArgs(
ShareBox::SubmitCallback ShareBox::DefaultForwardCallback( ShareBox::SubmitCallback ShareBox::DefaultForwardCallback(
std::shared_ptr<Ui::Show> show, std::shared_ptr<Ui::Show> show,
not_null<History*> history, not_null<History*> history,
MessageIdsList msgIds) { MessageIdsList msgIds,
std::optional<TimeId> videoTimestamp) {
struct State final { struct State final {
base::flat_set<mtpRequestId> requests; base::flat_set<mtpRequestId> requests;
}; };
@ -1536,6 +1539,9 @@ ShareBox::SubmitCallback ShareBox::DefaultForwardCallback(
: Flag(0)) : Flag(0))
| ((forwardOptions == Data::ForwardOptions::NoNamesAndCaptions) | ((forwardOptions == Data::ForwardOptions::NoNamesAndCaptions)
? Flag::f_drop_media_captions ? Flag::f_drop_media_captions
: Flag(0))
| (videoTimestamp.has_value()
? Flag::f_video_timestamp
: Flag(0)); : Flag(0));
auto mtpMsgIds = QVector<MTPint>(); auto mtpMsgIds = QVector<MTPint>();
mtpMsgIds.reserve(existingIds.size()); mtpMsgIds.reserve(existingIds.size());
@ -1593,7 +1599,7 @@ ShareBox::SubmitCallback ShareBox::DefaultForwardCallback(
MTP_int(options.scheduled), MTP_int(options.scheduled),
MTP_inputPeerEmpty(), // send_as MTP_inputPeerEmpty(), // send_as
Data::ShortcutIdToMTP(session, options.shortcutId), Data::ShortcutIdToMTP(session, options.shortcutId),
MTPint() // video_timestamp MTP_int(videoTimestamp.value_or(0))
)).done([=](const MTPUpdates &updates, mtpRequestId reqId) { )).done([=](const MTPUpdates &updates, mtpRequestId reqId) {
threadHistory->session().api().applyUpdates(updates); threadHistory->session().api().applyUpdates(updates);
state->requests.remove(reqId); state->requests.remove(reqId);

View file

@ -104,7 +104,8 @@ public:
[[nodiscard]] static SubmitCallback DefaultForwardCallback( [[nodiscard]] static SubmitCallback DefaultForwardCallback(
std::shared_ptr<Ui::Show> show, std::shared_ptr<Ui::Show> show,
not_null<History*> history, not_null<History*> history,
MessageIdsList msgIds); MessageIdsList msgIds,
std::optional<TimeId> videoTimestamp = {});
struct Descriptor { struct Descriptor {
not_null<Main::Session*> session; not_null<Main::Session*> session;
@ -113,7 +114,9 @@ public:
FilterCallback filterCallback; FilterCallback filterCallback;
object_ptr<Ui::RpWidget> bottomWidget = { nullptr }; object_ptr<Ui::RpWidget> bottomWidget = { nullptr };
rpl::producer<QString> copyLinkText; rpl::producer<QString> copyLinkText;
rpl::producer<QString> titleOverride;
ShareBoxStyleOverrides st; ShareBoxStyleOverrides st;
std::optional<TimeId> videoTimestamp;
struct { struct {
int sendersCount = 0; int sendersCount = 0;
int captionsCount = 0; int captionsCount = 0;

View file

@ -99,8 +99,8 @@ namespace Ui {
namespace { namespace {
constexpr auto kPriceTabAll = 0; constexpr auto kPriceTabAll = 0;
constexpr auto kPriceTabLimited = -1; constexpr auto kPriceTabLimited = -2;
constexpr auto kPriceTabInStock = -2; constexpr auto kPriceTabInStock = -1;
constexpr auto kGiftMessageLimit = 255; constexpr auto kGiftMessageLimit = 255;
constexpr auto kSentToastDuration = 3 * crl::time(1000); constexpr auto kSentToastDuration = 3 * crl::time(1000);
constexpr auto kSwitchUpgradeCoverInterval = 3 * crl::time(1000); constexpr auto kSwitchUpgradeCoverInterval = 3 * crl::time(1000);
@ -2371,7 +2371,7 @@ void ShowUniqueGiftWearBox(
object_ptr<Ui::FlatLabel>( object_ptr<Ui::FlatLabel>(
raw, raw,
std::move(text), std::move(text),
st.infoAbout ? *st.infoAbout : st::boxDividerLabel), st.infoAbout ? *st.infoAbout : st::upgradeGiftSubtext),
st::settingsPremiumRowAboutPadding); st::settingsPremiumRowAboutPadding);
object_ptr<Info::Profile::FloatingIcon>( object_ptr<Info::Profile::FloatingIcon>(
raw, raw,
@ -2603,7 +2603,7 @@ void UpgradeBox(
object_ptr<Ui::FlatLabel>( object_ptr<Ui::FlatLabel>(
raw, raw,
std::move(text), std::move(text),
st::boxDividerLabel), st::upgradeGiftSubtext),
st::settingsPremiumRowAboutPadding); st::settingsPremiumRowAboutPadding);
object_ptr<Info::Profile::FloatingIcon>( object_ptr<Info::Profile::FloatingIcon>(
raw, raw,

View file

@ -578,8 +578,7 @@ void Instance::handleCallUpdate(
if (inCall() if (inCall()
&& _currentCall->type() == Call::Type::Outgoing && _currentCall->type() == Call::Type::Outgoing
&& _currentCall->user()->id == session->userPeerId() && _currentCall->user()->id == session->userPeerId()
&& (peerFromUser(phoneCall.vparticipant_id()) && (user->id == _currentCall->user()->session().userPeerId())) {
== _currentCall->user()->session().userPeerId())) {
// Ignore call from the same running app, other account. // Ignore call from the same running app, other account.
return; return;
} }

View file

@ -42,6 +42,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/emoji_config.h" #include "ui/emoji_config.h"
#include "ui/painter.h" #include "ui/painter.h"
#include "ui/rect.h" #include "ui/rect.h"
#include "ui/integration.h"
#include "core/application.h" #include "core/application.h"
#include "lang/lang_keys.h" #include "lang/lang_keys.h"
#include "main/main_session.h" #include "main/main_session.h"
@ -65,6 +66,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
namespace Calls { namespace Calls {
namespace { namespace {
constexpr auto kHideControlsTimeout = 5 * crl::time(1000);
constexpr auto kHideControlsQuickTimeout = 2 * crl::time(1000);
[[nodiscard]] QByteArray BatterySvg( [[nodiscard]] QByteArray BatterySvg(
const QSize &s, const QSize &s,
const QColor &c) { const QColor &c) {
@ -118,7 +122,9 @@ Panel::Panel(not_null<Call*> call)
st::callMicrophoneMute, st::callMicrophoneMute,
&st::callMicrophoneUnmute)) &st::callMicrophoneUnmute))
, _name(widget(), st::callName) , _name(widget(), st::callName)
, _status(widget(), st::callStatus) { , _status(widget(), st::callStatus)
, _hideControlsTimer([=] { requestControlsHidden(true); })
, _controlsShownForceTimer([=] { controlsShownForce(false); }) {
_layerBg->setStyleOverrides(&st::groupCallBox, &st::groupCallLayerBox); _layerBg->setStyleOverrides(&st::groupCallBox, &st::groupCallLayerBox);
_layerBg->setHideByBackgroundClick(true); _layerBg->setHideByBackgroundClick(true);
@ -188,6 +194,25 @@ void Panel::initWindow() {
&& window()->isFullScreen()) { && window()->isFullScreen()) {
window()->showNormal(); window()->showNormal();
} }
} else if (e->type() == QEvent::WindowStateChange) {
const auto state = window()->windowState();
_fullScreenOrMaximized = (state & Qt::WindowFullScreen)
|| (state & Qt::WindowMaximized);
} else if (e->type() == QEvent::Enter) {
_mouseInside = true;
Ui::Integration::Instance().registerLeaveSubscription(
window().get());
if (!_fullScreenOrMaximized.current()) {
requestControlsHidden(false);
_hideControlsTimer.cancel();
}
} else if (e->type() == QEvent::Leave) {
_mouseInside = false;
Ui::Integration::Instance().unregisterLeaveSubscription(
window().get());
if (!_fullScreenOrMaximized.current()) {
_hideControlsTimer.callOnce(kHideControlsQuickTimeout);
}
} }
return base::EventFilterResult::Continue; return base::EventFilterResult::Continue;
}); });
@ -415,7 +440,11 @@ void Panel::refreshIncomingGeometry() {
void Panel::reinitWithCall(Call *call) { void Panel::reinitWithCall(Call *call) {
_callLifetime.destroy(); _callLifetime.destroy();
_call = call; _call = call;
const auto guard = gsl::finally([&] {
updateControlsShown();
});
if (!_call) { if (!_call) {
_fingerprint.destroy();
_incoming = nullptr; _incoming = nullptr;
_outgoingVideoBubble = nullptr; _outgoingVideoBubble = nullptr;
_powerSaveBlocker = nullptr; _powerSaveBlocker = nullptr;
@ -457,6 +486,51 @@ void Panel::reinitWithCall(Call *call) {
_window.backend()); _window.backend());
_incoming->widget()->hide(); _incoming->widget()->hide();
_incoming->rp()->shownValue() | rpl::start_with_next([=] {
updateControlsShown();
}, _incoming->rp()->lifetime());
_hideControlsFilter = nullptr;
_fullScreenOrMaximized.value(
) | rpl::start_with_next([=](bool fullScreenOrMaximized) {
if (fullScreenOrMaximized) {
class Filter final : public QObject {
public:
explicit Filter(Fn<void(QObject*)> moved) : _moved(moved) {
qApp->installEventFilter(this);
}
bool eventFilter(QObject *watched, QEvent *event) {
if (event->type() == QEvent::MouseMove) {
_moved(watched);
}
return false;
}
private:
Fn<void(QObject*)> _moved;
};
_hideControlsFilter.reset(new Filter([=](QObject *what) {
_mouseInside = true;
if (what->isWidgetType()
&& window()->isAncestorOf(static_cast<QWidget*>(what))) {
_hideControlsTimer.callOnce(kHideControlsTimeout);
requestControlsHidden(false);
updateControlsShown();
}
}));
_hideControlsTimer.callOnce(kHideControlsTimeout);
} else {
_hideControlsFilter = nullptr;
_hideControlsTimer.cancel();
if (_mouseInside) {
requestControlsHidden(false);
updateControlsShown();
}
}
}, _incoming->rp()->lifetime());
_call->mutedValue( _call->mutedValue(
) | rpl::start_with_next([=](bool mute) { ) | rpl::start_with_next([=](bool mute) {
_mute->entity()->setProgress(mute ? 1. : 0.); _mute->entity()->setProgress(mute ? 1. : 0.);
@ -603,6 +677,8 @@ void Panel::createRemoteAudioMute() {
const auto r = _remoteAudioMute->rect(); const auto r = _remoteAudioMute->rect();
auto hq = PainterHighQualityEnabler(p); auto hq = PainterHighQualityEnabler(p);
p.setOpacity(_controlsShownAnimation.value(
_controlsShown ? 1. : 0.));
p.setBrush(st::videoPlayIconBg); p.setBrush(st::videoPlayIconBg);
p.setPen(Qt::NoPen); p.setPen(Qt::NoPen);
p.drawRoundedRect(r, r.height() / 2, r.height() / 2); p.drawRoundedRect(r, r.height() / 2, r.height() / 2);
@ -661,6 +737,8 @@ void Panel::createRemoteLowBattery() {
const auto r = _remoteLowBattery->rect(); const auto r = _remoteLowBattery->rect();
auto hq = PainterHighQualityEnabler(p); auto hq = PainterHighQualityEnabler(p);
p.setOpacity(_controlsShownAnimation.value(
_controlsShown ? 1. : 0.));
p.setBrush(st::videoPlayIconBg); p.setBrush(st::videoPlayIconBg);
p.setPen(Qt::NoPen); p.setPen(Qt::NoPen);
p.drawRoundedRect(r, r.height() / 2, r.height() / 2); p.drawRoundedRect(r, r.height() / 2, r.height() / 2);
@ -782,6 +860,9 @@ void Panel::showDevicesMenu(
} }
Core::App().saveSettingsDelayed(); Core::App().saveSettingsDelayed();
}; };
controlsShownForce(true);
updateControlsShown();
_devicesMenu = MakeDeviceSelectionMenu( _devicesMenu = MakeDeviceSelectionMenu(
widget(), widget(),
&Core::App().mediaDevices(), &Core::App().mediaDevices(),
@ -791,6 +872,9 @@ void Panel::showDevicesMenu(
Ui::PopupMenu::VerticalOrigin::Bottom); Ui::PopupMenu::VerticalOrigin::Bottom);
_devicesMenu->popup(button->mapToGlobal(QPoint()) _devicesMenu->popup(button->mapToGlobal(QPoint())
- QPoint(st::callDeviceSelectionMenu.menu.widthMin / 2, 0)); - QPoint(st::callDeviceSelectionMenu.menu.widthMin / 2, 0));
QObject::connect(_devicesMenu.get(), &QObject::destroyed, window(), [=] {
_controlsShownForceTimer.callOnce(kHideControlsQuickTimeout);
});
} }
void Panel::refreshOutgoingPreviewInBody(State state) { void Panel::refreshOutgoingPreviewInBody(State state) {
@ -823,6 +907,33 @@ QRect Panel::outgoingFrameGeometry() const {
return _outgoingVideoBubble->geometry(); return _outgoingVideoBubble->geometry();
} }
void Panel::requestControlsHidden(bool hidden) {
_hideControlsRequested = hidden;
updateControlsShown();
}
void Panel::controlsShownForce(bool shown) {
_controlsShownForce = shown;
if (shown) {
_controlsShownForceTimer.cancel();
}
updateControlsShown();
}
void Panel::updateControlsShown() {
const auto shown = !_incoming
|| _incoming->widget()->isHidden()
|| _controlsShownForce
|| !_hideControlsRequested;
if (_controlsShown != shown) {
_controlsShown = shown;
_controlsShownAnimation.start([=] {
updateControlsGeometry();
}, shown ? 0. : 1., shown ? 1. : 0., st::slideDuration);
updateControlsGeometry();
}
}
void Panel::updateControlsGeometry() { void Panel::updateControlsGeometry() {
if (widget()->size().isEmpty()) { if (widget()->size().isEmpty()) {
return; return;
@ -830,6 +941,8 @@ void Panel::updateControlsGeometry() {
if (_incoming) { if (_incoming) {
refreshIncomingGeometry(); refreshIncomingGeometry();
} }
const auto shown = _controlsShownAnimation.value(
_controlsShown ? 1. : 0.);
if (_fingerprint) { if (_fingerprint) {
#ifndef Q_OS_MAC #ifndef Q_OS_MAC
const auto controlsGeometry = _controls->controls.geometry(); const auto controlsGeometry = _controls->controls.geometry();
@ -848,14 +961,14 @@ void Panel::updateControlsGeometry() {
const auto minRight = 0; const auto minRight = 0;
#endif // _controls #endif // _controls
const auto desired = (widget()->width() - _fingerprint->width()) / 2; const auto desired = (widget()->width() - _fingerprint->width()) / 2;
const auto top = anim::interpolate(
-_fingerprint->height(),
st::callFingerprintTop,
shown);
if (minLeft) { if (minLeft) {
_fingerprint->moveToLeft( _fingerprint->moveToLeft(std::max(desired, minLeft), top);
std::max(desired, minLeft),
st::callFingerprintTop);
} else { } else {
_fingerprint->moveToRight( _fingerprint->moveToRight(std::max(desired, minRight), top);
std::max(desired, minRight),
st::callFingerprintTop);
} }
} }
const auto innerHeight = std::max(widget()->height(), st::callHeightMin); const auto innerHeight = std::max(widget()->height(), st::callHeightMin);
@ -885,7 +998,11 @@ void Panel::updateControlsGeometry() {
/ (_outgoingPreviewInBody ? 3 : 2); / (_outgoingPreviewInBody ? 3 : 2);
_bodyTop = availableTop + skipHeight; _bodyTop = availableTop + skipHeight;
_buttonsTop = availableTop + available; _buttonsTopShown = availableTop + available;
_buttonsTop = anim::interpolate(
widget()->height(),
_buttonsTopShown,
shown);
const auto previewTop = _bodyTop + _bodySt->height + skipHeight; const auto previewTop = _bodyTop + _bodySt->height + skipHeight;
_userpic->setGeometry( _userpic->setGeometry(
@ -908,6 +1025,8 @@ void Panel::updateControlsGeometry() {
(_buttonsTop (_buttonsTop
- st::callRemoteAudioMuteSkip - st::callRemoteAudioMuteSkip
- _remoteAudioMute->height())); - _remoteAudioMute->height()));
_remoteAudioMute->update();
_remoteAudioMute->entity()->setOpacity(shown);
} }
if (_remoteLowBattery) { if (_remoteLowBattery) {
_remoteLowBattery->moveToLeft( _remoteLowBattery->moveToLeft(
@ -915,6 +1034,8 @@ void Panel::updateControlsGeometry() {
(_buttonsTop (_buttonsTop
- st::callRemoteAudioMuteSkip - st::callRemoteAudioMuteSkip
- _remoteLowBattery->height())); - _remoteLowBattery->height()));
_remoteLowBattery->update();
_remoteLowBattery->entity()->setOpacity(shown);
} }
if (_outgoingPreviewInBody) { if (_outgoingPreviewInBody) {
@ -925,7 +1046,7 @@ void Panel::updateControlsGeometry() {
previewTop, previewTop,
bodyPreviewSize.width(), bodyPreviewSize.width(),
bodyPreviewSize.height())); bodyPreviewSize.height()));
} else { } else if (_outgoingVideoBubble) {
updateOutgoingVideoBubbleGeometry(); updateOutgoingVideoBubbleGeometry();
} }

View file

@ -111,6 +111,9 @@ private:
[[nodiscard]] bool handleClose() const; [[nodiscard]] bool handleClose() const;
void requestControlsHidden(bool hidden);
void controlsShownForce(bool shown);
void updateControlsShown();
void updateControlsGeometry(); void updateControlsGeometry();
void updateHangupGeometry(); void updateHangupGeometry();
void updateStatusGeometry(); void updateStatusGeometry();
@ -177,8 +180,19 @@ private:
std::unique_ptr<VideoBubble> _outgoingVideoBubble; std::unique_ptr<VideoBubble> _outgoingVideoBubble;
QPixmap _bottomShadow; QPixmap _bottomShadow;
int _bodyTop = 0; int _bodyTop = 0;
int _buttonsTopShown = 0;
int _buttonsTop = 0; int _buttonsTop = 0;
base::Timer _hideControlsTimer;
base::Timer _controlsShownForceTimer;
std::unique_ptr<QObject> _hideControlsFilter;
bool _hideControlsRequested = false;
rpl::variable<bool> _fullScreenOrMaximized;
Ui::Animations::Simple _controlsShownAnimation;
bool _controlsShownForce = false;
bool _controlsShown = true;
bool _mouseInside = false;
base::unique_qptr<Ui::PopupMenu> _devicesMenu; base::unique_qptr<Ui::PopupMenu> _devicesMenu;
base::Timer _updateDurationTimer; base::Timer _updateDurationTimer;

View file

@ -855,6 +855,17 @@ historyComposeButton: FlatButton {
color: historyComposeButtonBgRipple; color: historyComposeButtonBgRipple;
} }
} }
historyGiftToChannel: IconButton(defaultIconButton) {
width: 46px;
height: 46px;
icon: icon{{ "menu/gift_premium", windowActiveTextFg }};
iconOver: icon{{ "menu/gift_premium", windowActiveTextFg }};
rippleAreaPosition: point(3px, 3px);
rippleAreaSize: 40px;
ripple: universalRippleAnimation;
}
historyUnblock: FlatButton(historyComposeButton) { historyUnblock: FlatButton(historyComposeButton) {
color: attentionButtonFg; color: attentionButtonFg;
overColor: attentionButtonFgOver; overColor: attentionButtonFgOver;

View file

@ -628,6 +628,13 @@ void EmojiListWidget::applyNextSearchQuery() {
const auto modeChanged = (_searchMode != searching); const auto modeChanged = (_searchMode != searching);
clearSelection(); clearSelection();
if (modeChanged) { if (modeChanged) {
if (_picker) {
_picker->hideAnimated();
}
_colorAllRipple = nullptr;
for (auto &set : _custom) {
set.ripple = nullptr;
}
_searchMode = searching; _searchMode = searching;
} }
if (!searching) { if (!searching) {

View file

@ -329,6 +329,7 @@ not_null<DocumentData*> GenerateLocalSticker(
path, path,
QByteArray(), QByteArray(),
nullptr, nullptr,
nullptr,
SendMediaType::File, SendMediaType::File,
FileLoadTo(0, {}, {}, 0), FileLoadTo(0, {}, {}, 0),
{}, {},

View file

@ -61,6 +61,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "settings/settings_privacy_security.h" #include "settings/settings_privacy_security.h"
#include "settings/settings_chat.h" #include "settings/settings_chat.h"
#include "settings/settings_premium.h" #include "settings/settings_premium.h"
#include "storage/storage_account.h"
#include "mainwidget.h" #include "mainwidget.h"
#include "main/main_account.h" #include "main/main_account.h"
#include "main/main_app_config.h" #include "main/main_app_config.h"
@ -602,6 +603,8 @@ bool ResolveUsernameOrPhone(
const auto threadParam = params.value(u"thread"_q); const auto threadParam = params.value(u"thread"_q);
const auto threadId = topicId ? topicId : threadParam.toInt(); const auto threadId = topicId ? topicId : threadParam.toInt();
const auto gameParam = params.value(u"game"_q); const auto gameParam = params.value(u"game"_q);
const auto videot = params.value(u"t"_q);
if (!gameParam.isEmpty() && validDomain(gameParam)) { if (!gameParam.isEmpty() && validDomain(gameParam)) {
startToken = gameParam; startToken = gameParam;
resolveType = ResolveType::ShareGame; resolveType = ResolveType::ShareGame;
@ -618,6 +621,9 @@ bool ResolveUsernameOrPhone(
.phone = phone, .phone = phone,
.messageId = post, .messageId = post,
.storyId = storyId, .storyId = storyId,
.videoTimestamp = (!videot.isEmpty()
? ParseVideoTimestamp(videot)
: std::optional<TimeId>()),
.text = params.value(u"text"_q), .text = params.value(u"text"_q),
.repliesInfo = commentId .repliesInfo = commentId
? Window::RepliesByLinkInfo{ ? Window::RepliesByLinkInfo{
@ -781,8 +787,8 @@ bool OpenMediaTimestamp(
if (!controller) { if (!controller) {
return false; return false;
} }
const auto time = match->captured(2).toInt(); const auto position = match->captured(2).toInt();
if (time < 0) { if (position < 0) {
return false; return false;
} }
const auto base = match->captured(1); const auto base = match->captured(1);
@ -795,7 +801,7 @@ bool OpenMediaTimestamp(
const auto session = &controller->session(); const auto session = &controller->session();
const auto document = session->data().document(documentId); const auto document = session->data().document(documentId);
const auto context = session->data().message(itemId); const auto context = session->data().message(itemId);
const auto timeMs = time * crl::time(1000); const auto time = position * crl::time(1000);
if (document->isVideoFile()) { if (document->isVideoFile()) {
controller->window().openInMediaView(Media::View::OpenRequest( controller->window().openInMediaView(Media::View::OpenRequest(
controller, controller,
@ -803,11 +809,9 @@ bool OpenMediaTimestamp(
context, context,
context ? context->topicRootId() : MsgId(0), context ? context->topicRootId() : MsgId(0),
false, false,
timeMs)); time));
} else if (document->isSong() || document->isVoiceMessage()) { } else if (document->isSong() || document->isVoiceMessage()) {
session->settings().setMediaLastPlaybackPosition( session->local().setMediaLastPlaybackPosition(documentId, time);
documentId,
timeMs);
Media::Player::instance()->play({ document, itemId }); Media::Player::instance()->play({ document, itemId });
} }
return true; return true;
@ -1759,4 +1763,14 @@ void ResolveAndShowUniqueGift(
ResolveAndShowUniqueGift(std::move(show), slug, {}); ResolveAndShowUniqueGift(std::move(show), slug, {});
} }
TimeId ParseVideoTimestamp(QStringView value) {
const auto kExp = u"^(?:(\\d+)h)?(?:(\\d+)m)?(?:(\\d+)s)?$"_q;
const auto m = QRegularExpression(kExp).match(value);
return m.hasMatch()
? (m.capturedView(1).toInt() * 3600
+ m.capturedView(2).toInt() * 60
+ m.capturedView(3).toInt())
: value.toInt();
}
} // namespace Core } // namespace Core

View file

@ -50,4 +50,6 @@ void ResolveAndShowUniqueGift(
std::shared_ptr<ChatHelpers::Show> show, std::shared_ptr<ChatHelpers::Show> show,
const QString &slug); const QString &slug);
[[nodiscard]] TimeId ParseVideoTimestamp(QStringView value);
} // namespace Core } // namespace Core

View file

@ -28,6 +28,7 @@ namespace {
constexpr auto kCountLimit = 2048; // How many shortcuts can be in json file. constexpr auto kCountLimit = 2048; // How many shortcuts can be in json file.
rpl::event_stream<not_null<Request*>> RequestsStream; rpl::event_stream<not_null<Request*>> RequestsStream;
bool Paused/* = false*/;
const auto AutoRepeatCommands = base::flat_set<Command>{ const auto AutoRepeatCommands = base::flat_set<Command>{
Command::MediaPrevious, Command::MediaPrevious,
@ -112,45 +113,15 @@ const auto CommandByName = base::flat_map<QString, Command>{
// //
}; };
const auto CommandNames = base::flat_map<Command, QString>{ const base::flat_map<Command, QString> &CommandNames() {
{ Command::Close , u"close_telegram"_q }, static const auto result = [&] {
{ Command::Lock , u"lock_telegram"_q }, auto result = base::flat_map<Command, QString>();
{ Command::Minimize , u"minimize_telegram"_q }, for (const auto &[name, command] : CommandByName) {
{ Command::Quit , u"quit_telegram"_q }, result.emplace(command, name);
}
{ Command::MediaPlay , u"media_play"_q }, return result;
{ Command::MediaPause , u"media_pause"_q }, }();
{ Command::MediaPlayPause , u"media_playpause"_q }, return result;
{ Command::MediaStop , u"media_stop"_q },
{ Command::MediaPrevious , u"media_previous"_q },
{ Command::MediaNext , u"media_next"_q },
{ Command::Search , u"search"_q },
{ Command::ChatPrevious , u"previous_chat"_q },
{ Command::ChatNext , u"next_chat"_q },
{ Command::ChatFirst , u"first_chat"_q },
{ Command::ChatLast , u"last_chat"_q },
{ Command::ChatSelf , u"self_chat"_q },
{ Command::FolderPrevious , u"previous_folder"_q },
{ Command::FolderNext , u"next_folder"_q },
{ Command::ShowAllChats , u"all_chats"_q },
{ Command::ShowFolder1 , u"folder1"_q },
{ Command::ShowFolder2 , u"folder2"_q },
{ Command::ShowFolder3 , u"folder3"_q },
{ Command::ShowFolder4 , u"folder4"_q },
{ Command::ShowFolder5 , u"folder5"_q },
{ Command::ShowFolder6 , u"folder6"_q },
{ Command::ShowFolderLast , u"last_folder"_q },
{ Command::ShowArchive , u"show_archive"_q },
{ Command::ShowContacts , u"show_contacts"_q },
{ Command::ReadChat , u"read_chat"_q },
{ Command::ShowChatMenu , u"show_chat_menu"_q },
}; };
[[maybe_unused]] constexpr auto kNoValue = { [[maybe_unused]] constexpr auto kNoValue = {
@ -175,19 +146,39 @@ public:
[[nodiscard]] const QStringList &errors() const; [[nodiscard]] const QStringList &errors() const;
[[nodiscard]] auto keysDefaults() const
-> base::flat_map<QKeySequence, base::flat_set<Command>>;
[[nodiscard]] auto keysCurrents() const
-> base::flat_map<QKeySequence, base::flat_set<Command>>;
void change(
QKeySequence was,
QKeySequence now,
Command command,
std::optional<Command> restore);
void resetToDefaults();
private: private:
void fillDefaults(); void fillDefaults();
void writeDefaultFile(); void writeDefaultFile();
void writeCustomFile();
bool readCustomFile(); bool readCustomFile();
void set(const QString &keys, Command command, bool replace = false); void set(const QString &keys, Command command, bool replace = false);
void set(const QKeySequence &result, Command command, bool replace);
void remove(const QString &keys); void remove(const QString &keys);
void remove(const QKeySequence &keys);
void unregister(base::unique_qptr<QAction> shortcut); void unregister(base::unique_qptr<QAction> shortcut);
void pruneListened();
QStringList _errors; QStringList _errors;
base::flat_map<QKeySequence, base::unique_qptr<QAction>> _shortcuts; base::flat_map<QKeySequence, base::unique_qptr<QAction>> _shortcuts;
base::flat_multi_map<not_null<QObject*>, Command> _commandByObject; base::flat_multi_map<not_null<QObject*>, Command> _commandByObject;
std::vector<QPointer<QWidget>> _listened;
base::flat_map<QKeySequence, base::flat_set<Command>> _defaults;
base::flat_set<QAction*> _mediaShortcuts; base::flat_set<QAction*> _mediaShortcuts;
base::flat_set<QAction*> _supportShortcuts; base::flat_set<QAction*> _supportShortcuts;
@ -278,6 +269,54 @@ const QStringList &Manager::errors() const {
return _errors; return _errors;
} }
auto Manager::keysDefaults() const
-> base::flat_map<QKeySequence, base::flat_set<Command>> {
return _defaults;
}
auto Manager::keysCurrents() const
-> base::flat_map<QKeySequence, base::flat_set<Command>> {
auto result = base::flat_map<QKeySequence, base::flat_set<Command>>();
for (const auto &[keys, command] : _shortcuts) {
auto i = _commandByObject.findFirst(command);
const auto end = _commandByObject.end();
for (; i != end && (i->first == command); ++i) {
result[keys].emplace(i->second);
}
}
return result;
}
void Manager::change(
QKeySequence was,
QKeySequence now,
Command command,
std::optional<Command> restore) {
if (!was.isEmpty()) {
remove(was);
}
if (!now.isEmpty()) {
set(now, command, true);
}
if (restore) {
Assert(!was.isEmpty());
set(was, *restore, true);
}
writeCustomFile();
}
void Manager::resetToDefaults() {
while (!_shortcuts.empty()) {
remove(_shortcuts.begin()->first);
}
for (const auto &[sequence, commands] : _defaults) {
for (const auto command : commands) {
set(sequence, command, false);
}
}
writeCustomFile();
}
std::vector<Command> Manager::lookup(not_null<QObject*> object) const { std::vector<Command> Manager::lookup(not_null<QObject*> object) const {
auto result = std::vector<Command>(); auto result = std::vector<Command>();
auto i = _commandByObject.findFirst(object); auto i = _commandByObject.findFirst(object);
@ -301,11 +340,23 @@ void Manager::toggleSupport(bool toggled) {
} }
void Manager::listen(not_null<QWidget*> widget) { void Manager::listen(not_null<QWidget*> widget) {
pruneListened();
_listened.push_back(widget.get());
for (const auto &[keys, shortcut] : _shortcuts) { for (const auto &[keys, shortcut] : _shortcuts) {
widget->addAction(shortcut.get()); widget->addAction(shortcut.get());
} }
} }
void Manager::pruneListened() {
for (auto i = begin(_listened); i != end(_listened);) {
if (i->data()) {
++i;
} else {
i = _listened.erase(i);
}
}
}
bool Manager::readCustomFile() { bool Manager::readCustomFile() {
// read custom shortcuts from file if it exists or write an empty custom shortcuts file // read custom shortcuts from file if it exists or write an empty custom shortcuts file
QFile file(CustomFilePath()); QFile file(CustomFilePath());
@ -440,7 +491,9 @@ void Manager::fillDefaults() {
set(u"ctrl+r"_q, Command::ReadChat); set(u"ctrl+r"_q, Command::ReadChat);
set(u"ctrl+="_q, Command::ShowChatMenu); set(u"ctrl+\\"_q, Command::ShowChatMenu);
_defaults = keysCurrents();
} }
void Manager::writeDefaultFile() { void Manager::writeDefaultFile() {
@ -466,8 +519,8 @@ void Manager::writeDefaultFile() {
auto i = _commandByObject.findFirst(object); auto i = _commandByObject.findFirst(object);
const auto end = _commandByObject.end(); const auto end = _commandByObject.end();
for (; i != end && i->first == object; ++i) { for (; i != end && i->first == object; ++i) {
const auto j = CommandNames.find(i->second); const auto j = CommandNames().find(i->second);
if (j != CommandNames.end()) { if (j != CommandNames().end()) {
QJsonObject entry; QJsonObject entry;
entry.insert(u"keys"_q, sequence.toString().toLower()); entry.insert(u"keys"_q, sequence.toString().toLower());
entry.insert(u"command"_q, j->second); entry.insert(u"command"_q, j->second);
@ -488,6 +541,55 @@ void Manager::writeDefaultFile() {
} }
} }
auto document = QJsonDocument();
document.setArray(shortcuts);
file.write(document.toJson(QJsonDocument::Indented));
}
void Manager::writeCustomFile() {
auto shortcuts = QJsonArray();
for (const auto &[sequence, shortcut] : _shortcuts) {
const auto object = shortcut.get();
auto i = _commandByObject.findFirst(object);
const auto end = _commandByObject.end();
for (; i != end && i->first == object; ++i) {
const auto d = _defaults.find(sequence);
if (d == _defaults.end() || !d->second.contains(i->second)) {
const auto j = CommandNames().find(i->second);
if (j != CommandNames().end()) {
QJsonObject entry;
entry.insert(u"keys"_q, sequence.toString().toLower());
entry.insert(u"command"_q, j->second);
shortcuts.append(entry);
}
}
}
}
for (const auto &[sequence, command] : _defaults) {
if (!_shortcuts.contains(sequence)) {
QJsonObject entry;
entry.insert(u"keys"_q, sequence.toString().toLower());
entry.insert(u"command"_q, QJsonValue());
shortcuts.append(entry);
}
}
if (shortcuts.isEmpty()) {
WriteDefaultCustomFile();
return;
}
auto file = QFile(CustomFilePath());
if (!file.open(QIODevice::WriteOnly)) {
LOG(("Shortcut Warning: could not write custom shortcuts file."));
return;
}
const char *customHeader = R"HEADER(
// This is a list of changed shortcuts for Telegram Desktop
// You can edit them in Settings > Chat Settings > Keyboard Shortcuts.
)HEADER";
file.write(customHeader);
auto document = QJsonDocument(); auto document = QJsonDocument();
document.setArray(shortcuts); document.setArray(shortcuts);
@ -504,8 +606,15 @@ void Manager::set(const QString &keys, Command command, bool replace) {
_errors.push_back(u"Could not derive key sequence '%1'!"_q.arg(keys)); _errors.push_back(u"Could not derive key sequence '%1'!"_q.arg(keys));
return; return;
} }
set(result, command, replace);
}
void Manager::set(
const QKeySequence &keys,
Command command,
bool replace) {
auto shortcut = base::make_unique_q<QAction>(); auto shortcut = base::make_unique_q<QAction>();
shortcut->setShortcut(result); shortcut->setShortcut(keys);
shortcut->setShortcutContext(Qt::ApplicationShortcut); shortcut->setShortcutContext(Qt::ApplicationShortcut);
if (!AutoRepeatCommands.contains(command)) { if (!AutoRepeatCommands.contains(command)) {
shortcut->setAutoRepeat(false); shortcut->setAutoRepeat(false);
@ -516,20 +625,26 @@ void Manager::set(const QString &keys, Command command, bool replace) {
shortcut->setEnabled(false); shortcut->setEnabled(false);
} }
auto object = shortcut.get(); auto object = shortcut.get();
auto i = _shortcuts.find(result); auto i = _shortcuts.find(keys);
if (i == end(_shortcuts)) { if (i == end(_shortcuts)) {
i = _shortcuts.emplace(result, std::move(shortcut)).first; i = _shortcuts.emplace(keys, std::move(shortcut)).first;
} else if (replace) { } else if (replace) {
unregister(std::exchange(i->second, std::move(shortcut))); unregister(std::exchange(i->second, std::move(shortcut)));
} else { } else {
object = i->second.get(); object = i->second.get();
} }
_commandByObject.emplace(object, command); _commandByObject.emplace(object, command);
if (!shortcut && isMediaShortcut) { if (!shortcut) { // Added the new one.
_mediaShortcuts.emplace(i->second.get()); if (isMediaShortcut) {
} _mediaShortcuts.emplace(i->second.get());
if (!shortcut && isSupportShortcut) { }
_supportShortcuts.emplace(i->second.get()); if (isSupportShortcut) {
_supportShortcuts.emplace(i->second.get());
}
pruneListened();
for (const auto &widget : _listened) {
widget->addAction(i->second.get());
}
} }
} }
@ -543,7 +658,11 @@ void Manager::remove(const QString &keys) {
_errors.push_back(u"Could not derive key sequence '%1'!"_q.arg(keys)); _errors.push_back(u"Could not derive key sequence '%1'!"_q.arg(keys));
return; return;
} }
const auto i = _shortcuts.find(result); remove(result);
}
void Manager::remove(const QKeySequence &keys) {
const auto i = _shortcuts.find(keys);
if (i != end(_shortcuts)) { if (i != end(_shortcuts)) {
unregister(std::move(i->second)); unregister(std::move(i->second));
_shortcuts.erase(i); _shortcuts.erase(i);
@ -552,7 +671,7 @@ void Manager::remove(const QString &keys) {
void Manager::unregister(base::unique_qptr<QAction> shortcut) { void Manager::unregister(base::unique_qptr<QAction> shortcut) {
if (shortcut) { if (shortcut) {
_commandByObject.erase(shortcut.get()); _commandByObject.removeAll(shortcut.get());
_mediaShortcuts.erase(shortcut.get()); _mediaShortcuts.erase(shortcut.get());
_supportShortcuts.erase(shortcut.get()); _supportShortcuts.erase(shortcut.get());
} }
@ -598,7 +717,9 @@ bool Launch(Command command) {
} }
bool Launch(std::vector<Command> commands) { bool Launch(std::vector<Command> commands) {
if (auto handler = RequestHandler(std::move(commands))) { if (Paused) {
return false;
} else if (auto handler = RequestHandler(std::move(commands))) {
return handler(); return handler();
} }
return false; return false;
@ -630,6 +751,69 @@ void ToggleSupportShortcuts(bool toggled) {
Data.toggleSupport(toggled); Data.toggleSupport(toggled);
} }
void Pause() {
Paused = true;
}
void Unpause() {
Paused = false;
}
auto KeysDefaults()
-> base::flat_map<QKeySequence, base::flat_set<Command>> {
return Data.keysDefaults();
}
auto KeysCurrents()
-> base::flat_map<QKeySequence, base::flat_set<Command>> {
return Data.keysCurrents();
}
void Change(
QKeySequence was,
QKeySequence now,
Command command,
std::optional<Command> restore) {
Data.change(was, now, command, restore);
}
void ResetToDefaults() {
Data.resetToDefaults();
}
bool AllowWithoutModifiers(int key) {
const auto service = {
Qt::Key_Escape,
Qt::Key_Tab,
Qt::Key_Backtab,
Qt::Key_Backspace,
Qt::Key_Return,
Qt::Key_Enter,
Qt::Key_Insert,
Qt::Key_Delete,
Qt::Key_Pause,
Qt::Key_Print,
Qt::Key_SysReq,
Qt::Key_Clear,
Qt::Key_Home,
Qt::Key_End,
Qt::Key_Left,
Qt::Key_Up,
Qt::Key_Right,
Qt::Key_Down,
Qt::Key_PageUp,
Qt::Key_PageDown,
Qt::Key_Shift,
Qt::Key_Control,
Qt::Key_Meta,
Qt::Key_Alt,
Qt::Key_CapsLock,
Qt::Key_NumLock,
Qt::Key_ScrollLock,
};
return (key >= 0x80) && !ranges::contains(service, key);
}
void Finish() { void Finish() {
Data.clear(); Data.clear();
} }

View file

@ -139,4 +139,21 @@ void ToggleMediaShortcuts(bool toggled);
// have some conflicts with default input shortcuts, like Ctrl+Delete. // have some conflicts with default input shortcuts, like Ctrl+Delete.
void ToggleSupportShortcuts(bool toggled); void ToggleSupportShortcuts(bool toggled);
void Pause();
void Unpause();
[[nodiscard]] auto KeysDefaults()
-> base::flat_map<QKeySequence, base::flat_set<Command>>;
[[nodiscard]] auto KeysCurrents()
-> base::flat_map<QKeySequence, base::flat_set<Command>>;
void Change(
QKeySequence was,
QKeySequence now,
Command command,
std::optional<Command> restore = {});
void ResetToDefaults();
[[nodiscard]] bool AllowWithoutModifiers(int key);
} // namespace Shortcuts } // namespace Shortcuts

View file

@ -22,7 +22,7 @@ constexpr auto AppId = "{53F49750-6209-4FBF-9CA8-7A333C87D666}"_cs;
constexpr auto AppNameOld = "AyuGram for Windows"_cs; constexpr auto AppNameOld = "AyuGram for Windows"_cs;
constexpr auto AppName = "AyuGram Desktop"_cs; constexpr auto AppName = "AyuGram Desktop"_cs;
constexpr auto AppFile = "AyuGram"_cs; constexpr auto AppFile = "AyuGram"_cs;
constexpr auto AppVersion = 5010007; constexpr auto AppVersion = 5011001;
constexpr auto AppVersionStr = "5.10.7"; constexpr auto AppVersionStr = "5.11.1";
constexpr auto AppBetaVersion = false; constexpr auto AppBetaVersion = false;
constexpr auto AppAlphaVersion = TDESKTOP_ALPHA_VERSION; constexpr auto AppAlphaVersion = TDESKTOP_ALPHA_VERSION;

View file

@ -1012,7 +1012,7 @@ PeerId ChannelData::groupCallDefaultJoinAs() const {
void ChannelData::setAllowedReactions(Data::AllowedReactions value) { void ChannelData::setAllowedReactions(Data::AllowedReactions value) {
if (_allowedReactions != value) { if (_allowedReactions != value) {
if (value.paidEnabled) { if (value.paidEnabled) {
session().api().globalPrivacy().loadPaidReactionAnonymous(); session().api().globalPrivacy().loadPaidReactionShownPeer();
} }
const auto enabled = [](const Data::AllowedReactions &allowed) { const auto enabled = [](const Data::AllowedReactions &allowed) {
return (allowed.type != Data::AllowedReactionsType::Some) return (allowed.type != Data::AllowedReactionsType::Some)

View file

@ -12,6 +12,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_streaming.h" #include "data/data_streaming.h"
#include "data/data_document_media.h" #include "data/data_document_media.h"
#include "data/data_reply_preview.h" #include "data/data_reply_preview.h"
#include "data/data_web_page.h"
#include "lang/lang_keys.h" #include "lang/lang_keys.h"
#include "inline_bots/inline_bot_layout_item.h" #include "inline_bots/inline_bot_layout_item.h"
#include "main/main_session.h" #include "main/main_session.h"
@ -1881,3 +1882,18 @@ void DocumentData::collectLocalData(not_null<DocumentData*> local) {
session().local().writeFileLocation(mediaKey(), _location); session().local().writeFileLocation(mediaKey(), _location);
} }
} }
PhotoData *LookupVideoCover(
not_null<DocumentData*> document,
HistoryItem *item) {
const auto media = item ? item->media() : nullptr;
if (const auto webpage = media ? media->webpage() : nullptr) {
if (webpage->document == document && webpage->photoIsVideoCover) {
return webpage->photo;
}
return nullptr;
}
return (media && media->document() == document)
? media->videoCover()
: nullptr;
}

View file

@ -13,6 +13,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_cloud_file.h" #include "data/data_cloud_file.h"
#include "core/file_location.h" #include "core/file_location.h"
class HistoryItem;
class PhotoData;
enum class ChatRestriction; enum class ChatRestriction;
class mtpFileLoader; class mtpFileLoader;
@ -402,6 +404,10 @@ private:
}; };
[[nodiscard]] PhotoData *LookupVideoCover(
not_null<DocumentData*> document,
HistoryItem *item);
VoiceWaveform documentWaveformDecode(const QByteArray &encoded5bit); VoiceWaveform documentWaveformDecode(const QByteArray &encoded5bit);
QByteArray documentWaveformEncode5bit(const VoiceWaveform &waveform); QByteArray documentWaveformEncode5bit(const VoiceWaveform &waveform);

View file

@ -105,6 +105,7 @@ struct FileReferenceAccumulator {
push(data.vphoto()); push(data.vphoto());
}, [&](const MTPDmessageMediaDocument &data) { }, [&](const MTPDmessageMediaDocument &data) {
push(data.vdocument()); push(data.vdocument());
push(data.vvideo_cover());
push(data.valt_documents()); push(data.valt_documents());
}, [&](const MTPDmessageMediaWebPage &data) { }, [&](const MTPDmessageMediaWebPage &data) {
push(data.vwebpage()); push(data.vwebpage());

View file

@ -447,16 +447,17 @@ Call ComputeCallData(const MTPDmessageActionPhoneCall &call) {
auto result = Call(); auto result = Call();
result.finishReason = [&] { result.finishReason = [&] {
if (const auto reason = call.vreason()) { if (const auto reason = call.vreason()) {
switch (reason->type()) { return reason->match([](const MTPDphoneCallDiscardReasonBusy &) {
case mtpc_phoneCallDiscardReasonBusy:
return CallFinishReason::Busy; return CallFinishReason::Busy;
case mtpc_phoneCallDiscardReasonDisconnect: }, [](const MTPDphoneCallDiscardReasonDisconnect &) {
return CallFinishReason::Disconnected; return CallFinishReason::Disconnected;
case mtpc_phoneCallDiscardReasonHangup: }, [](const MTPDphoneCallDiscardReasonHangup &) {
return CallFinishReason::Hangup; return CallFinishReason::Hangup;
case mtpc_phoneCallDiscardReasonMissed: }, [](const MTPDphoneCallDiscardReasonMissed &) {
return CallFinishReason::Missed; return CallFinishReason::Missed;
} }, [](const MTPDphoneCallDiscardReasonAllowGroupCall &) {
return CallFinishReason::AllowGroupCall;
});
Unexpected("Call reason type."); Unexpected("Call reason type.");
} }
return CallFinishReason::Hangup; return CallFinishReason::Hangup;
@ -552,6 +553,14 @@ DocumentData *Media::document() const {
return nullptr; return nullptr;
} }
PhotoData *Media::videoCover() const {
return nullptr;
}
TimeId Media::videoTimestamp() const {
return 0;
}
bool Media::hasQualitiesList() const { bool Media::hasQualitiesList() const {
return false; return false;
} }
@ -968,17 +977,16 @@ std::unique_ptr<HistoryView::Media> MediaPhoto::createView(
MediaFile::MediaFile( MediaFile::MediaFile(
not_null<HistoryItem*> parent, not_null<HistoryItem*> parent,
not_null<DocumentData*> document, not_null<DocumentData*> document,
bool skipPremiumEffect, Args &&args)
bool hasQualitiesList,
bool spoiler,
crl::time ttlSeconds)
: Media(parent) : Media(parent)
, _document(document) , _document(document)
, _videoCover(args.videoCover)
, _ttlSeconds(args.ttlSeconds)
, _emoji(document->sticker() ? document->sticker()->alt : QString()) , _emoji(document->sticker() ? document->sticker()->alt : QString())
, _skipPremiumEffect(skipPremiumEffect) , _videoTimestamp(args.videoTimestamp)
, _hasQualitiesList(hasQualitiesList) , _skipPremiumEffect(args.skipPremiumEffect)
, _spoiler(spoiler) , _hasQualitiesList(args.hasQualitiesList)
, _ttlSeconds(ttlSeconds) { , _spoiler(args.spoiler) {
parent->history()->owner().registerDocumentItem(_document, parent); parent->history()->owner().registerDocumentItem(_document, parent);
if (!_emoji.isEmpty()) { if (!_emoji.isEmpty()) {
@ -1002,19 +1010,28 @@ MediaFile::~MediaFile() {
} }
std::unique_ptr<Media> MediaFile::clone(not_null<HistoryItem*> parent) { std::unique_ptr<Media> MediaFile::clone(not_null<HistoryItem*> parent) {
return std::make_unique<MediaFile>( return std::make_unique<MediaFile>(parent, _document, MediaFile::Args{
parent, .ttlSeconds = _ttlSeconds,
_document, .videoCover = _videoCover,
!_document->session().premium(), .videoTimestamp = _videoTimestamp,
_hasQualitiesList, .hasQualitiesList = _hasQualitiesList,
_spoiler, .skipPremiumEffect = !_document->session().premium(),
_ttlSeconds); .spoiler = _spoiler,
});
} }
DocumentData *MediaFile::document() const { DocumentData *MediaFile::document() const {
return _document; return _document;
} }
PhotoData *MediaFile::videoCover() const {
return _videoCover;
}
TimeId MediaFile::videoTimestamp() const {
return _videoTimestamp;
}
bool MediaFile::hasQualitiesList() const { bool MediaFile::hasQualitiesList() const {
return _hasQualitiesList; return _hasQualitiesList;
} }
@ -1285,7 +1302,11 @@ bool MediaFile::updateSentMedia(const MTPMessageMedia &media) {
"or with ttl_seconds in updateSentMedia()")); "or with ttl_seconds in updateSentMedia()"));
return false; return false;
} }
parent()->history()->owner().documentConvert(_document, *content); const auto owner = &parent()->history()->owner();
owner->documentConvert(_document, *content);
if (const auto cover = _videoCover ? data.vvideo_cover() : nullptr) {
owner->photoConvert(_videoCover, *cover);
}
return true; return true;
} }

View file

@ -46,6 +46,7 @@ enum class CallFinishReason : char {
Busy, Busy,
Disconnected, Disconnected,
Hangup, Hangup,
AllowGroupCall,
}; };
struct SharedContact final { struct SharedContact final {
@ -178,6 +179,8 @@ public:
virtual std::unique_ptr<Media> clone(not_null<HistoryItem*> parent) = 0; virtual std::unique_ptr<Media> clone(not_null<HistoryItem*> parent) = 0;
virtual DocumentData *document() const; virtual DocumentData *document() const;
virtual PhotoData *videoCover() const;
virtual TimeId videoTimestamp() const;
virtual bool hasQualitiesList() const; virtual bool hasQualitiesList() const;
virtual PhotoData *photo() const; virtual PhotoData *photo() const;
virtual WebPageData *webpage() const; virtual WebPageData *webpage() const;
@ -297,18 +300,26 @@ private:
class MediaFile final : public Media { class MediaFile final : public Media {
public: public:
struct Args {
crl::time ttlSeconds = 0;
PhotoData *videoCover = nullptr;
TimeId videoTimestamp = 0;
bool hasQualitiesList = false;
bool skipPremiumEffect = false;
bool spoiler = false;
};
MediaFile( MediaFile(
not_null<HistoryItem*> parent, not_null<HistoryItem*> parent,
not_null<DocumentData*> document, not_null<DocumentData*> document,
bool skipPremiumEffect, Args &&args);
bool hasQualitiesList,
bool spoiler,
crl::time ttlSeconds);
~MediaFile(); ~MediaFile();
std::unique_ptr<Media> clone(not_null<HistoryItem*> parent) override; std::unique_ptr<Media> clone(not_null<HistoryItem*> parent) override;
DocumentData *document() const override; DocumentData *document() const override;
PhotoData *videoCover() const override;
TimeId videoTimestamp() const override;
bool hasQualitiesList() const override; bool hasQualitiesList() const override;
bool uploading() const override; bool uploading() const override;
@ -338,14 +349,17 @@ public:
private: private:
not_null<DocumentData*> _document; not_null<DocumentData*> _document;
QString _emoji; PhotoData *_videoCover = nullptr;
bool _skipPremiumEffect = false;
bool _hasQualitiesList = false;
bool _spoiler = false;
// Video (unsupported) / Voice / Round. // Video (unsupported) / Voice / Round.
crl::time _ttlSeconds = 0; crl::time _ttlSeconds = 0;
QString _emoji;
TimeId _videoTimestamp = 0;
bool _skipPremiumEffect = false;
bool _hasQualitiesList = false;
bool _spoiler = false;
}; };
class MediaContact final : public Media { class MediaContact final : public Media {

View file

@ -17,6 +17,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "main/main_app_config.h" #include "main/main_app_config.h"
#include "main/session/send_as_peers.h" #include "main/session/send_as_peers.h"
#include "data/components/credits.h" #include "data/components/credits.h"
#include "data/data_channel.h"
#include "data/data_user.h" #include "data/data_user.h"
#include "data/data_session.h" #include "data/data_session.h"
#include "data/data_histories.h" #include "data/data_histories.h"
@ -158,8 +159,23 @@ constexpr auto kPaidAccumulatePeriod = 5 * crl::time(1000) + 500;
return (i != end(top)) && i->my; return (i != end(top)) && i->my;
} }
[[nodiscard]] std::optional<bool> MaybeAnonymous(uint32 privacySet, uint32 anonymous) { [[nodiscard]] std::optional<PeerId> MaybeShownPeer(
return privacySet ? (anonymous == 1) : std::optional<bool>(); uint32 privacySet,
PeerId shownPeer) {
return privacySet ? shownPeer : std::optional<PeerId>();
}
[[nodiscard]] MTPPaidReactionPrivacy PaidReactionShownPeerToTL(
not_null<Main::Session*> session,
std::optional<PeerId> shownPeer) {
return !shownPeer
? MTPPaidReactionPrivacy()
: !*shownPeer
? MTP_paidReactionPrivacyAnonymous()
: (*shownPeer == session->userPeerId())
? MTP_paidReactionPrivacyDefault()
: MTP_paidReactionPrivacyPeer(
session->data().peer(*shownPeer)->input);
} }
} // namespace } // namespace
@ -180,6 +196,13 @@ PossibleItemReactionsRef LookupPossibleReactions(
} }
} }
const auto session = &peer->session(); const auto session = &peer->session();
if (const auto channel = peer->asChannel()) {
if ((!channel->amCreator())
&& (channel->adminRights() & ChatAdminRight::Anonymous)
&& (session->sendAsPeers().resolveChosen(channel) == channel)) {
return {};
}
}
const auto reactions = &session->data().reactions(); const auto reactions = &session->data().reactions();
const auto &full = reactions->list(Reactions::Type::Active); const auto &full = reactions->list(Reactions::Type::Active);
const auto &top = reactions->list(Reactions::Type::Top); const auto &top = reactions->list(Reactions::Type::Top);
@ -1752,7 +1775,7 @@ void Reactions::sendPaidPrivacyRequest(
not_null<HistoryItem*> item, not_null<HistoryItem*> item,
PaidReactionSend send) { PaidReactionSend send) {
Expects(!_sendingPaid.contains(item)); Expects(!_sendingPaid.contains(item));
Expects(send.anonymous.has_value()); Expects(send.shownPeer.has_value());
Expects(!send.count); Expects(!send.count);
const auto id = item->fullId(); const auto id = item->fullId();
@ -1761,7 +1784,7 @@ void Reactions::sendPaidPrivacyRequest(
MTPmessages_TogglePaidReactionPrivacy( MTPmessages_TogglePaidReactionPrivacy(
item->history()->peer->input, item->history()->peer->input,
MTP_int(id.msg), MTP_int(id.msg),
MTP_bool(*send.anonymous)) PaidReactionShownPeerToTL(&_owner->session(), send.shownPeer))
).done([=] { ).done([=] {
if (const auto item = _owner->message(id)) { if (const auto item = _owner->message(id)) {
if (_sendingPaid.remove(item)) { if (_sendingPaid.remove(item)) {
@ -1795,12 +1818,14 @@ void Reactions::sendPaidRequest(
auto &api = _owner->session().api(); auto &api = _owner->session().api();
using Flag = MTPmessages_SendPaidReaction::Flag; using Flag = MTPmessages_SendPaidReaction::Flag;
const auto requestId = api.request(MTPmessages_SendPaidReaction( const auto requestId = api.request(MTPmessages_SendPaidReaction(
MTP_flags(send.anonymous ? Flag::f_private : Flag()), MTP_flags(send.shownPeer ? Flag::f_private : Flag()),
item->history()->peer->input, item->history()->peer->input,
MTP_int(id.msg), MTP_int(id.msg),
MTP_int(send.count), MTP_int(send.count),
MTP_long(randomId), MTP_long(randomId),
MTP_bool(send.anonymous.value_or(false)) (!send.shownPeer
? MTPPaidReactionPrivacy()
: PaidReactionShownPeerToTL(&_owner->session(), *send.shownPeer))
)).done([=](const MTPUpdates &result) { )).done([=](const MTPUpdates &result) {
if (const auto item = _owner->message(id)) { if (const auto item = _owner->message(id)) {
if (_sendingPaid.remove(item)) { if (_sendingPaid.remove(item)) {
@ -1851,9 +1876,9 @@ MessageReactions::~MessageReactions() {
finishPaidSending({ finishPaidSending({
.count = int(paid->sending), .count = int(paid->sending),
.valid = true, .valid = true,
.anonymous = MaybeAnonymous( .shownPeer = MaybeShownPeer(
paid->sendingPrivacySet, paid->sendingPrivacySet,
paid->sendingAnonymous), paid->sendingShownPeer),
}, false); }, false);
} }
} }
@ -2217,7 +2242,7 @@ void MessageReactions::markRead() {
void MessageReactions::scheduleSendPaid( void MessageReactions::scheduleSendPaid(
int count, int count,
std::optional<bool> anonymous) { std::optional<PeerId> shownPeer) {
Expects(count >= 0); Expects(count >= 0);
if (!_paid) { if (!_paid) {
@ -2225,9 +2250,9 @@ void MessageReactions::scheduleSendPaid(
} }
_paid->scheduled += count; _paid->scheduled += count;
_paid->scheduledFlag = 1; _paid->scheduledFlag = 1;
if (anonymous.has_value()) { if (shownPeer.has_value()) {
_paid->scheduledAnonymous = anonymous.value_or(false) ? 1 : 0; _paid->scheduledShownPeer = *shownPeer;
_paid->scheduledPrivacySet = anonymous.has_value(); _paid->scheduledPrivacySet = true;
} }
if (count > 0) { if (count > 0) {
_item->history()->session().credits().lock(StarsAmount(count)); _item->history()->session().credits().lock(StarsAmount(count));
@ -2248,7 +2273,7 @@ void MessageReactions::cancelScheduledPaid() {
} }
_paid->scheduled = 0; _paid->scheduled = 0;
_paid->scheduledFlag = 0; _paid->scheduledFlag = 0;
_paid->scheduledAnonymous = 0; _paid->scheduledShownPeer = 0;
_paid->scheduledPrivacySet = 0; _paid->scheduledPrivacySet = 0;
} }
if (!_paid->sendingFlag && _paid->top.empty()) { if (!_paid->sendingFlag && _paid->top.empty()) {
@ -2263,18 +2288,18 @@ PaidReactionSend MessageReactions::startPaidSending() {
} }
_paid->sending = _paid->scheduled; _paid->sending = _paid->scheduled;
_paid->sendingFlag = _paid->scheduledFlag; _paid->sendingFlag = _paid->scheduledFlag;
_paid->sendingAnonymous = _paid->scheduledAnonymous; _paid->sendingShownPeer = _paid->scheduledShownPeer;
_paid->sendingPrivacySet = _paid->scheduledPrivacySet; _paid->sendingPrivacySet = _paid->scheduledPrivacySet;
_paid->scheduled = 0; _paid->scheduled = 0;
_paid->scheduledFlag = 0; _paid->scheduledFlag = 0;
_paid->scheduledAnonymous = 0; _paid->scheduledShownPeer = 0;
_paid->scheduledPrivacySet = 0; _paid->scheduledPrivacySet = 0;
return { return {
.count = int(_paid->sending), .count = int(_paid->sending),
.valid = true, .valid = true,
.anonymous = MaybeAnonymous( .shownPeer = MaybeShownPeer(
_paid->sendingPrivacySet, _paid->sendingPrivacySet,
_paid->sendingAnonymous), _paid->sendingShownPeer),
}; };
} }
@ -2284,13 +2309,13 @@ void MessageReactions::finishPaidSending(
Expects(_paid != nullptr); Expects(_paid != nullptr);
Expects(send.count == _paid->sending); Expects(send.count == _paid->sending);
Expects(send.valid == (_paid->sendingFlag == 1)); Expects(send.valid == (_paid->sendingFlag == 1));
Expects(send.anonymous == MaybeAnonymous( Expects(send.shownPeer == MaybeShownPeer(
_paid->sendingPrivacySet, _paid->sendingPrivacySet,
_paid->sendingAnonymous)); _paid->sendingShownPeer));
_paid->sending = 0; _paid->sending = 0;
_paid->sendingFlag = 0; _paid->sendingFlag = 0;
_paid->sendingAnonymous = 0; _paid->sendingShownPeer = 0;
_paid->sendingPrivacySet = 0; _paid->sendingPrivacySet = 0;
if (!_paid->scheduledFlag && _paid->top.empty()) { if (!_paid->scheduledFlag && _paid->top.empty()) {
_paid = nullptr; _paid = nullptr;
@ -2299,9 +2324,9 @@ void MessageReactions::finishPaidSending(
return top.my; return top.my;
}); });
if (i != end(_paid->top)) { if (i != end(_paid->top)) {
i->peer = send.anonymous i->peer = send.shownPeer
? nullptr ? _item->history()->owner().peer(*send.shownPeer).get()
: _item->history()->session().user().get(); : nullptr;
} }
} }
if (const auto amount = send.count) { if (const auto amount = send.count) {
@ -2322,22 +2347,23 @@ int MessageReactions::localPaidCount() const {
return _paid ? (_paid->scheduled + _paid->sending) : 0; return _paid ? (_paid->scheduled + _paid->sending) : 0;
} }
bool MessageReactions::localPaidAnonymous() const { PeerId MessageReactions::localPaidShownPeer() const {
const auto minePaidAnonymous = [&] { const auto minePaidShownPeer = [&] {
for (const auto &entry : _paid->top) { for (const auto &entry : _paid->top) {
if (entry.my) { if (entry.my) {
return !entry.peer; return entry.peer ? entry.peer->id : PeerId();
} }
} }
const auto api = &_item->history()->session().api(); const auto api = &_item->history()->session().api();
return api->globalPrivacy().paidReactionAnonymousCurrent(); return api->globalPrivacy().paidReactionShownPeerCurrent();
}; };
return _paid return !_paid
&& ((_paid->scheduledFlag && _paid->scheduledPrivacySet) ? PeerId()
? (_paid->scheduledAnonymous == 1) : (_paid->scheduledFlag && _paid->scheduledPrivacySet)
: (_paid->sendingFlag && _paid->sendingPrivacySet) ? _paid->scheduledShownPeer
? (_paid->sendingAnonymous == 1) : (_paid->sendingFlag && _paid->sendingPrivacySet)
: minePaidAnonymous()); ? _paid->sendingShownPeer
: minePaidShownPeer();
} }
bool MessageReactions::clearCloudData() { bool MessageReactions::clearCloudData() {

View file

@ -71,7 +71,7 @@ struct MyTagInfo {
struct PaidReactionSend { struct PaidReactionSend {
int count = 0; int count = 0;
bool valid = false; bool valid = false;
std::optional<bool> anonymous = false; std::optional<PeerId> shownPeer = PeerId();
}; };
class Reactions final : private CustomEmojiManager::Listener { class Reactions final : private CustomEmojiManager::Listener {
@ -409,7 +409,7 @@ public:
[[nodiscard]] bool hasUnread() const; [[nodiscard]] bool hasUnread() const;
void markRead(); void markRead();
void scheduleSendPaid(int count, std::optional<bool> anonymous); void scheduleSendPaid(int count, std::optional<PeerId> shownPeer);
[[nodiscard]] int scheduledPaid() const; [[nodiscard]] int scheduledPaid() const;
void cancelScheduledPaid(); void cancelScheduledPaid();
@ -418,19 +418,19 @@ public:
[[nodiscard]] bool localPaidData() const; [[nodiscard]] bool localPaidData() const;
[[nodiscard]] int localPaidCount() const; [[nodiscard]] int localPaidCount() const;
[[nodiscard]] bool localPaidAnonymous() const; [[nodiscard]] PeerId localPaidShownPeer() const;
bool clearCloudData(); bool clearCloudData();
private: private:
struct Paid { struct Paid {
std::vector<TopPaid> top; std::vector<TopPaid> top;
uint32 scheduled: 29 = 0; PeerId scheduledShownPeer = 0;
PeerId sendingShownPeer = 0;
uint32 scheduled: 30 = 0;
uint32 scheduledFlag : 1 = 0; uint32 scheduledFlag : 1 = 0;
uint32 scheduledAnonymous : 1 = 0;
uint32 scheduledPrivacySet : 1 = 0; uint32 scheduledPrivacySet : 1 = 0;
uint32 sending : 29 = 0; uint32 sending : 30 = 0;
uint32 sendingFlag : 1 = 0; uint32 sendingFlag : 1 = 0;
uint32 sendingAnonymous : 1 = 0;
uint32 sendingPrivacySet : 1 = 0; uint32 sendingPrivacySet : 1 = 0;
}; };
const not_null<HistoryItem*> _item; const not_null<HistoryItem*> _item;

View file

@ -3524,6 +3524,7 @@ not_null<WebPageData*> Session::processWebpage(
0, 0,
QString(), QString(),
false, false,
false,
data.vdate().v data.vdate().v
? data.vdate().v ? data.vdate().v
: (base::unixtime::now() + kDefaultPendingTimeout)); : (base::unixtime::now() + kDefaultPendingTimeout));
@ -3551,6 +3552,7 @@ not_null<WebPageData*> Session::webpage(
0, 0,
QString(), QString(),
false, false,
false,
TimeId(0)); TimeId(0));
} }
@ -3571,6 +3573,7 @@ not_null<WebPageData*> Session::webpage(
int duration, int duration,
const QString &author, const QString &author,
bool hasLargeMedia, bool hasLargeMedia,
bool photoIsVideoCover,
TimeId pendingTill) { TimeId pendingTill) {
const auto result = webpage(id); const auto result = webpage(id);
webpageApplyFields( webpageApplyFields(
@ -3591,6 +3594,7 @@ not_null<WebPageData*> Session::webpage(
duration, duration,
author, author,
hasLargeMedia, hasLargeMedia,
photoIsVideoCover,
pendingTill); pendingTill);
return result; return result;
} }
@ -3778,6 +3782,7 @@ void Session::webpageApplyFields(
data.vduration().value_or_empty(), data.vduration().value_or_empty(),
qs(data.vauthor().value_or_empty()), qs(data.vauthor().value_or_empty()),
data.is_has_large_media(), data.is_has_large_media(),
data.is_video_cover_photo(),
pendingTill); pendingTill);
} }
@ -3799,6 +3804,7 @@ void Session::webpageApplyFields(
int duration, int duration,
const QString &author, const QString &author,
bool hasLargeMedia, bool hasLargeMedia,
bool photoIsVideoCover,
TimeId pendingTill) { TimeId pendingTill) {
const auto requestPending = (!page->pendingTill && pendingTill > 0); const auto requestPending = (!page->pendingTill && pendingTill > 0);
const auto changed = page->applyChanges( const auto changed = page->applyChanges(
@ -3818,6 +3824,7 @@ void Session::webpageApplyFields(
duration, duration,
author, author,
hasLargeMedia, hasLargeMedia,
photoIsVideoCover,
pendingTill); pendingTill);
if (requestPending) { if (requestPending) {
_session->api().requestWebPageDelayed(page); _session->api().requestWebPageDelayed(page);

View file

@ -631,6 +631,7 @@ public:
int duration, int duration,
const QString &author, const QString &author,
bool hasLargeMedia, bool hasLargeMedia,
bool photoIsVideoCover,
TimeId pendingTill); TimeId pendingTill);
[[nodiscard]] not_null<GameData*> game(GameId id); [[nodiscard]] not_null<GameData*> game(GameId id);
@ -916,6 +917,7 @@ private:
int duration, int duration,
const QString &author, const QString &author,
bool hasLargeMedia, bool hasLargeMedia,
bool photoIsVideoCover,
TimeId pendingTill); TimeId pendingTill);
void gameApplyFields( void gameApplyFields(

View file

@ -316,7 +316,7 @@ void Stories::scheduleExpireTimer() {
const auto nearest = _expiring.front().first; const auto nearest = _expiring.front().first;
const auto now = base::unixtime::now(); const auto now = base::unixtime::now();
const auto delay = (nearest > now) const auto delay = (nearest > now)
? (nearest - now) ? std::min(nearest - now, 86'400)
: 0; : 0;
_expireTimer.callOnce(delay * crl::time(1000)); _expireTimer.callOnce(delay * crl::time(1000));
} }

View file

@ -14,6 +14,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_photo.h" #include "data/data_photo.h"
#include "data/data_channel.h" #include "data/data_channel.h"
#include "data/data_document.h" #include "data/data_document.h"
#include "core/local_url_handlers.h"
#include "lang/lang_keys.h" #include "lang/lang_keys.h"
#include "iv/iv_data.h" #include "iv/iv_data.h"
#include "ui/image/image.h" #include "ui/image/image.h"
@ -228,6 +229,7 @@ bool WebPageData::applyChanges(
int newDuration, int newDuration,
const QString &newAuthor, const QString &newAuthor,
bool newHasLargeMedia, bool newHasLargeMedia,
bool newPhotoIsVideoCover,
int newPendingTill) { int newPendingTill) {
if (newPendingTill != 0 if (newPendingTill != 0
&& (!url.isEmpty() || failed) && (!url.isEmpty() || failed)
@ -265,6 +267,9 @@ bool WebPageData::applyChanges(
|| (hasSiteName + hasTitle + hasDescription < 2)) { || (hasSiteName + hasTitle + hasDescription < 2)) {
newHasLargeMedia = false; newHasLargeMedia = false;
} }
if (!newDocument || !newDocument->isVideoFile() || !newPhoto) {
newPhotoIsVideoCover = false;
}
if (type == newType if (type == newType
&& url == resultUrl && url == resultUrl
@ -283,6 +288,7 @@ bool WebPageData::applyChanges(
&& duration == newDuration && duration == newDuration
&& author == resultAuthor && author == resultAuthor
&& hasLargeMedia == (newHasLargeMedia ? 1 : 0) && hasLargeMedia == (newHasLargeMedia ? 1 : 0)
&& photoIsVideoCover == (newPhotoIsVideoCover ? 1 : 0)
&& pendingTill == newPendingTill) { && pendingTill == newPendingTill) {
return false; return false;
} }
@ -291,6 +297,7 @@ bool WebPageData::applyChanges(
} }
type = newType; type = newType;
hasLargeMedia = newHasLargeMedia ? 1 : 0; hasLargeMedia = newHasLargeMedia ? 1 : 0;
photoIsVideoCover = newPhotoIsVideoCover ? 1 : 0;
url = resultUrl; url = resultUrl;
displayUrl = resultDisplayUrl; displayUrl = resultDisplayUrl;
siteName = resultSiteName; siteName = resultSiteName;
@ -374,6 +381,21 @@ QString WebPageData::displayedSiteName() const {
: siteName; : siteName;
} }
TimeId WebPageData::extractVideoTimestamp() const {
const auto take = [&](const QStringList &list, int index) {
return (index >= 0 && index < list.size()) ? list[index] : QString();
};
const auto hashed = take(url.split('#'), 0);
const auto params = take(hashed.split('?'), 1);
const auto parts = params.split('&');
for (const auto &part : parts) {
if (part.startsWith(u"t="_q)) {
return Core::ParseVideoTimestamp(part.mid(2));
}
}
return 0;
}
bool WebPageData::computeDefaultSmallMedia() const { bool WebPageData::computeDefaultSmallMedia() const {
if (!collage.items.empty()) { if (!collage.items.empty()) {
return false; return false;
@ -382,7 +404,8 @@ bool WebPageData::computeDefaultSmallMedia() const {
&& description.empty() && description.empty()
&& author.isEmpty()) { && author.isEmpty()) {
return false; return false;
} else if (!document } else if (!uniqueGift
&& !document
&& photo && photo
&& type != WebPageType::Photo && type != WebPageType::Photo
&& type != WebPageType::Document && type != WebPageType::Document

View file

@ -106,6 +106,7 @@ struct WebPageData {
int newDuration, int newDuration,
const QString &newAuthor, const QString &newAuthor,
bool newHasLargeMedia, bool newHasLargeMedia,
bool newPhotoIsVideoCover,
int newPendingTill); int newPendingTill);
static void ApplyChanges( static void ApplyChanges(
@ -114,6 +115,7 @@ struct WebPageData {
const MTPmessages_Messages &result); const MTPmessages_Messages &result);
[[nodiscard]] QString displayedSiteName() const; [[nodiscard]] QString displayedSiteName() const;
[[nodiscard]] TimeId extractVideoTimestamp() const;
[[nodiscard]] bool computeDefaultSmallMedia() const; [[nodiscard]] bool computeDefaultSmallMedia() const;
[[nodiscard]] bool suggestEnlargePhoto() const; [[nodiscard]] bool suggestEnlargePhoto() const;
@ -134,7 +136,8 @@ struct WebPageData {
std::shared_ptr<Data::UniqueGift> uniqueGift; std::shared_ptr<Data::UniqueGift> uniqueGift;
int duration = 0; int duration = 0;
TimeId pendingTill = 0; TimeId pendingTill = 0;
uint32 version : 30 = 0; uint32 version : 29 = 0;
uint32 photoIsVideoCover : 1 = 0;
uint32 hasLargeMedia : 1 = 0; uint32 hasLargeMedia : 1 = 0;
uint32 failed : 1 = 0; uint32 failed : 1 = 0;

View file

@ -285,6 +285,7 @@ InnerWidget::InnerWidget(
) | rpl::start_with_next([=] { ) | rpl::start_with_next([=] {
_topicJumpCache = nullptr; _topicJumpCache = nullptr;
_chatsFilterTags.clear(); _chatsFilterTags.clear();
_rightButtons.clear();
}, lifetime()); }, lifetime());
session().downloaderTaskFinished( session().downloaderTaskFinished(
@ -2090,6 +2091,7 @@ bool InnerWidget::updateReorderPinned(QPoint localPosition) {
const auto draggingHeight = _dragging->height(); const auto draggingHeight = _dragging->height();
auto yaddWas = _pinnedRows[_draggingIndex].yadd.current(); auto yaddWas = _pinnedRows[_draggingIndex].yadd.current();
auto shift = 0; auto shift = 0;
auto shiftHeight = 0;
auto now = crl::now(); auto now = crl::now();
if (_dragStart.y() > localPosition.y() && _draggingIndex > 0) { if (_dragStart.y() > localPosition.y() && _draggingIndex > 0) {
shift = -floorclamp(_dragStart.y() - localPosition.y() + (draggingHeight / 2), draggingHeight, 0, _draggingIndex); shift = -floorclamp(_dragStart.y() - localPosition.y() + (draggingHeight / 2), draggingHeight, 0, _draggingIndex);
@ -2099,6 +2101,7 @@ bool InnerWidget::updateReorderPinned(QPoint localPosition) {
std::swap(_pinnedRows[from], _pinnedRows[from - 1]); std::swap(_pinnedRows[from], _pinnedRows[from - 1]);
_pinnedRows[from].yadd = anim::value(_pinnedRows[from].yadd.current() - draggingHeight, 0); _pinnedRows[from].yadd = anim::value(_pinnedRows[from].yadd.current() - draggingHeight, 0);
_pinnedRows[from].animStartTime = now; _pinnedRows[from].animStartTime = now;
shiftHeight -= (*(_shownList->cbegin() + from))->height();
} }
} else if (_dragStart.y() < localPosition.y() && _draggingIndex + 1 < pinnedCount) { } else if (_dragStart.y() < localPosition.y() && _draggingIndex + 1 < pinnedCount) {
shift = floorclamp(localPosition.y() - _dragStart.y() + (draggingHeight / 2), draggingHeight, 0, pinnedCount - _draggingIndex - 1); shift = floorclamp(localPosition.y() - _dragStart.y() + (draggingHeight / 2), draggingHeight, 0, pinnedCount - _draggingIndex - 1);
@ -2108,18 +2111,21 @@ bool InnerWidget::updateReorderPinned(QPoint localPosition) {
std::swap(_pinnedRows[from], _pinnedRows[from + 1]); std::swap(_pinnedRows[from], _pinnedRows[from + 1]);
_pinnedRows[from].yadd = anim::value(_pinnedRows[from].yadd.current() + draggingHeight, 0); _pinnedRows[from].yadd = anim::value(_pinnedRows[from].yadd.current() + draggingHeight, 0);
_pinnedRows[from].animStartTime = now; _pinnedRows[from].animStartTime = now;
shiftHeight += (*(_shownList->cbegin() + from))->height();
} }
} }
if (shift) { if (shift) {
_draggingIndex += shift; _draggingIndex += shift;
_aboveIndex = _draggingIndex; _aboveIndex = _draggingIndex;
_dragStart.setY(_dragStart.y() + shift * _st->height); _dragStart.setY(_dragStart.y() + shiftHeight);
if (!_pinnedShiftAnimation.animating()) { if (!_pinnedShiftAnimation.animating()) {
_pinnedShiftAnimation.start(); _pinnedShiftAnimation.start();
} }
} }
_aboveTopShift = qCeil(_pinnedRows[_aboveIndex].yadd.current()); _aboveTopShift = qCeil(_pinnedRows[_aboveIndex].yadd.current());
_pinnedRows[_draggingIndex].yadd = anim::value(yaddWas - shift * _st->height, localPosition.y() - _dragStart.y()); _pinnedRows[_draggingIndex].yadd = anim::value(
yaddWas - shiftHeight,
localPosition.y() - _dragStart.y());
if (!_pinnedRows[_draggingIndex].animStartTime) { if (!_pinnedRows[_draggingIndex].animStartTime) {
_pinnedRows[_draggingIndex].yadd.finish(); _pinnedRows[_draggingIndex].yadd.finish();
} }
@ -2170,7 +2176,7 @@ bool InnerWidget::pinnedShiftAnimationCallback(crl::time now) {
} }
if (updateMin >= 0) { if (updateMin >= 0) {
const auto minHeight = _st->height; const auto minHeight = _st->height;
const auto maxHeight = st::forumDialogRow.height; const auto maxHeight = st::taggedForumDialogRow.height;
auto top = pinnedOffset(); auto top = pinnedOffset();
auto updateFrom = top + minHeight * (updateMin - 1); auto updateFrom = top + minHeight * (updateMin - 1);
auto updateHeight = maxHeight * (updateMax - updateMin + 3); auto updateHeight = maxHeight * (updateMax - updateMin + 3);

View file

@ -470,7 +470,13 @@ void Row::PaintCornerBadgeFrame(
for (auto i = 0; i != storiesUnreadCount; ++i) { for (auto i = 0; i != storiesUnreadCount; ++i) {
segments.push_back({ storiesUnreadBrush, storiesUnread }); segments.push_back({ storiesUnreadBrush, storiesUnread });
} }
Ui::PaintOutlineSegments(q, outline, segments); if (peer && peer->forum()) {
const auto radius = context.st->photoSize
* Ui::ForumUserpicRadiusMultiplier();
Ui::PaintOutlineSegments(q, outline, radius, segments);
} else {
Ui::PaintOutlineSegments(q, outline, segments);
}
} }
if (subscribed) { if (subscribed) {

View file

@ -1215,6 +1215,7 @@ void Widget::setupShortcuts() {
}); });
request->check(Command::ShowChatMenu, 1) && request->handle([=] { request->check(Command::ShowChatMenu, 1) && request->handle([=] {
if (_inner) { if (_inner) {
Window::ActivateWindow(controller());
_inner->showPeerMenu(); _inner->showPeerMenu();
} }
return true; return true;

View file

@ -442,6 +442,9 @@ void PaintRow(
const auto promoted = (history && history->useTopPromotion()) const auto promoted = (history && history->useTopPromotion())
&& !context.search; && !context.search;
const auto verifyInfo = (from && !from->isSelf())
? from->botVerifyDetails()
: nullptr;
if (promoted) { if (promoted) {
const auto type = history->topPromotionType(); const auto type = history->topPromotionType();
const auto custom = type.isEmpty() const auto custom = type.isEmpty()
@ -453,10 +456,10 @@ void PaintRow(
? tr::lng_badge_psa_default(tr::now) ? tr::lng_badge_psa_default(tr::now)
: custom; : custom;
PaintRowTopRight(p, text, rectForName, context); PaintRowTopRight(p, text, rectForName, context);
} else if (const auto info = from ? from->botVerifyDetails() : nullptr) { } else if (verifyInfo) {
if (!rowBadge.ready(info)) { if (!rowBadge.ready(verifyInfo)) {
rowBadge.set( rowBadge.set(
info, verifyInfo,
from->owner().customEmojiManager().factory(), from->owner().customEmojiManager().factory(),
customEmojiRepaint); customEmojiRepaint);
} }

View file

@ -42,6 +42,15 @@ QSizeF FlipSizeByRotation(const QSizeF &size, int angle) {
return (((angle / 90) % 2) == 1) ? size.transposed() : size; return (((angle / 90) % 2) == 1) ? size.transposed() : size;
} }
[[nodiscard]] QRectF OriginalCrop(QSize outer, QSize inner) {
const auto size = inner.scaled(outer, Qt::KeepAspectRatio);
return QRectF(
(outer.width() - size.width()) / 2,
(outer.height() - size.height()) / 2,
size.width(),
size.height());
}
} // namespace } // namespace
Crop::Crop( Crop::Crop(
@ -60,6 +69,8 @@ Crop::Crop(
, _data(std::move(data)) , _data(std::move(data))
, _cropOriginal(modifications.crop.isValid() , _cropOriginal(modifications.crop.isValid()
? modifications.crop ? modifications.crop
: !_data.exactSize.isEmpty()
? OriginalCrop(_imageSize, _data.exactSize)
: QRectF(QPoint(), _imageSize)) : QRectF(QPoint(), _imageSize))
, _angle(modifications.angle) , _angle(modifications.angle)
, _flipped(modifications.flipped) , _flipped(modifications.flipped)

View file

@ -32,6 +32,7 @@ struct EditorData {
TextWithEntities about; TextWithEntities about;
QString confirm; QString confirm;
QSize exactSize;
CropType cropType = CropType::Rect; CropType cropType = CropType::Rect;
bool keepAspectRatio = false; bool keepAspectRatio = false;
}; };

View file

@ -26,22 +26,26 @@ void OpenWithPreparedFile(
std::shared_ptr<ChatHelpers::Show> show, std::shared_ptr<ChatHelpers::Show> show,
not_null<Ui::PreparedFile*> file, not_null<Ui::PreparedFile*> file,
int previewWidth, int previewWidth,
Fn<void()> &&doneCallback) { Fn<void(bool ok)> &&doneCallback,
QSize exactSize) {
using ImageInfo = Ui::PreparedFileInformation::Image; using ImageInfo = Ui::PreparedFileInformation::Image;
const auto image = std::get_if<ImageInfo>(&file->information->media); const auto image = std::get_if<ImageInfo>(&file->information->media);
if (!image) { if (!image) {
doneCallback(false);
return; return;
} }
const auto photoType = (file->type == Ui::PreparedFile::Type::Photo); const auto photoType = (file->type == Ui::PreparedFile::Type::Photo);
const auto modifiedFileType = (file->type == Ui::PreparedFile::Type::File) const auto modifiedFileType = (file->type == Ui::PreparedFile::Type::File)
&& !image->modifications.empty(); && !image->modifications.empty();
if (!photoType && !modifiedFileType) { if (!photoType && !modifiedFileType) {
doneCallback(false);
return; return;
} }
const auto sideLimit = PhotoSideLimit(); const auto sideLimit = PhotoSideLimit();
auto callback = [=, done = std::move(doneCallback)]( const auto accepted = std::make_shared<bool>();
const PhotoModifications &mods) { auto callback = [=](const PhotoModifications &mods) {
*accepted = true;
image->modifications = mods; image->modifications = mods;
Storage::UpdateImageDetails(*file, previewWidth, sideLimit); Storage::UpdateImageDetails(*file, previewWidth, sideLimit);
{ {
@ -51,19 +55,26 @@ void OpenWithPreparedFile(
? PreparedFile::Type::Photo ? PreparedFile::Type::Photo
: PreparedFile::Type::File; : PreparedFile::Type::File;
} }
done(); doneCallback(true);
}; };
auto copy = image->data; auto copy = image->data;
const auto fileImage = std::make_shared<Image>(std::move(copy)); const auto fileImage = std::make_shared<Image>(std::move(copy));
const auto keepRatio = !exactSize.isEmpty();
auto editor = base::make_unique_q<PhotoEditor>( auto editor = base::make_unique_q<PhotoEditor>(
parent, parent,
show, show,
show, show,
fileImage, fileImage,
image->modifications); image->modifications,
EditorData{ .exactSize = exactSize, .keepAspectRatio = keepRatio });
const auto raw = editor.get(); const auto raw = editor.get();
auto layer = std::make_unique<LayerWidget>(parent, std::move(editor)); auto layer = std::make_unique<LayerWidget>(parent, std::move(editor));
InitEditorLayer(layer.get(), raw, std::move(callback)); InitEditorLayer(layer.get(), raw, std::move(callback));
QObject::connect(layer.get(), &QObject::destroyed, [=] {
if (!*accepted) {
doneCallback(false);
}
});
show->showLayer(std::move(layer), Ui::LayerOption::KeepOther); show->showLayer(std::move(layer), Ui::LayerOption::KeepOther);
} }

View file

@ -34,7 +34,8 @@ void OpenWithPreparedFile(
std::shared_ptr<ChatHelpers::Show> show, std::shared_ptr<ChatHelpers::Show> show,
not_null<Ui::PreparedFile*> file, not_null<Ui::PreparedFile*> file,
int previewWidth, int previewWidth,
Fn<void()> &&doneCallback); Fn<void(bool ok)> &&doneCallback,
QSize exactSize = {});
void PrepareProfilePhoto( void PrepareProfilePhoto(
not_null<QWidget*> parent, not_null<QWidget*> parent,

View file

@ -385,6 +385,7 @@ QByteArray SerializeMessage(
}; };
const auto pushPhoto = [&](const Image &image) { const auto pushPhoto = [&](const Image &image) {
pushPath(image.file, "photo"); pushPath(image.file, "photo");
push("photo_file_size", image.file.size);
if (image.width && image.height) { if (image.width && image.height) {
push("width", image.width); push("width", image.width);
push("height", image.height); push("height", image.height);
@ -696,8 +697,10 @@ QByteArray SerializeMessage(
}, [&](const Document &data) { }, [&](const Document &data) {
pushPath(data.file, "file"); pushPath(data.file, "file");
push("file_name", data.name); push("file_name", data.name);
push("file_size", data.file.size);
if (data.thumb.width > 0) { if (data.thumb.width > 0) {
pushPath(data.thumb.file, "thumbnail"); pushPath(data.thumb.file, "thumbnail");
push("thumbnail_file_size", data.thumb.file.size);
} }
const auto pushType = [&](const QByteArray &value) { const auto pushType = [&](const QByteArray &value) {
push("media_type", value); push("media_type", value);
@ -739,6 +742,7 @@ QByteArray SerializeMessage(
})); }));
if (!data.vcard.content.isEmpty()) { if (!data.vcard.content.isEmpty()) {
pushPath(data.vcard, "contact_vcard"); pushPath(data.vcard, "contact_vcard");
push("contact_vcard_file_size", data.vcard.size);
} }
}, [&](const GeoPoint &data) { }, [&](const GeoPoint &data) {
pushBare( pushBare(

View file

@ -55,6 +55,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "boxes/about_sponsored_box.h" #include "boxes/about_sponsored_box.h"
#include "boxes/delete_messages_box.h" #include "boxes/delete_messages_box.h"
#include "boxes/report_messages_box.h" #include "boxes/report_messages_box.h"
#include "boxes/star_gift_box.h" // ShowStarGiftBox
#include "boxes/sticker_set_box.h" #include "boxes/sticker_set_box.h"
#include "boxes/translate_box.h" #include "boxes/translate_box.h"
#include "chat_helpers/message_field.h" #include "chat_helpers/message_field.h"
@ -1627,6 +1628,7 @@ void HistoryInner::mouseMoveEvent(QMouseEvent *e) {
mouseReleaseEvent(e); mouseReleaseEvent(e);
} }
if (reallyMoved) { if (reallyMoved) {
_mouseActive = true;
lastGlobalPosition = e->globalPos(); lastGlobalPosition = e->globalPos();
if (!buttonsPressed || (_scrollDateLink && ClickHandler::getPressed() == _scrollDateLink)) { if (!buttonsPressed || (_scrollDateLink && ClickHandler::getPressed() == _scrollDateLink)) {
keepScrollDateForNow(); keepScrollDateForNow();
@ -1673,6 +1675,7 @@ void HistoryInner::mousePressEvent(QMouseEvent *e) {
e->accept(); e->accept();
return; // ignore mouse press, that was hiding context menu return; // ignore mouse press, that was hiding context menu
} }
_mouseActive = true;
mouseActionStart(e->globalPos(), e->button()); mouseActionStart(e->globalPos(), e->button());
} }
@ -2835,6 +2838,29 @@ void HistoryInner::showContextMenu(QContextMenuEvent *e, bool showFromTouch) {
_menu->addAction(tr::lng_profile_copy_phone(tr::now), [=] { _menu->addAction(tr::lng_profile_copy_phone(tr::now), [=] {
QGuiApplication::clipboard()->setText(phone); QGuiApplication::clipboard()->setText(phone);
}, &st::menuIconCopy); }, &st::menuIconCopy);
} else if (const auto gift = media->gift()) {
const auto peer = item->history()->peer;
const auto user = peer->asUser();
if (!user
|| (!user->isInaccessible()
&& !user->isNotificationsUser())) {
const auto controller = _controller;
const auto starGiftUpgrade = gift->upgrade
&& (gift->type == Data::GiftType::StarGift);
const auto isGift = gift->slug.isEmpty()
|| !gift->channel;
const auto out = item->out();
const auto outgoingGift = isGift
&& (starGiftUpgrade ? !out : out);
if (outgoingGift) {
_menu->addAction(
tr::lng_context_gift_send(tr::now),
[=] {
Ui::ShowStarGiftBox(controller, peer);
},
&st::menuIconGiftPremium);
}
}
} }
} }
if (!item->isService() && view && actionText.isEmpty()) { if (!item->isService() && view && actionText.isEmpty()) {
@ -3314,7 +3340,7 @@ void HistoryInner::checkActivation() {
session().data().histories().readInboxTill(view->data()); session().data().histories().readInboxTill(view->data());
} }
void HistoryInner::recountHistoryGeometry() { void HistoryInner::recountHistoryGeometry(bool initial) {
_contentWidth = _scroll->width(); _contentWidth = _scroll->width();
if (_history->hasPendingResizedItems() if (_history->hasPendingResizedItems()
@ -3372,7 +3398,7 @@ void HistoryInner::recountHistoryGeometry() {
} }
auto historyPaddingTopDelta = (newHistoryPaddingTop - oldHistoryPaddingTop); auto historyPaddingTopDelta = (newHistoryPaddingTop - oldHistoryPaddingTop);
if (historyPaddingTopDelta != 0) { if (!initial && historyPaddingTopDelta != 0) {
if (_history->scrollTopItem) { if (_history->scrollTopItem) {
_history->scrollTopOffset += historyPaddingTopDelta; _history->scrollTopOffset += historyPaddingTopDelta;
} else if (_migrated && _migrated->scrollTopItem) { } else if (_migrated && _migrated->scrollTopItem) {
@ -3567,6 +3593,7 @@ void HistoryInner::setShownPinned(HistoryItem *item) {
} }
void HistoryInner::enterEventHook(QEnterEvent *e) { void HistoryInner::enterEventHook(QEnterEvent *e) {
_mouseActive = true;
mouseActionUpdate(QCursor::pos()); mouseActionUpdate(QCursor::pos());
return TWidget::enterEventHook(e); return TWidget::enterEventHook(e);
} }
@ -3583,6 +3610,7 @@ void HistoryInner::leaveEventHook(QEvent *e) {
_cursor = style::cur_default; _cursor = style::cur_default;
setCursor(_cursor); setCursor(_cursor);
} }
_mouseActive = false;
return TWidget::leaveEventHook(e); return TWidget::leaveEventHook(e);
} }
@ -3933,7 +3961,7 @@ auto HistoryInner::reactionButtonParameters(
} }
void HistoryInner::mouseActionUpdate() { void HistoryInner::mouseActionUpdate() {
if (hasPendingResizedItems()) { if (hasPendingResizedItems() || !_mouseActive) {
return; return;
} }

View file

@ -121,7 +121,7 @@ public:
void setItemsRevealHeight(int revealHeight); void setItemsRevealHeight(int revealHeight);
void changeItemsRevealHeight(int revealHeight); void changeItemsRevealHeight(int revealHeight);
void checkActivation(); void checkActivation();
void recountHistoryGeometry(); void recountHistoryGeometry(bool initial = false);
void updateSize(); void updateSize();
void setShownPinned(HistoryItem *item); void setShownPinned(HistoryItem *item);
@ -499,6 +499,7 @@ private:
HistoryItem *_dragStateItem = nullptr; HistoryItem *_dragStateItem = nullptr;
CursorState _mouseCursorState = CursorState(); CursorState _mouseCursorState = CursorState();
uint16 _mouseTextSymbol = 0; uint16 _mouseTextSymbol = 0;
bool _mouseActive = false;
bool _dragStateUserpic = false; bool _dragStateUserpic = false;
bool _pressWasInactive = false; bool _pressWasInactive = false;
bool _recountedAfterPendingResizedItems = false; bool _recountedAfterPendingResizedItems = false;

View file

@ -310,13 +310,19 @@ std::unique_ptr<Data::Media> HistoryItem::CreateMedia(
} }
return document->match([&](const MTPDdocument &document) -> Result { return document->match([&](const MTPDdocument &document) -> Result {
const auto list = media.valt_documents(); const auto list = media.valt_documents();
return std::make_unique<Data::MediaFile>( const auto owner = &item->history()->owner();
item, const auto data = owner->processDocument(document);
item->history()->owner().processDocument(document, list), using Args = Data::MediaFile::Args;
media.is_nopremium(), return std::make_unique<Data::MediaFile>(item, data, Args{
list && !list->v.isEmpty(), .ttlSeconds = media.vttl_seconds().value_or_empty(),
media.is_spoiler(), .videoCover = (media.vvideo_cover()
media.vttl_seconds().value_or_empty()); ? owner->processPhoto(*media.vvideo_cover()).get()
: nullptr),
.videoTimestamp = media.vvideo_timestamp().value_or_empty(),
.hasQualitiesList = list && !list->v.isEmpty(),
.skipPremiumEffect = media.is_nopremium(),
.spoiler = media.is_spoiler(),
});
}, [](const MTPDdocumentEmpty &) -> Result { }, [](const MTPDdocumentEmpty &) -> Result {
return nullptr; return nullptr;
}); });
@ -693,16 +699,12 @@ HistoryItem::HistoryItem(
: HistoryItem(history, fields) { : HistoryItem(history, fields) {
createComponentsHelper(std::move(fields)); createComponentsHelper(std::move(fields));
const auto skipPremiumEffect = !history->session().premium();
const auto video = document->video(); const auto video = document->video();
const auto spoiler = false; using Args = Data::MediaFile::Args;
_media = std::make_unique<Data::MediaFile>( _media = std::make_unique<Data::MediaFile>(this, document, Args{
this, .hasQualitiesList = video && !video->qualities.empty(),
document, .skipPremiumEffect = !history->session().premium(),
skipPremiumEffect, });
video && !video->qualities.empty(),
spoiler,
/*ttlSeconds = */0);
setText(caption); setText(caption);
} }
@ -764,6 +766,7 @@ HistoryItem::HistoryItem(
0, 0,
QString(), QString(),
false, false,
false,
0); 0);
auto webpageMedia = std::make_unique<Data::MediaWebPage>( auto webpageMedia = std::make_unique<Data::MediaWebPage>(
this, this,
@ -1903,17 +1906,12 @@ void HistoryItem::applyChanges(not_null<Data::Story*> story) {
} }
void HistoryItem::setStoryFields(not_null<Data::Story*> story) { void HistoryItem::setStoryFields(not_null<Data::Story*> story) {
const auto spoiler = false;
if (const auto photo = story->photo()) { if (const auto photo = story->photo()) {
const auto spoiler = false;
_media = std::make_unique<Data::MediaPhoto>(this, photo, spoiler); _media = std::make_unique<Data::MediaPhoto>(this, photo, spoiler);
} else if (const auto document = story->document()) { } else if (const auto document = story->document()) {
_media = std::make_unique<Data::MediaFile>( using Args = Data::MediaFile::Args;
this, _media = std::make_unique<Data::MediaFile>(this, document, Args{});
document,
/*skipPremiumEffect=*/false,
/*hasQualitiesList=*/false,
spoiler,
/*ttlSeconds = */0);
} }
setText(story->caption()); setText(story->caption());
if (story->pinnedToTop()) { if (story->pinnedToTop()) {
@ -2693,14 +2691,16 @@ bool HistoryItem::canReact() const {
return true; return true;
} }
void HistoryItem::addPaidReaction(int count, std::optional<bool> anonymous) { void HistoryItem::addPaidReaction(
int count,
std::optional<PeerId> shownPeer) {
Expects(count >= 0); Expects(count >= 0);
Expects(_history->peer->isBroadcast() || isDiscussionPost()); Expects(_history->peer->isBroadcast() || isDiscussionPost());
if (!_reactions) { if (!_reactions) {
_reactions = std::make_unique<Data::MessageReactions>(this); _reactions = std::make_unique<Data::MessageReactions>(this);
} }
_reactions->scheduleSendPaid(count, anonymous); _reactions->scheduleSendPaid(count, shownPeer);
if (count > 0) { if (count > 0) {
_history->owner().notifyItemDataChange(this); _history->owner().notifyItemDataChange(this);
} }
@ -2796,8 +2796,10 @@ int HistoryItem::reactionsPaidScheduled() const {
return _reactions ? _reactions->scheduledPaid() : 0; return _reactions ? _reactions->scheduledPaid() : 0;
} }
bool HistoryItem::reactionsLocalAnonymous() const { PeerId HistoryItem::reactionsLocalShownPeer() const {
return _reactions ? _reactions->localPaidAnonymous() : false; return _reactions
? _reactions->localPaidShownPeer()
: _history->session().userPeerId();
} }
bool HistoryItem::reactionsAreTags() const { bool HistoryItem::reactionsAreTags() const {
@ -2825,9 +2827,8 @@ auto HistoryItem::topPaidReactionsWithLocal() const
result, result,
[](const TopPaid &entry) { return entry.my != 0; }); [](const TopPaid &entry) { return entry.my != 0; });
const auto peerForMine = [&] { const auto peerForMine = [&] {
return _reactions->localPaidAnonymous() const auto peerId = _reactions->localPaidShownPeer();
? nullptr return peerId ? history()->owner().peer(peerId).get() : nullptr;
: history()->session().user().get();
}; };
if (const auto local = _reactions->localPaidCount()) { if (const auto local = _reactions->localPaidCount()) {
const auto top = [&](int mine) { const auto top = [&](int mine) {

View file

@ -458,7 +458,7 @@ public:
void toggleReaction( void toggleReaction(
const Data::ReactionId &reaction, const Data::ReactionId &reaction,
HistoryReactionSource source); HistoryReactionSource source);
void addPaidReaction(int count, std::optional<bool> anonymous = {}); void addPaidReaction(int count, std::optional<PeerId> shownPeer = {});
void cancelScheduledPaidReaction(); void cancelScheduledPaidReaction();
[[nodiscard]] Data::PaidReactionSend startPaidReactionSending(); [[nodiscard]] Data::PaidReactionSend startPaidReactionSending();
void finishPaidReactionSending( void finishPaidReactionSending(
@ -476,7 +476,7 @@ public:
[[nodiscard]] auto topPaidReactionsWithLocal() const [[nodiscard]] auto topPaidReactionsWithLocal() const
-> std::vector<Data::MessageReactionsTopPaid>; -> std::vector<Data::MessageReactionsTopPaid>;
[[nodiscard]] int reactionsPaidScheduled() const; [[nodiscard]] int reactionsPaidScheduled() const;
[[nodiscard]] bool reactionsLocalAnonymous() const; [[nodiscard]] PeerId reactionsLocalShownPeer() const;
[[nodiscard]] bool canViewReactions() const; [[nodiscard]] bool canViewReactions() const;
[[nodiscard]] std::vector<Data::ReactionId> chosenReactions() const; [[nodiscard]] std::vector<Data::ReactionId> chosenReactions() const;
[[nodiscard]] Data::ReactionId lookupUnreadReaction( [[nodiscard]] Data::ReactionId lookupUnreadReaction(

View file

@ -9,6 +9,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "api/api_text_entities.h" #include "api/api_text_entities.h"
#include "base/qt/qt_key_modifiers.h" #include "base/qt/qt_key_modifiers.h"
#include "base/options.h"
#include "lang/lang_keys.h" #include "lang/lang_keys.h"
#include "ui/effects/ripple_animation.h" #include "ui/effects/ripple_animation.h"
#include "ui/effects/spoiler_mess.h" #include "ui/effects/spoiler_mess.h"
@ -67,8 +68,20 @@ namespace {
const auto kPsaForwardedPrefix = "cloud_lng_forwarded_psa_"; const auto kPsaForwardedPrefix = "cloud_lng_forwarded_psa_";
base::options::toggle FastButtonsModeOption({
.id = kOptionFastButtonsMode,
.name = "Fast buttons mode",
.description = "Trigger inline keyboard buttons by 1-9 keyboard keys.",
});
} // namespace } // namespace
const char kOptionFastButtonsMode[] = "fast-buttons-mode";
bool FastButtonsMode() {
return FastButtonsModeOption.value();
}
void HistoryMessageVia::create( void HistoryMessageVia::create(
not_null<Data::Session*> owner, not_null<Data::Session*> owner,
UserId userId) { UserId userId) {
@ -946,10 +959,10 @@ void ReplyKeyboard::paint(
} }
bool ReplyKeyboard::hasFastButtonMode() const { bool ReplyKeyboard::hasFastButtonMode() const {
return _item->inlineReplyKeyboard() return FastButtonsMode()
&& _item->inlineReplyKeyboard()
&& (_item == _item->history()->lastMessage()) && (_item == _item->history()->lastMessage())
&& _item->history()->session().supportMode() && _item->history()->session().fastButtonsBots().enabled(
&& _item->history()->session().supportHelper().fastButtonMode(
_item->history()->peer); _item->history()->peer);
} }

View file

@ -54,6 +54,9 @@ namespace style {
struct BotKeyboardButton; struct BotKeyboardButton;
} // namespace style } // namespace style
extern const char kOptionFastButtonsMode[];
[[nodiscard]] bool FastButtonsMode();
struct HistoryMessageVia : public RuntimeComponent<HistoryMessageVia, HistoryItem> { struct HistoryMessageVia : public RuntimeComponent<HistoryMessageVia, HistoryItem> {
void create(not_null<Data::Session*> owner, UserId userId); void create(not_null<Data::Session*> owner, UserId userId);
void resize(int32 availw) const; void resize(int32 availw) const;

View file

@ -22,6 +22,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "boxes/moderate_messages_box.h" #include "boxes/moderate_messages_box.h"
#include "boxes/premium_limits_box.h" #include "boxes/premium_limits_box.h"
#include "boxes/premium_preview_box.h" #include "boxes/premium_preview_box.h"
#include "boxes/star_gift_box.h"
#include "boxes/peers/edit_peer_permissions_box.h" // ShowAboutGigagroup. #include "boxes/peers/edit_peer_permissions_box.h" // ShowAboutGigagroup.
#include "boxes/peers/edit_peer_requests_box.h" #include "boxes/peers/edit_peer_requests_box.h"
#include "core/file_utilities.h" #include "core/file_utilities.h"
@ -409,6 +410,7 @@ HistoryWidget::HistoryWidget(
_joinChannel->addClickHandler([=] { joinChannel(); }); _joinChannel->addClickHandler([=] { joinChannel(); });
_muteUnmute->addClickHandler([=] { toggleMuteUnmute(); }); _muteUnmute->addClickHandler([=] { toggleMuteUnmute(); });
_discuss->addClickHandler([=] { goToDiscussionGroup(); }); _discuss->addClickHandler([=] { goToDiscussionGroup(); });
setupGiftToChannelButton();
_reportMessages->addClickHandler([=] { reportSelectedMessages(); }); _reportMessages->addClickHandler([=] { reportSelectedMessages(); });
_field->submits( _field->submits(
) | rpl::start_with_next([=](Qt::KeyboardModifiers modifiers) { ) | rpl::start_with_next([=](Qt::KeyboardModifiers modifiers) {
@ -1087,6 +1089,18 @@ void HistoryWidget::refreshJoinChannelText() {
} }
} }
void HistoryWidget::refreshGiftToChannelShown() {
if (!_giftToChannelIn || !_giftToChannelOut) {
return;
}
const auto channel = _peer->asChannel();
const auto shown = channel
&& channel->isBroadcast()
&& channel->stargiftsAvailable();
_giftToChannelIn->setVisible(shown);
_giftToChannelOut->setVisible(shown);
}
void HistoryWidget::refreshTopBarActiveChat() { void HistoryWidget::refreshTopBarActiveChat() {
const auto state = computeDialogsEntryState(); const auto state = computeDialogsEntryState();
_topBar->setActiveChat(state, _history->sendActionPainter()); _topBar->setActiveChat(state, _history->sendActionPainter());
@ -2090,6 +2104,7 @@ void HistoryWidget::setupShortcuts() {
return true; return true;
}); });
request->check(Command::ShowChatMenu, 1) && request->handle([=] { request->check(Command::ShowChatMenu, 1) && request->handle([=] {
Window::ActivateWindow(controller());
_topBar->showPeerMenu(); _topBar->showPeerMenu();
return true; return true;
}); });
@ -2112,6 +2127,25 @@ void HistoryWidget::setupShortcuts() {
}, lifetime()); }, lifetime());
} }
void HistoryWidget::setupGiftToChannelButton() {
const auto setupButton = [=](not_null<Ui::RpWidget*> parent) {
auto *button = Ui::CreateChild<Ui::IconButton>(
parent.get(),
st::historyGiftToChannel);
parent->widthValue() | rpl::start_with_next([=](int width) {
button->moveToRight(0, 0);
}, button->lifetime());
button->setClickedCallback([=] {
if (_peer) {
Ui::ShowStarGiftBox(controller(), _peer);
}
});
return button;
};
_giftToChannelIn = setupButton(_muteUnmute);
_giftToChannelOut = setupButton(_joinChannel);
}
void HistoryWidget::pushReplyReturn(not_null<HistoryItem*> item) { void HistoryWidget::pushReplyReturn(not_null<HistoryItem*> item) {
if (item->history() != _history && item->history() != _migrated) { if (item->history() != _history && item->history() != _migrated) {
return; return;
@ -2270,15 +2304,12 @@ bool HistoryWidget::insideJumpToEndInsteadOfToUnread() const {
} }
void HistoryWidget::showHistory( void HistoryWidget::showHistory(
const PeerId &peerId, PeerId peerId,
MsgId showAtMsgId, MsgId showAtMsgId,
const TextWithEntities &highlightPart, const Window::SectionShow &params) {
int highlightPartOffsetHint) {
_pinnedClickedId = FullMsgId(); _pinnedClickedId = FullMsgId();
_minPinnedId = std::nullopt; _minPinnedId = std::nullopt;
_showAtMsgHighlightPart = {}; _showAtMsgParams = {};
_showAtMsgHighlightPartOffsetHint = 0;
const auto wasState = controller()->dialogsEntryStateCurrent(); const auto wasState = controller()->dialogsEntryStateCurrent();
const auto startBot = (showAtMsgId == ShowAndStartBotMsgId); const auto startBot = (showAtMsgId == ShowAndStartBotMsgId);
@ -2328,16 +2359,10 @@ void HistoryWidget::showHistory(
).arg(_history->inboxReadTillId().bare ).arg(_history->inboxReadTillId().bare
).arg(Logs::b(_history->loadedAtBottom()) ).arg(Logs::b(_history->loadedAtBottom())
).arg(showAtMsgId.bare)); ).arg(showAtMsgId.bare));
delayedShowAt( delayedShowAt(showAtMsgId, params);
showAtMsgId,
highlightPart,
highlightPartOffsetHint);
} else if (_showAtMsgId != showAtMsgId) { } else if (_showAtMsgId != showAtMsgId) {
clearAllLoadRequests(); clearAllLoadRequests();
setMsgId( setMsgId(showAtMsgId, params);
showAtMsgId,
highlightPart,
highlightPartOffsetHint);
firstLoadMessages(); firstLoadMessages();
doneShow(); doneShow();
} }
@ -2357,10 +2382,7 @@ void HistoryWidget::showHistory(
_cornerButtons.skipReplyReturn(skipId); _cornerButtons.skipReplyReturn(skipId);
} }
setMsgId( setMsgId(showAtMsgId, params);
showAtMsgId,
highlightPart,
highlightPartOffsetHint);
if (_historyInited) { if (_historyInited) {
DEBUG_LOG(("JumpToEnd(%1, %2, %3): " DEBUG_LOG(("JumpToEnd(%1, %2, %3): "
"Showing instant at %4." "Showing instant at %4."
@ -2465,8 +2487,7 @@ void HistoryWidget::showHistory(
clearInlineBot(); clearInlineBot();
_showAtMsgId = showAtMsgId; _showAtMsgId = showAtMsgId;
_showAtMsgHighlightPart = highlightPart; _showAtMsgParams = params;
_showAtMsgHighlightPartOffsetHint = highlightPartOffsetHint;
_historyInited = false; _historyInited = false;
_contactStatus = nullptr; _contactStatus = nullptr;
_businessBotStatus = nullptr; _businessBotStatus = nullptr;
@ -2485,6 +2506,8 @@ void HistoryWidget::showHistory(
) | rpl::start_with_next([=] { ) | rpl::start_with_next([=] {
updateControlsGeometry(); updateControlsGeometry();
}, _contactStatus->bar().lifetime()); }, _contactStatus->bar().lifetime());
refreshGiftToChannelShown();
if (const auto user = _peer->asUser()) { if (const auto user = _peer->asUser()) {
_businessBotStatus = std::make_unique<BusinessBotStatus>( _businessBotStatus = std::make_unique<BusinessBotStatus>(
controller(), controller(),
@ -2977,14 +3000,12 @@ void HistoryWidget::refreshSilentToggle() {
} }
void HistoryWidget::setupFastButtonMode() { void HistoryWidget::setupFastButtonMode() {
if (!session().supportMode()) {
return;
}
const auto field = _field->rawTextEdit(); const auto field = _field->rawTextEdit();
base::install_event_filter(field, [=](not_null<QEvent*> e) { base::install_event_filter(field, [=](not_null<QEvent*> e) {
if (e->type() != QEvent::KeyPress if (e->type() != QEvent::KeyPress
|| !_history || !_history
|| !session().supportHelper().fastButtonMode(_history->peer) || !FastButtonsMode()
|| !session().fastButtonsBots().enabled(_history->peer)
|| !_field->getLastText().isEmpty()) { || !_field->getLastText().isEmpty()) {
return base::EventFilterResult::Continue; return base::EventFilterResult::Continue;
} }
@ -3737,10 +3758,7 @@ void HistoryWidget::messagesReceived(
} }
_delayedShowAtRequest = 0; _delayedShowAtRequest = 0;
setMsgId( setMsgId(_delayedShowAtMsgId, _delayedShowAtMsgParams);
_delayedShowAtMsgId,
_delayedShowAtMsgHighlightPart,
_delayedShowAtMsgHighlightPartOffsetHint);
historyLoaded(); historyLoaded();
} }
if (session().supportMode()) { if (session().supportMode()) {
@ -3992,15 +4010,11 @@ void HistoryWidget::loadMessagesDown() {
void HistoryWidget::delayedShowAt( void HistoryWidget::delayedShowAt(
MsgId showAtMsgId, MsgId showAtMsgId,
const TextWithEntities &highlightPart, const Window::SectionShow &params) {
int highlightPartOffsetHint) {
if (!_history) { if (!_history) {
return; return;
} }
if (_delayedShowAtMsgHighlightPart != highlightPart) { _delayedShowAtMsgParams = params;
_delayedShowAtMsgHighlightPart = highlightPart;
}
_delayedShowAtMsgHighlightPartOffsetHint = highlightPartOffsetHint;
if (_delayedShowAtRequest && _delayedShowAtMsgId == showAtMsgId) { if (_delayedShowAtRequest && _delayedShowAtMsgId == showAtMsgId) {
return; return;
} }
@ -4135,7 +4149,10 @@ void HistoryWidget::preloadHistoryIfNeeded() {
preloadHistoryByScroll(); preloadHistoryByScroll();
checkReplyReturns(); checkReplyReturns();
} }
if (clearMaybeSendStart() && !_history->isDisplayedEmpty()) { const auto hasNonEmpty = _history->findFirstNonEmpty();
const auto readyForBotStart = hasNonEmpty
|| (_history->loadedAtTop() && _history->loadedAtBottom());
if (readyForBotStart && clearMaybeSendStart() && hasNonEmpty) {
sendBotStartCommand(); sendBotStartCommand();
} }
} }
@ -4663,12 +4680,8 @@ PeerData *HistoryWidget::peer() const {
// Sometimes _showAtMsgId is set directly. // Sometimes _showAtMsgId is set directly.
void HistoryWidget::setMsgId( void HistoryWidget::setMsgId(
MsgId showAtMsgId, MsgId showAtMsgId,
const TextWithEntities &highlightPart, const Window::SectionShow &params) {
int highlightPartOffsetHint) { _showAtMsgParams = params;
if (_showAtMsgHighlightPart != highlightPart) {
_showAtMsgHighlightPart = highlightPart;
}
_showAtMsgHighlightPartOffsetHint = highlightPartOffsetHint;
if (_showAtMsgId != showAtMsgId) { if (_showAtMsgId != showAtMsgId) {
_showAtMsgId = showAtMsgId; _showAtMsgId = showAtMsgId;
if (_history) { if (_history) {
@ -6429,8 +6442,8 @@ int HistoryWidget::countInitialScrollTop() {
enqueueMessageHighlight({ enqueueMessageHighlight({
item, item,
base::take(_showAtMsgHighlightPart), base::take(_showAtMsgParams.highlightPart),
base::take(_showAtMsgHighlightPartOffsetHint), base::take(_showAtMsgParams.highlightPartOffsetHint),
}); });
const auto result = itemTopForHighlight(view); const auto result = itemTopForHighlight(view);
createUnreadBarIfBelowVisibleArea(result); createUnreadBarIfBelowVisibleArea(result);
@ -6647,6 +6660,22 @@ void HistoryWidget::updateHistoryGeometry(
} }
const auto toY = std::clamp(newScrollTop, 0, _scroll->scrollTopMax()); const auto toY = std::clamp(newScrollTop, 0, _scroll->scrollTopMax());
synteticScrollToY(toY); synteticScrollToY(toY);
if (initial && _showAtMsgId) {
const auto timestamp = base::take(_showAtMsgParams.videoTimestamp);
if (timestamp.has_value()) {
const auto item = session().data().message(_peer, _showAtMsgId);
const auto media = item ? item->media() : nullptr;
const auto document = media ? media->document() : nullptr;
if (document && document->isVideoFile()) {
controller()->openDocument(
document,
true,
{ item->fullId() },
nullptr,
timestamp);
}
}
}
} }
void HistoryWidget::revealItemsCallback() { void HistoryWidget::revealItemsCallback() {
@ -6748,7 +6777,7 @@ void HistoryWidget::startMessageSendingAnimation(
void HistoryWidget::updateListSize() { void HistoryWidget::updateListSize() {
Expects(_list != nullptr); Expects(_list != nullptr);
_list->recountHistoryGeometry(); _list->recountHistoryGeometry(!_historyInited);
auto washidden = _scroll->isHidden(); auto washidden = _scroll->isHidden();
if (washidden) { if (washidden) {
_scroll->show(); _scroll->show();
@ -8517,9 +8546,13 @@ void HistoryWidget::fullInfoUpdated() {
handlePeerUpdate(); handlePeerUpdate();
checkSuggestToGigagroup(); checkSuggestToGigagroup();
if (clearMaybeSendStart() && !_history->isDisplayedEmpty()) { const auto hasNonEmpty = _history->findFirstNonEmpty();
const auto readyForBotStart = hasNonEmpty
|| (_history->loadedAtTop() && _history->loadedAtBottom());
if (readyForBotStart && clearMaybeSendStart() && hasNonEmpty) {
sendBotStartCommand(); sendBotStartCommand();
} }
refreshGiftToChannelShown();
} }
if (updateCmdStartShown()) { if (updateCmdStartShown()) {
refresh = true; refresh = true;

View file

@ -16,6 +16,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "chat_helpers/field_characters_count_manager.h" #include "chat_helpers/field_characters_count_manager.h"
#include "data/data_report.h" #include "data/data_report.h"
#include "window/section_widget.h" #include "window/section_widget.h"
#include "window/window_session_controller.h"
#include "ui/widgets/fields/input_field.h" #include "ui/widgets/fields/input_field.h"
#include "mtproto/sender.h" #include "mtproto/sender.h"
@ -86,10 +87,6 @@ namespace Webrtc {
enum class RecordAvailability : uchar; enum class RecordAvailability : uchar;
} // namespace Webrtc } // namespace Webrtc
namespace Window {
class SessionController;
} // namespace Window
namespace ChatHelpers { namespace ChatHelpers {
class TabbedPanel; class TabbedPanel;
class TabbedSelector; class TabbedSelector;
@ -160,10 +157,7 @@ public:
void loadMessages(); void loadMessages();
void loadMessagesDown(); void loadMessagesDown();
void firstLoadMessages(); void firstLoadMessages();
void delayedShowAt( void delayedShowAt(MsgId showAtMsgId, const Window::SectionShow &params);
MsgId showAtMsgId,
const TextWithEntities &highlightPart,
int highlightPartOffsetHint);
bool updateReplaceMediaButton(); bool updateReplaceMediaButton();
void updateFieldPlaceholder(); void updateFieldPlaceholder();
@ -176,10 +170,7 @@ public:
History *history() const; History *history() const;
PeerData *peer() const; PeerData *peer() const;
void setMsgId( void setMsgId(MsgId showAtMsgId, const Window::SectionShow &params = {});
MsgId showAtMsgId,
const TextWithEntities &highlightPart = {},
int highlightPartOffsetHint = 0);
MsgId msgId() const; MsgId msgId() const;
bool hasTopBarShadow() const { bool hasTopBarShadow() const {
@ -244,10 +235,9 @@ public:
bool applyDraft( bool applyDraft(
FieldHistoryAction fieldHistoryAction = FieldHistoryAction::Clear); FieldHistoryAction fieldHistoryAction = FieldHistoryAction::Clear);
void showHistory( void showHistory(
const PeerId &peer, PeerId peerId,
MsgId showAtMsgId, MsgId showAtMsgId,
const TextWithEntities &highlightPart = {}, const Window::SectionShow &params = {});
int highlightPartOffsetHint = 0);
void setChooseReportMessagesDetails( void setChooseReportMessagesDetails(
Data::ReportInput reportInput, Data::ReportInput reportInput,
Fn<void(std::vector<MsgId>)> callback); Fn<void(std::vector<MsgId>)> callback);
@ -406,6 +396,7 @@ private:
void refreshTopBarActiveChat(); void refreshTopBarActiveChat();
void refreshJoinChannelText(); void refreshJoinChannelText();
void refreshGiftToChannelShown();
void requestMessageData(MsgId msgId); void requestMessageData(MsgId msgId);
void messageDataReceived(not_null<PeerData*> peer, MsgId msgId); void messageDataReceived(not_null<PeerData*> peer, MsgId msgId);
@ -527,6 +518,7 @@ private:
} }
void setupShortcuts(); void setupShortcuts();
void setupGiftToChannelButton();
void handlePeerMigration(); void handlePeerMigration();
@ -731,8 +723,7 @@ private:
bool _canSendTexts = false; bool _canSendTexts = false;
MsgId _showAtMsgId = ShowAtUnreadMsgId; MsgId _showAtMsgId = ShowAtUnreadMsgId;
base::flat_set<MsgId> _topicsRequested; base::flat_set<MsgId> _topicsRequested;
TextWithEntities _showAtMsgHighlightPart; Window::SectionShow _showAtMsgParams;
int _showAtMsgHighlightPartOffsetHint = 0;
bool _showAndMaybeSendStart = false; bool _showAndMaybeSendStart = false;
int _firstLoadRequest = 0; // Not real mtpRequestId. int _firstLoadRequest = 0; // Not real mtpRequestId.
@ -740,8 +731,7 @@ private:
int _preloadDownRequest = 0; // Not real mtpRequestId. int _preloadDownRequest = 0; // Not real mtpRequestId.
MsgId _delayedShowAtMsgId = -1; MsgId _delayedShowAtMsgId = -1;
TextWithEntities _delayedShowAtMsgHighlightPart; Window::SectionShow _delayedShowAtMsgParams;
int _delayedShowAtMsgHighlightPartOffsetHint = 0;
int _delayedShowAtRequest = 0; // Not real mtpRequestId. int _delayedShowAtRequest = 0; // Not real mtpRequestId.
History *_supportPreloadHistory = nullptr; History *_supportPreloadHistory = nullptr;
@ -789,6 +779,8 @@ private:
object_ptr<Ui::FlatButton> _botStart; object_ptr<Ui::FlatButton> _botStart;
object_ptr<Ui::FlatButton> _joinChannel; object_ptr<Ui::FlatButton> _joinChannel;
object_ptr<Ui::FlatButton> _muteUnmute; object_ptr<Ui::FlatButton> _muteUnmute;
QPointer<Ui::IconButton> _giftToChannelIn;
QPointer<Ui::IconButton> _giftToChannelOut;
object_ptr<Ui::FlatButton> _discuss; object_ptr<Ui::FlatButton> _discuss;
object_ptr<Ui::FlatButton> _reportMessages; object_ptr<Ui::FlatButton> _reportMessages;
struct { struct {

View file

@ -947,7 +947,8 @@ void DraftOptionsBox(
AddFilledSkip(bottom); AddFilledSkip(bottom);
if (!hasOnlyForcedForwardedInfo) { if (!hasOnlyForcedForwardedInfo
&& !HasOnlyDroppedForwardedInfo(items)) {
Settings::AddButtonWithIcon( Settings::AddButtonWithIcon(
bottom, bottom,
(dropNames (dropNames

View file

@ -153,7 +153,7 @@ void ForwardPanel::updateTexts() {
Unexpected("Corrupt forwarded information in message."); Unexpected("Corrupt forwarded information in message.");
} }
} }
if (!keepNames) { if (!keepNames || HasOnlyDroppedForwardedInfo(_data.items)) {
from = tr::lng_forward_sender_names_removed(tr::now); from = tr::lng_forward_sender_names_removed(tr::now);
} else if (names.size() > 2) { } else if (names.size() > 2) {
from = tr::lng_forwarding_from( from = tr::lng_forwarding_from(
@ -445,4 +445,13 @@ bool HasOnlyForcedForwardedInfo(const HistoryItemsList &list) {
return true; return true;
} }
bool HasOnlyDroppedForwardedInfo(const HistoryItemsList &list) {
for (const auto &item : list) {
if (!item->computeDropForwardedInfo()) {
return false;
}
}
return true;
}
} // namespace HistoryView::Controls } // namespace HistoryView::Controls

View file

@ -85,5 +85,6 @@ void EditWebPageOptions(
Fn<void(Data::WebPageDraft)> done); Fn<void(Data::WebPageDraft)> done);
[[nodiscard]] bool HasOnlyForcedForwardedInfo(const HistoryItemsList &list); [[nodiscard]] bool HasOnlyForcedForwardedInfo(const HistoryItemsList &list);
[[nodiscard]] bool HasOnlyDroppedForwardedInfo(const HistoryItemsList &list);
} // namespace HistoryView::Controls } // namespace HistoryView::Controls

View file

@ -1315,25 +1315,28 @@ base::unique_qptr<Ui::PopupMenu> FillContextMenu(
void CopyPostLink( void CopyPostLink(
not_null<Window::SessionController*> controller, not_null<Window::SessionController*> controller,
FullMsgId itemId, FullMsgId itemId,
Context context) { Context context,
CopyPostLink(controller->uiShow(), itemId, context); std::optional<TimeId> videoTimestamp) {
CopyPostLink(controller->uiShow(), itemId, context, videoTimestamp);
} }
void CopyPostLink( void CopyPostLink(
std::shared_ptr<Main::SessionShow> show, std::shared_ptr<Main::SessionShow> show,
FullMsgId itemId, FullMsgId itemId,
Context context) { Context context,
std::optional<TimeId> videoTimestamp) {
const auto item = show->session().data().message(itemId); const auto item = show->session().data().message(itemId);
if (!item || !item->hasDirectLink()) { if (!item || !item->hasDirectLink()) {
return; return;
} }
const auto inRepliesContext = (context == Context::Replies); const auto inRepliesContext = (context == Context::Replies);
const auto forceNonPublicLink = base::IsCtrlPressed(); const auto forceNonPublicLink = !videoTimestamp && base::IsCtrlPressed();
QGuiApplication::clipboard()->setText( QGuiApplication::clipboard()->setText(
item->history()->session().api().exportDirectMessageLink( item->history()->session().api().exportDirectMessageLink(
item, item,
inRepliesContext, inRepliesContext,
forceNonPublicLink)); forceNonPublicLink,
videoTimestamp));
const auto isPublicLink = [&] { const auto isPublicLink = [&] {
if (forceNonPublicLink) { if (forceNonPublicLink) {
@ -1354,7 +1357,7 @@ void CopyPostLink(
} }
return channel->hasUsername(); return channel->hasUsername();
}(); }();
if (isPublicLink) { if (isPublicLink && !videoTimestamp) {
show->showToast({ show->showToast({
.text = tr::lng_channel_public_link_copied( .text = tr::lng_channel_public_link_copied(
tr::now, Ui::Text::Bold tr::now, Ui::Text::Bold

View file

@ -60,11 +60,13 @@ base::unique_qptr<Ui::PopupMenu> FillContextMenu(
void CopyPostLink( void CopyPostLink(
not_null<Window::SessionController*> controller, not_null<Window::SessionController*> controller,
FullMsgId itemId, FullMsgId itemId,
Context context); Context context,
std::optional<TimeId> videoTimestamp = {});
void CopyPostLink( void CopyPostLink(
std::shared_ptr<Main::SessionShow> show, std::shared_ptr<Main::SessionShow> show,
FullMsgId itemId, FullMsgId itemId,
Context context); Context context,
std::optional<TimeId> videoTimestamp = {});
void CopyStoryLink( void CopyStoryLink(
std::shared_ptr<Main::SessionShow> show, std::shared_ptr<Main::SessionShow> show,
FullStoryId storyId); FullStoryId storyId);

View file

@ -38,6 +38,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "lang/lang_keys.h" #include "lang/lang_keys.h"
#include "mainwidget.h" #include "mainwidget.h"
#include "main/main_session.h" #include "main/main_session.h"
#include "settings/settings_premium.h"
#include "ui/text/text_options.h" #include "ui/text/text_options.h"
#include "ui/painter.h" #include "ui/painter.h"
#include "window/themes/window_theme.h" // IsNightMode. #include "window/themes/window_theme.h" // IsNightMode.
@ -378,6 +379,7 @@ struct Message::CommentsButton {
struct Message::FromNameStatus { struct Message::FromNameStatus {
EmojiStatusId id; EmojiStatusId id;
std::unique_ptr<Ui::Text::CustomEmoji> custom; std::unique_ptr<Ui::Text::CustomEmoji> custom;
ClickHandlerPtr link;
int skip = 0; int skip = 0;
}; };
@ -2815,6 +2817,25 @@ bool Message::getStateFromName(
Unexpected("Corrupt forwarded information in message."); Unexpected("Corrupt forwarded information in message.");
} }
}(); }();
const auto statusWidth = (from && _fromNameStatus)
? st::dialogsPremiumIcon.icon.width()
: 0;
if (statusWidth && availableWidth > statusWidth) {
const auto x = availableLeft + std::min(
availableWidth - statusWidth,
nameText->maxWidth()
) - (_fromNameStatus->custom ? (2 * _fromNameStatus->skip) : 0);
const auto checkWidth = _fromNameStatus->custom
? (st::emojiSize - 2 * _fromNameStatus->skip)
: statusWidth;
if (point.x() >= x && point.x() < x + checkWidth) {
ensureFromNameStatusLink(from);
outResult->link = _fromNameStatus->link;
return true;
}
availableWidth -= statusWidth;
}
if (point.x() >= availableLeft if (point.x() >= availableLeft
&& point.x() < availableLeft + availableWidth && point.x() < availableLeft + availableWidth
&& point.x() < availableLeft + nameText->maxWidth()) { && point.x() < availableLeft + nameText->maxWidth()) {
@ -2835,6 +2856,21 @@ bool Message::getStateFromName(
return false; return false;
} }
void Message::ensureFromNameStatusLink(not_null<PeerData*> peer) const {
Expects(_fromNameStatus != nullptr);
if (_fromNameStatus->link) {
return;
}
_fromNameStatus->link = std::make_shared<LambdaClickHandler>([=](
ClickContext context) {
const auto controller = ExtractController(context);
if (controller) {
Settings::ShowEmojiStatusPremium(controller, peer);
}
});
}
bool Message::getStateTopicButton( bool Message::getStateTopicButton(
QPoint point, QPoint point,
QRect &trect, QRect &trect,

View file

@ -300,6 +300,7 @@ private:
void refreshRightBadge(); void refreshRightBadge();
void validateFromNameText(PeerData *from) const; void validateFromNameText(PeerData *from) const;
void ensureFromNameStatusLink(not_null<PeerData*> peer) const;
mutable std::unique_ptr<RightAction> _rightAction; mutable std::unique_ptr<RightAction> _rightAction;
mutable ClickHandlerPtr _fastReplyLink; mutable ClickHandlerPtr _fastReplyLink;

View file

@ -163,7 +163,7 @@ PaidReactionToast::~PaidReactionToast() {
bool PaidReactionToast::maybeShowFor(not_null<HistoryItem*> item) { bool PaidReactionToast::maybeShowFor(not_null<HistoryItem*> item) {
const auto count = item->reactionsPaidScheduled(); const auto count = item->reactionsPaidScheduled();
const auto anonymous = item->reactionsLocalAnonymous(); const auto shownPeer = item->reactionsLocalShownPeer();
const auto at = _owner->reactions().sendingScheduledPaidAt(item); const auto at = _owner->reactions().sendingScheduledPaidAt(item);
if (!count || !at) { if (!count || !at) {
return false; return false;
@ -173,14 +173,14 @@ bool PaidReactionToast::maybeShowFor(not_null<HistoryItem*> item) {
if (at <= crl::now() + ignore) { if (at <= crl::now() + ignore) {
return false; return false;
} }
showFor(item->fullId(), count, anonymous, at - ignore, total); showFor(item->fullId(), count, shownPeer, at - ignore, total);
return true; return true;
} }
void PaidReactionToast::showFor( void PaidReactionToast::showFor(
FullMsgId itemId, FullMsgId itemId,
int count, int count,
bool anonymous, PeerId shownPeer,
crl::time finish, crl::time finish,
crl::time total) { crl::time total) {
const auto old = _weak.get(); const auto old = _weak.get();
@ -188,7 +188,7 @@ void PaidReactionToast::showFor(
if (i != end(_stack)) { if (i != end(_stack)) {
if (old && i + 1 == end(_stack)) { if (old && i + 1 == end(_stack)) {
_count = count; _count = count;
_anonymous = anonymous; _shownPeer = shownPeer;
_timeFinish = finish; _timeFinish = finish;
return; return;
} }
@ -202,14 +202,14 @@ void PaidReactionToast::showFor(
_hiding.push_back(base::take(_weak)); _hiding.push_back(base::take(_weak));
} }
_count.reset(); _count.reset();
_anonymous.reset(); _shownPeer.reset();
_timeFinish.reset(); _timeFinish.reset();
_count = count; _count = count;
_anonymous = anonymous; _shownPeer = shownPeer;
_timeFinish = finish; _timeFinish = finish;
auto text = rpl::combine( auto text = rpl::combine(
rpl::conditional( rpl::conditional(
_anonymous.value(), _shownPeer.value() | rpl::map(rpl::mappers::_1 == PeerId()),
tr::lng_paid_react_toast_anonymous( tr::lng_paid_react_toast_anonymous(
lt_count, lt_count,
_count.value() | tr::to_count(), _count.value() | tr::to_count(),

View file

@ -40,7 +40,7 @@ private:
void showFor( void showFor(
FullMsgId itemId, FullMsgId itemId,
int count, int count,
bool anonymous, PeerId shownPeer,
crl::time left, crl::time left,
crl::time total); crl::time total);
@ -54,7 +54,7 @@ private:
base::weak_ptr<Ui::Toast::Instance> _weak; base::weak_ptr<Ui::Toast::Instance> _weak;
std::vector<base::weak_ptr<Ui::Toast::Instance>> _hiding; std::vector<base::weak_ptr<Ui::Toast::Instance>> _hiding;
rpl::variable<int> _count; rpl::variable<int> _count;
rpl::variable<bool> _anonymous; rpl::variable<PeerId> _shownPeer;
rpl::variable<crl::time> _timeFinish; rpl::variable<crl::time> _timeFinish;
std::vector<FullMsgId> _stack; std::vector<FullMsgId> _stack;

View file

@ -510,9 +510,19 @@ QSize Document::countOptimalSize() {
accumulate_max(maxWidth, tleft + named->name.maxWidth() + tright); accumulate_max(maxWidth, tleft + named->name.maxWidth() + tright);
accumulate_min(maxWidth, st::msgMaxWidth); accumulate_min(maxWidth, st::msgMaxWidth);
} }
if (voice && voice->transcribe) { if (voice) {
maxWidth += st::historyTranscribeSkip const auto maxWaveformWidth = ::Media::Player::kWaveformSamplesCount *
+ voice->transcribe->size().width(); (st::msgWaveformBar + st::msgWaveformSkip);
const auto transcribeWidth = voice->transcribe
? (voice->transcribe->size().width() + st::historyTranscribeSkip)
: 0;
accumulate_max(
maxWidth,
maxWaveformWidth
+ rect::m::sum::h(st.padding)
+ st.thumbSize
+ st.thumbSkip
+ transcribeWidth);
} }
auto minHeight = st.padding.top() + st.thumbSize + st.padding.bottom(); auto minHeight = st.padding.top() + st.thumbSize + st.padding.bottom();

View file

@ -69,7 +69,10 @@ QSize Game::countOptimalSize() {
// init attach // init attach
if (!_attach) { if (!_attach) {
_attach = CreateAttach(_parent, _data->document, _data->photo); _attach = CreateAttach(
_parent,
_data->document,
_data->document ? nullptr : _data->photo);
} }
// init strings // init strings

View file

@ -19,6 +19,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "media/streaming/media_streaming_instance.h" #include "media/streaming/media_streaming_instance.h"
#include "media/streaming/media_streaming_player.h" #include "media/streaming/media_streaming_player.h"
#include "media/streaming/media_streaming_utility.h" #include "media/streaming/media_streaming_utility.h"
#include "media/view/media_view_open_common.h"
#include "media/view/media_view_playback_progress.h" #include "media/view/media_view_playback_progress.h"
#include "ui/boxes/confirm_box.h" #include "ui/boxes/confirm_box.h"
#include "ui/painter.h" #include "ui/painter.h"
@ -47,6 +48,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/ui_utility.h" #include "ui/ui_utility.h"
#include "ui/effects/path_shift_gradient.h" #include "ui/effects/path_shift_gradient.h"
#include "ui/effects/spoiler_mess.h" #include "ui/effects/spoiler_mess.h"
#include "data/data_photo.h"
#include "data/data_photo_media.h"
#include "data/data_session.h" #include "data/data_session.h"
#include "data/data_stories.h" #include "data/data_stories.h"
#include "data/data_streaming.h" #include "data/data_streaming.h"
@ -54,6 +57,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_file_click_handler.h" #include "data/data_file_click_handler.h"
#include "data/data_file_origin.h" #include "data/data_file_origin.h"
#include "data/data_document_media.h" #include "data/data_document_media.h"
#include "data/data_web_page.h"
#include "storage/storage_account.h"
#include "styles/style_chat.h" #include "styles/style_chat.h"
#include <QSvgRenderer> #include <QSvgRenderer>
@ -145,6 +150,7 @@ Gif::Gif(
bool spoiler) bool spoiler)
: File(parent, realParent) : File(parent, realParent)
, _data(document) , _data(document)
, _videoCover(LookupVideoCover(document, realParent))
, _storyId(realParent->media() , _storyId(realParent->media()
? realParent->media()->storyId() ? realParent->media()->storyId()
: FullStoryId()) : FullStoryId())
@ -154,7 +160,9 @@ Gif::Gif(
? std::make_unique<MediaSpoiler>() ? std::make_unique<MediaSpoiler>()
: nullptr) : nullptr)
, _downloadSize(Ui::FormatSizeText(_data->size)) , _downloadSize(Ui::FormatSizeText(_data->size))
, _sensitiveSpoiler(realParent->isMediaSensitive()) { , _videoTimestamp(::Media::View::ExtractVideoTimestamp(realParent))
, _sensitiveSpoiler(realParent->isMediaSensitive())
, _hasVideoCover(realParent->media() && realParent->media()->videoCover()) {
if (_data->isVideoMessage() && _parent->data()->media()->ttlSeconds()) { if (_data->isVideoMessage() && _parent->data()->media()->ttlSeconds()) {
if (_spoiler) { if (_spoiler) {
_drawTtl = CreateTtlPaintCallback([=] { repaint(); }); _drawTtl = CreateTtlPaintCallback([=] { repaint(); });
@ -200,6 +208,12 @@ Gif::Gif(
if ((_dataMedia = _data->activeMediaView())) { if ((_dataMedia = _data->activeMediaView())) {
dataMediaCreated(); dataMediaCreated();
} else if (_videoCover) {
if (_videoCover->inlineThumbnailBytes().isEmpty()
&& (_videoCover->hasExact(Data::PhotoSize::Small)
|| _videoCover->hasExact(Data::PhotoSize::Thumbnail))) {
_videoCover->load(Data::PhotoSize::Small, realParent->fullId());
}
} else { } else {
_data->loadThumbnail(realParent->fullId()); _data->loadThumbnail(realParent->fullId());
if (!autoplayEnabled()) { if (!autoplayEnabled()) {
@ -396,6 +410,14 @@ bool Gif::downloadInCorner() const {
&& !_data->inappPlaybackFailed(); && !_data->inappPlaybackFailed();
} }
bool Gif::autoplayUnderCursor() const {
return (_videoTimestamp || _hasVideoCover);
}
bool Gif::underCursor() const {
return ClickHandler::getActive() == currentVideoLink();
}
bool Gif::autoplayEnabled() const { bool Gif::autoplayEnabled() const {
if (_realParent->isSponsored()) { if (_realParent->isSponsored()) {
return true; return true;
@ -413,6 +435,8 @@ bool Gif::hideMessageText() const {
void Gif::draw(Painter &p, const PaintContext &context) const { void Gif::draw(Painter &p, const PaintContext &context) const {
if (width() < st::msgPadding.left() + st::msgPadding.right() + 1) return; if (width() < st::msgPadding.left() + st::msgPadding.right() + 1) return;
_smallGroupPart = false;
ensureDataMediaCreated(); ensureDataMediaCreated();
const auto item = _parent->data(); const auto item = _parent->data();
const auto loaded = dataLoaded(); const auto loaded = dataLoaded();
@ -469,11 +493,14 @@ void Gif::draw(Painter &p, const PaintContext &context) const {
validateSpoilerImageCache(rthumb.size(), rounding); validateSpoilerImageCache(rthumb.size(), rounding);
} }
const auto startPlay = autoplay const auto canStartPlay = autoplay
&& !_streamed && !_streamed
&& !activeRoundPlaying && !activeRoundPlaying
&& !fullHiddenBySpoiler; && !fullHiddenBySpoiler;
if (startPlay) { const auto shouldBePlaying = !autoplayUnderCursor() || underCursor();
if (!shouldBePlaying && _videoTimestamp != 0) {
const_cast<Gif*>(this)->stopAnimation();
} else if (canStartPlay) {
const_cast<Gif*>(this)->playAnimation(true); const_cast<Gif*>(this)->playAnimation(true);
} else { } else {
checkStreamedIsStarted(); checkStreamedIsStarted();
@ -514,8 +541,9 @@ void Gif::draw(Painter &p, const PaintContext &context) const {
const auto skipDrawingContent = context.skipDrawingParts const auto skipDrawingContent = context.skipDrawingParts
== PaintContext::SkipDrawingParts::Content; == PaintContext::SkipDrawingParts::Content;
if (streamed && !skipDrawingContent && !fullHiddenBySpoiler) { const auto drawStreamed = streamed && (shouldBePlaying || !_videoCover);
auto paused = context.paused; if (drawStreamed && !skipDrawingContent && !fullHiddenBySpoiler) {
auto paused = context.paused || !shouldBePlaying;
auto request = ::Media::Streaming::FrameRequest{ auto request = ::Media::Streaming::FrameRequest{
.outer = QSize(usew, painth) * style::DevicePixelRatio(), .outer = QSize(usew, painth) * style::DevicePixelRatio(),
.blurredBackground = true, .blurredBackground = true,
@ -583,6 +611,9 @@ void Gif::draw(Painter &p, const PaintContext &context) const {
validateThumbCache({ usew, painth }, isRound, rounding); validateThumbCache({ usew, painth }, isRound, rounding);
p.drawImage(rthumb, _thumbCache); p.drawImage(rthumb, _thumbCache);
} }
if (!isRound) {
paintTimestampMark(p, rthumb, rounding);
}
if (revealed < 1.) { if (revealed < 1.) {
p.setOpacity(1. - revealed); p.setOpacity(1. - revealed);
@ -868,6 +899,70 @@ void Gif::paintTranscribe(
context); context);
} }
void Gif::paintTimestampMark(
Painter &p,
QRect rthumb,
std::optional<Ui::BubbleRounding> rounding) const {
if (_videoTimestamp <= 0 && _videoPosition < crl::time(200)) {
return;
}
const auto convert = [](Ui::BubbleCornerRounding rounding) {
return (rounding == Ui::BubbleCornerRounding::Small)
? Ui::BubbleRadiusSmall()
: (rounding == Ui::BubbleCornerRounding::Large)
? Ui::BubbleRadiusLarge()
: 0;
};
const auto radiusl = rounding
? convert(rounding->bottomLeft)
: st::roundRadiusSmall;
const auto radiusr = rounding
? convert(rounding->bottomRight)
: st::roundRadiusSmall;
const auto line = st::historyVideoTimestampProgressLine;
const auto duration = _data->duration();
const auto position = (_videoPosition > 0)
? _videoPosition
: (_videoTimestamp * crl::time(1000));
if (rthumb.height() <= line
|| rthumb.width() <= radiusl + radiusr
|| position > duration) {
return;
}
auto hq = PainterHighQualityEnabler(p);
const auto used = rthumb.width() - radiusl - radiusr;
const auto progress = position / float64(duration);
const auto edge = radiusl + int(base::SafeRound(used * progress));
const auto top = rthumb.y() + rthumb.height() - line;
p.save();
p.setPen(Qt::NoPen);
if (edge > 0) {
p.setBrush(st::windowBgActive);
p.setClipRect(rthumb.x(), top, edge, line);
p.drawRoundedRect(
rthumb.x(),
top - 2 * radiusl,
edge + radiusl,
line + 2 * radiusl,
radiusl,
radiusl);
}
if (const auto width = rthumb.width() - edge; width > 0) {
const auto left = rthumb.x() + edge;
p.setBrush(st::mediaviewPlaybackProgressFg);
p.setClipRect(left, top, width, line);
p.drawRoundedRect(
left - radiusr,
top - 2 * radiusr,
width + radiusr,
line + 2 * radiusr,
radiusr,
radiusr);
}
p.restore();
}
void Gif::drawSpoilerTag( void Gif::drawSpoilerTag(
Painter &p, Painter &p,
QRect rthumb, QRect rthumb,
@ -891,6 +986,8 @@ QImage Gif::spoilerTagBackground() const {
} }
void Gif::validateVideoThumbnail() const { void Gif::validateVideoThumbnail() const {
Expects(!_videoCover);
const auto content = _dataMedia->videoThumbnailContent(); const auto content = _dataMedia->videoThumbnailContent();
if (_videoThumbnailFrame || content.isEmpty()) { if (_videoThumbnailFrame || content.isEmpty()) {
return; return;
@ -906,13 +1003,25 @@ void Gif::validateThumbCache(
QSize outer, QSize outer,
bool isEllipse, bool isEllipse,
std::optional<Ui::BubbleRounding> rounding) const { std::optional<Ui::BubbleRounding> rounding) const {
const auto good = _dataMedia->goodThumbnail(); const auto good = _videoCoverMedia
const auto normal = good ? good : _dataMedia->thumbnail(); ? _videoCoverMedia->image(Data::PhotoSize::Large)
: _dataMedia->goodThumbnail();
const auto normal = good
? good
: _videoCoverMedia
? nullptr
: _dataMedia->thumbnail();
if (!normal) { if (!normal) {
_data->loadThumbnail(_realParent->fullId()); if (_videoCoverMedia) {
validateVideoThumbnail(); _videoCover->load(Data::PhotoSize::Small, _realParent->fullId());
} else {
_data->loadThumbnail(_realParent->fullId());
validateVideoThumbnail();
}
} }
const auto videothumb = normal ? nullptr : _videoThumbnailFrame.get(); const auto videothumb = (normal || _videoCoverMedia)
? nullptr
: _videoThumbnailFrame.get();
const auto blurred = normal const auto blurred = normal
? (!good ? (!good
&& (normal->width() < kUseNonBlurredThreshold) && (normal->width() < kUseNonBlurredThreshold)
@ -934,9 +1043,17 @@ void Gif::validateThumbCache(
} }
QImage Gif::prepareThumbCache(QSize outer) const { QImage Gif::prepareThumbCache(QSize outer) const {
const auto good = _dataMedia->goodThumbnail(); const auto good = _videoCoverMedia
const auto normal = good ? good : _dataMedia->thumbnail(); ? _videoCoverMedia->image(Data::PhotoSize::Large)
const auto videothumb = normal ? nullptr : _videoThumbnailFrame.get(); : _dataMedia->goodThumbnail();
const auto normal = good
? good
: _videoCoverMedia
? nullptr
: _dataMedia->thumbnail();
const auto videothumb = (normal || _videoCoverMedia)
? nullptr
: _videoThumbnailFrame.get();
auto blurred = (!good auto blurred = (!good
&& normal && normal
&& (normal->width() < kUseNonBlurredThreshold) && (normal->width() < kUseNonBlurredThreshold)
@ -946,6 +1063,10 @@ QImage Gif::prepareThumbCache(QSize outer) const {
const auto blurFromLarge = good || (normal && !blurred); const auto blurFromLarge = good || (normal && !blurred);
const auto large = blurFromLarge ? normal : videothumb; const auto large = blurFromLarge ? normal : videothumb;
if (videothumb) { if (videothumb) {
} else if (_videoCoverMedia) {
if (const auto embedded = _videoCoverMedia->thumbnailInline()) {
blurred = embedded;
}
} else if (const auto embedded = _dataMedia->thumbnailInline()) { } else if (const auto embedded = _dataMedia->thumbnailInline()) {
blurred = embedded; blurred = embedded;
} }
@ -971,7 +1092,9 @@ void Gif::validateSpoilerImageCache(
&& _spoiler->backgroundRounding == rounding) { && _spoiler->backgroundRounding == rounding) {
return; return;
} }
const auto normal = _dataMedia->thumbnail(); const auto normal = _videoCoverMedia
? _videoCoverMedia->image(Data::PhotoSize::Small)
: _dataMedia->thumbnail();
auto container = std::optional<Image>(); auto container = std::optional<Image>();
const auto downscale = [&](Image *image) { const auto downscale = [&](Image *image) {
if (!image || (image->width() <= 40 && image->height() <= 40)) { if (!image || (image->width() <= 40 && image->height() <= 40)) {
@ -983,7 +1106,9 @@ void Gif::validateSpoilerImageCache(
Qt::SmoothTransformation)); Qt::SmoothTransformation));
return &*container; return &*container;
}; };
const auto embedded = _dataMedia->thumbnailInline(); const auto embedded = _videoCoverMedia
? _videoCoverMedia->thumbnailInline()
: _dataMedia->thumbnailInline();
const auto blurred = embedded ? embedded : downscale(normal); const auto blurred = embedded ? embedded : downscale(normal);
_spoiler->background = Images::Round( _spoiler->background = Images::Round(
PrepareWithBlurredBackground( PrepareWithBlurredBackground(
@ -1174,15 +1299,7 @@ TextState Gif::textState(QPoint point, StateRequest request) const {
: (isRound && _parent->data()->media()->ttlSeconds()) : (isRound && _parent->data()->media()->ttlSeconds())
? _openl // Overriden. ? _openl // Overriden.
: _spoiler->link) : _spoiler->link)
: _data->uploading() : currentVideoLink();
? _cancell
: _realParent->isSending()
? nullptr
: (dataLoaded() || _dataMedia->canBePlayed(_realParent))
? _openl
: _data->loading()
? _cancell
: _savel;
} }
const auto checkBottomInfo = !inWebPage const auto checkBottomInfo = !inWebPage
&& (unwrapped || !bubble || isBubbleBottom()); && (unwrapped || !bubble || isBubbleBottom());
@ -1290,8 +1407,8 @@ void Gif::drawGrouped(
|| _data->displayLoading(); || _data->displayLoading();
const auto st = context.st; const auto st = context.st;
const auto sti = context.imageStyle(); const auto sti = context.imageStyle();
const auto fullFeatured = fullFeaturedGrouped(sides); _smallGroupPart = !fullFeaturedGrouped(sides);
const auto cornerDownload = fullFeatured && downloadInCorner(); const auto cornerDownload = !_smallGroupPart && downloadInCorner();
const auto canBePlayed = _dataMedia->canBePlayed(_realParent); const auto canBePlayed = _dataMedia->canBePlayed(_realParent);
const auto revealed = _spoiler const auto revealed = _spoiler
@ -1302,16 +1419,22 @@ void Gif::drawGrouped(
validateSpoilerImageCache(geometry.size(), rounding); validateSpoilerImageCache(geometry.size(), rounding);
} }
const auto autoplay = fullFeatured const auto autoplay = !_smallGroupPart
&& autoplayEnabled() && autoplayEnabled()
&& canBePlayed && canBePlayed
&& CanPlayInline(_data); && CanPlayInline(_data);
const auto startPlay = autoplay && !_streamed; const auto canStartPlay = autoplay
if (startPlay) { && !_streamed
&& !fullHiddenBySpoiler;
const auto shouldBePlaying = !autoplayUnderCursor() || underCursor();
if (!shouldBePlaying && _videoTimestamp != 0) {
const_cast<Gif*>(this)->stopAnimation();
} else if (canStartPlay) {
const_cast<Gif*>(this)->playAnimation(true); const_cast<Gif*>(this)->playAnimation(true);
} else { } else {
checkStreamedIsStarted(); checkStreamedIsStarted();
} }
const auto streamingMode = _streamed || autoplay; const auto streamingMode = _streamed || autoplay;
const auto activeOwnPlaying = activeOwnStreamed(); const auto activeOwnPlaying = activeOwnStreamed();
@ -1364,7 +1487,9 @@ void Gif::drawGrouped(
activeOwnPlaying->frozenStatusText = QString(); activeOwnPlaying->frozenStatusText = QString();
} }
p.drawImage(geometry, streamed->frame(request)); p.drawImage(geometry, streamed->frame(request));
if (!context.paused) { const auto paused = context.paused
|| (autoplayUnderCursor() && !underCursor());
if (!paused) {
streamed->markFrameShown(); streamed->markFrameShown();
} }
} }
@ -1479,7 +1604,7 @@ void Gif::drawGrouped(
} }
p.setOpacity(1.); p.setOpacity(1.);
} }
if (fullFeatured) { if (!_smallGroupPart) {
drawCornerStatus(p, context, geometry.topLeft()); drawCornerStatus(p, context, geometry.topLeft());
} }
} }
@ -1492,8 +1617,7 @@ TextState Gif::getStateGrouped(
if (!geometry.contains(point)) { if (!geometry.contains(point)) {
return {}; return {};
} }
const auto isFullFeaturedGrouped = fullFeaturedGrouped(sides); if (!_smallGroupPart) {
if (isFullFeaturedGrouped) {
const auto state = cornerStatusTextState( const auto state = cornerStatusTextState(
point, point,
request, request,
@ -1503,37 +1627,53 @@ TextState Gif::getStateGrouped(
} }
} }
ensureDataMediaCreated(); ensureDataMediaCreated();
auto link = (_spoiler && !_spoiler->revealed) auto link = (_spoiler && !_spoiler->revealed)
? (_sensitiveSpoiler ? spoilerTagLink() : _spoiler->link) ? (_sensitiveSpoiler ? spoilerTagLink() : _spoiler->link)
: _data->uploading() : currentVideoLink();
return TextState(_parent, std::move(link));
}
ClickHandlerPtr Gif::currentVideoLink() const {
return _data->uploading()
? _cancell ? _cancell
: _realParent->isSending() : _realParent->isSending()
? nullptr ? nullptr
: dataLoaded() : dataLoaded()
? _openl ? _openl
: (_data->loading() && !isFullFeaturedGrouped) : (_data->loading() && _smallGroupPart)
? _cancell ? _cancell
: _dataMedia->canBePlayed(_realParent) : _dataMedia->canBePlayed(_realParent)
? _openl ? _openl
: _data->loading()
? _cancell
: _savel; : _savel;
return TextState(_parent, std::move(link));
} }
void Gif::ensureDataMediaCreated() const { void Gif::ensureDataMediaCreated() const {
if (_dataMedia) { if (_dataMedia && (!_videoCover || _videoCoverMedia)) {
return; return;
} }
_dataMedia = _data->createMediaView(); _dataMedia = _data->createMediaView();
_videoCoverMedia = _videoCover
? _videoCover->createMediaView()
: nullptr;
dataMediaCreated(); dataMediaCreated();
} }
void Gif::dataMediaCreated() const { void Gif::dataMediaCreated() const {
Expects(_dataMedia != nullptr); Expects(_dataMedia != nullptr);
_dataMedia->goodThumbnailWanted(); if (_videoCoverMedia) {
_dataMedia->thumbnailWanted(_realParent->fullId()); _videoCoverMedia->wanted(
if (!autoplayEnabled()) { Data::PhotoSize::Large,
_dataMedia->videoThumbnailWanted(_realParent->fullId()); _realParent->fullId());
} else {
_dataMedia->goodThumbnailWanted();
_dataMedia->thumbnailWanted(_realParent->fullId());
if (!autoplayEnabled()) {
_dataMedia->videoThumbnailWanted(_realParent->fullId());
}
} }
history()->owner().registerHeavyViewPart(_parent); history()->owner().registerHeavyViewPart(_parent);
togglePollingStory(true); togglePollingStory(true);
@ -1670,12 +1810,18 @@ void Gif::validateGroupedCache(
ensureDataMediaCreated(); ensureDataMediaCreated();
const auto good = _dataMedia->goodThumbnail(); const auto good = _videoCoverMedia
const auto thumb = _dataMedia->thumbnail(); ? _videoCoverMedia->image(Data::PhotoSize::Large)
: _dataMedia->goodThumbnail();
const auto thumb = _videoCoverMedia
? nullptr
: _dataMedia->thumbnail();
const auto image = good const auto image = good
? good ? good
: thumb : thumb
? thumb ? thumb
: _videoCoverMedia
? _videoCoverMedia->thumbnailInline()
: _dataMedia->thumbnailInline(); : _dataMedia->thumbnailInline();
const auto blur = !good const auto blur = !good
&& (!thumb && (!thumb
@ -1746,7 +1892,8 @@ void Gif::updateStatusText() const {
} }
const auto round = activeRoundStreamed(); const auto round = activeRoundStreamed();
const auto own = activeOwnStreamed(); const auto own = activeOwnStreamed();
if (round || (own && own->frozenFrame.isNull() && _data->isVideoFile())) { if (round || (own && _data->isVideoFile())) {
const auto frozen = own && !own->frozenFrame.isNull();
const auto streamed = round ? round : &own->instance; const auto streamed = round ? round : &own->instance;
const auto state = streamed->player().prepareLegacyState(); const auto state = streamed->player().prepareLegacyState();
if (state.length) { if (state.length) {
@ -1756,9 +1903,17 @@ void Gif::updateStatusText() const {
} else if (!::Media::Player::IsStoppedOrStopping(state.state)) { } else if (!::Media::Player::IsStoppedOrStopping(state.state)) {
position = state.position; position = state.position;
} }
statusSize = -1 - int((state.length - position) / state.frequency + 1); if (!frozen) {
statusSize = -1 - int((state.length - position) / state.frequency + 1);
}
_videoPosition = std::max(
crl::time(position * crl::time(1000) / state.frequency),
crl::time(1));
} else { } else {
statusSize = -1 - (_data->duration() / 1000); if (!frozen) {
statusSize = -1 - (_data->duration() / 1000);
}
_videoPosition = 0;
} }
} }
if (statusSize != _statusSize) { if (statusSize != _statusSize) {
@ -1918,6 +2073,10 @@ void Gif::startStreamedPlayer() const {
//if (!_streamed->withSound) { //if (!_streamed->withSound) {
options.mode = ::Media::Streaming::Mode::Video; options.mode = ::Media::Streaming::Mode::Video;
options.loop = true; options.loop = true;
options.position = _videoTimestamp
? (_videoTimestamp * crl::time(1000))
: _parent->history()->session().local().mediaLastPlaybackPosition(
_data->id);
//} //}
_streamed->instance.play(options); _streamed->instance.play(options);
} }
@ -1925,10 +2084,12 @@ void Gif::startStreamedPlayer() const {
void Gif::checkStreamedIsStarted() const { void Gif::checkStreamedIsStarted() const {
if (!_streamed || _streamed->instance.playerLocked()) { if (!_streamed || _streamed->instance.playerLocked()) {
return; return;
} else if (_streamed->instance.paused()) {
_streamed->instance.resume();
} }
if (!_streamed->instance.active() && !_streamed->instance.failed()) { if (_streamed->instance.active()) {
if (_streamed->instance.paused()) {
_streamed->instance.resume();
}
} else if (!_streamed->instance.failed()) {
startStreamedPlayer(); startStreamedPlayer();
} }
} }
@ -1941,6 +2102,7 @@ void Gif::setStreamed(std::unique_ptr<Streamed> value) {
history()->owner().registerHeavyViewPart(_parent); history()->owner().registerHeavyViewPart(_parent);
togglePollingStory(true); togglePollingStory(true);
} else if (removed) { } else if (removed) {
_videoPosition = 0;
_parent->checkHeavyPart(); _parent->checkHeavyPart();
} }
} }

View file

@ -15,9 +15,11 @@ struct HistoryMessageVia;
struct HistoryMessageReply; struct HistoryMessageReply;
struct HistoryMessageForwarded; struct HistoryMessageForwarded;
class Painter; class Painter;
class PhotoData;
namespace Data { namespace Data {
class DocumentMedia; class DocumentMedia;
class PhotoMedia;
} // namespace Data } // namespace Data
namespace Media { namespace Media {
@ -37,6 +39,7 @@ enum class Error;
namespace HistoryView { namespace HistoryView {
class Photo;
class Reply; class Reply;
class TranscribeButton; class TranscribeButton;
@ -137,6 +140,8 @@ private:
void dataMediaCreated() const; void dataMediaCreated() const;
[[nodiscard]] bool autoplayEnabled() const; [[nodiscard]] bool autoplayEnabled() const;
[[nodiscard]] bool autoplayUnderCursor() const;
[[nodiscard]] bool underCursor() const;
void playAnimation(bool autoplay) override; void playAnimation(bool autoplay) override;
QSize countOptimalSize() override; QSize countOptimalSize() override;
@ -163,6 +168,10 @@ private:
int y, int y,
bool right, bool right,
const PaintContext &context) const; const PaintContext &context) const;
void paintTimestampMark(
Painter &p,
QRect rthumb,
std::optional<Ui::BubbleRounding> rounding) const;
[[nodiscard]] bool needInfoDisplay() const; [[nodiscard]] bool needInfoDisplay() const;
[[nodiscard]] bool needCornerStatusDisplay() const; [[nodiscard]] bool needCornerStatusDisplay() const;
@ -202,28 +211,35 @@ private:
QPoint point, QPoint point,
StateRequest request, StateRequest request,
QPoint position) const; QPoint position) const;
[[nodiscard]] ClickHandlerPtr currentVideoLink() const;
void togglePollingStory(bool enabled) const; void togglePollingStory(bool enabled) const;
TtlRoundPaintCallback _drawTtl; TtlRoundPaintCallback _drawTtl;
const not_null<DocumentData*> _data; const not_null<DocumentData*> _data;
PhotoData *_videoCover = nullptr;
const FullStoryId _storyId; const FullStoryId _storyId;
std::unique_ptr<Streamed> _streamed; std::unique_ptr<Streamed> _streamed;
const std::unique_ptr<MediaSpoiler> _spoiler; const std::unique_ptr<MediaSpoiler> _spoiler;
mutable std::unique_ptr<MediaSpoilerTag> _spoilerTag; mutable std::unique_ptr<MediaSpoilerTag> _spoilerTag;
mutable std::unique_ptr<TranscribeButton> _transcribe; mutable std::unique_ptr<TranscribeButton> _transcribe;
mutable std::shared_ptr<Data::DocumentMedia> _dataMedia; mutable std::shared_ptr<Data::DocumentMedia> _dataMedia;
mutable std::shared_ptr<Data::PhotoMedia> _videoCoverMedia;
mutable std::unique_ptr<Image> _videoThumbnailFrame; mutable std::unique_ptr<Image> _videoThumbnailFrame;
QString _downloadSize; QString _downloadSize;
mutable QImage _thumbCache; mutable QImage _thumbCache;
mutable QImage _roundingMask; mutable QImage _roundingMask;
mutable crl::time _videoPosition = 0;
mutable TimeId _videoTimestamp = 0;
mutable std::optional<Ui::BubbleRounding> _thumbCacheRounding; mutable std::optional<Ui::BubbleRounding> _thumbCacheRounding;
mutable bool _thumbCacheBlurred : 1 = false; mutable bool _thumbCacheBlurred : 1 = false;
mutable bool _thumbIsEllipse : 1 = false; mutable bool _thumbIsEllipse : 1 = false;
mutable bool _pollingStory : 1 = false; mutable bool _pollingStory : 1 = false;
mutable bool _purchasedPriceTag : 1 = false; mutable bool _purchasedPriceTag : 1 = false;
mutable bool _smallGroupPart : 1 = false;
const bool _sensitiveSpoiler : 1 = false; const bool _sensitiveSpoiler : 1 = false;
const bool _hasVideoCover : 1 = false;
}; };

View file

@ -114,7 +114,6 @@ private:
void ensureDataMediaCreated() const; void ensureDataMediaCreated() const;
void dataMediaCreated() const; void dataMediaCreated() const;
void setupSpoilerTag() const;
QSize countOptimalSize() override; QSize countOptimalSize() override;
QSize countCurrentSize(int newWidth) override; QSize countCurrentSize(int newWidth) override;

View file

@ -496,7 +496,9 @@ auto UniqueGiftBg(
p.setPen(Qt::NoPen); p.setPen(Qt::NoPen);
const auto webpreview = (media.get() != view->media()); const auto webpreview = (media.get() != view->media());
const auto thickness = webpreview ? 0 : st::chatUniqueGiftBorder * 2; const auto thickness = webpreview ? 0 : st::chatUniqueGiftBorder * 2;
const auto radius = st::msgServiceGiftBoxRadius - thickness; const auto radius = webpreview
? st::roundRadiusLarge
: (st::msgServiceGiftBoxRadius - thickness);
const auto full = QRect(0, 0, media->width(), media->height()); const auto full = QRect(0, 0, media->width(), media->height());
const auto inner = full.marginsRemoved( const auto inner = full.marginsRemoved(
{ thickness, thickness, thickness, thickness }); { thickness, thickness, thickness, thickness });
@ -519,7 +521,8 @@ auto UniqueGiftBg(
const auto width = media->width(); const auto width = media->width();
const auto shift = width / 12; const auto shift = width / 12;
const auto doubled = width + 2 * shift; const auto doubled = width + 2 * shift;
const auto outer = QRect(-shift, -shift, doubled, doubled); const auto top = (webpreview ? 2 : 1) * (-shift);
const auto outer = QRect(-shift, top, doubled, doubled);
p.setClipRect(inner); p.setClipRect(inner);
Ui::PaintPoints( Ui::PaintPoints(
p, p,

View file

@ -80,15 +80,11 @@ constexpr auto kSponsoredUserpicLines = 2;
const auto spoiler = false; const auto spoiler = false;
for (const auto &item : data.items) { for (const auto &item : data.items) {
if (const auto document = std::get_if<DocumentData*>(&item)) { if (const auto document = std::get_if<DocumentData*>(&item)) {
const auto hasQualitiesList = false; using MediaFile = Data::MediaFile;
const auto skipPremiumEffect = false; using Args = MediaFile::Args;
result.push_back(std::make_unique<Data::MediaFile>( const auto data = *document;
parent, result.push_back(
*document, std::make_unique<Data::MediaFile>(parent, data, Args{}));
skipPremiumEffect,
hasQualitiesList,
spoiler,
/*ttlSeconds = */0));
} else if (const auto photo = std::get_if<PhotoData*>(&item)) { } else if (const auto photo = std::get_if<PhotoData*>(&item)) {
result.push_back(std::make_unique<Data::MediaPhoto>( result.push_back(std::make_unique<Data::MediaPhoto>(
parent, parent,
@ -317,13 +313,13 @@ void WebPage::setupAdditionalData() {
UrlClickHandler::Open(link); UrlClickHandler::Open(link);
}); });
if (!_attach) { if (!_attach) {
const auto maybePhoto = details.mediaPhotoId
? session->data().photo(details.mediaPhotoId).get()
: nullptr;
const auto maybeDocument = details.mediaDocumentId const auto maybeDocument = details.mediaDocumentId
? session->data().document( ? session->data().document(
details.mediaDocumentId).get() details.mediaDocumentId).get()
: nullptr; : nullptr;
const auto maybePhoto = (!maybeDocument && details.mediaPhotoId)
? session->data().photo(details.mediaPhotoId).get()
: nullptr;
_attach = CreateAttach( _attach = CreateAttach(
_parent, _parent,
maybeDocument, maybeDocument,
@ -528,7 +524,9 @@ QSize WebPage::countOptimalSize() {
_attach = CreateAttach( _attach = CreateAttach(
_parent, _parent,
_data->document, _data->document,
_data->photo, ((!_data->document || _data->photoIsVideoCover)
? _data->photo
: nullptr),
_collage, _collage,
_data->url); _data->url);
} }

View file

@ -1164,6 +1164,7 @@ infoHoursOuter: RoundButton(defaultActiveButton) {
} }
infoHoursOuterMargin: margins(8px, 4px, 8px, 4px); infoHoursOuterMargin: margins(8px, 4px, 8px, 4px);
infoHoursDaySkip: 6px; infoHoursDaySkip: 6px;
infoHoursArrowSize: 4px;
infoSharedMediaScroll: ScrollArea(defaultScrollArea) { infoSharedMediaScroll: ScrollArea(defaultScrollArea) {
round: 1px; round: 1px;

View file

@ -110,6 +110,8 @@ WrapWidget::WrapWidget(
Wrap wrap, Wrap wrap,
not_null<Memento*> memento) not_null<Memento*> memento)
: SectionWidget(parent, window, rpl::producer<PeerData*>()) : SectionWidget(parent, window, rpl::producer<PeerData*>())
, _isSeparatedWindow(
window->windowId().type == Window::SeparateType::SharedMedia)
, _wrap(wrap) , _wrap(wrap)
, _controller(createController(window, memento->content())) , _controller(createController(window, memento->content()))
, _topShadow(this) , _topShadow(this)
@ -446,10 +448,7 @@ void WrapWidget::setupTopBarMenuToggle() {
addTopBarMenuButton(); addTopBarMenuButton();
} }
}, _topBar->lifetime()); }, _topBar->lifetime());
} else if (section.type() == Section::Type::PeerGifts } else if (section.type() == Section::Type::PeerGifts && key.peer()) {
&& key.peer()
&& key.peer()->isChannel()
&& key.peer()->canManageGifts()) {
addTopBarMenuButton(); addTopBarMenuButton();
} }
} }
@ -504,6 +503,7 @@ void WrapWidget::addTopBarMenuButton() {
using Command = Shortcuts::Command; using Command = Shortcuts::Command;
request->check(Command::ShowChatMenu, 1) && request->handle([=] { request->check(Command::ShowChatMenu, 1) && request->handle([=] {
Window::ActivateWindow(_controller->parentController());
showTopBarMenu(false); showTopBarMenu(false);
return true; return true;
}); });
@ -1048,7 +1048,8 @@ const Ui::RoundRect *WrapWidget::bottomSkipRounding() const {
} }
bool WrapWidget::hasBackButton() const { bool WrapWidget::hasBackButton() const {
return (wrap() == Wrap::Narrow || hasStackHistory()); return !_isSeparatedWindow
&& (wrap() == Wrap::Narrow || hasStackHistory());
} }
bool WrapWidget::willHaveBackButton( bool WrapWidget::willHaveBackButton(

View file

@ -208,6 +208,8 @@ private:
void addProfileCallsButton(); void addProfileCallsButton();
void showTopBarMenu(bool check); void showTopBarMenu(bool check);
const bool _isSeparatedWindow = false;
rpl::variable<Wrap> _wrap; rpl::variable<Wrap> _wrap;
std::unique_ptr<Controller> _controller; std::unique_ptr<Controller> _controller;
object_ptr<ContentWidget> _content = { nullptr }; object_ptr<ContentWidget> _content = { nullptr };

View file

@ -0,0 +1,305 @@
/*
This file is part of Telegram Desktop,
the official desktop application for the Telegram messaging service.
For license and copyright information please follow this link:
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#include "info/media/info_media_buttons.h"
#include "base/call_delayed.h"
#include "base/qt/qt_key_modifiers.h"
#include "core/application.h"
#include "data/data_channel.h"
#include "data/data_saved_messages.h"
#include "data/data_session.h"
#include "data/data_stories_ids.h"
#include "data/data_user.h"
#include "history/view/history_view_sublist_section.h"
#include "info/info_controller.h"
#include "info/info_memento.h"
#include "info/profile/info_profile_values.h"
#include "info/stories/info_stories_widget.h"
#include "ui/widgets/buttons.h"
#include "ui/widgets/popup_menu.h"
#include "ui/wrap/slide_wrap.h"
#include "ui/wrap/vertical_layout.h"
#include "window/window_separate_id.h"
#include "window/window_session_controller.h"
#include "styles/style_info.h"
#include "styles/style_menu_icons.h"
namespace Info::Media {
namespace {
[[nodiscard]] Window::SeparateSharedMediaType ToSeparateType(
Storage::SharedMediaType type) {
using Type = Storage::SharedMediaType;
using SeparatedType = Window::SeparateSharedMediaType;
return (type == Type::Photo)
? SeparatedType::Photos
: (type == Type::Video)
? SeparatedType::Videos
: (type == Type::File)
? SeparatedType::Files
: (type == Type::MusicFile)
? SeparatedType::Audio
: (type == Type::Link)
? SeparatedType::Links
: (type == Type::RoundVoiceFile)
? SeparatedType::Voices
: (type == Type::GIF)
? SeparatedType::GIF
: SeparatedType::None;
}
[[nodiscard]] Window::SeparateId SeparateId(
not_null<PeerData*> peer,
MsgId topicRootId,
Storage::SharedMediaType type) {
if (peer->isSelf()) {
return { nullptr };
}
const auto separateType = ToSeparateType(type);
if (separateType == Window::SeparateSharedMediaType::None) {
return { nullptr };
}
return { Window::SeparateSharedMedia(separateType, peer, topicRootId) };
}
void AddContextMenuToButton(
not_null<Ui::AbstractButton*> button,
Fn<void()> openInWindow) {
if (!openInWindow) {
return;
}
button->setAcceptBoth();
struct State final {
base::unique_qptr<Ui::PopupMenu> menu;
};
const auto state = button->lifetime().make_state<State>();
button->addClickHandler([=](Qt::MouseButton mouse) {
if (mouse != Qt::RightButton) {
return;
}
state->menu = base::make_unique_q<Ui::PopupMenu>(
button.get(),
st::popupMenuWithIcons);
state->menu->addAction(tr::lng_context_new_window(tr::now), [=] {
base::call_delayed(
st::popupMenuWithIcons.showDuration,
crl::guard(button, openInWindow));
}, &st::menuIconNewWindow);
state->menu->popup(QCursor::pos());
});
}
} // namespace
tr::phrase<lngtag_count> MediaTextPhrase(Type type) {
switch (type) {
case Type::Photo: return tr::lng_profile_photos;
case Type::GIF: return tr::lng_profile_gifs;
case Type::Video: return tr::lng_profile_videos;
case Type::File: return tr::lng_profile_files;
case Type::MusicFile: return tr::lng_profile_songs;
case Type::Link: return tr::lng_profile_shared_links;
case Type::RoundVoiceFile: return tr::lng_profile_audios;
}
Unexpected("Type in MediaTextPhrase()");
};
Fn<QString(int)> MediaText(Type type) {
return [phrase = MediaTextPhrase(type)](int count) {
return phrase(tr::now, lt_count, count);
};
}
not_null<Ui::SlideWrap<Ui::SettingsButton>*> AddCountedButton(
Ui::VerticalLayout *parent,
rpl::producer<int> &&count,
Fn<QString(int)> &&textFromCount,
Ui::MultiSlideTracker &tracker) {
using namespace ::Settings;
auto forked = std::move(count)
| start_spawning(parent->lifetime());
auto text = rpl::duplicate(
forked
) | rpl::map([textFromCount](int count) {
return (count > 0)
? textFromCount(count)
: QString();
});
auto button = parent->add(object_ptr<Ui::SlideWrap<Ui::SettingsButton>>(
parent,
object_ptr<Ui::SettingsButton>(
parent,
std::move(text),
st::infoSharedMediaButton))
)->setDuration(
st::infoSlideDuration
)->toggleOn(
rpl::duplicate(forked) | rpl::map(rpl::mappers::_1 > 0)
);
tracker.track(button);
return button;
};
not_null<Ui::SettingsButton*> AddButton(
Ui::VerticalLayout *parent,
not_null<Window::SessionNavigation*> navigation,
not_null<PeerData*> peer,
MsgId topicRootId,
PeerData *migrated,
Type type,
Ui::MultiSlideTracker &tracker) {
auto result = AddCountedButton(
parent,
Profile::SharedMediaCountValue(peer, topicRootId, migrated, type),
MediaText(type),
tracker)->entity();
const auto separateId = SeparateId(peer, topicRootId, type);
const auto openInWindow = separateId
? [=] { navigation->parentController()->showInNewWindow(separateId); }
: Fn<void()>(nullptr);
AddContextMenuToButton(result, openInWindow);
result->addClickHandler([=](Qt::MouseButton mouse) {
if (mouse == Qt::RightButton) {
return;
}
if (openInWindow
&& (base::IsCtrlPressed() || mouse == Qt::MiddleButton)) {
return openInWindow();
}
const auto topic = topicRootId
? peer->forumTopicFor(topicRootId)
: nullptr;
if (topicRootId && !topic) {
return;
}
const auto separateId = SeparateId(peer, topicRootId, type);
if (Core::App().separateWindowFor(separateId) && openInWindow) {
openInWindow();
} else {
navigation->showSection(topicRootId
? std::make_shared<Info::Memento>(topic, Section(type))
: std::make_shared<Info::Memento>(peer, Section(type)));
}
});
return result;
};
not_null<Ui::SettingsButton*> AddCommonGroupsButton(
Ui::VerticalLayout *parent,
not_null<Window::SessionNavigation*> navigation,
not_null<UserData*> user,
Ui::MultiSlideTracker &tracker) {
auto result = AddCountedButton(
parent,
Profile::CommonGroupsCountValue(user),
[](int count) {
return tr::lng_profile_common_groups(tr::now, lt_count, count);
},
tracker)->entity();
result->addClickHandler([=] {
navigation->showSection(
std::make_shared<Info::Memento>(
user,
Section::Type::CommonGroups));
});
return result;
}
not_null<Ui::SettingsButton*> AddSimilarPeersButton(
Ui::VerticalLayout *parent,
not_null<Window::SessionNavigation*> navigation,
not_null<PeerData*> peer,
Ui::MultiSlideTracker &tracker) {
auto result = AddCountedButton(
parent,
Profile::SimilarPeersCountValue(peer),
[=](int count) {
return peer->isBroadcast()
? tr::lng_profile_similar_channels(tr::now, lt_count, count)
: tr::lng_profile_similar_bots(tr::now, lt_count, count);
},
tracker)->entity();
result->addClickHandler([=] {
navigation->showSection(
std::make_shared<Info::Memento>(
peer,
Section::Type::SimilarPeers));
});
return result;
}
not_null<Ui::SettingsButton*> AddStoriesButton(
Ui::VerticalLayout *parent,
not_null<Window::SessionNavigation*> navigation,
not_null<PeerData*> peer,
Ui::MultiSlideTracker &tracker) {
auto count = rpl::single(0) | rpl::then(Data::SavedStoriesIds(
peer,
ServerMaxStoryId - 1,
0
) | rpl::map([](const Data::StoriesIdsSlice &slice) {
return slice.fullCount().value_or(0);
}));
const auto phrase = peer->isChannel() ? (+[](int count) {
return tr::lng_profile_posts(tr::now, lt_count, count);
}) : (+[](int count) {
return tr::lng_profile_saved_stories(tr::now, lt_count, count);
});
auto result = AddCountedButton(
parent,
std::move(count),
phrase,
tracker)->entity();
result->addClickHandler([=] {
navigation->showSection(Info::Stories::Make(peer));
});
return result;
}
not_null<Ui::SettingsButton*> AddSavedSublistButton(
Ui::VerticalLayout *parent,
not_null<Window::SessionNavigation*> navigation,
not_null<PeerData*> peer,
Ui::MultiSlideTracker &tracker) {
auto result = AddCountedButton(
parent,
Profile::SavedSublistCountValue(peer),
[](int count) {
return tr::lng_profile_saved_messages(tr::now, lt_count, count);
},
tracker)->entity();
result->addClickHandler([=] {
navigation->showSection(
std::make_shared<HistoryView::SublistMemento>(
peer->owner().savedMessages().sublist(peer)));
});
return result;
}
not_null<Ui::SettingsButton*> AddPeerGiftsButton(
Ui::VerticalLayout *parent,
not_null<Window::SessionNavigation*> navigation,
not_null<PeerData*> peer,
Ui::MultiSlideTracker &tracker) {
auto result = AddCountedButton(
parent,
Profile::PeerGiftsCountValue(peer),
[](int count) {
return tr::lng_profile_peer_gifts(tr::now, lt_count, count);
},
tracker)->entity();
result->addClickHandler([=] {
navigation->showSection(
std::make_shared<Info::Memento>(
peer,
Section::Type::PeerGifts));
});
return result;
}
} // namespace Info::Media

View file

@ -7,220 +7,73 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/ */
#pragma once #pragma once
#include <rpl/mappers.h>
#include <rpl/map.h>
#include "lang/lang_keys.h" #include "lang/lang_keys.h"
#include "data/data_saved_messages.h"
#include "data/data_session.h"
#include "data/data_stories_ids.h"
#include "storage/storage_shared_media.h" #include "storage/storage_shared_media.h"
#include "history/view/history_view_sublist_section.h"
#include "info/info_memento.h" namespace Ui {
#include "info/info_controller.h" class AbstractButton;
#include "info/profile/info_profile_values.h" class MultiSlideTracker;
#include "info/stories/info_stories_widget.h" class SettingsButton;
#include "ui/wrap/slide_wrap.h" class VerticalLayout;
#include "ui/wrap/vertical_layout.h" template <typename Widget>
#include "ui/widgets/buttons.h" class SlideWrap;
#include "window/window_session_controller.h" } // namespace Ui
#include "data/data_channel.h"
#include "data/data_user.h" namespace Window {
#include "styles/style_info.h" class SessionNavigation;
} // namespace Window
namespace Info::Media { namespace Info::Media {
using Type = Storage::SharedMediaType; using Type = Storage::SharedMediaType;
inline tr::phrase<lngtag_count> MediaTextPhrase(Type type) { [[nodiscard]] tr::phrase<lngtag_count> MediaTextPhrase(Type type);
switch (type) {
case Type::Photo: return tr::lng_profile_photos;
case Type::GIF: return tr::lng_profile_gifs;
case Type::Video: return tr::lng_profile_videos;
case Type::File: return tr::lng_profile_files;
case Type::MusicFile: return tr::lng_profile_songs;
case Type::Link: return tr::lng_profile_shared_links;
case Type::RoundVoiceFile: return tr::lng_profile_audios;
}
Unexpected("Type in MediaTextPhrase()");
};
inline auto MediaText(Type type) { [[nodiscard]] Fn<QString(int)> MediaText(Type type);
return [phrase = MediaTextPhrase(type)](int count) {
return phrase(tr::now, lt_count, count);
};
}
template <typename Count, typename Text> [[nodiscard]] not_null<Ui::SlideWrap<Ui::SettingsButton>*> AddCountedButton(
inline auto AddCountedButton( Ui::VerticalLayout *parent,
Ui::VerticalLayout *parent, rpl::producer<int> &&count,
Count &&count, Fn<QString(int)> &&textFromCount,
Text &&textFromCount, Ui::MultiSlideTracker &tracker);
Ui::MultiSlideTracker &tracker) {
using namespace rpl::mappers;
using namespace ::Settings; [[nodiscard]] not_null<Ui::SettingsButton*> AddButton(
auto forked = std::move(count) Ui::VerticalLayout *parent,
| start_spawning(parent->lifetime()); not_null<Window::SessionNavigation*> navigation,
auto text = rpl::duplicate( not_null<PeerData*> peer,
forked MsgId topicRootId,
) | rpl::map([textFromCount](int count) { PeerData *migrated,
return (count > 0) Type type,
? textFromCount(count) Ui::MultiSlideTracker &tracker);
: QString();
});
auto button = parent->add(object_ptr<Ui::SlideWrap<Ui::SettingsButton>>(
parent,
object_ptr<Ui::SettingsButton>(
parent,
std::move(text),
st::infoSharedMediaButton))
)->setDuration(
st::infoSlideDuration
)->toggleOn(
rpl::duplicate(forked) | rpl::map(_1 > 0)
);
tracker.track(button);
return button;
};
inline auto AddButton( [[nodiscard]] not_null<Ui::SettingsButton*> AddCommonGroupsButton(
Ui::VerticalLayout *parent, Ui::VerticalLayout *parent,
not_null<Window::SessionNavigation*> navigation, not_null<Window::SessionNavigation*> navigation,
not_null<PeerData*> peer, not_null<UserData*> user,
MsgId topicRootId, Ui::MultiSlideTracker &tracker);
PeerData *migrated,
Type type,
Ui::MultiSlideTracker &tracker) {
auto result = AddCountedButton(
parent,
Profile::SharedMediaCountValue(peer, topicRootId, migrated, type),
MediaText(type),
tracker)->entity();
result->addClickHandler([=] {
const auto topic = topicRootId
? peer->forumTopicFor(topicRootId)
: nullptr;
if (topicRootId && !topic) {
return;
}
navigation->showSection(topicRootId
? std::make_shared<Info::Memento>(topic, Section(type))
: std::make_shared<Info::Memento>(peer, Section(type)));
});
return result;
};
inline auto AddCommonGroupsButton( [[nodiscard]] not_null<Ui::SettingsButton*> AddSimilarPeersButton(
Ui::VerticalLayout *parent, Ui::VerticalLayout *parent,
not_null<Window::SessionNavigation*> navigation, not_null<Window::SessionNavigation*> navigation,
not_null<UserData*> user, not_null<PeerData*> peer,
Ui::MultiSlideTracker &tracker) { Ui::MultiSlideTracker &tracker);
auto result = AddCountedButton(
parent,
Profile::CommonGroupsCountValue(user),
[](int count) {
return tr::lng_profile_common_groups(tr::now, lt_count, count);
},
tracker)->entity();
result->addClickHandler([=] {
navigation->showSection(
std::make_shared<Info::Memento>(
user,
Section::Type::CommonGroups));
});
return result;
}
inline auto AddSimilarPeersButton( [[nodiscard]] not_null<Ui::SettingsButton*> AddStoriesButton(
Ui::VerticalLayout *parent, Ui::VerticalLayout *parent,
not_null<Window::SessionNavigation*> navigation, not_null<Window::SessionNavigation*> navigation,
not_null<PeerData*> peer, not_null<PeerData*> peer,
Ui::MultiSlideTracker &tracker) { Ui::MultiSlideTracker &tracker);
auto result = AddCountedButton(
parent,
Profile::SimilarPeersCountValue(peer),
[=](int count) {
return peer->isBroadcast()
? tr::lng_profile_similar_channels(tr::now, lt_count, count)
: tr::lng_profile_similar_bots(tr::now, lt_count, count);
},
tracker)->entity();
result->addClickHandler([=] {
navigation->showSection(
std::make_shared<Info::Memento>(
peer,
Section::Type::SimilarPeers));
});
return result;
}
inline auto AddStoriesButton( [[nodiscard]] not_null<Ui::SettingsButton*> AddSavedSublistButton(
Ui::VerticalLayout *parent, Ui::VerticalLayout *parent,
not_null<Window::SessionNavigation*> navigation, not_null<Window::SessionNavigation*> navigation,
not_null<PeerData*> peer, not_null<PeerData*> peer,
Ui::MultiSlideTracker &tracker) { Ui::MultiSlideTracker &tracker);
auto count = rpl::single(0) | rpl::then(Data::SavedStoriesIds(
peer,
ServerMaxStoryId - 1,
0
) | rpl::map([](const Data::StoriesIdsSlice &slice) {
return slice.fullCount().value_or(0);
}));
const auto phrase = peer->isChannel() ? (+[](int count) {
return tr::lng_profile_posts(tr::now, lt_count, count);
}) : (+[](int count) {
return tr::lng_profile_saved_stories(tr::now, lt_count, count);
});
auto result = AddCountedButton(
parent,
std::move(count),
phrase,
tracker)->entity();
result->addClickHandler([=] {
navigation->showSection(Info::Stories::Make(peer));
});
return result;
}
inline auto AddSavedSublistButton( [[nodiscard]] not_null<Ui::SettingsButton*> AddPeerGiftsButton(
Ui::VerticalLayout *parent, Ui::VerticalLayout *parent,
not_null<Window::SessionNavigation*> navigation, not_null<Window::SessionNavigation*> navigation,
not_null<PeerData*> peer, not_null<PeerData*> peer,
Ui::MultiSlideTracker &tracker) { Ui::MultiSlideTracker &tracker);
auto result = AddCountedButton(
parent,
Profile::SavedSublistCountValue(peer),
[](int count) {
return tr::lng_profile_saved_messages(tr::now, lt_count, count);
},
tracker)->entity();
result->addClickHandler([=] {
navigation->showSection(
std::make_shared<HistoryView::SublistMemento>(
peer->owner().savedMessages().sublist(peer)));
});
return result;
}
inline auto AddPeerGiftsButton(
Ui::VerticalLayout *parent,
not_null<Window::SessionNavigation*> navigation,
not_null<PeerData*> peer,
Ui::MultiSlideTracker &tracker) {
auto result = AddCountedButton(
parent,
Profile::PeerGiftsCountValue(peer),
[](int count) {
return tr::lng_profile_peer_gifts(tr::now, lt_count, count);
},
tracker)->entity();
result->addClickHandler([=] {
navigation->showSection(
std::make_shared<Info::Memento>(
peer,
Section::Type::PeerGifts));
});
return result;
}
} // namespace Info::Media } // namespace Info::Media

View file

@ -15,10 +15,12 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "info/profile/info_profile_icon.h" #include "info/profile/info_profile_icon.h"
#include "info/info_controller.h" #include "info/info_controller.h"
#include "data/data_forum_topic.h" #include "data/data_forum_topic.h"
#include "data/data_peer.h"
#include "ui/widgets/discrete_sliders.h" #include "ui/widgets/discrete_sliders.h"
#include "ui/widgets/shadow.h" #include "ui/widgets/shadow.h"
#include "ui/widgets/buttons.h" #include "ui/widgets/buttons.h"
#include "ui/widgets/box_content_divider.h" #include "ui/widgets/box_content_divider.h"
#include "ui/wrap/slide_wrap.h"
#include "ui/wrap/vertical_layout.h" #include "ui/wrap/vertical_layout.h"
#include "ui/search_field_controller.h" #include "ui/search_field_controller.h"
#include "styles/style_info.h" #include "styles/style_info.h"

View file

@ -343,11 +343,11 @@ void InnerWidget::validateButtons() {
} }
const auto giftId = _entries[index].gift.info.id; const auto giftId = _entries[index].gift.info.id;
const auto manageId = _entries[index].gift.manageId; const auto manageId = _entries[index].gift.manageId;
const auto &descriptor = _entries[index].descriptor;
const auto already = ranges::find(_views, giftId, &View::giftId); const auto already = ranges::find(_views, giftId, &View::giftId);
if (already != end(_views)) { if (already != end(_views)) {
views.push_back(base::take(*already)); views.push_back(base::take(*already));
} else { } else {
const auto &descriptor = _entries[index].descriptor;
const auto unused = ranges::find_if(_views, [&](const View &v) { const auto unused = ranges::find_if(_views, [&](const View &v) {
return v.button && !idUsed(v.giftId, column, row); return v.button && !idUsed(v.giftId, column, row);
}); });
@ -358,16 +358,16 @@ void InnerWidget::validateButtons() {
button->show(); button->show();
views.push_back({ .button = std::move(button) }); views.push_back({ .button = std::move(button) });
} }
auto &view = views.back();
const auto callback = [=] {
showGift(index);
};
view.index = index;
view.manageId = manageId;
view.giftId = giftId;
view.button->setDescriptor(descriptor, mode);
view.button->setClickedCallback(callback);
} }
auto &view = views.back();
const auto callback = [=] {
showGift(index);
};
view.index = index;
view.manageId = manageId;
view.giftId = giftId;
view.button->setDescriptor(descriptor, mode);
view.button->setClickedCallback(callback);
return true; return true;
}; };
for (auto j = fromRow; j != tillRow; ++j) { for (auto j = fromRow; j != tillRow; ++j) {
@ -630,24 +630,26 @@ void Widget::fillTopBarMenu(const Ui::Menu::MenuCallback &addAction) {
}); });
}, filter.skipUnique ? nullptr : &st::mediaPlayerMenuCheck); }, filter.skipUnique ? nullptr : &st::mediaPlayerMenuCheck);
addAction({ .isSeparator = true }); if (_inner->peer()->canManageGifts() && _inner->peer()->isChannel()) {
addAction({ .isSeparator = true });
addAction(tr::lng_peer_gifts_filter_saved(tr::now), [=] { addAction(tr::lng_peer_gifts_filter_saved(tr::now), [=] {
change([](Filter &filter) { change([](Filter &filter) {
filter.skipSaved = !filter.skipSaved; filter.skipSaved = !filter.skipSaved;
if (filter.skipSaved && filter.skipUnsaved) { if (filter.skipSaved && filter.skipUnsaved) {
filter.skipUnsaved = false; filter.skipUnsaved = false;
} }
}); });
}, filter.skipSaved ? nullptr : &st::mediaPlayerMenuCheck); }, filter.skipSaved ? nullptr : &st::mediaPlayerMenuCheck);
addAction(tr::lng_peer_gifts_filter_unsaved(tr::now), [=] { addAction(tr::lng_peer_gifts_filter_unsaved(tr::now), [=] {
change([](Filter &filter) { change([](Filter &filter) {
filter.skipUnsaved = !filter.skipUnsaved; filter.skipUnsaved = !filter.skipUnsaved;
if (filter.skipSaved && filter.skipUnsaved) { if (filter.skipSaved && filter.skipUnsaved) {
filter.skipSaved = false; filter.skipSaved = false;
} }
}); });
}, filter.skipUnsaved ? nullptr : &st::mediaPlayerMenuCheck); }, filter.skipUnsaved ? nullptr : &st::mediaPlayerMenuCheck);
}
} }
rpl::producer<QString> Widget::title() { rpl::producer<QString> Widget::title() {

View file

@ -44,6 +44,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "dialogs/ui/dialogs_message_view.h" #include "dialogs/ui/dialogs_message_view.h"
#include "history/history.h" #include "history/history.h"
#include "history/history_item.h" #include "history/history_item.h"
#include "history/history_item_components.h"
#include "history/history_item_helpers.h" #include "history/history_item_helpers.h"
#include "history/view/history_view_item_preview.h" #include "history/view/history_view_item_preview.h"
#include "info/bot/earn/info_bot_earn_widget.h" #include "info/bot/earn/info_bot_earn_widget.h"
@ -69,6 +70,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/boxes/peer_qr_box.h" #include "ui/boxes/peer_qr_box.h"
#include "ui/boxes/report_box_graphics.h" #include "ui/boxes/report_box_graphics.h"
#include "ui/controls/userpic_button.h" #include "ui/controls/userpic_button.h"
#include "ui/effects/toggle_arrow.h"
#include "ui/painter.h" #include "ui/painter.h"
#include "ui/rect.h" #include "ui/rect.h"
#include "ui/ui_utility.h" #include "ui/ui_utility.h"
@ -93,6 +95,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "styles/style_layers.h" #include "styles/style_layers.h"
#include "styles/style_menu_icons.h" #include "styles/style_menu_icons.h"
#include "styles/style_settings.h" // settingsButtonRightSkip. #include "styles/style_settings.h" // settingsButtonRightSkip.
#include "styles/style_window.h" // mainMenuToggleFourStrokes.
#include <QtGui/QGuiApplication> #include <QtGui/QGuiApplication>
#include <QtGui/QClipboard> #include <QtGui/QClipboard>
@ -516,6 +519,8 @@ base::options::toggle ShowPeerIdBelowAbout({
dayHoursTextValue(state->day.value()) dayHoursTextValue(state->day.value())
) | rpl::after_next(recount), ) | rpl::after_next(recount),
st::infoHoursValue); st::infoHoursValue);
const auto timingArrow = Ui::CreateChild<Ui::RpWidget>(openedWrap);
timingArrow->resize(Size(timing->st().style.font->height));
timing->setAttribute(Qt::WA_TransparentForMouseEvents); timing->setAttribute(Qt::WA_TransparentForMouseEvents);
state->opened.value() | rpl::start_with_next([=](bool value) { state->opened.value() | rpl::start_with_next([=](bool value) {
opened->setTextColorOverride(value opened->setTextColorOverride(value
@ -529,7 +534,8 @@ base::options::toggle ShowPeerIdBelowAbout({
timing->sizeValue() timing->sizeValue()
) | rpl::start_with_next([=](int width, int h1, QSize size) { ) | rpl::start_with_next([=](int width, int h1, QSize size) {
opened->moveToLeft(0, 0, width); opened->moveToLeft(0, 0, width);
timing->moveToRight(0, 0, width); timingArrow->moveToRight(0, 0, width);
timing->moveToRight(timingArrow->width(), 0, width);
const auto margins = opened->getMargins(); const auto margins = opened->getMargins();
const auto added = margins.top() + margins.bottom(); const auto added = margins.top() + margins.bottom();
@ -542,10 +548,7 @@ base::options::toggle ShowPeerIdBelowAbout({
tr::lng_info_hours_label(), tr::lng_info_hours_label(),
st::infoLabel); st::infoLabel);
label->setAttribute(Qt::WA_TransparentForMouseEvents); label->setAttribute(Qt::WA_TransparentForMouseEvents);
const auto link = Ui::CreateChild<Ui::LinkButton>( auto linkText = rpl::combine(
labelWrap,
QString());
rpl::combine(
state->nonTrivial.value(), state->nonTrivial.value(),
state->hours.value(), state->hours.value(),
state->mine.value(), state->mine.value(),
@ -560,10 +563,12 @@ base::options::toggle ShowPeerIdBelowAbout({
: my : my
? tr::lng_info_hours_my_time() ? tr::lng_info_hours_my_time()
: tr::lng_info_hours_local_time(); : tr::lng_info_hours_local_time();
}) | rpl::flatten_latest( }) | rpl::flatten_latest();
) | rpl::start_with_next([=](const QString &text) { const auto link = Ui::CreateChild<Ui::RoundButton>(
link->setText(text); labelWrap,
}, link->lifetime()); std::move(linkText),
st::defaultTableSmallButton);
link->setTextTransform(Ui::RoundButton::TextTransform::NoTransform);
link->setClickedCallback([=] { link->setClickedCallback([=] {
state->myTimezone = !state->myTimezone.current(); state->myTimezone = !state->myTimezone.current();
state->expanded = true; state->expanded = true;
@ -587,6 +592,38 @@ base::options::toggle ShowPeerIdBelowAbout({
inner, inner,
object_ptr<Ui::VerticalLayout>(inner))); object_ptr<Ui::VerticalLayout>(inner)));
other->toggleOn(state->expanded.value(), anim::type::normal); other->toggleOn(state->expanded.value(), anim::type::normal);
constexpr auto kSlideDuration = float64(st::slideWrapDuration);
other->setDuration(kSlideDuration);
{
const auto arrowAnimation
= other->lifetime().make_state<Ui::Animations::Basic>();
arrowAnimation->init([=] {
timingArrow->update();
if (!other->animating()) {
arrowAnimation->stop();
}
});
timingArrow->paintRequest() | rpl::start_with_next([=] {
auto p = QPainter(timingArrow);
const auto progress = other->animating()
? (crl::now() - arrowAnimation->started()) / kSlideDuration
: 1.;
const auto path = Ui::ToggleUpDownArrowPath(
timingArrow->width() / 2,
timingArrow->height() / 2,
st::infoHoursArrowSize,
st::mainMenuToggleFourStrokes,
other->toggled() ? progress : 1 - progress);
auto hq = PainterHighQualityEnabler(p);
p.fillPath(path, timing->st().textFg);
}, timingArrow->lifetime());
state->expanded.value() | rpl::start_with_next([=] {
arrowAnimation->start();
}, other->lifetime());
}
other->finishAnimating(); other->finishAnimating();
const auto days = other->entity(); const auto days = other->entity();
@ -1729,6 +1766,10 @@ object_ptr<Ui::RpWidget> DetailsFiller::setupPersonalChannel(
&& user->personalChannelMessageId(); && user->personalChannelMessageId();
})); }));
messageChannelWrap->finishAnimating(); messageChannelWrap->finishAnimating();
messageChannelWrap->toggledValue(
) | rpl::filter(rpl::mappers::_1) | rpl::start_with_next([=] {
messageChannelWrap->resizeToWidth(messageChannelWrap->width());
}, messageChannelWrap->lifetime());
const auto clear = [=] { const auto clear = [=] {
while (messageChannelWrap->entity()->count()) { while (messageChannelWrap->entity()->count()) {
@ -1825,12 +1866,12 @@ object_ptr<Ui::RpWidget> DetailsFiller::setupPersonalChannel(
} }
}, preview->lifetime()); }, preview->lifetime());
line->sizeValue( line->sizeValue() | rpl::filter_size(
) | rpl::start_with_next([=](const QSize &size) { ) | rpl::start_with_next([=](const QSize &size) {
const auto left = stLabeled.left(); const auto left = stLabeled.left();
const auto right = st::infoPersonalChannelDateSkip; const auto right = st::infoPersonalChannelDateSkip;
const auto top = stLabeled.top(); const auto top = stLabeled.top();
date->moveToRight(right, top); date->moveToRight(right, top, size.width());
name->resizeToWidth(size.width() name->resizeToWidth(size.width()
- left - left
@ -1861,14 +1902,9 @@ object_ptr<Ui::RpWidget> DetailsFiller::setupPersonalChannel(
st::infoProfileLabeledPadding.bottom())); st::infoProfileLabeledPadding.bottom()));
} }
{ {
const auto button = Ui::CreateChild<Ui::RippleButton>( const auto button = Ui::CreateSimpleRectButton(
messageChannelWrap->entity(), messageChannelWrap->entity(),
st::defaultRippleAnimation); st::defaultRippleAnimation);
button->paintRequest(
) | rpl::start_with_next([=](const QRect &rect) {
auto p = QPainter(button);
button->paintRipple(p, 0, 0);
}, button->lifetime());
inner->geometryValue( inner->geometryValue(
) | rpl::start_with_next([=](const QRect &rect) { ) | rpl::start_with_next([=](const QRect &rect) {
button->setGeometry(rect); button->setGeometry(rect);
@ -1880,6 +1916,8 @@ object_ptr<Ui::RpWidget> DetailsFiller::setupPersonalChannel(
msg); msg);
}); });
button->lower(); button->lower();
inner->lifetime().make_state<base::unique_qptr<Ui::RpWidget>>(
button);
} }
inner->setAttribute(Qt::WA_TransparentForMouseEvents); inner->setAttribute(Qt::WA_TransparentForMouseEvents);
Ui::AddSkip(messageChannelWrap->entity()); Ui::AddSkip(messageChannelWrap->entity());
@ -2445,7 +2483,7 @@ void ActionsFiller::addDeleteContactAction(not_null<UserData*> user) {
void ActionsFiller::addFastButtonsMode(not_null<UserData*> user) { void ActionsFiller::addFastButtonsMode(not_null<UserData*> user) {
Expects(user->isBot()); Expects(user->isBot());
const auto helper = &user->session().supportHelper(); const auto bots = &user->session().fastButtonsBots();
const auto button = _wrap->add(object_ptr<Ui::SettingsButton>( const auto button = _wrap->add(object_ptr<Ui::SettingsButton>(
_wrap, _wrap,
rpl::single(u"Fast buttons mode"_q), rpl::single(u"Fast buttons mode"_q),
@ -2459,17 +2497,17 @@ void ActionsFiller::addFastButtonsMode(not_null<UserData*> user) {
AddDivider(_wrap); AddDivider(_wrap);
AddSkip(_wrap); AddSkip(_wrap);
button->toggleOn(helper->fastButtonModeValue(user)); button->toggleOn(bots->enabledValue(user));
button->toggledValue( button->toggledValue(
) | rpl::filter([=](bool value) { ) | rpl::filter([=](bool value) {
return value != helper->fastButtonMode(user); return value != bots->enabled(user);
}) | rpl::start_with_next([=](bool value) { }) | rpl::start_with_next([=](bool value) {
helper->setFastButtonMode(user, value); bots->setEnabled(user, value);
}, button->lifetime()); }, button->lifetime());
} }
void ActionsFiller::addBotCommandActions(not_null<UserData*> user) { void ActionsFiller::addBotCommandActions(not_null<UserData*> user) {
if (user->session().supportMode()) { if (FastButtonsMode()) {
addFastButtonsMode(user); addFastButtonsMode(user);
} }
const auto window = _controller->parentController(); const auto window = _controller->parentController();

View file

@ -7,43 +7,32 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/ */
#include "info/profile/info_profile_inner_widget.h" #include "info/profile/info_profile_inner_widget.h"
#include "info/info_memento.h"
#include "info/info_controller.h" #include "info/info_controller.h"
#include "info/profile/info_profile_widget.h" #include "info/profile/info_profile_widget.h"
#include "info/profile/info_profile_text.h"
#include "info/profile/info_profile_values.h"
#include "info/profile/info_profile_cover.h" #include "info/profile/info_profile_cover.h"
#include "info/profile/info_profile_icon.h" #include "info/profile/info_profile_icon.h"
#include "info/profile/info_profile_members.h" #include "info/profile/info_profile_members.h"
#include "info/profile/info_profile_actions.h" #include "info/profile/info_profile_actions.h"
#include "info/media/info_media_buttons.h" #include "info/media/info_media_buttons.h"
#include "boxes/abstract_box.h"
#include "boxes/add_contact_box.h"
#include "data/data_changes.h" #include "data/data_changes.h"
#include "data/data_channel.h"
#include "data/data_forum_topic.h" #include "data/data_forum_topic.h"
#include "data/data_peer.h"
#include "data/data_photo.h" #include "data/data_photo.h"
#include "data/data_file_origin.h" #include "data/data_file_origin.h"
#include "ui/boxes/confirm_box.h" #include "data/data_user.h"
#include "mainwidget.h"
#include "main/main_session.h" #include "main/main_session.h"
#include "apiwrap.h" #include "apiwrap.h"
#include "api/api_peer_photo.h" #include "api/api_peer_photo.h"
#include "window/main_window.h"
#include "window/window_session_controller.h"
#include "storage/storage_shared_media.h"
#include "lang/lang_keys.h" #include "lang/lang_keys.h"
#include "ui/widgets/buttons.h" #include "ui/widgets/buttons.h"
#include "ui/widgets/checkbox.h" #include "ui/widgets/checkbox.h"
#include "ui/widgets/scroll_area.h" #include "ui/widgets/scroll_area.h"
#include "ui/widgets/shadow.h" #include "ui/widgets/shadow.h"
#include "ui/widgets/box_content_divider.h"
#include "ui/wrap/slide_wrap.h"
#include "ui/wrap/vertical_layout.h" #include "ui/wrap/vertical_layout.h"
#include "ui/wrap/slide_wrap.h"
#include "ui/ui_utility.h" #include "ui/ui_utility.h"
#include "data/data_channel.h"
#include "data/data_shared_media.h"
#include "styles/style_info.h" #include "styles/style_info.h"
#include "styles/style_boxes.h"
// AyuGram includes // AyuGram includes
#include "ayu/ayu_settings.h" #include "ayu/ayu_settings.h"

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