mirror of
https://github.com/AyuGram/AyuGramDesktop.git
synced 2025-04-04 16:49:41 +02:00
Merge tag 'v5.11.1' into dev
This commit is contained in:
commit
6163cc8a5a
188 changed files with 4147 additions and 1082 deletions
35
.devcontainer.json
Normal file
35
.devcontainer.json
Normal 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"
|
||||
}
|
5
.github/workflows/linux.yml
vendored
5
.github/workflows/linux.yml
vendored
|
@ -83,6 +83,7 @@ jobs:
|
|||
fi
|
||||
|
||||
docker run --rm \
|
||||
-u $(id -u) \
|
||||
-v $PWD:/usr/src/tdesktop \
|
||||
-e CONFIG=Debug \
|
||||
tdesktop:centos_env \
|
||||
|
@ -114,8 +115,8 @@ jobs:
|
|||
if: env.UPLOAD_ARTIFACT == 'true'
|
||||
run: |
|
||||
cd $REPO_NAME/out/Debug
|
||||
sudo mkdir artifact
|
||||
sudo mv {Telegram,Updater} artifact/
|
||||
mkdir artifact
|
||||
mv {Telegram,Updater} artifact/
|
||||
- uses: actions/upload-artifact@v4
|
||||
if: env.UPLOAD_ARTIFACT == 'true'
|
||||
name: Upload artifact.
|
||||
|
|
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -19,6 +19,7 @@ Release/
|
|||
ipch/
|
||||
.vs/
|
||||
.vscode/
|
||||
.cache/
|
||||
|
||||
/Telegram/log.txt
|
||||
/Telegram/data
|
||||
|
|
|
@ -1041,6 +1041,7 @@ PRIVATE
|
|||
info/global_media/info_global_media_inner_widget.h
|
||||
info/global_media/info_global_media_provider.cpp
|
||||
info/global_media/info_global_media_provider.h
|
||||
info/media/info_media_buttons.cpp
|
||||
info/media/info_media_buttons.h
|
||||
info/media/info_media_common.cpp
|
||||
info/media/info_media_common.h
|
||||
|
@ -1285,6 +1286,8 @@ PRIVATE
|
|||
media/streaming/media_streaming_video_track.h
|
||||
media/view/media_view_group_thumbs.cpp
|
||||
media/view/media_view_group_thumbs.h
|
||||
media/view/media_view_open_common.cpp
|
||||
media/view/media_view_open_common.h
|
||||
media/view/media_view_overlay_opengl.cpp
|
||||
media/view/media_view_overlay_opengl.h
|
||||
media/view/media_view_overlay_raster.cpp
|
||||
|
@ -1303,7 +1306,6 @@ PRIVATE
|
|||
media/view/media_view_playback_controls.h
|
||||
media/view/media_view_playback_progress.cpp
|
||||
media/view/media_view_playback_progress.h
|
||||
media/view/media_view_open_common.h
|
||||
media/system_media_controls_manager.h
|
||||
media/system_media_controls_manager.cpp
|
||||
menu/menu_antispam_validator.cpp
|
||||
|
@ -1553,6 +1555,8 @@ PRIVATE
|
|||
settings/settings_privacy_security.h
|
||||
settings/settings_scale_preview.cpp
|
||||
settings/settings_scale_preview.h
|
||||
settings/settings_shortcuts.cpp
|
||||
settings/settings_shortcuts.h
|
||||
settings/settings_type.h
|
||||
settings/settings_websites.cpp
|
||||
settings/settings_websites.h
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
// This is a list of your own shortcuts for Telegram Desktop
|
||||
// You can see full list of commands in the 'shortcuts-default.json' file
|
||||
// Place a null value instead of a command string to switch the shortcut off
|
||||
// You can also edit them in Settings > Chat Settings > Keyboard Shortcuts.
|
||||
|
||||
[
|
||||
// {
|
||||
|
|
BIN
Telegram/Resources/icons/menu/shortcut.png
Normal file
BIN
Telegram/Resources/icons/menu/shortcut.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 447 B |
BIN
Telegram/Resources/icons/menu/shortcut@2x.png
Normal file
BIN
Telegram/Resources/icons/menu/shortcut@2x.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 671 B |
BIN
Telegram/Resources/icons/menu/shortcut@3x.png
Normal file
BIN
Telegram/Resources/icons/menu/shortcut@3x.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1,004 B |
|
@ -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_corner_reaction" = "Reaction button on messages";
|
||||
|
||||
"lng_settings_shortcuts" = "Keyboard shortcuts";
|
||||
|
||||
"lng_shortcuts_reset" = "Reset to default";
|
||||
"lng_shortcuts_recording" = "Recording...";
|
||||
"lng_shortcuts_add_another" = "Add another";
|
||||
|
||||
"lng_shortcuts_close" = "Close the window";
|
||||
"lng_shortcuts_lock" = "Lock the application";
|
||||
"lng_shortcuts_minimize" = "Minimize the window";
|
||||
"lng_shortcuts_quit" = "Quit the application";
|
||||
"lng_shortcuts_media_play" = "Play the media";
|
||||
"lng_shortcuts_media_pause" = "Pause the media";
|
||||
"lng_shortcuts_media_play_pause" = "Toggle media playback";
|
||||
"lng_shortcuts_media_stop" = "Stop media playback";
|
||||
"lng_shortcuts_media_previous" = "Previous track";
|
||||
"lng_shortcuts_media_next" = "Next track";
|
||||
"lng_shortcuts_search" = "Search messages";
|
||||
"lng_shortcuts_chat_previous" = "Previous chat";
|
||||
"lng_shortcuts_chat_next" = "Next chat";
|
||||
"lng_shortcuts_chat_first" = "First chat";
|
||||
"lng_shortcuts_chat_last" = "Last chat";
|
||||
"lng_shortcuts_chat_self" = "Saved Messages";
|
||||
"lng_shortcuts_chat_pinned_n" = "Pinned chat #{index}";
|
||||
"lng_shortcuts_show_account_n" = "Account #{index}";
|
||||
"lng_shortcuts_show_all_chats" = "All Chats folder";
|
||||
"lng_shortcuts_show_folder_n" = "Folder #{index}";
|
||||
"lng_shortcuts_show_folder_last" = "Last folder";
|
||||
"lng_shortcuts_folder_next" = "Next folder";
|
||||
"lng_shortcuts_folder_previous" = "Previous folder";
|
||||
"lng_shortcuts_scheduled" = "Scheduled messages";
|
||||
"lng_shortcuts_archive" = "Archived chats";
|
||||
"lng_shortcuts_contacts" = "Contacts list";
|
||||
"lng_shortcuts_just_send" = "Just send";
|
||||
"lng_shortcuts_silent_send" = "Silent send";
|
||||
"lng_shortcuts_schedule" = "Schedule";
|
||||
"lng_shortcuts_read_chat" = "Mark chat as read";
|
||||
"lng_shortcuts_archive_chat" = "Archive chat";
|
||||
"lng_shortcuts_media_fullscreen" = "Toggle video fullscreen";
|
||||
"lng_shortcuts_show_chat_menu" = "Show chat menu";
|
||||
|
||||
"lng_settings_chat_reactions_title" = "Quick Reaction";
|
||||
"lng_settings_chat_reactions_subtitle" = "Choose your favorite reaction";
|
||||
"lng_settings_chat_message_reply_from" = "Bob Harris";
|
||||
|
@ -2551,6 +2591,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
"lng_premium_double_limits_subtitle_accounts" = "Connected Accounts";
|
||||
"lng_premium_double_limits_about_accounts#one" = "Connect {count} account with different mobile numbers";
|
||||
"lng_premium_double_limits_about_accounts#other" = "Connect {count} accounts with different mobile numbers";
|
||||
|
||||
"lng_premium_double_limits_subtitle_similar_channels" = "Similar Channel";
|
||||
"lng_premium_double_limits_about_similar_channels#one" = "View up to {count} similar channel";
|
||||
"lng_premium_double_limits_about_similar_channels#other" = "View up to {count} similar channels";
|
||||
//
|
||||
|
||||
"lng_premium_gift_title" = "Gift Telegram Premium";
|
||||
|
@ -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_button" = "Copy Link";
|
||||
"lng_boost_channel_or" = "or";
|
||||
"lng_boost_channel_gifting" = "Boost your channel by gifting your subscribers Telegram Premium. {link}";
|
||||
"lng_boost_channel_gifting_link" = "Get boosts >";
|
||||
//"lng_boost_channel_or" = "or";
|
||||
//"lng_boost_channel_gifting" = "Boost your channel by gifting your subscribers Telegram Premium. {link}";
|
||||
//"lng_boost_channel_gifting_link" = "Get boosts >";
|
||||
"lng_boost_group_ask" = "Ask your **Premium** members to boost your group with this link:";
|
||||
//"lng_boost_group_gifting" = "Boost your group by gifting your members Telegram Premium. {link}";
|
||||
|
||||
"lng_feature_stories#one" = "**{count}** Story Per Day";
|
||||
"lng_feature_stories#other" = "**{count}** Stories Per Day";
|
||||
|
@ -3326,6 +3372,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
"lng_gift_unique_backdrop" = "Backdrop";
|
||||
"lng_gift_unique_symbol" = "Symbol";
|
||||
"lng_gift_unique_rarity" = "Only {percent} of such collectibles have this attribute.";
|
||||
"lng_gift_unique_availability_label" = "Quantity";
|
||||
"lng_gift_unique_availability#one" = "{count} of {amount} issued";
|
||||
"lng_gift_unique_availability#other" = "{count} of {amount} issued";
|
||||
"lng_gift_unique_info" = "Gifted to {recipient} on {date}.";
|
||||
|
@ -3836,6 +3883,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
"lng_choose_image" = "Choose an image";
|
||||
"lng_choose_file" = "Choose a file";
|
||||
"lng_choose_files" = "Choose Files";
|
||||
"lng_choose_cover" = "Choose video cover";
|
||||
"lng_choose_cover_bad" = "Can't use this file as a caption.";
|
||||
"lng_game_tag" = "Game";
|
||||
|
||||
"lng_context_new_window" = "Open in new window";
|
||||
|
@ -3952,6 +4001,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
"lng_context_read_show" = "show when";
|
||||
"lng_context_edit_shortcut" = "Edit Shortcut";
|
||||
"lng_context_delete_shortcut" = "Delete Quick Reply";
|
||||
"lng_context_gift_send" = "Send Another Gift";
|
||||
|
||||
"lng_add_tag_about" = "Tag this message with an emoji for quick search.";
|
||||
"lng_subscribe_tag_about" = "Organize your Saved Messages with tags. {link}";
|
||||
|
@ -3980,6 +4030,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
"lng_context_disable_spoiler" = "Remove Spoiler";
|
||||
"lng_context_make_paid" = "Make This Content Paid";
|
||||
"lng_context_change_price" = "Change Price";
|
||||
"lng_context_edit_cover" = "Edit Cover";
|
||||
"lng_context_clear_cover" = "Clear Cover";
|
||||
|
||||
"lng_context_mention" = "Mention";
|
||||
"lng_context_search_from" = "Search messages";
|
||||
|
@ -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_share_title" = "Share to";
|
||||
"lng_share_at_time_title" = "Share at {time} to";
|
||||
"lng_share_copy_link" = "Copy share link";
|
||||
"lng_share_confirm" = "Send";
|
||||
"lng_share_wrong_user" = "This game was opened from a different user.";
|
||||
|
@ -4232,7 +4285,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
"lng_media_save_progress" = "{ready} of {total} {mb}";
|
||||
"lng_mediaview_save_as" = "Save As...";
|
||||
"lng_mediaview_copy" = "Copy";
|
||||
"lng_mediaview_copy_frame" = "Copy Frame";
|
||||
"lng_mediaview_forward" = "Forward";
|
||||
"lng_mediaview_share_at_time" = "Share at {time}";
|
||||
"lng_mediaview_delete" = "Delete";
|
||||
"lng_mediaview_save_to_profile" = "Post to Profile";
|
||||
"lng_mediaview_pin_story_done" = "Story pinned";
|
||||
|
|
|
@ -10,7 +10,7 @@
|
|||
<Identity Name="TelegramMessengerLLP.TelegramDesktop"
|
||||
ProcessorArchitecture="ARCHITECTURE"
|
||||
Publisher="CN=536BC709-8EE1-4478-AF22-F0F0F26FF64A"
|
||||
Version="5.10.7.0" />
|
||||
Version="5.11.1.0" />
|
||||
<Properties>
|
||||
<DisplayName>Telegram Desktop</DisplayName>
|
||||
<PublisherDisplayName>Telegram Messenger LLP</PublisherDisplayName>
|
||||
|
|
|
@ -44,8 +44,8 @@ IDI_ICON1 ICON "..\\art\\icon256.ico"
|
|||
//
|
||||
|
||||
VS_VERSION_INFO VERSIONINFO
|
||||
FILEVERSION 5,10,7,0
|
||||
PRODUCTVERSION 5,10,7,0
|
||||
FILEVERSION 5,11,1,0
|
||||
PRODUCTVERSION 5,11,1,0
|
||||
FILEFLAGSMASK 0x3fL
|
||||
#ifdef _DEBUG
|
||||
FILEFLAGS 0x1L
|
||||
|
@ -62,10 +62,10 @@ BEGIN
|
|||
BEGIN
|
||||
VALUE "CompanyName", "Radolyn Labs"
|
||||
VALUE "FileDescription", "AyuGram Desktop"
|
||||
VALUE "FileVersion", "5.10.7.0"
|
||||
VALUE "FileVersion", "5.11.1.0"
|
||||
VALUE "LegalCopyright", "Copyright (C) 2014-2025"
|
||||
VALUE "ProductName", "AyuGram Desktop"
|
||||
VALUE "ProductVersion", "5.10.7.0"
|
||||
VALUE "ProductVersion", "5.11.1.0"
|
||||
END
|
||||
END
|
||||
BLOCK "VarFileInfo"
|
||||
|
|
|
@ -35,8 +35,8 @@ LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
|
|||
//
|
||||
|
||||
VS_VERSION_INFO VERSIONINFO
|
||||
FILEVERSION 5,10,7,0
|
||||
PRODUCTVERSION 5,10,7,0
|
||||
FILEVERSION 5,11,1,0
|
||||
PRODUCTVERSION 5,11,1,0
|
||||
FILEFLAGSMASK 0x3fL
|
||||
#ifdef _DEBUG
|
||||
FILEFLAGS 0x1L
|
||||
|
@ -53,10 +53,10 @@ BEGIN
|
|||
BEGIN
|
||||
VALUE "CompanyName", "Radolyn Labs"
|
||||
VALUE "FileDescription", "AyuGram Desktop Updater"
|
||||
VALUE "FileVersion", "5.10.7.0"
|
||||
VALUE "FileVersion", "5.11.1.0"
|
||||
VALUE "LegalCopyright", "Copyright (C) 2014-2025"
|
||||
VALUE "ProductName", "AyuGram Desktop"
|
||||
VALUE "ProductVersion", "5.10.7.0"
|
||||
VALUE "ProductVersion", "5.11.1.0"
|
||||
END
|
||||
END
|
||||
BLOCK "VarFileInfo"
|
||||
|
|
|
@ -217,7 +217,11 @@ void Authorizations::toggleCallsDisabled(uint64 hash, bool disabled) {
|
|||
MTP_bool(disabled)
|
||||
)).done([=] {
|
||||
_toggleCallsDisabledRequests.remove(hash);
|
||||
}).fail([=] {
|
||||
}).fail([=](const MTP::Error &error) {
|
||||
LOG(("API Error: toggle calls %1. Hash: %2. %3.")
|
||||
.arg(disabled ? u"disabled"_q : u"enabled"_q)
|
||||
.arg(hash)
|
||||
.arg(error.type()));
|
||||
_toggleCallsDisabledRequests.remove(hash);
|
||||
}).send();
|
||||
_toggleCallsDisabledRequests.emplace(hash, id);
|
||||
|
|
|
@ -74,6 +74,7 @@ struct MessageToSend {
|
|||
struct RemoteFileInfo {
|
||||
MTPInputFile file;
|
||||
std::optional<MTPInputFile> thumb;
|
||||
std::optional<MTPInputPhoto> videoCover;
|
||||
std::vector<MTPInputDocument> attachedStickers;
|
||||
};
|
||||
|
||||
|
|
|
@ -276,16 +276,22 @@ mtpRequestId EditTextMessage(
|
|||
takeFileReference = [=] { return photo->fileReference(); };
|
||||
} else if (const auto document = media->document()) {
|
||||
using Flag = MTPDinputMediaDocument::Flag;
|
||||
const auto videoCover = media->videoCover();
|
||||
const auto videoTimestamp = media->videoTimestamp();
|
||||
const auto flags = Flag()
|
||||
| (media->ttlSeconds() ? Flag::f_ttl_seconds : Flag())
|
||||
| (spoilered ? Flag::f_spoiler : Flag());
|
||||
| (spoilered ? Flag::f_spoiler : Flag())
|
||||
| (videoTimestamp ? Flag::f_video_timestamp : Flag())
|
||||
| (videoCover ? Flag::f_video_cover : Flag());
|
||||
takeInputMedia = [=] {
|
||||
return MTP_inputMediaDocument(
|
||||
MTP_flags(flags),
|
||||
document->mtpInput(),
|
||||
MTPInputPhoto(), // video_cover
|
||||
(videoCover
|
||||
? videoCover->mtpInput()
|
||||
: MTPInputPhoto()),
|
||||
MTP_int(media->ttlSeconds()),
|
||||
MTPint(), // video_timestamp
|
||||
MTP_int(videoTimestamp),
|
||||
MTPstring()); // query
|
||||
};
|
||||
takeFileReference = [=] { return document->fileReference(); };
|
||||
|
|
|
@ -13,6 +13,32 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
|
||||
namespace Api {
|
||||
|
||||
PeerId ParsePaidReactionShownPeer(
|
||||
not_null<Main::Session*> session,
|
||||
const MTPPaidReactionPrivacy &value) {
|
||||
return value.match([&](const MTPDpaidReactionPrivacyDefault &) {
|
||||
return session->userPeerId();
|
||||
}, [](const MTPDpaidReactionPrivacyAnonymous &) {
|
||||
return PeerId();
|
||||
}, [&](const MTPDpaidReactionPrivacyPeer &data) {
|
||||
return data.vpeer().match([&](const MTPDinputPeerSelf &) {
|
||||
return session->userPeerId();
|
||||
}, [](const MTPDinputPeerUser &data) {
|
||||
return peerFromUser(data.vuser_id());
|
||||
}, [](const MTPDinputPeerChat &data) {
|
||||
return peerFromChat(data.vchat_id());
|
||||
}, [](const MTPDinputPeerChannel &data) {
|
||||
return peerFromChannel(data.vchannel_id());
|
||||
}, [](const MTPDinputPeerUserFromMessage &data) -> PeerId {
|
||||
Unexpected("From message peer in ParsePaidReactionShownPeer.");
|
||||
}, [](const MTPDinputPeerChannelFromMessage &data) -> PeerId {
|
||||
Unexpected("From message peer in ParsePaidReactionShownPeer.");
|
||||
}, [](const MTPDinputPeerEmpty &) -> PeerId {
|
||||
Unexpected("Empty peer in ParsePaidReactionShownPeer.");
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
GlobalPrivacy::GlobalPrivacy(not_null<ApiWrap*> api)
|
||||
: _session(&api->session())
|
||||
, _api(&api->instance()) {
|
||||
|
@ -115,27 +141,27 @@ rpl::producer<bool> GlobalPrivacy::newRequirePremium() const {
|
|||
return _newRequirePremium.value();
|
||||
}
|
||||
|
||||
void GlobalPrivacy::loadPaidReactionAnonymous() {
|
||||
if (_paidReactionAnonymousLoaded) {
|
||||
void GlobalPrivacy::loadPaidReactionShownPeer() {
|
||||
if (_paidReactionShownPeerLoaded) {
|
||||
return;
|
||||
}
|
||||
_paidReactionAnonymousLoaded = true;
|
||||
_paidReactionShownPeerLoaded = true;
|
||||
_api.request(MTPmessages_GetPaidReactionPrivacy(
|
||||
)).done([=](const MTPUpdates &result) {
|
||||
_session->api().applyUpdates(result);
|
||||
}).send();
|
||||
}
|
||||
|
||||
void GlobalPrivacy::updatePaidReactionAnonymous(bool value) {
|
||||
_paidReactionAnonymous = value;
|
||||
void GlobalPrivacy::updatePaidReactionShownPeer(PeerId shownPeer) {
|
||||
_paidReactionShownPeer = shownPeer;
|
||||
}
|
||||
|
||||
bool GlobalPrivacy::paidReactionAnonymousCurrent() const {
|
||||
return _paidReactionAnonymous.current();
|
||||
PeerId GlobalPrivacy::paidReactionShownPeerCurrent() const {
|
||||
return _paidReactionShownPeer.current();
|
||||
}
|
||||
|
||||
rpl::producer<bool> GlobalPrivacy::paidReactionAnonymous() const {
|
||||
return _paidReactionAnonymous.value();
|
||||
rpl::producer<PeerId> GlobalPrivacy::paidReactionShownPeer() const {
|
||||
return _paidReactionShownPeer.value();
|
||||
}
|
||||
|
||||
void GlobalPrivacy::updateArchiveAndMute(bool value) {
|
||||
|
|
|
@ -23,6 +23,10 @@ enum class UnarchiveOnNewMessage {
|
|||
AnyUnmuted,
|
||||
};
|
||||
|
||||
[[nodiscard]] PeerId ParsePaidReactionShownPeer(
|
||||
not_null<Main::Session*> session,
|
||||
const MTPPaidReactionPrivacy &value);
|
||||
|
||||
class GlobalPrivacy final {
|
||||
public:
|
||||
explicit GlobalPrivacy(not_null<ApiWrap*> api);
|
||||
|
@ -49,10 +53,10 @@ public:
|
|||
[[nodiscard]] bool newRequirePremiumCurrent() const;
|
||||
[[nodiscard]] rpl::producer<bool> newRequirePremium() const;
|
||||
|
||||
void loadPaidReactionAnonymous();
|
||||
void updatePaidReactionAnonymous(bool value);
|
||||
[[nodiscard]] bool paidReactionAnonymousCurrent() const;
|
||||
[[nodiscard]] rpl::producer<bool> paidReactionAnonymous() const;
|
||||
void loadPaidReactionShownPeer();
|
||||
void updatePaidReactionShownPeer(PeerId shownPeer);
|
||||
[[nodiscard]] PeerId paidReactionShownPeerCurrent() const;
|
||||
[[nodiscard]] rpl::producer<PeerId> paidReactionShownPeer() const;
|
||||
|
||||
private:
|
||||
void apply(const MTPGlobalPrivacySettings &data);
|
||||
|
@ -72,9 +76,9 @@ private:
|
|||
rpl::variable<bool> _showArchiveAndMute = false;
|
||||
rpl::variable<bool> _hideReadTime = false;
|
||||
rpl::variable<bool> _newRequirePremium = false;
|
||||
rpl::variable<bool> _paidReactionAnonymous = false;
|
||||
rpl::variable<PeerId> _paidReactionShownPeer = false;
|
||||
std::vector<Fn<void()>> _callbacks;
|
||||
bool _paidReactionAnonymousLoaded = false;
|
||||
bool _paidReactionShownPeerLoaded = false;
|
||||
|
||||
};
|
||||
|
||||
|
|
|
@ -111,7 +111,8 @@ MTPInputMedia PrepareUploadedDocument(
|
|||
| (info.thumb ? Flag::f_thumb : Flag())
|
||||
| (item->groupId() ? Flag::f_nosound_video : Flag())
|
||||
| (info.attachedStickers.empty() ? Flag::f_stickers : Flag())
|
||||
| (ttlSeconds ? Flag::f_ttl_seconds : Flag());
|
||||
| (ttlSeconds ? Flag::f_ttl_seconds : Flag())
|
||||
| (info.videoCover ? Flag::f_video_cover : Flag());
|
||||
const auto document = item->media()->document();
|
||||
return MTP_inputMediaUploadedDocument(
|
||||
MTP_flags(flags),
|
||||
|
@ -121,7 +122,7 @@ MTPInputMedia PrepareUploadedDocument(
|
|||
ComposeSendingDocumentAttributes(document),
|
||||
MTP_vector<MTPInputDocument>(
|
||||
ranges::to<QVector<MTPInputDocument>>(info.attachedStickers)),
|
||||
MTPInputPhoto(), // video_cover
|
||||
info.videoCover.value_or(MTPInputPhoto()),
|
||||
MTP_int(0), // video_timestamp
|
||||
MTP_int(ttlSeconds));
|
||||
}
|
||||
|
|
|
@ -549,10 +549,11 @@ void SendConfirmedFile(
|
|||
using Flag = MTPDmessageMediaDocument::Flag;
|
||||
return MTP_messageMediaDocument(
|
||||
MTP_flags(Flag::f_document
|
||||
| (file->spoiler ? Flag::f_spoiler : Flag())),
|
||||
| (file->spoiler ? Flag::f_spoiler : Flag())
|
||||
| (file->videoCover ? Flag::f_video_cover : Flag())),
|
||||
file->document,
|
||||
MTPVector<MTPDocument>(), // alt_documents
|
||||
MTPPhoto(), // video_cover
|
||||
file->videoCover ? file->videoCover->photo : MTPPhoto(),
|
||||
MTPint(), // video_timestamp
|
||||
MTPint());
|
||||
} else if (file->type == SendMediaType::Audio) {
|
||||
|
@ -561,10 +562,11 @@ void SendConfirmedFile(
|
|||
return MTP_messageMediaDocument(
|
||||
MTP_flags(Flag::f_document
|
||||
| Flag::f_voice
|
||||
| (ttlSeconds ? Flag::f_ttl_seconds : Flag())),
|
||||
| (ttlSeconds ? Flag::f_ttl_seconds : Flag())
|
||||
| (file->videoCover ? Flag::f_video_cover : Flag())),
|
||||
file->document,
|
||||
MTPVector<MTPDocument>(), // alt_documents
|
||||
MTPPhoto(), // video_cover
|
||||
file->videoCover ? file->videoCover->photo : MTPPhoto(),
|
||||
MTPint(), // video_timestamp
|
||||
MTP_int(ttlSeconds));
|
||||
} else if (file->type == SendMediaType::Round) {
|
||||
|
|
|
@ -2717,8 +2717,8 @@ void Updates::feedUpdate(const MTPUpdate &update) {
|
|||
|
||||
case mtpc_updatePaidReactionPrivacy: {
|
||||
const auto &data = update.c_updatePaidReactionPrivacy();
|
||||
_session->api().globalPrivacy().updatePaidReactionAnonymous(
|
||||
mtpIsTrue(data.vprivate()));
|
||||
_session->api().globalPrivacy().updatePaidReactionShownPeer(
|
||||
Api::ParsePaidReactionShownPeer(_session, data.vprivate()));
|
||||
} break;
|
||||
|
||||
}
|
||||
|
|
|
@ -148,6 +148,16 @@ void ShowChannelsLimitBox(not_null<PeerData*> peer) {
|
|||
action.replaceMediaOf);
|
||||
}
|
||||
|
||||
[[nodiscard]] QString FormatVideoTimestamp(TimeId seconds) {
|
||||
const auto minutes = seconds / 60;
|
||||
const auto hours = minutes / 60;
|
||||
return hours
|
||||
? u"%1h%2m%3s"_q.arg(hours).arg(minutes % 60).arg(seconds % 60)
|
||||
: minutes
|
||||
? u"%1m%2s"_q.arg(minutes).arg(seconds % 60)
|
||||
: QString::number(seconds);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
ApiWrap::ApiWrap(not_null<Main::Session*> session)
|
||||
|
@ -740,7 +750,8 @@ void ApiWrap::finalizeMessageDataRequest(
|
|||
QString ApiWrap::exportDirectMessageLink(
|
||||
not_null<HistoryItem*> item,
|
||||
bool inRepliesContext,
|
||||
bool forceNonPublicLink) {
|
||||
bool forceNonPublicLink,
|
||||
std::optional<TimeId> videoTimestamp) {
|
||||
Expects(item->history()->peer->isChannel());
|
||||
|
||||
const auto itemId = item->fullId();
|
||||
|
@ -792,19 +803,6 @@ QString ApiWrap::exportDirectMessageLink(
|
|||
: linkThreadId
|
||||
? (QString::number(linkThreadId.bare) + '/' + post)
|
||||
: post);
|
||||
if (linkChannel->hasUsername()
|
||||
&& !forceNonPublicLink
|
||||
&& !linkChannel->isMegagroup()
|
||||
&& !linkCommentId
|
||||
&& !linkThreadId) {
|
||||
if (const auto media = item->media()) {
|
||||
if (const auto document = media->document()) {
|
||||
if (document->isVideoMessage()) {
|
||||
return u"https://telesco.pe/"_q + query;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return session().createInternalLinkFull(query);
|
||||
};
|
||||
if (forceNonPublicLink) {
|
||||
|
@ -826,7 +824,14 @@ QString ApiWrap::exportDirectMessageLink(
|
|||
_unlikelyMessageLinks.emplace_or_assign(itemId, link);
|
||||
}
|
||||
}).send();
|
||||
return current;
|
||||
const auto addTimestamp = channel->hasUsername()
|
||||
&& !inRepliesContext
|
||||
&& videoTimestamp.has_value();
|
||||
const auto addedSeparator = (current.indexOf('?') >= 0) ? '&' : '?';
|
||||
const auto addedTimestamp = addTimestamp
|
||||
? (addedSeparator + u"t="_q + FormatVideoTimestamp(*videoTimestamp))
|
||||
: QString();
|
||||
return current + addedTimestamp;
|
||||
}
|
||||
|
||||
QString ApiWrap::exportDirectStoryLink(not_null<Data::Story*> story) {
|
||||
|
@ -3624,6 +3629,18 @@ void ApiWrap::editMedia(
|
|||
file.path,
|
||||
file.content,
|
||||
std::move(file.information),
|
||||
(file.videoCover
|
||||
? std::make_unique<FileLoadTask>(
|
||||
&session(),
|
||||
file.videoCover->path,
|
||||
file.videoCover->content,
|
||||
std::move(file.videoCover->information),
|
||||
nullptr,
|
||||
SendMediaType::Photo,
|
||||
to,
|
||||
TextWithTags(),
|
||||
false)
|
||||
: nullptr),
|
||||
type,
|
||||
to,
|
||||
caption,
|
||||
|
@ -3665,6 +3682,19 @@ void ApiWrap::sendFiles(
|
|||
file.path,
|
||||
file.content,
|
||||
std::move(file.information),
|
||||
(file.videoCover
|
||||
? std::make_unique<FileLoadTask>(
|
||||
&session(),
|
||||
file.videoCover->path,
|
||||
file.videoCover->content,
|
||||
std::move(file.videoCover->information),
|
||||
nullptr,
|
||||
SendMediaType::Photo,
|
||||
to,
|
||||
TextWithTags(),
|
||||
false,
|
||||
nullptr)
|
||||
: nullptr),
|
||||
uploadWithType,
|
||||
to,
|
||||
caption,
|
||||
|
@ -3690,11 +3720,13 @@ void ApiWrap::sendFile(
|
|||
auto caption = TextWithTags();
|
||||
const auto spoiler = false;
|
||||
const auto information = nullptr;
|
||||
const auto videoCover = nullptr;
|
||||
_fileLoader->addTask(std::make_unique<FileLoadTask>(
|
||||
&session(),
|
||||
QString(),
|
||||
fileContent,
|
||||
information,
|
||||
videoCover,
|
||||
type,
|
||||
to,
|
||||
caption,
|
||||
|
@ -4209,19 +4241,30 @@ void ApiWrap::uploadAlbumMedia(
|
|||
return;
|
||||
}
|
||||
const auto &fields = document->c_document();
|
||||
const auto mtpCover = data.vvideo_cover();
|
||||
const auto cover = (mtpCover && mtpCover->type() == mtpc_photo)
|
||||
? &(mtpCover->c_photo())
|
||||
: (const MTPDphoto*)nullptr;
|
||||
using Flag = MTPDinputMediaDocument::Flag;
|
||||
const auto flags = Flag()
|
||||
| (data.vttl_seconds() ? Flag::f_ttl_seconds : Flag())
|
||||
| (spoiler ? Flag::f_spoiler : Flag());
|
||||
| (spoiler ? Flag::f_spoiler : Flag())
|
||||
| (data.vvideo_timestamp() ? Flag::f_video_timestamp : Flag())
|
||||
| (cover ? Flag::f_video_cover : Flag());
|
||||
const auto media = MTP_inputMediaDocument(
|
||||
MTP_flags(flags),
|
||||
MTP_inputDocument(
|
||||
fields.vid(),
|
||||
fields.vaccess_hash(),
|
||||
fields.vfile_reference()),
|
||||
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()),
|
||||
MTPint(), // video_timestamp
|
||||
MTPstring()); // query
|
||||
sendAlbumWithUploaded(item, groupId, media);
|
||||
} break;
|
||||
|
|
|
@ -166,7 +166,8 @@ public:
|
|||
QString exportDirectMessageLink(
|
||||
not_null<HistoryItem*> item,
|
||||
bool inRepliesContext,
|
||||
bool forceNonPublicLink = false);
|
||||
bool forceNonPublicLink = false,
|
||||
std::optional<TimeId> videoTimestamp = {});
|
||||
QString exportDirectStoryLink(not_null<Data::Story*> item);
|
||||
|
||||
void requestContacts();
|
||||
|
|
|
@ -467,13 +467,16 @@ void EditCaptionBox::rebuildPreview() {
|
|||
}
|
||||
} else {
|
||||
const auto &file = _preparedList.files.front();
|
||||
|
||||
const auto isVideoFile = file.isVideoFile();
|
||||
const auto media = Ui::SingleMediaPreview::Create(
|
||||
this,
|
||||
st::defaultComposeControls,
|
||||
gifPaused,
|
||||
file,
|
||||
[] { return true; },
|
||||
[=](Ui::AttachActionType type) {
|
||||
return (type != Ui::AttachActionType::EditCover)
|
||||
|| isVideoFile;
|
||||
},
|
||||
Ui::AttachControls::Type::EditOnly);
|
||||
_isPhoto = (media && media->isPhoto());
|
||||
const auto withCheckbox = _isPhoto && CanBeCompressed(_albumType);
|
||||
|
@ -719,7 +722,7 @@ void EditCaptionBox::setupPhotoEditorEventHandler() {
|
|||
controller->uiShow(),
|
||||
&_preparedList.files.front(),
|
||||
st::sendMediaPreviewSize,
|
||||
[=] { rebuildPreview(); });
|
||||
[=](bool ok) { if (ok) rebuildPreview(); });
|
||||
} else {
|
||||
EditPhotoImage(_controller, _photoMedia, hasSpoiler(), [=](
|
||||
Ui::PreparedList &&list) {
|
||||
|
|
|
@ -44,6 +44,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
#include "ui/basic_click_handlers.h" // UrlClickHandler::Open.
|
||||
#include "ui/boxes/boost_box.h" // StartFireworks.
|
||||
#include "ui/controls/userpic_button.h"
|
||||
#include "ui/effects/credits_graphics.h"
|
||||
#include "ui/effects/premium_graphics.h"
|
||||
#include "ui/effects/premium_stars_colored.h"
|
||||
#include "ui/effects/premium_top_bar.h"
|
||||
|
@ -405,21 +406,14 @@ void AddTableRow(
|
|||
auto result = object_ptr<Ui::RpWidget>(table);
|
||||
const auto raw = result.data();
|
||||
|
||||
const auto session = &show->session();
|
||||
const auto makeContext = [session](Fn<void()> update) {
|
||||
return Core::MarkedTextContext{
|
||||
.session = session,
|
||||
.customEmojiRepaint = std::move(update),
|
||||
};
|
||||
};
|
||||
auto star = session->data().customEmojiManager().creditsEmoji();
|
||||
const auto star = Ui::CreateSingleStarWidget(
|
||||
raw,
|
||||
table->st().defaultValue.style.font->height);
|
||||
const auto label = Ui::CreateChild<Ui::FlatLabel>(
|
||||
raw,
|
||||
rpl::single(star.append(
|
||||
' ' + Lang::FormatStarsAmountDecimal(entry.credits))),
|
||||
Lang::FormatStarsAmountDecimal(entry.credits),
|
||||
table->st().defaultValue,
|
||||
st::defaultPopupMenu,
|
||||
std::move(makeContext));
|
||||
st::defaultPopupMenu);
|
||||
|
||||
const auto convert = convertToStars
|
||||
? Ui::CreateChild<Ui::RoundButton>(
|
||||
|
@ -430,7 +424,8 @@ void AddTableRow(
|
|||
table->st().smallButton)
|
||||
: nullptr;
|
||||
if (convert) {
|
||||
convert->setTextTransform(Ui::RoundButton::TextTransform::NoTransform);
|
||||
using namespace Ui;
|
||||
convert->setTextTransform(RoundButton::TextTransform::NoTransform);
|
||||
convert->setClickedCallback(std::move(convertToStars));
|
||||
}
|
||||
rpl::combine(
|
||||
|
@ -440,11 +435,13 @@ void AddTableRow(
|
|||
const auto convertSkip = convertWidth
|
||||
? (st::normalFont->spacew + convertWidth)
|
||||
: 0;
|
||||
label->resizeToNaturalWidth(width - convertSkip);
|
||||
label->moveToLeft(0, 0, width);
|
||||
const auto labelLeft = rect::right(star) + st::normalFont->spacew;
|
||||
label->resizeToNaturalWidth(width - convertSkip - labelLeft);
|
||||
star->moveToLeft(0, 0, width);
|
||||
label->moveToLeft(labelLeft, 0, width);
|
||||
if (convert) {
|
||||
convert->moveToLeft(
|
||||
label->width() + st::normalFont->spacew,
|
||||
rect::right(label) + st::normalFont->spacew,
|
||||
(table->st().defaultValue.style.font->ascent
|
||||
- table->st().smallButton.style.font->ascent),
|
||||
width);
|
||||
|
@ -1490,10 +1487,15 @@ void AddStarGiftTable(
|
|||
auto amount = rpl::single(TextWithEntities{
|
||||
Lang::FormatCountDecimal(entry.limitedCount)
|
||||
});
|
||||
const auto count = unique
|
||||
? (entry.limitedCount - entry.limitedLeft)
|
||||
: entry.limitedLeft;
|
||||
AddTableRow(
|
||||
table,
|
||||
tr::lng_gift_availability(),
|
||||
((!unique && !entry.limitedLeft)
|
||||
(unique
|
||||
? tr::lng_gift_unique_availability_label()
|
||||
: tr::lng_gift_availability()),
|
||||
((!unique && !count)
|
||||
? tr::lng_gift_availability_none(
|
||||
lt_amount,
|
||||
std::move(amount),
|
||||
|
@ -1502,12 +1504,12 @@ void AddStarGiftTable(
|
|||
? tr::lng_gift_unique_availability
|
||||
: tr::lng_gift_availability_left)(
|
||||
lt_count_decimal,
|
||||
rpl::single(entry.limitedLeft * 1.),
|
||||
rpl::single(count * 1.),
|
||||
lt_amount,
|
||||
std::move(amount),
|
||||
Ui::Text::WithEntities)));
|
||||
}
|
||||
if (!unique && !entry.soldOutInfo) {
|
||||
if (!unique && !entry.soldOutInfo && startUpgrade) {
|
||||
AddTableRow(
|
||||
table,
|
||||
tr::lng_gift_unique_status(),
|
||||
|
|
|
@ -922,7 +922,13 @@ void PeerListRow::paintDisabledCheckUserpic(
|
|||
|
||||
p.setPen(userpicBorderPen);
|
||||
p.setBrush(Qt::NoBrush);
|
||||
p.drawEllipse(userpicEllipse);
|
||||
if (peer()->forum()) {
|
||||
const auto radius = userpicDiameter
|
||||
* Ui::ForumUserpicRadiusMultiplier();
|
||||
p.drawRoundedRect(userpicEllipse, radius, radius);
|
||||
} else {
|
||||
p.drawEllipse(userpicEllipse);
|
||||
}
|
||||
|
||||
p.setPen(iconBorderPen);
|
||||
p.setBrush(st.disabledCheckFg);
|
||||
|
|
|
@ -317,6 +317,7 @@ PreviewWrap::PreviewWrap(
|
|||
0, // duration
|
||||
QString(), // author
|
||||
false, // hasLargeMedia
|
||||
false, // photoIsVideoCover
|
||||
0)) // pendingTill
|
||||
, _theme(theme)
|
||||
, _style(style)
|
||||
|
|
|
@ -106,6 +106,8 @@ PeerShortInfoCover::PeerShortInfoCover(
|
|||
, _statusStyle(std::make_unique<CustomLabelStyle>(_st.status))
|
||||
, _status(_widget.get(), std::move(status), _statusStyle->st)
|
||||
, _roundMask(Images::CornersMask(_st.radius))
|
||||
, _roundMaskRetina(
|
||||
Images::CornersMask(_st.radius / style::DevicePixelRatio()))
|
||||
, _videoPaused(std::move(videoPaused)) {
|
||||
_widget->setCursor(_cursor);
|
||||
|
||||
|
@ -190,7 +192,7 @@ void PeerShortInfoCover::paint(QPainter &p) {
|
|||
if (!frame.isNull()) {
|
||||
frame = Images::Round(
|
||||
std::move(frame),
|
||||
_roundMask,
|
||||
_roundMaskRetina,
|
||||
RectPart::TopLeft | RectPart::TopRight);
|
||||
} else if (_userpicImage.isNull()) {
|
||||
auto image = QImage(
|
||||
|
@ -226,10 +228,11 @@ void PeerShortInfoCover::paintCoverImage(QPainter &p, const QImage &image) {
|
|||
const auto top = _widget->height() - fill;
|
||||
const auto factor = style::DevicePixelRatio();
|
||||
if (fill > 0) {
|
||||
const auto t = roundedHeight + _scrollTop;
|
||||
p.drawImage(
|
||||
QRect(0, top, roundedWidth, fill),
|
||||
QRect(0, t, roundedWidth * factor, (roundedWidth - t) * factor),
|
||||
image,
|
||||
QRect(0, top * factor, roundedWidth * factor, fill * factor));
|
||||
QRect(0, t, roundedWidth * factor, (roundedWidth - t) * factor));
|
||||
}
|
||||
if (covered <= 0) {
|
||||
return;
|
||||
|
@ -238,9 +241,9 @@ void PeerShortInfoCover::paintCoverImage(QPainter &p, const QImage &image) {
|
|||
const auto from = top - rounded;
|
||||
auto q = QPainter(&_roundedTopImage);
|
||||
q.drawImage(
|
||||
QRect(0, 0, roundedWidth, rounded),
|
||||
QRect(0, 0, roundedWidth * factor, rounded * factor),
|
||||
image,
|
||||
QRect(0, from * factor, roundedWidth * factor, rounded * factor));
|
||||
QRect(0, _scrollTop, roundedWidth * factor, rounded * factor));
|
||||
q.end();
|
||||
_roundedTopImage = Images::Round(
|
||||
std::move(_roundedTopImage),
|
||||
|
|
|
@ -123,6 +123,7 @@ private:
|
|||
object_ptr<Ui::FlatLabel> _additionalStatus = { nullptr };
|
||||
|
||||
std::array<QImage, 4> _roundMask;
|
||||
std::array<QImage, 4> _roundMaskRetina;
|
||||
QImage _userpicImage;
|
||||
QImage _roundedTopImage;
|
||||
QImage _barSmall;
|
||||
|
|
|
@ -1526,6 +1526,18 @@ void DoubledLimitsPreviewBox(
|
|||
Main::Domain::kPremiumMaxAccounts,
|
||||
till,
|
||||
});
|
||||
{
|
||||
const auto premium = limits.similarChannelsPremium();
|
||||
entries.push_back({
|
||||
tr::lng_premium_double_limits_subtitle_similar_channels(),
|
||||
tr::lng_premium_double_limits_about_similar_channels(
|
||||
lt_count,
|
||||
rpl::single(float64(premium)),
|
||||
Ui::Text::RichLangValue),
|
||||
limits.similarChannelsDefault(),
|
||||
premium,
|
||||
});
|
||||
}
|
||||
Ui::Premium::ShowListBox(
|
||||
box,
|
||||
st::defaultPremiumLimits,
|
||||
|
|
|
@ -250,7 +250,7 @@ SendFilesBox::Block::Block(
|
|||
int till,
|
||||
Fn<bool()> gifPaused,
|
||||
SendFilesWay way,
|
||||
Fn<bool()> canToggleSpoiler)
|
||||
Fn<bool(const Ui::PreparedFile &, Ui::AttachActionType)> actionAllowed)
|
||||
: _items(items)
|
||||
, _from(from)
|
||||
, _till(till) {
|
||||
|
@ -268,7 +268,9 @@ SendFilesBox::Block::Block(
|
|||
st,
|
||||
my,
|
||||
way,
|
||||
std::move(canToggleSpoiler));
|
||||
[=](int index, Ui::AttachActionType type) {
|
||||
return actionAllowed((*_items)[from + index], type);
|
||||
});
|
||||
_preview.reset(preview);
|
||||
} else {
|
||||
const auto media = Ui::SingleMediaPreview::Create(
|
||||
|
@ -276,7 +278,9 @@ SendFilesBox::Block::Block(
|
|||
st,
|
||||
gifPaused,
|
||||
first,
|
||||
std::move(canToggleSpoiler));
|
||||
[=](Ui::AttachActionType type) {
|
||||
return actionAllowed((*_items)[from], type);
|
||||
});
|
||||
if (media) {
|
||||
_isSingleMedia = true;
|
||||
_preview.reset(media);
|
||||
|
@ -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 {
|
||||
if (_isAlbum) {
|
||||
const auto album = static_cast<Ui::AlbumPreview*>(_preview.get());
|
||||
|
@ -1046,7 +1082,16 @@ void SendFilesBox::pushBlock(int from, int till) {
|
|||
till,
|
||||
gifPaused,
|
||||
_sendWay.current(),
|
||||
[=] { return !hasPrice(); });
|
||||
[=](const Ui::PreparedFile &file, Ui::AttachActionType type) {
|
||||
return (type == Ui::AttachActionType::ToggleSpoiler)
|
||||
? !hasPrice()
|
||||
: (type == Ui::AttachActionType::EditCover)
|
||||
? (file.isVideoFile()
|
||||
&& _captionToPeer
|
||||
&& (_captionToPeer->isBroadcast()
|
||||
|| _captionToPeer->isSelf()))
|
||||
: (file.videoCover != nullptr);
|
||||
});
|
||||
auto &block = _blocks.back();
|
||||
const auto widget = _inner->add(
|
||||
block.takeWidget(),
|
||||
|
@ -1167,7 +1212,79 @@ void SendFilesBox::pushBlock(int from, int till) {
|
|||
show,
|
||||
&_list.files[index],
|
||||
st::sendMediaPreviewSize,
|
||||
[=] { refreshAllAfterChanges(from); });
|
||||
[=](bool ok) { if (ok) refreshAllAfterChanges(from); });
|
||||
}, widget->lifetime());
|
||||
|
||||
block.itemEditCoverRequest(
|
||||
) | rpl::start_with_next([=, show = _show](int index) {
|
||||
applyBlockChanges();
|
||||
|
||||
const auto replace = [=](Ui::PreparedList list) {
|
||||
if (list.files.empty()) {
|
||||
return;
|
||||
}
|
||||
auto &entry = _list.files[index];
|
||||
const auto video = entry.information
|
||||
? std::get_if<Ui::PreparedFileInformation::Video>(
|
||||
&entry.information->media)
|
||||
: nullptr;
|
||||
if (!video) {
|
||||
return;
|
||||
}
|
||||
auto old = std::shared_ptr<Ui::PreparedFile>(
|
||||
std::move(entry.videoCover));
|
||||
entry.videoCover = std::make_unique<Ui::PreparedFile>(
|
||||
std::move(list.files.front()));
|
||||
Editor::OpenWithPreparedFile(
|
||||
this,
|
||||
show,
|
||||
entry.videoCover.get(),
|
||||
st::sendMediaPreviewSize,
|
||||
crl::guard(this, [=](bool ok) {
|
||||
if (!ok) {
|
||||
_list.files[index].videoCover = old
|
||||
? std::make_unique<Ui::PreparedFile>(
|
||||
std::move(*old))
|
||||
: nullptr;
|
||||
}
|
||||
refreshAllAfterChanges(from);
|
||||
}),
|
||||
video->thumbnail.size());
|
||||
};
|
||||
const auto checkResult = [=](const Ui::PreparedList &list) {
|
||||
if (list.files.empty()) {
|
||||
return true;
|
||||
}
|
||||
if (list.files.front().type != Ui::PreparedFile::Type::Photo) {
|
||||
show->showToast(tr::lng_choose_cover_bad(tr::now));
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
const auto callback = [=](FileDialog::OpenResult &&result) {
|
||||
const auto premium = _show->session().premium();
|
||||
FileDialogCallback(
|
||||
std::move(result),
|
||||
checkResult,
|
||||
replace,
|
||||
premium,
|
||||
show);
|
||||
};
|
||||
|
||||
FileDialog::GetOpenPath(
|
||||
this,
|
||||
tr::lng_choose_cover(tr::now),
|
||||
FileDialog::ImagesFilter(),
|
||||
crl::guard(this, callback));
|
||||
}, widget->lifetime());
|
||||
|
||||
block.itemClearCoverRequest(
|
||||
) | rpl::start_with_next([=](int index) {
|
||||
applyBlockChanges();
|
||||
refreshAllAfterChanges(from, [&] {
|
||||
auto &entry = _list.files[index];
|
||||
entry.videoCover = nullptr;
|
||||
});
|
||||
}, widget->lifetime());
|
||||
|
||||
block.orderUpdated() | rpl::start_with_next([=]{
|
||||
|
|
|
@ -153,7 +153,9 @@ private:
|
|||
int till,
|
||||
Fn<bool()> gifPaused,
|
||||
Ui::SendFilesWay way,
|
||||
Fn<bool()> canToggleSpoiler);
|
||||
Fn<bool(
|
||||
const Ui::PreparedFile &,
|
||||
Ui::AttachActionType)> actionAllowed);
|
||||
Block(Block &&other) = default;
|
||||
Block &operator=(Block &&other) = default;
|
||||
|
||||
|
@ -164,6 +166,8 @@ private:
|
|||
[[nodiscard]] rpl::producer<int> itemDeleteRequest() const;
|
||||
[[nodiscard]] rpl::producer<int> itemReplaceRequest() const;
|
||||
[[nodiscard]] rpl::producer<int> itemModifyRequest() const;
|
||||
[[nodiscard]] rpl::producer<int> itemEditCoverRequest() const;
|
||||
[[nodiscard]] rpl::producer<int> itemClearCoverRequest() const;
|
||||
[[nodiscard]] rpl::producer<> orderUpdated() const;
|
||||
|
||||
void setSendWay(Ui::SendFilesWay way);
|
||||
|
|
|
@ -282,7 +282,9 @@ void ShareBox::prepare() {
|
|||
_select->resizeToWidth(st::boxWideWidth);
|
||||
Ui::SendPendingMoveResizeEvents(_select);
|
||||
|
||||
setTitle(tr::lng_share_title());
|
||||
setTitle(_descriptor.titleOverride
|
||||
? std::move(_descriptor.titleOverride)
|
||||
: tr::lng_share_title());
|
||||
|
||||
_inner = setInnerWidget(
|
||||
object_ptr<Inner>(this, _descriptor, uiShow()),
|
||||
|
@ -1500,7 +1502,8 @@ ChatHelpers::ForwardedMessagePhraseArgs CreateForwardedMessagePhraseArgs(
|
|||
ShareBox::SubmitCallback ShareBox::DefaultForwardCallback(
|
||||
std::shared_ptr<Ui::Show> show,
|
||||
not_null<History*> history,
|
||||
MessageIdsList msgIds) {
|
||||
MessageIdsList msgIds,
|
||||
std::optional<TimeId> videoTimestamp) {
|
||||
struct State final {
|
||||
base::flat_set<mtpRequestId> requests;
|
||||
};
|
||||
|
@ -1536,6 +1539,9 @@ ShareBox::SubmitCallback ShareBox::DefaultForwardCallback(
|
|||
: Flag(0))
|
||||
| ((forwardOptions == Data::ForwardOptions::NoNamesAndCaptions)
|
||||
? Flag::f_drop_media_captions
|
||||
: Flag(0))
|
||||
| (videoTimestamp.has_value()
|
||||
? Flag::f_video_timestamp
|
||||
: Flag(0));
|
||||
auto mtpMsgIds = QVector<MTPint>();
|
||||
mtpMsgIds.reserve(existingIds.size());
|
||||
|
@ -1593,7 +1599,7 @@ ShareBox::SubmitCallback ShareBox::DefaultForwardCallback(
|
|||
MTP_int(options.scheduled),
|
||||
MTP_inputPeerEmpty(), // send_as
|
||||
Data::ShortcutIdToMTP(session, options.shortcutId),
|
||||
MTPint() // video_timestamp
|
||||
MTP_int(videoTimestamp.value_or(0))
|
||||
)).done([=](const MTPUpdates &updates, mtpRequestId reqId) {
|
||||
threadHistory->session().api().applyUpdates(updates);
|
||||
state->requests.remove(reqId);
|
||||
|
|
|
@ -104,7 +104,8 @@ public:
|
|||
[[nodiscard]] static SubmitCallback DefaultForwardCallback(
|
||||
std::shared_ptr<Ui::Show> show,
|
||||
not_null<History*> history,
|
||||
MessageIdsList msgIds);
|
||||
MessageIdsList msgIds,
|
||||
std::optional<TimeId> videoTimestamp = {});
|
||||
|
||||
struct Descriptor {
|
||||
not_null<Main::Session*> session;
|
||||
|
@ -113,7 +114,9 @@ public:
|
|||
FilterCallback filterCallback;
|
||||
object_ptr<Ui::RpWidget> bottomWidget = { nullptr };
|
||||
rpl::producer<QString> copyLinkText;
|
||||
rpl::producer<QString> titleOverride;
|
||||
ShareBoxStyleOverrides st;
|
||||
std::optional<TimeId> videoTimestamp;
|
||||
struct {
|
||||
int sendersCount = 0;
|
||||
int captionsCount = 0;
|
||||
|
|
|
@ -99,8 +99,8 @@ namespace Ui {
|
|||
namespace {
|
||||
|
||||
constexpr auto kPriceTabAll = 0;
|
||||
constexpr auto kPriceTabLimited = -1;
|
||||
constexpr auto kPriceTabInStock = -2;
|
||||
constexpr auto kPriceTabLimited = -2;
|
||||
constexpr auto kPriceTabInStock = -1;
|
||||
constexpr auto kGiftMessageLimit = 255;
|
||||
constexpr auto kSentToastDuration = 3 * crl::time(1000);
|
||||
constexpr auto kSwitchUpgradeCoverInterval = 3 * crl::time(1000);
|
||||
|
@ -2371,7 +2371,7 @@ void ShowUniqueGiftWearBox(
|
|||
object_ptr<Ui::FlatLabel>(
|
||||
raw,
|
||||
std::move(text),
|
||||
st.infoAbout ? *st.infoAbout : st::boxDividerLabel),
|
||||
st.infoAbout ? *st.infoAbout : st::upgradeGiftSubtext),
|
||||
st::settingsPremiumRowAboutPadding);
|
||||
object_ptr<Info::Profile::FloatingIcon>(
|
||||
raw,
|
||||
|
@ -2603,7 +2603,7 @@ void UpgradeBox(
|
|||
object_ptr<Ui::FlatLabel>(
|
||||
raw,
|
||||
std::move(text),
|
||||
st::boxDividerLabel),
|
||||
st::upgradeGiftSubtext),
|
||||
st::settingsPremiumRowAboutPadding);
|
||||
object_ptr<Info::Profile::FloatingIcon>(
|
||||
raw,
|
||||
|
|
|
@ -578,8 +578,7 @@ void Instance::handleCallUpdate(
|
|||
if (inCall()
|
||||
&& _currentCall->type() == Call::Type::Outgoing
|
||||
&& _currentCall->user()->id == session->userPeerId()
|
||||
&& (peerFromUser(phoneCall.vparticipant_id())
|
||||
== _currentCall->user()->session().userPeerId())) {
|
||||
&& (user->id == _currentCall->user()->session().userPeerId())) {
|
||||
// Ignore call from the same running app, other account.
|
||||
return;
|
||||
}
|
||||
|
|
|
@ -42,6 +42,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
#include "ui/emoji_config.h"
|
||||
#include "ui/painter.h"
|
||||
#include "ui/rect.h"
|
||||
#include "ui/integration.h"
|
||||
#include "core/application.h"
|
||||
#include "lang/lang_keys.h"
|
||||
#include "main/main_session.h"
|
||||
|
@ -65,6 +66,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
namespace Calls {
|
||||
namespace {
|
||||
|
||||
constexpr auto kHideControlsTimeout = 5 * crl::time(1000);
|
||||
constexpr auto kHideControlsQuickTimeout = 2 * crl::time(1000);
|
||||
|
||||
[[nodiscard]] QByteArray BatterySvg(
|
||||
const QSize &s,
|
||||
const QColor &c) {
|
||||
|
@ -118,7 +122,9 @@ Panel::Panel(not_null<Call*> call)
|
|||
st::callMicrophoneMute,
|
||||
&st::callMicrophoneUnmute))
|
||||
, _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->setHideByBackgroundClick(true);
|
||||
|
||||
|
@ -188,6 +194,25 @@ void Panel::initWindow() {
|
|||
&& window()->isFullScreen()) {
|
||||
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;
|
||||
});
|
||||
|
@ -415,7 +440,11 @@ void Panel::refreshIncomingGeometry() {
|
|||
void Panel::reinitWithCall(Call *call) {
|
||||
_callLifetime.destroy();
|
||||
_call = call;
|
||||
const auto guard = gsl::finally([&] {
|
||||
updateControlsShown();
|
||||
});
|
||||
if (!_call) {
|
||||
_fingerprint.destroy();
|
||||
_incoming = nullptr;
|
||||
_outgoingVideoBubble = nullptr;
|
||||
_powerSaveBlocker = nullptr;
|
||||
|
@ -457,6 +486,51 @@ void Panel::reinitWithCall(Call *call) {
|
|||
_window.backend());
|
||||
_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(
|
||||
) | rpl::start_with_next([=](bool mute) {
|
||||
_mute->entity()->setProgress(mute ? 1. : 0.);
|
||||
|
@ -603,6 +677,8 @@ void Panel::createRemoteAudioMute() {
|
|||
const auto r = _remoteAudioMute->rect();
|
||||
|
||||
auto hq = PainterHighQualityEnabler(p);
|
||||
p.setOpacity(_controlsShownAnimation.value(
|
||||
_controlsShown ? 1. : 0.));
|
||||
p.setBrush(st::videoPlayIconBg);
|
||||
p.setPen(Qt::NoPen);
|
||||
p.drawRoundedRect(r, r.height() / 2, r.height() / 2);
|
||||
|
@ -661,6 +737,8 @@ void Panel::createRemoteLowBattery() {
|
|||
const auto r = _remoteLowBattery->rect();
|
||||
|
||||
auto hq = PainterHighQualityEnabler(p);
|
||||
p.setOpacity(_controlsShownAnimation.value(
|
||||
_controlsShown ? 1. : 0.));
|
||||
p.setBrush(st::videoPlayIconBg);
|
||||
p.setPen(Qt::NoPen);
|
||||
p.drawRoundedRect(r, r.height() / 2, r.height() / 2);
|
||||
|
@ -782,6 +860,9 @@ void Panel::showDevicesMenu(
|
|||
}
|
||||
Core::App().saveSettingsDelayed();
|
||||
};
|
||||
controlsShownForce(true);
|
||||
updateControlsShown();
|
||||
|
||||
_devicesMenu = MakeDeviceSelectionMenu(
|
||||
widget(),
|
||||
&Core::App().mediaDevices(),
|
||||
|
@ -791,6 +872,9 @@ void Panel::showDevicesMenu(
|
|||
Ui::PopupMenu::VerticalOrigin::Bottom);
|
||||
_devicesMenu->popup(button->mapToGlobal(QPoint())
|
||||
- QPoint(st::callDeviceSelectionMenu.menu.widthMin / 2, 0));
|
||||
QObject::connect(_devicesMenu.get(), &QObject::destroyed, window(), [=] {
|
||||
_controlsShownForceTimer.callOnce(kHideControlsQuickTimeout);
|
||||
});
|
||||
}
|
||||
|
||||
void Panel::refreshOutgoingPreviewInBody(State state) {
|
||||
|
@ -823,6 +907,33 @@ QRect Panel::outgoingFrameGeometry() const {
|
|||
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() {
|
||||
if (widget()->size().isEmpty()) {
|
||||
return;
|
||||
|
@ -830,6 +941,8 @@ void Panel::updateControlsGeometry() {
|
|||
if (_incoming) {
|
||||
refreshIncomingGeometry();
|
||||
}
|
||||
const auto shown = _controlsShownAnimation.value(
|
||||
_controlsShown ? 1. : 0.);
|
||||
if (_fingerprint) {
|
||||
#ifndef Q_OS_MAC
|
||||
const auto controlsGeometry = _controls->controls.geometry();
|
||||
|
@ -848,14 +961,14 @@ void Panel::updateControlsGeometry() {
|
|||
const auto minRight = 0;
|
||||
#endif // _controls
|
||||
const auto desired = (widget()->width() - _fingerprint->width()) / 2;
|
||||
const auto top = anim::interpolate(
|
||||
-_fingerprint->height(),
|
||||
st::callFingerprintTop,
|
||||
shown);
|
||||
if (minLeft) {
|
||||
_fingerprint->moveToLeft(
|
||||
std::max(desired, minLeft),
|
||||
st::callFingerprintTop);
|
||||
_fingerprint->moveToLeft(std::max(desired, minLeft), top);
|
||||
} else {
|
||||
_fingerprint->moveToRight(
|
||||
std::max(desired, minRight),
|
||||
st::callFingerprintTop);
|
||||
_fingerprint->moveToRight(std::max(desired, minRight), top);
|
||||
}
|
||||
}
|
||||
const auto innerHeight = std::max(widget()->height(), st::callHeightMin);
|
||||
|
@ -885,7 +998,11 @@ void Panel::updateControlsGeometry() {
|
|||
/ (_outgoingPreviewInBody ? 3 : 2);
|
||||
|
||||
_bodyTop = availableTop + skipHeight;
|
||||
_buttonsTop = availableTop + available;
|
||||
_buttonsTopShown = availableTop + available;
|
||||
_buttonsTop = anim::interpolate(
|
||||
widget()->height(),
|
||||
_buttonsTopShown,
|
||||
shown);
|
||||
const auto previewTop = _bodyTop + _bodySt->height + skipHeight;
|
||||
|
||||
_userpic->setGeometry(
|
||||
|
@ -908,6 +1025,8 @@ void Panel::updateControlsGeometry() {
|
|||
(_buttonsTop
|
||||
- st::callRemoteAudioMuteSkip
|
||||
- _remoteAudioMute->height()));
|
||||
_remoteAudioMute->update();
|
||||
_remoteAudioMute->entity()->setOpacity(shown);
|
||||
}
|
||||
if (_remoteLowBattery) {
|
||||
_remoteLowBattery->moveToLeft(
|
||||
|
@ -915,6 +1034,8 @@ void Panel::updateControlsGeometry() {
|
|||
(_buttonsTop
|
||||
- st::callRemoteAudioMuteSkip
|
||||
- _remoteLowBattery->height()));
|
||||
_remoteLowBattery->update();
|
||||
_remoteLowBattery->entity()->setOpacity(shown);
|
||||
}
|
||||
|
||||
if (_outgoingPreviewInBody) {
|
||||
|
@ -925,7 +1046,7 @@ void Panel::updateControlsGeometry() {
|
|||
previewTop,
|
||||
bodyPreviewSize.width(),
|
||||
bodyPreviewSize.height()));
|
||||
} else {
|
||||
} else if (_outgoingVideoBubble) {
|
||||
updateOutgoingVideoBubbleGeometry();
|
||||
}
|
||||
|
||||
|
|
|
@ -111,6 +111,9 @@ private:
|
|||
|
||||
[[nodiscard]] bool handleClose() const;
|
||||
|
||||
void requestControlsHidden(bool hidden);
|
||||
void controlsShownForce(bool shown);
|
||||
void updateControlsShown();
|
||||
void updateControlsGeometry();
|
||||
void updateHangupGeometry();
|
||||
void updateStatusGeometry();
|
||||
|
@ -177,8 +180,19 @@ private:
|
|||
std::unique_ptr<VideoBubble> _outgoingVideoBubble;
|
||||
QPixmap _bottomShadow;
|
||||
int _bodyTop = 0;
|
||||
int _buttonsTopShown = 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::Timer _updateDurationTimer;
|
||||
|
|
|
@ -855,6 +855,17 @@ historyComposeButton: FlatButton {
|
|||
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) {
|
||||
color: attentionButtonFg;
|
||||
overColor: attentionButtonFgOver;
|
||||
|
|
|
@ -628,6 +628,13 @@ void EmojiListWidget::applyNextSearchQuery() {
|
|||
const auto modeChanged = (_searchMode != searching);
|
||||
clearSelection();
|
||||
if (modeChanged) {
|
||||
if (_picker) {
|
||||
_picker->hideAnimated();
|
||||
}
|
||||
_colorAllRipple = nullptr;
|
||||
for (auto &set : _custom) {
|
||||
set.ripple = nullptr;
|
||||
}
|
||||
_searchMode = searching;
|
||||
}
|
||||
if (!searching) {
|
||||
|
|
|
@ -329,6 +329,7 @@ not_null<DocumentData*> GenerateLocalSticker(
|
|||
path,
|
||||
QByteArray(),
|
||||
nullptr,
|
||||
nullptr,
|
||||
SendMediaType::File,
|
||||
FileLoadTo(0, {}, {}, 0),
|
||||
{},
|
||||
|
|
|
@ -61,6 +61,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
#include "settings/settings_privacy_security.h"
|
||||
#include "settings/settings_chat.h"
|
||||
#include "settings/settings_premium.h"
|
||||
#include "storage/storage_account.h"
|
||||
#include "mainwidget.h"
|
||||
#include "main/main_account.h"
|
||||
#include "main/main_app_config.h"
|
||||
|
@ -602,6 +603,8 @@ bool ResolveUsernameOrPhone(
|
|||
const auto threadParam = params.value(u"thread"_q);
|
||||
const auto threadId = topicId ? topicId : threadParam.toInt();
|
||||
const auto gameParam = params.value(u"game"_q);
|
||||
const auto videot = params.value(u"t"_q);
|
||||
|
||||
if (!gameParam.isEmpty() && validDomain(gameParam)) {
|
||||
startToken = gameParam;
|
||||
resolveType = ResolveType::ShareGame;
|
||||
|
@ -618,6 +621,9 @@ bool ResolveUsernameOrPhone(
|
|||
.phone = phone,
|
||||
.messageId = post,
|
||||
.storyId = storyId,
|
||||
.videoTimestamp = (!videot.isEmpty()
|
||||
? ParseVideoTimestamp(videot)
|
||||
: std::optional<TimeId>()),
|
||||
.text = params.value(u"text"_q),
|
||||
.repliesInfo = commentId
|
||||
? Window::RepliesByLinkInfo{
|
||||
|
@ -781,8 +787,8 @@ bool OpenMediaTimestamp(
|
|||
if (!controller) {
|
||||
return false;
|
||||
}
|
||||
const auto time = match->captured(2).toInt();
|
||||
if (time < 0) {
|
||||
const auto position = match->captured(2).toInt();
|
||||
if (position < 0) {
|
||||
return false;
|
||||
}
|
||||
const auto base = match->captured(1);
|
||||
|
@ -795,7 +801,7 @@ bool OpenMediaTimestamp(
|
|||
const auto session = &controller->session();
|
||||
const auto document = session->data().document(documentId);
|
||||
const auto context = session->data().message(itemId);
|
||||
const auto timeMs = time * crl::time(1000);
|
||||
const auto time = position * crl::time(1000);
|
||||
if (document->isVideoFile()) {
|
||||
controller->window().openInMediaView(Media::View::OpenRequest(
|
||||
controller,
|
||||
|
@ -803,11 +809,9 @@ bool OpenMediaTimestamp(
|
|||
context,
|
||||
context ? context->topicRootId() : MsgId(0),
|
||||
false,
|
||||
timeMs));
|
||||
time));
|
||||
} else if (document->isSong() || document->isVoiceMessage()) {
|
||||
session->settings().setMediaLastPlaybackPosition(
|
||||
documentId,
|
||||
timeMs);
|
||||
session->local().setMediaLastPlaybackPosition(documentId, time);
|
||||
Media::Player::instance()->play({ document, itemId });
|
||||
}
|
||||
return true;
|
||||
|
@ -1759,4 +1763,14 @@ void ResolveAndShowUniqueGift(
|
|||
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
|
||||
|
|
|
@ -50,4 +50,6 @@ void ResolveAndShowUniqueGift(
|
|||
std::shared_ptr<ChatHelpers::Show> show,
|
||||
const QString &slug);
|
||||
|
||||
[[nodiscard]] TimeId ParseVideoTimestamp(QStringView value);
|
||||
|
||||
} // namespace Core
|
||||
|
|
|
@ -28,6 +28,7 @@ namespace {
|
|||
constexpr auto kCountLimit = 2048; // How many shortcuts can be in json file.
|
||||
|
||||
rpl::event_stream<not_null<Request*>> RequestsStream;
|
||||
bool Paused/* = false*/;
|
||||
|
||||
const auto AutoRepeatCommands = base::flat_set<Command>{
|
||||
Command::MediaPrevious,
|
||||
|
@ -112,45 +113,15 @@ const auto CommandByName = base::flat_map<QString, Command>{
|
|||
//
|
||||
};
|
||||
|
||||
const auto CommandNames = base::flat_map<Command, QString>{
|
||||
{ Command::Close , u"close_telegram"_q },
|
||||
{ Command::Lock , u"lock_telegram"_q },
|
||||
{ Command::Minimize , u"minimize_telegram"_q },
|
||||
{ Command::Quit , u"quit_telegram"_q },
|
||||
|
||||
{ Command::MediaPlay , u"media_play"_q },
|
||||
{ Command::MediaPause , u"media_pause"_q },
|
||||
{ Command::MediaPlayPause , u"media_playpause"_q },
|
||||
{ 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 },
|
||||
const base::flat_map<Command, QString> &CommandNames() {
|
||||
static const auto result = [&] {
|
||||
auto result = base::flat_map<Command, QString>();
|
||||
for (const auto &[name, command] : CommandByName) {
|
||||
result.emplace(command, name);
|
||||
}
|
||||
return result;
|
||||
}();
|
||||
return result;
|
||||
};
|
||||
|
||||
[[maybe_unused]] constexpr auto kNoValue = {
|
||||
|
@ -175,19 +146,39 @@ public:
|
|||
|
||||
[[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:
|
||||
void fillDefaults();
|
||||
void writeDefaultFile();
|
||||
void writeCustomFile();
|
||||
bool readCustomFile();
|
||||
|
||||
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 QKeySequence &keys);
|
||||
void unregister(base::unique_qptr<QAction> shortcut);
|
||||
|
||||
void pruneListened();
|
||||
|
||||
QStringList _errors;
|
||||
|
||||
base::flat_map<QKeySequence, base::unique_qptr<QAction>> _shortcuts;
|
||||
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*> _supportShortcuts;
|
||||
|
@ -278,6 +269,54 @@ const QStringList &Manager::errors() const {
|
|||
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 {
|
||||
auto result = std::vector<Command>();
|
||||
auto i = _commandByObject.findFirst(object);
|
||||
|
@ -301,11 +340,23 @@ void Manager::toggleSupport(bool toggled) {
|
|||
}
|
||||
|
||||
void Manager::listen(not_null<QWidget*> widget) {
|
||||
pruneListened();
|
||||
_listened.push_back(widget.get());
|
||||
for (const auto &[keys, shortcut] : _shortcuts) {
|
||||
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() {
|
||||
// read custom shortcuts from file if it exists or write an empty custom shortcuts file
|
||||
QFile file(CustomFilePath());
|
||||
|
@ -440,7 +491,9 @@ void Manager::fillDefaults() {
|
|||
|
||||
set(u"ctrl+r"_q, Command::ReadChat);
|
||||
|
||||
set(u"ctrl+="_q, Command::ShowChatMenu);
|
||||
set(u"ctrl+\\"_q, Command::ShowChatMenu);
|
||||
|
||||
_defaults = keysCurrents();
|
||||
}
|
||||
|
||||
void Manager::writeDefaultFile() {
|
||||
|
@ -466,8 +519,8 @@ void Manager::writeDefaultFile() {
|
|||
auto i = _commandByObject.findFirst(object);
|
||||
const auto end = _commandByObject.end();
|
||||
for (; i != end && i->first == object; ++i) {
|
||||
const auto j = CommandNames.find(i->second);
|
||||
if (j != CommandNames.end()) {
|
||||
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);
|
||||
|
@ -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();
|
||||
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));
|
||||
return;
|
||||
}
|
||||
set(result, command, replace);
|
||||
}
|
||||
|
||||
void Manager::set(
|
||||
const QKeySequence &keys,
|
||||
Command command,
|
||||
bool replace) {
|
||||
auto shortcut = base::make_unique_q<QAction>();
|
||||
shortcut->setShortcut(result);
|
||||
shortcut->setShortcut(keys);
|
||||
shortcut->setShortcutContext(Qt::ApplicationShortcut);
|
||||
if (!AutoRepeatCommands.contains(command)) {
|
||||
shortcut->setAutoRepeat(false);
|
||||
|
@ -516,20 +625,26 @@ void Manager::set(const QString &keys, Command command, bool replace) {
|
|||
shortcut->setEnabled(false);
|
||||
}
|
||||
auto object = shortcut.get();
|
||||
auto i = _shortcuts.find(result);
|
||||
auto i = _shortcuts.find(keys);
|
||||
if (i == end(_shortcuts)) {
|
||||
i = _shortcuts.emplace(result, std::move(shortcut)).first;
|
||||
i = _shortcuts.emplace(keys, std::move(shortcut)).first;
|
||||
} else if (replace) {
|
||||
unregister(std::exchange(i->second, std::move(shortcut)));
|
||||
} else {
|
||||
object = i->second.get();
|
||||
}
|
||||
_commandByObject.emplace(object, command);
|
||||
if (!shortcut && isMediaShortcut) {
|
||||
_mediaShortcuts.emplace(i->second.get());
|
||||
}
|
||||
if (!shortcut && isSupportShortcut) {
|
||||
_supportShortcuts.emplace(i->second.get());
|
||||
if (!shortcut) { // Added the new one.
|
||||
if (isMediaShortcut) {
|
||||
_mediaShortcuts.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));
|
||||
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)) {
|
||||
unregister(std::move(i->second));
|
||||
_shortcuts.erase(i);
|
||||
|
@ -552,7 +671,7 @@ void Manager::remove(const QString &keys) {
|
|||
|
||||
void Manager::unregister(base::unique_qptr<QAction> shortcut) {
|
||||
if (shortcut) {
|
||||
_commandByObject.erase(shortcut.get());
|
||||
_commandByObject.removeAll(shortcut.get());
|
||||
_mediaShortcuts.erase(shortcut.get());
|
||||
_supportShortcuts.erase(shortcut.get());
|
||||
}
|
||||
|
@ -598,7 +717,9 @@ bool Launch(Command command) {
|
|||
}
|
||||
|
||||
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 false;
|
||||
|
@ -630,6 +751,69 @@ void ToggleSupportShortcuts(bool 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() {
|
||||
Data.clear();
|
||||
}
|
||||
|
|
|
@ -139,4 +139,21 @@ void ToggleMediaShortcuts(bool toggled);
|
|||
// have some conflicts with default input shortcuts, like Ctrl+Delete.
|
||||
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
|
||||
|
|
|
@ -22,7 +22,7 @@ constexpr auto AppId = "{53F49750-6209-4FBF-9CA8-7A333C87D666}"_cs;
|
|||
constexpr auto AppNameOld = "AyuGram for Windows"_cs;
|
||||
constexpr auto AppName = "AyuGram Desktop"_cs;
|
||||
constexpr auto AppFile = "AyuGram"_cs;
|
||||
constexpr auto AppVersion = 5010007;
|
||||
constexpr auto AppVersionStr = "5.10.7";
|
||||
constexpr auto AppVersion = 5011001;
|
||||
constexpr auto AppVersionStr = "5.11.1";
|
||||
constexpr auto AppBetaVersion = false;
|
||||
constexpr auto AppAlphaVersion = TDESKTOP_ALPHA_VERSION;
|
||||
|
|
|
@ -1012,7 +1012,7 @@ PeerId ChannelData::groupCallDefaultJoinAs() const {
|
|||
void ChannelData::setAllowedReactions(Data::AllowedReactions value) {
|
||||
if (_allowedReactions != value) {
|
||||
if (value.paidEnabled) {
|
||||
session().api().globalPrivacy().loadPaidReactionAnonymous();
|
||||
session().api().globalPrivacy().loadPaidReactionShownPeer();
|
||||
}
|
||||
const auto enabled = [](const Data::AllowedReactions &allowed) {
|
||||
return (allowed.type != Data::AllowedReactionsType::Some)
|
||||
|
|
|
@ -12,6 +12,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
#include "data/data_streaming.h"
|
||||
#include "data/data_document_media.h"
|
||||
#include "data/data_reply_preview.h"
|
||||
#include "data/data_web_page.h"
|
||||
#include "lang/lang_keys.h"
|
||||
#include "inline_bots/inline_bot_layout_item.h"
|
||||
#include "main/main_session.h"
|
||||
|
@ -1881,3 +1882,18 @@ void DocumentData::collectLocalData(not_null<DocumentData*> local) {
|
|||
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;
|
||||
}
|
||||
|
|
|
@ -13,6 +13,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
#include "data/data_cloud_file.h"
|
||||
#include "core/file_location.h"
|
||||
|
||||
class HistoryItem;
|
||||
class PhotoData;
|
||||
enum class ChatRestriction;
|
||||
class mtpFileLoader;
|
||||
|
||||
|
@ -402,6 +404,10 @@ private:
|
|||
|
||||
};
|
||||
|
||||
[[nodiscard]] PhotoData *LookupVideoCover(
|
||||
not_null<DocumentData*> document,
|
||||
HistoryItem *item);
|
||||
|
||||
VoiceWaveform documentWaveformDecode(const QByteArray &encoded5bit);
|
||||
QByteArray documentWaveformEncode5bit(const VoiceWaveform &waveform);
|
||||
|
||||
|
|
|
@ -105,6 +105,7 @@ struct FileReferenceAccumulator {
|
|||
push(data.vphoto());
|
||||
}, [&](const MTPDmessageMediaDocument &data) {
|
||||
push(data.vdocument());
|
||||
push(data.vvideo_cover());
|
||||
push(data.valt_documents());
|
||||
}, [&](const MTPDmessageMediaWebPage &data) {
|
||||
push(data.vwebpage());
|
||||
|
|
|
@ -447,16 +447,17 @@ Call ComputeCallData(const MTPDmessageActionPhoneCall &call) {
|
|||
auto result = Call();
|
||||
result.finishReason = [&] {
|
||||
if (const auto reason = call.vreason()) {
|
||||
switch (reason->type()) {
|
||||
case mtpc_phoneCallDiscardReasonBusy:
|
||||
return reason->match([](const MTPDphoneCallDiscardReasonBusy &) {
|
||||
return CallFinishReason::Busy;
|
||||
case mtpc_phoneCallDiscardReasonDisconnect:
|
||||
}, [](const MTPDphoneCallDiscardReasonDisconnect &) {
|
||||
return CallFinishReason::Disconnected;
|
||||
case mtpc_phoneCallDiscardReasonHangup:
|
||||
}, [](const MTPDphoneCallDiscardReasonHangup &) {
|
||||
return CallFinishReason::Hangup;
|
||||
case mtpc_phoneCallDiscardReasonMissed:
|
||||
}, [](const MTPDphoneCallDiscardReasonMissed &) {
|
||||
return CallFinishReason::Missed;
|
||||
}
|
||||
}, [](const MTPDphoneCallDiscardReasonAllowGroupCall &) {
|
||||
return CallFinishReason::AllowGroupCall;
|
||||
});
|
||||
Unexpected("Call reason type.");
|
||||
}
|
||||
return CallFinishReason::Hangup;
|
||||
|
@ -552,6 +553,14 @@ DocumentData *Media::document() const {
|
|||
return nullptr;
|
||||
}
|
||||
|
||||
PhotoData *Media::videoCover() const {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
TimeId Media::videoTimestamp() const {
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool Media::hasQualitiesList() const {
|
||||
return false;
|
||||
}
|
||||
|
@ -968,17 +977,16 @@ std::unique_ptr<HistoryView::Media> MediaPhoto::createView(
|
|||
MediaFile::MediaFile(
|
||||
not_null<HistoryItem*> parent,
|
||||
not_null<DocumentData*> document,
|
||||
bool skipPremiumEffect,
|
||||
bool hasQualitiesList,
|
||||
bool spoiler,
|
||||
crl::time ttlSeconds)
|
||||
Args &&args)
|
||||
: Media(parent)
|
||||
, _document(document)
|
||||
, _videoCover(args.videoCover)
|
||||
, _ttlSeconds(args.ttlSeconds)
|
||||
, _emoji(document->sticker() ? document->sticker()->alt : QString())
|
||||
, _skipPremiumEffect(skipPremiumEffect)
|
||||
, _hasQualitiesList(hasQualitiesList)
|
||||
, _spoiler(spoiler)
|
||||
, _ttlSeconds(ttlSeconds) {
|
||||
, _videoTimestamp(args.videoTimestamp)
|
||||
, _skipPremiumEffect(args.skipPremiumEffect)
|
||||
, _hasQualitiesList(args.hasQualitiesList)
|
||||
, _spoiler(args.spoiler) {
|
||||
parent->history()->owner().registerDocumentItem(_document, parent);
|
||||
|
||||
if (!_emoji.isEmpty()) {
|
||||
|
@ -1002,19 +1010,28 @@ MediaFile::~MediaFile() {
|
|||
}
|
||||
|
||||
std::unique_ptr<Media> MediaFile::clone(not_null<HistoryItem*> parent) {
|
||||
return std::make_unique<MediaFile>(
|
||||
parent,
|
||||
_document,
|
||||
!_document->session().premium(),
|
||||
_hasQualitiesList,
|
||||
_spoiler,
|
||||
_ttlSeconds);
|
||||
return std::make_unique<MediaFile>(parent, _document, MediaFile::Args{
|
||||
.ttlSeconds = _ttlSeconds,
|
||||
.videoCover = _videoCover,
|
||||
.videoTimestamp = _videoTimestamp,
|
||||
.hasQualitiesList = _hasQualitiesList,
|
||||
.skipPremiumEffect = !_document->session().premium(),
|
||||
.spoiler = _spoiler,
|
||||
});
|
||||
}
|
||||
|
||||
DocumentData *MediaFile::document() const {
|
||||
return _document;
|
||||
}
|
||||
|
||||
PhotoData *MediaFile::videoCover() const {
|
||||
return _videoCover;
|
||||
}
|
||||
|
||||
TimeId MediaFile::videoTimestamp() const {
|
||||
return _videoTimestamp;
|
||||
}
|
||||
|
||||
bool MediaFile::hasQualitiesList() const {
|
||||
return _hasQualitiesList;
|
||||
}
|
||||
|
@ -1285,7 +1302,11 @@ bool MediaFile::updateSentMedia(const MTPMessageMedia &media) {
|
|||
"or with ttl_seconds in updateSentMedia()"));
|
||||
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;
|
||||
}
|
||||
|
||||
|
|
|
@ -46,6 +46,7 @@ enum class CallFinishReason : char {
|
|||
Busy,
|
||||
Disconnected,
|
||||
Hangup,
|
||||
AllowGroupCall,
|
||||
};
|
||||
|
||||
struct SharedContact final {
|
||||
|
@ -178,6 +179,8 @@ public:
|
|||
virtual std::unique_ptr<Media> clone(not_null<HistoryItem*> parent) = 0;
|
||||
|
||||
virtual DocumentData *document() const;
|
||||
virtual PhotoData *videoCover() const;
|
||||
virtual TimeId videoTimestamp() const;
|
||||
virtual bool hasQualitiesList() const;
|
||||
virtual PhotoData *photo() const;
|
||||
virtual WebPageData *webpage() const;
|
||||
|
@ -297,18 +300,26 @@ private:
|
|||
|
||||
class MediaFile final : public Media {
|
||||
public:
|
||||
struct Args {
|
||||
crl::time ttlSeconds = 0;
|
||||
PhotoData *videoCover = nullptr;
|
||||
TimeId videoTimestamp = 0;
|
||||
bool hasQualitiesList = false;
|
||||
bool skipPremiumEffect = false;
|
||||
bool spoiler = false;
|
||||
};
|
||||
|
||||
MediaFile(
|
||||
not_null<HistoryItem*> parent,
|
||||
not_null<DocumentData*> document,
|
||||
bool skipPremiumEffect,
|
||||
bool hasQualitiesList,
|
||||
bool spoiler,
|
||||
crl::time ttlSeconds);
|
||||
Args &&args);
|
||||
~MediaFile();
|
||||
|
||||
std::unique_ptr<Media> clone(not_null<HistoryItem*> parent) override;
|
||||
|
||||
DocumentData *document() const override;
|
||||
PhotoData *videoCover() const override;
|
||||
TimeId videoTimestamp() const override;
|
||||
bool hasQualitiesList() const override;
|
||||
|
||||
bool uploading() const override;
|
||||
|
@ -338,14 +349,17 @@ public:
|
|||
|
||||
private:
|
||||
not_null<DocumentData*> _document;
|
||||
QString _emoji;
|
||||
bool _skipPremiumEffect = false;
|
||||
bool _hasQualitiesList = false;
|
||||
bool _spoiler = false;
|
||||
PhotoData *_videoCover = nullptr;
|
||||
|
||||
// Video (unsupported) / Voice / Round.
|
||||
crl::time _ttlSeconds = 0;
|
||||
|
||||
QString _emoji;
|
||||
TimeId _videoTimestamp = 0;
|
||||
bool _skipPremiumEffect = false;
|
||||
bool _hasQualitiesList = false;
|
||||
bool _spoiler = false;
|
||||
|
||||
};
|
||||
|
||||
class MediaContact final : public Media {
|
||||
|
|
|
@ -17,6 +17,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
#include "main/main_app_config.h"
|
||||
#include "main/session/send_as_peers.h"
|
||||
#include "data/components/credits.h"
|
||||
#include "data/data_channel.h"
|
||||
#include "data/data_user.h"
|
||||
#include "data/data_session.h"
|
||||
#include "data/data_histories.h"
|
||||
|
@ -158,8 +159,23 @@ constexpr auto kPaidAccumulatePeriod = 5 * crl::time(1000) + 500;
|
|||
return (i != end(top)) && i->my;
|
||||
}
|
||||
|
||||
[[nodiscard]] std::optional<bool> MaybeAnonymous(uint32 privacySet, uint32 anonymous) {
|
||||
return privacySet ? (anonymous == 1) : std::optional<bool>();
|
||||
[[nodiscard]] std::optional<PeerId> MaybeShownPeer(
|
||||
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
|
||||
|
@ -180,6 +196,13 @@ PossibleItemReactionsRef LookupPossibleReactions(
|
|||
}
|
||||
}
|
||||
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 &full = reactions->list(Reactions::Type::Active);
|
||||
const auto &top = reactions->list(Reactions::Type::Top);
|
||||
|
@ -1752,7 +1775,7 @@ void Reactions::sendPaidPrivacyRequest(
|
|||
not_null<HistoryItem*> item,
|
||||
PaidReactionSend send) {
|
||||
Expects(!_sendingPaid.contains(item));
|
||||
Expects(send.anonymous.has_value());
|
||||
Expects(send.shownPeer.has_value());
|
||||
Expects(!send.count);
|
||||
|
||||
const auto id = item->fullId();
|
||||
|
@ -1761,7 +1784,7 @@ void Reactions::sendPaidPrivacyRequest(
|
|||
MTPmessages_TogglePaidReactionPrivacy(
|
||||
item->history()->peer->input,
|
||||
MTP_int(id.msg),
|
||||
MTP_bool(*send.anonymous))
|
||||
PaidReactionShownPeerToTL(&_owner->session(), send.shownPeer))
|
||||
).done([=] {
|
||||
if (const auto item = _owner->message(id)) {
|
||||
if (_sendingPaid.remove(item)) {
|
||||
|
@ -1795,12 +1818,14 @@ void Reactions::sendPaidRequest(
|
|||
auto &api = _owner->session().api();
|
||||
using Flag = MTPmessages_SendPaidReaction::Flag;
|
||||
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,
|
||||
MTP_int(id.msg),
|
||||
MTP_int(send.count),
|
||||
MTP_long(randomId),
|
||||
MTP_bool(send.anonymous.value_or(false))
|
||||
(!send.shownPeer
|
||||
? MTPPaidReactionPrivacy()
|
||||
: PaidReactionShownPeerToTL(&_owner->session(), *send.shownPeer))
|
||||
)).done([=](const MTPUpdates &result) {
|
||||
if (const auto item = _owner->message(id)) {
|
||||
if (_sendingPaid.remove(item)) {
|
||||
|
@ -1851,9 +1876,9 @@ MessageReactions::~MessageReactions() {
|
|||
finishPaidSending({
|
||||
.count = int(paid->sending),
|
||||
.valid = true,
|
||||
.anonymous = MaybeAnonymous(
|
||||
.shownPeer = MaybeShownPeer(
|
||||
paid->sendingPrivacySet,
|
||||
paid->sendingAnonymous),
|
||||
paid->sendingShownPeer),
|
||||
}, false);
|
||||
}
|
||||
}
|
||||
|
@ -2217,7 +2242,7 @@ void MessageReactions::markRead() {
|
|||
|
||||
void MessageReactions::scheduleSendPaid(
|
||||
int count,
|
||||
std::optional<bool> anonymous) {
|
||||
std::optional<PeerId> shownPeer) {
|
||||
Expects(count >= 0);
|
||||
|
||||
if (!_paid) {
|
||||
|
@ -2225,9 +2250,9 @@ void MessageReactions::scheduleSendPaid(
|
|||
}
|
||||
_paid->scheduled += count;
|
||||
_paid->scheduledFlag = 1;
|
||||
if (anonymous.has_value()) {
|
||||
_paid->scheduledAnonymous = anonymous.value_or(false) ? 1 : 0;
|
||||
_paid->scheduledPrivacySet = anonymous.has_value();
|
||||
if (shownPeer.has_value()) {
|
||||
_paid->scheduledShownPeer = *shownPeer;
|
||||
_paid->scheduledPrivacySet = true;
|
||||
}
|
||||
if (count > 0) {
|
||||
_item->history()->session().credits().lock(StarsAmount(count));
|
||||
|
@ -2248,7 +2273,7 @@ void MessageReactions::cancelScheduledPaid() {
|
|||
}
|
||||
_paid->scheduled = 0;
|
||||
_paid->scheduledFlag = 0;
|
||||
_paid->scheduledAnonymous = 0;
|
||||
_paid->scheduledShownPeer = 0;
|
||||
_paid->scheduledPrivacySet = 0;
|
||||
}
|
||||
if (!_paid->sendingFlag && _paid->top.empty()) {
|
||||
|
@ -2263,18 +2288,18 @@ PaidReactionSend MessageReactions::startPaidSending() {
|
|||
}
|
||||
_paid->sending = _paid->scheduled;
|
||||
_paid->sendingFlag = _paid->scheduledFlag;
|
||||
_paid->sendingAnonymous = _paid->scheduledAnonymous;
|
||||
_paid->sendingShownPeer = _paid->scheduledShownPeer;
|
||||
_paid->sendingPrivacySet = _paid->scheduledPrivacySet;
|
||||
_paid->scheduled = 0;
|
||||
_paid->scheduledFlag = 0;
|
||||
_paid->scheduledAnonymous = 0;
|
||||
_paid->scheduledShownPeer = 0;
|
||||
_paid->scheduledPrivacySet = 0;
|
||||
return {
|
||||
.count = int(_paid->sending),
|
||||
.valid = true,
|
||||
.anonymous = MaybeAnonymous(
|
||||
.shownPeer = MaybeShownPeer(
|
||||
_paid->sendingPrivacySet,
|
||||
_paid->sendingAnonymous),
|
||||
_paid->sendingShownPeer),
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -2284,13 +2309,13 @@ void MessageReactions::finishPaidSending(
|
|||
Expects(_paid != nullptr);
|
||||
Expects(send.count == _paid->sending);
|
||||
Expects(send.valid == (_paid->sendingFlag == 1));
|
||||
Expects(send.anonymous == MaybeAnonymous(
|
||||
Expects(send.shownPeer == MaybeShownPeer(
|
||||
_paid->sendingPrivacySet,
|
||||
_paid->sendingAnonymous));
|
||||
_paid->sendingShownPeer));
|
||||
|
||||
_paid->sending = 0;
|
||||
_paid->sendingFlag = 0;
|
||||
_paid->sendingAnonymous = 0;
|
||||
_paid->sendingShownPeer = 0;
|
||||
_paid->sendingPrivacySet = 0;
|
||||
if (!_paid->scheduledFlag && _paid->top.empty()) {
|
||||
_paid = nullptr;
|
||||
|
@ -2299,9 +2324,9 @@ void MessageReactions::finishPaidSending(
|
|||
return top.my;
|
||||
});
|
||||
if (i != end(_paid->top)) {
|
||||
i->peer = send.anonymous
|
||||
? nullptr
|
||||
: _item->history()->session().user().get();
|
||||
i->peer = send.shownPeer
|
||||
? _item->history()->owner().peer(*send.shownPeer).get()
|
||||
: nullptr;
|
||||
}
|
||||
}
|
||||
if (const auto amount = send.count) {
|
||||
|
@ -2322,22 +2347,23 @@ int MessageReactions::localPaidCount() const {
|
|||
return _paid ? (_paid->scheduled + _paid->sending) : 0;
|
||||
}
|
||||
|
||||
bool MessageReactions::localPaidAnonymous() const {
|
||||
const auto minePaidAnonymous = [&] {
|
||||
PeerId MessageReactions::localPaidShownPeer() const {
|
||||
const auto minePaidShownPeer = [&] {
|
||||
for (const auto &entry : _paid->top) {
|
||||
if (entry.my) {
|
||||
return !entry.peer;
|
||||
return entry.peer ? entry.peer->id : PeerId();
|
||||
}
|
||||
}
|
||||
const auto api = &_item->history()->session().api();
|
||||
return api->globalPrivacy().paidReactionAnonymousCurrent();
|
||||
return api->globalPrivacy().paidReactionShownPeerCurrent();
|
||||
};
|
||||
return _paid
|
||||
&& ((_paid->scheduledFlag && _paid->scheduledPrivacySet)
|
||||
? (_paid->scheduledAnonymous == 1)
|
||||
: (_paid->sendingFlag && _paid->sendingPrivacySet)
|
||||
? (_paid->sendingAnonymous == 1)
|
||||
: minePaidAnonymous());
|
||||
return !_paid
|
||||
? PeerId()
|
||||
: (_paid->scheduledFlag && _paid->scheduledPrivacySet)
|
||||
? _paid->scheduledShownPeer
|
||||
: (_paid->sendingFlag && _paid->sendingPrivacySet)
|
||||
? _paid->sendingShownPeer
|
||||
: minePaidShownPeer();
|
||||
}
|
||||
|
||||
bool MessageReactions::clearCloudData() {
|
||||
|
|
|
@ -71,7 +71,7 @@ struct MyTagInfo {
|
|||
struct PaidReactionSend {
|
||||
int count = 0;
|
||||
bool valid = false;
|
||||
std::optional<bool> anonymous = false;
|
||||
std::optional<PeerId> shownPeer = PeerId();
|
||||
};
|
||||
|
||||
class Reactions final : private CustomEmojiManager::Listener {
|
||||
|
@ -409,7 +409,7 @@ public:
|
|||
[[nodiscard]] bool hasUnread() const;
|
||||
void markRead();
|
||||
|
||||
void scheduleSendPaid(int count, std::optional<bool> anonymous);
|
||||
void scheduleSendPaid(int count, std::optional<PeerId> shownPeer);
|
||||
[[nodiscard]] int scheduledPaid() const;
|
||||
void cancelScheduledPaid();
|
||||
|
||||
|
@ -418,19 +418,19 @@ public:
|
|||
|
||||
[[nodiscard]] bool localPaidData() const;
|
||||
[[nodiscard]] int localPaidCount() const;
|
||||
[[nodiscard]] bool localPaidAnonymous() const;
|
||||
[[nodiscard]] PeerId localPaidShownPeer() const;
|
||||
bool clearCloudData();
|
||||
|
||||
private:
|
||||
struct Paid {
|
||||
std::vector<TopPaid> top;
|
||||
uint32 scheduled: 29 = 0;
|
||||
PeerId scheduledShownPeer = 0;
|
||||
PeerId sendingShownPeer = 0;
|
||||
uint32 scheduled: 30 = 0;
|
||||
uint32 scheduledFlag : 1 = 0;
|
||||
uint32 scheduledAnonymous : 1 = 0;
|
||||
uint32 scheduledPrivacySet : 1 = 0;
|
||||
uint32 sending : 29 = 0;
|
||||
uint32 sending : 30 = 0;
|
||||
uint32 sendingFlag : 1 = 0;
|
||||
uint32 sendingAnonymous : 1 = 0;
|
||||
uint32 sendingPrivacySet : 1 = 0;
|
||||
};
|
||||
const not_null<HistoryItem*> _item;
|
||||
|
|
|
@ -3524,6 +3524,7 @@ not_null<WebPageData*> Session::processWebpage(
|
|||
0,
|
||||
QString(),
|
||||
false,
|
||||
false,
|
||||
data.vdate().v
|
||||
? data.vdate().v
|
||||
: (base::unixtime::now() + kDefaultPendingTimeout));
|
||||
|
@ -3551,6 +3552,7 @@ not_null<WebPageData*> Session::webpage(
|
|||
0,
|
||||
QString(),
|
||||
false,
|
||||
false,
|
||||
TimeId(0));
|
||||
}
|
||||
|
||||
|
@ -3571,6 +3573,7 @@ not_null<WebPageData*> Session::webpage(
|
|||
int duration,
|
||||
const QString &author,
|
||||
bool hasLargeMedia,
|
||||
bool photoIsVideoCover,
|
||||
TimeId pendingTill) {
|
||||
const auto result = webpage(id);
|
||||
webpageApplyFields(
|
||||
|
@ -3591,6 +3594,7 @@ not_null<WebPageData*> Session::webpage(
|
|||
duration,
|
||||
author,
|
||||
hasLargeMedia,
|
||||
photoIsVideoCover,
|
||||
pendingTill);
|
||||
return result;
|
||||
}
|
||||
|
@ -3778,6 +3782,7 @@ void Session::webpageApplyFields(
|
|||
data.vduration().value_or_empty(),
|
||||
qs(data.vauthor().value_or_empty()),
|
||||
data.is_has_large_media(),
|
||||
data.is_video_cover_photo(),
|
||||
pendingTill);
|
||||
}
|
||||
|
||||
|
@ -3799,6 +3804,7 @@ void Session::webpageApplyFields(
|
|||
int duration,
|
||||
const QString &author,
|
||||
bool hasLargeMedia,
|
||||
bool photoIsVideoCover,
|
||||
TimeId pendingTill) {
|
||||
const auto requestPending = (!page->pendingTill && pendingTill > 0);
|
||||
const auto changed = page->applyChanges(
|
||||
|
@ -3818,6 +3824,7 @@ void Session::webpageApplyFields(
|
|||
duration,
|
||||
author,
|
||||
hasLargeMedia,
|
||||
photoIsVideoCover,
|
||||
pendingTill);
|
||||
if (requestPending) {
|
||||
_session->api().requestWebPageDelayed(page);
|
||||
|
|
|
@ -631,6 +631,7 @@ public:
|
|||
int duration,
|
||||
const QString &author,
|
||||
bool hasLargeMedia,
|
||||
bool photoIsVideoCover,
|
||||
TimeId pendingTill);
|
||||
|
||||
[[nodiscard]] not_null<GameData*> game(GameId id);
|
||||
|
@ -916,6 +917,7 @@ private:
|
|||
int duration,
|
||||
const QString &author,
|
||||
bool hasLargeMedia,
|
||||
bool photoIsVideoCover,
|
||||
TimeId pendingTill);
|
||||
|
||||
void gameApplyFields(
|
||||
|
|
|
@ -316,7 +316,7 @@ void Stories::scheduleExpireTimer() {
|
|||
const auto nearest = _expiring.front().first;
|
||||
const auto now = base::unixtime::now();
|
||||
const auto delay = (nearest > now)
|
||||
? (nearest - now)
|
||||
? std::min(nearest - now, 86'400)
|
||||
: 0;
|
||||
_expireTimer.callOnce(delay * crl::time(1000));
|
||||
}
|
||||
|
|
|
@ -14,6 +14,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
#include "data/data_photo.h"
|
||||
#include "data/data_channel.h"
|
||||
#include "data/data_document.h"
|
||||
#include "core/local_url_handlers.h"
|
||||
#include "lang/lang_keys.h"
|
||||
#include "iv/iv_data.h"
|
||||
#include "ui/image/image.h"
|
||||
|
@ -228,6 +229,7 @@ bool WebPageData::applyChanges(
|
|||
int newDuration,
|
||||
const QString &newAuthor,
|
||||
bool newHasLargeMedia,
|
||||
bool newPhotoIsVideoCover,
|
||||
int newPendingTill) {
|
||||
if (newPendingTill != 0
|
||||
&& (!url.isEmpty() || failed)
|
||||
|
@ -265,6 +267,9 @@ bool WebPageData::applyChanges(
|
|||
|| (hasSiteName + hasTitle + hasDescription < 2)) {
|
||||
newHasLargeMedia = false;
|
||||
}
|
||||
if (!newDocument || !newDocument->isVideoFile() || !newPhoto) {
|
||||
newPhotoIsVideoCover = false;
|
||||
}
|
||||
|
||||
if (type == newType
|
||||
&& url == resultUrl
|
||||
|
@ -283,6 +288,7 @@ bool WebPageData::applyChanges(
|
|||
&& duration == newDuration
|
||||
&& author == resultAuthor
|
||||
&& hasLargeMedia == (newHasLargeMedia ? 1 : 0)
|
||||
&& photoIsVideoCover == (newPhotoIsVideoCover ? 1 : 0)
|
||||
&& pendingTill == newPendingTill) {
|
||||
return false;
|
||||
}
|
||||
|
@ -291,6 +297,7 @@ bool WebPageData::applyChanges(
|
|||
}
|
||||
type = newType;
|
||||
hasLargeMedia = newHasLargeMedia ? 1 : 0;
|
||||
photoIsVideoCover = newPhotoIsVideoCover ? 1 : 0;
|
||||
url = resultUrl;
|
||||
displayUrl = resultDisplayUrl;
|
||||
siteName = resultSiteName;
|
||||
|
@ -374,6 +381,21 @@ QString WebPageData::displayedSiteName() const {
|
|||
: 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 {
|
||||
if (!collage.items.empty()) {
|
||||
return false;
|
||||
|
@ -382,7 +404,8 @@ bool WebPageData::computeDefaultSmallMedia() const {
|
|||
&& description.empty()
|
||||
&& author.isEmpty()) {
|
||||
return false;
|
||||
} else if (!document
|
||||
} else if (!uniqueGift
|
||||
&& !document
|
||||
&& photo
|
||||
&& type != WebPageType::Photo
|
||||
&& type != WebPageType::Document
|
||||
|
|
|
@ -106,6 +106,7 @@ struct WebPageData {
|
|||
int newDuration,
|
||||
const QString &newAuthor,
|
||||
bool newHasLargeMedia,
|
||||
bool newPhotoIsVideoCover,
|
||||
int newPendingTill);
|
||||
|
||||
static void ApplyChanges(
|
||||
|
@ -114,6 +115,7 @@ struct WebPageData {
|
|||
const MTPmessages_Messages &result);
|
||||
|
||||
[[nodiscard]] QString displayedSiteName() const;
|
||||
[[nodiscard]] TimeId extractVideoTimestamp() const;
|
||||
[[nodiscard]] bool computeDefaultSmallMedia() const;
|
||||
[[nodiscard]] bool suggestEnlargePhoto() const;
|
||||
|
||||
|
@ -134,7 +136,8 @@ struct WebPageData {
|
|||
std::shared_ptr<Data::UniqueGift> uniqueGift;
|
||||
int duration = 0;
|
||||
TimeId pendingTill = 0;
|
||||
uint32 version : 30 = 0;
|
||||
uint32 version : 29 = 0;
|
||||
uint32 photoIsVideoCover : 1 = 0;
|
||||
uint32 hasLargeMedia : 1 = 0;
|
||||
uint32 failed : 1 = 0;
|
||||
|
||||
|
|
|
@ -285,6 +285,7 @@ InnerWidget::InnerWidget(
|
|||
) | rpl::start_with_next([=] {
|
||||
_topicJumpCache = nullptr;
|
||||
_chatsFilterTags.clear();
|
||||
_rightButtons.clear();
|
||||
}, lifetime());
|
||||
|
||||
session().downloaderTaskFinished(
|
||||
|
@ -2090,6 +2091,7 @@ bool InnerWidget::updateReorderPinned(QPoint localPosition) {
|
|||
const auto draggingHeight = _dragging->height();
|
||||
auto yaddWas = _pinnedRows[_draggingIndex].yadd.current();
|
||||
auto shift = 0;
|
||||
auto shiftHeight = 0;
|
||||
auto now = crl::now();
|
||||
if (_dragStart.y() > localPosition.y() && _draggingIndex > 0) {
|
||||
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]);
|
||||
_pinnedRows[from].yadd = anim::value(_pinnedRows[from].yadd.current() - draggingHeight, 0);
|
||||
_pinnedRows[from].animStartTime = now;
|
||||
shiftHeight -= (*(_shownList->cbegin() + from))->height();
|
||||
}
|
||||
} else if (_dragStart.y() < localPosition.y() && _draggingIndex + 1 < pinnedCount) {
|
||||
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]);
|
||||
_pinnedRows[from].yadd = anim::value(_pinnedRows[from].yadd.current() + draggingHeight, 0);
|
||||
_pinnedRows[from].animStartTime = now;
|
||||
shiftHeight += (*(_shownList->cbegin() + from))->height();
|
||||
}
|
||||
}
|
||||
if (shift) {
|
||||
_draggingIndex += shift;
|
||||
_aboveIndex = _draggingIndex;
|
||||
_dragStart.setY(_dragStart.y() + shift * _st->height);
|
||||
_dragStart.setY(_dragStart.y() + shiftHeight);
|
||||
if (!_pinnedShiftAnimation.animating()) {
|
||||
_pinnedShiftAnimation.start();
|
||||
}
|
||||
}
|
||||
_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) {
|
||||
_pinnedRows[_draggingIndex].yadd.finish();
|
||||
}
|
||||
|
@ -2170,7 +2176,7 @@ bool InnerWidget::pinnedShiftAnimationCallback(crl::time now) {
|
|||
}
|
||||
if (updateMin >= 0) {
|
||||
const auto minHeight = _st->height;
|
||||
const auto maxHeight = st::forumDialogRow.height;
|
||||
const auto maxHeight = st::taggedForumDialogRow.height;
|
||||
auto top = pinnedOffset();
|
||||
auto updateFrom = top + minHeight * (updateMin - 1);
|
||||
auto updateHeight = maxHeight * (updateMax - updateMin + 3);
|
||||
|
|
|
@ -470,7 +470,13 @@ void Row::PaintCornerBadgeFrame(
|
|||
for (auto i = 0; i != storiesUnreadCount; ++i) {
|
||||
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) {
|
||||
|
|
|
@ -1215,6 +1215,7 @@ void Widget::setupShortcuts() {
|
|||
});
|
||||
request->check(Command::ShowChatMenu, 1) && request->handle([=] {
|
||||
if (_inner) {
|
||||
Window::ActivateWindow(controller());
|
||||
_inner->showPeerMenu();
|
||||
}
|
||||
return true;
|
||||
|
|
|
@ -442,6 +442,9 @@ void PaintRow(
|
|||
|
||||
const auto promoted = (history && history->useTopPromotion())
|
||||
&& !context.search;
|
||||
const auto verifyInfo = (from && !from->isSelf())
|
||||
? from->botVerifyDetails()
|
||||
: nullptr;
|
||||
if (promoted) {
|
||||
const auto type = history->topPromotionType();
|
||||
const auto custom = type.isEmpty()
|
||||
|
@ -453,10 +456,10 @@ void PaintRow(
|
|||
? tr::lng_badge_psa_default(tr::now)
|
||||
: custom;
|
||||
PaintRowTopRight(p, text, rectForName, context);
|
||||
} else if (const auto info = from ? from->botVerifyDetails() : nullptr) {
|
||||
if (!rowBadge.ready(info)) {
|
||||
} else if (verifyInfo) {
|
||||
if (!rowBadge.ready(verifyInfo)) {
|
||||
rowBadge.set(
|
||||
info,
|
||||
verifyInfo,
|
||||
from->owner().customEmojiManager().factory(),
|
||||
customEmojiRepaint);
|
||||
}
|
||||
|
|
|
@ -42,6 +42,15 @@ QSizeF FlipSizeByRotation(const QSizeF &size, int angle) {
|
|||
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
|
||||
|
||||
Crop::Crop(
|
||||
|
@ -60,6 +69,8 @@ Crop::Crop(
|
|||
, _data(std::move(data))
|
||||
, _cropOriginal(modifications.crop.isValid()
|
||||
? modifications.crop
|
||||
: !_data.exactSize.isEmpty()
|
||||
? OriginalCrop(_imageSize, _data.exactSize)
|
||||
: QRectF(QPoint(), _imageSize))
|
||||
, _angle(modifications.angle)
|
||||
, _flipped(modifications.flipped)
|
||||
|
|
|
@ -32,6 +32,7 @@ struct EditorData {
|
|||
|
||||
TextWithEntities about;
|
||||
QString confirm;
|
||||
QSize exactSize;
|
||||
CropType cropType = CropType::Rect;
|
||||
bool keepAspectRatio = false;
|
||||
};
|
||||
|
|
|
@ -26,22 +26,26 @@ void OpenWithPreparedFile(
|
|||
std::shared_ptr<ChatHelpers::Show> show,
|
||||
not_null<Ui::PreparedFile*> file,
|
||||
int previewWidth,
|
||||
Fn<void()> &&doneCallback) {
|
||||
Fn<void(bool ok)> &&doneCallback,
|
||||
QSize exactSize) {
|
||||
using ImageInfo = Ui::PreparedFileInformation::Image;
|
||||
const auto image = std::get_if<ImageInfo>(&file->information->media);
|
||||
if (!image) {
|
||||
doneCallback(false);
|
||||
return;
|
||||
}
|
||||
const auto photoType = (file->type == Ui::PreparedFile::Type::Photo);
|
||||
const auto modifiedFileType = (file->type == Ui::PreparedFile::Type::File)
|
||||
&& !image->modifications.empty();
|
||||
if (!photoType && !modifiedFileType) {
|
||||
doneCallback(false);
|
||||
return;
|
||||
}
|
||||
|
||||
const auto sideLimit = PhotoSideLimit();
|
||||
auto callback = [=, done = std::move(doneCallback)](
|
||||
const PhotoModifications &mods) {
|
||||
const auto accepted = std::make_shared<bool>();
|
||||
auto callback = [=](const PhotoModifications &mods) {
|
||||
*accepted = true;
|
||||
image->modifications = mods;
|
||||
Storage::UpdateImageDetails(*file, previewWidth, sideLimit);
|
||||
{
|
||||
|
@ -51,19 +55,26 @@ void OpenWithPreparedFile(
|
|||
? PreparedFile::Type::Photo
|
||||
: PreparedFile::Type::File;
|
||||
}
|
||||
done();
|
||||
doneCallback(true);
|
||||
};
|
||||
auto copy = image->data;
|
||||
const auto fileImage = std::make_shared<Image>(std::move(copy));
|
||||
const auto keepRatio = !exactSize.isEmpty();
|
||||
auto editor = base::make_unique_q<PhotoEditor>(
|
||||
parent,
|
||||
show,
|
||||
show,
|
||||
fileImage,
|
||||
image->modifications);
|
||||
image->modifications,
|
||||
EditorData{ .exactSize = exactSize, .keepAspectRatio = keepRatio });
|
||||
const auto raw = editor.get();
|
||||
auto layer = std::make_unique<LayerWidget>(parent, std::move(editor));
|
||||
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);
|
||||
}
|
||||
|
||||
|
|
|
@ -34,7 +34,8 @@ void OpenWithPreparedFile(
|
|||
std::shared_ptr<ChatHelpers::Show> show,
|
||||
not_null<Ui::PreparedFile*> file,
|
||||
int previewWidth,
|
||||
Fn<void()> &&doneCallback);
|
||||
Fn<void(bool ok)> &&doneCallback,
|
||||
QSize exactSize = {});
|
||||
|
||||
void PrepareProfilePhoto(
|
||||
not_null<QWidget*> parent,
|
||||
|
|
|
@ -385,6 +385,7 @@ QByteArray SerializeMessage(
|
|||
};
|
||||
const auto pushPhoto = [&](const Image &image) {
|
||||
pushPath(image.file, "photo");
|
||||
push("photo_file_size", image.file.size);
|
||||
if (image.width && image.height) {
|
||||
push("width", image.width);
|
||||
push("height", image.height);
|
||||
|
@ -696,8 +697,10 @@ QByteArray SerializeMessage(
|
|||
}, [&](const Document &data) {
|
||||
pushPath(data.file, "file");
|
||||
push("file_name", data.name);
|
||||
push("file_size", data.file.size);
|
||||
if (data.thumb.width > 0) {
|
||||
pushPath(data.thumb.file, "thumbnail");
|
||||
push("thumbnail_file_size", data.thumb.file.size);
|
||||
}
|
||||
const auto pushType = [&](const QByteArray &value) {
|
||||
push("media_type", value);
|
||||
|
@ -739,6 +742,7 @@ QByteArray SerializeMessage(
|
|||
}));
|
||||
if (!data.vcard.content.isEmpty()) {
|
||||
pushPath(data.vcard, "contact_vcard");
|
||||
push("contact_vcard_file_size", data.vcard.size);
|
||||
}
|
||||
}, [&](const GeoPoint &data) {
|
||||
pushBare(
|
||||
|
|
|
@ -55,6 +55,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
#include "boxes/about_sponsored_box.h"
|
||||
#include "boxes/delete_messages_box.h"
|
||||
#include "boxes/report_messages_box.h"
|
||||
#include "boxes/star_gift_box.h" // ShowStarGiftBox
|
||||
#include "boxes/sticker_set_box.h"
|
||||
#include "boxes/translate_box.h"
|
||||
#include "chat_helpers/message_field.h"
|
||||
|
@ -1627,6 +1628,7 @@ void HistoryInner::mouseMoveEvent(QMouseEvent *e) {
|
|||
mouseReleaseEvent(e);
|
||||
}
|
||||
if (reallyMoved) {
|
||||
_mouseActive = true;
|
||||
lastGlobalPosition = e->globalPos();
|
||||
if (!buttonsPressed || (_scrollDateLink && ClickHandler::getPressed() == _scrollDateLink)) {
|
||||
keepScrollDateForNow();
|
||||
|
@ -1673,6 +1675,7 @@ void HistoryInner::mousePressEvent(QMouseEvent *e) {
|
|||
e->accept();
|
||||
return; // ignore mouse press, that was hiding context menu
|
||||
}
|
||||
_mouseActive = true;
|
||||
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), [=] {
|
||||
QGuiApplication::clipboard()->setText(phone);
|
||||
}, &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()) {
|
||||
|
@ -3314,7 +3340,7 @@ void HistoryInner::checkActivation() {
|
|||
session().data().histories().readInboxTill(view->data());
|
||||
}
|
||||
|
||||
void HistoryInner::recountHistoryGeometry() {
|
||||
void HistoryInner::recountHistoryGeometry(bool initial) {
|
||||
_contentWidth = _scroll->width();
|
||||
|
||||
if (_history->hasPendingResizedItems()
|
||||
|
@ -3372,7 +3398,7 @@ void HistoryInner::recountHistoryGeometry() {
|
|||
}
|
||||
|
||||
auto historyPaddingTopDelta = (newHistoryPaddingTop - oldHistoryPaddingTop);
|
||||
if (historyPaddingTopDelta != 0) {
|
||||
if (!initial && historyPaddingTopDelta != 0) {
|
||||
if (_history->scrollTopItem) {
|
||||
_history->scrollTopOffset += historyPaddingTopDelta;
|
||||
} else if (_migrated && _migrated->scrollTopItem) {
|
||||
|
@ -3567,6 +3593,7 @@ void HistoryInner::setShownPinned(HistoryItem *item) {
|
|||
}
|
||||
|
||||
void HistoryInner::enterEventHook(QEnterEvent *e) {
|
||||
_mouseActive = true;
|
||||
mouseActionUpdate(QCursor::pos());
|
||||
return TWidget::enterEventHook(e);
|
||||
}
|
||||
|
@ -3583,6 +3610,7 @@ void HistoryInner::leaveEventHook(QEvent *e) {
|
|||
_cursor = style::cur_default;
|
||||
setCursor(_cursor);
|
||||
}
|
||||
_mouseActive = false;
|
||||
return TWidget::leaveEventHook(e);
|
||||
}
|
||||
|
||||
|
@ -3933,7 +3961,7 @@ auto HistoryInner::reactionButtonParameters(
|
|||
}
|
||||
|
||||
void HistoryInner::mouseActionUpdate() {
|
||||
if (hasPendingResizedItems()) {
|
||||
if (hasPendingResizedItems() || !_mouseActive) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
@ -121,7 +121,7 @@ public:
|
|||
void setItemsRevealHeight(int revealHeight);
|
||||
void changeItemsRevealHeight(int revealHeight);
|
||||
void checkActivation();
|
||||
void recountHistoryGeometry();
|
||||
void recountHistoryGeometry(bool initial = false);
|
||||
void updateSize();
|
||||
void setShownPinned(HistoryItem *item);
|
||||
|
||||
|
@ -499,6 +499,7 @@ private:
|
|||
HistoryItem *_dragStateItem = nullptr;
|
||||
CursorState _mouseCursorState = CursorState();
|
||||
uint16 _mouseTextSymbol = 0;
|
||||
bool _mouseActive = false;
|
||||
bool _dragStateUserpic = false;
|
||||
bool _pressWasInactive = false;
|
||||
bool _recountedAfterPendingResizedItems = false;
|
||||
|
|
|
@ -310,13 +310,19 @@ std::unique_ptr<Data::Media> HistoryItem::CreateMedia(
|
|||
}
|
||||
return document->match([&](const MTPDdocument &document) -> Result {
|
||||
const auto list = media.valt_documents();
|
||||
return std::make_unique<Data::MediaFile>(
|
||||
item,
|
||||
item->history()->owner().processDocument(document, list),
|
||||
media.is_nopremium(),
|
||||
list && !list->v.isEmpty(),
|
||||
media.is_spoiler(),
|
||||
media.vttl_seconds().value_or_empty());
|
||||
const auto owner = &item->history()->owner();
|
||||
const auto data = owner->processDocument(document);
|
||||
using Args = Data::MediaFile::Args;
|
||||
return std::make_unique<Data::MediaFile>(item, data, Args{
|
||||
.ttlSeconds = media.vttl_seconds().value_or_empty(),
|
||||
.videoCover = (media.vvideo_cover()
|
||||
? 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 {
|
||||
return nullptr;
|
||||
});
|
||||
|
@ -693,16 +699,12 @@ HistoryItem::HistoryItem(
|
|||
: HistoryItem(history, fields) {
|
||||
createComponentsHelper(std::move(fields));
|
||||
|
||||
const auto skipPremiumEffect = !history->session().premium();
|
||||
const auto video = document->video();
|
||||
const auto spoiler = false;
|
||||
_media = std::make_unique<Data::MediaFile>(
|
||||
this,
|
||||
document,
|
||||
skipPremiumEffect,
|
||||
video && !video->qualities.empty(),
|
||||
spoiler,
|
||||
/*ttlSeconds = */0);
|
||||
using Args = Data::MediaFile::Args;
|
||||
_media = std::make_unique<Data::MediaFile>(this, document, Args{
|
||||
.hasQualitiesList = video && !video->qualities.empty(),
|
||||
.skipPremiumEffect = !history->session().premium(),
|
||||
});
|
||||
setText(caption);
|
||||
}
|
||||
|
||||
|
@ -764,6 +766,7 @@ HistoryItem::HistoryItem(
|
|||
0,
|
||||
QString(),
|
||||
false,
|
||||
false,
|
||||
0);
|
||||
auto webpageMedia = std::make_unique<Data::MediaWebPage>(
|
||||
this,
|
||||
|
@ -1903,17 +1906,12 @@ void HistoryItem::applyChanges(not_null<Data::Story*> story) {
|
|||
}
|
||||
|
||||
void HistoryItem::setStoryFields(not_null<Data::Story*> story) {
|
||||
const auto spoiler = false;
|
||||
if (const auto photo = story->photo()) {
|
||||
const auto spoiler = false;
|
||||
_media = std::make_unique<Data::MediaPhoto>(this, photo, spoiler);
|
||||
} else if (const auto document = story->document()) {
|
||||
_media = std::make_unique<Data::MediaFile>(
|
||||
this,
|
||||
document,
|
||||
/*skipPremiumEffect=*/false,
|
||||
/*hasQualitiesList=*/false,
|
||||
spoiler,
|
||||
/*ttlSeconds = */0);
|
||||
using Args = Data::MediaFile::Args;
|
||||
_media = std::make_unique<Data::MediaFile>(this, document, Args{});
|
||||
}
|
||||
setText(story->caption());
|
||||
if (story->pinnedToTop()) {
|
||||
|
@ -2693,14 +2691,16 @@ bool HistoryItem::canReact() const {
|
|||
return true;
|
||||
}
|
||||
|
||||
void HistoryItem::addPaidReaction(int count, std::optional<bool> anonymous) {
|
||||
void HistoryItem::addPaidReaction(
|
||||
int count,
|
||||
std::optional<PeerId> shownPeer) {
|
||||
Expects(count >= 0);
|
||||
Expects(_history->peer->isBroadcast() || isDiscussionPost());
|
||||
|
||||
if (!_reactions) {
|
||||
_reactions = std::make_unique<Data::MessageReactions>(this);
|
||||
}
|
||||
_reactions->scheduleSendPaid(count, anonymous);
|
||||
_reactions->scheduleSendPaid(count, shownPeer);
|
||||
if (count > 0) {
|
||||
_history->owner().notifyItemDataChange(this);
|
||||
}
|
||||
|
@ -2796,8 +2796,10 @@ int HistoryItem::reactionsPaidScheduled() const {
|
|||
return _reactions ? _reactions->scheduledPaid() : 0;
|
||||
}
|
||||
|
||||
bool HistoryItem::reactionsLocalAnonymous() const {
|
||||
return _reactions ? _reactions->localPaidAnonymous() : false;
|
||||
PeerId HistoryItem::reactionsLocalShownPeer() const {
|
||||
return _reactions
|
||||
? _reactions->localPaidShownPeer()
|
||||
: _history->session().userPeerId();
|
||||
}
|
||||
|
||||
bool HistoryItem::reactionsAreTags() const {
|
||||
|
@ -2825,9 +2827,8 @@ auto HistoryItem::topPaidReactionsWithLocal() const
|
|||
result,
|
||||
[](const TopPaid &entry) { return entry.my != 0; });
|
||||
const auto peerForMine = [&] {
|
||||
return _reactions->localPaidAnonymous()
|
||||
? nullptr
|
||||
: history()->session().user().get();
|
||||
const auto peerId = _reactions->localPaidShownPeer();
|
||||
return peerId ? history()->owner().peer(peerId).get() : nullptr;
|
||||
};
|
||||
if (const auto local = _reactions->localPaidCount()) {
|
||||
const auto top = [&](int mine) {
|
||||
|
|
|
@ -458,7 +458,7 @@ public:
|
|||
void toggleReaction(
|
||||
const Data::ReactionId &reaction,
|
||||
HistoryReactionSource source);
|
||||
void addPaidReaction(int count, std::optional<bool> anonymous = {});
|
||||
void addPaidReaction(int count, std::optional<PeerId> shownPeer = {});
|
||||
void cancelScheduledPaidReaction();
|
||||
[[nodiscard]] Data::PaidReactionSend startPaidReactionSending();
|
||||
void finishPaidReactionSending(
|
||||
|
@ -476,7 +476,7 @@ public:
|
|||
[[nodiscard]] auto topPaidReactionsWithLocal() const
|
||||
-> std::vector<Data::MessageReactionsTopPaid>;
|
||||
[[nodiscard]] int reactionsPaidScheduled() const;
|
||||
[[nodiscard]] bool reactionsLocalAnonymous() const;
|
||||
[[nodiscard]] PeerId reactionsLocalShownPeer() const;
|
||||
[[nodiscard]] bool canViewReactions() const;
|
||||
[[nodiscard]] std::vector<Data::ReactionId> chosenReactions() const;
|
||||
[[nodiscard]] Data::ReactionId lookupUnreadReaction(
|
||||
|
|
|
@ -9,6 +9,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
|
||||
#include "api/api_text_entities.h"
|
||||
#include "base/qt/qt_key_modifiers.h"
|
||||
#include "base/options.h"
|
||||
#include "lang/lang_keys.h"
|
||||
#include "ui/effects/ripple_animation.h"
|
||||
#include "ui/effects/spoiler_mess.h"
|
||||
|
@ -67,8 +68,20 @@ namespace {
|
|||
|
||||
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
|
||||
|
||||
const char kOptionFastButtonsMode[] = "fast-buttons-mode";
|
||||
|
||||
bool FastButtonsMode() {
|
||||
return FastButtonsModeOption.value();
|
||||
}
|
||||
|
||||
void HistoryMessageVia::create(
|
||||
not_null<Data::Session*> owner,
|
||||
UserId userId) {
|
||||
|
@ -946,10 +959,10 @@ void ReplyKeyboard::paint(
|
|||
}
|
||||
|
||||
bool ReplyKeyboard::hasFastButtonMode() const {
|
||||
return _item->inlineReplyKeyboard()
|
||||
return FastButtonsMode()
|
||||
&& _item->inlineReplyKeyboard()
|
||||
&& (_item == _item->history()->lastMessage())
|
||||
&& _item->history()->session().supportMode()
|
||||
&& _item->history()->session().supportHelper().fastButtonMode(
|
||||
&& _item->history()->session().fastButtonsBots().enabled(
|
||||
_item->history()->peer);
|
||||
}
|
||||
|
||||
|
|
|
@ -54,6 +54,9 @@ namespace style {
|
|||
struct BotKeyboardButton;
|
||||
} // namespace style
|
||||
|
||||
extern const char kOptionFastButtonsMode[];
|
||||
[[nodiscard]] bool FastButtonsMode();
|
||||
|
||||
struct HistoryMessageVia : public RuntimeComponent<HistoryMessageVia, HistoryItem> {
|
||||
void create(not_null<Data::Session*> owner, UserId userId);
|
||||
void resize(int32 availw) const;
|
||||
|
|
|
@ -22,6 +22,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
#include "boxes/moderate_messages_box.h"
|
||||
#include "boxes/premium_limits_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_requests_box.h"
|
||||
#include "core/file_utilities.h"
|
||||
|
@ -409,6 +410,7 @@ HistoryWidget::HistoryWidget(
|
|||
_joinChannel->addClickHandler([=] { joinChannel(); });
|
||||
_muteUnmute->addClickHandler([=] { toggleMuteUnmute(); });
|
||||
_discuss->addClickHandler([=] { goToDiscussionGroup(); });
|
||||
setupGiftToChannelButton();
|
||||
_reportMessages->addClickHandler([=] { reportSelectedMessages(); });
|
||||
_field->submits(
|
||||
) | 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() {
|
||||
const auto state = computeDialogsEntryState();
|
||||
_topBar->setActiveChat(state, _history->sendActionPainter());
|
||||
|
@ -2090,6 +2104,7 @@ void HistoryWidget::setupShortcuts() {
|
|||
return true;
|
||||
});
|
||||
request->check(Command::ShowChatMenu, 1) && request->handle([=] {
|
||||
Window::ActivateWindow(controller());
|
||||
_topBar->showPeerMenu();
|
||||
return true;
|
||||
});
|
||||
|
@ -2112,6 +2127,25 @@ void HistoryWidget::setupShortcuts() {
|
|||
}, 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) {
|
||||
if (item->history() != _history && item->history() != _migrated) {
|
||||
return;
|
||||
|
@ -2270,15 +2304,12 @@ bool HistoryWidget::insideJumpToEndInsteadOfToUnread() const {
|
|||
}
|
||||
|
||||
void HistoryWidget::showHistory(
|
||||
const PeerId &peerId,
|
||||
PeerId peerId,
|
||||
MsgId showAtMsgId,
|
||||
const TextWithEntities &highlightPart,
|
||||
int highlightPartOffsetHint) {
|
||||
|
||||
const Window::SectionShow ¶ms) {
|
||||
_pinnedClickedId = FullMsgId();
|
||||
_minPinnedId = std::nullopt;
|
||||
_showAtMsgHighlightPart = {};
|
||||
_showAtMsgHighlightPartOffsetHint = 0;
|
||||
_showAtMsgParams = {};
|
||||
|
||||
const auto wasState = controller()->dialogsEntryStateCurrent();
|
||||
const auto startBot = (showAtMsgId == ShowAndStartBotMsgId);
|
||||
|
@ -2328,16 +2359,10 @@ void HistoryWidget::showHistory(
|
|||
).arg(_history->inboxReadTillId().bare
|
||||
).arg(Logs::b(_history->loadedAtBottom())
|
||||
).arg(showAtMsgId.bare));
|
||||
delayedShowAt(
|
||||
showAtMsgId,
|
||||
highlightPart,
|
||||
highlightPartOffsetHint);
|
||||
delayedShowAt(showAtMsgId, params);
|
||||
} else if (_showAtMsgId != showAtMsgId) {
|
||||
clearAllLoadRequests();
|
||||
setMsgId(
|
||||
showAtMsgId,
|
||||
highlightPart,
|
||||
highlightPartOffsetHint);
|
||||
setMsgId(showAtMsgId, params);
|
||||
firstLoadMessages();
|
||||
doneShow();
|
||||
}
|
||||
|
@ -2357,10 +2382,7 @@ void HistoryWidget::showHistory(
|
|||
_cornerButtons.skipReplyReturn(skipId);
|
||||
}
|
||||
|
||||
setMsgId(
|
||||
showAtMsgId,
|
||||
highlightPart,
|
||||
highlightPartOffsetHint);
|
||||
setMsgId(showAtMsgId, params);
|
||||
if (_historyInited) {
|
||||
DEBUG_LOG(("JumpToEnd(%1, %2, %3): "
|
||||
"Showing instant at %4."
|
||||
|
@ -2465,8 +2487,7 @@ void HistoryWidget::showHistory(
|
|||
clearInlineBot();
|
||||
|
||||
_showAtMsgId = showAtMsgId;
|
||||
_showAtMsgHighlightPart = highlightPart;
|
||||
_showAtMsgHighlightPartOffsetHint = highlightPartOffsetHint;
|
||||
_showAtMsgParams = params;
|
||||
_historyInited = false;
|
||||
_contactStatus = nullptr;
|
||||
_businessBotStatus = nullptr;
|
||||
|
@ -2485,6 +2506,8 @@ void HistoryWidget::showHistory(
|
|||
) | rpl::start_with_next([=] {
|
||||
updateControlsGeometry();
|
||||
}, _contactStatus->bar().lifetime());
|
||||
|
||||
refreshGiftToChannelShown();
|
||||
if (const auto user = _peer->asUser()) {
|
||||
_businessBotStatus = std::make_unique<BusinessBotStatus>(
|
||||
controller(),
|
||||
|
@ -2977,14 +3000,12 @@ void HistoryWidget::refreshSilentToggle() {
|
|||
}
|
||||
|
||||
void HistoryWidget::setupFastButtonMode() {
|
||||
if (!session().supportMode()) {
|
||||
return;
|
||||
}
|
||||
const auto field = _field->rawTextEdit();
|
||||
base::install_event_filter(field, [=](not_null<QEvent*> e) {
|
||||
if (e->type() != QEvent::KeyPress
|
||||
|| !_history
|
||||
|| !session().supportHelper().fastButtonMode(_history->peer)
|
||||
|| !FastButtonsMode()
|
||||
|| !session().fastButtonsBots().enabled(_history->peer)
|
||||
|| !_field->getLastText().isEmpty()) {
|
||||
return base::EventFilterResult::Continue;
|
||||
}
|
||||
|
@ -3737,10 +3758,7 @@ void HistoryWidget::messagesReceived(
|
|||
}
|
||||
|
||||
_delayedShowAtRequest = 0;
|
||||
setMsgId(
|
||||
_delayedShowAtMsgId,
|
||||
_delayedShowAtMsgHighlightPart,
|
||||
_delayedShowAtMsgHighlightPartOffsetHint);
|
||||
setMsgId(_delayedShowAtMsgId, _delayedShowAtMsgParams);
|
||||
historyLoaded();
|
||||
}
|
||||
if (session().supportMode()) {
|
||||
|
@ -3992,15 +4010,11 @@ void HistoryWidget::loadMessagesDown() {
|
|||
|
||||
void HistoryWidget::delayedShowAt(
|
||||
MsgId showAtMsgId,
|
||||
const TextWithEntities &highlightPart,
|
||||
int highlightPartOffsetHint) {
|
||||
const Window::SectionShow ¶ms) {
|
||||
if (!_history) {
|
||||
return;
|
||||
}
|
||||
if (_delayedShowAtMsgHighlightPart != highlightPart) {
|
||||
_delayedShowAtMsgHighlightPart = highlightPart;
|
||||
}
|
||||
_delayedShowAtMsgHighlightPartOffsetHint = highlightPartOffsetHint;
|
||||
_delayedShowAtMsgParams = params;
|
||||
if (_delayedShowAtRequest && _delayedShowAtMsgId == showAtMsgId) {
|
||||
return;
|
||||
}
|
||||
|
@ -4135,7 +4149,10 @@ void HistoryWidget::preloadHistoryIfNeeded() {
|
|||
preloadHistoryByScroll();
|
||||
checkReplyReturns();
|
||||
}
|
||||
if (clearMaybeSendStart() && !_history->isDisplayedEmpty()) {
|
||||
const auto hasNonEmpty = _history->findFirstNonEmpty();
|
||||
const auto readyForBotStart = hasNonEmpty
|
||||
|| (_history->loadedAtTop() && _history->loadedAtBottom());
|
||||
if (readyForBotStart && clearMaybeSendStart() && hasNonEmpty) {
|
||||
sendBotStartCommand();
|
||||
}
|
||||
}
|
||||
|
@ -4663,12 +4680,8 @@ PeerData *HistoryWidget::peer() const {
|
|||
// Sometimes _showAtMsgId is set directly.
|
||||
void HistoryWidget::setMsgId(
|
||||
MsgId showAtMsgId,
|
||||
const TextWithEntities &highlightPart,
|
||||
int highlightPartOffsetHint) {
|
||||
if (_showAtMsgHighlightPart != highlightPart) {
|
||||
_showAtMsgHighlightPart = highlightPart;
|
||||
}
|
||||
_showAtMsgHighlightPartOffsetHint = highlightPartOffsetHint;
|
||||
const Window::SectionShow ¶ms) {
|
||||
_showAtMsgParams = params;
|
||||
if (_showAtMsgId != showAtMsgId) {
|
||||
_showAtMsgId = showAtMsgId;
|
||||
if (_history) {
|
||||
|
@ -6429,8 +6442,8 @@ int HistoryWidget::countInitialScrollTop() {
|
|||
|
||||
enqueueMessageHighlight({
|
||||
item,
|
||||
base::take(_showAtMsgHighlightPart),
|
||||
base::take(_showAtMsgHighlightPartOffsetHint),
|
||||
base::take(_showAtMsgParams.highlightPart),
|
||||
base::take(_showAtMsgParams.highlightPartOffsetHint),
|
||||
});
|
||||
const auto result = itemTopForHighlight(view);
|
||||
createUnreadBarIfBelowVisibleArea(result);
|
||||
|
@ -6647,6 +6660,22 @@ void HistoryWidget::updateHistoryGeometry(
|
|||
}
|
||||
const auto toY = std::clamp(newScrollTop, 0, _scroll->scrollTopMax());
|
||||
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() {
|
||||
|
@ -6748,7 +6777,7 @@ void HistoryWidget::startMessageSendingAnimation(
|
|||
void HistoryWidget::updateListSize() {
|
||||
Expects(_list != nullptr);
|
||||
|
||||
_list->recountHistoryGeometry();
|
||||
_list->recountHistoryGeometry(!_historyInited);
|
||||
auto washidden = _scroll->isHidden();
|
||||
if (washidden) {
|
||||
_scroll->show();
|
||||
|
@ -8517,9 +8546,13 @@ void HistoryWidget::fullInfoUpdated() {
|
|||
handlePeerUpdate();
|
||||
checkSuggestToGigagroup();
|
||||
|
||||
if (clearMaybeSendStart() && !_history->isDisplayedEmpty()) {
|
||||
const auto hasNonEmpty = _history->findFirstNonEmpty();
|
||||
const auto readyForBotStart = hasNonEmpty
|
||||
|| (_history->loadedAtTop() && _history->loadedAtBottom());
|
||||
if (readyForBotStart && clearMaybeSendStart() && hasNonEmpty) {
|
||||
sendBotStartCommand();
|
||||
}
|
||||
refreshGiftToChannelShown();
|
||||
}
|
||||
if (updateCmdStartShown()) {
|
||||
refresh = true;
|
||||
|
|
|
@ -16,6 +16,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
#include "chat_helpers/field_characters_count_manager.h"
|
||||
#include "data/data_report.h"
|
||||
#include "window/section_widget.h"
|
||||
#include "window/window_session_controller.h"
|
||||
#include "ui/widgets/fields/input_field.h"
|
||||
#include "mtproto/sender.h"
|
||||
|
||||
|
@ -86,10 +87,6 @@ namespace Webrtc {
|
|||
enum class RecordAvailability : uchar;
|
||||
} // namespace Webrtc
|
||||
|
||||
namespace Window {
|
||||
class SessionController;
|
||||
} // namespace Window
|
||||
|
||||
namespace ChatHelpers {
|
||||
class TabbedPanel;
|
||||
class TabbedSelector;
|
||||
|
@ -160,10 +157,7 @@ public:
|
|||
void loadMessages();
|
||||
void loadMessagesDown();
|
||||
void firstLoadMessages();
|
||||
void delayedShowAt(
|
||||
MsgId showAtMsgId,
|
||||
const TextWithEntities &highlightPart,
|
||||
int highlightPartOffsetHint);
|
||||
void delayedShowAt(MsgId showAtMsgId, const Window::SectionShow ¶ms);
|
||||
|
||||
bool updateReplaceMediaButton();
|
||||
void updateFieldPlaceholder();
|
||||
|
@ -176,10 +170,7 @@ public:
|
|||
|
||||
History *history() const;
|
||||
PeerData *peer() const;
|
||||
void setMsgId(
|
||||
MsgId showAtMsgId,
|
||||
const TextWithEntities &highlightPart = {},
|
||||
int highlightPartOffsetHint = 0);
|
||||
void setMsgId(MsgId showAtMsgId, const Window::SectionShow ¶ms = {});
|
||||
MsgId msgId() const;
|
||||
|
||||
bool hasTopBarShadow() const {
|
||||
|
@ -244,10 +235,9 @@ public:
|
|||
bool applyDraft(
|
||||
FieldHistoryAction fieldHistoryAction = FieldHistoryAction::Clear);
|
||||
void showHistory(
|
||||
const PeerId &peer,
|
||||
PeerId peerId,
|
||||
MsgId showAtMsgId,
|
||||
const TextWithEntities &highlightPart = {},
|
||||
int highlightPartOffsetHint = 0);
|
||||
const Window::SectionShow ¶ms = {});
|
||||
void setChooseReportMessagesDetails(
|
||||
Data::ReportInput reportInput,
|
||||
Fn<void(std::vector<MsgId>)> callback);
|
||||
|
@ -406,6 +396,7 @@ private:
|
|||
void refreshTopBarActiveChat();
|
||||
|
||||
void refreshJoinChannelText();
|
||||
void refreshGiftToChannelShown();
|
||||
void requestMessageData(MsgId msgId);
|
||||
void messageDataReceived(not_null<PeerData*> peer, MsgId msgId);
|
||||
|
||||
|
@ -527,6 +518,7 @@ private:
|
|||
}
|
||||
|
||||
void setupShortcuts();
|
||||
void setupGiftToChannelButton();
|
||||
|
||||
void handlePeerMigration();
|
||||
|
||||
|
@ -731,8 +723,7 @@ private:
|
|||
bool _canSendTexts = false;
|
||||
MsgId _showAtMsgId = ShowAtUnreadMsgId;
|
||||
base::flat_set<MsgId> _topicsRequested;
|
||||
TextWithEntities _showAtMsgHighlightPart;
|
||||
int _showAtMsgHighlightPartOffsetHint = 0;
|
||||
Window::SectionShow _showAtMsgParams;
|
||||
bool _showAndMaybeSendStart = false;
|
||||
|
||||
int _firstLoadRequest = 0; // Not real mtpRequestId.
|
||||
|
@ -740,8 +731,7 @@ private:
|
|||
int _preloadDownRequest = 0; // Not real mtpRequestId.
|
||||
|
||||
MsgId _delayedShowAtMsgId = -1;
|
||||
TextWithEntities _delayedShowAtMsgHighlightPart;
|
||||
int _delayedShowAtMsgHighlightPartOffsetHint = 0;
|
||||
Window::SectionShow _delayedShowAtMsgParams;
|
||||
int _delayedShowAtRequest = 0; // Not real mtpRequestId.
|
||||
|
||||
History *_supportPreloadHistory = nullptr;
|
||||
|
@ -789,6 +779,8 @@ private:
|
|||
object_ptr<Ui::FlatButton> _botStart;
|
||||
object_ptr<Ui::FlatButton> _joinChannel;
|
||||
object_ptr<Ui::FlatButton> _muteUnmute;
|
||||
QPointer<Ui::IconButton> _giftToChannelIn;
|
||||
QPointer<Ui::IconButton> _giftToChannelOut;
|
||||
object_ptr<Ui::FlatButton> _discuss;
|
||||
object_ptr<Ui::FlatButton> _reportMessages;
|
||||
struct {
|
||||
|
|
|
@ -947,7 +947,8 @@ void DraftOptionsBox(
|
|||
|
||||
AddFilledSkip(bottom);
|
||||
|
||||
if (!hasOnlyForcedForwardedInfo) {
|
||||
if (!hasOnlyForcedForwardedInfo
|
||||
&& !HasOnlyDroppedForwardedInfo(items)) {
|
||||
Settings::AddButtonWithIcon(
|
||||
bottom,
|
||||
(dropNames
|
||||
|
|
|
@ -153,7 +153,7 @@ void ForwardPanel::updateTexts() {
|
|||
Unexpected("Corrupt forwarded information in message.");
|
||||
}
|
||||
}
|
||||
if (!keepNames) {
|
||||
if (!keepNames || HasOnlyDroppedForwardedInfo(_data.items)) {
|
||||
from = tr::lng_forward_sender_names_removed(tr::now);
|
||||
} else if (names.size() > 2) {
|
||||
from = tr::lng_forwarding_from(
|
||||
|
@ -445,4 +445,13 @@ bool HasOnlyForcedForwardedInfo(const HistoryItemsList &list) {
|
|||
return true;
|
||||
}
|
||||
|
||||
bool HasOnlyDroppedForwardedInfo(const HistoryItemsList &list) {
|
||||
for (const auto &item : list) {
|
||||
if (!item->computeDropForwardedInfo()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace HistoryView::Controls
|
||||
|
|
|
@ -85,5 +85,6 @@ void EditWebPageOptions(
|
|||
Fn<void(Data::WebPageDraft)> done);
|
||||
|
||||
[[nodiscard]] bool HasOnlyForcedForwardedInfo(const HistoryItemsList &list);
|
||||
[[nodiscard]] bool HasOnlyDroppedForwardedInfo(const HistoryItemsList &list);
|
||||
|
||||
} // namespace HistoryView::Controls
|
||||
|
|
|
@ -1315,25 +1315,28 @@ base::unique_qptr<Ui::PopupMenu> FillContextMenu(
|
|||
void CopyPostLink(
|
||||
not_null<Window::SessionController*> controller,
|
||||
FullMsgId itemId,
|
||||
Context context) {
|
||||
CopyPostLink(controller->uiShow(), itemId, context);
|
||||
Context context,
|
||||
std::optional<TimeId> videoTimestamp) {
|
||||
CopyPostLink(controller->uiShow(), itemId, context, videoTimestamp);
|
||||
}
|
||||
|
||||
void CopyPostLink(
|
||||
std::shared_ptr<Main::SessionShow> show,
|
||||
FullMsgId itemId,
|
||||
Context context) {
|
||||
Context context,
|
||||
std::optional<TimeId> videoTimestamp) {
|
||||
const auto item = show->session().data().message(itemId);
|
||||
if (!item || !item->hasDirectLink()) {
|
||||
return;
|
||||
}
|
||||
const auto inRepliesContext = (context == Context::Replies);
|
||||
const auto forceNonPublicLink = base::IsCtrlPressed();
|
||||
const auto forceNonPublicLink = !videoTimestamp && base::IsCtrlPressed();
|
||||
QGuiApplication::clipboard()->setText(
|
||||
item->history()->session().api().exportDirectMessageLink(
|
||||
item,
|
||||
inRepliesContext,
|
||||
forceNonPublicLink));
|
||||
forceNonPublicLink,
|
||||
videoTimestamp));
|
||||
|
||||
const auto isPublicLink = [&] {
|
||||
if (forceNonPublicLink) {
|
||||
|
@ -1354,7 +1357,7 @@ void CopyPostLink(
|
|||
}
|
||||
return channel->hasUsername();
|
||||
}();
|
||||
if (isPublicLink) {
|
||||
if (isPublicLink && !videoTimestamp) {
|
||||
show->showToast({
|
||||
.text = tr::lng_channel_public_link_copied(
|
||||
tr::now, Ui::Text::Bold
|
||||
|
|
|
@ -60,11 +60,13 @@ base::unique_qptr<Ui::PopupMenu> FillContextMenu(
|
|||
void CopyPostLink(
|
||||
not_null<Window::SessionController*> controller,
|
||||
FullMsgId itemId,
|
||||
Context context);
|
||||
Context context,
|
||||
std::optional<TimeId> videoTimestamp = {});
|
||||
void CopyPostLink(
|
||||
std::shared_ptr<Main::SessionShow> show,
|
||||
FullMsgId itemId,
|
||||
Context context);
|
||||
Context context,
|
||||
std::optional<TimeId> videoTimestamp = {});
|
||||
void CopyStoryLink(
|
||||
std::shared_ptr<Main::SessionShow> show,
|
||||
FullStoryId storyId);
|
||||
|
|
|
@ -38,6 +38,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
#include "lang/lang_keys.h"
|
||||
#include "mainwidget.h"
|
||||
#include "main/main_session.h"
|
||||
#include "settings/settings_premium.h"
|
||||
#include "ui/text/text_options.h"
|
||||
#include "ui/painter.h"
|
||||
#include "window/themes/window_theme.h" // IsNightMode.
|
||||
|
@ -378,6 +379,7 @@ struct Message::CommentsButton {
|
|||
struct Message::FromNameStatus {
|
||||
EmojiStatusId id;
|
||||
std::unique_ptr<Ui::Text::CustomEmoji> custom;
|
||||
ClickHandlerPtr link;
|
||||
int skip = 0;
|
||||
};
|
||||
|
||||
|
@ -2815,6 +2817,25 @@ bool Message::getStateFromName(
|
|||
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
|
||||
&& point.x() < availableLeft + availableWidth
|
||||
&& point.x() < availableLeft + nameText->maxWidth()) {
|
||||
|
@ -2835,6 +2856,21 @@ bool Message::getStateFromName(
|
|||
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(
|
||||
QPoint point,
|
||||
QRect &trect,
|
||||
|
|
|
@ -300,6 +300,7 @@ private:
|
|||
|
||||
void refreshRightBadge();
|
||||
void validateFromNameText(PeerData *from) const;
|
||||
void ensureFromNameStatusLink(not_null<PeerData*> peer) const;
|
||||
|
||||
mutable std::unique_ptr<RightAction> _rightAction;
|
||||
mutable ClickHandlerPtr _fastReplyLink;
|
||||
|
|
|
@ -163,7 +163,7 @@ PaidReactionToast::~PaidReactionToast() {
|
|||
|
||||
bool PaidReactionToast::maybeShowFor(not_null<HistoryItem*> item) {
|
||||
const auto count = item->reactionsPaidScheduled();
|
||||
const auto anonymous = item->reactionsLocalAnonymous();
|
||||
const auto shownPeer = item->reactionsLocalShownPeer();
|
||||
const auto at = _owner->reactions().sendingScheduledPaidAt(item);
|
||||
if (!count || !at) {
|
||||
return false;
|
||||
|
@ -173,14 +173,14 @@ bool PaidReactionToast::maybeShowFor(not_null<HistoryItem*> item) {
|
|||
if (at <= crl::now() + ignore) {
|
||||
return false;
|
||||
}
|
||||
showFor(item->fullId(), count, anonymous, at - ignore, total);
|
||||
showFor(item->fullId(), count, shownPeer, at - ignore, total);
|
||||
return true;
|
||||
}
|
||||
|
||||
void PaidReactionToast::showFor(
|
||||
FullMsgId itemId,
|
||||
int count,
|
||||
bool anonymous,
|
||||
PeerId shownPeer,
|
||||
crl::time finish,
|
||||
crl::time total) {
|
||||
const auto old = _weak.get();
|
||||
|
@ -188,7 +188,7 @@ void PaidReactionToast::showFor(
|
|||
if (i != end(_stack)) {
|
||||
if (old && i + 1 == end(_stack)) {
|
||||
_count = count;
|
||||
_anonymous = anonymous;
|
||||
_shownPeer = shownPeer;
|
||||
_timeFinish = finish;
|
||||
return;
|
||||
}
|
||||
|
@ -202,14 +202,14 @@ void PaidReactionToast::showFor(
|
|||
_hiding.push_back(base::take(_weak));
|
||||
}
|
||||
_count.reset();
|
||||
_anonymous.reset();
|
||||
_shownPeer.reset();
|
||||
_timeFinish.reset();
|
||||
_count = count;
|
||||
_anonymous = anonymous;
|
||||
_shownPeer = shownPeer;
|
||||
_timeFinish = finish;
|
||||
auto text = rpl::combine(
|
||||
rpl::conditional(
|
||||
_anonymous.value(),
|
||||
_shownPeer.value() | rpl::map(rpl::mappers::_1 == PeerId()),
|
||||
tr::lng_paid_react_toast_anonymous(
|
||||
lt_count,
|
||||
_count.value() | tr::to_count(),
|
||||
|
|
|
@ -40,7 +40,7 @@ private:
|
|||
void showFor(
|
||||
FullMsgId itemId,
|
||||
int count,
|
||||
bool anonymous,
|
||||
PeerId shownPeer,
|
||||
crl::time left,
|
||||
crl::time total);
|
||||
|
||||
|
@ -54,7 +54,7 @@ private:
|
|||
base::weak_ptr<Ui::Toast::Instance> _weak;
|
||||
std::vector<base::weak_ptr<Ui::Toast::Instance>> _hiding;
|
||||
rpl::variable<int> _count;
|
||||
rpl::variable<bool> _anonymous;
|
||||
rpl::variable<PeerId> _shownPeer;
|
||||
rpl::variable<crl::time> _timeFinish;
|
||||
|
||||
std::vector<FullMsgId> _stack;
|
||||
|
|
|
@ -510,9 +510,19 @@ QSize Document::countOptimalSize() {
|
|||
accumulate_max(maxWidth, tleft + named->name.maxWidth() + tright);
|
||||
accumulate_min(maxWidth, st::msgMaxWidth);
|
||||
}
|
||||
if (voice && voice->transcribe) {
|
||||
maxWidth += st::historyTranscribeSkip
|
||||
+ voice->transcribe->size().width();
|
||||
if (voice) {
|
||||
const auto maxWaveformWidth = ::Media::Player::kWaveformSamplesCount *
|
||||
(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();
|
||||
|
|
|
@ -69,7 +69,10 @@ QSize Game::countOptimalSize() {
|
|||
|
||||
// init attach
|
||||
if (!_attach) {
|
||||
_attach = CreateAttach(_parent, _data->document, _data->photo);
|
||||
_attach = CreateAttach(
|
||||
_parent,
|
||||
_data->document,
|
||||
_data->document ? nullptr : _data->photo);
|
||||
}
|
||||
|
||||
// init strings
|
||||
|
|
|
@ -19,6 +19,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
#include "media/streaming/media_streaming_instance.h"
|
||||
#include "media/streaming/media_streaming_player.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 "ui/boxes/confirm_box.h"
|
||||
#include "ui/painter.h"
|
||||
|
@ -47,6 +48,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
#include "ui/ui_utility.h"
|
||||
#include "ui/effects/path_shift_gradient.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_stories.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_origin.h"
|
||||
#include "data/data_document_media.h"
|
||||
#include "data/data_web_page.h"
|
||||
#include "storage/storage_account.h"
|
||||
#include "styles/style_chat.h"
|
||||
|
||||
#include <QSvgRenderer>
|
||||
|
@ -145,6 +150,7 @@ Gif::Gif(
|
|||
bool spoiler)
|
||||
: File(parent, realParent)
|
||||
, _data(document)
|
||||
, _videoCover(LookupVideoCover(document, realParent))
|
||||
, _storyId(realParent->media()
|
||||
? realParent->media()->storyId()
|
||||
: FullStoryId())
|
||||
|
@ -154,7 +160,9 @@ Gif::Gif(
|
|||
? std::make_unique<MediaSpoiler>()
|
||||
: nullptr)
|
||||
, _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 (_spoiler) {
|
||||
_drawTtl = CreateTtlPaintCallback([=] { repaint(); });
|
||||
|
@ -200,6 +208,12 @@ Gif::Gif(
|
|||
|
||||
if ((_dataMedia = _data->activeMediaView())) {
|
||||
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 {
|
||||
_data->loadThumbnail(realParent->fullId());
|
||||
if (!autoplayEnabled()) {
|
||||
|
@ -396,6 +410,14 @@ bool Gif::downloadInCorner() const {
|
|||
&& !_data->inappPlaybackFailed();
|
||||
}
|
||||
|
||||
bool Gif::autoplayUnderCursor() const {
|
||||
return (_videoTimestamp || _hasVideoCover);
|
||||
}
|
||||
|
||||
bool Gif::underCursor() const {
|
||||
return ClickHandler::getActive() == currentVideoLink();
|
||||
}
|
||||
|
||||
bool Gif::autoplayEnabled() const {
|
||||
if (_realParent->isSponsored()) {
|
||||
return true;
|
||||
|
@ -413,6 +435,8 @@ bool Gif::hideMessageText() const {
|
|||
void Gif::draw(Painter &p, const PaintContext &context) const {
|
||||
if (width() < st::msgPadding.left() + st::msgPadding.right() + 1) return;
|
||||
|
||||
_smallGroupPart = false;
|
||||
|
||||
ensureDataMediaCreated();
|
||||
const auto item = _parent->data();
|
||||
const auto loaded = dataLoaded();
|
||||
|
@ -469,11 +493,14 @@ void Gif::draw(Painter &p, const PaintContext &context) const {
|
|||
validateSpoilerImageCache(rthumb.size(), rounding);
|
||||
}
|
||||
|
||||
const auto startPlay = autoplay
|
||||
const auto canStartPlay = autoplay
|
||||
&& !_streamed
|
||||
&& !activeRoundPlaying
|
||||
&& !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);
|
||||
} else {
|
||||
checkStreamedIsStarted();
|
||||
|
@ -514,8 +541,9 @@ void Gif::draw(Painter &p, const PaintContext &context) const {
|
|||
|
||||
const auto skipDrawingContent = context.skipDrawingParts
|
||||
== PaintContext::SkipDrawingParts::Content;
|
||||
if (streamed && !skipDrawingContent && !fullHiddenBySpoiler) {
|
||||
auto paused = context.paused;
|
||||
const auto drawStreamed = streamed && (shouldBePlaying || !_videoCover);
|
||||
if (drawStreamed && !skipDrawingContent && !fullHiddenBySpoiler) {
|
||||
auto paused = context.paused || !shouldBePlaying;
|
||||
auto request = ::Media::Streaming::FrameRequest{
|
||||
.outer = QSize(usew, painth) * style::DevicePixelRatio(),
|
||||
.blurredBackground = true,
|
||||
|
@ -583,6 +611,9 @@ void Gif::draw(Painter &p, const PaintContext &context) const {
|
|||
validateThumbCache({ usew, painth }, isRound, rounding);
|
||||
p.drawImage(rthumb, _thumbCache);
|
||||
}
|
||||
if (!isRound) {
|
||||
paintTimestampMark(p, rthumb, rounding);
|
||||
}
|
||||
|
||||
if (revealed < 1.) {
|
||||
p.setOpacity(1. - revealed);
|
||||
|
@ -868,6 +899,70 @@ void Gif::paintTranscribe(
|
|||
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(
|
||||
Painter &p,
|
||||
QRect rthumb,
|
||||
|
@ -891,6 +986,8 @@ QImage Gif::spoilerTagBackground() const {
|
|||
}
|
||||
|
||||
void Gif::validateVideoThumbnail() const {
|
||||
Expects(!_videoCover);
|
||||
|
||||
const auto content = _dataMedia->videoThumbnailContent();
|
||||
if (_videoThumbnailFrame || content.isEmpty()) {
|
||||
return;
|
||||
|
@ -906,13 +1003,25 @@ void Gif::validateThumbCache(
|
|||
QSize outer,
|
||||
bool isEllipse,
|
||||
std::optional<Ui::BubbleRounding> rounding) const {
|
||||
const auto good = _dataMedia->goodThumbnail();
|
||||
const auto normal = good ? good : _dataMedia->thumbnail();
|
||||
const auto good = _videoCoverMedia
|
||||
? _videoCoverMedia->image(Data::PhotoSize::Large)
|
||||
: _dataMedia->goodThumbnail();
|
||||
const auto normal = good
|
||||
? good
|
||||
: _videoCoverMedia
|
||||
? nullptr
|
||||
: _dataMedia->thumbnail();
|
||||
if (!normal) {
|
||||
_data->loadThumbnail(_realParent->fullId());
|
||||
validateVideoThumbnail();
|
||||
if (_videoCoverMedia) {
|
||||
_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
|
||||
? (!good
|
||||
&& (normal->width() < kUseNonBlurredThreshold)
|
||||
|
@ -934,9 +1043,17 @@ void Gif::validateThumbCache(
|
|||
}
|
||||
|
||||
QImage Gif::prepareThumbCache(QSize outer) const {
|
||||
const auto good = _dataMedia->goodThumbnail();
|
||||
const auto normal = good ? good : _dataMedia->thumbnail();
|
||||
const auto videothumb = normal ? nullptr : _videoThumbnailFrame.get();
|
||||
const auto good = _videoCoverMedia
|
||||
? _videoCoverMedia->image(Data::PhotoSize::Large)
|
||||
: _dataMedia->goodThumbnail();
|
||||
const auto normal = good
|
||||
? good
|
||||
: _videoCoverMedia
|
||||
? nullptr
|
||||
: _dataMedia->thumbnail();
|
||||
const auto videothumb = (normal || _videoCoverMedia)
|
||||
? nullptr
|
||||
: _videoThumbnailFrame.get();
|
||||
auto blurred = (!good
|
||||
&& normal
|
||||
&& (normal->width() < kUseNonBlurredThreshold)
|
||||
|
@ -946,6 +1063,10 @@ QImage Gif::prepareThumbCache(QSize outer) const {
|
|||
const auto blurFromLarge = good || (normal && !blurred);
|
||||
const auto large = blurFromLarge ? normal : videothumb;
|
||||
if (videothumb) {
|
||||
} else if (_videoCoverMedia) {
|
||||
if (const auto embedded = _videoCoverMedia->thumbnailInline()) {
|
||||
blurred = embedded;
|
||||
}
|
||||
} else if (const auto embedded = _dataMedia->thumbnailInline()) {
|
||||
blurred = embedded;
|
||||
}
|
||||
|
@ -971,7 +1092,9 @@ void Gif::validateSpoilerImageCache(
|
|||
&& _spoiler->backgroundRounding == rounding) {
|
||||
return;
|
||||
}
|
||||
const auto normal = _dataMedia->thumbnail();
|
||||
const auto normal = _videoCoverMedia
|
||||
? _videoCoverMedia->image(Data::PhotoSize::Small)
|
||||
: _dataMedia->thumbnail();
|
||||
auto container = std::optional<Image>();
|
||||
const auto downscale = [&](Image *image) {
|
||||
if (!image || (image->width() <= 40 && image->height() <= 40)) {
|
||||
|
@ -983,7 +1106,9 @@ void Gif::validateSpoilerImageCache(
|
|||
Qt::SmoothTransformation));
|
||||
return &*container;
|
||||
};
|
||||
const auto embedded = _dataMedia->thumbnailInline();
|
||||
const auto embedded = _videoCoverMedia
|
||||
? _videoCoverMedia->thumbnailInline()
|
||||
: _dataMedia->thumbnailInline();
|
||||
const auto blurred = embedded ? embedded : downscale(normal);
|
||||
_spoiler->background = Images::Round(
|
||||
PrepareWithBlurredBackground(
|
||||
|
@ -1174,15 +1299,7 @@ TextState Gif::textState(QPoint point, StateRequest request) const {
|
|||
: (isRound && _parent->data()->media()->ttlSeconds())
|
||||
? _openl // Overriden.
|
||||
: _spoiler->link)
|
||||
: _data->uploading()
|
||||
? _cancell
|
||||
: _realParent->isSending()
|
||||
? nullptr
|
||||
: (dataLoaded() || _dataMedia->canBePlayed(_realParent))
|
||||
? _openl
|
||||
: _data->loading()
|
||||
? _cancell
|
||||
: _savel;
|
||||
: currentVideoLink();
|
||||
}
|
||||
const auto checkBottomInfo = !inWebPage
|
||||
&& (unwrapped || !bubble || isBubbleBottom());
|
||||
|
@ -1290,8 +1407,8 @@ void Gif::drawGrouped(
|
|||
|| _data->displayLoading();
|
||||
const auto st = context.st;
|
||||
const auto sti = context.imageStyle();
|
||||
const auto fullFeatured = fullFeaturedGrouped(sides);
|
||||
const auto cornerDownload = fullFeatured && downloadInCorner();
|
||||
_smallGroupPart = !fullFeaturedGrouped(sides);
|
||||
const auto cornerDownload = !_smallGroupPart && downloadInCorner();
|
||||
const auto canBePlayed = _dataMedia->canBePlayed(_realParent);
|
||||
|
||||
const auto revealed = _spoiler
|
||||
|
@ -1302,16 +1419,22 @@ void Gif::drawGrouped(
|
|||
validateSpoilerImageCache(geometry.size(), rounding);
|
||||
}
|
||||
|
||||
const auto autoplay = fullFeatured
|
||||
const auto autoplay = !_smallGroupPart
|
||||
&& autoplayEnabled()
|
||||
&& canBePlayed
|
||||
&& CanPlayInline(_data);
|
||||
const auto startPlay = autoplay && !_streamed;
|
||||
if (startPlay) {
|
||||
const auto canStartPlay = autoplay
|
||||
&& !_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);
|
||||
} else {
|
||||
checkStreamedIsStarted();
|
||||
}
|
||||
|
||||
const auto streamingMode = _streamed || autoplay;
|
||||
const auto activeOwnPlaying = activeOwnStreamed();
|
||||
|
||||
|
@ -1364,7 +1487,9 @@ void Gif::drawGrouped(
|
|||
activeOwnPlaying->frozenStatusText = QString();
|
||||
}
|
||||
p.drawImage(geometry, streamed->frame(request));
|
||||
if (!context.paused) {
|
||||
const auto paused = context.paused
|
||||
|| (autoplayUnderCursor() && !underCursor());
|
||||
if (!paused) {
|
||||
streamed->markFrameShown();
|
||||
}
|
||||
}
|
||||
|
@ -1479,7 +1604,7 @@ void Gif::drawGrouped(
|
|||
}
|
||||
p.setOpacity(1.);
|
||||
}
|
||||
if (fullFeatured) {
|
||||
if (!_smallGroupPart) {
|
||||
drawCornerStatus(p, context, geometry.topLeft());
|
||||
}
|
||||
}
|
||||
|
@ -1492,8 +1617,7 @@ TextState Gif::getStateGrouped(
|
|||
if (!geometry.contains(point)) {
|
||||
return {};
|
||||
}
|
||||
const auto isFullFeaturedGrouped = fullFeaturedGrouped(sides);
|
||||
if (isFullFeaturedGrouped) {
|
||||
if (!_smallGroupPart) {
|
||||
const auto state = cornerStatusTextState(
|
||||
point,
|
||||
request,
|
||||
|
@ -1503,37 +1627,53 @@ TextState Gif::getStateGrouped(
|
|||
}
|
||||
}
|
||||
ensureDataMediaCreated();
|
||||
|
||||
auto link = (_spoiler && !_spoiler->revealed)
|
||||
? (_sensitiveSpoiler ? spoilerTagLink() : _spoiler->link)
|
||||
: _data->uploading()
|
||||
: currentVideoLink();
|
||||
return TextState(_parent, std::move(link));
|
||||
}
|
||||
|
||||
ClickHandlerPtr Gif::currentVideoLink() const {
|
||||
return _data->uploading()
|
||||
? _cancell
|
||||
: _realParent->isSending()
|
||||
? nullptr
|
||||
: dataLoaded()
|
||||
? _openl
|
||||
: (_data->loading() && !isFullFeaturedGrouped)
|
||||
: (_data->loading() && _smallGroupPart)
|
||||
? _cancell
|
||||
: _dataMedia->canBePlayed(_realParent)
|
||||
? _openl
|
||||
: _data->loading()
|
||||
? _cancell
|
||||
: _savel;
|
||||
return TextState(_parent, std::move(link));
|
||||
}
|
||||
|
||||
void Gif::ensureDataMediaCreated() const {
|
||||
if (_dataMedia) {
|
||||
if (_dataMedia && (!_videoCover || _videoCoverMedia)) {
|
||||
return;
|
||||
}
|
||||
_dataMedia = _data->createMediaView();
|
||||
_videoCoverMedia = _videoCover
|
||||
? _videoCover->createMediaView()
|
||||
: nullptr;
|
||||
dataMediaCreated();
|
||||
}
|
||||
|
||||
void Gif::dataMediaCreated() const {
|
||||
Expects(_dataMedia != nullptr);
|
||||
|
||||
_dataMedia->goodThumbnailWanted();
|
||||
_dataMedia->thumbnailWanted(_realParent->fullId());
|
||||
if (!autoplayEnabled()) {
|
||||
_dataMedia->videoThumbnailWanted(_realParent->fullId());
|
||||
if (_videoCoverMedia) {
|
||||
_videoCoverMedia->wanted(
|
||||
Data::PhotoSize::Large,
|
||||
_realParent->fullId());
|
||||
} else {
|
||||
_dataMedia->goodThumbnailWanted();
|
||||
_dataMedia->thumbnailWanted(_realParent->fullId());
|
||||
if (!autoplayEnabled()) {
|
||||
_dataMedia->videoThumbnailWanted(_realParent->fullId());
|
||||
}
|
||||
}
|
||||
history()->owner().registerHeavyViewPart(_parent);
|
||||
togglePollingStory(true);
|
||||
|
@ -1670,12 +1810,18 @@ void Gif::validateGroupedCache(
|
|||
|
||||
ensureDataMediaCreated();
|
||||
|
||||
const auto good = _dataMedia->goodThumbnail();
|
||||
const auto thumb = _dataMedia->thumbnail();
|
||||
const auto good = _videoCoverMedia
|
||||
? _videoCoverMedia->image(Data::PhotoSize::Large)
|
||||
: _dataMedia->goodThumbnail();
|
||||
const auto thumb = _videoCoverMedia
|
||||
? nullptr
|
||||
: _dataMedia->thumbnail();
|
||||
const auto image = good
|
||||
? good
|
||||
: thumb
|
||||
? thumb
|
||||
: _videoCoverMedia
|
||||
? _videoCoverMedia->thumbnailInline()
|
||||
: _dataMedia->thumbnailInline();
|
||||
const auto blur = !good
|
||||
&& (!thumb
|
||||
|
@ -1746,7 +1892,8 @@ void Gif::updateStatusText() const {
|
|||
}
|
||||
const auto round = activeRoundStreamed();
|
||||
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 state = streamed->player().prepareLegacyState();
|
||||
if (state.length) {
|
||||
|
@ -1756,9 +1903,17 @@ void Gif::updateStatusText() const {
|
|||
} else if (!::Media::Player::IsStoppedOrStopping(state.state)) {
|
||||
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 {
|
||||
statusSize = -1 - (_data->duration() / 1000);
|
||||
if (!frozen) {
|
||||
statusSize = -1 - (_data->duration() / 1000);
|
||||
}
|
||||
_videoPosition = 0;
|
||||
}
|
||||
}
|
||||
if (statusSize != _statusSize) {
|
||||
|
@ -1918,6 +2073,10 @@ void Gif::startStreamedPlayer() const {
|
|||
//if (!_streamed->withSound) {
|
||||
options.mode = ::Media::Streaming::Mode::Video;
|
||||
options.loop = true;
|
||||
options.position = _videoTimestamp
|
||||
? (_videoTimestamp * crl::time(1000))
|
||||
: _parent->history()->session().local().mediaLastPlaybackPosition(
|
||||
_data->id);
|
||||
//}
|
||||
_streamed->instance.play(options);
|
||||
}
|
||||
|
@ -1925,10 +2084,12 @@ void Gif::startStreamedPlayer() const {
|
|||
void Gif::checkStreamedIsStarted() const {
|
||||
if (!_streamed || _streamed->instance.playerLocked()) {
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
@ -1941,6 +2102,7 @@ void Gif::setStreamed(std::unique_ptr<Streamed> value) {
|
|||
history()->owner().registerHeavyViewPart(_parent);
|
||||
togglePollingStory(true);
|
||||
} else if (removed) {
|
||||
_videoPosition = 0;
|
||||
_parent->checkHeavyPart();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -15,9 +15,11 @@ struct HistoryMessageVia;
|
|||
struct HistoryMessageReply;
|
||||
struct HistoryMessageForwarded;
|
||||
class Painter;
|
||||
class PhotoData;
|
||||
|
||||
namespace Data {
|
||||
class DocumentMedia;
|
||||
class PhotoMedia;
|
||||
} // namespace Data
|
||||
|
||||
namespace Media {
|
||||
|
@ -37,6 +39,7 @@ enum class Error;
|
|||
|
||||
namespace HistoryView {
|
||||
|
||||
class Photo;
|
||||
class Reply;
|
||||
class TranscribeButton;
|
||||
|
||||
|
@ -137,6 +140,8 @@ private:
|
|||
void dataMediaCreated() const;
|
||||
|
||||
[[nodiscard]] bool autoplayEnabled() const;
|
||||
[[nodiscard]] bool autoplayUnderCursor() const;
|
||||
[[nodiscard]] bool underCursor() const;
|
||||
|
||||
void playAnimation(bool autoplay) override;
|
||||
QSize countOptimalSize() override;
|
||||
|
@ -163,6 +168,10 @@ private:
|
|||
int y,
|
||||
bool right,
|
||||
const PaintContext &context) const;
|
||||
void paintTimestampMark(
|
||||
Painter &p,
|
||||
QRect rthumb,
|
||||
std::optional<Ui::BubbleRounding> rounding) const;
|
||||
|
||||
[[nodiscard]] bool needInfoDisplay() const;
|
||||
[[nodiscard]] bool needCornerStatusDisplay() const;
|
||||
|
@ -202,28 +211,35 @@ private:
|
|||
QPoint point,
|
||||
StateRequest request,
|
||||
QPoint position) const;
|
||||
[[nodiscard]] ClickHandlerPtr currentVideoLink() const;
|
||||
|
||||
void togglePollingStory(bool enabled) const;
|
||||
|
||||
TtlRoundPaintCallback _drawTtl;
|
||||
|
||||
const not_null<DocumentData*> _data;
|
||||
PhotoData *_videoCover = nullptr;
|
||||
const FullStoryId _storyId;
|
||||
std::unique_ptr<Streamed> _streamed;
|
||||
const std::unique_ptr<MediaSpoiler> _spoiler;
|
||||
mutable std::unique_ptr<MediaSpoilerTag> _spoilerTag;
|
||||
mutable std::unique_ptr<TranscribeButton> _transcribe;
|
||||
mutable std::shared_ptr<Data::DocumentMedia> _dataMedia;
|
||||
mutable std::shared_ptr<Data::PhotoMedia> _videoCoverMedia;
|
||||
mutable std::unique_ptr<Image> _videoThumbnailFrame;
|
||||
QString _downloadSize;
|
||||
mutable QImage _thumbCache;
|
||||
mutable QImage _roundingMask;
|
||||
mutable crl::time _videoPosition = 0;
|
||||
mutable TimeId _videoTimestamp = 0;
|
||||
mutable std::optional<Ui::BubbleRounding> _thumbCacheRounding;
|
||||
mutable bool _thumbCacheBlurred : 1 = false;
|
||||
mutable bool _thumbIsEllipse : 1 = false;
|
||||
mutable bool _pollingStory : 1 = false;
|
||||
mutable bool _purchasedPriceTag : 1 = false;
|
||||
mutable bool _smallGroupPart : 1 = false;
|
||||
const bool _sensitiveSpoiler : 1 = false;
|
||||
const bool _hasVideoCover : 1 = false;
|
||||
|
||||
};
|
||||
|
||||
|
|
|
@ -114,7 +114,6 @@ private:
|
|||
|
||||
void ensureDataMediaCreated() const;
|
||||
void dataMediaCreated() const;
|
||||
void setupSpoilerTag() const;
|
||||
|
||||
QSize countOptimalSize() override;
|
||||
QSize countCurrentSize(int newWidth) override;
|
||||
|
|
|
@ -496,7 +496,9 @@ auto UniqueGiftBg(
|
|||
p.setPen(Qt::NoPen);
|
||||
const auto webpreview = (media.get() != view->media());
|
||||
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 inner = full.marginsRemoved(
|
||||
{ thickness, thickness, thickness, thickness });
|
||||
|
@ -519,7 +521,8 @@ auto UniqueGiftBg(
|
|||
const auto width = media->width();
|
||||
const auto shift = width / 12;
|
||||
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);
|
||||
Ui::PaintPoints(
|
||||
p,
|
||||
|
|
|
@ -80,15 +80,11 @@ constexpr auto kSponsoredUserpicLines = 2;
|
|||
const auto spoiler = false;
|
||||
for (const auto &item : data.items) {
|
||||
if (const auto document = std::get_if<DocumentData*>(&item)) {
|
||||
const auto hasQualitiesList = false;
|
||||
const auto skipPremiumEffect = false;
|
||||
result.push_back(std::make_unique<Data::MediaFile>(
|
||||
parent,
|
||||
*document,
|
||||
skipPremiumEffect,
|
||||
hasQualitiesList,
|
||||
spoiler,
|
||||
/*ttlSeconds = */0));
|
||||
using MediaFile = Data::MediaFile;
|
||||
using Args = MediaFile::Args;
|
||||
const auto data = *document;
|
||||
result.push_back(
|
||||
std::make_unique<Data::MediaFile>(parent, data, Args{}));
|
||||
} else if (const auto photo = std::get_if<PhotoData*>(&item)) {
|
||||
result.push_back(std::make_unique<Data::MediaPhoto>(
|
||||
parent,
|
||||
|
@ -317,13 +313,13 @@ void WebPage::setupAdditionalData() {
|
|||
UrlClickHandler::Open(link);
|
||||
});
|
||||
if (!_attach) {
|
||||
const auto maybePhoto = details.mediaPhotoId
|
||||
? session->data().photo(details.mediaPhotoId).get()
|
||||
: nullptr;
|
||||
const auto maybeDocument = details.mediaDocumentId
|
||||
? session->data().document(
|
||||
details.mediaDocumentId).get()
|
||||
: nullptr;
|
||||
const auto maybePhoto = (!maybeDocument && details.mediaPhotoId)
|
||||
? session->data().photo(details.mediaPhotoId).get()
|
||||
: nullptr;
|
||||
_attach = CreateAttach(
|
||||
_parent,
|
||||
maybeDocument,
|
||||
|
@ -528,7 +524,9 @@ QSize WebPage::countOptimalSize() {
|
|||
_attach = CreateAttach(
|
||||
_parent,
|
||||
_data->document,
|
||||
_data->photo,
|
||||
((!_data->document || _data->photoIsVideoCover)
|
||||
? _data->photo
|
||||
: nullptr),
|
||||
_collage,
|
||||
_data->url);
|
||||
}
|
||||
|
|
|
@ -1164,6 +1164,7 @@ infoHoursOuter: RoundButton(defaultActiveButton) {
|
|||
}
|
||||
infoHoursOuterMargin: margins(8px, 4px, 8px, 4px);
|
||||
infoHoursDaySkip: 6px;
|
||||
infoHoursArrowSize: 4px;
|
||||
|
||||
infoSharedMediaScroll: ScrollArea(defaultScrollArea) {
|
||||
round: 1px;
|
||||
|
|
|
@ -110,6 +110,8 @@ WrapWidget::WrapWidget(
|
|||
Wrap wrap,
|
||||
not_null<Memento*> memento)
|
||||
: SectionWidget(parent, window, rpl::producer<PeerData*>())
|
||||
, _isSeparatedWindow(
|
||||
window->windowId().type == Window::SeparateType::SharedMedia)
|
||||
, _wrap(wrap)
|
||||
, _controller(createController(window, memento->content()))
|
||||
, _topShadow(this)
|
||||
|
@ -446,10 +448,7 @@ void WrapWidget::setupTopBarMenuToggle() {
|
|||
addTopBarMenuButton();
|
||||
}
|
||||
}, _topBar->lifetime());
|
||||
} else if (section.type() == Section::Type::PeerGifts
|
||||
&& key.peer()
|
||||
&& key.peer()->isChannel()
|
||||
&& key.peer()->canManageGifts()) {
|
||||
} else if (section.type() == Section::Type::PeerGifts && key.peer()) {
|
||||
addTopBarMenuButton();
|
||||
}
|
||||
}
|
||||
|
@ -504,6 +503,7 @@ void WrapWidget::addTopBarMenuButton() {
|
|||
using Command = Shortcuts::Command;
|
||||
|
||||
request->check(Command::ShowChatMenu, 1) && request->handle([=] {
|
||||
Window::ActivateWindow(_controller->parentController());
|
||||
showTopBarMenu(false);
|
||||
return true;
|
||||
});
|
||||
|
@ -1048,7 +1048,8 @@ const Ui::RoundRect *WrapWidget::bottomSkipRounding() const {
|
|||
}
|
||||
|
||||
bool WrapWidget::hasBackButton() const {
|
||||
return (wrap() == Wrap::Narrow || hasStackHistory());
|
||||
return !_isSeparatedWindow
|
||||
&& (wrap() == Wrap::Narrow || hasStackHistory());
|
||||
}
|
||||
|
||||
bool WrapWidget::willHaveBackButton(
|
||||
|
|
|
@ -208,6 +208,8 @@ private:
|
|||
void addProfileCallsButton();
|
||||
void showTopBarMenu(bool check);
|
||||
|
||||
const bool _isSeparatedWindow = false;
|
||||
|
||||
rpl::variable<Wrap> _wrap;
|
||||
std::unique_ptr<Controller> _controller;
|
||||
object_ptr<ContentWidget> _content = { nullptr };
|
||||
|
|
305
Telegram/SourceFiles/info/media/info_media_buttons.cpp
Normal file
305
Telegram/SourceFiles/info/media/info_media_buttons.cpp
Normal 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
|
|
@ -7,220 +7,73 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
*/
|
||||
#pragma once
|
||||
|
||||
#include <rpl/mappers.h>
|
||||
#include <rpl/map.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 "history/view/history_view_sublist_section.h"
|
||||
#include "info/info_memento.h"
|
||||
#include "info/info_controller.h"
|
||||
#include "info/profile/info_profile_values.h"
|
||||
#include "info/stories/info_stories_widget.h"
|
||||
#include "ui/wrap/slide_wrap.h"
|
||||
#include "ui/wrap/vertical_layout.h"
|
||||
#include "ui/widgets/buttons.h"
|
||||
#include "window/window_session_controller.h"
|
||||
#include "data/data_channel.h"
|
||||
#include "data/data_user.h"
|
||||
#include "styles/style_info.h"
|
||||
|
||||
namespace Ui {
|
||||
class AbstractButton;
|
||||
class MultiSlideTracker;
|
||||
class SettingsButton;
|
||||
class VerticalLayout;
|
||||
template <typename Widget>
|
||||
class SlideWrap;
|
||||
} // namespace Ui
|
||||
|
||||
namespace Window {
|
||||
class SessionNavigation;
|
||||
} // namespace Window
|
||||
|
||||
namespace Info::Media {
|
||||
|
||||
using Type = Storage::SharedMediaType;
|
||||
|
||||
inline 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()");
|
||||
};
|
||||
[[nodiscard]] tr::phrase<lngtag_count> MediaTextPhrase(Type type);
|
||||
|
||||
inline auto MediaText(Type type) {
|
||||
return [phrase = MediaTextPhrase(type)](int count) {
|
||||
return phrase(tr::now, lt_count, count);
|
||||
};
|
||||
}
|
||||
[[nodiscard]] Fn<QString(int)> MediaText(Type type);
|
||||
|
||||
template <typename Count, typename Text>
|
||||
inline auto AddCountedButton(
|
||||
Ui::VerticalLayout *parent,
|
||||
Count &&count,
|
||||
Text &&textFromCount,
|
||||
Ui::MultiSlideTracker &tracker) {
|
||||
using namespace rpl::mappers;
|
||||
[[nodiscard]] 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(_1 > 0)
|
||||
);
|
||||
tracker.track(button);
|
||||
return button;
|
||||
};
|
||||
[[nodiscard]] 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);
|
||||
|
||||
inline auto 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();
|
||||
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;
|
||||
};
|
||||
[[nodiscard]] not_null<Ui::SettingsButton*> AddCommonGroupsButton(
|
||||
Ui::VerticalLayout *parent,
|
||||
not_null<Window::SessionNavigation*> navigation,
|
||||
not_null<UserData*> user,
|
||||
Ui::MultiSlideTracker &tracker);
|
||||
|
||||
inline auto 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;
|
||||
}
|
||||
[[nodiscard]] not_null<Ui::SettingsButton*> AddSimilarPeersButton(
|
||||
Ui::VerticalLayout *parent,
|
||||
not_null<Window::SessionNavigation*> navigation,
|
||||
not_null<PeerData*> peer,
|
||||
Ui::MultiSlideTracker &tracker);
|
||||
|
||||
inline auto 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;
|
||||
}
|
||||
[[nodiscard]] not_null<Ui::SettingsButton*> AddStoriesButton(
|
||||
Ui::VerticalLayout *parent,
|
||||
not_null<Window::SessionNavigation*> navigation,
|
||||
not_null<PeerData*> peer,
|
||||
Ui::MultiSlideTracker &tracker);
|
||||
|
||||
inline auto 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;
|
||||
}
|
||||
[[nodiscard]] not_null<Ui::SettingsButton*> AddSavedSublistButton(
|
||||
Ui::VerticalLayout *parent,
|
||||
not_null<Window::SessionNavigation*> navigation,
|
||||
not_null<PeerData*> peer,
|
||||
Ui::MultiSlideTracker &tracker);
|
||||
|
||||
inline auto 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;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
[[nodiscard]] not_null<Ui::SettingsButton*> AddPeerGiftsButton(
|
||||
Ui::VerticalLayout *parent,
|
||||
not_null<Window::SessionNavigation*> navigation,
|
||||
not_null<PeerData*> peer,
|
||||
Ui::MultiSlideTracker &tracker);
|
||||
|
||||
} // namespace Info::Media
|
||||
|
|
|
@ -15,10 +15,12 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
#include "info/profile/info_profile_icon.h"
|
||||
#include "info/info_controller.h"
|
||||
#include "data/data_forum_topic.h"
|
||||
#include "data/data_peer.h"
|
||||
#include "ui/widgets/discrete_sliders.h"
|
||||
#include "ui/widgets/shadow.h"
|
||||
#include "ui/widgets/buttons.h"
|
||||
#include "ui/widgets/box_content_divider.h"
|
||||
#include "ui/wrap/slide_wrap.h"
|
||||
#include "ui/wrap/vertical_layout.h"
|
||||
#include "ui/search_field_controller.h"
|
||||
#include "styles/style_info.h"
|
||||
|
|
|
@ -343,11 +343,11 @@ void InnerWidget::validateButtons() {
|
|||
}
|
||||
const auto giftId = _entries[index].gift.info.id;
|
||||
const auto manageId = _entries[index].gift.manageId;
|
||||
const auto &descriptor = _entries[index].descriptor;
|
||||
const auto already = ranges::find(_views, giftId, &View::giftId);
|
||||
if (already != end(_views)) {
|
||||
views.push_back(base::take(*already));
|
||||
} else {
|
||||
const auto &descriptor = _entries[index].descriptor;
|
||||
const auto unused = ranges::find_if(_views, [&](const View &v) {
|
||||
return v.button && !idUsed(v.giftId, column, row);
|
||||
});
|
||||
|
@ -358,16 +358,16 @@ void InnerWidget::validateButtons() {
|
|||
button->show();
|
||||
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;
|
||||
};
|
||||
for (auto j = fromRow; j != tillRow; ++j) {
|
||||
|
@ -630,24 +630,26 @@ void Widget::fillTopBarMenu(const Ui::Menu::MenuCallback &addAction) {
|
|||
});
|
||||
}, 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), [=] {
|
||||
change([](Filter &filter) {
|
||||
filter.skipSaved = !filter.skipSaved;
|
||||
if (filter.skipSaved && filter.skipUnsaved) {
|
||||
filter.skipUnsaved = false;
|
||||
}
|
||||
});
|
||||
}, filter.skipSaved ? nullptr : &st::mediaPlayerMenuCheck);
|
||||
addAction(tr::lng_peer_gifts_filter_unsaved(tr::now), [=] {
|
||||
change([](Filter &filter) {
|
||||
filter.skipUnsaved = !filter.skipUnsaved;
|
||||
if (filter.skipSaved && filter.skipUnsaved) {
|
||||
filter.skipSaved = false;
|
||||
}
|
||||
});
|
||||
}, filter.skipUnsaved ? nullptr : &st::mediaPlayerMenuCheck);
|
||||
addAction(tr::lng_peer_gifts_filter_saved(tr::now), [=] {
|
||||
change([](Filter &filter) {
|
||||
filter.skipSaved = !filter.skipSaved;
|
||||
if (filter.skipSaved && filter.skipUnsaved) {
|
||||
filter.skipUnsaved = false;
|
||||
}
|
||||
});
|
||||
}, filter.skipSaved ? nullptr : &st::mediaPlayerMenuCheck);
|
||||
addAction(tr::lng_peer_gifts_filter_unsaved(tr::now), [=] {
|
||||
change([](Filter &filter) {
|
||||
filter.skipUnsaved = !filter.skipUnsaved;
|
||||
if (filter.skipSaved && filter.skipUnsaved) {
|
||||
filter.skipSaved = false;
|
||||
}
|
||||
});
|
||||
}, filter.skipUnsaved ? nullptr : &st::mediaPlayerMenuCheck);
|
||||
}
|
||||
}
|
||||
|
||||
rpl::producer<QString> Widget::title() {
|
||||
|
|
|
@ -44,6 +44,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
#include "dialogs/ui/dialogs_message_view.h"
|
||||
#include "history/history.h"
|
||||
#include "history/history_item.h"
|
||||
#include "history/history_item_components.h"
|
||||
#include "history/history_item_helpers.h"
|
||||
#include "history/view/history_view_item_preview.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/report_box_graphics.h"
|
||||
#include "ui/controls/userpic_button.h"
|
||||
#include "ui/effects/toggle_arrow.h"
|
||||
#include "ui/painter.h"
|
||||
#include "ui/rect.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_menu_icons.h"
|
||||
#include "styles/style_settings.h" // settingsButtonRightSkip.
|
||||
#include "styles/style_window.h" // mainMenuToggleFourStrokes.
|
||||
|
||||
#include <QtGui/QGuiApplication>
|
||||
#include <QtGui/QClipboard>
|
||||
|
@ -516,6 +519,8 @@ base::options::toggle ShowPeerIdBelowAbout({
|
|||
dayHoursTextValue(state->day.value())
|
||||
) | rpl::after_next(recount),
|
||||
st::infoHoursValue);
|
||||
const auto timingArrow = Ui::CreateChild<Ui::RpWidget>(openedWrap);
|
||||
timingArrow->resize(Size(timing->st().style.font->height));
|
||||
timing->setAttribute(Qt::WA_TransparentForMouseEvents);
|
||||
state->opened.value() | rpl::start_with_next([=](bool value) {
|
||||
opened->setTextColorOverride(value
|
||||
|
@ -529,7 +534,8 @@ base::options::toggle ShowPeerIdBelowAbout({
|
|||
timing->sizeValue()
|
||||
) | rpl::start_with_next([=](int width, int h1, QSize size) {
|
||||
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 added = margins.top() + margins.bottom();
|
||||
|
@ -542,10 +548,7 @@ base::options::toggle ShowPeerIdBelowAbout({
|
|||
tr::lng_info_hours_label(),
|
||||
st::infoLabel);
|
||||
label->setAttribute(Qt::WA_TransparentForMouseEvents);
|
||||
const auto link = Ui::CreateChild<Ui::LinkButton>(
|
||||
labelWrap,
|
||||
QString());
|
||||
rpl::combine(
|
||||
auto linkText = rpl::combine(
|
||||
state->nonTrivial.value(),
|
||||
state->hours.value(),
|
||||
state->mine.value(),
|
||||
|
@ -560,10 +563,12 @@ base::options::toggle ShowPeerIdBelowAbout({
|
|||
: my
|
||||
? tr::lng_info_hours_my_time()
|
||||
: tr::lng_info_hours_local_time();
|
||||
}) | rpl::flatten_latest(
|
||||
) | rpl::start_with_next([=](const QString &text) {
|
||||
link->setText(text);
|
||||
}, link->lifetime());
|
||||
}) | rpl::flatten_latest();
|
||||
const auto link = Ui::CreateChild<Ui::RoundButton>(
|
||||
labelWrap,
|
||||
std::move(linkText),
|
||||
st::defaultTableSmallButton);
|
||||
link->setTextTransform(Ui::RoundButton::TextTransform::NoTransform);
|
||||
link->setClickedCallback([=] {
|
||||
state->myTimezone = !state->myTimezone.current();
|
||||
state->expanded = true;
|
||||
|
@ -587,6 +592,38 @@ base::options::toggle ShowPeerIdBelowAbout({
|
|||
inner,
|
||||
object_ptr<Ui::VerticalLayout>(inner)));
|
||||
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();
|
||||
const auto days = other->entity();
|
||||
|
||||
|
@ -1729,6 +1766,10 @@ object_ptr<Ui::RpWidget> DetailsFiller::setupPersonalChannel(
|
|||
&& user->personalChannelMessageId();
|
||||
}));
|
||||
messageChannelWrap->finishAnimating();
|
||||
messageChannelWrap->toggledValue(
|
||||
) | rpl::filter(rpl::mappers::_1) | rpl::start_with_next([=] {
|
||||
messageChannelWrap->resizeToWidth(messageChannelWrap->width());
|
||||
}, messageChannelWrap->lifetime());
|
||||
|
||||
const auto clear = [=] {
|
||||
while (messageChannelWrap->entity()->count()) {
|
||||
|
@ -1825,12 +1866,12 @@ object_ptr<Ui::RpWidget> DetailsFiller::setupPersonalChannel(
|
|||
}
|
||||
}, preview->lifetime());
|
||||
|
||||
line->sizeValue(
|
||||
line->sizeValue() | rpl::filter_size(
|
||||
) | rpl::start_with_next([=](const QSize &size) {
|
||||
const auto left = stLabeled.left();
|
||||
const auto right = st::infoPersonalChannelDateSkip;
|
||||
const auto top = stLabeled.top();
|
||||
date->moveToRight(right, top);
|
||||
date->moveToRight(right, top, size.width());
|
||||
|
||||
name->resizeToWidth(size.width()
|
||||
- left
|
||||
|
@ -1861,14 +1902,9 @@ object_ptr<Ui::RpWidget> DetailsFiller::setupPersonalChannel(
|
|||
st::infoProfileLabeledPadding.bottom()));
|
||||
}
|
||||
{
|
||||
const auto button = Ui::CreateChild<Ui::RippleButton>(
|
||||
const auto button = Ui::CreateSimpleRectButton(
|
||||
messageChannelWrap->entity(),
|
||||
st::defaultRippleAnimation);
|
||||
button->paintRequest(
|
||||
) | rpl::start_with_next([=](const QRect &rect) {
|
||||
auto p = QPainter(button);
|
||||
button->paintRipple(p, 0, 0);
|
||||
}, button->lifetime());
|
||||
inner->geometryValue(
|
||||
) | rpl::start_with_next([=](const QRect &rect) {
|
||||
button->setGeometry(rect);
|
||||
|
@ -1880,6 +1916,8 @@ object_ptr<Ui::RpWidget> DetailsFiller::setupPersonalChannel(
|
|||
msg);
|
||||
});
|
||||
button->lower();
|
||||
inner->lifetime().make_state<base::unique_qptr<Ui::RpWidget>>(
|
||||
button);
|
||||
}
|
||||
inner->setAttribute(Qt::WA_TransparentForMouseEvents);
|
||||
Ui::AddSkip(messageChannelWrap->entity());
|
||||
|
@ -2445,7 +2483,7 @@ void ActionsFiller::addDeleteContactAction(not_null<UserData*> user) {
|
|||
void ActionsFiller::addFastButtonsMode(not_null<UserData*> user) {
|
||||
Expects(user->isBot());
|
||||
|
||||
const auto helper = &user->session().supportHelper();
|
||||
const auto bots = &user->session().fastButtonsBots();
|
||||
const auto button = _wrap->add(object_ptr<Ui::SettingsButton>(
|
||||
_wrap,
|
||||
rpl::single(u"Fast buttons mode"_q),
|
||||
|
@ -2459,17 +2497,17 @@ void ActionsFiller::addFastButtonsMode(not_null<UserData*> user) {
|
|||
AddDivider(_wrap);
|
||||
AddSkip(_wrap);
|
||||
|
||||
button->toggleOn(helper->fastButtonModeValue(user));
|
||||
button->toggleOn(bots->enabledValue(user));
|
||||
button->toggledValue(
|
||||
) | rpl::filter([=](bool value) {
|
||||
return value != helper->fastButtonMode(user);
|
||||
return value != bots->enabled(user);
|
||||
}) | rpl::start_with_next([=](bool value) {
|
||||
helper->setFastButtonMode(user, value);
|
||||
bots->setEnabled(user, value);
|
||||
}, button->lifetime());
|
||||
}
|
||||
|
||||
void ActionsFiller::addBotCommandActions(not_null<UserData*> user) {
|
||||
if (user->session().supportMode()) {
|
||||
if (FastButtonsMode()) {
|
||||
addFastButtonsMode(user);
|
||||
}
|
||||
const auto window = _controller->parentController();
|
||||
|
|
|
@ -7,43 +7,32 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
*/
|
||||
#include "info/profile/info_profile_inner_widget.h"
|
||||
|
||||
#include "info/info_memento.h"
|
||||
#include "info/info_controller.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_icon.h"
|
||||
#include "info/profile/info_profile_members.h"
|
||||
#include "info/profile/info_profile_actions.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_channel.h"
|
||||
#include "data/data_forum_topic.h"
|
||||
#include "data/data_peer.h"
|
||||
#include "data/data_photo.h"
|
||||
#include "data/data_file_origin.h"
|
||||
#include "ui/boxes/confirm_box.h"
|
||||
#include "mainwidget.h"
|
||||
#include "data/data_user.h"
|
||||
#include "main/main_session.h"
|
||||
#include "apiwrap.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 "ui/widgets/buttons.h"
|
||||
#include "ui/widgets/checkbox.h"
|
||||
#include "ui/widgets/scroll_area.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/slide_wrap.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_boxes.h"
|
||||
|
||||
// AyuGram includes
|
||||
#include "ayu/ayu_settings.h"
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue