diff --git a/.github/workflows/mac_packaged.yml b/.github/workflows/mac_packaged.yml
index 631ff2a70..51ba9917a 100644
--- a/.github/workflows/mac_packaged.yml
+++ b/.github/workflows/mac_packaged.yml
@@ -73,6 +73,7 @@ jobs:
sudo xcode-select -s /Applications/Xcode.app/Contents/Developer
xcodebuild -version > CACHE_KEY.txt
+ brew list --versions >> CACHE_KEY.txt
echo $MANUAL_CACHING >> CACHE_KEY.txt
echo "$GITHUB_WORKSPACE" >> CACHE_KEY.txt
if [ "$AUTO_CACHING" = "1" ]; then
diff --git a/Telegram/CMakeLists.txt b/Telegram/CMakeLists.txt
index 154b4a02c..462e737c6 100644
--- a/Telegram/CMakeLists.txt
+++ b/Telegram/CMakeLists.txt
@@ -478,6 +478,8 @@ PRIVATE
chat_helpers/tabbed_section.h
chat_helpers/tabbed_selector.cpp
chat_helpers/tabbed_selector.h
+ chat_helpers/ttl_media_layer_widget.cpp
+ chat_helpers/ttl_media_layer_widget.h
core/application.cpp
core/application.h
core/base_integration.cpp
@@ -681,6 +683,8 @@ PRIVATE
dialogs/dialogs_row.h
dialogs/dialogs_search_from_controllers.cpp
dialogs/dialogs_search_from_controllers.h
+ dialogs/dialogs_search_tags.cpp
+ dialogs/dialogs_search_tags.h
dialogs/dialogs_widget.cpp
dialogs/dialogs_widget.h
dialogs/ui/dialogs_layout.cpp
diff --git a/Telegram/Resources/art/ttl/video_message_icon.svg b/Telegram/Resources/art/ttl/video_message_icon.svg
new file mode 100644
index 000000000..aeadc8385
--- /dev/null
+++ b/Telegram/Resources/art/ttl/video_message_icon.svg
@@ -0,0 +1,4 @@
+
+
diff --git a/Telegram/Resources/icons/chat/audio_once.png b/Telegram/Resources/icons/chat/audio_once.png
new file mode 100644
index 000000000..4d1f93652
Binary files /dev/null and b/Telegram/Resources/icons/chat/audio_once.png differ
diff --git a/Telegram/Resources/icons/chat/audio_once@2x.png b/Telegram/Resources/icons/chat/audio_once@2x.png
new file mode 100644
index 000000000..f66cfbbcb
Binary files /dev/null and b/Telegram/Resources/icons/chat/audio_once@2x.png differ
diff --git a/Telegram/Resources/icons/chat/audio_once@3x.png b/Telegram/Resources/icons/chat/audio_once@3x.png
new file mode 100644
index 000000000..bce2481c5
Binary files /dev/null and b/Telegram/Resources/icons/chat/audio_once@3x.png differ
diff --git a/Telegram/Resources/icons/chat/mini_media_once.png b/Telegram/Resources/icons/chat/mini_media_once.png
new file mode 100644
index 000000000..8984491b7
Binary files /dev/null and b/Telegram/Resources/icons/chat/mini_media_once.png differ
diff --git a/Telegram/Resources/icons/chat/mini_media_once@2x.png b/Telegram/Resources/icons/chat/mini_media_once@2x.png
new file mode 100644
index 000000000..f8514d3af
Binary files /dev/null and b/Telegram/Resources/icons/chat/mini_media_once@2x.png differ
diff --git a/Telegram/Resources/icons/chat/mini_media_once@3x.png b/Telegram/Resources/icons/chat/mini_media_once@3x.png
new file mode 100644
index 000000000..3900061be
Binary files /dev/null and b/Telegram/Resources/icons/chat/mini_media_once@3x.png differ
diff --git a/Telegram/Resources/icons/voice_lock/audio_once_bg.png b/Telegram/Resources/icons/voice_lock/audio_once_bg.png
new file mode 100644
index 000000000..62778676c
Binary files /dev/null and b/Telegram/Resources/icons/voice_lock/audio_once_bg.png differ
diff --git a/Telegram/Resources/icons/voice_lock/audio_once_bg@2x.png b/Telegram/Resources/icons/voice_lock/audio_once_bg@2x.png
new file mode 100644
index 000000000..225d6af3b
Binary files /dev/null and b/Telegram/Resources/icons/voice_lock/audio_once_bg@2x.png differ
diff --git a/Telegram/Resources/icons/voice_lock/audio_once_bg@3x.png b/Telegram/Resources/icons/voice_lock/audio_once_bg@3x.png
new file mode 100644
index 000000000..1d82b2d6e
Binary files /dev/null and b/Telegram/Resources/icons/voice_lock/audio_once_bg@3x.png differ
diff --git a/Telegram/Resources/icons/voice_lock/audio_once_number.png b/Telegram/Resources/icons/voice_lock/audio_once_number.png
new file mode 100644
index 000000000..61e22f105
Binary files /dev/null and b/Telegram/Resources/icons/voice_lock/audio_once_number.png differ
diff --git a/Telegram/Resources/icons/voice_lock/audio_once_number@2x.png b/Telegram/Resources/icons/voice_lock/audio_once_number@2x.png
new file mode 100644
index 000000000..15047cb48
Binary files /dev/null and b/Telegram/Resources/icons/voice_lock/audio_once_number@2x.png differ
diff --git a/Telegram/Resources/icons/voice_lock/audio_once_number@3x.png b/Telegram/Resources/icons/voice_lock/audio_once_number@3x.png
new file mode 100644
index 000000000..6e39511e1
Binary files /dev/null and b/Telegram/Resources/icons/voice_lock/audio_once_number@3x.png differ
diff --git a/Telegram/Resources/icons/voice_lock/recorded_delete.png b/Telegram/Resources/icons/voice_lock/recorded_delete.png
new file mode 100644
index 000000000..f204f8f22
Binary files /dev/null and b/Telegram/Resources/icons/voice_lock/recorded_delete.png differ
diff --git a/Telegram/Resources/icons/voice_lock/recorded_delete@2x.png b/Telegram/Resources/icons/voice_lock/recorded_delete@2x.png
new file mode 100644
index 000000000..8bebe1a61
Binary files /dev/null and b/Telegram/Resources/icons/voice_lock/recorded_delete@2x.png differ
diff --git a/Telegram/Resources/icons/voice_lock/recorded_delete@3x.png b/Telegram/Resources/icons/voice_lock/recorded_delete@3x.png
new file mode 100644
index 000000000..db0067c53
Binary files /dev/null and b/Telegram/Resources/icons/voice_lock/recorded_delete@3x.png differ
diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings
index 97b30588e..c6570fe5b 100644
--- a/Telegram/Resources/langs/lang.strings
+++ b/Telegram/Resources/langs/lang.strings
@@ -1719,6 +1719,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_ttl_voice_expired" = "Voice message expired";
"lng_ttl_round_sent" = "You sent a self-destructing video message.";
"lng_ttl_round_expired" = "Round message expired";
+"lng_ttl_voice_tooltip_in" = "This voice message can only be played once.";
+"lng_ttl_voice_tooltip_out" = "This message will disappear once **{user}** plays it once.";
+"lng_ttl_voice_close_in" = "Delete and close";
+"lng_ttl_round_tooltip_in" = "This video message can only be played once.";
+"lng_ttl_round_tooltip_out" = "This message will disappear once **{user}** plays it once.";
"lng_profile_add_more_after_create" = "You will be able to add more members after you create the group.";
"lng_profile_camera_title" = "Capture yourself";
@@ -2487,6 +2492,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_record_listen_cancel_sure" = "Are you sure you want to discard your recorded voice message?";
"lng_record_lock_discard" = "Discard";
"lng_record_hold_tip" = "Please hold the mouse button pressed to record a voice message.";
+"lng_record_once_first_tooltip" = "Tap to set this message to **Play Once**.";
+"lng_record_once_active_tooltip" = "The recipients will be able to listen to it only once.";
"lng_will_be_notified" = "Members will be notified when you post";
"lng_wont_be_notified" = "Members will not be notified when you post";
"lng_willbe_history" = "Please select a chat to start messaging";
@@ -2749,7 +2756,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_context_seen_reacted#other" = "{count} Reacted";
"lng_context_seen_reacted_none" = "Nobody Reacted";
"lng_context_seen_reacted_all" = "Show All Reactions";
-"lng_context_set_as_quick" = "Set As Quick";
+"lng_context_set_as_quick" = "Set as Quick";
+"lng_context_filter_by_tag" = "Filter by Tag";
+"lng_context_remove_tag" = "Remove Tag";
"lng_context_delete_from_disk" = "Delete from disk";
"lng_context_delete_all_files" = "Delete all files";
"lng_context_save_custom_sound" = "Save for notifications";
@@ -4346,6 +4355,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_stories_views#one" = "{count} view";
"lng_stories_views#other" = "{count} views";
"lng_stories_no_views" = "No views";
+"lng_stories_view_reactions" = "View reactions";
"lng_stories_unsupported" = "This story is not supported\nby your version of Telegram.";
"lng_stories_cant_reply" = "You can't reply to this story.";
"lng_stories_about_silent" = "This video has no sound.";
diff --git a/Telegram/Resources/qrc/telegram/telegram.qrc b/Telegram/Resources/qrc/telegram/telegram.qrc
index f2f085027..38e9f48e4 100644
--- a/Telegram/Resources/qrc/telegram/telegram.qrc
+++ b/Telegram/Resources/qrc/telegram/telegram.qrc
@@ -26,6 +26,7 @@
../../art/recording/recording_info_audio.svg
../../art/recording/recording_info_video_landscape.svg
../../art/recording/recording_info_video_portrait.svg
+ ../../art/ttl/video_message_icon.svg
../../icons/settings/dino.svg
../../icons/settings/star.svg
../../icons/settings/starmini.svg
diff --git a/Telegram/Resources/uwp/AppX/AppxManifest.xml b/Telegram/Resources/uwp/AppX/AppxManifest.xml
index bc517dc13..732d62eb2 100644
--- a/Telegram/Resources/uwp/AppX/AppxManifest.xml
+++ b/Telegram/Resources/uwp/AppX/AppxManifest.xml
@@ -10,7 +10,7 @@
+ Version="4.14.6.0" />
Telegram Desktop
Telegram Messenger LLP
diff --git a/Telegram/Resources/winrc/Telegram.rc b/Telegram/Resources/winrc/Telegram.rc
index f30626251..169be97b2 100644
--- a/Telegram/Resources/winrc/Telegram.rc
+++ b/Telegram/Resources/winrc/Telegram.rc
@@ -44,8 +44,8 @@ IDI_ICON1 ICON "..\\art\\icon256.ico"
//
VS_VERSION_INFO VERSIONINFO
- FILEVERSION 4,14,3,0
- PRODUCTVERSION 4,14,3,0
+ FILEVERSION 4,14,6,0
+ PRODUCTVERSION 4,14,6,0
FILEFLAGSMASK 0x3fL
#ifdef _DEBUG
FILEFLAGS 0x1L
@@ -62,10 +62,10 @@ BEGIN
BEGIN
VALUE "CompanyName", "Radolyn Labs"
VALUE "FileDescription", "AyuGram Desktop"
- VALUE "FileVersion", "4.14.3.0"
+ VALUE "FileVersion", "4.14.6.0"
VALUE "LegalCopyright", "Copyright (C) 2014-2024"
VALUE "ProductName", "AyuGram Desktop"
- VALUE "ProductVersion", "4.14.3.0"
+ VALUE "ProductVersion", "4.14.6.0"
END
END
BLOCK "VarFileInfo"
diff --git a/Telegram/Resources/winrc/Updater.rc b/Telegram/Resources/winrc/Updater.rc
index 014686458..6aca907d9 100644
--- a/Telegram/Resources/winrc/Updater.rc
+++ b/Telegram/Resources/winrc/Updater.rc
@@ -35,8 +35,8 @@ LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
//
VS_VERSION_INFO VERSIONINFO
- FILEVERSION 4,14,3,0
- PRODUCTVERSION 4,14,3,0
+ FILEVERSION 4,14,6,0
+ PRODUCTVERSION 4,14,6,0
FILEFLAGSMASK 0x3fL
#ifdef _DEBUG
FILEFLAGS 0x1L
@@ -53,10 +53,10 @@ BEGIN
BEGIN
VALUE "CompanyName", "Radolyn Labs"
VALUE "FileDescription", "AyuGram Desktop Updater"
- VALUE "FileVersion", "4.14.3.0"
+ VALUE "FileVersion", "4.14.6.0"
VALUE "LegalCopyright", "Copyright (C) 2014-2024"
VALUE "ProductName", "AyuGram Desktop"
- VALUE "ProductVersion", "4.14.3.0"
+ VALUE "ProductVersion", "4.14.6.0"
END
END
BLOCK "VarFileInfo"
diff --git a/Telegram/SourceFiles/api/api_messages_search.cpp b/Telegram/SourceFiles/api/api_messages_search.cpp
index 15119b906..cc97e22c0 100644
--- a/Telegram/SourceFiles/api/api_messages_search.cpp
+++ b/Telegram/SourceFiles/api/api_messages_search.cpp
@@ -91,6 +91,7 @@ void MessagesSearch::searchRequest() {
? _from->input
: MTP_inputPeerEmpty()),
MTPInputPeer(), // saved_peer_id
+ MTPVector(), // saved_reaction
MTPint(), // top_msg_id
MTP_inputMessagesFilterEmpty(),
MTP_int(0), // min_date
diff --git a/Telegram/SourceFiles/api/api_sending.cpp b/Telegram/SourceFiles/api/api_sending.cpp
index d971a03f4..0e5d7c585 100644
--- a/Telegram/SourceFiles/api/api_sending.cpp
+++ b/Telegram/SourceFiles/api/api_sending.cpp
@@ -402,7 +402,6 @@ void SendConfirmedFile(
flags |= MessageFlag::HasReplyInfo;
}
const auto anonymousPost = peer->amAnonymous();
- const auto silentPost = ShouldSendSilent(peer, file->to.options);
FillMessagePostFlags(action, peer, flags);
if (file->to.options.scheduled) {
flags |= MessageFlag::IsOrWasScheduled;
diff --git a/Telegram/SourceFiles/api/api_updates.cpp b/Telegram/SourceFiles/api/api_updates.cpp
index 897a78d72..c862cc9ee 100644
--- a/Telegram/SourceFiles/api/api_updates.cpp
+++ b/Telegram/SourceFiles/api/api_updates.cpp
@@ -2540,6 +2540,10 @@ void Updates::feedUpdate(const MTPUpdate &update) {
session().data().reactions().refreshRecentDelayed();
} break;
+ case mtpc_updateSavedReactionTags: {
+ session().data().reactions().refreshMyTagsDelayed();
+ } break;
+
////// Cloud saved GIFs
case mtpc_updateSavedGifs: {
session().data().stickers().setLastSavedGifsUpdate(0);
diff --git a/Telegram/SourceFiles/boxes/auto_download_box.cpp b/Telegram/SourceFiles/boxes/auto_download_box.cpp
index ae42f3cc8..3617a9eb6 100644
--- a/Telegram/SourceFiles/boxes/auto_download_box.cpp
+++ b/Telegram/SourceFiles/boxes/auto_download_box.cpp
@@ -162,7 +162,7 @@ void AutoDownloadBox::setupContent() {
*downloadValues,
*autoPlayValues);
auto allowMore = values | ranges::views::filter([&](Pair pair) {
- const auto [type, enabled] = pair;
+ const auto &[type, enabled] = pair;
const auto value = enabled ? limitByType(type) : 0;
const auto old = settings->bytesLimit(_source, type);
return (old < value);
@@ -170,7 +170,7 @@ void AutoDownloadBox::setupContent() {
return pair.first;
});
const auto less = ranges::any_of(*autoPlayValues, [&](Pair pair) {
- const auto [type, enabled] = pair;
+ const auto &[type, enabled] = pair;
const auto value = enabled ? limitByType(type) : 0;
return value < settings->bytesLimit(_source, type);
});
@@ -179,7 +179,7 @@ void AutoDownloadBox::setupContent() {
allowMore.end());
const auto changed = ranges::any_of(values, [&](Pair pair) {
- const auto [type, enabled] = pair;
+ const auto &[type, enabled] = pair;
const auto value = enabled ? limitByType(type) : 0;
return value != settings->bytesLimit(_source, type);
});
diff --git a/Telegram/SourceFiles/boxes/connection_box.cpp b/Telegram/SourceFiles/boxes/connection_box.cpp
index 3bdaa932d..4403f56fa 100644
--- a/Telegram/SourceFiles/boxes/connection_box.cpp
+++ b/Telegram/SourceFiles/boxes/connection_box.cpp
@@ -748,7 +748,7 @@ void ProxiesBox::applyView(View &&view) {
const auto wrap = _wrap
? _wrap.data()
: _initialWrap.data();
- const auto [i, ok] = _rows.emplace(id, nullptr);
+ const auto &[i, ok] = _rows.emplace(id, nullptr);
i->second.reset(wrap->insert(
0,
object_ptr(
diff --git a/Telegram/SourceFiles/boxes/language_box.cpp b/Telegram/SourceFiles/boxes/language_box.cpp
index ab709a87d..aa69a6fee 100644
--- a/Telegram/SourceFiles/boxes/language_box.cpp
+++ b/Telegram/SourceFiles/boxes/language_box.cpp
@@ -1121,7 +1121,7 @@ void LanguageBox::prepare() {
using namespace rpl::mappers;
- const auto [recent, official] = PrepareLists();
+ const auto &[recent, official] = PrepareLists();
const auto inner = setInnerWidget(
object_ptr(this, recent, official),
st::boxScroll,
diff --git a/Telegram/SourceFiles/boxes/share_box.cpp b/Telegram/SourceFiles/boxes/share_box.cpp
index d1b3dbb37..007355317 100644
--- a/Telegram/SourceFiles/boxes/share_box.cpp
+++ b/Telegram/SourceFiles/boxes/share_box.cpp
@@ -882,7 +882,7 @@ auto ShareBox::Inner::getChat(not_null row)
row->attached = i->second.get();
return i->second.get();
}
- const auto [i, ok] = _dataMap.emplace(
+ const auto &[i, ok] = _dataMap.emplace(
peer,
std::make_unique(peer, _st.item, [=] { repaintChat(peer); }));
updateChatName(i->second.get());
diff --git a/Telegram/SourceFiles/calls/calls_box_controller.cpp b/Telegram/SourceFiles/calls/calls_box_controller.cpp
index 9eb53580f..11c6347d5 100644
--- a/Telegram/SourceFiles/calls/calls_box_controller.cpp
+++ b/Telegram/SourceFiles/calls/calls_box_controller.cpp
@@ -521,6 +521,7 @@ void BoxController::loadMoreRows() {
MTP_string(), // q
MTP_inputPeerEmpty(),
MTPInputPeer(), // saved_peer_id
+ MTPVector(), // saved_reaction
MTPint(), // top_msg_id
MTP_inputMessagesFilterPhoneCalls(MTP_flags(0)),
MTP_int(0), // min_date
diff --git a/Telegram/SourceFiles/calls/calls_top_bar.cpp b/Telegram/SourceFiles/calls/calls_top_bar.cpp
index 17f0008e8..6a3314578 100644
--- a/Telegram/SourceFiles/calls/calls_top_bar.cpp
+++ b/Telegram/SourceFiles/calls/calls_top_bar.cpp
@@ -267,7 +267,7 @@ TopBar::TopBar(
? object_ptr(
this,
st::callBarLabel,
- tr::lng_call_bar_hangup(tr::now).toUpper())
+ tr::lng_call_bar_hangup(tr::now))
: object_ptr(nullptr))
, _mute(this, st::callBarMuteToggle)
, _info(this)
diff --git a/Telegram/SourceFiles/calls/calls_video_bubble.cpp b/Telegram/SourceFiles/calls/calls_video_bubble.cpp
index aecabfd55..043ee8c41 100644
--- a/Telegram/SourceFiles/calls/calls_video_bubble.cpp
+++ b/Telegram/SourceFiles/calls/calls_video_bubble.cpp
@@ -127,12 +127,13 @@ void VideoBubble::paint() {
const auto inner = _content.rect().marginsRemoved(padding);
Ui::Shadow::paint(p, inner, _content.width(), st::boxRoundShadow);
const auto factor = cIntRetinaFactor();
+ const auto left = _mirrored
+ ? (_frame.width() - (inner.width() * factor))
+ : 0;
p.drawImage(
inner,
_frame,
- QRect(
- QPoint(_frame.width() - (inner.width() * factor), 0),
- inner.size() * factor));
+ QRect(QPoint(left, 0), inner.size() * factor));
}
_track->markFrameShown();
}
@@ -152,11 +153,10 @@ void VideoBubble::prepareFrame() {
.resize = size,
.outer = size,
};
- const auto frame = _track->frame(request).mirrored(!_mirrored, false);
+ const auto frame = _track->frame(request);
if (_frame.width() < size.width() || _frame.height() < size.height()) {
- _frame = QImage(
- size * cIntRetinaFactor(),
- QImage::Format_ARGB32_Premultiplied);
+ _frame = QImage(size, QImage::Format_ARGB32_Premultiplied);
+ _frame.fill(Qt::transparent);
}
Assert(_frame.width() >= frame.width()
&& _frame.height() >= frame.height());
@@ -174,7 +174,7 @@ void VideoBubble::prepareFrame() {
ImageRoundRadius::Large,
RectPart::AllCorners,
QRect(QPoint(), size)
- ).mirrored(true, false);
+ ).mirrored(_mirrored, false);
}
void VideoBubble::setState(Webrtc::VideoState state) {
diff --git a/Telegram/SourceFiles/calls/group/calls_group_call.cpp b/Telegram/SourceFiles/calls/group/calls_group_call.cpp
index 49dd3db91..5ec38443d 100644
--- a/Telegram/SourceFiles/calls/group/calls_group_call.cpp
+++ b/Telegram/SourceFiles/calls/group/calls_group_call.cpp
@@ -3013,7 +3013,7 @@ void GroupCall::checkLastSpoke() {
const auto now = crl::now();
auto list = base::take(_lastSpoke);
for (auto i = list.begin(); i != list.end();) {
- const auto [ssrc, when] = *i;
+ const auto &[ssrc, when] = *i;
if (when.anything + kKeepInListFor >= now) {
hasRecent = true;
++i;
diff --git a/Telegram/SourceFiles/calls/group/calls_group_panel.cpp b/Telegram/SourceFiles/calls/group/calls_group_panel.cpp
index d3bac6aeb..6455ad040 100644
--- a/Telegram/SourceFiles/calls/group/calls_group_panel.cpp
+++ b/Telegram/SourceFiles/calls/group/calls_group_panel.cpp
@@ -1060,11 +1060,13 @@ void Panel::setupVideo(not_null viewport) {
_call->videoEndpointLargeValue(),
_call->videoEndpointPinnedValue()
) | rpl::map(_1 == endpoint && _2);
+ const auto self = (endpoint.peer == _call->joinAs());
viewport->add(
endpoint,
VideoTileTrack{ GroupCall::TrackPointer(track), row },
GroupCall::TrackSizeValue(track),
- std::move(pinned));
+ std::move(pinned),
+ self);
};
for (const auto &[endpoint, track] : _call->activeVideoTracks()) {
setupTile(endpoint, track);
diff --git a/Telegram/SourceFiles/calls/group/calls_group_viewport.cpp b/Telegram/SourceFiles/calls/group/calls_group_viewport.cpp
index 48c76d0ae..9623171b9 100644
--- a/Telegram/SourceFiles/calls/group/calls_group_viewport.cpp
+++ b/Telegram/SourceFiles/calls/group/calls_group_viewport.cpp
@@ -237,13 +237,15 @@ void Viewport::add(
const VideoEndpoint &endpoint,
VideoTileTrack track,
rpl::producer trackSize,
- rpl::producer pinned) {
+ rpl::producer pinned,
+ bool self) {
_tiles.push_back(std::make_unique(
endpoint,
track,
std::move(trackSize),
std::move(pinned),
- [=] { widget()->update(); }));
+ [=] { widget()->update(); },
+ self));
_tiles.back()->trackSizeValue(
) | rpl::filter([](QSize size) {
diff --git a/Telegram/SourceFiles/calls/group/calls_group_viewport.h b/Telegram/SourceFiles/calls/group/calls_group_viewport.h
index c6567e037..9d5021f85 100644
--- a/Telegram/SourceFiles/calls/group/calls_group_viewport.h
+++ b/Telegram/SourceFiles/calls/group/calls_group_viewport.h
@@ -80,7 +80,8 @@ public:
const VideoEndpoint &endpoint,
VideoTileTrack track,
rpl::producer trackSize,
- rpl::producer pinned);
+ rpl::producer pinned,
+ bool self);
void remove(const VideoEndpoint &endpoint);
void showLarge(const VideoEndpoint &endpoint);
diff --git a/Telegram/SourceFiles/calls/group/calls_group_viewport_opengl.cpp b/Telegram/SourceFiles/calls/group/calls_group_viewport_opengl.cpp
index 946c6dca8..e11644e68 100644
--- a/Telegram/SourceFiles/calls/group/calls_group_viewport_opengl.cpp
+++ b/Telegram/SourceFiles/calls/group/calls_group_viewport_opengl.cpp
@@ -531,6 +531,12 @@ void Viewport::RendererGL::paintTile(
{ { 1.f, 0.f } },
{ { 0.f, 0.f } },
} };
+ if (tile->mirror()) {
+ std::swap(toBlurTexCoords[0], toBlurTexCoords[1]);
+ std::swap(toBlurTexCoords[2], toBlurTexCoords[3]);
+ std::swap(texCoords[0], texCoords[1]);
+ std::swap(texCoords[2], texCoords[3]);
+ }
if (const auto shift = (frameRotation / 90); shift > 0) {
std::rotate(
toBlurTexCoords.begin(),
diff --git a/Telegram/SourceFiles/calls/group/calls_group_viewport_raster.cpp b/Telegram/SourceFiles/calls/group/calls_group_viewport_raster.cpp
index f85929ea1..ab335c55c 100644
--- a/Telegram/SourceFiles/calls/group/calls_group_viewport_raster.cpp
+++ b/Telegram/SourceFiles/calls/group/calls_group_viewport_raster.cpp
@@ -105,14 +105,14 @@ void Viewport::RendererSW::paintTile(
tileData.blurredFrame = Images::BlurLargeImage(
data.original.scaled(
VideoTile::PausedVideoSize(),
- Qt::KeepAspectRatio),
+ Qt::KeepAspectRatio).mirrored(tile->mirror(), false),
kBlurRadius);
}
const auto &image = _userpicFrame
? tileData.userpicFrame
: _pausedFrame
? tileData.blurredFrame
- : data.original;
+ : data.original.mirrored(tile->mirror(), false);
const auto frameRotation = _userpicFrame ? 0 : data.rotation;
Assert(!image.isNull());
diff --git a/Telegram/SourceFiles/calls/group/calls_group_viewport_tile.cpp b/Telegram/SourceFiles/calls/group/calls_group_viewport_tile.cpp
index 58ac0fb9d..1b31a2012 100644
--- a/Telegram/SourceFiles/calls/group/calls_group_viewport_tile.cpp
+++ b/Telegram/SourceFiles/calls/group/calls_group_viewport_tile.cpp
@@ -28,12 +28,14 @@ Viewport::VideoTile::VideoTile(
VideoTileTrack track,
rpl::producer trackSize,
rpl::producer pinned,
- Fn update)
+ Fn update,
+ bool self)
: _endpoint(endpoint)
, _update(std::move(update))
, _track(std::move(track))
, _trackSize(std::move(trackSize))
-, _rtmp(endpoint.rtmp()) {
+, _rtmp(endpoint.rtmp())
+, _self(self) {
Expects(_track.track != nullptr);
Expects(_track.row != nullptr);
@@ -48,6 +50,10 @@ Viewport::VideoTile::VideoTile(
setup(std::move(pinned));
}
+bool Viewport::VideoTile::mirror() const {
+ return _self && (_endpoint.type == VideoEndpointType::Camera);
+}
+
QRect Viewport::VideoTile::pinOuter() const {
return _pinOuter;
}
diff --git a/Telegram/SourceFiles/calls/group/calls_group_viewport_tile.h b/Telegram/SourceFiles/calls/group/calls_group_viewport_tile.h
index 445bf2d7e..6f85f4a01 100644
--- a/Telegram/SourceFiles/calls/group/calls_group_viewport_tile.h
+++ b/Telegram/SourceFiles/calls/group/calls_group_viewport_tile.h
@@ -28,7 +28,8 @@ public:
VideoTileTrack track,
rpl::producer trackSize,
rpl::producer pinned,
- Fn update);
+ Fn update,
+ bool self);
[[nodiscard]] not_null track() const {
return _track.track;
@@ -54,6 +55,10 @@ public:
[[nodiscard]] bool visible() const {
return !_hidden && !_geometry.isEmpty();
}
+ [[nodiscard]] bool self() const {
+ return _self;
+ }
+ [[nodiscard]] bool mirror() const;
[[nodiscard]] QRect pinOuter() const;
[[nodiscard]] QRect pinInner() const;
[[nodiscard]] QRect backOuter() const;
@@ -123,6 +128,7 @@ private:
bool _pinned = false;
bool _hidden = true;
bool _rtmp = false;
+ bool _self = false;
std::optional _quality;
rpl::lifetime _lifetime;
diff --git a/Telegram/SourceFiles/calls/group/ui/desktop_capture_choose_source.cpp b/Telegram/SourceFiles/calls/group/ui/desktop_capture_choose_source.cpp
index 87ac643d3..ad59290c5 100644
--- a/Telegram/SourceFiles/calls/group/ui/desktop_capture_choose_source.cpp
+++ b/Telegram/SourceFiles/calls/group/ui/desktop_capture_choose_source.cpp
@@ -114,6 +114,7 @@ private:
const not_null _finish;
const not_null _withAudio;
+ QSize _fixedSize;
std::vector> _sources;
Source *_selected = nullptr;
QString _selectedId;
@@ -337,7 +338,7 @@ void ChooseSourceProcess::setupPanel() {
+ (kRows - 1) * skips.height()
+ (st::desktopCaptureSourceSize.height() / 2)
+ bottomHeight;
- _window->setFixedSize({ width, height });
+ _fixedSize = QSize(width, height);
_window->setStaysOnTop(true);
_window->body()->paintRequest(
@@ -598,6 +599,7 @@ void ChooseSourceProcess::setupGeometryWithParent(
if (parentScreen && myScreen != parentScreen) {
_window->windowHandle()->setScreen(parentScreen);
}
+ _window->setFixedSize(_fixedSize);
_window->move(
parent->x() + (parent->width() - _window->width()) / 2,
parent->y() + (parent->height() - _window->height()) / 2);
diff --git a/Telegram/SourceFiles/chat_helpers/chat_helpers.style b/Telegram/SourceFiles/chat_helpers/chat_helpers.style
index c86e98b75..99f6f9900 100644
--- a/Telegram/SourceFiles/chat_helpers/chat_helpers.style
+++ b/Telegram/SourceFiles/chat_helpers/chat_helpers.style
@@ -1063,12 +1063,17 @@ historyRecordVoiceShowDuration: 120;
historyRecordVoiceDuration: 120;
historyRecordVoice: icon {{ "chat/input_record", historyRecordVoiceFg }};
historyRecordVoiceOver: icon {{ "chat/input_record", historyRecordVoiceFgOver }};
+historyRecordVoiceOnceBg: icon {{ "voice_lock/audio_once_bg", historySendIconFg }};
+historyRecordVoiceOnceBgOver: icon {{ "voice_lock/audio_once_bg", historySendIconFgOver }};
+historyRecordVoiceOnceFg: icon {{ "voice_lock/audio_once_number", windowFgActive }};
+historyRecordVoiceOnceFgOver: icon {{ "voice_lock/audio_once_number", windowFgActive }};
+historyRecordVoiceOnceInactive: icon {{ "chat/audio_once", windowSubTextFg }};
historyRecordVoiceActive: icon {{ "chat/input_record_filled", historyRecordVoiceFgActiveIcon }};
historyRecordSendIconPosition: point(2px, 0px);
historyRecordVoiceRippleBgActive: lightButtonBgOver;
historyRecordSignalRadius: 5px;
historyRecordCancel: windowSubTextFg;
-historyRecordCancelActive: windowActiveTextFg;
+historyRecordCancelActive: historySendIconFg;
historyRecordFont: font(13px);
historyRecordDurationSkip: 12px;
historyRecordDurationFg: historyComposeAreaFg;
@@ -1111,20 +1116,26 @@ historyRecordLockArrow: icon {{ "voice_lock/voice_arrow", historyToDownFg }};
historyRecordLockRippleMargin: margins(6px, 6px, 6px, 6px);
historyRecordDelete: IconButton(historyAttach) {
- icon: icon {{ "info/info_media_delete", historyComposeIconFg }};
- iconOver: icon {{ "info/info_media_delete", historyComposeIconFgOver }};
+ icon: icon {{ "voice_lock/recorded_delete", historyComposeIconFg }};
+ iconOver: icon {{ "voice_lock/recorded_delete", historyComposeIconFgOver }};
iconPosition: point(10px, 11px);
}
historyRecordWaveformRightSkip: 10px;
-historyRecordWaveformBgMargins: margins(5px, 7px, 5px, 7px);
+historyRecordWaveformBgMargins: margins(5px, 8px, 5px, 9px);
historyRecordWaveformBar: 3px;
-historyRecordLockPosition: point(1px, 35px);
+historyRecordLockPosition: point(1px, 22px);
historyRecordCancelButtonWidth: 100px;
historyRecordCancelButtonFg: lightButtonFg;
+historyRecordTooltip: ImportantTooltip(defaultImportantTooltip) {
+ padding: margins(4px, 4px, 4px, 4px);
+ radius: 11px;
+ arrow: 6px;
+}
+
historySilentToggle: IconButton(historyBotKeyboardShow) {
icon: icon {{ "chat/input_silent", historyComposeIconFg }};
iconOver: icon {{ "chat/input_silent", historyComposeIconFgOver }};
@@ -1266,3 +1277,17 @@ dragDropColor: windowActiveTextFg;
dragMargin: margins(0px, 10px, 0px, 10px);
dragPadding: margins(20px, 10px, 20px, 10px);
dragHeight: 72px;
+
+ttlMediaImportantTooltipLabel: FlatLabel(defaultImportantTooltipLabel) {
+ style: TextStyle(defaultTextStyle) {
+ font: font(14px);
+ }
+}
+ttlMediaButton: RoundButton(defaultActiveButton) {
+ textBg: shadowFg;
+ textBgOver: shadowFg;
+ ripple: universalRippleAnimation;
+ height: 31px;
+ textTop: 6px;
+}
+ttlMediaButtonBottomSkip: 14px;
diff --git a/Telegram/SourceFiles/chat_helpers/emoji_sets_manager.cpp b/Telegram/SourceFiles/chat_helpers/emoji_sets_manager.cpp
index 7c6ae96c8..fed24fc5b 100644
--- a/Telegram/SourceFiles/chat_helpers/emoji_sets_manager.cpp
+++ b/Telegram/SourceFiles/chat_helpers/emoji_sets_manager.cpp
@@ -247,7 +247,7 @@ void Row::paintPreview(QPainter &p) const {
const auto width = st::manageEmojiPreviewWidth;
const auto height = st::manageEmojiPreviewWidth;
auto &&preview = ranges::views::zip(_preview, ranges::views::ints(0, int(_preview.size())));
- for (const auto [pixmap, index] : preview) {
+ for (const auto &[pixmap, index] : preview) {
const auto row = (index / 2);
const auto column = (index % 2);
const auto left = x + (column ? width - st::manageEmojiPreview : 0);
diff --git a/Telegram/SourceFiles/chat_helpers/stickers_gift_box_pack.cpp b/Telegram/SourceFiles/chat_helpers/stickers_gift_box_pack.cpp
index e20c361d6..f54b1f1d3 100644
--- a/Telegram/SourceFiles/chat_helpers/stickers_gift_box_pack.cpp
+++ b/Telegram/SourceFiles/chat_helpers/stickers_gift_box_pack.cpp
@@ -26,6 +26,8 @@ DocumentData *GiftBoxPack::lookup(int months) const {
const auto fallback = _documents.empty() ? nullptr : _documents[0];
if (it == begin(_localMonths)) {
return fallback;
+ } else if (it == end(_localMonths)) {
+ return _documents.back();
}
const auto left = *(it - 1);
const auto right = *it;
diff --git a/Telegram/SourceFiles/chat_helpers/ttl_media_layer_widget.cpp b/Telegram/SourceFiles/chat_helpers/ttl_media_layer_widget.cpp
new file mode 100644
index 000000000..f2445a166
--- /dev/null
+++ b/Telegram/SourceFiles/chat_helpers/ttl_media_layer_widget.cpp
@@ -0,0 +1,392 @@
+/*
+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 "chat_helpers/ttl_media_layer_widget.h"
+
+#include "base/event_filter.h"
+#include "data/data_document.h"
+#include "data/data_session.h"
+#include "editor/editor_layer_widget.h"
+#include "history/history.h"
+#include "history/history_item.h"
+#include "history/view/history_view_element.h"
+#include "history/view/media/history_view_document.h"
+#include "lang/lang_keys.h"
+#include "main/main_session.h"
+#include "mainwidget.h"
+#include "media/audio/media_audio.h"
+#include "media/player/media_player_instance.h"
+#include "ui/chat/chat_style.h"
+#include "ui/chat/chat_theme.h"
+#include "ui/effects/path_shift_gradient.h"
+#include "ui/painter.h"
+#include "ui/rect.h"
+#include "ui/text/text_utilities.h"
+#include "ui/widgets/buttons.h"
+#include "ui/widgets/labels.h"
+#include "ui/widgets/tooltip.h"
+#include "window/section_widget.h" // Window::ChatThemeValueFromPeer.
+#include "window/themes/window_theme.h"
+#include "window/window_controller.h"
+#include "window/window_session_controller.h"
+#include "styles/style_chat.h"
+#include "styles/style_chat_helpers.h"
+#include "styles/style_dialogs.h"
+
+namespace ChatHelpers {
+namespace {
+
+class PreviewDelegate final : public HistoryView::DefaultElementDelegate {
+public:
+ PreviewDelegate(
+ not_null parent,
+ not_null st,
+ rpl::producer chatWideValue,
+ Fn update);
+
+ bool elementAnimationsPaused() override;
+ not_null elementPathShiftGradient() override;
+ HistoryView::Context elementContext() override;
+ bool elementIsChatWide() override;
+
+private:
+ const not_null _parent;
+ const std::unique_ptr _pathGradient;
+ rpl::variable _chatWide;
+
+};
+
+PreviewDelegate::PreviewDelegate(
+ not_null parent,
+ not_null st,
+ rpl::producer chatWideValue,
+ Fn update)
+: _parent(parent)
+, _pathGradient(HistoryView::MakePathShiftGradient(st, update))
+, _chatWide(std::move(chatWideValue)) {
+}
+
+bool PreviewDelegate::elementAnimationsPaused() {
+ return _parent->window()->isActiveWindow();
+}
+
+not_null PreviewDelegate::elementPathShiftGradient() {
+ return _pathGradient.get();
+}
+
+HistoryView::Context PreviewDelegate::elementContext() {
+ return HistoryView::Context::TTLViewer;
+}
+
+bool PreviewDelegate::elementIsChatWide() {
+ return _chatWide.current();
+}
+
+class PreviewWrap final : public Ui::RpWidget {
+public:
+ PreviewWrap(
+ not_null parent,
+ not_null item,
+ rpl::producer viewportValue,
+ rpl::producer chatWideValue,
+ rpl::producer> theme);
+ ~PreviewWrap();
+
+ [[nodiscard]] rpl::producer<> closeRequests() const;
+
+private:
+ void paintEvent(QPaintEvent *e) override;
+ void createView();
+ [[nodiscard]] bool goodItem() const;
+ void clear();
+
+ const not_null _item;
+ const std::unique_ptr _style;
+ const std::unique_ptr _delegate;
+ rpl::variable _globalViewport;
+ rpl::variable _chatWide;
+ std::shared_ptr _theme;
+ std::unique_ptr _element;
+ QRect _viewport;
+ QRect _elementGeometry;
+ rpl::variable _elementInner;
+ rpl::lifetime _elementLifetime;
+
+ QImage _lastFrameCache;
+
+ rpl::event_stream<> _closeRequests;
+
+};
+
+PreviewWrap::PreviewWrap(
+ not_null parent,
+ not_null item,
+ rpl::producer viewportValue,
+ rpl::producer chatWideValue,
+ rpl::producer> theme)
+: RpWidget(parent)
+, _item(item)
+, _style(std::make_unique(
+ item->history()->session().colorIndicesValue()))
+, _delegate(std::make_unique(
+ parent,
+ _style.get(),
+ std::move(chatWideValue),
+ [=] { update(_elementGeometry); }))
+, _globalViewport(std::move(viewportValue)) {
+ const auto closeCallback = [=] { _closeRequests.fire({}); };
+ HistoryView::TTLVoiceStops(
+ item->fullId()
+ ) | rpl::start_with_next([=] {
+ _lastFrameCache = Ui::GrabWidgetToImage(this, _elementGeometry);
+ closeCallback();
+ }, lifetime());
+
+ const auto isRound = _item
+ && _item->media()
+ && _item->media()->document()
+ && _item->media()->document()->isVideoMessage();
+
+ std::move(
+ theme
+ ) | rpl::start_with_next([=](std::shared_ptr theme) {
+ _theme = std::move(theme);
+ _style->apply(_theme.get());
+ }, lifetime());
+
+ const auto session = &_item->history()->session();
+ session->data().viewRepaintRequest(
+ ) | rpl::start_with_next([=](not_null view) {
+ if (view == _element.get()) {
+ update(_elementGeometry);
+ }
+ }, lifetime());
+ session->data().itemViewRefreshRequest(
+ ) | rpl::start_with_next([=](not_null item) {
+ if (item == _item) {
+ if (goodItem()) {
+ createView();
+ update();
+ } else {
+ clear();
+ _closeRequests.fire({});
+ }
+ }
+ }, lifetime());
+ session->data().itemDataChanges(
+ ) | rpl::start_with_next([=](not_null item) {
+ if (item == _item) {
+ _element->itemDataChanged();
+ }
+ }, lifetime());
+ session->data().itemRemoved(
+ ) | rpl::start_with_next([=](not_null item) {
+ if (item == _item) {
+ _closeRequests.fire({});
+ }
+ }, lifetime());
+
+ {
+ const auto close = Ui::CreateChild(
+ this,
+ item->out()
+ ? tr::lng_close()
+ : tr::lng_ttl_voice_close_in(),
+ st::ttlMediaButton);
+ close->setFullRadius(true);
+ close->setClickedCallback(closeCallback);
+ close->setTextTransform(Ui::RoundButton::TextTransform::NoTransform);
+
+ rpl::combine(
+ sizeValue(),
+ _elementInner.value()
+ ) | rpl::start_with_next([=](QSize size, QRect inner) {
+ close->moveToLeft(
+ inner.x() + (inner.width() - close->width()) / 2,
+ (size.height()
+ - close->height()
+ - st::ttlMediaButtonBottomSkip));
+ }, close->lifetime());
+ }
+
+ QWidget::setAttribute(Qt::WA_OpaquePaintEvent, false);
+ createView();
+
+ {
+ auto text = item->out()
+ ? (isRound
+ ? tr::lng_ttl_round_tooltip_out
+ : tr::lng_ttl_voice_tooltip_out)(
+ lt_user,
+ rpl::single(
+ item->history()->peer->shortName()
+ ) | rpl::map(Ui::Text::RichLangValue),
+ Ui::Text::RichLangValue)
+ : (isRound
+ ? tr::lng_ttl_round_tooltip_in
+ : tr::lng_ttl_voice_tooltip_in)(Ui::Text::RichLangValue);
+ const auto tooltip = Ui::CreateChild(
+ this,
+ object_ptr>(
+ this,
+ Ui::MakeNiceTooltipLabel(
+ parent,
+ std::move(text),
+ st::dialogsStoriesTooltipMaxWidth,
+ st::ttlMediaImportantTooltipLabel),
+ st::defaultImportantTooltip.padding),
+ st::dialogsStoriesTooltip);
+ tooltip->toggleFast(true);
+ _elementInner.value(
+ ) | rpl::filter([](const QRect &inner) {
+ return !inner.isEmpty();
+ }) | rpl::start_with_next([=](const QRect &inner) {
+ tooltip->pointAt(inner, RectPart::Top, [=](QSize size) {
+ return QPoint{
+ inner.x() + (inner.width() - size.width()) / 2,
+ (inner.y()
+ - st::normalFont->height
+ - size.height()
+ - st::defaultImportantTooltip.padding.top()),
+ };
+ });
+ }, tooltip->lifetime());
+ }
+}
+
+rpl::producer<> PreviewWrap::closeRequests() const {
+ return _closeRequests.events();
+}
+
+bool PreviewWrap::goodItem() const {
+ const auto media = _item->media();
+ if (!media || !media->ttlSeconds()) {
+ return false;
+ }
+ const auto document = media->document();
+ return document
+ && (document->isVoiceMessage() || document->isVideoMessage());
+}
+
+void PreviewWrap::createView() {
+ clear();
+ _element = _item->createView(_delegate.get());
+ _element->initDimensions();
+ rpl::combine(
+ sizeValue(),
+ _globalViewport.value()
+ ) | rpl::start_with_next([=](QSize outer, QRect globalViewport) {
+ _viewport = globalViewport.isEmpty()
+ ? rect()
+ : mapFromGlobal(globalViewport);
+ if (_viewport.width() < st::msgMinWidth) {
+ return;
+ }
+ _element->resizeGetHeight(_viewport.width());
+ _elementGeometry = QRect(
+ (_viewport.width() - _element->width()) / 2,
+ (_viewport.height() - _element->height()) / 2,
+ _element->width(),
+ _element->height()
+ ).translated(_viewport.topLeft());
+ _elementInner = _element->innerGeometry().translated(
+ _elementGeometry.topLeft());
+ update();
+ }, _elementLifetime);
+}
+
+void PreviewWrap::clear() {
+ _elementLifetime.destroy();
+ _element = nullptr;
+}
+
+PreviewWrap::~PreviewWrap() {
+ clear();
+}
+
+void PreviewWrap::paintEvent(QPaintEvent *e) {
+ if (!_element || _elementGeometry.isEmpty()) {
+ return;
+ }
+
+ auto p = Painter(this);
+ p.translate(_elementGeometry.topLeft());
+ if (!_lastFrameCache.isNull()) {
+ p.drawImage(0, 0, _lastFrameCache);
+ } else {
+ auto context = _theme->preparePaintContext(
+ _style.get(),
+ Rect(_element->currentSize()),
+ Rect(_element->currentSize()),
+ !window()->isActiveWindow());
+ context.outbg = _element->hasOutLayout();
+ _element->draw(p, context);
+ }
+}
+
+rpl::producer GlobalViewportForWindow(
+ not_null controller) {
+ const auto delegate = controller->window().floatPlayerDelegate();
+ return rpl::single(rpl::empty) | rpl::then(
+ delegate->floatPlayerAreaUpdates()
+ ) | rpl::map([=] {
+ auto section = (Media::Player::FloatSectionDelegate*)nullptr;
+ delegate->floatPlayerEnumerateSections([&](
+ not_null check,
+ Window::Column column) {
+ if ((column == Window::Column::First && !section)
+ || column == Window::Column::Second) {
+ section = check;
+ }
+ });
+ if (section) {
+ const auto rect = section->floatPlayerAvailableRect();
+ if (rect.width() >= st::msgMinWidth) {
+ return rect;
+ }
+ }
+ return QRect();
+ });
+}
+
+} // namespace
+
+void ShowTTLMediaLayerWidget(
+ not_null controller,
+ not_null item) {
+ const auto parent = controller->content();
+ const auto show = controller->uiShow();
+ auto preview = base::make_unique_q(
+ parent,
+ item,
+ GlobalViewportForWindow(controller),
+ controller->adaptive().chatWideValue(),
+ Window::ChatThemeValueFromPeer(
+ controller,
+ item->history()->peer));
+ preview->closeRequests(
+ ) | rpl::start_with_next([=] {
+ show->hideLayer();
+ }, preview->lifetime());
+ auto layer = std::make_unique(
+ parent,
+ std::move(preview));
+ layer->lifetime().add([] { ::Media::Player::instance()->stop(); });
+ base::install_event_filter(layer.get(), [=](not_null e) {
+ if (e->type() == QEvent::KeyPress) {
+ const auto k = static_cast(e.get());
+ if (k->key() == Qt::Key_Escape) {
+ show->hideLayer();
+ }
+ return base::EventFilterResult::Cancel;
+ }
+ return base::EventFilterResult::Continue;
+ });
+ controller->showLayer(std::move(layer), Ui::LayerOption::KeepOther);
+}
+
+} // namespace ChatHelpers
diff --git a/Telegram/SourceFiles/chat_helpers/ttl_media_layer_widget.h b/Telegram/SourceFiles/chat_helpers/ttl_media_layer_widget.h
new file mode 100644
index 000000000..f31d04982
--- /dev/null
+++ b/Telegram/SourceFiles/chat_helpers/ttl_media_layer_widget.h
@@ -0,0 +1,22 @@
+/*
+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
+*/
+#pragma once
+
+class HistoryItem;
+
+namespace Window {
+class SessionController;
+} // namespace Window
+
+namespace ChatHelpers {
+
+void ShowTTLMediaLayerWidget(
+ not_null controller,
+ not_null item);
+
+} // namespace ChatHelpers
diff --git a/Telegram/SourceFiles/core/application.cpp b/Telegram/SourceFiles/core/application.cpp
index 57747616b..5e474569e 100644
--- a/Telegram/SourceFiles/core/application.cpp
+++ b/Telegram/SourceFiles/core/application.cpp
@@ -360,7 +360,7 @@ void Application::run() {
startDomain();
startTray();
- _lastActivePrimaryWindow->widget()->show();
+ _lastActivePrimaryWindow->firstShow();
startMediaView();
diff --git a/Telegram/SourceFiles/core/core_cloud_password.cpp b/Telegram/SourceFiles/core/core_cloud_password.cpp
index 99b5d589e..3a65f24d5 100644
--- a/Telegram/SourceFiles/core/core_cloud_password.cpp
+++ b/Telegram/SourceFiles/core/core_cloud_password.cpp
@@ -128,7 +128,7 @@ CloudPasswordResult ComputeCheck(
}
};
- const auto [a, AForHash, u] = GenerateAndCheckRandom();
+ const auto &[a, AForHash, u] = GenerateAndCheckRandom();
const auto g_b = BigNum::ModSub(B, kg_x, p, context);
if (!MTP::IsGoodModExpFirst(g_b, p)) {
LOG(("API Error: Bad g_b in cloud password check!"));
diff --git a/Telegram/SourceFiles/core/core_settings.cpp b/Telegram/SourceFiles/core/core_settings.cpp
index f22fe917d..a8a4a74c5 100644
--- a/Telegram/SourceFiles/core/core_settings.cpp
+++ b/Telegram/SourceFiles/core/core_settings.cpp
@@ -343,7 +343,8 @@ QByteArray Settings::serialize() const {
<< qint32(_ignoreBatterySaving.current() ? 1 : 0)
<< quint64(_macRoundIconDigest.value_or(0))
<< qint32(_storiesClickTooltipHidden.current() ? 1 : 0)
- << qint32(_recentEmojiSkip.size());
+ << qint32(_recentEmojiSkip.size())
+ << qint32(_ttlVoiceClickTooltipHidden.current() ? 1 : 0);
for (const auto &id : _recentEmojiSkip) {
stream << id;
}
@@ -459,6 +460,7 @@ void Settings::addFromSerialized(const QByteArray &serialized) {
qint32 storiesClickTooltipHidden = _storiesClickTooltipHidden.current() ? 1 : 0;
base::flat_set recentEmojiSkip;
qint32 trayIconMonochrome = (_trayIconMonochrome.current() ? 1 : 0);
+ qint32 ttlVoiceClickTooltipHidden = _ttlVoiceClickTooltipHidden.current() ? 1 : 0;
stream >> themesAccentColors;
if (!stream.atEnd()) {
@@ -715,6 +717,9 @@ void Settings::addFromSerialized(const QByteArray &serialized) {
// Let existing clients use the old value.
trayIconMonochrome = 0;
}
+ if (!stream.atEnd()) {
+ stream >> ttlVoiceClickTooltipHidden;
+ }
if (stream.status() != QDataStream::Ok) {
LOG(("App Error: "
"Bad data for Core::Settings::constructFromSerialized()"));
@@ -910,6 +915,7 @@ void Settings::addFromSerialized(const QByteArray &serialized) {
_storiesClickTooltipHidden = (storiesClickTooltipHidden == 1);
_recentEmojiSkip = std::move(recentEmojiSkip);
_trayIconMonochrome = (trayIconMonochrome == 1);
+ _ttlVoiceClickTooltipHidden = (ttlVoiceClickTooltipHidden == 1);
}
QString Settings::getSoundPath(const QString &key) const {
@@ -1266,6 +1272,7 @@ void Settings::resetOnLastLogout() {
_systemDarkModeEnabled = false;
_hiddenGroupCallTooltips = 0;
_storiesClickTooltipHidden = false;
+ _ttlVoiceClickTooltipHidden = false;
_recentEmojiPreload.clear();
_recentEmoji.clear();
diff --git a/Telegram/SourceFiles/core/core_settings.h b/Telegram/SourceFiles/core/core_settings.h
index 2ab3d21dd..337f5810a 100644
--- a/Telegram/SourceFiles/core/core_settings.h
+++ b/Telegram/SourceFiles/core/core_settings.h
@@ -826,6 +826,15 @@ public:
void setStoriesClickTooltipHidden(bool value) {
_storiesClickTooltipHidden = value;
}
+ [[nodiscard]] bool ttlVoiceClickTooltipHidden() const {
+ return _ttlVoiceClickTooltipHidden.current();
+ }
+ [[nodiscard]] rpl::producer ttlVoiceClickTooltipHiddenValue() const {
+ return _ttlVoiceClickTooltipHidden.value();
+ }
+ void setTtlVoiceClickTooltipHidden(bool value) {
+ _ttlVoiceClickTooltipHidden = value;
+ }
[[nodiscard]] static bool ThirdColumnByDefault();
[[nodiscard]] static float64 DefaultDialogsWidthRatio();
@@ -951,6 +960,7 @@ private:
rpl::variable _ignoreBatterySaving = false;
std::optional _macRoundIconDigest;
rpl::variable _storiesClickTooltipHidden = false;
+ rpl::variable _ttlVoiceClickTooltipHidden = false;
bool _tabbedReplacedWithInfo = false; // per-window
rpl::event_stream _tabbedReplacedWithInfoValue; // per-window
diff --git a/Telegram/SourceFiles/core/shortcuts.cpp b/Telegram/SourceFiles/core/shortcuts.cpp
index 8b90122b1..c90e88233 100644
--- a/Telegram/SourceFiles/core/shortcuts.cpp
+++ b/Telegram/SourceFiles/core/shortcuts.cpp
@@ -80,6 +80,13 @@ const auto CommandByName = base::flat_map{
{ u"next_folder"_q , Command::FolderNext },
{ u"all_chats"_q , Command::ShowAllChats },
+ { u"account1"_q , Command::ShowAccount1 },
+ { u"account2"_q , Command::ShowAccount2 },
+ { u"account3"_q , Command::ShowAccount3 },
+ { u"account4"_q , Command::ShowAccount4 },
+ { u"account5"_q , Command::ShowAccount5 },
+ { u"account6"_q , Command::ShowAccount6 },
+
{ u"folder1"_q , Command::ShowFolder1 },
{ u"folder2"_q , Command::ShowFolder2 },
{ u"folder3"_q , Command::ShowFolder3 },
@@ -126,6 +133,13 @@ const auto CommandNames = base::flat_map{
{ Command::FolderNext , u"next_folder"_q },
{ Command::ShowAllChats , u"all_chats"_q },
+ { Command::ShowAccount1 , u"account1"_q },
+ { Command::ShowAccount2 , u"account2"_q },
+ { Command::ShowAccount3 , u"account3"_q },
+ { Command::ShowAccount4 , u"account4"_q },
+ { Command::ShowAccount5 , u"account5"_q },
+ { Command::ShowAccount6 , u"account6"_q },
+
{ Command::ShowFolder1 , u"folder1"_q },
{ Command::ShowFolder2 , u"folder2"_q },
{ Command::ShowFolder3 , u"folder3"_q },
@@ -388,10 +402,18 @@ void Manager::fillDefaults() {
kShowFolder,
ranges::views::ints(1, ranges::unreachable));
- for (const auto [command, index] : folders) {
+ for (const auto &[command, index] : folders) {
set(u"%1+%2"_q.arg(ctrl).arg(index), command);
}
+ //auto &&accounts = ranges::views::zip(
+ // kShowAccount,
+ // ranges::views::ints(1, ranges::unreachable));
+
+ //for (const auto &[command, index] : accounts) {
+ // set(u"%1+shift+%2"_q.arg(ctrl).arg(index), command);
+ //}
+
set(u"%1+shift+down"_q.arg(ctrl), Command::FolderNext);
set(u"%1+shift+up"_q.arg(ctrl), Command::FolderPrevious);
@@ -436,6 +458,18 @@ void Manager::writeDefaultFile() {
}
}
+ // Commands without a default value.
+ for (const auto command : kShowAccount) {
+ const auto j = CommandNames.find(command);
+ if (j != CommandNames.end()) {
+ QJsonObject entry;
+ entry.insert(u"keys"_q, QJsonValue());
+ entry.insert(u"command"_q, j->second);
+ shortcuts.append(entry);
+ }
+ }
+
+
auto document = QJsonDocument();
document.setArray(shortcuts);
file.write(document.toJson(QJsonDocument::Indented));
diff --git a/Telegram/SourceFiles/core/shortcuts.h b/Telegram/SourceFiles/core/shortcuts.h
index d7b3dc3c5..b562517d5 100644
--- a/Telegram/SourceFiles/core/shortcuts.h
+++ b/Telegram/SourceFiles/core/shortcuts.h
@@ -38,6 +38,13 @@ enum class Command {
ChatPinned7,
ChatPinned8,
+ ShowAccount1,
+ ShowAccount2,
+ ShowAccount3,
+ ShowAccount4,
+ ShowAccount5,
+ ShowAccount6,
+
ShowAllChats,
ShowFolder1,
ShowFolder2,
@@ -79,6 +86,15 @@ enum class Command {
Command::ShowFolderLast,
};
+[[maybe_unused]] constexpr auto kShowAccount = {
+ Command::ShowAccount1,
+ Command::ShowAccount2,
+ Command::ShowAccount3,
+ Command::ShowAccount4,
+ Command::ShowAccount5,
+ Command::ShowAccount6,
+};
+
[[nodiscard]] FnMut RequestHandler(Command command);
class Request {
diff --git a/Telegram/SourceFiles/core/version.h b/Telegram/SourceFiles/core/version.h
index b2db36f80..946974abd 100644
--- a/Telegram/SourceFiles/core/version.h
+++ b/Telegram/SourceFiles/core/version.h
@@ -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 = 4014003;
-constexpr auto AppVersionStr = "4.14.3";
+constexpr auto AppVersion = 4014006;
+constexpr auto AppVersionStr = "4.14.6";
constexpr auto AppBetaVersion = false;
constexpr auto AppAlphaVersion = TDESKTOP_ALPHA_VERSION;
diff --git a/Telegram/SourceFiles/data/data_changes.cpp b/Telegram/SourceFiles/data/data_changes.cpp
index f20041565..773c50d5d 100644
--- a/Telegram/SourceFiles/data/data_changes.cpp
+++ b/Telegram/SourceFiles/data/data_changes.cpp
@@ -56,7 +56,7 @@ rpl::producer Changes::Manager::updates(
Flags flags) const {
return _stream.events(
) | rpl::filter([=](const UpdateType &update) {
- const auto [updateData, updateFlags] = update;
+ const auto &[updateData, updateFlags] = update;
return (updateData == data) && (updateFlags & flags);
});
}
diff --git a/Telegram/SourceFiles/data/data_document_resolver.cpp b/Telegram/SourceFiles/data/data_document_resolver.cpp
index 2c34719ff..8ab502564 100644
--- a/Telegram/SourceFiles/data/data_document_resolver.cpp
+++ b/Telegram/SourceFiles/data/data_document_resolver.cpp
@@ -9,7 +9,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "base/options.h"
#include "base/platform/base_platform_info.h"
-#include "ui/boxes/confirm_box.h"
+#include "boxes/abstract_box.h" // Ui::show().
+#include "chat_helpers/ttl_media_layer_widget.h"
#include "core/application.h"
#include "core/core_settings.h"
#include "core/mime_type.h"
@@ -17,17 +18,17 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_document_media.h"
#include "data/data_file_click_handler.h"
#include "data/data_session.h"
-#include "history/view/media/history_view_gif.h"
#include "history/history.h"
#include "history/history_item.h"
-#include "media/player/media_player_instance.h"
+#include "history/view/media/history_view_gif.h"
#include "lang/lang_keys.h"
+#include "media/player/media_player_instance.h"
#include "platform/platform_file_utilities.h"
+#include "ui/boxes/confirm_box.h"
#include "ui/chat/chat_theme.h"
#include "ui/text/text_utilities.h"
#include "ui/widgets/checkbox.h"
#include "window/window_session_controller.h"
-#include "boxes/abstract_box.h" // Ui::show().
#include "styles/style_layers.h"
#include
@@ -298,6 +299,12 @@ void ResolveDocument(
|| document->isVoiceMessage()
|| document->isVideoMessage()) {
::Media::Player::instance()->playPause({ document, msgId });
+ if (controller
+ && item
+ && item->media()
+ && item->media()->ttlSeconds()) {
+ ChatHelpers::ShowTTLMediaLayerWidget(controller, item);
+ }
} else {
showDocument();
}
diff --git a/Telegram/SourceFiles/data/data_group_call.cpp b/Telegram/SourceFiles/data/data_group_call.cpp
index 0567477c4..72b4d387f 100644
--- a/Telegram/SourceFiles/data/data_group_call.cpp
+++ b/Telegram/SourceFiles/data/data_group_call.cpp
@@ -847,7 +847,7 @@ void GroupCall::requestUnknownParticipants() {
auto result = base::flat_map();
result.reserve(kRequestPerPage);
while (result.size() < kRequestPerPage) {
- const auto [ssrc, when] = _unknownSpokenSsrcs.back();
+ const auto &[ssrc, when] = _unknownSpokenSsrcs.back();
result.emplace(ssrc, when);
_unknownSpokenSsrcs.erase(_unknownSpokenSsrcs.end() - 1);
}
@@ -863,7 +863,7 @@ void GroupCall::requestUnknownParticipants() {
result.reserve(available);
while (result.size() < available) {
const auto &back = _unknownSpokenPeerIds.back();
- const auto [participantPeerId, when] = back;
+ const auto &[participantPeerId, when] = back;
result.emplace(participantPeerId, when);
_unknownSpokenPeerIds.erase(_unknownSpokenPeerIds.end() - 1);
}
diff --git a/Telegram/SourceFiles/data/data_histories.cpp b/Telegram/SourceFiles/data/data_histories.cpp
index 6824531fa..454ae4ab7 100644
--- a/Telegram/SourceFiles/data/data_histories.cpp
+++ b/Telegram/SourceFiles/data/data_histories.cpp
@@ -129,7 +129,7 @@ not_null Histories::findOrCreate(PeerId peerId) {
if (const auto result = find(peerId)) {
return result;
}
- const auto [i, ok] = _map.emplace(
+ const auto &[i, ok] = _map.emplace(
peerId,
std::make_unique(&owner(), peerId));
return i->second.get();
@@ -363,7 +363,7 @@ void Histories::requestDialogEntry(
return;
}
- const auto [j, ok] = _dialogRequestsPending.try_emplace(history);
+ const auto &[j, ok] = _dialogRequestsPending.try_emplace(history);
if (callback) {
j->second.push_back(std::move(callback));
}
@@ -1152,7 +1152,7 @@ void Histories::finishSentRequest(
if (state->postponedRequestEntry && !postponeEntryRequest(*state)) {
const auto i = _dialogRequests.find(history);
Assert(i != end(_dialogRequests));
- const auto [j, ok] = _dialogRequestsPending.emplace(
+ const auto &[j, ok] = _dialogRequestsPending.emplace(
history,
std::move(i->second));
Assert(ok);
diff --git a/Telegram/SourceFiles/data/data_media_types.cpp b/Telegram/SourceFiles/data/data_media_types.cpp
index e9c4dd66e..fb6560e51 100644
--- a/Telegram/SourceFiles/data/data_media_types.cpp
+++ b/Telegram/SourceFiles/data/data_media_types.cpp
@@ -902,7 +902,7 @@ bool MediaFile::uploading() const {
Storage::SharedMediaTypesMask MediaFile::sharedMediaTypes() const {
using Type = Storage::SharedMediaType;
- if (_document->sticker()) {
+ if (_document->sticker() || ttlSeconds()) {
return {};
} else if (_document->isVideoMessage()) {
return Storage::SharedMediaTypesMask{}
diff --git a/Telegram/SourceFiles/data/data_message_reaction_id.cpp b/Telegram/SourceFiles/data/data_message_reaction_id.cpp
index 1103d2e15..2c1a9e503 100644
--- a/Telegram/SourceFiles/data/data_message_reaction_id.cpp
+++ b/Telegram/SourceFiles/data/data_message_reaction_id.cpp
@@ -11,6 +11,26 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
namespace Data {
+QString SearchTagToQuery(const ReactionId &tagId) {
+ if (const auto customId = tagId.custom()) {
+ return u"#tag-custom:%1"_q.arg(customId);
+ } else if (!tagId) {
+ return QString();
+ }
+ return u"#tag-emoji:"_q + tagId.emoji();
+}
+
+ReactionId SearchTagFromQuery(const QString &query) {
+ const auto list = query.split(QChar(' '));
+ const auto tag = list.isEmpty() ? QString() : list[0];
+ if (tag.startsWith(u"#tag-custom:"_q)) {
+ return ReactionId{ DocumentId(tag.mid(12).toULongLong()) };
+ } else if (tag.startsWith(u"#tag-emoji:"_q)) {
+ return ReactionId{ tag.mid(11) };
+ }
+ return {};
+}
+
QString ReactionEntityData(const ReactionId &id) {
if (id.empty()) {
return {};
diff --git a/Telegram/SourceFiles/data/data_message_reaction_id.h b/Telegram/SourceFiles/data/data_message_reaction_id.h
index 8c50fd9de..53d4a2df8 100644
--- a/Telegram/SourceFiles/data/data_message_reaction_id.h
+++ b/Telegram/SourceFiles/data/data_message_reaction_id.h
@@ -27,6 +27,10 @@ struct ReactionId {
return custom ? *custom : DocumentId();
}
+ explicit operator bool() const {
+ return !empty();
+ }
+
friend inline auto operator<=>(
const ReactionId &,
const ReactionId &) = default;
@@ -41,6 +45,9 @@ struct MessageReaction {
bool my = false;
};
+[[nodiscard]] QString SearchTagToQuery(const ReactionId &tagId);
+[[nodiscard]] ReactionId SearchTagFromQuery(const QString &query);
+
[[nodiscard]] QString ReactionEntityData(const ReactionId &id);
[[nodiscard]] ReactionId ReactionFromMTP(const MTPReaction &reaction);
diff --git a/Telegram/SourceFiles/data/data_message_reactions.cpp b/Telegram/SourceFiles/data/data_message_reactions.cpp
index 9ff9205f7..e3b87201f 100644
--- a/Telegram/SourceFiles/data/data_message_reactions.cpp
+++ b/Telegram/SourceFiles/data/data_message_reactions.cpp
@@ -43,6 +43,7 @@ constexpr auto kPollEach = 20 * crl::time(1000);
constexpr auto kSizeForDownscale = 64;
constexpr auto kRecentRequestTimeout = 10 * crl::time(1000);
constexpr auto kRecentReactionsLimit = 40;
+constexpr auto kMyTagsRequestTimeout = crl::time(1000);
constexpr auto kTopRequestDelay = 60 * crl::time(1000);
constexpr auto kTopReactionsLimit = 14;
@@ -69,6 +70,27 @@ constexpr auto kTopReactionsLimit = 14;
return result;
}
+[[nodiscard]] std::vector ListFromMTP(
+ const MTPDmessages_savedReactionTags &data) {
+ const auto &list = data.vtags().v;
+ auto result = std::vector();
+ result.reserve(list.size());
+ for (const auto &reaction : list) {
+ const auto &data = reaction.data();
+ const auto id = ReactionFromMTP(data.vreaction());
+ if (id.empty()) {
+ LOG(("API Error: reactionEmpty in messages.reactions."));
+ } else {
+ result.push_back({
+ .id = id,
+ .title = qs(data.vtitle().value_or_empty()),
+ .count = data.vcount().v,
+ });
+ }
+ }
+ return result;
+}
+
[[nodiscard]] Reaction CustomReaction(not_null document) {
return Reaction{
.id = { { document->id } },
@@ -126,6 +148,8 @@ PossibleItemReactionsRef LookupPossibleReactions(
const auto &full = reactions->list(Reactions::Type::Active);
const auto &top = reactions->list(Reactions::Type::Top);
const auto &recent = reactions->list(Reactions::Type::Recent);
+ const auto &myTags = reactions->list(Reactions::Type::MyTags);
+ const auto &tags = reactions->list(Reactions::Type::Tags);
const auto &all = item->reactions();
const auto limit = UniqueReactionsLimit(peer);
const auto premiumPossible = session->premiumPossible();
@@ -148,7 +172,20 @@ PossibleItemReactionsRef LookupPossibleReactions(
}
};
reactions->clearTemporary();
- if (limited) {
+ if (item->reactionsAreTags()) {
+ auto &&all = ranges::views::concat(myTags, tags);
+ result.recent.reserve(myTags.size() + tags.size());
+ for (const auto &reaction : all) {
+ if (premiumPossible
+ || ranges::contains(tags, reaction.id, &Reaction::id)) {
+ if (added.emplace(reaction.id).second) {
+ result.recent.push_back(&reaction);
+ }
+ }
+ }
+ result.customAllowed = premiumPossible;
+ result.tags = true;
+ } else if (limited) {
result.recent.reserve(all.size());
add([&](const Reaction &reaction) {
return ranges::contains(all, reaction.id, &MessageReaction::id);
@@ -198,23 +235,26 @@ PossibleItemReactionsRef LookupPossibleReactions(
result.customAllowed = (allowed.type == AllowedReactionsType::All)
&& premiumPossible;
}
- const auto i = ranges::find(
- result.recent,
- reactions->favoriteId(),
- &Reaction::id);
- if (i != end(result.recent) && i != begin(result.recent)) {
- std::rotate(begin(result.recent), i, i + 1);
+ if (!item->reactionsAreTags()) {
+ const auto i = ranges::find(
+ result.recent,
+ reactions->favoriteId(),
+ &Reaction::id);
+ if (i != end(result.recent) && i != begin(result.recent)) {
+ std::rotate(begin(result.recent), i, i + 1);
+ }
}
return result;
}
PossibleItemReactions::PossibleItemReactions(
const PossibleItemReactionsRef &other)
- : recent(other.recent | ranges::views::transform([](const auto &value) {
+: recent(other.recent | ranges::views::transform([](const auto &value) {
return *value;
}) | ranges::to_vector)
, morePremiumAvailable(other.morePremiumAvailable)
-, customAllowed(other.customAllowed) {
+, customAllowed(other.customAllowed)
+, tags(other.tags){
}
Reactions::Reactions(not_null owner)
@@ -285,16 +325,42 @@ void Reactions::refreshDefault() {
requestDefault();
}
+void Reactions::refreshMyTags() {
+ requestMyTags();
+}
+
+void Reactions::refreshMyTagsDelayed() {
+ if (_myTagsRequestId || _myTagsRequestScheduled) {
+ return;
+ }
+ _myTagsRequestScheduled = true;
+ base::call_delayed(kMyTagsRequestTimeout, &_owner->session(), [=] {
+ if (_myTagsRequestScheduled) {
+ requestMyTags();
+ }
+ });
+}
+
+void Reactions::refreshTags() {
+ requestTags();
+}
+
const std::vector &Reactions::list(Type type) const {
switch (type) {
case Type::Active: return _active;
case Type::Recent: return _recent;
case Type::Top: return _top;
case Type::All: return _available;
+ case Type::MyTags: return _myTags;
+ case Type::Tags: return _tags;
}
Unexpected("Type in Reactions::list.");
}
+const std::vector &Reactions::myTagsInfo() const {
+ return _myTagsInfo;
+}
+
ReactionId Reactions::favoriteId() const {
return _favoriteId;
}
@@ -319,6 +385,56 @@ void Reactions::setFavorite(const ReactionId &id) {
applyFavorite(id);
}
+void Reactions::incrementMyTag(const ReactionId &id) {
+ auto i = ranges::find(_myTagsInfo, id, &MyTagInfo::id);
+ if (i == end(_myTagsInfo)) {
+ _myTagsInfo.push_back({ .id = id, .count = 0 });
+ i = end(_myTagsInfo) - 1;
+ }
+ ++i->count;
+ while (i != begin(_myTagsInfo)) {
+ auto j = i - 1;
+ if (j->count >= i->count) {
+ break;
+ }
+ std::swap(*i, *j);
+ i = j;
+ }
+ scheduleMyTagsUpdate();
+}
+
+void Reactions::decrementMyTag(const ReactionId &id) {
+ auto i = ranges::find(_myTagsInfo, id, &MyTagInfo::id);
+ if (i->count <= 0) {
+ return;
+ }
+ --i->count;
+ while (i + 1 != end(_myTagsInfo)) {
+ auto j = i + 1;
+ if (j->count <= i->count) {
+ break;
+ }
+ std::swap(*i, *j);
+ i = j;
+ }
+ scheduleMyTagsUpdate();
+}
+
+void Reactions::scheduleMyTagsUpdate() {
+ _myTagsUpdateScheduled = true;
+ crl::on_main(&session(), [=] {
+ if (!_myTagsUpdateScheduled) {
+ return;
+ }
+ _myTagsUpdateScheduled = false;
+ _myTagsIds = _myTagsInfo | ranges::views::transform(
+ &MyTagInfo::id
+ ) | ranges::to_vector;
+ _myTags = resolveByIds(_myTagsIds, _unresolvedMyTags);
+ _myTagsUpdated.fire({});
+ });
+}
+
DocumentData *Reactions::chooseGenericAnimation(
not_null custom) const {
const auto sticker = custom->sticker();
@@ -380,6 +496,14 @@ rpl::producer<> Reactions::favoriteUpdates() const {
return _favoriteUpdated.events();
}
+rpl::producer<> Reactions::myTagsUpdates() const {
+ return _myTagsUpdated.events();
+}
+
+rpl::producer<> Reactions::tagsUpdates() const {
+ return _tagsUpdated.events();
+}
+
void Reactions::preloadImageFor(const ReactionId &id) {
if (_images.contains(id) || id.emoji().isEmpty()) {
return;
@@ -622,6 +746,46 @@ void Reactions::requestGeneric() {
}).send();
}
+void Reactions::requestMyTags() {
+ if (_myTagsRequestId) {
+ return;
+ }
+ auto &api = _owner->session().api();
+ _myTagsRequestScheduled = false;
+ _myTagsRequestId = api.request(MTPmessages_GetSavedReactionTags(
+ MTP_long(_myTagsHash)
+ )).done([=](const MTPmessages_SavedReactionTags &result) {
+ _myTagsRequestId = 0;
+ result.match([&](const MTPDmessages_savedReactionTags &data) {
+ updateMyTags(data);
+ }, [](const MTPDmessages_savedReactionTagsNotModified&) {
+ });
+ }).fail([=] {
+ _myTagsRequestId = 0;
+ _myTagsHash = 0;
+ }).send();
+}
+
+void Reactions::requestTags() {
+ if (_tagsRequestId) {
+ return;
+ }
+ auto &api = _owner->session().api();
+ _tagsRequestId = api.request(MTPmessages_GetDefaultTagReactions(
+ MTP_long(_tagsHash)
+ )).done([=](const MTPmessages_Reactions &result) {
+ _tagsRequestId = 0;
+ result.match([&](const MTPDmessages_reactions &data) {
+ updateTags(data);
+ }, [](const MTPDmessages_reactionsNotModified&) {
+ });
+ }).fail([=] {
+ _tagsRequestId = 0;
+ _tagsHash = 0;
+ }).send();
+
+}
+
void Reactions::updateTop(const MTPDmessages_reactions &data) {
_topHash = data.vhash().v;
_topIds = ListFromMTP(data);
@@ -690,6 +854,23 @@ void Reactions::updateGeneric(const MTPDmessages_stickerSet &data) {
}
}
+void Reactions::updateMyTags(const MTPDmessages_savedReactionTags &data) {
+ _myTagsHash = data.vhash().v;
+ _myTagsInfo = ListFromMTP(data);
+ _myTagsIds = _myTagsInfo | ranges::views::transform(
+ &MyTagInfo::id
+ ) | ranges::to_vector;
+ _myTags = resolveByIds(_myTagsIds, _unresolvedMyTags);
+ _myTagsUpdated.fire({});
+}
+
+void Reactions::updateTags(const MTPDmessages_reactions &data) {
+ _tagsHash = data.vhash().v;
+ _tagsIds = ListFromMTP(data);
+ _tags = resolveByIds(_tagsIds, _unresolvedTags);
+ _tagsUpdated.fire({});
+}
+
void Reactions::recentUpdated() {
_topRefreshTimer.callOnce(kTopRequestDelay);
_recentUpdated.fire({});
@@ -701,9 +882,25 @@ void Reactions::defaultUpdated() {
if (_genericAnimations.empty()) {
requestGeneric();
}
+ refreshMyTags();
+ refreshTags();
_defaultUpdated.fire({});
}
+void Reactions::myTagsUpdated() {
+ if (_genericAnimations.empty()) {
+ requestGeneric();
+ }
+ _myTagsUpdated.fire({});
+}
+
+void Reactions::tagsUpdated() {
+ if (_genericAnimations.empty()) {
+ requestGeneric();
+ }
+ _tagsUpdated.fire({});
+}
+
not_null Reactions::resolveListener() {
return static_cast(this);
}
@@ -715,6 +912,10 @@ void Reactions::customEmojiResolveDone(not_null document) {
const auto top = (i != end(_unresolvedTop));
const auto j = _unresolvedRecent.find(id);
const auto recent = (j != end(_unresolvedRecent));
+ const auto k = _unresolvedMyTags.find(id);
+ const auto myTag = (k != end(_unresolvedMyTags));
+ const auto l = _unresolvedTags.find(id);
+ const auto tag = (l != end(_unresolvedTags));
if (favorite) {
_unresolvedFavoriteId = ReactionId();
_favorite = resolveById(_favoriteId);
@@ -727,6 +928,14 @@ void Reactions::customEmojiResolveDone(not_null document) {
_unresolvedRecent.erase(j);
_recent = resolveByIds(_recentIds, _unresolvedRecent);
}
+ if (myTag) {
+ _unresolvedMyTags.erase(k);
+ _myTags = resolveByIds(_myTagsIds, _unresolvedMyTags);
+ }
+ if (tag) {
+ _unresolvedTags.erase(l);
+ _tags = resolveByIds(_tagsIds, _unresolvedTags);
+ }
if (favorite) {
_favoriteUpdated.fire({});
}
@@ -736,6 +945,12 @@ void Reactions::customEmojiResolveDone(not_null document) {
if (recent) {
_recentUpdated.fire({});
}
+ if (myTag) {
+ _myTagsUpdated.fire({});
+ }
+ if (tag) {
+ _tagsUpdated.fire({});
+ }
}
std::optional Reactions::resolveById(const ReactionId &id) {
@@ -1003,6 +1218,10 @@ void MessageReactions::add(const ReactionId &id, bool addToRecent) {
return;
}
auto my = 0;
+ const auto tags = _item->reactionsAreTags();
+ if (tags) {
+ history->owner().reactions().incrementMyTag(id);
+ }
_list.erase(ranges::remove_if(_list, [&](MessageReaction &one) {
const auto removing = one.my && (my == myLimit || ++my == myLimit);
if (!removing) {
@@ -1024,6 +1243,9 @@ void MessageReactions::add(const ReactionId &id, bool addToRecent) {
}
}
}
+ if (tags) {
+ history->owner().reactions().decrementMyTag(one.id);
+ }
return removed;
}), end(_list));
const auto peer = history->peer;
diff --git a/Telegram/SourceFiles/data/data_message_reactions.h b/Telegram/SourceFiles/data/data_message_reactions.h
index 61b29107d..5e8721665 100644
--- a/Telegram/SourceFiles/data/data_message_reactions.h
+++ b/Telegram/SourceFiles/data/data_message_reactions.h
@@ -42,6 +42,7 @@ struct PossibleItemReactionsRef {
std::vector> recent;
bool morePremiumAvailable = false;
bool customAllowed = false;
+ bool tags = false;
};
struct PossibleItemReactions {
@@ -51,11 +52,18 @@ struct PossibleItemReactions {
std::vector recent;
bool morePremiumAvailable = false;
bool customAllowed = false;
+ bool tags = false;
};
[[nodiscard]] PossibleItemReactionsRef LookupPossibleReactions(
not_null item);
+struct MyTagInfo {
+ ReactionId id;
+ QString title;
+ int count = 0;
+};
+
class Reactions final : private CustomEmojiManager::Listener {
public:
explicit Reactions(not_null owner);
@@ -70,17 +78,25 @@ public:
void refreshRecent();
void refreshRecentDelayed();
void refreshDefault();
+ void refreshMyTags();
+ void refreshMyTagsDelayed();
+ void refreshTags();
enum class Type {
Active,
Recent,
Top,
All,
+ MyTags,
+ Tags,
};
[[nodiscard]] const std::vector &list(Type type) const;
+ [[nodiscard]] const std::vector &myTagsInfo() const;
[[nodiscard]] ReactionId favoriteId() const;
[[nodiscard]] const Reaction *favorite() const;
void setFavorite(const ReactionId &id);
+ void incrementMyTag(const ReactionId &id);
+ void decrementMyTag(const ReactionId &id);
[[nodiscard]] DocumentData *chooseGenericAnimation(
not_null custom) const;
@@ -88,6 +104,8 @@ public:
[[nodiscard]] rpl::producer<> recentUpdates() const;
[[nodiscard]] rpl::producer<> defaultUpdates() const;
[[nodiscard]] rpl::producer<> favoriteUpdates() const;
+ [[nodiscard]] rpl::producer<> myTagsUpdates() const;
+ [[nodiscard]] rpl::producer<> tagsUpdates() const;
enum class ImageSize {
BottomInfo,
@@ -130,14 +148,20 @@ private:
void requestRecent();
void requestDefault();
void requestGeneric();
+ void requestMyTags();
+ void requestTags();
void updateTop(const MTPDmessages_reactions &data);
void updateRecent(const MTPDmessages_reactions &data);
void updateDefault(const MTPDmessages_availableReactions &data);
void updateGeneric(const MTPDmessages_stickerSet &data);
+ void updateMyTags(const MTPDmessages_savedReactionTags &data);
+ void updateTags(const MTPDmessages_reactions &data);
void recentUpdated();
void defaultUpdated();
+ void myTagsUpdated();
+ void tagsUpdated();
[[nodiscard]] std::optional resolveById(const ReactionId &id);
[[nodiscard]] std::vector resolveByIds(
@@ -145,6 +169,7 @@ private:
base::flat_set &unresolved);
void resolve(const ReactionId &id);
void applyFavorite(const ReactionId &id);
+ void scheduleMyTagsUpdate();
[[nodiscard]] std::optional parse(
const MTPAvailableReaction &entry);
@@ -167,6 +192,13 @@ private:
std::vector _recent;
std::vector _recentIds;
base::flat_set _unresolvedRecent;
+ std::vector _myTags;
+ std::vector _myTagsIds;
+ std::vector _myTagsInfo;
+ base::flat_set _unresolvedMyTags;
+ std::vector _tags;
+ std::vector _tagsIds;
+ base::flat_set _unresolvedTags;
std::vector _top;
std::vector _topIds;
base::flat_set _unresolvedTop;
@@ -184,6 +216,8 @@ private:
rpl::event_stream<> _recentUpdated;
rpl::event_stream<> _defaultUpdated;
rpl::event_stream<> _favoriteUpdated;
+ rpl::event_stream<> _myTagsUpdated;
+ rpl::event_stream<> _tagsUpdated;
// We need &i->second stay valid while inserting new items.
// So we use std::map instead of base::flat_map here.
@@ -203,6 +237,14 @@ private:
mtpRequestId _genericRequestId = 0;
+ mtpRequestId _myTagsRequestId = 0;
+ bool _myTagsRequestScheduled = false;
+ bool _myTagsUpdateScheduled = false;
+ uint64 _myTagsHash = 0;
+
+ mtpRequestId _tagsRequestId = 0;
+ uint64 _tagsHash = 0;
+
base::flat_map _images;
rpl::lifetime _imagesLoadLifetime;
bool _waitingForList = false;
diff --git a/Telegram/SourceFiles/data/data_saved_messages.cpp b/Telegram/SourceFiles/data/data_saved_messages.cpp
index 66bc87609..d81f97fe5 100644
--- a/Telegram/SourceFiles/data/data_saved_messages.cpp
+++ b/Telegram/SourceFiles/data/data_saved_messages.cpp
@@ -20,6 +20,8 @@ namespace {
constexpr auto kPerPage = 50;
constexpr auto kFirstPerPage = 10;
+constexpr auto kListPerPage = 100;
+constexpr auto kListFirstPerPage = 20;
} // namespace
@@ -82,7 +84,7 @@ void SavedMessages::sendLoadMore() {
MTP_int(_offsetDate),
MTP_int(_offsetId),
_offsetPeer ? _offsetPeer->input : MTP_inputPeerEmpty(),
- MTP_int(kPerPage),
+ MTP_int(_offsetId ? kListPerPage : kListFirstPerPage),
MTP_long(0)) // hash
).done([=](const MTPmessages_SavedDialogs &result) {
apply(result, false);
diff --git a/Telegram/SourceFiles/data/data_search_controller.cpp b/Telegram/SourceFiles/data/data_search_controller.cpp
index 688ce4ae9..43c5efb66 100644
--- a/Telegram/SourceFiles/data/data_search_controller.cpp
+++ b/Telegram/SourceFiles/data/data_search_controller.cpp
@@ -98,6 +98,7 @@ std::optional PrepareSearchRequest(
MTP_string(query),
MTP_inputPeerEmpty(),
MTPInputPeer(), // saved_peer_id
+ MTPVector(), // saved_reaction
MTP_int(topicRootId),
filter,
MTP_int(0), // min_date
diff --git a/Telegram/SourceFiles/data/data_session.cpp b/Telegram/SourceFiles/data/data_session.cpp
index 3885b9155..05405f78e 100644
--- a/Telegram/SourceFiles/data/data_session.cpp
+++ b/Telegram/SourceFiles/data/data_session.cpp
@@ -1109,7 +1109,7 @@ void Session::watchForOffline(not_null user, TimeId now) {
return;
}
const auto till = user->onlineTill;
- const auto [i, ok] = _watchingForOffline.emplace(user, till);
+ const auto &[i, ok] = _watchingForOffline.emplace(user, till);
if (!ok) {
if (i->second == till) {
return;
@@ -1638,7 +1638,7 @@ HistoryItem *Session::changeMessageId(PeerId peerId, MsgId wasId, MsgId nowId) {
}
const auto item = i->second;
list->erase(i);
- const auto [j, ok] = list->emplace(nowId, item);
+ const auto &[j, ok] = list->emplace(nowId, item);
if (!peerIsChannel(peerId)) {
if (IsServerMsgId(wasId)) {
@@ -1801,7 +1801,7 @@ void Session::registerHighlightProcess(
not_null item) {
Expects(item->inHighlightProcess());
- const auto [i, ok] = _highlightings.emplace(processId, item);
+ const auto &[i, ok] = _highlightings.emplace(processId, item);
Ensures(ok);
}
@@ -4272,7 +4272,7 @@ not_null Session::folder(FolderId id) {
if (const auto result = folderLoaded(id)) {
return result;
}
- const auto [it, ok] = _folders.emplace(
+ const auto &[it, ok] = _folders.emplace(
id,
std::make_unique(this, id));
return it->second.get();
diff --git a/Telegram/SourceFiles/data/data_stories.cpp b/Telegram/SourceFiles/data/data_stories.cpp
index 5d9326465..79d573bab 100644
--- a/Telegram/SourceFiles/data/data_stories.cpp
+++ b/Telegram/SourceFiles/data/data_stories.cpp
@@ -238,7 +238,7 @@ Story *Stories::applySingle(PeerId peerId, const MTPstoryItem &story) {
void Stories::requestPeerStories(
not_null peer,
Fn done) {
- const auto [i, ok] = _requestingPeerStories.emplace(peer);
+ const auto &[i, ok] = _requestingPeerStories.emplace(peer);
if (done) {
i->second.push_back(std::move(done));
}
diff --git a/Telegram/SourceFiles/data/data_types.h b/Telegram/SourceFiles/data/data_types.h
index 31c976859..46f75419a 100644
--- a/Telegram/SourceFiles/data/data_types.h
+++ b/Telegram/SourceFiles/data/data_types.h
@@ -313,6 +313,8 @@ enum class MessageFlag : uint64 {
ShowSimilarChannels = (1ULL << 41),
Sponsored = (1ULL << 42),
+
+ ReactionsAreTags = (1ULL << 43),
};
inline constexpr bool is_flag_type(MessageFlag) { return true; }
using MessageFlags = base::flags;
diff --git a/Telegram/SourceFiles/dialogs/dialogs.style b/Telegram/SourceFiles/dialogs/dialogs.style
index b42c490a3..083cf3a54 100644
--- a/Telegram/SourceFiles/dialogs/dialogs.style
+++ b/Telegram/SourceFiles/dialogs/dialogs.style
@@ -623,3 +623,6 @@ dialogsStoriesTooltipHide: IconButton(defaultIconButton) {
searchedBarHeight: 32px;
searchedBarFont: normalFont;
searchedBarPosition: point(17px, 7px);
+
+dialogsSearchTagSkip: point(8px, 4px);
+dialogsSearchTagBottom: 10px;
diff --git a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp
index afbf0ca77..cea479bb4 100644
--- a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp
+++ b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp
@@ -15,6 +15,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "dialogs/dialogs_indexed_list.h"
#include "dialogs/dialogs_widget.h"
#include "dialogs/dialogs_search_from_controllers.h"
+#include "dialogs/dialogs_search_tags.h"
#include "history/history.h"
#include "history/history_item.h"
#include "core/shortcuts.h"
@@ -40,7 +41,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_chat_filters.h"
#include "data/data_cloud_file.h"
#include "data/data_changes.h"
+#include "data/data_message_reactions.h"
#include "data/data_saved_messages.h"
+#include "data/data_saved_sublist.h"
#include "data/data_stories.h"
#include "data/stickers/data_stickers.h"
#include "data/data_send_action.h"
@@ -477,22 +480,30 @@ int InnerWidget::peerSearchOffset() const {
+ st::searchedBarHeight;
}
-int InnerWidget::searchedOffset() const {
- auto result = peerSearchOffset();
+int InnerWidget::searchInChatOffset() const {
+ auto result = peerSearchOffset() - st::searchedBarHeight;
if (!_peerSearchResults.empty()) {
result += (_peerSearchResults.size() * st::dialogsRowHeight)
+ st::searchedBarHeight;
}
- result += searchInChatSkip();
return result;
}
+int InnerWidget::searchedOffset() const {
+ return searchInChatOffset()
+ + searchInChatSkip()
+ + st::searchedBarHeight;
+}
+
int InnerWidget::searchInChatSkip() const {
auto result = 0;
+ if (_searchTags) {
+ result += _searchTags->height();
+ }
if (_searchInChat) {
result += st::searchedBarHeight + st::dialogsSearchInHeight;
}
- if (_searchFromPeer) {
+ if (_searchFromShown) {
if (_searchInChat) {
result += st::lineWidth;
}
@@ -1111,17 +1122,25 @@ void InnerWidget::paintSearchInChat(
auto height = searchInChatSkip();
auto top = 0;
+ if (_searchTags) {
+ const auto height = _searchTags->height();
+ p.fillRect(0, top, width(), height, currentBg());
+ const auto position = QPoint(_searchTagsLeft, 0);
+ _searchTags->paint(p, position, context.now, context.paused);
+ top += height;
+ }
p.setFont(st::searchedBarFont);
if (_searchInChat) {
- top += st::searchedBarHeight;
- p.fillRect(0, 0, width(), top, st::searchedBarBg);
+ const auto bar = st::searchedBarHeight;
+ p.fillRect(0, top, width(), top + bar, st::searchedBarBg);
p.setPen(st::searchedBarFg);
- p.drawTextLeft(st::searchedBarPosition.x(), st::searchedBarPosition.y(), width(), tr::lng_dlg_search_in(tr::now));
+ p.drawTextLeft(st::searchedBarPosition.x(), top + st::searchedBarPosition.y(), width(), tr::lng_dlg_search_in(tr::now));
+ top += bar;
}
auto fullRect = QRect(0, top, width(), height - top);
p.fillRect(fullRect, currentBg());
if (_searchInChat) {
- if (_searchFromPeer) {
+ if (_searchFromShown) {
p.fillRect(QRect(0, top + st::dialogsSearchInHeight, width(), st::lineWidth), st::shadowFg);
}
p.setPen(st::dialogsNameFg);
@@ -1135,15 +1154,17 @@ void InnerWidget::paintSearchInChat(
} else {
paintSearchInPeer(p, peer, _searchInChatUserpic, top, _searchInChatText);
}
+ } else if (const auto sublist = _searchInChat.sublist()) {
+ paintSearchInSaved(p, top, _searchInChatText);
} else {
Unexpected("Empty Key in paintSearchInChat.");
}
top += st::dialogsSearchInHeight + st::lineWidth;
}
- if (_searchFromPeer) {
+ if (_searchFromShown) {
p.setPen(st::dialogsTextFg);
p.setTextPalette(st::dialogsSearchFromPalette);
- paintSearchInPeer(p, _searchFromPeer, _searchFromUserUserpic, top, _searchFromUserText);
+ paintSearchInPeer(p, _searchFromShown, _searchFromUserUserpic, top, _searchFromUserText);
p.restoreTextPalette();
}
}
@@ -1276,6 +1297,21 @@ void InnerWidget::selectByMouse(QPoint globalPosition) {
_lastMousePosition = globalPosition;
_lastRowLocalMouseX = local.x();
+ const auto tagBase = QPoint(_searchTagsLeft, searchInChatOffset());
+ const auto tagPoint = local - tagBase;
+ const auto inTags = _searchTags
+ && QRect(
+ tagBase,
+ QSize(width() - 2 * _searchTagsLeft, _searchTags->height())
+ ).contains(local);
+ const auto tagLink = inTags
+ ? _searchTags->lookupHandler(tagPoint)
+ : nullptr;
+ ClickHandler::setActive(tagLink);
+ if (inTags) {
+ setCursor(tagLink ? style::cur_pointer : style::cur_default);
+ }
+
const auto w = width();
const auto mouseY = local.y();
clearIrrelevantState();
@@ -1370,7 +1406,7 @@ void InnerWidget::selectByMouse(QPoint globalPosition) {
updateSelectedRow();
}
}
- if (wasSelected != isSelected()) {
+ if (!inTags && wasSelected != isSelected()) {
setCursor(wasSelected ? style::cur_default : style::cur_pointer);
}
}
@@ -1452,6 +1488,7 @@ void InnerWidget::mousePressEvent(QMouseEvent *e) {
QSize(width(), _st->height),
row->repaint());
}
+ ClickHandler::pressed();
if (anim::Disabled()
&& (!_pressed || !_pressed->entry()->isPinnedDialog(_filterId))) {
mousePressReleased(e->globalPos(), e->button(), e->modifiers());
@@ -1743,6 +1780,9 @@ void InnerWidget::mousePressReleased(
chooseRow(modifiers, pressedTopicRootId);
}
}
+ if (auto activated = ClickHandler::unpressed()) {
+ ActivateClickHandler(window(), activated, ClickContext{ button });
+ }
}
void InnerWidget::setCollapsedPressed(int pressed) {
@@ -1825,9 +1865,10 @@ void InnerWidget::moveCancelSearchButtons() {
st::columnMinimalWidthLeft - _narrowWidth);
const auto left = widthForCancelButton - st::dialogsSearchInSkip - _cancelSearchInChat->width();
const auto top = (st::dialogsSearchInHeight - st::dialogsCancelSearchInPeer.height) / 2;
- _cancelSearchInChat->moveToLeft(left, st::searchedBarHeight + top);
- const auto skip = _searchInChat ? (st::searchedBarHeight + st::dialogsSearchInHeight + st::lineWidth) : 0;
- _cancelSearchFromUser->moveToLeft(left, skip + top);
+ const auto skip = st::searchedBarHeight + (_searchTags ? _searchTags->height() : 0);
+ _cancelSearchInChat->moveToLeft(left, skip + top);
+ const auto next = _searchInChat ? (skip + st::dialogsSearchInHeight + st::lineWidth) : 0;
+ _cancelSearchFromUser->moveToLeft(left, next + top);
}
void InnerWidget::dialogRowReplaced(
@@ -2330,7 +2371,9 @@ void InnerWidget::applyFilterUpdate(QString newFilter, bool force) {
newFilter = words.isEmpty() ? QString() : words.join(' ');
if (newFilter != _filter || force) {
_filter = newFilter;
- if (_filter.isEmpty() && !_searchFromPeer) {
+ if (_filter.isEmpty()
+ && !_searchFromPeer
+ && _searchTagsSelected.empty()) {
clearFilter();
} else {
setState(WidgetState::Filtered);
@@ -2350,7 +2393,9 @@ void InnerWidget::applyFilterUpdate(QString newFilter, bool force) {
top += i->row->height();
}
};
- if (!_searchInChat && !_searchFromPeer && !words.isEmpty()) {
+ if (!_searchInChat
+ && !_searchFromPeer
+ && !words.isEmpty()) {
if (_savedSublists) {
const auto owner = &session().data();
append(owner->savedMessages().chatsList()->indexed());
@@ -2413,7 +2458,7 @@ void InnerWidget::appendToFiltered(Key key) {
}
auto row = std::make_unique(key, 0, 0);
row->recountHeight(_narrowRatio);
- const auto [i, ok] = _filterResultsGlobal.emplace(key, std::move(row));
+ const auto &[i, ok] = _filterResultsGlobal.emplace(key, std::move(row));
const auto height = filteredHeight();
_filterResults.emplace_back(i->second.get());
_filterResults.back().top = height;
@@ -2791,6 +2836,11 @@ void InnerWidget::refresh(bool toTop) {
return refreshWithCollapsedRows(toTop);
}
refreshEmptyLabel();
+ if (_searchTags) {
+ _searchTagsLeft = st::dialogsFilterSkip
+ + st::dialogsFilterPadding.x();
+ _searchTags->resizeToWidth(width() - 2 * _searchTagsLeft);
+ }
auto h = 0;
if (_state == WidgetState::Default) {
if (_shownList->empty()) {
@@ -2918,26 +2968,73 @@ bool InnerWidget::hasFilteredResults() const {
return !_filterResults.empty() && _hashtagResults.empty();
}
-void InnerWidget::searchInChat(Key key, PeerData *from) {
+void InnerWidget::searchInChat(
+ Key key,
+ PeerData *from,
+ std::vector tags) {
_searchInMigrated = nullptr;
- if (const auto peer = key.peer()) {
+ const auto sublist = key.sublist();
+ const auto peer = sublist ? session().user().get() : key.peer();
+ if (peer) {
if (const auto migrateTo = peer->migrateTo()) {
- return searchInChat(peer->owner().history(migrateTo), from);
+ const auto to = peer->owner().history(migrateTo);
+ return searchInChat(to, from, tags);
} else if (const auto migrateFrom = peer->migrateFrom()) {
_searchInMigrated = peer->owner().history(migrateFrom);
}
+
+ if (peer->isSelf()) {
+ const auto reactions = &peer->owner().reactions();
+ const auto list = [=] {
+ // Disable reactions as tags for now.
+ //return reactions->list(Data::Reactions::Type::MyTags);
+ return std::vector();
+ };
+ _searchTags = std::make_unique(
+ &peer->owner(),
+ rpl::single(
+ list()
+ ) | rpl::then(
+ reactions->myTagsUpdates() | rpl::map(list)
+ ),
+ tags);
+
+ _searchTags->selectedValue(
+ ) | rpl::start_with_next([=](std::vector &&list) {
+ _searchTagsSelected = std::move(list);
+ }, _searchTags->lifetime());
+
+ _searchTags->repaintRequests() | rpl::start_with_next([=] {
+ const auto height = _searchTags->height();
+ update(0, searchInChatOffset(), width(), height);
+ }, _searchTags->lifetime());
+
+ _searchTags->heightValue() | rpl::filter(
+ rpl::mappers::_1 > 0
+ ) | rpl::start_with_next([=] {
+ refresh();
+ moveCancelSearchButtons();
+ }, _searchTags->lifetime());
+ } else {
+ _searchTags = nullptr;
+ _searchTagsSelected.clear();
+ }
+ } else {
+ _searchTags = nullptr;
+ _searchTagsSelected.clear();
}
_searchInChat = key;
_searchFromPeer = from;
+ _searchFromShown = key.sublist() ? key.sublist()->peer().get() : from;
if (_searchInChat) {
onHashtagFilterUpdate(QStringView());
_cancelSearchInChat->show();
} else {
_cancelSearchInChat->hide();
}
- if (_searchFromPeer) {
+ if (_searchFromShown) {
_cancelSearchFromUser->show();
- _searchFromUserUserpic = _searchFromPeer->createUserpicView();
+ _searchFromUserUserpic = _searchFromShown->createUserpicView();
} else {
_cancelSearchFromUser->hide();
_searchFromUserUserpic = {};
@@ -2946,7 +3043,7 @@ void InnerWidget::searchInChat(Key key, PeerData *from) {
refreshSearchInChatLabel();
}
- if (const auto peer = _searchInChat.peer()) {
+ if (peer) {
_searchInChatUserpic = peer->createUserpicView();
} else {
_searchInChatUserpic = {};
@@ -2957,6 +3054,13 @@ void InnerWidget::searchInChat(Key key, PeerData *from) {
_searchInChat || !_filter.isEmpty());
}
+auto InnerWidget::searchTagsValue() const
+-> rpl::producer> {
+ return _searchTags
+ ? _searchTags->selectedValue()
+ : rpl::single(std::vector());
+}
+
void InnerWidget::refreshSearchInChatLabel() {
const auto dialog = [&] {
if (const auto topic = _searchInChat.topic()) {
@@ -2968,6 +3072,8 @@ void InnerWidget::refreshSearchInChatLabel() {
return tr::lng_replies_messages(tr::now);
}
return peer->name();
+ } else if (_searchInChat.sublist()) {
+ return tr::lng_saved_messages(tr::now);
}
return QString();
}();
@@ -2977,7 +3083,7 @@ void InnerWidget::refreshSearchInChatLabel() {
dialog,
Ui::DialogTextOptions());
}
- const auto from = _searchFromPeer ? _searchFromPeer->name() : QString();
+ const auto from = _searchFromShown ? _searchFromShown->name() : u""_q;
if (!from.isEmpty()) {
const auto fromUserText = tr::lng_dlg_search_from(
tr::now,
@@ -3809,7 +3915,7 @@ void InnerWidget::setupShortcuts() {
auto &&folders = ranges::views::zip(
Shortcuts::kShowFolder,
ranges::views::ints(0, ranges::unreachable));
- for (const auto [command, index] : folders) {
+ for (const auto &[command, index] : folders) {
const auto select = (command == Command::ShowFolderLast)
? (filtersCount - 1)
: std::clamp(index, 0, filtersCount - 1);
@@ -3836,7 +3942,7 @@ void InnerWidget::setupShortcuts() {
auto &&pinned = ranges::views::zip(
kPinned,
ranges::views::ints(0, ranges::unreachable));
- for (const auto [command, index] : pinned) {
+ for (const auto &[command, index] : pinned) {
request->check(command) && request->handle([=, index = index] {
const auto list = (_filterId
? session().data().chatsFilters().chatsList(_filterId)
diff --git a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.h b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.h
index 7915bd912..0f12e7211 100644
--- a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.h
+++ b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.h
@@ -43,6 +43,7 @@ namespace Data {
class Thread;
class Folder;
class Forum;
+struct ReactionId;
} // namespace Data
namespace Dialogs::Ui {
@@ -57,6 +58,7 @@ namespace Dialogs {
class Row;
class FakeRow;
class IndexedList;
+class SearchTags;
struct ChosenRow {
Key key;
@@ -137,7 +139,12 @@ public:
}
[[nodiscard]] bool hasFilteredResults() const;
- void searchInChat(Key key, PeerData *from);
+ void searchInChat(
+ Key key,
+ PeerData *from,
+ std::vector tags);
+ [[nodiscard]] auto searchTagsValue() const
+ -> rpl::producer>;
void applyFilterUpdate(QString newFilter, bool force = false);
void onHashtagFilterUpdate(QStringView newFilter);
@@ -325,6 +332,7 @@ private:
[[nodiscard]] int filteredIndex(int y) const;
[[nodiscard]] int filteredHeight(int till = -1) const;
[[nodiscard]] int peerSearchOffset() const;
+ [[nodiscard]] int searchInChatOffset() const;
[[nodiscard]] int searchedOffset() const;
[[nodiscard]] int searchInChatSkip() const;
@@ -478,10 +486,14 @@ private:
Key _searchInChat;
History *_searchInMigrated = nullptr;
PeerData *_searchFromPeer = nullptr;
+ PeerData *_searchFromShown = nullptr;
mutable Ui::PeerUserpicView _searchInChatUserpic;
mutable Ui::PeerUserpicView _searchFromUserUserpic;
Ui::Text::String _searchInChatText;
Ui::Text::String _searchFromUserText;
+ std::unique_ptr _searchTags;
+ std::vector _searchTagsSelected;
+ int _searchTagsLeft = 0;
RowDescriptor _menuRow;
base::flat_map<
diff --git a/Telegram/SourceFiles/dialogs/dialogs_search_tags.cpp b/Telegram/SourceFiles/dialogs/dialogs_search_tags.cpp
new file mode 100644
index 000000000..7f0aa16eb
--- /dev/null
+++ b/Telegram/SourceFiles/dialogs/dialogs_search_tags.cpp
@@ -0,0 +1,269 @@
+/*
+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 "dialogs/dialogs_search_tags.h"
+
+#include "base/qt/qt_key_modifiers.h"
+#include "data/stickers/data_custom_emoji.h"
+#include "data/data_document.h"
+#include "data/data_message_reactions.h"
+#include "data/data_session.h"
+#include "history/view/reactions/history_view_reactions.h"
+#include "ui/effects/animation_value.h"
+#include "ui/power_saving.h"
+#include "styles/style_chat.h"
+#include "styles/style_dialogs.h"
+
+namespace Dialogs {
+
+struct SearchTags::Tag {
+ Data::ReactionId id;
+ std::unique_ptr custom;
+ mutable QImage image;
+ QRect geometry;
+ ClickHandlerPtr link;
+ bool selected = false;
+};
+
+SearchTags::SearchTags(
+ not_null owner,
+ rpl::producer> tags,
+ std::vector selected)
+: _owner(owner)
+, _added(selected) {
+ std::move(
+ tags
+ ) | rpl::start_with_next([=](const std::vector &list) {
+ fill(list);
+ }, _lifetime);
+
+ // Mark the `selected` reactions as selected in `_tags`.
+ for (const auto &id : selected) {
+ const auto i = ranges::find(_tags, id, &Tag::id);
+ if (i != end(_tags)) {
+ i->selected = true;
+ }
+ }
+
+ style::PaletteChanged(
+ ) | rpl::start_with_next([=] {
+ _normalBg = _selectedBg = QImage();
+ }, _lifetime);
+}
+
+SearchTags::~SearchTags() = default;
+
+void SearchTags::fill(const std::vector &list) {
+ const auto selected = collectSelected();
+ _tags.clear();
+ _tags.reserve(list.size());
+ const auto link = [&](Data::ReactionId id) {
+ return std::make_shared(crl::guard(this, [=] {
+ const auto i = ranges::find(_tags, id, &Tag::id);
+ if (i != end(_tags)) {
+ if (!i->selected && !base::IsShiftPressed()) {
+ for (auto &tag : _tags) {
+ tag.selected = false;
+ }
+ }
+ i->selected = !i->selected;
+ _selectedChanges.fire({});
+ }
+ }));
+ };
+ const auto push = [&](Data::ReactionId id) {
+ const auto customId = id.custom();
+ _tags.push_back({
+ .id = id,
+ .custom = (customId
+ ? _owner->customEmojiManager().create(
+ customId,
+ [=] { _repaintRequests.fire({}); })
+ : nullptr),
+ .link = link(id),
+ .selected = ranges::contains(selected, id),
+ });
+ if (!customId) {
+ _owner->reactions().preloadImageFor(id);
+ }
+ };
+ for (const auto &reaction : list) {
+ push(reaction.id);
+ }
+ for (const auto &reaction : _added) {
+ if (!ranges::contains(_tags, reaction, &Tag::id)) {
+ push(reaction);
+ }
+ }
+ if (_width > 0) {
+ layout();
+ }
+}
+
+void SearchTags::layout() {
+ Expects(_width > 0);
+
+ const auto &bg = validateBg(false);
+ const auto skip = st::dialogsSearchTagSkip;
+ const auto size = bg.size() / bg.devicePixelRatio();
+ const auto xsingle = size.width() + skip.x();
+ const auto ysingle = size.height() + skip.y();
+ const auto columns = std::max((_width + skip.x()) / xsingle, 1);
+ const auto rows = (_tags.size() + columns - 1) / columns;
+ for (auto row = 0; row != rows; ++row) {
+ for (auto column = 0; column != columns; ++column) {
+ const auto index = row * columns + column;
+ if (index >= _tags.size()) {
+ break;
+ }
+ const auto x = column * xsingle;
+ const auto y = row * ysingle;
+ _tags[index].geometry = QRect(QPoint(x, y), size);
+ }
+ }
+ const auto bottom = st::dialogsSearchTagBottom;
+ _height = rows ? (rows * ysingle - skip.y() + bottom) : 0;
+}
+
+void SearchTags::resizeToWidth(int width) {
+ if (_width == width || width <= 0) {
+ return;
+ }
+ _width = width;
+ layout();
+}
+
+int SearchTags::height() const {
+ return _height.current();
+}
+
+rpl::producer SearchTags::heightValue() const {
+ return _height.value();
+}
+
+rpl::producer<> SearchTags::repaintRequests() const {
+ return _repaintRequests.events();
+}
+
+ClickHandlerPtr SearchTags::lookupHandler(QPoint point) const {
+ for (const auto &tag : _tags) {
+ if (tag.geometry.contains(point.x(), point.y())) {
+ return tag.link;
+ }
+ }
+ return nullptr;
+}
+
+auto SearchTags::selectedValue() const
+-> rpl::producer> {
+ return _selectedChanges.events() | rpl::map([=] {
+ return collectSelected();
+ });
+}
+
+void SearchTags::paintCustomFrame(
+ QPainter &p,
+ not_null emoji,
+ QPoint innerTopLeft,
+ crl::time now,
+ bool paused,
+ const QColor &textColor) const {
+ if (_customCache.isNull()) {
+ using namespace Ui::Text;
+ const auto size = st::emojiSize;
+ const auto factor = style::DevicePixelRatio();
+ const auto adjusted = AdjustCustomEmojiSize(size);
+ _customCache = QImage(
+ QSize(adjusted, adjusted) * factor,
+ QImage::Format_ARGB32_Premultiplied);
+ _customCache.setDevicePixelRatio(factor);
+ _customSkip = (size - adjusted) / 2;
+ }
+ _customCache.fill(Qt::transparent);
+ auto q = QPainter(&_customCache);
+ emoji->paint(q, {
+ .textColor = textColor,
+ .now = now,
+ .paused = paused || On(PowerSaving::kEmojiChat),
+ });
+ q.end();
+ _customCache = Images::Round(
+ std::move(_customCache),
+ (Images::Option::RoundLarge
+ | Images::Option::RoundSkipTopRight
+ | Images::Option::RoundSkipBottomRight));
+
+ p.drawImage(
+ innerTopLeft + QPoint(_customSkip, _customSkip),
+ _customCache);
+}
+
+void SearchTags::paint(
+ QPainter &p,
+ QPoint position,
+ crl::time now,
+ bool paused) const {
+ const auto size = st::reactionInlineSize;
+ const auto skip = (size - st::reactionInlineImage) / 2;
+ const auto padding = st::reactionInlinePadding;
+ for (const auto &tag : _tags) {
+ const auto geometry = tag.geometry.translated(position);
+ p.drawImage(geometry.topLeft(), validateBg(tag.selected));
+ if (!tag.custom && tag.image.isNull()) {
+ tag.image = _owner->reactions().resolveImageFor(
+ tag.id,
+ ::Data::Reactions::ImageSize::InlineList);
+ }
+ const auto inner = geometry.marginsRemoved(padding);
+ const auto image = QRect(
+ inner.topLeft() + QPoint(skip, skip),
+ QSize(st::reactionInlineImage, st::reactionInlineImage));
+ if (const auto custom = tag.custom.get()) {
+ const auto textFg = tag.selected
+ ? st::dialogsNameFgActive->c
+ : st::dialogsNameFgOver->c;
+ paintCustomFrame(
+ p,
+ custom,
+ inner.topLeft(),
+ now,
+ paused,
+ textFg);
+ } else if (!tag.image.isNull()) {
+ p.drawImage(image.topLeft(), tag.image);
+ }
+ }
+}
+
+const QImage &SearchTags::validateBg(bool selected) const {
+ using namespace HistoryView::Reactions;
+ auto &image = selected ? _selectedBg : _normalBg;
+ if (image.isNull()) {
+ const auto tagBg = selected
+ ? st::dialogsBgActive->c
+ : st::dialogsBgOver->c;
+ const auto dotBg = selected
+ ? anim::with_alpha(tagBg, InlineList::TagDotAlpha())
+ : st::windowSubTextFg->c;
+ image = InlineList::PrepareTagBg(tagBg, dotBg);
+ }
+ return image;
+}
+
+std::vector SearchTags::collectSelected() const {
+ return _tags | ranges::views::filter(
+ &Tag::selected
+ ) | ranges::views::transform(
+ &Tag::id
+ ) | ranges::to_vector;
+}
+
+rpl::lifetime &SearchTags::lifetime() {
+ return _lifetime;
+}
+
+} // namespace Dialogs
diff --git a/Telegram/SourceFiles/dialogs/dialogs_search_tags.h b/Telegram/SourceFiles/dialogs/dialogs_search_tags.h
new file mode 100644
index 000000000..4c52162eb
--- /dev/null
+++ b/Telegram/SourceFiles/dialogs/dialogs_search_tags.h
@@ -0,0 +1,80 @@
+/*
+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
+*/
+#pragma once
+
+#include "base/weak_ptr.h"
+
+namespace Data {
+class Session;
+struct Reaction;
+struct ReactionId;
+} // namespace Data
+
+namespace Ui::Text {
+class CustomEmoji;
+} // namespace Ui::Text
+
+namespace Dialogs {
+
+class SearchTags final : public base::has_weak_ptr {
+public:
+ SearchTags(
+ not_null owner,
+ rpl::producer> tags,
+ std::vector selected);
+ ~SearchTags();
+
+ void resizeToWidth(int width);
+ [[nodiscard]] int height() const;
+ [[nodiscard]] rpl::producer heightValue() const;
+ [[nodiscard]] rpl::producer<> repaintRequests() const;
+
+ [[nodiscard]] ClickHandlerPtr lookupHandler(QPoint point) const;
+ [[nodiscard]] auto selectedValue() const
+ -> rpl::producer>;
+
+ void paint(
+ QPainter &p,
+ QPoint position,
+ crl::time now,
+ bool paused) const;
+
+ [[nodiscard]] rpl::lifetime &lifetime();
+
+private:
+ struct Tag;
+
+ void fill(const std::vector &list);
+ void paintCustomFrame(
+ QPainter &p,
+ not_null emoji,
+ QPoint innerTopLeft,
+ crl::time now,
+ bool paused,
+ const QColor &textColor) const;
+ void layout();
+ [[nodiscard]] std::vector collectSelected() const;
+ [[nodiscard]] const QImage &validateBg(bool selected) const;
+
+ const not_null _owner;
+ std::vector _added;
+ std::vector _tags;
+ rpl::event_stream<> _selectedChanges;
+ rpl::event_stream<> _repaintRequests;
+ mutable QImage _normalBg;
+ mutable QImage _selectedBg;
+ mutable QImage _customCache;
+ mutable int _customSkip = 0;
+ rpl::variable _height;
+ int _width = 0;
+
+ rpl::lifetime _lifetime;
+
+};
+
+} // namespace Dialogs
diff --git a/Telegram/SourceFiles/dialogs/dialogs_widget.cpp b/Telegram/SourceFiles/dialogs/dialogs_widget.cpp
index 4dc510f95..f0104f030 100644
--- a/Telegram/SourceFiles/dialogs/dialogs_widget.cpp
+++ b/Telegram/SourceFiles/dialogs/dialogs_widget.cpp
@@ -67,6 +67,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_changes.h"
#include "data/data_download_manager.h"
#include "data/data_chat_filters.h"
+#include "data/data_saved_sublist.h"
#include "data/data_stories.h"
#include "info/downloads/info_downloads_widget.h"
#include "info/info_memento.h"
@@ -832,7 +833,7 @@ void Widget::setupStories() {
{
return;
}
-
+
_stories->verticalScrollEvents(
) | rpl::start_with_next([=](not_null e) {
_scroll->viewportEvent(e);
@@ -1733,7 +1734,7 @@ void Widget::loadMoreBlockedByDate() {
bool Widget::searchMessages(bool searchCache) {
auto result = false;
auto q = currentSearchQuery().trimmed();
- if (q.isEmpty() && !_searchFromAuthor) {
+ if (q.isEmpty() && !_searchFromAuthor && _searchTags.empty()) {
cancelSearchRequest();
_api.request(base::take(_peerSearchRequest)).cancel();
_api.request(base::take(_topicSearchRequest)).cancel();
@@ -1750,6 +1751,7 @@ bool Widget::searchMessages(bool searchCache) {
if (i != _searchCache.end()) {
_searchQuery = q;
_searchQueryFrom = _searchFromAuthor;
+ _searchQueryTags = _searchTags;
_searchNextRate = 0;
_searchFull = _searchFullMigrated = false;
cancelSearchRequest();
@@ -1761,9 +1763,12 @@ bool Widget::searchMessages(bool searchCache) {
0);
result = true;
}
- } else if (_searchQuery != q || _searchQueryFrom != _searchFromAuthor) {
+ } else if (_searchQuery != q
+ || _searchQueryFrom != _searchFromAuthor
+ || _searchQueryTags != _searchTags) {
_searchQuery = q;
_searchQueryFrom = _searchFromAuthor;
+ _searchQueryTags = _searchTags;
_searchNextRate = 0;
_searchFull = _searchFullMigrated = false;
cancelSearchRequest();
@@ -1772,18 +1777,31 @@ bool Widget::searchMessages(bool searchCache) {
auto &histories = session().data().histories();
const auto type = Data::Histories::RequestType::History;
const auto history = session().data().history(peer);
+ const auto sublist = _openedForum
+ ? nullptr
+ : _searchInChat.sublist();
+ const auto fromPeer = sublist ? nullptr : _searchQueryFrom;
+ const auto savedPeer = sublist
+ ? sublist->peer().get()
+ : nullptr;
_searchInHistoryRequest = histories.sendRequest(history, type, [=](Fn finish) {
const auto type = SearchRequestType::PeerFromStart;
using Flag = MTPmessages_Search::Flag;
_searchRequest = session().api().request(MTPmessages_Search(
MTP_flags((topic ? Flag::f_top_msg_id : Flag())
- | (_searchQueryFrom ? Flag::f_from_id : Flag())),
+ | (fromPeer ? Flag::f_from_id : Flag())
+ | (savedPeer ? Flag::f_saved_peer_id : Flag())
+ | (_searchQueryTags.empty()
+ ? Flag()
+ : Flag::f_saved_reaction)),
peer->input,
MTP_string(_searchQuery),
- (_searchQueryFrom
- ? _searchQueryFrom->input
- : MTP_inputPeerEmpty()),
- MTPInputPeer(), // saved_peer_id
+ (fromPeer ? fromPeer->input : MTP_inputPeerEmpty()),
+ (savedPeer ? savedPeer->input : MTP_inputPeerEmpty()),
+ MTP_vector_from_range(
+ _searchQueryTags | ranges::views::transform(
+ Data::ReactionToMTP
+ )),
MTP_int(topic ? topic->rootId() : 0),
MTP_inputMessagesFilterEmpty(),
MTP_int(0), // min_date
@@ -1887,6 +1905,7 @@ bool Widget::searchMessages(bool searchCache) {
bool Widget::searchForPeersRequired(const QString &query) const {
return !_searchInChat
&& !_searchFromAuthor
+ && _searchTags.empty()
&& !_openedForum
&& !query.isEmpty()
&& (query[0] != '#');
@@ -1895,6 +1914,7 @@ bool Widget::searchForPeersRequired(const QString &query) const {
bool Widget::searchForTopicsRequired(const QString &query) const {
return !_searchInChat
&& !_searchFromAuthor
+ && _searchTags.empty()
&& _openedForum
&& !query.isEmpty()
&& (query[0] != '#')
@@ -1911,7 +1931,7 @@ void Widget::showMainMenu() {
controller()->widget()->showMainMenu();
}
-void Widget::searchMessages(const QString &query, Key inChat, UserData *from) {
+void Widget::searchMessages(QString query, Key inChat, UserData *from) {
if (_childList) {
const auto forum = controller()->shownForum().current();
const auto topic = inChat.topic();
@@ -1926,6 +1946,12 @@ void Widget::searchMessages(const QString &query, Key inChat, UserData *from) {
controller()->closeFolder();
}
+ auto tags = std::vector();
+ if (const auto tagId = Data::SearchTagFromQuery(query)) {
+ inChat = session().data().history(session().user());
+ query = QString();
+ tags.push_back(tagId);
+ }
const auto inChatChanged = [&] {
const auto inPeer = inChat.peer();
const auto inTopic = inChat.topic();
@@ -1938,7 +1964,7 @@ void Widget::searchMessages(const QString &query, Key inChat, UserData *from) {
} else if ((inTopic || (inPeer && !inPeer->isForum()))
&& (inChat == _searchInChat)) {
return false;
- } else if (const auto inPeer = inChat.peer()) {
+ } else if (inPeer) {
if (const auto to = inPeer->migrateTo()) {
if (to == _searchInChat.peer() && !_searchInChat.topic()) {
return false;
@@ -1947,10 +1973,12 @@ void Widget::searchMessages(const QString &query, Key inChat, UserData *from) {
}
return true;
}();
- if ((currentSearchQuery() != query) || inChatChanged) {
+ if ((currentSearchQuery() != query)
+ || inChatChanged
+ || _searchTags != tags) {
if (inChat) {
cancelSearch();
- setSearchInChat(inChat);
+ setSearchInChat(inChat, nullptr, tags);
}
setSearchQuery(query);
applyFilterUpdate(true);
@@ -2017,6 +2045,13 @@ void Widget::searchMore() {
const auto topic = searchInTopic();
const auto type = Data::Histories::RequestType::History;
const auto history = session().data().history(peer);
+ const auto sublist = _openedForum
+ ? nullptr
+ : _searchInChat.sublist();
+ const auto fromPeer = sublist ? nullptr : _searchQueryFrom;
+ const auto savedPeer = sublist
+ ? sublist->peer().get()
+ : nullptr;
_searchInHistoryRequest = histories.sendRequest(history, type, [=](Fn finish) {
const auto type = _lastSearchId
? SearchRequestType::PeerFromOffset
@@ -2024,13 +2059,19 @@ void Widget::searchMore() {
using Flag = MTPmessages_Search::Flag;
_searchRequest = session().api().request(MTPmessages_Search(
MTP_flags((topic ? Flag::f_top_msg_id : Flag())
- | (_searchQueryFrom ? Flag::f_from_id : Flag())),
+ | (fromPeer ? Flag::f_from_id : Flag())
+ | (savedPeer ? Flag::f_saved_peer_id : Flag())
+ | (_searchQueryTags.empty()
+ ? Flag()
+ : Flag::f_saved_reaction)),
peer->input,
MTP_string(_searchQuery),
- (_searchQueryFrom
- ? _searchQueryFrom->input
- : MTP_inputPeerEmpty()),
- MTPInputPeer(), // saved_peer_id
+ (fromPeer ? fromPeer->input : MTP_inputPeerEmpty()),
+ (savedPeer ? savedPeer->input : MTP_inputPeerEmpty()),
+ MTP_vector_from_range(
+ _searchQueryTags | ranges::views::transform(
+ Data::ReactionToMTP
+ )),
MTP_int(topic ? topic->rootId() : 0),
MTP_inputMessagesFilterEmpty(),
MTP_int(0), // min_date
@@ -2104,6 +2145,7 @@ void Widget::searchMore() {
? _searchQueryFrom->input
: MTP_inputPeerEmpty()),
MTPInputPeer(), // saved_peer_id
+ MTPVector(), // saved_reaction
MTPint(), // top_msg_id
MTP_inputMessagesFilterEmpty(),
MTP_int(0), // min_date
@@ -2423,7 +2465,7 @@ void Widget::applyFilterUpdate(bool force) {
updateStoriesVisibility();
const auto filterText = currentSearchQuery();
_inner->applyFilterUpdate(filterText, force);
- if (filterText.isEmpty() && !_searchFromAuthor) {
+ if (filterText.isEmpty() && !_searchFromAuthor && _searchTags.empty()) {
clearSearchCache();
}
_cancelSearch->toggle(!filterText.isEmpty(), anim::type::normal);
@@ -2439,7 +2481,9 @@ void Widget::applyFilterUpdate(bool force) {
_peerSearchQuery = QString();
}
- if (_chooseFromUser->toggled() || _searchFromAuthor) {
+ if (_chooseFromUser->toggled()
+ || _searchFromAuthor
+ || !_searchTags.empty()) {
auto switchToChooseFrom = HistoryView::SwitchToChooseFromQuery();
if (_lastFilterText != switchToChooseFrom
&& switchToChooseFrom.startsWith(_lastFilterText)
@@ -2583,9 +2627,12 @@ void Widget::searchInChat(Key chat) {
searchMessages(QString(), chat);
}
-bool Widget::setSearchInChat(Key chat, PeerData *from) {
+bool Widget::setSearchInChat(
+ Key chat,
+ PeerData *from,
+ std::vector tags) {
if (_childList) {
- if (_childList->setSearchInChat(chat, from)) {
+ if (_childList->setSearchInChat(chat, from, tags)) {
return true;
}
hideChildList();
@@ -2621,7 +2668,8 @@ bool Widget::setSearchInChat(Key chat, PeerData *from) {
if (_layout != Layout::Main) {
return false;
} else if (const auto migrateTo = peer->migrateTo()) {
- return setSearchInChat(peer->owner().history(migrateTo), from);
+ const auto to = peer->owner().history(migrateTo);
+ return setSearchInChat(to, from, tags);
} else if (const auto migrateFrom = peer->migrateFrom()) {
_searchInMigrated = peer->owner().history(migrateFrom);
}
@@ -2640,7 +2688,20 @@ bool Widget::setSearchInChat(Key chat, PeerData *from) {
if (_searchInChat && _layout == Layout::Main) {
controller()->closeFolder();
}
- _inner->searchInChat(_searchInChat, _searchFromAuthor);
+ _searchTags = std::move(tags);
+ _inner->searchInChat(_searchInChat, _searchFromAuthor, _searchTags);
+ _searchTagsLifetime = _inner->searchTagsValue(
+ ) | rpl::start_with_next([=](std::vector &&list) {
+ if (_searchTags != list) {
+ clearSearchCache();
+ _searchTags = std::move(list);
+ if (_searchTags.empty()) {
+ applyFilterUpdate(true);
+ } else {
+ searchMessages();
+ }
+ }
+ });
if (_subsectionTopBar) {
_subsectionTopBar->searchEnableJumpToDate(
_openedForum && _searchInChat);
@@ -2653,6 +2714,12 @@ bool Widget::setSearchInChat(Key chat, PeerData *from) {
return true;
}
+bool Widget::setSearchInChat(
+ Key chat,
+ PeerData *from) {
+ return setSearchInChat(chat, from, {});
+}
+
void Widget::clearSearchCache() {
_searchCache.clear();
_singleMessageSearch.clear();
@@ -2661,6 +2728,7 @@ void Widget::clearSearchCache() {
}
_searchQuery = QString();
_searchQueryFrom = nullptr;
+ _searchQueryTags.clear();
_topicSearchQuery = QString();
_topicSearchOffsetDate = 0;
_topicSearchOffsetId = _topicSearchOffsetTopicId = 0;
@@ -3072,6 +3140,8 @@ void Widget::cancelSearchRequest() {
PeerData *Widget::searchInPeer() const {
return _openedForum
? _openedForum->channel().get()
+ : _searchInChat.sublist()
+ ? session().user().get()
: _searchInChat.peer();
}
diff --git a/Telegram/SourceFiles/dialogs/dialogs_widget.h b/Telegram/SourceFiles/dialogs/dialogs_widget.h
index 5fce5f9e3..3713ed721 100644
--- a/Telegram/SourceFiles/dialogs/dialogs_widget.h
+++ b/Telegram/SourceFiles/dialogs/dialogs_widget.h
@@ -22,6 +22,7 @@ class Error;
namespace Data {
class Forum;
enum class StorySourcesList : uchar;
+struct ReactionId;
} // namespace Data
namespace Main {
@@ -116,7 +117,7 @@ public:
void scrollToEntry(const RowDescriptor &entry);
- void searchMessages(const QString &query, Key inChat = {}, UserData *from = nullptr);
+ void searchMessages(QString query, Key inChat = {}, UserData *from = nullptr);
void searchTopics();
void searchMore();
@@ -179,7 +180,13 @@ private:
void trackScroll(not_null widget);
[[nodiscard]] bool searchForPeersRequired(const QString &query) const;
[[nodiscard]] bool searchForTopicsRequired(const QString &query) const;
- bool setSearchInChat(Key chat, PeerData *from = nullptr);
+ bool setSearchInChat(
+ Key chat,
+ PeerData *from,
+ std::vector tags);
+ bool setSearchInChat(
+ Key chat,
+ PeerData *from = nullptr);
void showCalendar();
void showSearchFrom();
void showMainMenu();
@@ -285,6 +292,8 @@ private:
Dialogs::Key _searchInChat;
History *_searchInMigrated = nullptr;
PeerData *_searchFromAuthor = nullptr;
+ std::vector _searchTags;
+ rpl::lifetime _searchTagsLifetime;
QString _lastFilterText;
rpl::event_stream> _storiesContents;
@@ -313,6 +322,7 @@ private:
QString _searchQuery;
PeerData *_searchQueryFrom = nullptr;
+ std::vector _searchQueryTags;
int32 _searchNextRate = 0;
bool _searchFull = false;
bool _searchFullMigrated = false;
diff --git a/Telegram/SourceFiles/editor/editor_crop.cpp b/Telegram/SourceFiles/editor/editor_crop.cpp
index 78a91aa6a..54f8369c2 100644
--- a/Telegram/SourceFiles/editor/editor_crop.cpp
+++ b/Telegram/SourceFiles/editor/editor_crop.cpp
@@ -212,8 +212,8 @@ void Crop::computeDownState(const QPoint &p) {
const auto edge = mouseState(p);
const auto &inner = _innerRect;
const auto &crop = _cropPaint;
- const auto [iLeft, iTop, iRight, iBottom] = RectEdges(inner);
- const auto [cLeft, cTop, cRight, cBottom] = RectEdges(crop);
+ const auto &[iLeft, iTop, iRight, iBottom] = RectEdges(inner);
+ const auto &[cLeft, cTop, cRight, cBottom] = RectEdges(crop);
_down = InfoAtDown{
.rect = crop,
.edge = edge,
diff --git a/Telegram/SourceFiles/export/export_api_wrap.cpp b/Telegram/SourceFiles/export/export_api_wrap.cpp
index 61f29b16c..b23329c03 100644
--- a/Telegram/SourceFiles/export/export_api_wrap.cpp
+++ b/Telegram/SourceFiles/export/export_api_wrap.cpp
@@ -1553,7 +1553,7 @@ void ApiWrap::appendChatsSlice(
continue;
}
}
- const auto [i, ok] = process.indexByPeer.emplace(
+ const auto &[i, ok] = process.indexByPeer.emplace(
info.peerId,
nextIndex);
if (ok) {
@@ -1625,6 +1625,7 @@ void ApiWrap::requestChatMessages(
MTP_string(), // query
MTP_inputPeerSelf(),
MTPInputPeer(), // saved_peer_id
+ MTPVector(), // saved_reaction
MTPint(), // top_msg_id
MTP_inputMessagesFilterEmpty(),
MTP_int(0), // min_date
diff --git a/Telegram/SourceFiles/export/output/export_output_html.cpp b/Telegram/SourceFiles/export/output/export_output_html.cpp
index a5181618d..74c1f1f3d 100644
--- a/Telegram/SourceFiles/export/output/export_output_html.cpp
+++ b/Telegram/SourceFiles/export/output/export_output_html.cpp
@@ -1563,7 +1563,7 @@ QByteArray HtmlWriter::Wrap::pushStickerMedia(
const QString &basePath) {
using namespace Data;
- const auto [thumb, size] = WriteImageThumb(
+ const auto &[thumb, size] = WriteImageThumb(
basePath,
data.file.relativePath,
CalculateThumbSize(
@@ -1730,7 +1730,7 @@ QByteArray HtmlWriter::Wrap::pushPhotoMedia(
const QString &basePath) {
using namespace Data;
- const auto [thumb, size] = WriteImageThumb(
+ const auto &[thumb, size] = WriteImageThumb(
basePath,
data.image.file.relativePath,
CalculateThumbSize(
@@ -2790,7 +2790,7 @@ Result HtmlWriter::writeDialogSlice(const Data::MessagesSlice &data) {
_settings.path,
FormatDateText(date)));
}
- const auto [info, content] = _chat->pushMessage(
+ const auto &[info, content] = _chat->pushMessage(
message,
previous,
_dialog,
diff --git a/Telegram/SourceFiles/history/admin_log/history_admin_log_inner.cpp b/Telegram/SourceFiles/history/admin_log/history_admin_log_inner.cpp
index fb6f950e0..2598e1f36 100644
--- a/Telegram/SourceFiles/history/admin_log/history_admin_log_inner.cpp
+++ b/Telegram/SourceFiles/history/admin_log/history_admin_log_inner.cpp
@@ -943,10 +943,10 @@ void InnerWidget::paintEvent(QPaintEvent *e) {
auto clip = e->rect();
auto context = _controller->preparePaintContext({
.theme = _theme.get(),
- .visibleAreaTop = _visibleTop,
- .visibleAreaTopGlobal = mapToGlobal(QPoint(0, _visibleTop)).y(),
- .visibleAreaWidth = width(),
.clip = clip,
+ .visibleAreaPositionGlobal = mapToGlobal(QPoint(0, _visibleTop)),
+ .visibleAreaTop = _visibleTop,
+ .visibleAreaWidth = width(),
});
if (_items.empty() && _upLoaded && _downLoaded) {
paintEmpty(p, context.st);
diff --git a/Telegram/SourceFiles/history/history.cpp b/Telegram/SourceFiles/history/history.cpp
index 629fae9c9..8c4dfa5dc 100644
--- a/Telegram/SourceFiles/history/history.cpp
+++ b/Telegram/SourceFiles/history/history.cpp
@@ -470,7 +470,7 @@ not_null History::insertItem(
std::unique_ptr item) {
Expects(item != nullptr);
- const auto [i, ok] = _messages.insert(std::move(item));
+ const auto &[i, ok] = _messages.insert(std::move(item));
const auto result = i->get();
owner().registerMessage(result);
diff --git a/Telegram/SourceFiles/history/history_inner_widget.cpp b/Telegram/SourceFiles/history/history_inner_widget.cpp
index 54241b6b7..b5083b3b3 100644
--- a/Telegram/SourceFiles/history/history_inner_widget.cpp
+++ b/Telegram/SourceFiles/history/history_inner_widget.cpp
@@ -984,14 +984,14 @@ void HistoryInner::paintEmpty(
Ui::ChatPaintContext HistoryInner::preparePaintContext(
const QRect &clip) const {
- const auto visibleAreaTopGlobal = mapToGlobal(
- QPoint(0, _visibleAreaTop)).y();
+ const auto visibleAreaPositionGlobal = mapToGlobal(
+ QPoint(0, _visibleAreaTop));
return _controller->preparePaintContext({
.theme = _theme.get(),
- .visibleAreaTop = _visibleAreaTop,
- .visibleAreaTopGlobal = visibleAreaTopGlobal,
- .visibleAreaWidth = width(),
.clip = clip,
+ .visibleAreaPositionGlobal = visibleAreaPositionGlobal,
+ .visibleAreaTop = _visibleAreaTop,
+ .visibleAreaWidth = width(),
});
}
@@ -1995,7 +1995,7 @@ void HistoryInner::mouseActionFinish(
&& !_selected.empty()
&& _selected.cbegin()->second != FullSelection
&& !hasCopyRestriction(_selected.cbegin()->first)) {
- const auto [item, selection] = *_selected.cbegin();
+ const auto &[item, selection] = *_selected.cbegin();
if (const auto view = viewByItem(item)) {
TextUtilities::SetClipboardText(
view->selectedText(selection),
@@ -2935,7 +2935,7 @@ TextForMimeData HistoryInner::getSelectedText() const {
return TextForMimeData();
}
if (selected.cbegin()->second != FullSelection) {
- const auto [item, selection] = *selected.cbegin();
+ const auto &[item, selection] = *selected.cbegin();
if (const auto view = viewByItem(item)) {
return view->selectedText(selection);
}
diff --git a/Telegram/SourceFiles/history/history_item.cpp b/Telegram/SourceFiles/history/history_item.cpp
index 3ce819c7c..85d9bce45 100644
--- a/Telegram/SourceFiles/history/history_item.cpp
+++ b/Telegram/SourceFiles/history/history_item.cpp
@@ -2461,6 +2461,11 @@ const std::vector &HistoryItem::reactions() const {
return _reactions ? _reactions->list() : kEmpty;
}
+bool HistoryItem::reactionsAreTags() const {
+ // Disable reactions as tags for now.
+ return false;// _flags & MessageFlag::ReactionsAreTags;
+}
+
auto HistoryItem::recentReactions() const
-> const base::flat_map<
Data::ReactionId,
@@ -3634,31 +3639,40 @@ bool HistoryItem::changeReactions(const MTPMessageReactions *reactions) {
}
if (!reactions) {
_flags &= ~MessageFlag::CanViewReactions;
+ if (_history->peer->isSelf()) {
+ _flags |= MessageFlag::ReactionsAreTags;
+ }
return (base::take(_reactions) != nullptr);
}
- return reactions->match([&](const MTPDmessageReactions &data) {
- if (data.is_can_see_list()) {
- _flags |= MessageFlag::CanViewReactions;
- } else {
- _flags &= ~MessageFlag::CanViewReactions;
+ const auto &data = reactions->data();
+ const auto empty = data.vresults().v.isEmpty();
+ if (data.is_reactions_as_tags()
+ || (empty && _history->peer->isSelf())) {
+ _flags |= MessageFlag::ReactionsAreTags;
+ } else {
+ _flags &= ~MessageFlag::ReactionsAreTags;
+ }
+ if (data.is_can_see_list()) {
+ _flags |= MessageFlag::CanViewReactions;
+ } else {
+ _flags &= ~MessageFlag::CanViewReactions;
+ }
+ if (empty) {
+ return (base::take(_reactions) != nullptr);
+ } else if (!_reactions) {
+ _reactions = std::make_unique(this);
+ }
+ const auto min = data.is_min();
+ const auto &list = data.vresults().v;
+ const auto &recent = data.vrecent_reactions().value_or_empty();
+ if (min && hasUnreadReaction()) {
+ // We can't update reactions from min if we have unread.
+ if (_reactions->checkIfChanged(list, recent, min)) {
+ updateReactionsUnknown();
}
- if (data.vresults().v.isEmpty()) {
- return (base::take(_reactions) != nullptr);
- } else if (!_reactions) {
- _reactions = std::make_unique(this);
- }
- const auto min = data.is_min();
- const auto &list = data.vresults().v;
- const auto &recent = data.vrecent_reactions().value_or_empty();
- if (min && hasUnreadReaction()) {
- // We can't update reactions from min if we have unread.
- if (_reactions->checkIfChanged(list, recent, min)) {
- updateReactionsUnknown();
- }
- return false;
- }
- return _reactions->change(list, recent, min);
- });
+ return false;
+ }
+ return _reactions->change(list, recent, min);
}
void HistoryItem::applyTTL(const MTPDmessage &data) {
diff --git a/Telegram/SourceFiles/history/history_item.h b/Telegram/SourceFiles/history/history_item.h
index 63db67fc3..929c286cf 100644
--- a/Telegram/SourceFiles/history/history_item.h
+++ b/Telegram/SourceFiles/history/history_item.h
@@ -455,6 +455,7 @@ public:
not_null from) const;
[[nodiscard]] crl::time lastReactionsRefreshTime() const;
+ [[nodiscard]] bool reactionsAreTags() const;
[[nodiscard]] bool hasDirectLink() const;
[[nodiscard]] bool changesWallPaper() const;
diff --git a/Telegram/SourceFiles/history/history_item_helpers.cpp b/Telegram/SourceFiles/history/history_item_helpers.cpp
index 3521646d3..06bd10837 100644
--- a/Telegram/SourceFiles/history/history_item_helpers.cpp
+++ b/Telegram/SourceFiles/history/history_item_helpers.cpp
@@ -813,8 +813,3 @@ void ClearMediaAsExpired(not_null item) {
}
}
}
-
-[[nodiscard]] bool IsVoiceOncePlayable(not_null item) {
- const auto settings = &AyuSettings::getInstance();
- return !item->out() && item->media()->ttlSeconds() && !settings->saveDeletedMessages;
-}
diff --git a/Telegram/SourceFiles/history/history_item_helpers.h b/Telegram/SourceFiles/history/history_item_helpers.h
index f96d9c092..c438eccbe 100644
--- a/Telegram/SourceFiles/history/history_item_helpers.h
+++ b/Telegram/SourceFiles/history/history_item_helpers.h
@@ -158,4 +158,3 @@ ClickHandlerPtr JumpToStoryClickHandler(
void ShowTrialTranscribesToast(int left, TimeId until);
void ClearMediaAsExpired(not_null item);
-[[nodiscard]] bool IsVoiceOncePlayable(not_null item);
diff --git a/Telegram/SourceFiles/history/history_widget.cpp b/Telegram/SourceFiles/history/history_widget.cpp
index 2db71e78b..682509c4b 100644
--- a/Telegram/SourceFiles/history/history_widget.cpp
+++ b/Telegram/SourceFiles/history/history_widget.cpp
@@ -2614,12 +2614,15 @@ bool HistoryWidget::updateReplaceMediaButton() {
return false;
}
_replaceMedia.create(this, st::historyReplaceMedia);
+ const auto hideDuration = st::historyReplaceMedia.ripple.hideDuration;
_replaceMedia->setClickedCallback([=] {
- EditCaptionBox::StartMediaReplace(
- controller(),
- { _history->peer->id, _editMsgId },
- _field->getTextWithTags(),
- crl::guard(_list, [=] { cancelEdit(); }));
+ base::call_delayed(hideDuration, this, [=] {
+ EditCaptionBox::StartMediaReplace(
+ controller(),
+ { _history->peer->id, _editMsgId },
+ _field->getTextWithTags(),
+ crl::guard(_list, [=] { cancelEdit(); }));
+ });
});
return true;
}
@@ -6309,7 +6312,8 @@ std::optional HistoryWidget::cornerButtonsDownShown() {
if (!_list || _firstLoadRequest) {
return false;
}
- if (_voiceRecordBar->isLockPresent()) {
+ if (_voiceRecordBar->isLockPresent()
+ || _voiceRecordBar->isTTLButtonShown()) {
return false;
}
if (!_history->loadedAtBottom() || _cornerButtons.replyReturn()) {
diff --git a/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.cpp b/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.cpp
index b25be58f9..e8e81511e 100644
--- a/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.cpp
+++ b/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.cpp
@@ -7,6 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#include "history/view/controls/history_view_compose_controls.h"
+#include "base/call_delayed.h"
#include "base/event_filter.h"
#include "base/platform/base_platform_info.h"
#include "base/qt_signal_producer.h"
@@ -2692,12 +2693,15 @@ bool ComposeControls::updateReplaceMediaButton() {
_replaceMedia = std::make_unique(
_wrap.get(),
st::historyReplaceMedia);
+ const auto hideDuration = st::historyReplaceMedia.ripple.hideDuration;
_replaceMedia->setClickedCallback([=] {
- EditCaptionBox::StartMediaReplace(
- _regularWindow,
- _editingId,
- _field->getTextWithTags(),
- crl::guard(_wrap.get(), [=] { cancelEditMessage(); }));
+ base::call_delayed(hideDuration, _wrap.get(), [=] {
+ EditCaptionBox::StartMediaReplace(
+ _regularWindow,
+ _editingId,
+ _field->getTextWithTags(),
+ crl::guard(_wrap.get(), [=] { cancelEditMessage(); }));
+ });
});
return true;
}
@@ -2921,6 +2925,10 @@ bool ComposeControls::isLockPresent() const {
return _voiceRecordBar->isLockPresent();
}
+bool ComposeControls::isTTLButtonShown() const {
+ return _voiceRecordBar->isTTLButtonShown();
+}
+
rpl::producer ComposeControls::lockShowStarts() const {
return _voiceRecordBar->lockShowStarts();
}
diff --git a/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.h b/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.h
index 9ae515d6c..a846cd4ee 100644
--- a/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.h
+++ b/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.h
@@ -220,6 +220,7 @@ public:
[[nodiscard]] rpl::producer lockShowStarts() const;
[[nodiscard]] bool isLockPresent() const;
+ [[nodiscard]] bool isTTLButtonShown() const;
[[nodiscard]] bool isRecording() const;
[[nodiscard]] bool isRecordingPressed() const;
[[nodiscard]] rpl::producer recordingActiveValue() const;
diff --git a/Telegram/SourceFiles/history/view/controls/history_view_voice_record_bar.cpp b/Telegram/SourceFiles/history/view/controls/history_view_voice_record_bar.cpp
index 85bee7a6c..d958139c2 100644
--- a/Telegram/SourceFiles/history/view/controls/history_view_voice_record_bar.cpp
+++ b/Telegram/SourceFiles/history/view/controls/history_view_voice_record_bar.cpp
@@ -31,7 +31,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/effects/animation_value.h"
#include "ui/effects/ripple_animation.h"
#include "ui/text/format_values.h"
+#include "ui/text/text_utilities.h"
#include "ui/painter.h"
+#include "ui/widgets/tooltip.h"
#include "ui/rect.h"
#include "styles/style_chat.h"
#include "styles/style_chat_helpers.h"
@@ -284,7 +286,6 @@ protected:
private:
const style::RecordBar &_st;
const QRect _rippleRect;
- const QString _text;
Ui::Animations::Simple _activeAnimation;
@@ -296,11 +297,11 @@ TTLButton::TTLButton(
: RippleButton(parent, st.lock.ripple)
, _st(st)
, _rippleRect(Rect(Size(st::historyRecordLockTopShadow.width()))
- - (st::historyRecordLockRippleMargin))
-, _text(u"1"_q) {
- resize(Size(st::historyRecordLockTopShadow.width()));
+ - (st::historyRecordLockRippleMargin)) {
+ QWidget::resize(Size(st::historyRecordLockTopShadow.width()));
+ Ui::AbstractButton::setDisabled(true);
- setClickedCallback([=] {
+ Ui::AbstractButton::setClickedCallback([=] {
Ui::AbstractButton::setDisabled(!Ui::AbstractButton::isDisabled());
const auto isActive = !Ui::AbstractButton::isDisabled();
_activeAnimation.start(
@@ -310,6 +311,77 @@ TTLButton::TTLButton(
st::historyRecordVoiceShowDuration);
});
+ Ui::RpWidget::shownValue() | rpl::filter(
+ rpl::mappers::_1
+ ) | rpl::take(1) | rpl::start_with_next([=] {
+ auto text = rpl::conditional(
+ Core::App().settings().ttlVoiceClickTooltipHiddenValue(),
+ tr::lng_record_once_active_tooltip(
+ Ui::Text::RichLangValue),
+ tr::lng_record_once_first_tooltip(
+ Ui::Text::RichLangValue));
+ const auto tooltip = Ui::CreateChild(
+ parent.get(),
+ object_ptr>(
+ parent.get(),
+ Ui::MakeNiceTooltipLabel(
+ parent,
+ std::move(text),
+ st::historyMessagesTTLLabel.minWidth,
+ st::ttlMediaImportantTooltipLabel),
+ st::defaultImportantTooltip.padding),
+ st::historyRecordTooltip);
+ Ui::RpWidget::geometryValue(
+ ) | rpl::start_with_next([=](const QRect &r) {
+ if (r.isEmpty()) {
+ return;
+ }
+ tooltip->pointAt(r, RectPart::Right, [=](QSize size) {
+ return QPoint(
+ r.left()
+ - size.width()
+ - st::defaultImportantTooltip.padding.left(),
+ r.top()
+ + r.height()
+ - size.height()
+ + st::historyRecordTooltip.padding.top());
+ });
+ }, tooltip->lifetime());
+ tooltip->show();
+ if (!Core::App().settings().ttlVoiceClickTooltipHidden()) {
+ clicks(
+ ) | rpl::take(1) | rpl::start_with_next([=] {
+ Core::App().settings().setTtlVoiceClickTooltipHidden(true);
+ }, tooltip->lifetime());
+ tooltip->toggleAnimated(true);
+ } else {
+ tooltip->toggleFast(false);
+ }
+
+ clicks(
+ ) | rpl::start_with_next([=] {
+ const auto toggled = !Ui::AbstractButton::isDisabled();
+ tooltip->toggleAnimated(toggled);
+
+ if (toggled) {
+ constexpr auto kTimeout = crl::time(3000);
+ tooltip->hideAfter(kTimeout);
+ }
+ }, tooltip->lifetime());
+
+ Ui::RpWidget::geometryValue(
+ ) | rpl::map([=](const QRect &r) {
+ return (r.left() + r.width() > parentWidget()->width());
+ }) | rpl::distinct_until_changed(
+ ) | rpl::start_with_next([=](bool toHide) {
+ const auto isFirstTooltip =
+ !Core::App().settings().ttlVoiceClickTooltipHidden();
+ if (isFirstTooltip || (!isFirstTooltip && toHide)) {
+ tooltip->toggleAnimated(!toHide);
+ }
+ }, tooltip->lifetime());
+ }, lifetime());
+
paintRequest(
) | rpl::start_with_next([=](const QRect &clip) {
auto p = QPainter(this);
@@ -318,49 +390,16 @@ TTLButton::TTLButton(
Ui::RippleButton::paintRipple(p, _rippleRect.x(), _rippleRect.y());
- const auto innerRect = QRectF(inner)
- - st::historyRecordLockMargin * 2;
- auto hq = PainterHighQualityEnabler(p);
-
- p.setFont(st::semiboldFont);
- p.setPen(_st.lock.fg);
- p.drawText(inner, _text, style::al_center);
-
- const auto penWidth = st::historyRecordTTLLineWidth;
- auto pen = QPen(_st.lock.fg);
- pen.setJoinStyle(Qt::RoundJoin);
- pen.setCapStyle(Qt::RoundCap);
- pen.setWidthF(penWidth);
-
- p.setPen(pen);
- p.setBrush(Qt::NoBrush);
- p.drawArc(innerRect, arc::kQuarterLength, arc::kHalfLength);
-
- {
- p.setClipRect(innerRect
- - QMarginsF(
- innerRect.width() / 2,
- -penWidth,
- -penWidth,
- -penWidth));
- pen.setStyle(Qt::DotLine);
- p.setPen(pen);
- p.drawEllipse(innerRect);
- p.setClipping(false);
- }
-
const auto activeProgress = _activeAnimation.value(
!Ui::AbstractButton::isDisabled() ? 1 : 0);
+
+ p.setOpacity(1. - activeProgress);
+ st::historyRecordVoiceOnceInactive.paintInCenter(p, inner);
+
if (activeProgress) {
p.setOpacity(activeProgress);
- pen.setStyle(Qt::SolidLine);
- pen.setBrush(st::windowBgActive);
- p.setPen(pen);
- p.setBrush(pen.brush());
- p.drawEllipse(innerRect);
-
- p.setPen(st::windowFgActive);
- p.drawText(innerRect, _text, style::al_center);
+ st::historyRecordVoiceOnceBg.paintInCenter(p, inner);
+ st::historyRecordVoiceOnceFg.paintInCenter(p, inner);
}
}, lifetime());
@@ -368,7 +407,7 @@ TTLButton::TTLButton(
void TTLButton::clearState() {
Ui::AbstractButton::setDisabled(true);
- update();
+ QWidget::update();
Ui::RpWidget::hide();
}
@@ -1136,7 +1175,6 @@ VoiceRecordBar::VoiceRecordBar(
, _show(std::move(descriptor.show))
, _send(std::move(descriptor.send))
, _lock(std::make_unique(_outerContainer, _st.lock))
-, _ttlButton(std::make_unique(_outerContainer, _st))
, _level(std::make_unique(_outerContainer, _st))
, _cancel(std::make_unique(this, _st, descriptor.recorderHeight))
, _startTimer([=] { startRecording(); })
@@ -1215,6 +1253,9 @@ void VoiceRecordBar::updateLockGeometry() {
void VoiceRecordBar::updateTTLGeometry(
TTLAnimationType type,
float64 progress) {
+ if (!_ttlButton) {
+ return;
+ }
const auto parent = parentWidget();
const auto me = Ui::MapFrom(_outerContainer, parent, geometry());
const auto anyTop = me.y() - st::historyRecordLockPosition.y();
@@ -1364,6 +1405,7 @@ void VoiceRecordBar::init() {
}
updateTTLGeometry(TTLAnimationType::TopBottom, 1. - value);
};
+ _showListenAnimation.stop();
_showListenAnimation.start(std::move(callback), 0., to, duration);
}, lifetime());
@@ -1373,6 +1415,11 @@ void VoiceRecordBar::init() {
_lock->locks(
) | rpl::start_with_next([=] {
if (_hasTTLFilter && _hasTTLFilter()) {
+ if (!_ttlButton) {
+ _ttlButton = std::make_unique(
+ _outerContainer,
+ _st);
+ }
_ttlButton->show();
}
updateTTLGeometry(TTLAnimationType::RightTopStatic, 0);
@@ -1495,13 +1542,18 @@ void VoiceRecordBar::setTTLFilter(FilterCallback &&callback) {
}
void VoiceRecordBar::initLockGeometry() {
- rpl::combine(
- _lock->heightValue(),
- geometryValue(),
- static_cast(parentWidget())->geometryValue()
+ const auto parent = static_cast(parentWidget());
+ rpl::merge(
+ _lock->heightValue() | rpl::to_empty,
+ geometryValue() | rpl::to_empty,
+ parent->geometryValue() | rpl::to_empty
) | rpl::start_with_next([=] {
updateLockGeometry();
}, lifetime());
+ parent->geometryValue(
+ ) | rpl::start_with_next([=] {
+ updateTTLGeometry(TTLAnimationType::RightLeft, 1.);
+ }, lifetime());
}
void VoiceRecordBar::initLevelGeometry() {
@@ -1600,10 +1652,12 @@ void VoiceRecordBar::stop(bool send) {
if (isHidden() && !send) {
return;
}
+ const auto ttlBeforeHide = peekTTLState();
auto disappearanceCallback = [=] {
hide();
- stopRecording(send ? StopType::Send : StopType::Cancel);
+ const auto type = send ? StopType::Send : StopType::Cancel;
+ stopRecording(type, ttlBeforeHide);
};
_lockShowing = false;
visibilityAnimate(false, std::move(disappearanceCallback));
@@ -1621,6 +1675,8 @@ void VoiceRecordBar::finish() {
_listen = nullptr;
+ [[maybe_unused]] const auto s = takeTTLState();
+
_sendActionUpdates.fire({ Api::SendProgressType::RecordVoice, -1 });
}
@@ -1631,7 +1687,7 @@ void VoiceRecordBar::hideFast() {
[[maybe_unused]] const auto s = takeTTLState();
}
-void VoiceRecordBar::stopRecording(StopType type) {
+void VoiceRecordBar::stopRecording(StopType type, bool ttlBeforeHide) {
using namespace ::Media::Capture;
if (type == StopType::Cancel) {
instance()->stop(crl::guard(this, [=](Result &&data) {
@@ -1652,9 +1708,9 @@ void VoiceRecordBar::stopRecording(StopType type) {
if (type == StopType::Send) {
const auto options = Api::SendOptions{
- .ttlSeconds = takeTTLState()
+ .ttlSeconds = (ttlBeforeHide
? std::numeric_limits::max()
- : 0
+ : 0),
};
auto settings = &AyuSettings::getInstance();
@@ -1861,6 +1917,10 @@ bool VoiceRecordBar::isRecordingByAnotherBar() const {
return !isRecording() && ::Media::Capture::instance()->started();
}
+bool VoiceRecordBar::isTTLButtonShown() const {
+ return _ttlButton && !_ttlButton->isHidden();
+}
+
bool VoiceRecordBar::hasDuration() const {
return _recordingSamples > 0;
}
@@ -1892,7 +1952,14 @@ void VoiceRecordBar::computeAndSetLockProgress(QPoint globalPos) {
_lock->requestPaintProgress(Progress(localPos.y(), higher - lower));
}
+bool VoiceRecordBar::peekTTLState() const {
+ return _ttlButton && !_ttlButton->isDisabled();
+}
+
bool VoiceRecordBar::takeTTLState() const {
+ if (!_ttlButton) {
+ return false;
+ }
const auto hasTtl = !_ttlButton->isDisabled();
_ttlButton->clearState();
return hasTtl;
diff --git a/Telegram/SourceFiles/history/view/controls/history_view_voice_record_bar.h b/Telegram/SourceFiles/history/view/controls/history_view_voice_record_bar.h
index 31c0eb30e..f1a58465e 100644
--- a/Telegram/SourceFiles/history/view/controls/history_view_voice_record_bar.h
+++ b/Telegram/SourceFiles/history/view/controls/history_view_voice_record_bar.h
@@ -98,6 +98,7 @@ public:
[[nodiscard]] bool isListenState() const;
[[nodiscard]] bool isActive() const;
[[nodiscard]] bool isRecordingByAnotherBar() const;
+ [[nodiscard]] bool isTTLButtonShown() const;
private:
enum class StopType {
@@ -125,7 +126,7 @@ private:
[[nodiscard]] bool recordingAnimationCallback(crl::time now);
void stop(bool send);
- void stopRecording(StopType type);
+ void stopRecording(StopType type, bool ttlBeforeHide = false);
void visibilityAnimate(bool show, Fn &&callback);
[[nodiscard]] bool showRecordButton() const;
@@ -148,6 +149,7 @@ private:
void computeAndSetLockProgress(QPoint globalPos);
+ [[nodiscard]] bool peekTTLState() const;
[[nodiscard]] bool takeTTLState() const;
const style::RecordBar &_st;
@@ -155,9 +157,9 @@ private:
const std::shared_ptr _show;
const std::shared_ptr _send;
const std::unique_ptr _lock;
- const std::unique_ptr _ttlButton;
const std::unique_ptr _level;
const std::unique_ptr _cancel;
+ std::unique_ptr _ttlButton;
std::unique_ptr _listen;
base::Timer _startTimer;
diff --git a/Telegram/SourceFiles/history/view/history_view_context_menu.cpp b/Telegram/SourceFiles/history/view/history_view_context_menu.cpp
index a6ba67967..eb2af43bb 100644
--- a/Telegram/SourceFiles/history/view/history_view_context_menu.cpp
+++ b/Telegram/SourceFiles/history/view/history_view_context_menu.cpp
@@ -26,6 +26,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "history/view/media/history_view_web_page.h"
#include "history/view/reactions/history_view_reactions_list.h"
#include "ui/widgets/popup_menu.h"
+#include "ui/widgets/menu/menu_action.h"
+#include "ui/widgets/menu/menu_common.h"
#include "ui/widgets/menu/menu_multiline_action.h"
#include "ui/image/image.h"
#include "ui/toast/toast.h"
@@ -1318,6 +1320,62 @@ void AddWhoReactedAction(
showAllChosen));
}
+void ShowTagMenu(
+ not_null*> menu,
+ QPoint position,
+ not_null context,
+ not_null item,
+ const Data::ReactionId &id,
+ not_null controller) {
+ using namespace Data;
+ const auto itemId = item->fullId();
+ const auto owner = &controller->session().data();
+ *menu = base::make_unique_q(
+ context,
+ st::popupMenuExpandedSeparator);
+ (*menu)->addAction(tr::lng_context_filter_by_tag(tr::now), [=] {
+ HashtagClickHandler(SearchTagToQuery(id)).onClick({
+ .button = Qt::LeftButton,
+ .other = QVariant::fromValue(ClickHandlerContext{
+ .sessionWindow = controller,
+ }),
+ });
+ }, &st::menuIconFave);
+
+ const auto removeTag = [=] {
+ if (const auto item = owner->message(itemId)) {
+ const auto &list = item->reactions();
+ if (ranges::contains(list, id, &MessageReaction::id)) {
+ item->toggleReaction(
+ id,
+ HistoryItem::ReactionSource::Quick);
+ }
+ }
+ };
+ (*menu)->addAction(base::make_unique_q(
+ (*menu)->menu(),
+ st::menuWithIconsAttention,
+ Ui::Menu::CreateAction(
+ (*menu)->menu(),
+ tr::lng_context_remove_tag(tr::now),
+ removeTag),
+ &st::menuIconDisableAttention,
+ &st::menuIconDisableAttention));
+
+ if (const auto custom = id.custom()) {
+ if (const auto set = owner->document(custom)->sticker()) {
+ if (set->set.id) {
+ AddEmojiPacksAction(
+ menu->get(),
+ { set->set },
+ EmojiPacksSource::Reaction,
+ controller);
+ }
+ }
+ }
+ (*menu)->popup(position);
+}
+
void ShowWhoReactedMenu(
not_null*> menu,
QPoint position,
@@ -1326,6 +1384,11 @@ void ShowWhoReactedMenu(
const Data::ReactionId &id,
not_null controller,
rpl::lifetime &lifetime) {
+ if (item->reactionsAreTags()) {
+ ShowTagMenu(menu, position, context, item, id, controller);
+ return;
+ }
+
struct State {
int addedToBottom = 0;
};
diff --git a/Telegram/SourceFiles/history/view/history_view_element.h b/Telegram/SourceFiles/history/view/history_view_element.h
index 3b524915f..134a7bf70 100644
--- a/Telegram/SourceFiles/history/view/history_view_element.h
+++ b/Telegram/SourceFiles/history/view/history_view_element.h
@@ -58,6 +58,7 @@ enum class Context : char {
AdminLog,
ContactPreview,
SavedSublist,
+ TTLViewer,
};
enum class OnlyEmojiAndSpaces : char {
diff --git a/Telegram/SourceFiles/history/view/history_view_list_widget.cpp b/Telegram/SourceFiles/history/view/history_view_list_widget.cpp
index 50822615c..6dcc3497f 100644
--- a/Telegram/SourceFiles/history/view/history_view_list_widget.cpp
+++ b/Telegram/SourceFiles/history/view/history_view_list_widget.cpp
@@ -901,7 +901,7 @@ not_null ListWidget::enforceViewForItem(
return j->second.get();
}
}
- const auto [i, ok] = _views.emplace(
+ const auto &[i, ok] = _views.emplace(
item,
item->createView(this));
return i->second.get();
@@ -1094,7 +1094,7 @@ void ListWidget::repaintScrollDateCallback() {
auto ListWidget::collectSelectedItems() const -> SelectedItems {
auto transformation = [&](const auto &item) {
- const auto [itemId, selection] = item;
+ const auto &[itemId, selection] = item;
auto result = SelectedItem(itemId);
result.canDelete = selection.canDelete;
result.canForward = selection.canForward;
@@ -2009,10 +2009,10 @@ Ui::ChatPaintContext ListWidget::preparePaintContext(
const QRect &clip) const {
return controller()->preparePaintContext({
.theme = _delegate->listChatTheme(),
- .visibleAreaTop = _visibleTop,
- .visibleAreaTopGlobal = mapToGlobal(QPoint(0, _visibleTop)).y(),
- .visibleAreaWidth = width(),
.clip = clip,
+ .visibleAreaPositionGlobal = mapToGlobal(QPoint(0, _visibleTop)),
+ .visibleAreaTop = _visibleTop,
+ .visibleAreaWidth = width(),
});
}
@@ -3766,7 +3766,7 @@ void ListWidget::refreshItem(not_null view) {
}
return nullptr;
}();
- const auto [i, ok] = _views.emplace(
+ const auto &[i, ok] = _views.emplace(
item,
item->createView(this, was.get()));
const auto now = i->second.get();
diff --git a/Telegram/SourceFiles/history/view/history_view_message.cpp b/Telegram/SourceFiles/history/view/history_view_message.cpp
index 628e3eb10..e3263e41d 100644
--- a/Telegram/SourceFiles/history/view/history_view_message.cpp
+++ b/Telegram/SourceFiles/history/view/history_view_message.cpp
@@ -1984,6 +1984,7 @@ bool Message::hasFromPhoto() const {
case Context::AdminLog:
return true;
case Context::History:
+ case Context::TTLViewer:
case Context::Pinned:
case Context::Replies:
case Context::SavedSublist: {
@@ -2917,8 +2918,12 @@ bool Message::isSignedAuthorElided() const {
bool Message::embedReactionsInBottomInfo() const {
const auto item = data();
const auto user = item->history()->peer->asUser();
- if (!user || user->isPremium() || user->session().premium()) {
+ if (!user
+ || user->isPremium()
+ || user->isSelf()
+ || user->session().premium()) {
// Only in messages of a non premium user with a non premium user.
+ // In saved messages we use reactions for tags, we don't embed them.
return false;
}
auto seenMy = false;
@@ -2961,8 +2966,14 @@ void Message::refreshReactions() {
if (!_reactions) {
const auto handlerFactory = [=](ReactionId id) {
const auto weak = base::make_weak(this);
- return std::make_shared([=] {
+ return std::make_shared([=](
+ ClickContext context) {
if (const auto strong = weak.get()) {
+ if (strong->data()->reactionsAreTags()) {
+ const auto tag = Data::SearchTagToQuery(id);
+ HashtagClickHandler(tag).onClick(context);
+ return;
+ }
strong->data()->toggleReaction(
id,
HistoryItem::ReactionSource::Existing);
@@ -3160,6 +3171,7 @@ bool Message::hasFromName() const {
case Context::AdminLog:
return true;
case Context::History:
+ case Context::TTLViewer:
case Context::Pinned:
case Context::Replies:
case Context::SavedSublist: {
@@ -3192,7 +3204,7 @@ bool Message::hasFromName() const {
case Context::ContactPreview:
return false;
}
- Unexpected("Context in Message::hasFromPhoto.");
+ Unexpected("Context in Message::hasFromName.");
}
bool Message::displayFromName() const {
diff --git a/Telegram/SourceFiles/history/view/history_view_replies_section.cpp b/Telegram/SourceFiles/history/view/history_view_replies_section.cpp
index 530af5fae..157a61977 100644
--- a/Telegram/SourceFiles/history/view/history_view_replies_section.cpp
+++ b/Telegram/SourceFiles/history/view/history_view_replies_section.cpp
@@ -1838,7 +1838,8 @@ bool RepliesWidget::cornerButtonsIgnoreVisibility() {
}
std::optional RepliesWidget::cornerButtonsDownShown() {
- if (_composeControls->isLockPresent()) {
+ if (_composeControls->isLockPresent()
+ || _composeControls->isTTLButtonShown()) {
return false;
}
const auto top = _scroll->scrollTop() + st::historyToDownShownAfter;
@@ -1851,7 +1852,9 @@ std::optional RepliesWidget::cornerButtonsDownShown() {
}
bool RepliesWidget::cornerButtonsUnreadMayBeShown() {
- return _loaded && !_composeControls->isLockPresent();
+ return _loaded
+ && !_composeControls->isLockPresent()
+ && !_composeControls->isTTLButtonShown();
}
bool RepliesWidget::cornerButtonsHas(CornerButtonType type) {
diff --git a/Telegram/SourceFiles/history/view/history_view_scheduled_section.cpp b/Telegram/SourceFiles/history/view/history_view_scheduled_section.cpp
index ed57d5ee8..a086d71cb 100644
--- a/Telegram/SourceFiles/history/view/history_view_scheduled_section.cpp
+++ b/Telegram/SourceFiles/history/view/history_view_scheduled_section.cpp
@@ -843,7 +843,8 @@ bool ScheduledWidget::cornerButtonsIgnoreVisibility() {
}
std::optional ScheduledWidget::cornerButtonsDownShown() {
- if (_composeControls->isLockPresent()) {
+ if (_composeControls->isLockPresent()
+ || _composeControls->isTTLButtonShown()) {
return false;
}
const auto top = _scroll->scrollTop() + st::historyToDownShownAfter;
@@ -857,7 +858,8 @@ std::optional ScheduledWidget::cornerButtonsDownShown() {
bool ScheduledWidget::cornerButtonsUnreadMayBeShown() {
return _inner->loadedAtBottomKnown()
- && !_composeControls->isLockPresent();
+ && !_composeControls->isLockPresent()
+ && !_composeControls->isTTLButtonShown();
}
bool ScheduledWidget::cornerButtonsHas(CornerButtonType type) {
diff --git a/Telegram/SourceFiles/history/view/history_view_sublist_section.cpp b/Telegram/SourceFiles/history/view/history_view_sublist_section.cpp
index a1c9578f8..e96738d0d 100644
--- a/Telegram/SourceFiles/history/view/history_view_sublist_section.cpp
+++ b/Telegram/SourceFiles/history/view/history_view_sublist_section.cpp
@@ -8,6 +8,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "history/view/history_view_sublist_section.h"
#include "main/main_session.h"
+#include "core/application.h"
+#include "core/shortcuts.h"
#include "data/data_saved_messages.h"
#include "data/data_saved_sublist.h"
#include "data/data_session.h"
@@ -19,6 +21,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "history/history.h"
#include "history/history_item.h"
#include "lang/lang_keys.h"
+#include "mainwidget.h"
#include "ui/chat/chat_style.h"
#include "ui/widgets/buttons.h"
#include "ui/widgets/scroll_area.h"
@@ -115,6 +118,10 @@ SublistWidget::SublistWidget(
) | rpl::start_with_next([=] {
clearSelected();
}, _topBar->lifetime());
+ _topBar->searchRequest(
+ ) | rpl::start_with_next([=] {
+ searchInSublist();
+ }, _topBar->lifetime());
_translateBar->raise();
_topBarShadow->raise();
@@ -134,6 +141,7 @@ SublistWidget::SublistWidget(
onScroll();
}, lifetime());
+ setupShortcuts();
setupTranslateBar();
}
@@ -658,4 +666,24 @@ void SublistWidget::clearSelected() {
_inner->cancelSelection();
}
+void SublistWidget::setupShortcuts() {
+ Shortcuts::Requests(
+ ) | rpl::filter([=] {
+ return Ui::AppInFocus()
+ && Ui::InFocusChain(this)
+ && !controller()->isLayerShown()
+ && (Core::App().activeWindow() == &controller()->window());
+ }) | rpl::start_with_next([=](not_null request) {
+ using Command = Shortcuts::Command;
+ request->check(Command::Search, 1) && request->handle([=] {
+ searchInSublist();
+ return true;
+ });
+ }, lifetime());
+}
+
+void SublistWidget::searchInSublist() {
+ controller()->content()->searchInChat(_sublist);
+}
+
} // namespace HistoryView
diff --git a/Telegram/SourceFiles/history/view/history_view_sublist_section.h b/Telegram/SourceFiles/history/view/history_view_sublist_section.h
index 819ce363c..ff6d34ace 100644
--- a/Telegram/SourceFiles/history/view/history_view_sublist_section.h
+++ b/Telegram/SourceFiles/history/view/history_view_sublist_section.h
@@ -167,11 +167,13 @@ private:
void setupOpenChatButton();
void setupAboutHiddenAuthor();
void setupTranslateBar();
+ void setupShortcuts();
void confirmDeleteSelected();
void confirmForwardSelected();
void clearSelected();
void recountChatWidth();
+ void searchInSublist();
const not_null _sublist;
const not_null _history;
diff --git a/Telegram/SourceFiles/history/view/history_view_top_bar_widget.cpp b/Telegram/SourceFiles/history/view/history_view_top_bar_widget.cpp
index 744d4ad3a..555de8e18 100644
--- a/Telegram/SourceFiles/history/view/history_view_top_bar_widget.cpp
+++ b/Telegram/SourceFiles/history/view/history_view_top_bar_widget.cpp
@@ -55,6 +55,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_send_action.h"
#include "chat_helpers/emoji_interactions.h"
#include "base/unixtime.h"
+#include "base/event_filter.h"
#include "support/support_helper.h"
#include "apiwrap.h"
#include "api/api_chat_participants.h"
@@ -64,6 +65,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "styles/style_info.h"
#include "styles/style_menu_icons.h"
+#include
+
// AyuGram includes
#include "ayu/ayu_settings.h"
#include "data/data_chat_filters.h"
@@ -235,6 +238,16 @@ TopBarWidget::TopBarWidget(
updateConnectingState();
}, lifetime());
+ base::install_event_filter(
+ this,
+ window()->windowHandle(),
+ [=](not_null e) {
+ if (e->type() == QEvent::Expose) {
+ updateConnectingState();
+ }
+ return base::EventFilterResult::Continue;
+ });
+
setCursor(style::cur_pointer);
}
@@ -246,7 +259,8 @@ Main::Session &TopBarWidget::session() const {
void TopBarWidget::updateConnectingState() {
const auto state = _controller->session().mtp().dcstate();
- if (state == MTP::ConnectedState) {
+ const auto exposed = window()->windowHandle()->isExposed();
+ if (state == MTP::ConnectedState || !exposed) {
if (_connecting) {
_connecting = nullptr;
update();
@@ -926,7 +940,9 @@ int TopBarWidget::countSelectedButtonsTop(float64 selectedShown) {
void TopBarWidget::updateSearchVisibility() {
const auto searchAllowedMode = (_activeChat.section == Section::History)
|| (_activeChat.section == Section::Replies
- && _activeChat.key.topic());
+ && _activeChat.key.topic())
+ || (_activeChat.section == Section::SavedSublist
+ && _activeChat.key.sublist());
_search->setVisible(searchAllowedMode && !_chooseForReportReason);
}
diff --git a/Telegram/SourceFiles/history/view/media/history_view_document.cpp b/Telegram/SourceFiles/history/view/media/history_view_document.cpp
index 7ca7bc747..3eb378a17 100644
--- a/Telegram/SourceFiles/history/view/media/history_view_document.cpp
+++ b/Telegram/SourceFiles/history/view/media/history_view_document.cpp
@@ -45,52 +45,18 @@ namespace {
constexpr auto kAudioVoiceMsgUpdateView = crl::time(100);
-void DrawCornerBadgeTTL(
- QPainter &p,
- const style::color &bg,
- const style::color &fg,
- const QRect &circleRect) {
- p.save();
- const auto partRect = QRectF(
- rect::right(circleRect)
+[[nodiscard]] QRect TTLRectFromInner(const QRect &inner) {
+ return QRect(
+ rect::right(inner)
- st::dialogsTTLBadgeSize
- + rect::m::sum::h(st::dialogsTTLBadgeInnerMargins),
- rect::bottom(circleRect)
+ + rect::m::sum::h(st::dialogsTTLBadgeInnerMargins)
+ - st::dialogsTTLBadgeSkip.x(),
+ rect::bottom(inner)
- st::dialogsTTLBadgeSize
- + rect::m::sum::v(st::dialogsTTLBadgeInnerMargins),
+ + rect::m::sum::v(st::dialogsTTLBadgeInnerMargins)
+ - st::dialogsTTLBadgeSkip.y(),
st::dialogsTTLBadgeSize,
st::dialogsTTLBadgeSize);
-
- auto hq = PainterHighQualityEnabler(p);
- p.setPen(Qt::NoPen);
- p.setBrush(bg);
- p.drawEllipse(partRect);
-
- const auto innerRect = partRect - st::dialogsTTLBadgeInnerMargins;
- const auto ttlText = u"1"_q;
-
- p.setFont(st::dialogsScamFont);
- p.setPen(fg);
- p.drawText(innerRect, ttlText, style::al_center);
-
- constexpr auto kPenWidth = 1.5;
-
- const auto penWidth = style::ConvertScaleExact(kPenWidth);
- auto pen = QPen(fg);
- pen.setJoinStyle(Qt::RoundJoin);
- pen.setCapStyle(Qt::RoundCap);
- pen.setWidthF(penWidth);
-
- p.setPen(pen);
- p.setBrush(Qt::NoBrush);
- p.drawArc(innerRect, arc::kQuarterLength, arc::kHalfLength);
-
- p.setClipRect(innerRect
- - QMarginsF(innerRect.width() / 2, -penWidth, -penWidth, -penWidth));
- pen.setStyle(Qt::DotLine);
- p.setPen(pen);
- p.drawEllipse(innerRect);
- p.restore();
}
[[nodiscard]] HistoryView::TtlPaintCallback CreateTtlPaintCallback(
@@ -99,43 +65,54 @@ void DrawCornerBadgeTTL(
struct State final {
std::unique_ptr start;
std::unique_ptr idle;
+ bool started = false;
};
const auto iconSize = Size(std::min(
st::historyFileInPause.width(),
st::historyFileInPause.height()));
const auto state = lifetime->make_state();
- state->start = Lottie::MakeIcon({
- .name = u"voice_ttl_start"_q,
+ //state->start = Lottie::MakeIcon({
+ // .name = u"voice_ttl_start"_q,
+ // .color = &st::historyFileInIconFg,
+ // .sizeOverride = iconSize,
+ //});
+ state->idle = Lottie::MakeIcon({
+ .name = u"voice_ttl_idle"_q,
.color = &st::historyFileInIconFg,
.sizeOverride = iconSize,
});
- const auto animateSingle = [=](
- not_null icon,
- Fn next) {
- auto callback = [=] {
- update();
- if (icon->frameIndex() == icon->framesCount()) {
- next();
- }
- };
- icon->animate(std::move(callback), 0, icon->framesCount());
- };
- const auto animate = [=](auto reanimate) -> void {
- animateSingle(state->idle.get(), [=] { reanimate(reanimate); });
- };
- animateSingle(
- state->start.get(),
- [=] {
- state->idle = Lottie::MakeIcon({
- .name = u"voice_ttl_idle"_q,
- .color = &st::historyFileInIconFg,
- .sizeOverride = iconSize,
- });
- animate(animate);
- });
+ const auto weak = std::weak_ptr(lifetime);
return [=](QPainter &p, QRect r, QColor c) {
- (state->idle ? state->idle : state->start)->paintInCenter(p, r, c);
+ if (weak.expired()) {
+ return;
+ }
+ {
+ const auto &icon = state->idle;
+ if (icon) {
+ icon->paintInCenter(p, r, c);
+ if (!icon->animating()) {
+ icon->animate(update, 0, icon->framesCount());
+ }
+ return;
+ }
+ }
+ {
+ const auto &icon = state->start;
+ icon->paintInCenter(p, r, c);
+ if (!icon->animating()) {
+ if (!state->started) {
+ icon->animate(update, 0, icon->framesCount());
+ state->started = true;
+ } else {
+ state->idle = Lottie::MakeIcon({
+ .name = u"voice_ttl_idle"_q,
+ .color = &st::historyFileInIconFg,
+ .sizeOverride = iconSize,
+ });
+ }
+ }
+ }
};
}
@@ -214,7 +191,8 @@ void PaintWaveform(
const PaintContext &context,
const VoiceData *voiceData,
int availableWidth,
- float64 progress) {
+ float64 progress,
+ bool ttl) {
const auto wf = [&]() -> const VoiceWaveform* {
if (!voiceData) {
return nullptr;
@@ -226,11 +204,14 @@ void PaintWaveform(
}
return &voiceData->waveform;
}();
+ if (ttl) {
+ progress = 1. - progress;
+ }
const auto stm = context.messageStyle();
// Rescale waveform by going in waveform.size * bar_count 1D grid.
const auto active = stm->msgWaveformActive;
- const auto inactive = stm->msgWaveformInactive;
+ const auto inactive = ttl ? stm->msgBg : stm->msgWaveformInactive;
const auto wfSize = wf
? int(wf->size())
: ::Media::Player::kWaveformSamplesCount;
@@ -267,10 +248,12 @@ void PaintWaveform(
p.fillRect(
QRectF(barLeft, barTop, leftWidth, barHeight),
active);
- p.fillRect(
- QRectF(activeWidth, barTop, rightWidth, barHeight),
- inactive);
- } else {
+ if (!ttl) {
+ p.fillRect(
+ QRectF(activeWidth, barTop, rightWidth, barHeight),
+ inactive);
+ }
+ } else if (!ttl || barLeft < activeWidth) {
const auto &color = (barLeft >= activeWidth) ? inactive : active;
p.fillRect(QRectF(barLeft, barTop, barWidth, barHeight), color);
}
@@ -326,41 +309,38 @@ Document::Document(
}
if ((_data->isVoiceMessage() || isRound)
- && IsVoiceOncePlayable(_parent->data())) {
- _parent->data()->removeFromSharedMediaIndex();
- setDocumentLinks(_data, realParent, [=] {
- _openl = nullptr;
-
+ && _parent->data()->media()->ttlSeconds()) {
+ const auto fullId = _realParent->fullId();
+ if (_parent->delegate()->elementContext() == Context::TTLViewer) {
auto lifetime = std::make_shared();
- rpl::merge(
- ::Media::Player::instance()->updatedNotifier(
- ) | rpl::filter([=](::Media::Player::TrackState state) {
- using State = ::Media::Player::State;
- const auto badState = state.state == State::Stopped
- || state.state == State::StoppedAtEnd
- || state.state == State::StoppedAtError
- || state.state == State::StoppedAtStart;
- return (state.id.contextId() != _realParent->fullId())
- && !badState;
- }) | rpl::to_empty,
- ::Media::Player::instance()->tracksFinished(
- ) | rpl::filter([=](AudioMsgId::Type type) {
- return (type == AudioMsgId::Type::Voice);
- }) | rpl::to_empty,
- ::Media::Player::instance()->stops(AudioMsgId::Type::Voice)
- ) | rpl::start_with_next([=]() mutable {
- _drawTtl = nullptr;
- const auto item = _parent->data();
+ TTLVoiceStops(fullId) | rpl::start_with_next([=]() mutable {
if (lifetime) {
base::take(lifetime)->destroy();
}
- // Destroys this.
- ClearMediaAsExpired(item);
}, *lifetime);
_drawTtl = CreateTtlPaintCallback(lifetime, [=] { repaint(); });
+ } else if (!_parent->data()->out()) {
+ const auto &data = &_parent->data()->history()->owner();
+ _parent->data()->removeFromSharedMediaIndex();
+ setDocumentLinks(_data, realParent, [=] {
+ _openl = nullptr;
- return false;
- });
+ auto lifetime = std::make_shared();
+ TTLVoiceStops(fullId) | rpl::start_with_next([=]() mutable {
+ if (lifetime) {
+ base::take(lifetime)->destroy();
+ }
+ if (const auto item = data->message(fullId)) {
+ // Destroys this.
+ ClearMediaAsExpired(item);
+ }
+ }, *lifetime);
+
+ return false;
+ });
+ } else {
+ setDocumentLinks(_data, realParent);
+ }
} else {
setDocumentLinks(_data, realParent);
}
@@ -427,7 +407,7 @@ void Document::createComponents(bool caption) {
_realParent->fullId());
}
if (const auto voice = Get