Merge tag 'v5.11.1' into dev

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

35
.devcontainer.json Normal file
View file

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

View file

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

1
.gitignore vendored
View file

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

View file

@ -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

View file

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 447 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 671 B

Binary file not shown.

After

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

View file

@ -642,6 +642,46 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_settings_chat_quick_action_react" = "Send reaction with double click";
"lng_settings_chat_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";

View file

@ -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>

View file

@ -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"

View file

@ -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"

View file

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

View file

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

View file

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

View file

@ -13,6 +13,32 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
namespace Api {
PeerId ParsePaidReactionShownPeer(
not_null<Main::Session*> session,
const MTPPaidReactionPrivacy &value) {
return value.match([&](const MTPDpaidReactionPrivacyDefault &) {
return session->userPeerId();
}, [](const MTPDpaidReactionPrivacyAnonymous &) {
return PeerId();
}, [&](const MTPDpaidReactionPrivacyPeer &data) {
return data.vpeer().match([&](const MTPDinputPeerSelf &) {
return session->userPeerId();
}, [](const MTPDinputPeerUser &data) {
return peerFromUser(data.vuser_id());
}, [](const MTPDinputPeerChat &data) {
return peerFromChat(data.vchat_id());
}, [](const MTPDinputPeerChannel &data) {
return peerFromChannel(data.vchannel_id());
}, [](const MTPDinputPeerUserFromMessage &data) -> PeerId {
Unexpected("From message peer in ParsePaidReactionShownPeer.");
}, [](const MTPDinputPeerChannelFromMessage &data) -> PeerId {
Unexpected("From message peer in ParsePaidReactionShownPeer.");
}, [](const MTPDinputPeerEmpty &) -> PeerId {
Unexpected("Empty peer in ParsePaidReactionShownPeer.");
});
});
}
GlobalPrivacy::GlobalPrivacy(not_null<ApiWrap*> api)
: _session(&api->session())
, _api(&api->instance()) {
@ -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) {

View file

@ -23,6 +23,10 @@ enum class UnarchiveOnNewMessage {
AnyUnmuted,
};
[[nodiscard]] PeerId ParsePaidReactionShownPeer(
not_null<Main::Session*> session,
const MTPPaidReactionPrivacy &value);
class GlobalPrivacy final {
public:
explicit GlobalPrivacy(not_null<ApiWrap*> api);
@ -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;
};

View file

@ -111,7 +111,8 @@ MTPInputMedia PrepareUploadedDocument(
| (info.thumb ? Flag::f_thumb : Flag())
| (item->groupId() ? Flag::f_nosound_video : Flag())
| (info.attachedStickers.empty() ? Flag::f_stickers : Flag())
| (ttlSeconds ? Flag::f_ttl_seconds : Flag());
| (ttlSeconds ? Flag::f_ttl_seconds : Flag())
| (info.videoCover ? Flag::f_video_cover : Flag());
const auto document = item->media()->document();
return MTP_inputMediaUploadedDocument(
MTP_flags(flags),
@ -121,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));
}

View file

@ -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) {

View file

@ -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;
}

View file

@ -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;

View file

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

View file

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

View file

@ -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(),

View file

@ -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);

View file

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

View file

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

View file

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

View file

@ -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,

View file

@ -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([=]{

View file

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

View file

@ -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);

View file

@ -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;

View file

@ -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,

View file

@ -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;
}

View file

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

View file

@ -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;

View file

@ -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;

View file

@ -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) {

View file

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

View file

@ -61,6 +61,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "settings/settings_privacy_security.h"
#include "settings/settings_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

View file

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

View file

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

View file

@ -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

View file

@ -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;

View file

@ -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)

View file

@ -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;
}

View file

@ -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);

View file

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

View file

@ -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;
}

View file

@ -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 {

View file

@ -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() {

View file

@ -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;

View file

@ -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);

View file

@ -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(

View file

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

View file

@ -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

View file

@ -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;

View file

@ -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);

View file

@ -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) {

View file

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

View file

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

View file

@ -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)

View file

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

View file

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

View file

@ -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,

View file

@ -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(

View file

@ -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;
}

View file

@ -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;

View file

@ -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) {

View file

@ -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(

View file

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

View file

@ -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;

View file

@ -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 &params) {
_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 &params) {
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 &params) {
_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;

View file

@ -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 &params);
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 &params = {});
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 &params = {});
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 {

View file

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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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);

View file

@ -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,

View file

@ -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;

View file

@ -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(),

View file

@ -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;

View file

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

View file

@ -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

View file

@ -19,6 +19,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "media/streaming/media_streaming_instance.h"
#include "media/streaming/media_streaming_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();
}
}

View file

@ -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;
};

View file

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

View file

@ -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,

View file

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

View file

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

View file

@ -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(

View file

@ -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 };

View file

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

View file

@ -7,220 +7,73 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#pragma once
#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

View file

@ -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"

View file

@ -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() {

View file

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

View file

@ -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