From 1c94acdeca5d4ec6c603ffcd41f9e7d2a0ba50e4 Mon Sep 17 00:00:00 2001 From: John Preston Date: Wed, 8 Apr 2026 17:35:05 +0700 Subject: [PATCH 001/184] Don't force drop-to-left in media context menu. --- Telegram/SourceFiles/boxes/send_files_box.cpp | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/Telegram/SourceFiles/boxes/send_files_box.cpp b/Telegram/SourceFiles/boxes/send_files_box.cpp index b098618f90..3aea786574 100644 --- a/Telegram/SourceFiles/boxes/send_files_box.cpp +++ b/Telegram/SourceFiles/boxes/send_files_box.cpp @@ -1409,7 +1409,10 @@ void SendFilesBox::pushBlock(int from, int till) { entry.videoCover = nullptr; }); }; - const auto showContextMenu = [=](int fileIndex, QPoint globalPosition) { + const auto showContextMenu = [=]( + int fileIndex, + QPoint globalPosition, + bool forceToLeft = false) { if (from >= till || fileIndex < from || fileIndex >= till @@ -1419,7 +1422,10 @@ void SendFilesBox::pushBlock(int from, int till) { state->menu = base::make_unique_q( widget, _st.tabbed.menu); - state->menu->setForcedOrigin(Ui::PanelAnimation::Origin::TopRight); + if (forceToLeft) { + using Origin = Ui::PanelAnimation::Origin; + state->menu->setForcedOrigin(Origin::TopRight); + } const auto &file = _list.files[fileIndex]; state->menu->addAction(tr::lng_attach_replace(tr::now), [=] { replaceAttachment(fileIndex); @@ -1552,7 +1558,7 @@ void SendFilesBox::pushBlock(int from, int till) { block.itemReplaceRequest( ) | rpl::on_next([=](int index) { - showContextMenu(index, QCursor::pos()); + showContextMenu(index, QCursor::pos(), true); }, widget->lifetime()); block.itemModifyRequest( From 7cd4bd3cd4960dee575e7af5d50fecc953d82f94 Mon Sep 17 00:00:00 2001 From: John Preston Date: Wed, 8 Apr 2026 17:35:36 +0700 Subject: [PATCH 002/184] Rewrite text appear animation. --- .../history/view/history_view_message.cpp | 323 +++++++++--------- .../history/view/history_view_message.h | 19 +- Telegram/lib_ui | 2 +- 3 files changed, 182 insertions(+), 162 deletions(-) diff --git a/Telegram/SourceFiles/history/view/history_view_message.cpp b/Telegram/SourceFiles/history/view/history_view_message.cpp index cbf4f8972d..e6603b1d27 100644 --- a/Telegram/SourceFiles/history/view/history_view_message.cpp +++ b/Telegram/SourceFiles/history/view/history_view_message.cpp @@ -215,6 +215,23 @@ Message::Message( } } initPaidInformation(); + + if (data->isTextAppearing()) { + AddComponents(TextAppearing::Bit()); + + if (replacing) { + if (const auto was = replacing->Get()) { + const auto appearing = Get(); + *appearing = std::move(*was); + appearing->widthAnimation.setCallback([=] { + textAppearWidthCallback(); + }); + appearing->heightAnimation.setCallback([=] { + textAppearHeightCallback(); + }); + } + } + } } Message::~Message() { @@ -642,11 +659,6 @@ QSize Message::performCountOptimalSize() { } else { RemoveComponents(Factcheck::Bit()); } - if (item->isTextAppearing()) { - AddComponents(TextAppearing::Bit()); - } else { - RemoveComponents(TextAppearing::Bit()); - } refreshRightBadge(); const auto markup = item->inlineReplyMarkup(); @@ -2192,33 +2204,32 @@ void Message::paintText( }; const auto appearing = Get(); - if (appearing - && (appearing->shownLines > 0) - && (appearing->shownLines <= int(appearing->lines.size()))) { - const auto lastLineIndex = appearing->shownLines - 1; + const auto appearingClip = appearing && appearing->use; + if (appearingClip) { + const auto shown = appearing->shownLine; + const auto &line = appearing->lines[shown]; + p.save(); auto clipPath = QPainterPath(); - const auto prevBottom = (lastLineIndex > 0) - ? appearing->lines[lastLineIndex - 1].top + const auto previousLineBottom = (shown > 0) + ? appearing->lines[shown - 1].bottom : 0; - if (prevBottom > 0) { + if (previousLineBottom > 0) { clipPath.addRect( trect.x(), trect.y(), trect.width(), - prevBottom); + previousLineBottom); } - const auto lastLineHeight = appearing->lines[lastLineIndex].top - - prevBottom; - const auto &lastLine = appearing->lines[lastLineIndex]; - const auto clipLeft = lastLine.rtl + const auto shownHeight = line.bottom - previousLineBottom; + const auto clipLeft = line.rtl ? (trect.x() + trect.width() - appearing->revealedLineWidth) : trect.x(); clipPath.addRect( clipLeft, - trect.y() + prevBottom, + trect.y() + previousLineBottom, appearing->revealedLineWidth, - lastLineHeight); + shownHeight); p.setClipPath(clipPath); } @@ -2252,8 +2263,7 @@ void Message::paintText( } else if (needRippleMask) { _linkRipple = nullptr; } - if (appearing && appearing->shownLines > 0 - && appearing->shownLines <= int(appearing->lines.size())) { + if (appearingClip) { p.restore(); } } @@ -4917,18 +4927,15 @@ QRect Message::countGeometry() const { contentWidth = mediaWidth; } } - if (const auto appearing = Get()) { - if (appearing->shownWidth > 0) { - const auto animatedWidth = st::msgPadding.left() - + appearing->shownWidth - + st::msgPadding.right(); - const auto minWidth = _bottomInfo.optimalSize().width() - + st::msgPadding.left() - + st::msgPadding.right(); - accumulate_min(contentWidth, std::max( - animatedWidth, - minWidth)); - } + if (const auto appearing = Get() + ; appearing && appearing->use) { + const auto animatedWidth = st::msgPadding.left() + + appearing->shownWidth + + st::msgPadding.right(); + const auto minWidth = _bottomInfo.optimalSize().width() + + st::msgPadding.left() + + st::msgPadding.right(); + accumulate_min(contentWidth, std::max(animatedWidth, minWidth)); } if (contentWidth < availableWidth && delegate()->elementChatMode() != ElementChatMode::Wide) { @@ -5089,7 +5096,16 @@ int Message::resizeContentGetHeight(int newWidth) { _reactions->resizeGetHeight(textWidth); } - const auto appearing = Get(); + auto appearing = Get(); + if (appearing) { + if (appearing->textWidth != textWidth) { + appearing->geometryValid = false; + appearing->textWidth = textWidth; + } + if (!textAppearValidate(appearing)) { + appearing = nullptr; + } + } if (contentWidth == maxWidth() && !appearing) { if (mediaDisplayed) { if (check) { @@ -5114,30 +5130,10 @@ int Message::resizeContentGetHeight(int newWidth) { if (botTop) { newHeight += botTop->height; } - const auto fullTextHeight = textHeightFor(textWidth); if (appearing) { - if (!appearing->geometryValid - || appearing->textWidth != textWidth) { - appearing->textWidth = textWidth; - appearing->lines = text().countLinesGeometry( - textWidth); - appearing->geometryValid = true; - if (appearing->shownHeight <= 0) { - appearing->shownHeight = appearing->lines.empty() - ? text().lineHeight() - : appearing->lines.front().top; - appearing->shownLines = 1; - appearing->revealedLineWidth = 0; - appearing->shownWidth = 0; - startTextAppearingWidthAnimation(); - } else if (!appearing->heightStarted - && !appearing->widthAnimation.animating()) { - startTextAppearingHeightAnimation(); - } - } newHeight += appearing->shownHeight; } else { - newHeight += fullTextHeight; + newHeight += textHeightFor(textWidth); } } if (!mediaOnBottom && (!_viewButton || !reactionsInBubble)) { @@ -5250,117 +5246,136 @@ void Message::invalidateTextDependentCache() { _bubbleTextualWidthCache = 0; } -void Message::startTextAppearingWidthAnimation() { - const auto appearing = Get(); - if (!appearing || appearing->lines.empty()) { - return; +bool Message::textAppearValidate(not_null appearing) { + while (true) { + if (!textAppearCheckLine(appearing)) { + return false; + } else if (!appearing->use + || appearing->widthAnimation.animating() + || appearing->heightAnimation.animating()) { + return true; + } + ++appearing->shownLine; + appearing->revealedLineWidth = 0; } - const auto lineIndex = appearing->shownLines - 1; - if (lineIndex < 0 - || lineIndex >= int(appearing->lines.size())) { - return; +} + +bool Message::textAppearCheckLine(not_null appearing) { + const auto recount = !appearing->geometryValid; + if (recount) { + appearing->geometryValid = true; + appearing->lines = text().countLinesGeometry(appearing->textWidth); } - appearing->heightStarted = false; - appearing->revealedLineWidth = 0; - const auto lineWidth = appearing->lines[lineIndex].width; - const auto duration = std::max( - kFullLineAppearDuration * lineWidth / st::msgMaxWidth, - crl::time(10)); + const auto lines = int(appearing->lines.size()); + const auto shown = appearing->shownLine; + const auto line = (shown < lines) ? &appearing->lines[shown] : nullptr; + const auto use = appearing->use = !anim::Disabled() + && line + && ((shown + 1 < lines) + || (shown + 1 == lines + && ((appearing->revealedLineWidth < line->width) + || (appearing->shownHeight < line->bottom)))); + if (!use) { + if (data()->isRegular()) { + RemoveComponents(TextAppearing::Bit()); + return false; + } else if (recount) { + appearing->shownLine = lines - 1; + appearing->revealedLineWidth = line ? line->width : 0; + appearing->shownHeight = line ? line->bottom : 0; + appearing->widthAnimation.stop(); + appearing->heightAnimation.stop(); + } + return true; + } + if (appearing->revealedLineWidth >= line->width) { + appearing->widthAnimation.stop(); + appearing->revealedLineWidth + = appearing->targetLineWidth + = line->width; + } else if (appearing->targetLineWidth != line->width) { + textAppearStartWidthAnimation(appearing); + } + if (!shown || appearing->shownHeight >= line->bottom) { + appearing->heightAnimation.stop(); + appearing->shownHeight = appearing->targetHeight = line->bottom; + } else if (appearing->targetHeight != line->bottom) { + const auto widthStart = appearing->startLineWidth; + const auto widthTarget = appearing->targetLineWidth; + const auto width = appearing->revealedLineWidth; + const auto progress = (width >= widthTarget) + ? 1. + : (widthTarget - width) / float64(widthTarget - widthStart); + const auto left = (1. - progress) * appearing->widthDuration; + if (appearing->heightAnimation.animating() + || !appearing->widthAnimation.animating() + || left <= kLineHeightAppearDuration) { + textAppearStartHeightAnimation(appearing); + } + } + return true; +} + +void Message::textAppearStartWidthAnimation( + not_null appearing) { + Expects(appearing->use); + + const auto shown = appearing->shownLine; + const auto lines = int(appearing->lines.size()); + const auto lineWidth = appearing->lines[shown].width; + const auto duration = (shown + 1 == lines) + ? kFullLineAppearDuration + : std::max( + kFullLineAppearDuration * lineWidth / st::msgMaxWidth, + crl::time(10)); appearing->widthDuration = duration; - appearing->widthAnimation.start( - [=] { textAppearingTick(); }, - 0., - 1., - duration, - anim::linear); + const auto from + = appearing->startLineWidth + = appearing->revealedLineWidth; + const auto to = appearing->targetLineWidth = lineWidth; + + Assert(from < to); + appearing->widthAnimation.start([=] { + textAppearWidthCallback(); + }, from, to, duration, anim::linear); } -void Message::startTextAppearingHeightAnimation() { +void Message::textAppearStartHeightAnimation( + not_null appearing) { + Expects(appearing->use); + + const auto from = appearing->shownHeight; + const auto to + = appearing->targetHeight + = appearing->lines[appearing->shownLine].bottom; + appearing->heightAnimation.start([=] { + textAppearHeightCallback(); + }, from, to, kLineHeightAppearDuration, anim::easeOutCubic); +} + +void Message::textAppearWidthCallback() { const auto appearing = Get(); - if (!appearing - || appearing->heightStarted - || (appearing->shownLines >= int(appearing->lines.size()))) { - return; + const auto now = int(base::SafeRound( + appearing->widthAnimation.value(appearing->targetLineWidth))); + if (now != appearing->revealedLineWidth) { + appearing->revealedLineWidth = now; + appearing->shownWidth = std::max(appearing->shownWidth, now); + repaint(); } - appearing->heightStarted = true; - const auto oldHeight = appearing->shownHeight; - const auto newTargetHeight = appearing->lines[appearing->shownLines].top; - appearing->heightAnimation.start( - [=] { textAppearingHeightTick(); }, - double(oldHeight), - double(newTargetHeight), - kLineHeightAppearDuration, - anim::easeOutCubic); + textAppearValidate(appearing); } -void Message::textAppearingTick() { - auto appearing = Get(); - if (!appearing) { - return; - } - const auto lineIndex = appearing->shownLines - 1; - if (lineIndex < 0 - || lineIndex >= int(appearing->lines.size())) { - return; - } - const auto progress = appearing->widthAnimation.value(1.); - appearing->revealedLineWidth = int( - appearing->lines[lineIndex].width * progress); - appearing->shownWidth = std::max( - appearing->shownWidth, - appearing->revealedLineWidth); - - const auto hasMoreLines = - (appearing->shownLines < int(appearing->lines.size())); - const auto remaining = crl::time( - appearing->widthDuration * (1. - progress)); - if (hasMoreLines - && (remaining <= kLineHeightAppearDuration) - && !appearing->heightStarted) { - startTextAppearingHeightAnimation(); - } - - if (!appearing->widthAnimation.animating()) { - tryAdvanceTextAppearing(); - } - - repaint(); -} - -void Message::textAppearingHeightTick() { - auto appearing = Get(); - if (!appearing) { - return; - } - - const auto targetHeight = appearing->lines[appearing->shownLines].top; - const auto oldShownHeight = appearing->shownHeight; - appearing->shownHeight = int(base::SafeRound( - appearing->heightAnimation.value(targetHeight))); - const auto delta = appearing->shownHeight - oldShownHeight; - if (delta) { +void Message::textAppearHeightCallback() { + const auto appearing = Get(); + const auto now = int(base::SafeRound( + appearing->heightAnimation.value(appearing->targetHeight))); + if (const auto delta = now - appearing->shownHeight) { + appearing->shownHeight = now; adjustHeight(delta); history()->viewHeightAdjusted(this, delta); + repaint(); } - - if (!appearing->heightAnimation.animating()) { - tryAdvanceTextAppearing(); - } -} - -void Message::tryAdvanceTextAppearing() { - auto appearing = Get(); - if (!appearing) { - return; - } - if (appearing->widthAnimation.animating() - || appearing->heightAnimation.animating()) { - return; - } - if (appearing->shownLines < int(appearing->lines.size())) { - appearing->shownLines++; - startTextAppearingWidthAnimation(); - } + textAppearValidate(appearing); } bool Message::needInfoDisplay() const { diff --git a/Telegram/SourceFiles/history/view/history_view_message.h b/Telegram/SourceFiles/history/view/history_view_message.h index 24a89991a7..644b9346f1 100644 --- a/Telegram/SourceFiles/history/view/history_view_message.h +++ b/Telegram/SourceFiles/history/view/history_view_message.h @@ -85,15 +85,18 @@ struct RightBadge : RuntimeComponent { struct TextAppearing : RuntimeComponent { std::vector lines; int textWidth = 0; - int shownLines = 0; + int shownLine = 0; int revealedLineWidth = 0; + int startLineWidth = 0; + int targetLineWidth = 0; int shownWidth = 0; int shownHeight = 0; + int targetHeight = 0; crl::time widthDuration = 0; Ui::Animations::Simple widthAnimation; Ui::Animations::Simple heightAnimation; bool geometryValid = false; - bool heightStarted = false; + bool use = false; }; struct BottomRippleMask { @@ -386,11 +389,13 @@ private: [[nodiscard]] ClickHandlerPtr psaTooltipLink() const; void psaTooltipToggled(bool shown) const; void invalidateTextDependentCache() override; - void startTextAppearingWidthAnimation(); - void startTextAppearingHeightAnimation(); - void textAppearingTick(); - void textAppearingHeightTick(); - void tryAdvanceTextAppearing(); + + bool textAppearValidate(not_null appearing); + bool textAppearCheckLine(not_null appearing); + void textAppearStartWidthAnimation(not_null appearing); + void textAppearStartHeightAnimation(not_null appearing); + void textAppearWidthCallback(); + void textAppearHeightCallback(); void refreshRightBadge(); [[nodiscard]] int rightBadgeWidth() const; diff --git a/Telegram/lib_ui b/Telegram/lib_ui index bcb18cb15d..adc7d7d126 160000 --- a/Telegram/lib_ui +++ b/Telegram/lib_ui @@ -1 +1 @@ -Subproject commit bcb18cb15d913ec0735ae3b3bbb15941d5772f5b +Subproject commit adc7d7d1263e0879c04fa09c37bee0b8b87e8449 From 4009a4737790f0372d13259d5ffdb27661eec65c Mon Sep 17 00:00:00 2001 From: John Preston Date: Wed, 8 Apr 2026 23:14:07 +0700 Subject: [PATCH 003/184] Fix word-wrapping of streamed text. --- Telegram/SourceFiles/data/data_session.cpp | 8 --- Telegram/SourceFiles/data/data_session.h | 1 - Telegram/SourceFiles/history/history_item.cpp | 9 +-- Telegram/SourceFiles/history/history_item.h | 3 +- .../history/history_streamed_drafts.cpp | 2 +- .../history/view/history_view_element.cpp | 6 -- .../history/view/history_view_element.h | 1 - .../history/view/history_view_message.cpp | 55 +++++++++++-------- 8 files changed, 35 insertions(+), 50 deletions(-) diff --git a/Telegram/SourceFiles/data/data_session.cpp b/Telegram/SourceFiles/data/data_session.cpp index 6ce87a8dbc..a3704e2d37 100644 --- a/Telegram/SourceFiles/data/data_session.cpp +++ b/Telegram/SourceFiles/data/data_session.cpp @@ -2102,14 +2102,6 @@ void Session::requestItemTextRefresh(not_null item) { } } -void Session::requestItemTextRefreshStreaming( - not_null item) { - enumerateItemViews(item, [&](not_null view) { - view->itemTextUpdatedStreaming(); - }); - requestItemResize(item); -} - void Session::registerRestricted( not_null item, const QString &reason) { diff --git a/Telegram/SourceFiles/data/data_session.h b/Telegram/SourceFiles/data/data_session.h index 1e54a52ab4..e66c1b81d3 100644 --- a/Telegram/SourceFiles/data/data_session.h +++ b/Telegram/SourceFiles/data/data_session.h @@ -406,7 +406,6 @@ public: void requestItemViewRefresh(not_null item); [[nodiscard]] rpl::producer> itemViewRefreshRequest() const; void requestItemTextRefresh(not_null item); - void requestItemTextRefreshStreaming(not_null item); void requestUnreadReactionsAnimation(not_null item); void notifyHistoryUnloaded(not_null history); [[nodiscard]] rpl::producer> historyUnloaded() const; diff --git a/Telegram/SourceFiles/history/history_item.cpp b/Telegram/SourceFiles/history/history_item.cpp index 4682c93d59..e698a95d65 100644 --- a/Telegram/SourceFiles/history/history_item.cpp +++ b/Telegram/SourceFiles/history/history_item.cpp @@ -3952,7 +3952,7 @@ void HistoryItem::detectTextLinks( } } -void HistoryItem::setText(const TextWithEntities &textWithEntities) { +void HistoryItem::setText(TextWithEntities textWithEntities) { detectTextLinks(textWithEntities); setTextValue((_media && _media->consumeMessageText(textWithEntities)) ? TextWithEntities() @@ -3972,13 +3972,6 @@ void HistoryItem::setTextValue(TextWithEntities text, bool force) { } } -void HistoryItem::setTextStreaming(TextWithEntities text) { - detectTextLinks(text); - _text = std::move(text); - RemoveComponents(HistoryMessageTranslation::Bit()); - history()->owner().requestItemTextRefreshStreaming(this); -} - bool HistoryItem::inHighlightProcess() const { return _flags & MessageFlag::InHighlightProcess; } diff --git a/Telegram/SourceFiles/history/history_item.h b/Telegram/SourceFiles/history/history_item.h index da5f581b04..f4c9b6d99d 100644 --- a/Telegram/SourceFiles/history/history_item.h +++ b/Telegram/SourceFiles/history/history_item.h @@ -521,8 +521,7 @@ public: return _media.get(); } [[nodiscard]] bool computeDropForwardedInfo() const; - void setText(const TextWithEntities &textWithEntities); - void setTextStreaming(TextWithEntities text); + void setText(TextWithEntities textWithEntities); [[nodiscard]] MsgId replyToId() const; [[nodiscard]] FullMsgId replyToFullId() const; diff --git a/Telegram/SourceFiles/history/history_streamed_drafts.cpp b/Telegram/SourceFiles/history/history_streamed_drafts.cpp index 5b84c4e516..e88d1b612e 100644 --- a/Telegram/SourceFiles/history/history_streamed_drafts.cpp +++ b/Telegram/SourceFiles/history/history_streamed_drafts.cpp @@ -82,7 +82,7 @@ bool HistoryStreamedDrafts::update( if (i == end(_drafts) || i->second.randomId != randomId) { return false; } - i->second.message->setTextStreaming(text); + i->second.message->setText(text); i->second.updated = crl::now(); return true; } diff --git a/Telegram/SourceFiles/history/view/history_view_element.cpp b/Telegram/SourceFiles/history/view/history_view_element.cpp index 6b884a5bd2..e568895c74 100644 --- a/Telegram/SourceFiles/history/view/history_view_element.cpp +++ b/Telegram/SourceFiles/history/view/history_view_element.cpp @@ -2539,12 +2539,6 @@ void Element::itemTextUpdated() { } } -void Element::itemTextUpdatedStreaming() { - clearSpecialOnlyEmoji(); - _text = Ui::Text::String(st::msgMinWidth); - invalidateTextSizeCache(); -} - void Element::blockquoteExpandChanged() { invalidateTextSizeCache(); history()->owner().requestViewResize(this); diff --git a/Telegram/SourceFiles/history/view/history_view_element.h b/Telegram/SourceFiles/history/view/history_view_element.h index f790f1ace5..08a1e3d55e 100644 --- a/Telegram/SourceFiles/history/view/history_view_element.h +++ b/Telegram/SourceFiles/history/view/history_view_element.h @@ -621,7 +621,6 @@ public: virtual void itemDataChanged(); void itemTextUpdated(); - void itemTextUpdatedStreaming(); void blockquoteExpandChanged(); virtual void unloadHeavyPart(); diff --git a/Telegram/SourceFiles/history/view/history_view_message.cpp b/Telegram/SourceFiles/history/view/history_view_message.cpp index e6603b1d27..994bac5fdd 100644 --- a/Telegram/SourceFiles/history/view/history_view_message.cpp +++ b/Telegram/SourceFiles/history/view/history_view_message.cpp @@ -2236,7 +2236,7 @@ void Message::paintText( auto highlightRequest = context.computeHighlightCache(); text().draw(p, { .position = trect.topLeft(), - .availableWidth = trect.width(), + .availableWidth = appearing ? appearing->textWidth : trect.width(), .palette = &stm->textPalette, .pre = stm->preCache.get(), .blockquote = context.quoteCache( @@ -5288,29 +5288,33 @@ bool Message::textAppearCheckLine(not_null appearing) { } return true; } - if (appearing->revealedLineWidth >= line->width) { - appearing->widthAnimation.stop(); - appearing->revealedLineWidth - = appearing->targetLineWidth - = line->width; - } else if (appearing->targetLineWidth != line->width) { - textAppearStartWidthAnimation(appearing); + if (appearing->targetLineWidth != line->width) { + if (appearing->revealedLineWidth >= line->width) { + appearing->widthAnimation.stop(); + appearing->revealedLineWidth + = appearing->targetLineWidth + = line->width; + } else { + textAppearStartWidthAnimation(appearing); + } } - if (!shown || appearing->shownHeight >= line->bottom) { - appearing->heightAnimation.stop(); - appearing->shownHeight = appearing->targetHeight = line->bottom; - } else if (appearing->targetHeight != line->bottom) { - const auto widthStart = appearing->startLineWidth; - const auto widthTarget = appearing->targetLineWidth; - const auto width = appearing->revealedLineWidth; - const auto progress = (width >= widthTarget) - ? 1. - : (widthTarget - width) / float64(widthTarget - widthStart); - const auto left = (1. - progress) * appearing->widthDuration; - if (appearing->heightAnimation.animating() - || !appearing->widthAnimation.animating() - || left <= kLineHeightAppearDuration) { - textAppearStartHeightAnimation(appearing); + if (appearing->targetHeight != line->bottom) { + if (!shown || appearing->shownHeight >= line->bottom) { + appearing->heightAnimation.stop(); + appearing->shownHeight = appearing->targetHeight = line->bottom; + } else { + const auto widthStart = appearing->startLineWidth; + const auto widthTarget = appearing->targetLineWidth; + const auto width = appearing->revealedLineWidth; + const auto progress = (width >= widthTarget) + ? 1. + : (widthTarget - width) / float64(widthTarget - widthStart); + const auto left = (1. - progress) * appearing->widthDuration; + if (appearing->heightAnimation.animating() + || !appearing->widthAnimation.animating() + || left <= kLineHeightAppearDuration) { + textAppearStartHeightAnimation(appearing); + } } } return true; @@ -5360,6 +5364,11 @@ void Message::textAppearWidthCallback() { if (now != appearing->revealedLineWidth) { appearing->revealedLineWidth = now; appearing->shownWidth = std::max(appearing->shownWidth, now); + if (appearing->lines[appearing->shownLine].rtl) { + appearing->shownWidth = std::max( + appearing->shownWidth, + appearing->textWidth); + } repaint(); } textAppearValidate(appearing); From 263ddc3bcf5fddfe9c383b757fcd14f98d95f4c5 Mon Sep 17 00:00:00 2001 From: John Preston Date: Thu, 9 Apr 2026 11:32:51 +0700 Subject: [PATCH 004/184] Fix possible crash in widget destruction. --- Telegram/lib_ui | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Telegram/lib_ui b/Telegram/lib_ui index adc7d7d126..798f246338 160000 --- a/Telegram/lib_ui +++ b/Telegram/lib_ui @@ -1 +1 @@ -Subproject commit adc7d7d1263e0879c04fa09c37bee0b8b87e8449 +Subproject commit 798f246338fffa6884cbd2bc02949a40ea1afac5 From 111b15a855daad92cdffa7c1acef63e7cd6d0d8f Mon Sep 17 00:00:00 2001 From: John Preston Date: Thu, 9 Apr 2026 16:09:16 +0700 Subject: [PATCH 005/184] Allow many lines in ConfirmInviteBox about. --- Telegram/SourceFiles/api/api_chat_invite.cpp | 521 ++++++++++--------- Telegram/SourceFiles/api/api_chat_invite.h | 80 --- Telegram/SourceFiles/boxes/boxes.style | 5 +- 3 files changed, 269 insertions(+), 337 deletions(-) diff --git a/Telegram/SourceFiles/api/api_chat_invite.cpp b/Telegram/SourceFiles/api/api_chat_invite.cpp index f1a3f81d1c..e7e9d1bc1a 100644 --- a/Telegram/SourceFiles/api/api_chat_invite.cpp +++ b/Telegram/SourceFiles/api/api_chat_invite.cpp @@ -48,6 +48,69 @@ namespace Api { namespace { +struct InviteParticipant { + not_null user; + Ui::PeerUserpicView userpic; +}; + +struct ChatInvite { + QString title; + QString about; + PhotoData *photo = nullptr; + int participantsCount = 0; + std::vector participants; + bool isPublic = false; + bool isChannel = false; + bool isMegagroup = false; + bool isBroadcast = false; + bool isRequestNeeded = false; + bool isFake = false; + bool isScam = false; + bool isVerified = false; +}; + +[[nodiscard]] ChatInvite ParseInvite( + not_null session, + const MTPDchatInvite &data) { + auto participants = std::vector(); + if (const auto list = data.vparticipants()) { + participants.reserve(list->v.size()); + for (const auto &participant : list->v) { + if (const auto user = session->data().processUser(participant)) { + participants.push_back(InviteParticipant{ user }); + } + } + } + const auto photo = session->data().processPhoto(data.vphoto()); + return { + .title = qs(data.vtitle()), + .about = data.vabout().value_or_empty(), + .photo = (photo->isNull() ? nullptr : photo.get()), + .participantsCount = data.vparticipants_count().v, + .participants = std::move(participants), + .isPublic = data.is_public(), + .isChannel = data.is_channel(), + .isMegagroup = data.is_megagroup(), + .isBroadcast = data.is_broadcast(), + .isRequestNeeded = data.is_request_needed(), + .isFake = data.is_fake(), + .isScam = data.is_scam(), + .isVerified = data.is_verified(), + }; +} + +[[nodiscard]] Info::Profile::BadgeType BadgeForInvite( + const ChatInvite &invite) { + using Type = Info::Profile::BadgeType; + return invite.isVerified + ? Type::Verified + : invite.isScam + ? Type::Scam + : invite.isFake + ? Type::Fake + : Type::None; +} + void SubmitChatInvite( base::weak_ptr weak, not_null session, @@ -356,6 +419,207 @@ void ConfirmSubscriptionBox( box->addButton(tr::lng_cancel(), [=] { box->closeBox(); }); } +void ConfirmInviteBox( + not_null box, + not_null session, + const MTPDchatInvite *invitePtr, + ChannelData *invitePeekChannel, + Fn submit) { + auto invite = ParseInvite(session, *invitePtr); + const auto isChannel = invite.isChannel && !invite.isMegagroup; + const auto requestApprove = invite.isRequestNeeded; + const auto count = invite.participantsCount; + + struct State { + std::shared_ptr photoMedia; + std::unique_ptr photoEmpty; + std::vector participants; + }; + const auto state = box->lifetime().make_state(); + state->participants = std::move(invite.participants); + + const auto status = [&] { + return invitePeekChannel + ? tr::lng_channel_invite_private(tr::now) + : (!state->participants.empty() + && int(state->participants.size()) < count) + ? tr::lng_group_invite_members(tr::now, lt_count, count) + : (count > 0 && isChannel) + ? tr::lng_chat_status_subscribers( + tr::now, + lt_count_decimal, + count) + : (count > 0) + ? tr::lng_chat_status_members(tr::now, lt_count_decimal, count) + : isChannel + ? tr::lng_channel_status(tr::now) + : tr::lng_group_status(tr::now); + }(); + + box->setNoContentMargin(true); + box->setWidth(st::boxWideWidth); + const auto content = box->verticalLayout(); + + Ui::AddSkip(content, st::confirmInvitePhotoTop); + const auto userpic = content->add( + object_ptr(content), + style::al_top); + const auto photoSize = st::confirmInvitePhotoSize; + userpic->resize(Size(photoSize)); + userpic->setNaturalWidth(photoSize); + userpic->paintRequest( + ) | rpl::on_next([=, small = Data::PhotoSize::Small] { + auto p = QPainter(userpic); + if (state->photoMedia) { + if (const auto image = state->photoMedia->image(small)) { + p.drawPixmap( + 0, + 0, + image->pix( + Size(photoSize), + { .options = Images::Option::RoundCircle })); + } + } else if (state->photoEmpty) { + state->photoEmpty->paintCircle( + p, + 0, + 0, + userpic->width(), + photoSize); + } + }, userpic->lifetime()); + userpic->setAttribute(Qt::WA_TransparentForMouseEvents); + if (const auto photo = invite.photo) { + state->photoMedia = photo->createMediaView(); + state->photoMedia->wanted( + Data::PhotoSize::Small, + Data::FileOrigin()); + if (!state->photoMedia->image(Data::PhotoSize::Small)) { + session->downloaderTaskFinished( + ) | rpl::on_next([=] { + userpic->update(); + }, userpic->lifetime()); + } + } else { + state->photoEmpty = std::make_unique( + Ui::EmptyUserpic::UserpicColor(0), + invite.title); + } + + Ui::AddSkip(content); + const auto title = box->addRow( + object_ptr( + box, + invite.title, + st::confirmInviteTitle), + style::al_top); + + const auto badgeType = BadgeForInvite(invite); + if (badgeType != Info::Profile::BadgeType::None) { + const auto badgeParent = title->parentWidget(); + const auto badge = box->lifetime().make_state( + badgeParent, + st::infoPeerBadge, + session, + rpl::single(Info::Profile::Badge::Content{ badgeType }), + nullptr, + [] { return false; }); + title->geometryValue( + ) | rpl::on_next([=](const QRect &r) { + badge->move(r.x() + r.width(), r.y(), r.y() + r.height()); + }, title->lifetime()); + } + + box->addRow( + object_ptr( + box, + status, + st::confirmInviteStatus), + style::al_top); + + if (!invite.about.isEmpty()) { + box->addRow( + object_ptr( + box, + invite.about, + st::confirmInviteAbout), + st::confirmInviteAboutPadding, + style::al_top); + } + + if (requestApprove) { + box->addRow( + object_ptr( + box, + (isChannel + ? tr::lng_group_request_about_channel(tr::now) + : tr::lng_group_request_about(tr::now)), + st::confirmInviteStatus), + st::confirmInviteAboutRequestsPadding, + style::al_top); + } + + if (!state->participants.empty()) { + while (state->participants.size() > 4) { + state->participants.pop_back(); + } + const auto padding = (st::confirmInviteUsersWidth + - 4 * st::confirmInviteUserPhotoSize) / 10; + const auto userWidth = st::confirmInviteUserPhotoSize + 2 * padding; + + auto strip = object_ptr(content); + const auto rawStrip = strip.data(); + rawStrip->resize(st::boxWideWidth, st::confirmInviteUserHeight); + rawStrip->setNaturalWidth(st::boxWideWidth); + + const auto shown = int(state->participants.size()); + const auto sumWidth = shown * userWidth; + const auto baseLeft = (st::boxWideWidth - sumWidth) / 2; + for (auto i = 0; i != shown; ++i) { + const auto &participant = state->participants[i]; + const auto name = Ui::CreateChild( + rawStrip, + st::confirmInviteUserName); + name->resizeToWidth( + st::confirmInviteUserPhotoSize + padding); + name->setText(participant.user->firstName.isEmpty() + ? participant.user->name() + : participant.user->firstName); + name->moveToLeft( + baseLeft + i * userWidth + (padding / 2), + st::confirmInviteUserNameTop - st::confirmInviteUserPhotoTop); + } + + rawStrip->paintRequest( + ) | rpl::on_next([=] { + auto p = Painter(rawStrip); + const auto total = int(state->participants.size()); + const auto totalWidth = total * userWidth; + auto left = (rawStrip->width() - totalWidth) / 2; + for (auto &participant : state->participants) { + participant.user->paintUserpicLeft( + p, + participant.userpic, + left + (userWidth - st::confirmInviteUserPhotoSize) / 2, + 0, + rawStrip->width(), + st::confirmInviteUserPhotoSize); + left += userWidth; + } + }, rawStrip->lifetime()); + + Ui::AddSkip(content, st::boxPadding.bottom()); + content->add(std::move(strip), style::margins()); + } + + box->addButton((requestApprove + ? tr::lng_group_request_to_join() + : isChannel + ? tr::lng_profile_join_channel() + : tr::lng_profile_join_group()), submit); + box->addButton(tr::lng_cancel(), [=] { box->closeBox(); }); +} + } // namespace void CheckChatInvite( @@ -399,9 +663,10 @@ void CheckChatInvite( session, hash, &data)) - : strong->show(Box( + : strong->show(Box( + ConfirmInviteBox, session, - data, + &data, invitePeekChannel, [=] { SubmitChatInvite(weak, session, hash, isGroup); })); if (invitePeekChannel) { @@ -450,255 +715,3 @@ void CheckChatInvite( } } // namespace Api - -struct ConfirmInviteBox::Participant { - not_null user; - Ui::PeerUserpicView userpic; -}; - -ConfirmInviteBox::ConfirmInviteBox( - QWidget*, - not_null session, - const MTPDchatInvite &data, - ChannelData *invitePeekChannel, - Fn submit) -: ConfirmInviteBox( - session, - Parse(session, data), - invitePeekChannel, - std::move(submit)) { -} - -ConfirmInviteBox::ConfirmInviteBox( - not_null session, - ChatInvite &&invite, - ChannelData *invitePeekChannel, - Fn submit) -: _session(session) -, _submit(std::move(submit)) -, _title(this, st::confirmInviteTitle) -, _badge(std::make_unique( - this, - st::infoPeerBadge, - _session, - rpl::single(Info::Profile::Badge::Content{ BadgeForInvite(invite) }), - nullptr, - [=] { return false; })) -, _status(this, st::confirmInviteStatus) -, _about(this, st::confirmInviteAbout) -, _aboutRequests(this, st::confirmInviteStatus) -, _participants(std::move(invite.participants)) -, _isChannel(invite.isChannel && !invite.isMegagroup) -, _requestApprove(invite.isRequestNeeded) { - const auto count = invite.participantsCount; - const auto status = [&] { - return invitePeekChannel - ? tr::lng_channel_invite_private(tr::now) - : (!_participants.empty() && _participants.size() < count) - ? tr::lng_group_invite_members(tr::now, lt_count, count) - : (count > 0 && _isChannel) - ? tr::lng_chat_status_subscribers( - tr::now, - lt_count_decimal, - count) - : (count > 0) - ? tr::lng_chat_status_members(tr::now, lt_count_decimal, count) - : _isChannel - ? tr::lng_channel_status(tr::now) - : tr::lng_group_status(tr::now); - }(); - _title->setText(invite.title); - _status->setText(status); - if (!invite.about.isEmpty()) { - _about->setText(invite.about); - } else { - _about.destroy(); - } - if (_requestApprove) { - _aboutRequests->setText(_isChannel - ? tr::lng_group_request_about_channel(tr::now) - : tr::lng_group_request_about(tr::now)); - } else { - _aboutRequests.destroy(); - } - - if (invite.photo) { - _photo = invite.photo->createMediaView(); - _photo->wanted(Data::PhotoSize::Small, Data::FileOrigin()); - if (!_photo->image(Data::PhotoSize::Small)) { - _session->downloaderTaskFinished( - ) | rpl::on_next([=] { - update(); - }, lifetime()); - } - } else { - _photoEmpty = std::make_unique( - Ui::EmptyUserpic::UserpicColor(0), - invite.title); - } -} - -ConfirmInviteBox::~ConfirmInviteBox() = default; - -ConfirmInviteBox::ChatInvite ConfirmInviteBox::Parse( - not_null session, - const MTPDchatInvite &data) { - auto participants = std::vector(); - if (const auto list = data.vparticipants()) { - participants.reserve(list->v.size()); - for (const auto &participant : list->v) { - if (const auto user = session->data().processUser(participant)) { - participants.push_back(Participant{ user }); - } - } - } - const auto photo = session->data().processPhoto(data.vphoto()); - return { - .title = qs(data.vtitle()), - .about = data.vabout().value_or_empty(), - .photo = (photo->isNull() ? nullptr : photo.get()), - .participantsCount = data.vparticipants_count().v, - .participants = std::move(participants), - .isPublic = data.is_public(), - .isChannel = data.is_channel(), - .isMegagroup = data.is_megagroup(), - .isBroadcast = data.is_broadcast(), - .isRequestNeeded = data.is_request_needed(), - .isFake = data.is_fake(), - .isScam = data.is_scam(), - .isVerified = data.is_verified(), - }; -} - -[[nodiscard]] Info::Profile::BadgeType ConfirmInviteBox::BadgeForInvite( - const ChatInvite &invite) { - using Type = Info::Profile::BadgeType; - return invite.isVerified - ? Type::Verified - : invite.isScam - ? Type::Scam - : invite.isFake - ? Type::Fake - : Type::None; -} - -void ConfirmInviteBox::prepare() { - addButton( - (_requestApprove - ? tr::lng_group_request_to_join() - : _isChannel - ? tr::lng_profile_join_channel() - : tr::lng_profile_join_group()), - _submit); - addButton(tr::lng_cancel(), [=] { closeBox(); }); - - while (_participants.size() > 4) { - _participants.pop_back(); - } - - auto newHeight = st::confirmInviteStatusTop + _status->height() + st::boxPadding.bottom(); - if (!_participants.empty()) { - int skip = (st::confirmInviteUsersWidth - 4 * st::confirmInviteUserPhotoSize) / 5; - int padding = skip / 2; - _userWidth = (st::confirmInviteUserPhotoSize + 2 * padding); - int sumWidth = _participants.size() * _userWidth; - int left = (st::boxWideWidth - sumWidth) / 2; - for (const auto &participant : _participants) { - auto name = new Ui::FlatLabel(this, st::confirmInviteUserName); - name->resizeToWidth(st::confirmInviteUserPhotoSize + padding); - name->setText(participant.user->firstName.isEmpty() - ? participant.user->name() - : participant.user->firstName); - name->moveToLeft(left + (padding / 2), st::confirmInviteUserNameTop); - left += _userWidth; - } - - newHeight += st::confirmInviteUserHeight; - } - if (_about) { - const auto padding = st::confirmInviteAboutPadding; - _about->resizeToWidth(st::boxWideWidth - padding.left() - padding.right()); - newHeight += padding.top() + _about->height() + padding.bottom(); - } - if (_aboutRequests) { - const auto padding = st::confirmInviteAboutRequestsPadding; - _aboutRequests->resizeToWidth(st::boxWideWidth - padding.left() - padding.right()); - newHeight += padding.top() + _aboutRequests->height() + padding.bottom(); - } - setDimensions(st::boxWideWidth, newHeight); -} - -void ConfirmInviteBox::resizeEvent(QResizeEvent *e) { - BoxContent::resizeEvent(e); - - const auto padding = st::boxRowPadding; - auto nameWidth = width() - padding.left() - padding.right(); - auto badgeWidth = 0; - if (const auto widget = _badge->widget()) { - badgeWidth = st::infoVerifiedCheckPosition.x() + widget->width(); - nameWidth -= badgeWidth; - } - _title->resizeToWidth(std::min(nameWidth, _title->textMaxWidth())); - _title->moveToLeft( - (width() - _title->width() - badgeWidth) / 2, - st::confirmInviteTitleTop); - const auto badgeLeft = _title->x() + _title->width(); - const auto badgeTop = _title->y(); - const auto badgeBottom = _title->y() + _title->height(); - _badge->move(badgeLeft, badgeTop, badgeBottom); - - _status->move( - (width() - _status->width()) / 2, - st::confirmInviteStatusTop); - auto bottom = _status->y() - + _status->height() - + st::boxPadding.bottom() - + (_participants.empty() ? 0 : st::confirmInviteUserHeight); - if (_about) { - const auto padding = st::confirmInviteAboutPadding; - _about->move((width() - _about->width()) / 2, bottom + padding.top()); - bottom += padding.top() + _about->height() + padding.bottom(); - } - if (_aboutRequests) { - const auto padding = st::confirmInviteAboutRequestsPadding; - _aboutRequests->move((width() - _aboutRequests->width()) / 2, bottom + padding.top()); - } -} - -void ConfirmInviteBox::paintEvent(QPaintEvent *e) { - BoxContent::paintEvent(e); - - Painter p(this); - - if (_photo) { - if (const auto image = _photo->image(Data::PhotoSize::Small)) { - const auto size = st::confirmInvitePhotoSize; - p.drawPixmap( - (width() - size) / 2, - st::confirmInvitePhotoTop, - image->pix( - { size, size }, - { .options = Images::Option::RoundCircle })); - } - } else if (_photoEmpty) { - _photoEmpty->paintCircle( - p, - (width() - st::confirmInvitePhotoSize) / 2, - st::confirmInvitePhotoTop, - width(), - st::confirmInvitePhotoSize); - } - - int sumWidth = _participants.size() * _userWidth; - int left = (width() - sumWidth) / 2; - for (auto &participant : _participants) { - participant.user->paintUserpicLeft( - p, - participant.userpic, - left + (_userWidth - st::confirmInviteUserPhotoSize) / 2, - st::confirmInviteUserPhotoTop, - width(), - st::confirmInviteUserPhotoSize); - left += _userWidth; - } -} diff --git a/Telegram/SourceFiles/api/api_chat_invite.h b/Telegram/SourceFiles/api/api_chat_invite.h index 123ccb1f8d..dbd5a43321 100644 --- a/Telegram/SourceFiles/api/api_chat_invite.h +++ b/Telegram/SourceFiles/api/api_chat_invite.h @@ -7,16 +7,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once -#include "ui/layers/box_content.h" - -class UserData; class ChannelData; -namespace Info::Profile { -class Badge; -enum class BadgeType : uchar; -} // namespace Info::Profile - namespace Main { class Session; } // namespace Main @@ -25,14 +17,6 @@ namespace Window { class SessionController; } // namespace Window -namespace Data { -class PhotoMedia; -} // namespace Data - -namespace Ui { -class EmptyUserpic; -} // namespace Ui - namespace Api { void CheckChatInvite( @@ -42,67 +26,3 @@ void CheckChatInvite( Fn loaded = nullptr); } // namespace Api - -class ConfirmInviteBox final : public Ui::BoxContent { -public: - ConfirmInviteBox( - QWidget*, - not_null session, - const MTPDchatInvite &data, - ChannelData *invitePeekChannel, - Fn submit); - ~ConfirmInviteBox(); - -protected: - void prepare() override; - - void resizeEvent(QResizeEvent *e) override; - void paintEvent(QPaintEvent *e) override; - -private: - struct Participant; - struct ChatInvite { - QString title; - QString about; - PhotoData *photo = nullptr; - int participantsCount = 0; - std::vector participants; - bool isPublic = false; - bool isChannel = false; - bool isMegagroup = false; - bool isBroadcast = false; - bool isRequestNeeded = false; - bool isFake = false; - bool isScam = false; - bool isVerified = false; - }; - [[nodiscard]] static ChatInvite Parse( - not_null session, - const MTPDchatInvite &data); - [[nodiscard]] Info::Profile::BadgeType BadgeForInvite( - const ChatInvite &invite); - - ConfirmInviteBox( - not_null session, - ChatInvite &&invite, - ChannelData *invitePeekChannel, - Fn submit); - - const not_null _session; - - Fn _submit; - object_ptr _title; - std::unique_ptr _badge; - object_ptr _status; - object_ptr _about; - object_ptr _aboutRequests; - std::shared_ptr _photo; - std::unique_ptr _photoEmpty; - std::vector _participants; - - bool _isChannel = false; - bool _requestApprove = false; - - int _userWidth = 0; - -}; diff --git a/Telegram/SourceFiles/boxes/boxes.style b/Telegram/SourceFiles/boxes/boxes.style index e3830b53d1..0698689800 100644 --- a/Telegram/SourceFiles/boxes/boxes.style +++ b/Telegram/SourceFiles/boxes/boxes.style @@ -92,7 +92,7 @@ restoreUserpicIcon: UserpicButton(defaultUserpicButton) { } confirmInviteTitle: FlatLabel(defaultFlatLabel) { - align: align(center); + align: align(top); minWidth: 320px; maxHeight: 24px; textFg: windowBoldFg; @@ -101,9 +101,8 @@ confirmInviteTitle: FlatLabel(defaultFlatLabel) { } } confirmInviteAbout: FlatLabel(boxLabel) { - align: align(center); + align: align(top); minWidth: 320px; - maxHeight: 60px; style: TextStyle(boxLabelStyle) { lineHeight: 19px; } From 88404cfddb450efd2443d791017d783c79afedeb Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Thu, 9 Apr 2026 09:32:41 +0300 Subject: [PATCH 006/184] Removed unused private fields. --- Telegram/SourceFiles/api/api_credits.h | 2 -- Telegram/SourceFiles/api/api_statistics.h | 2 -- Telegram/SourceFiles/boxes/star_gift_preview_box.cpp | 1 - Telegram/SourceFiles/boxes/stickers_box.cpp | 12 ++++-------- Telegram/SourceFiles/boxes/stickers_box.h | 1 - Telegram/SourceFiles/calls/calls_top_bar.h | 1 - Telegram/SourceFiles/data/components/credits.h | 1 - Telegram/SourceFiles/data/components/top_peers.h | 1 - Telegram/SourceFiles/data/data_saved_sublist.h | 2 -- .../SourceFiles/dialogs/ui/dialogs_suggestions.cpp | 1 - .../history/view/media/history_view_media_generic.h | 2 -- Telegram/SourceFiles/info/info_wrap_widget.h | 1 - .../info/peer_gifts/info_peer_gifts_widget.cpp | 1 - .../info/profile/info_profile_inner_widget.h | 2 -- .../info/saved/info_saved_music_provider.h | 2 -- .../SourceFiles/info/saved/info_saved_music_widget.h | 2 -- .../SourceFiles/info/stories/info_stories_provider.h | 2 -- .../SourceFiles/info/stories/info_stories_widget.h | 1 - Telegram/SourceFiles/iv/iv_instance.cpp | 1 - Telegram/SourceFiles/media/audio/media_audio_track.h | 1 - Telegram/SourceFiles/platform/mac/main_window_mac.h | 1 - Telegram/SourceFiles/ui/chat/chat_theme.h | 1 - Telegram/SourceFiles/window/window_filters_menu.h | 3 --- 23 files changed, 4 insertions(+), 40 deletions(-) diff --git a/Telegram/SourceFiles/api/api_credits.h b/Telegram/SourceFiles/api/api_credits.h index 1b5745e42b..0ebe857a78 100644 --- a/Telegram/SourceFiles/api/api_credits.h +++ b/Telegram/SourceFiles/api/api_credits.h @@ -111,8 +111,6 @@ private: const bool _isUser = false; Data::CreditsEarnStatistics _data; - mtpRequestId _requestId = 0; - }; [[nodiscard]] rpl::producer> PremiumPeerBot( diff --git a/Telegram/SourceFiles/api/api_statistics.h b/Telegram/SourceFiles/api/api_statistics.h index b419b8841c..b60dd3d28f 100644 --- a/Telegram/SourceFiles/api/api_statistics.h +++ b/Telegram/SourceFiles/api/api_statistics.h @@ -75,8 +75,6 @@ private: Data::PublicForwardsSlice _firstSlice; - mtpRequestId _requestId = 0; - }; class EarnStatistics final : public StatisticsRequestSender { diff --git a/Telegram/SourceFiles/boxes/star_gift_preview_box.cpp b/Telegram/SourceFiles/boxes/star_gift_preview_box.cpp index 72ac5b9d32..c6c9f65ac6 100644 --- a/Telegram/SourceFiles/boxes/star_gift_preview_box.cpp +++ b/Telegram/SourceFiles/boxes/star_gift_preview_box.cpp @@ -120,7 +120,6 @@ private: QColor _patternColor; Ui::Animations::Simple _selectedAnimation; bool _selected : 1 = false; - bool _patterned : 1 = false; QMargins _extend; diff --git a/Telegram/SourceFiles/boxes/stickers_box.cpp b/Telegram/SourceFiles/boxes/stickers_box.cpp index ed4a7d02c7..feaf023bab 100644 --- a/Telegram/SourceFiles/boxes/stickers_box.cpp +++ b/Telegram/SourceFiles/boxes/stickers_box.cpp @@ -414,8 +414,7 @@ StickersBox::StickersBox( std::shared_ptr show, Section section, bool masks) -: _st(st::stickersRowItem) -, _show(std::move(show)) +: _show(std::move(show)) , _session(&_show->session()) , _api(&_session->mtp()) , _tabs(this, st::stickersTabs) @@ -437,8 +436,7 @@ StickersBox::StickersBox( std::shared_ptr show, not_null megagroup, bool isEmoji) -: _st(st::stickersRowItem) -, _show(std::move(show)) +: _show(std::move(show)) , _session(&_show->session()) , _api(&_session->mtp()) , _section(Section::Installed) @@ -456,8 +454,7 @@ StickersBox::StickersBox( QWidget*, std::shared_ptr show, const QVector &attachedSets) -: _st(st::stickersRowItem) -, _show(std::move(show)) +: _show(std::move(show)) , _session(&_show->session()) , _api(&_session->mtp()) , _section(Section::Attached) @@ -472,8 +469,7 @@ StickersBox::StickersBox( QWidget*, std::shared_ptr show, const std::vector &emojiSets) -: _st(st::stickersRowItem) -, _show(std::move(show)) +: _show(std::move(show)) , _session(&_show->session()) , _api(&_session->mtp()) , _section(Section::Attached) diff --git a/Telegram/SourceFiles/boxes/stickers_box.h b/Telegram/SourceFiles/boxes/stickers_box.h index 89b31ccf51..a6a83da93a 100644 --- a/Telegram/SourceFiles/boxes/stickers_box.h +++ b/Telegram/SourceFiles/boxes/stickers_box.h @@ -143,7 +143,6 @@ private: std::array widgets() const; - const style::PeerListItem &_st; const std::shared_ptr _show; const not_null _session; MTP::Sender _api; diff --git a/Telegram/SourceFiles/calls/calls_top_bar.h b/Telegram/SourceFiles/calls/calls_top_bar.h index 283c647cd4..fbc6438a05 100644 --- a/Telegram/SourceFiles/calls/calls_top_bar.h +++ b/Telegram/SourceFiles/calls/calls_top_bar.h @@ -101,7 +101,6 @@ private: rpl::variable _isGroupConnecting = false; std::vector> _conferenceFirstUsers; - int _conferenceUsersCount = 0; QBrush _groupBrush; anim::linear_gradients _gradients; diff --git a/Telegram/SourceFiles/data/components/credits.h b/Telegram/SourceFiles/data/components/credits.h index c7a7325c4f..c20323e6f6 100644 --- a/Telegram/SourceFiles/data/components/credits.h +++ b/Telegram/SourceFiles/data/components/credits.h @@ -66,7 +66,6 @@ private: rpl::variable _nonLockedBalance; rpl::event_stream<> _loadedChanges; crl::time _lastLoaded = 0; - float64 _rate = 0.; rpl::variable _tonBalance; rpl::event_stream<> _tonLoadedChanges; diff --git a/Telegram/SourceFiles/data/components/top_peers.h b/Telegram/SourceFiles/data/components/top_peers.h index 7f834ad84f..6a247d488a 100644 --- a/Telegram/SourceFiles/data/components/top_peers.h +++ b/Telegram/SourceFiles/data/components/top_peers.h @@ -57,7 +57,6 @@ private: mtpRequestId _requestId = 0; bool _disabled = false; - bool _received = false; }; diff --git a/Telegram/SourceFiles/data/data_saved_sublist.h b/Telegram/SourceFiles/data/data_saved_sublist.h index b1d2422587..c62b618644 100644 --- a/Telegram/SourceFiles/data/data_saved_sublist.h +++ b/Telegram/SourceFiles/data/data_saved_sublist.h @@ -194,8 +194,6 @@ private: bool _restorePinnedWhenNonEmpty = false; - mtpRequestId _reloadUnreadCountRequestId = 0; - rpl::lifetime _lifetime; }; diff --git a/Telegram/SourceFiles/dialogs/ui/dialogs_suggestions.cpp b/Telegram/SourceFiles/dialogs/ui/dialogs_suggestions.cpp index b6d244b8db..d93db6152e 100644 --- a/Telegram/SourceFiles/dialogs/ui/dialogs_suggestions.cpp +++ b/Telegram/SourceFiles/dialogs/ui/dialogs_suggestions.cpp @@ -596,7 +596,6 @@ private: Fn)> _filterOut; rpl::producer<> _filterOutRefreshes; - History *_activeHistory = nullptr; bool _requested = false; rpl::lifetime _lifetime; diff --git a/Telegram/SourceFiles/history/view/media/history_view_media_generic.h b/Telegram/SourceFiles/history/view/media/history_view_media_generic.h index 61d61e2ba3..83eccab63a 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_media_generic.h +++ b/Telegram/SourceFiles/history/view/media/history_view_media_generic.h @@ -132,8 +132,6 @@ private: mutable Part::PaintBg _paintBg; ClickHandlerPtr _fullAreaLink; int _maxWidthCap = 0; - int _marginTop = 0; - int _marginBottom = 0; bool _expandCurrentWidth : 1 = false; bool _service : 1 = false; bool _hideServiceText : 1 = false; diff --git a/Telegram/SourceFiles/info/info_wrap_widget.h b/Telegram/SourceFiles/info/info_wrap_widget.h index 3362ec848d..fdfe5e8348 100644 --- a/Telegram/SourceFiles/info/info_wrap_widget.h +++ b/Telegram/SourceFiles/info/info_wrap_widget.h @@ -229,7 +229,6 @@ private: object_ptr _topBar = { nullptr }; object_ptr _topBarSurrogate = { nullptr }; Ui::Animations::Simple _topBarOverrideAnimation; - bool _topBarOverrideShown = false; object_ptr _topShadow; object_ptr _bottomShadow; diff --git a/Telegram/SourceFiles/info/peer_gifts/info_peer_gifts_widget.cpp b/Telegram/SourceFiles/info/peer_gifts/info_peer_gifts_widget.cpp index 37c790ccd8..a66e3d0c10 100644 --- a/Telegram/SourceFiles/info/peer_gifts/info_peer_gifts_widget.cpp +++ b/Telegram/SourceFiles/info/peer_gifts/info_peer_gifts_widget.cpp @@ -267,7 +267,6 @@ private: mtpRequestId _loadMoreRequestId = 0; Fn _collectionsLoadedCallback; QString _offset; - bool _reloading = false; bool _collectionsLoaded = false; rpl::event_stream _descriptorChanges; diff --git a/Telegram/SourceFiles/info/profile/info_profile_inner_widget.h b/Telegram/SourceFiles/info/profile/info_profile_inner_widget.h index 5ee2507af8..e56b067c34 100644 --- a/Telegram/SourceFiles/info/profile/info_profile_inner_widget.h +++ b/Telegram/SourceFiles/info/profile/info_profile_inner_widget.h @@ -107,8 +107,6 @@ private: rpl::event_stream _onlineCount; rpl::event_stream<> _showFinished; - PeerData *_reactionGroup = nullptr; - std::shared_ptr _nonPersonalView; rpl::variable> _topBarColor; diff --git a/Telegram/SourceFiles/info/saved/info_saved_music_provider.h b/Telegram/SourceFiles/info/saved/info_saved_music_provider.h index 67dc4af511..630401bd24 100644 --- a/Telegram/SourceFiles/info/saved/info_saved_music_provider.h +++ b/Telegram/SourceFiles/info/saved/info_saved_music_provider.h @@ -123,8 +123,6 @@ private: rpl::event_stream> _layoutRemoved; rpl::event_stream<> _refreshed; - bool _started = false; - rpl::lifetime _lifetime; rpl::lifetime _viewerLifetime; diff --git a/Telegram/SourceFiles/info/saved/info_saved_music_widget.h b/Telegram/SourceFiles/info/saved/info_saved_music_widget.h index 5ecef46511..cc29577c46 100644 --- a/Telegram/SourceFiles/info/saved/info_saved_music_widget.h +++ b/Telegram/SourceFiles/info/saved/info_saved_music_widget.h @@ -42,7 +42,6 @@ public: private: Media::Memento _media; - int _addingToAlbumId = 0; }; @@ -71,7 +70,6 @@ private: std::shared_ptr doCreateMemento() override; MusicInner *_inner = nullptr; - bool _shown = false; }; diff --git a/Telegram/SourceFiles/info/stories/info_stories_provider.h b/Telegram/SourceFiles/info/stories/info_stories_provider.h index 3dc7601f60..1f8baf0205 100644 --- a/Telegram/SourceFiles/info/stories/info_stories_provider.h +++ b/Telegram/SourceFiles/info/stories/info_stories_provider.h @@ -129,8 +129,6 @@ private: rpl::event_stream> _layoutRemoved; rpl::event_stream<> _refreshed; - bool _started = false; - rpl::lifetime _lifetime; rpl::lifetime _viewerLifetime; diff --git a/Telegram/SourceFiles/info/stories/info_stories_widget.h b/Telegram/SourceFiles/info/stories/info_stories_widget.h index f35238b6fd..ff891992bb 100644 --- a/Telegram/SourceFiles/info/stories/info_stories_widget.h +++ b/Telegram/SourceFiles/info/stories/info_stories_widget.h @@ -43,7 +43,6 @@ public: private: Media::Memento _media; - int _addingToAlbumId = 0; }; diff --git a/Telegram/SourceFiles/iv/iv_instance.cpp b/Telegram/SourceFiles/iv/iv_instance.cpp index 581c388b96..0d9c79b3bc 100644 --- a/Telegram/SourceFiles/iv/iv_instance.cpp +++ b/Telegram/SourceFiles/iv/iv_instance.cpp @@ -163,7 +163,6 @@ private: base::flat_map _embeds; base::flat_map _maps; std::vector _resources; - int _resource = -1; rpl::event_stream _events; diff --git a/Telegram/SourceFiles/media/audio/media_audio_track.h b/Telegram/SourceFiles/media/audio/media_audio_track.h index 5b37fb5e9f..a4476a28ee 100644 --- a/Telegram/SourceFiles/media/audio/media_audio_track.h +++ b/Telegram/SourceFiles/media/audio/media_audio_track.h @@ -68,7 +68,6 @@ private: bool _failed = false; bool _active = false; bool _looping = false; - float64 _volume = 1.; int64 _samplesCount = 0; int32 _sampleRate = 0; diff --git a/Telegram/SourceFiles/platform/mac/main_window_mac.h b/Telegram/SourceFiles/platform/mac/main_window_mac.h index 890639f1e2..ab212a6c76 100644 --- a/Telegram/SourceFiles/platform/mac/main_window_mac.h +++ b/Telegram/SourceFiles/platform/mac/main_window_mac.h @@ -59,7 +59,6 @@ private: std::unique_ptr _private; - mutable bool psIdle; mutable QTimer psIdleTimer; base::Timer _hideAfterFullScreenTimer; diff --git a/Telegram/SourceFiles/ui/chat/chat_theme.h b/Telegram/SourceFiles/ui/chat/chat_theme.h index 05e52280a9..2e00a9b04b 100644 --- a/Telegram/SourceFiles/ui/chat/chat_theme.h +++ b/Telegram/SourceFiles/ui/chat/chat_theme.h @@ -222,7 +222,6 @@ private: CacheBackgroundRequest _backgroundCachingRequest; CacheBackgroundRequest _nextCachingRequest; CacheBackgroundResult _backgroundNext; - int _backgroundVersion = 0; QSize _cacheBackgroundArea; crl::time _lastBackgroundAreaChangeTime = 0; std::optional _cacheBackgroundTimer; diff --git a/Telegram/SourceFiles/window/window_filters_menu.h b/Telegram/SourceFiles/window/window_filters_menu.h index 202b11a326..cf653a7270 100644 --- a/Telegram/SourceFiles/window/window_filters_menu.h +++ b/Telegram/SourceFiles/window/window_filters_menu.h @@ -75,9 +75,6 @@ private: Api::RemoveComplexChatFilter _removeApi; - FilterId _removingId = 0; - mtpRequestId _removingRequestId = 0; - base::unique_qptr _popupMenu; struct { base::Timer timer; From 61f89d7b332afbc541dcdf3246f617c12e5086b9 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Thu, 9 Apr 2026 10:26:35 +0300 Subject: [PATCH 007/184] Fixed build with Xcode 26.4. --- .github/workflows/mac_packaged.yml | 2 +- Telegram/build/prepare/prepare.py | 2 +- Telegram/lib_ui | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/mac_packaged.yml b/.github/workflows/mac_packaged.yml index 5153f3492a..2e91e85491 100644 --- a/.github/workflows/mac_packaged.yml +++ b/.github/workflows/mac_packaged.yml @@ -73,7 +73,7 @@ jobs: run: | brew update brew upgrade || true - brew install ada-url autoconf automake boost cmake ffmpeg@6 jpeg-xl libavif libheif libtool openal-soft openh264 openssl opus ninja pkg-config python qtbase qtimageformats qtsvg xz + brew install ada-url autoconf automake boost cmake ffmpeg@6 jpeg-xl libavif libheif libtool openal-soft openh264 openssl opus ninja pkg-config python qtbase qtimageformats qtsvg xz || true sudo xcode-select -s /Applications/Xcode.app/Contents/Developer sudo sed -i '' '/CMAKE_${lang}_FLAGS_DEBUG_INIT/s/ -g//' /opt/homebrew/share/cmake/Modules/Compiler/GNU.cmake diff --git a/Telegram/build/prepare/prepare.py b/Telegram/build/prepare/prepare.py index 6f7aad7fe2..f1810e80c1 100644 --- a/Telegram/build/prepare/prepare.py +++ b/Telegram/build/prepare/prepare.py @@ -1680,7 +1680,7 @@ win: stage('tg_owt', """ git clone https://github.com/desktop-app/tg_owt.git cd tg_owt - git checkout 5c5c71258777d0196dbb3a09cc37d2f56ead28ab + git checkout 89df288dd6ba5b2ec95b3c5eaf1e7e0c3a870fc4 git submodule update --init --recursive win: SET MOZJPEG_PATH=$LIBS_DIR/mozjpeg diff --git a/Telegram/lib_ui b/Telegram/lib_ui index 798f246338..fc740b2341 160000 --- a/Telegram/lib_ui +++ b/Telegram/lib_ui @@ -1 +1 @@ -Subproject commit 798f246338fffa6884cbd2bc02949a40ea1afac5 +Subproject commit fc740b234111115a245b75a65bfb193d3b358695 From d0e24b555c0f67f6e41438691cf39c514f0a561f Mon Sep 17 00:00:00 2001 From: John Preston Date: Thu, 9 Apr 2026 17:06:44 +0700 Subject: [PATCH 008/184] Bring fullscreen media viewer to active space on macOS. --- .../SourceFiles/platform/mac/overlay_widget_mac.mm | 12 ++++++++++++ Telegram/lib_ui | 2 +- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/Telegram/SourceFiles/platform/mac/overlay_widget_mac.mm b/Telegram/SourceFiles/platform/mac/overlay_widget_mac.mm index 84892cf879..914525fe20 100644 --- a/Telegram/SourceFiles/platform/mac/overlay_widget_mac.mm +++ b/Telegram/SourceFiles/platform/mac/overlay_widget_mac.mm @@ -93,6 +93,18 @@ void MacOverlayWidgetHelper::updateStyles(bool fullscreen) { ? NSNormalWindowLevel : NSPopUpMenuWindowLevel; [window setLevel:level]; + + // Fullscreen overlay: follow the currently active Space on activation. + // Windowed overlay: behave like a normal tool panel, so the user can move + // it to another desktop and it stays there. + auto behavior = [window collectionBehavior]; + if (fullscreen) { + behavior |= NSWindowCollectionBehaviorMoveToActiveSpace; + } else { + behavior &= ~NSWindowCollectionBehaviorMoveToActiveSpace; + } + [window setCollectionBehavior:behavior]; + [window setHidesOnDeactivate:!_data->window->testAttribute(Qt::WA_MacAlwaysShowToolWindow)]; [window setTitleVisibility:NSWindowTitleHidden]; [window setTitlebarAppearsTransparent:YES]; diff --git a/Telegram/lib_ui b/Telegram/lib_ui index fc740b2341..482e2dc1fd 160000 --- a/Telegram/lib_ui +++ b/Telegram/lib_ui @@ -1 +1 @@ -Subproject commit fc740b234111115a245b75a65bfb193d3b358695 +Subproject commit 482e2dc1fd94d3d2ca3f6e666499c647b0b67aba From 07250a1bcf7d765bb7abb61a550908e082aaae45 Mon Sep 17 00:00:00 2001 From: John Preston Date: Fri, 10 Apr 2026 12:54:27 +0700 Subject: [PATCH 009/184] Revert translation by "Don't translate {lang}". --- .../view/history_view_translate_tracker.cpp | 46 +++++++++++++++++-- .../view/history_view_translate_tracker.h | 2 + 2 files changed, 44 insertions(+), 4 deletions(-) diff --git a/Telegram/SourceFiles/history/view/history_view_translate_tracker.cpp b/Telegram/SourceFiles/history/view/history_view_translate_tracker.cpp index d090613186..245a9f7b00 100644 --- a/Telegram/SourceFiles/history/view/history_view_translate_tracker.cpp +++ b/Telegram/SourceFiles/history/view/history_view_translate_tracker.cpp @@ -14,6 +14,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_changes.h" #include "data/data_channel.h" #include "data/data_flags.h" +#include "data/data_peer.h" #include "data/data_peer_values.h" // Data::AmPremiumValue. #include "data/data_session.h" #include "history/history.h" @@ -74,12 +75,10 @@ void TranslateTracker::setup() { if (tracking) { recognizeCollected(); trackSkipLanguages(); + trackTranslationDisabled(); } else { checkRecognized({}); - _history->translateTo({}); - if (const auto migrated = _history->migrateFrom()) { - migrated->translateTo({}); - } + stopAndRevert(); } }, _lifetime); } @@ -249,6 +248,24 @@ void TranslateTracker::cancelSentRequest() { } } +void TranslateTracker::stopAndRevert() { + cancelToRequest(); + cancelSentRequest(); + const auto owner = &_history->owner(); + for (const auto &[id, entry] : _itemsForRecognize) { + if (const auto item = owner->message(id)) { + if (item->translation() + && item->translationShowRequiresCheck({})) { + item->translationShowRequiresRequest({}); + } + } + } + _history->translateTo({}); + if (const auto migrated = _history->migrateFrom()) { + migrated->translateTo({}); + } +} + void TranslateTracker::requestSome() { if (_requestInProcess || _itemsToRequest.empty()) { return; @@ -369,7 +386,28 @@ void TranslateTracker::recognizeCollected() { void TranslateTracker::trackSkipLanguages() { Core::App().settings().skipTranslationLanguagesValue( ) | rpl::on_next([=](const std::vector &skip) { + const auto wasOfferedFrom = _history->translateOfferedFrom(); + const auto wasTranslatedTo = _history->translatedTo(); checkRecognized(skip); + if (wasTranslatedTo + && wasOfferedFrom + && !_history->translateOfferedFrom()) { + stopAndRevert(); + } + }, _trackingLifetime); +} + +void TranslateTracker::trackTranslationDisabled() { + using PeerFlag = Data::PeerUpdate::Flag; + _history->session().changes().peerFlagsValue( + _history->peer, + PeerFlag::TranslationDisabled + ) | rpl::skip(1) | rpl::on_next([=] { + using TranslationFlag = PeerData::TranslationFlag; + if (_history->peer->translationFlag() == TranslationFlag::Disabled + && _history->translatedTo()) { + stopAndRevert(); + } }, _trackingLifetime); } diff --git a/Telegram/SourceFiles/history/view/history_view_translate_tracker.h b/Telegram/SourceFiles/history/view/history_view_translate_tracker.h index 786b6f1bb7..b758e54e73 100644 --- a/Telegram/SourceFiles/history/view/history_view_translate_tracker.h +++ b/Telegram/SourceFiles/history/view/history_view_translate_tracker.h @@ -49,12 +49,14 @@ private: bool add(not_null item, bool skipDependencies); void recognizeCollected(); void trackSkipLanguages(); + void trackTranslationDisabled(); void checkRecognized(); void checkRecognized(const std::vector &skip); void applyLimit(); void requestSome(); void cancelToRequest(); void cancelSentRequest(); + void stopAndRevert(); void switchTranslation(not_null item, LanguageId id); const not_null _history; From a8b59804fa18f0df3aaa5290e044b1cefaae19a6 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Wed, 8 Apr 2026 10:09:21 +0300 Subject: [PATCH 010/184] Disabled debug logging by default for macOS debug builds. --- Telegram/SourceFiles/core/launcher.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Telegram/SourceFiles/core/launcher.cpp b/Telegram/SourceFiles/core/launcher.cpp index d56d1d50b6..f0e5120aa7 100644 --- a/Telegram/SourceFiles/core/launcher.cpp +++ b/Telegram/SourceFiles/core/launcher.cpp @@ -119,7 +119,7 @@ void ComputeDebugMode() { auto file = QFile(debugModeSettingPath); if (file.exists() && file.open(QIODevice::ReadOnly)) { Logs::SetDebugEnabled(file.read(1) != "0"); -#if defined _DEBUG +#if defined _DEBUG && !defined Q_OS_MAC } else { Logs::SetDebugEnabled(true); #endif From bdcdafab2dd1705322b76b028268964c30d8d821 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Thu, 9 Apr 2026 16:47:15 +0300 Subject: [PATCH 011/184] Fixed album forwarding with top peers selector. --- Telegram/SourceFiles/boxes/share_box.cpp | 8 +++++--- .../history/view/history_view_top_peers_selector.cpp | 7 +++++-- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/Telegram/SourceFiles/boxes/share_box.cpp b/Telegram/SourceFiles/boxes/share_box.cpp index 92d098a56c..96130dc32d 100644 --- a/Telegram/SourceFiles/boxes/share_box.cpp +++ b/Telegram/SourceFiles/boxes/share_box.cpp @@ -2026,16 +2026,18 @@ void FastShareMessageToSelf( std::shared_ptr show, not_null item) { const auto self = show->session().user(); + const auto &owner = self->owner(); + const auto items = owner.idsToItems(owner.itemOrItsGroup(item)); const auto donePhraseArgs = ChatHelpers::ForwardedMessagePhraseArgs{ .toCount = 1, - .singleMessage = true, + .singleMessage = (items.size() == 1), .to1 = self, .to2 = nullptr, }; - auto sendAction = Api::SendAction(self->owner().history(self)); + auto sendAction = Api::SendAction(owner.history(self)); sendAction.clearDraft = false; show->session().api().forwardMessages( - Data::ResolvedForwardDraft{ .items = {item} }, + Data::ResolvedForwardDraft{ .items = items }, std::move(sendAction), [=] { auto phrase = rpl::variable( diff --git a/Telegram/SourceFiles/history/view/history_view_top_peers_selector.cpp b/Telegram/SourceFiles/history/view/history_view_top_peers_selector.cpp index 63da74acd6..aa7699dbfd 100644 --- a/Telegram/SourceFiles/history/view/history_view_top_peers_selector.cpp +++ b/Telegram/SourceFiles/history/view/history_view_top_peers_selector.cpp @@ -69,15 +69,18 @@ void ShowTopPeersSelector( const auto send = [=](not_null peer) { if (const auto item = session->data().message(fullId)) { + const auto items = session->data().idsToItems( + session->data().itemOrItsGroup(item)); + const auto single = (items.size() == 1); session->api().forwardMessages( - Data::ResolvedForwardDraft{ .items = { item } }, + Data::ResolvedForwardDraft{ .items = items }, Api::SendAction(session->data().history(peer)), [=] { using namespace ChatHelpers; auto text = rpl::variable( ForwardedMessagePhrase({ .toCount = 1, - .singleMessage = 1, + .singleMessage = single, .to1 = peer, })).current(); show->showToast(std::move(text)); From c773f16a4a27678cee71f6dc1a232fd0bebc1c1e Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Fri, 10 Apr 2026 09:13:17 +0300 Subject: [PATCH 012/184] Added proxy check IP exposure warning before status check. --- Telegram/Resources/langs/lang.strings | 3 +++ Telegram/SourceFiles/boxes/connection_box.cpp | 17 ++++++++++++++++- .../SourceFiles/core/core_settings_proxy.cpp | 18 +++++++++++++++++- .../SourceFiles/core/core_settings_proxy.h | 4 ++++ 4 files changed, 40 insertions(+), 2 deletions(-) diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index e92802de88..67a859e482 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -339,6 +339,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_proxy_invalid" = "The proxy link is invalid."; "lng_proxy_unsupported" = "Your Telegram Desktop version doesn't support this proxy type or the proxy link is invalid. Please update Telegram Desktop to the latest version."; "lng_proxy_incorrect_secret" = "This proxy link uses invalid **secret** parameter. Please contact the proxy provider and ask him to update MTProxy source code and configure it with a correct **secret** value. Then let him provide a new link."; +"lng_proxy_check_ip_warning_title" = "Warning"; +"lng_proxy_check_ip_warning" = "This will expose your IP address to the admin of the proxy server."; +"lng_proxy_check_ip_proceed" = "Proceed"; "lng_edit_deleted" = "This message was deleted"; "lng_edit_limit_reached#one" = "You've reached the message text limit. Please make the text shorter by {count} character."; diff --git a/Telegram/SourceFiles/boxes/connection_box.cpp b/Telegram/SourceFiles/boxes/connection_box.cpp index 1bdb77d824..0a172d31d4 100644 --- a/Telegram/SourceFiles/boxes/connection_box.cpp +++ b/Telegram/SourceFiles/boxes/connection_box.cpp @@ -1647,7 +1647,22 @@ void ProxiesBoxController::ShowApplyConfirmation( } }; statusLabel->setClickHandlerFilter([=](const auto &...) { - runCheck(); + auto &proxy = Core::App().settings().proxy(); + if (proxy.checkIpWarningShown()) { + runCheck(); + } else { + box->uiShow()->showBox(Ui::MakeConfirmBox({ + .text = tr::lng_proxy_check_ip_warning(), + .confirmed = [=] { + auto &proxy = Core::App().settings().proxy(); + proxy.setCheckIpWarningShown(true); + Local::writeSettings(); + runCheck(); + }, + .confirmText = tr::lng_proxy_check_ip_proceed(), + .title = tr::lng_proxy_check_ip_warning_title(), + })); + } return false; }); } diff --git a/Telegram/SourceFiles/core/core_settings_proxy.cpp b/Telegram/SourceFiles/core/core_settings_proxy.cpp index 1807aa2efe..6f338de6aa 100644 --- a/Telegram/SourceFiles/core/core_settings_proxy.cpp +++ b/Telegram/SourceFiles/core/core_settings_proxy.cpp @@ -107,7 +107,8 @@ QByteArray SettingsProxy::serialize() const { serializedList, 0, ranges::plus(), - &Serialize::bytearraySize); + &Serialize::bytearraySize) + + 1 * sizeof(qint32); // _checkIpWarningShown auto stream = Serialize::ByteArrayWriter(size); stream << qint32(_tryIPv6 ? 1 : 0) @@ -118,6 +119,7 @@ QByteArray SettingsProxy::serialize() const { for (const auto &i : serializedList) { stream << i; } + stream << qint32(_checkIpWarningShown ? 1 : 0); return std::move(stream).result(); } @@ -150,6 +152,11 @@ bool SettingsProxy::setFromSerialized(const QByteArray &serialized) { } } + auto checkIpWarningShown = qint32(0); + if (!stream.atEnd()) { + stream >> checkIpWarningShown; + } + if (!stream.ok()) { LOG(("App Error: " "Bad data for Core::SettingsProxy::setFromSerialized()")); @@ -158,6 +165,7 @@ bool SettingsProxy::setFromSerialized(const QByteArray &serialized) { _tryIPv6 = (tryIPv6 == 1); _useProxyForCalls = (useProxyForCalls == 1); + _checkIpWarningShown = (checkIpWarningShown == 1); _settings = IntToProxySettings(settings); _selected = DeserializeProxyData(selectedProxy); @@ -176,6 +184,14 @@ bool SettingsProxy::isDisabled() const { return _settings == MTP::ProxyData::Settings::Disabled; } +bool SettingsProxy::checkIpWarningShown() const { + return _checkIpWarningShown; +} + +void SettingsProxy::setCheckIpWarningShown(bool value) { + _checkIpWarningShown = value; +} + bool SettingsProxy::tryIPv6() const { return _tryIPv6; } diff --git a/Telegram/SourceFiles/core/core_settings_proxy.h b/Telegram/SourceFiles/core/core_settings_proxy.h index bd709c230a..340dd050d8 100644 --- a/Telegram/SourceFiles/core/core_settings_proxy.h +++ b/Telegram/SourceFiles/core/core_settings_proxy.h @@ -35,6 +35,9 @@ public: [[nodiscard]] MTP::ProxyData selected() const; void setSelected(MTP::ProxyData value); + [[nodiscard]] bool checkIpWarningShown() const; + void setCheckIpWarningShown(bool value); + [[nodiscard]] const std::vector &list() const; [[nodiscard]] std::vector &list(); @@ -44,6 +47,7 @@ public: private: bool _tryIPv6 = false; bool _useProxyForCalls = false; + bool _checkIpWarningShown = false; MTP::ProxyData::Settings _settings = MTP::ProxyData::Settings::System; MTP::ProxyData _selected; std::vector _list; From 432072a3d41d762f4055cc75b88bc9f14a10bba3 Mon Sep 17 00:00:00 2001 From: John Preston Date: Thu, 9 Apr 2026 14:00:06 +0700 Subject: [PATCH 013/184] Stream several messages together. --- Telegram/SourceFiles/data/data_session.cpp | 15 +++ Telegram/SourceFiles/history/history.cpp | 7 +- Telegram/SourceFiles/history/history.h | 1 + Telegram/SourceFiles/history/history_item.cpp | 4 + Telegram/SourceFiles/history/history_item.h | 1 + .../history/history_streamed_drafts.cpp | 107 ++++++++++++++---- .../history/history_streamed_drafts.h | 12 +- 7 files changed, 116 insertions(+), 31 deletions(-) diff --git a/Telegram/SourceFiles/data/data_session.cpp b/Telegram/SourceFiles/data/data_session.cpp index a3704e2d37..8667b03fb0 100644 --- a/Telegram/SourceFiles/data/data_session.cpp +++ b/Telegram/SourceFiles/data/data_session.cpp @@ -30,6 +30,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "history/history.h" #include "history/history_item.h" #include "history/history_item_components.h" +#include "history/history_streamed_drafts.h" #include "history/view/media/history_view_media.h" #include "history/view/history_view_element.h" #include "inline_bots/inline_bot_layout_item.h" @@ -3107,6 +3108,20 @@ HistoryItem *Session::addNewMessage( return nullptr; } + if (data.type() == mtpc_message) { + if (const auto h = historyLoaded(peerId)) { + if (const auto streamed = h->streamedDraftsIfExists()) { + if (const auto adopted = streamed->adoptIncoming( + data.c_message())) { + if (type == NewMessageType::Unread) { + CheckForSwitchInlineButton(adopted); + } + return adopted; + } + } + } + } + const auto result = history(peerId)->addNewMessage( id, data, diff --git a/Telegram/SourceFiles/history/history.cpp b/Telegram/SourceFiles/history/history.cpp index 497c1f91c4..930ce1c01f 100644 --- a/Telegram/SourceFiles/history/history.cpp +++ b/Telegram/SourceFiles/history/history.cpp @@ -1594,9 +1594,6 @@ void History::newItemAdded(not_null item) { if (const auto sublist = item->savedSublist()) { sublist->applyItemAdded(item); } - if (const auto streamed = _streamedDrafts.get()) { - streamed->applyItemAdded(item); - } if (const auto media = item->media()) { if (const auto gift = media->gift()) { if (const auto unique = gift->unique.get()) { @@ -3958,6 +3955,10 @@ HistoryStreamedDrafts &History::streamedDrafts() { return *_streamedDrafts; } +HistoryStreamedDrafts *History::streamedDraftsIfExists() const { + return _streamedDrafts.get(); +} + HistoryItem *History::joinedMessageInstance() const { return _joinedMessage; } diff --git a/Telegram/SourceFiles/history/history.h b/Telegram/SourceFiles/history/history.h index e8848ecae3..f85d8daa21 100644 --- a/Telegram/SourceFiles/history/history.h +++ b/Telegram/SourceFiles/history/history.h @@ -93,6 +93,7 @@ public: [[nodiscard]] Data::HistoryMessages *maybeMessages(); [[nodiscard]] HistoryStreamedDrafts &streamedDrafts(); + [[nodiscard]] HistoryStreamedDrafts *streamedDraftsIfExists() const; [[nodiscard]] HistoryItem *joinedMessageInstance() const; void checkLocalMessages(); diff --git a/Telegram/SourceFiles/history/history_item.cpp b/Telegram/SourceFiles/history/history_item.cpp index e698a95d65..2b56d44992 100644 --- a/Telegram/SourceFiles/history/history_item.cpp +++ b/Telegram/SourceFiles/history/history_item.cpp @@ -2711,6 +2711,10 @@ QString HistoryItem::notificationHeader() const { return QString(); } +void HistoryItem::markBeingSentForAdoption() { + _flags |= MessageFlag::BeingSent; +} + void HistoryItem::setRealId(MsgId newId) { Expects(_flags & MessageFlag::BeingSent); Expects(IsClientMsgId(id)); diff --git a/Telegram/SourceFiles/history/history_item.h b/Telegram/SourceFiles/history/history_item.h index f4c9b6d99d..9d30f6d271 100644 --- a/Telegram/SourceFiles/history/history_item.h +++ b/Telegram/SourceFiles/history/history_item.h @@ -440,6 +440,7 @@ public: bool isForumPost); void setPostAuthor(const QString &author); void setRealId(MsgId newId); + void markBeingSentForAdoption(); void incrementReplyToTopCounter(); void applyEffectWatchedOnUnreadKnown(); diff --git a/Telegram/SourceFiles/history/history_streamed_drafts.cpp b/Telegram/SourceFiles/history/history_streamed_drafts.cpp index e88d1b612e..ef67083543 100644 --- a/Telegram/SourceFiles/history/history_streamed_drafts.cpp +++ b/Telegram/SourceFiles/history/history_streamed_drafts.cpp @@ -9,6 +9,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "api/api_text_entities.h" #include "data/data_forum_topic.h" +#include "data/data_peer_id.h" #include "data/data_session.h" #include "history/history.h" #include "history/history_item.h" @@ -17,6 +18,15 @@ namespace { constexpr auto kClearTimeout = 30 * crl::time(1000); +[[nodiscard]] int CommonPrefixLength(const QString &a, const QString &b) { + const auto count = std::min(a.size(), b.size()); + auto i = 0; + while (i < count && a[i] == b[i]) { + ++i; + } + return i; +} + } // namespace HistoryStreamedDrafts::HistoryStreamedDrafts(not_null history) @@ -25,7 +35,7 @@ HistoryStreamedDrafts::HistoryStreamedDrafts(not_null history) } HistoryStreamedDrafts::~HistoryStreamedDrafts() { - for (const auto &[rootId, draft] : base::take(_drafts)) { + for (const auto &[randomId, draft] : base::take(_drafts)) { draft.message->destroy(); } } @@ -41,19 +51,18 @@ void HistoryStreamedDrafts::apply( if (!rootId) { rootId = Data::ForumTopic::kGeneralId; } + const auto randomId = data.vrandom_id().v; if (!when) { - clear(rootId); + clearByRandomId(randomId); return; } const auto text = Api::ParseTextWithEntities( &_history->session(), data.vtext()); - const auto randomId = data.vrandom_id().v; - if (update(rootId, randomId, text)) { + if (update(randomId, text)) { return; } - clear(rootId); - _drafts.emplace(rootId, Draft{ + _drafts.emplace(randomId, Draft{ .message = _history->addNewLocalMessage({ .id = _history->owner().nextLocalMessageId(), .flags = (MessageFlag::Local @@ -66,7 +75,8 @@ void HistoryStreamedDrafts::apply( }, .date = when, }, text, MTP_messageMediaEmpty()), - .randomId = randomId, + .rootId = rootId, + .fromId = fromId, .updated = crl::now(), }); if (!_checkTimer.isActive()) { @@ -75,11 +85,10 @@ void HistoryStreamedDrafts::apply( } bool HistoryStreamedDrafts::update( - MsgId rootId, uint64 randomId, const TextWithEntities &text) { - const auto i = _drafts.find(rootId); - if (i == end(_drafts) || i->second.randomId != randomId) { + const auto i = _drafts.find(randomId); + if (i == end(_drafts)) { return false; } i->second.message->setText(text); @@ -87,8 +96,8 @@ bool HistoryStreamedDrafts::update( return true; } -void HistoryStreamedDrafts::clear(MsgId rootId) { - if (const auto draft = _drafts.take(rootId)) { +void HistoryStreamedDrafts::clearByRandomId(uint64 randomId) { + if (const auto draft = _drafts.take(randomId)) { draft->message->destroy(); } if (_drafts.empty()) { @@ -98,18 +107,13 @@ void HistoryStreamedDrafts::clear(MsgId rootId) { bool HistoryStreamedDrafts::hasFor(not_null item) const { const auto rootId = item->topicRootId(); - const auto i = _drafts.find(rootId); - return (i != end(_drafts)) - && (i->second.message->from() == item->from()); -} - -void HistoryStreamedDrafts::applyItemAdded(not_null item) { - const auto rootId = item->topicRootId(); - const auto i = _drafts.find(rootId); - if (i == end(_drafts) || i->second.message->from() != item->from()) { - return; + const auto from = item->from(); + for (const auto &[randomId, draft] : _drafts) { + if (draft.rootId == rootId && draft.message->from() == from) { + return true; + } } - clear(rootId); + return false; } void HistoryStreamedDrafts::applyItemRemoved(not_null item) { @@ -124,6 +128,63 @@ void HistoryStreamedDrafts::applyItemRemoved(not_null item) { } } +HistoryItem *HistoryStreamedDrafts::adoptIncoming( + const MTPDmessage &data) { + if (_drafts.empty()) { + return nullptr; + } + const auto fromId = data.vfrom_id() + ? peerFromMTP(*data.vfrom_id()) + : _history->peer->id; + auto rootId = MsgId(0); + if (const auto reply = data.vreply_to()) { + reply->match([&](const MTPDmessageReplyHeader &d) { + if (d.is_forum_topic()) { + rootId = d.vreply_to_top_id().value_or_empty(); + if (!rootId) { + rootId = d.vreply_to_msg_id().value_or_empty(); + } + } + }, [](const MTPDmessageReplyStoryHeader &) {}); + } + if (!rootId) { + rootId = Data::ForumTopic::kGeneralId; + } + const auto incomingText = qs(data.vmessage()); + auto best = end(_drafts); + auto bestPrefix = 0; + for (auto i = begin(_drafts); i != end(_drafts); ++i) { + const auto &draft = i->second; + if (draft.rootId != rootId) { + continue; + } + if (draft.message->from()->id != fromId) { + continue; + } + const auto prefix = CommonPrefixLength( + draft.message->originalText().text, + incomingText); + if (prefix > bestPrefix) { + bestPrefix = prefix; + best = i; + } + } + if (best == end(_drafts) || bestPrefix <= 0) { + return nullptr; + } + const auto item = best->second.message.get(); + _drafts.erase(best); + + item->markBeingSentForAdoption(); + item->setRealId(data.vid().v); + item->applySentMessage(data); + + if (_drafts.empty()) { + scheduleDestroy(); + } + return item; +} + void HistoryStreamedDrafts::check() { auto closest = crl::time(); const auto now = crl::now(); diff --git a/Telegram/SourceFiles/history/history_streamed_drafts.h b/Telegram/SourceFiles/history/history_streamed_drafts.h index d521ccc80c..c46f6d102b 100644 --- a/Telegram/SourceFiles/history/history_streamed_drafts.h +++ b/Telegram/SourceFiles/history/history_streamed_drafts.h @@ -11,6 +11,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "base/weak_ptr.h" class History; +class MTPDmessage; class HistoryStreamedDrafts final : public base::has_weak_ptr { public: @@ -26,24 +27,25 @@ public: const MTPDsendMessageTextDraftAction &data); [[nodiscard]] bool hasFor(not_null item) const; - void applyItemAdded(not_null item); void applyItemRemoved(not_null item); + HistoryItem *adoptIncoming(const MTPDmessage &data); private: struct Draft { not_null message; - uint64 randomId = 0; + MsgId rootId = 0; + PeerId fromId = 0; crl::time updated = 0; }; - bool update(MsgId rootId, uint64 randomId, const TextWithEntities &text); - void clear(MsgId rootId); + bool update(uint64 randomId, const TextWithEntities &text); + void clearByRandomId(uint64 randomId); void check(); void scheduleDestroy(); const not_null _history; - base::flat_map _drafts; + base::flat_map _drafts; base::Timer _checkTimer; From 8dfb14cf7a8843fe8e65400989c652182733c7c2 Mon Sep 17 00:00:00 2001 From: John Preston Date: Thu, 9 Apr 2026 19:13:45 +0700 Subject: [PATCH 014/184] Fix possible crash in empty menu show. --- Telegram/lib_ui | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Telegram/lib_ui b/Telegram/lib_ui index 482e2dc1fd..4bff46b7b2 160000 --- a/Telegram/lib_ui +++ b/Telegram/lib_ui @@ -1 +1 @@ -Subproject commit 482e2dc1fd94d3d2ca3f6e666499c647b0b67aba +Subproject commit 4bff46b7b25c0ba93717ac5122306d88d53ca410 From cdf1f169447ee64d0fc79123cb37875cab914036 Mon Sep 17 00:00:00 2001 From: John Preston Date: Fri, 10 Apr 2026 11:00:33 +0700 Subject: [PATCH 015/184] Fix messages appearance in botforums. --- Telegram/SourceFiles/history/history_item.cpp | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/Telegram/SourceFiles/history/history_item.cpp b/Telegram/SourceFiles/history/history_item.cpp index 2b56d44992..1a36f2bd1c 100644 --- a/Telegram/SourceFiles/history/history_item.cpp +++ b/Telegram/SourceFiles/history/history_item.cpp @@ -2336,6 +2336,9 @@ void HistoryItem::applySentMessage(const MTPDmessage &data) { : PeerId(); if (!replyToPeer || replyToPeer == history()->peer->id) { if (const auto replyToId = data.vreply_to_msg_id()) { + if (!isService() && !Has()) { + AddComponents(HistoryMessageReply::Bit()); + } setReplyFields( replyToId->v, data.vreply_to_top_id().value_or(replyToId->v), @@ -2671,7 +2674,8 @@ void HistoryItem::addToMessagesIndex() { } void HistoryItem::incrementReplyToTopCounter() { - if (isRegular() && _history->peer->isMegagroup()) { + if (isRegular() + && (_history->peer->isMegagroup() || _history->peer->forum())) { _history->session().changes().messageUpdated( this, Data::MessageUpdate::Flag::ReplyToTopAdded); @@ -2743,9 +2747,10 @@ void HistoryItem::setRealId(MsgId newId) { _history->owner().requestItemResize(this); _history->owner().requestItemRepaint(this); - if (Has()) { - incrementReplyToTopCounter(); - } + incrementReplyToTopCounter(); + _history->session().changes().messageUpdated( + this, + Data::MessageUpdate::Flag::NewMaybeAdded); if (out() && starsPaid()) { _history->session().credits().load(true); From 8f2fbff93e46c4369c931c9939b8ada4f9d19fc8 Mon Sep 17 00:00:00 2001 From: John Preston Date: Fri, 10 Apr 2026 11:02:58 +0700 Subject: [PATCH 016/184] Fix streamed drafts updates. --- .../SourceFiles/history/history_streamed_drafts.cpp | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/Telegram/SourceFiles/history/history_streamed_drafts.cpp b/Telegram/SourceFiles/history/history_streamed_drafts.cpp index ef67083543..8a3ba345ab 100644 --- a/Telegram/SourceFiles/history/history_streamed_drafts.cpp +++ b/Telegram/SourceFiles/history/history_streamed_drafts.cpp @@ -10,6 +10,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "api/api_text_entities.h" #include "data/data_forum_topic.h" #include "data/data_peer_id.h" +#include "data/data_saved_sublist.h" #include "data/data_session.h" #include "history/history.h" #include "history/history_item.h" @@ -91,7 +92,9 @@ bool HistoryStreamedDrafts::update( if (i == end(_drafts)) { return false; } - i->second.message->setText(text); + const auto item = i->second.message; + item->setText(text); + item->invalidateChatListEntry(); i->second.updated = crl::now(); return true; } @@ -177,7 +180,13 @@ HistoryItem *HistoryStreamedDrafts::adoptIncoming( item->markBeingSentForAdoption(); item->setRealId(data.vid().v); - item->applySentMessage(data); + if (const auto topic = item->topic()) { + topic->applyMaybeLast(item); + } + if (const auto sublist = item->savedSublist()) { + sublist->applyMaybeLast(item); + } + _history->owner().updateExistingMessage(data); if (_drafts.empty()) { scheduleDestroy(); From 2810b186bb95fb61210175093bb4b9feb28a63d6 Mon Sep 17 00:00:00 2001 From: John Preston Date: Fri, 10 Apr 2026 14:16:50 +0700 Subject: [PATCH 017/184] Fix possible crash in forum new window open. --- .../history/view/history_view_subsection_tabs.cpp | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/Telegram/SourceFiles/history/view/history_view_subsection_tabs.cpp b/Telegram/SourceFiles/history/view/history_view_subsection_tabs.cpp index 5a46b0f373..8a9b9dcd45 100644 --- a/Telegram/SourceFiles/history/view/history_view_subsection_tabs.cpp +++ b/Telegram/SourceFiles/history/view/history_view_subsection_tabs.cpp @@ -261,12 +261,16 @@ void SubsectionTabs::setupSlider( if (_reordering) { return; } + const auto guard = base::make_weak(slider); const auto newWindow = base::IsCtrlPressed(); if (active >= 0 && active < _slice.size()) { const auto thread = _slice[active].thread; if (newWindow) { _controller->showInNewWindow(Window::SeparateId(thread)); - _refreshed.fire({}); // This should activate current section. + if (guard) { + // This should activate current section. + _refreshed.fire({}); + } } else { auto params = Window::SectionShow(); params.way = Window::SectionShow::Way::ClearStack; @@ -274,7 +278,9 @@ void SubsectionTabs::setupSlider( _controller->showThread(thread, ShowAtUnreadMsgId, params); } } - _reorder->finishReordering(); + if (guard) { + _reorder->finishReordering(); + } }, slider->lifetime()); _reorder->updates( From 4646cc7f21ff1faa112aee0843e3423312e7fb9c Mon Sep 17 00:00:00 2001 From: John Preston Date: Fri, 10 Apr 2026 14:17:04 +0700 Subject: [PATCH 018/184] Correct text appear continuation. --- Telegram/SourceFiles/data/data_session.cpp | 5 ++ Telegram/SourceFiles/data/data_types.h | 1 + Telegram/SourceFiles/history/history_item.cpp | 9 ++- Telegram/SourceFiles/history/history_item.h | 7 ++- .../history/history_streamed_drafts.cpp | 18 ++++-- .../history/view/history_view_element.cpp | 18 ++++++ .../history/view/history_view_element.h | 1 + .../history/view/history_view_message.cpp | 60 +++++++++++-------- 8 files changed, 84 insertions(+), 35 deletions(-) diff --git a/Telegram/SourceFiles/data/data_session.cpp b/Telegram/SourceFiles/data/data_session.cpp index 8667b03fb0..2163ec9554 100644 --- a/Telegram/SourceFiles/data/data_session.cpp +++ b/Telegram/SourceFiles/data/data_session.cpp @@ -2095,6 +2095,11 @@ void Session::requestItemTextRefresh(not_null item) { view->itemTextUpdated(); }); requestItemResize(item); + if (item->textAppearing()) { + enumerateItemViews(item, [&](not_null view) { + view->skipInactiveTextAppearing(); + }); + } }; if (const auto group = groups().find(item)) { call(group->items.front()); diff --git a/Telegram/SourceFiles/data/data_types.h b/Telegram/SourceFiles/data/data_types.h index 2a1dc3b616..54381b6330 100644 --- a/Telegram/SourceFiles/data/data_types.h +++ b/Telegram/SourceFiles/data/data_types.h @@ -366,6 +366,7 @@ enum class MessageFlag : uint64 { HasUnreadPollVote = (1ULL << 59), TextAppearing = (1ULL << 60), + TextAppearingStarted = (1ULL << 61), }; inline constexpr bool is_flag_type(MessageFlag) { return true; } using MessageFlags = base::flags; diff --git a/Telegram/SourceFiles/history/history_item.cpp b/Telegram/SourceFiles/history/history_item.cpp index 1a36f2bd1c..f84a4d1d3c 100644 --- a/Telegram/SourceFiles/history/history_item.cpp +++ b/Telegram/SourceFiles/history/history_item.cpp @@ -2715,16 +2715,19 @@ QString HistoryItem::notificationHeader() const { return QString(); } -void HistoryItem::markBeingSentForAdoption() { - _flags |= MessageFlag::BeingSent; +void HistoryItem::markTextAppearingStarted() { + _flags |= MessageFlag::TextAppearingStarted; } void HistoryItem::setRealId(MsgId newId) { - Expects(_flags & MessageFlag::BeingSent); + Expects(isSending() || textAppearing()); Expects(IsClientMsgId(id)); const auto oldId = std::exchange(id, newId); _flags &= ~(MessageFlag::BeingSent | MessageFlag::Local); + if (textAppearing()) { + markTextAppearingStarted(); + } if (isBusinessShortcut()) { _date = 0; } diff --git a/Telegram/SourceFiles/history/history_item.h b/Telegram/SourceFiles/history/history_item.h index 9d30f6d271..dd189bccef 100644 --- a/Telegram/SourceFiles/history/history_item.h +++ b/Telegram/SourceFiles/history/history_item.h @@ -343,9 +343,12 @@ public: [[nodiscard]] bool canBeSummarized() const { return _flags & MessageFlag::CanBeSummarized; } - [[nodiscard]] bool isTextAppearing() const { + [[nodiscard]] bool textAppearing() const { return _flags & MessageFlag::TextAppearing; } + [[nodiscard]] bool textAppearingStarted() const { + return _flags & MessageFlag::TextAppearingStarted; + } [[nodiscard]] bool hasRealFromId() const; [[nodiscard]] bool isPostHidingAuthor() const; [[nodiscard]] bool isPostShowingAuthor() const; @@ -440,7 +443,7 @@ public: bool isForumPost); void setPostAuthor(const QString &author); void setRealId(MsgId newId); - void markBeingSentForAdoption(); + void markTextAppearingStarted(); void incrementReplyToTopCounter(); void applyEffectWatchedOnUnreadKnown(); diff --git a/Telegram/SourceFiles/history/history_streamed_drafts.cpp b/Telegram/SourceFiles/history/history_streamed_drafts.cpp index 8a3ba345ab..4c0721ebe5 100644 --- a/Telegram/SourceFiles/history/history_streamed_drafts.cpp +++ b/Telegram/SourceFiles/history/history_streamed_drafts.cpp @@ -14,6 +14,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_session.h" #include "history/history.h" #include "history/history_item.h" +#include "main/main_session.h" namespace { @@ -57,9 +58,8 @@ void HistoryStreamedDrafts::apply( clearByRandomId(randomId); return; } - const auto text = Api::ParseTextWithEntities( - &_history->session(), - data.vtext()); + const auto session = &_history->session(); + const auto text = Api::ParseTextWithEntities(session, data.vtext()); if (update(randomId, text)) { return; } @@ -83,6 +83,17 @@ void HistoryStreamedDrafts::apply( if (!_checkTimer.isActive()) { _checkTimer.callOnce(kClearTimeout); } + crl::on_main(this, [=] { + crl::on_main(this, [=] { + // Thread topics create views for messages in double on_main: + // - First we postpone HistoryUpdate::Flag::ClientSideMessages. + // - Then we postpone RepliesList push of new messages list. + const auto i = _drafts.find(randomId); + if (i != end(_drafts)) { + i->second.message->markTextAppearingStarted(); + } + }); + }); } bool HistoryStreamedDrafts::update( @@ -178,7 +189,6 @@ HistoryItem *HistoryStreamedDrafts::adoptIncoming( const auto item = best->second.message.get(); _drafts.erase(best); - item->markBeingSentForAdoption(); item->setRealId(data.vid().v); if (const auto topic = item->topic()) { topic->applyMaybeLast(item); diff --git a/Telegram/SourceFiles/history/view/history_view_element.cpp b/Telegram/SourceFiles/history/view/history_view_element.cpp index e568895c74..6845dc7acd 100644 --- a/Telegram/SourceFiles/history/view/history_view_element.cpp +++ b/Telegram/SourceFiles/history/view/history_view_element.cpp @@ -67,6 +67,7 @@ namespace { // A new message from the same sender is attached to previous within 15 minutes. constexpr int kAttachMessageToPreviousSecondsDelta = 900; +constexpr auto kMaxShownLine = 1024 * 1024; Element *HoveredElement/* = nullptr*/; Element *PressedElement/* = nullptr*/; @@ -1596,6 +1597,23 @@ Ui::Text::OnlyCustomEmoji Element::onlyCustomEmoji() const { return _text.toOnlyCustomEmoji(); } +void Element::skipInactiveTextAppearing() { + if (pendingResize()) { + // This message isn't displayed right now, + // so we can skip text animation. + if (const auto appearing = Get()) { + appearing->widthAnimation.stop(); + appearing->heightAnimation.stop(); + appearing->shownLine = kMaxShownLine; + appearing->shownWidth + = appearing->shownHeight + = appearing->revealedLineWidth + = 0; + appearing->geometryValid = false; + } + } +} + const Ui::Text::String &Element::text() const { return _text; } diff --git a/Telegram/SourceFiles/history/view/history_view_element.h b/Telegram/SourceFiles/history/view/history_view_element.h index 08a1e3d55e..5c26a2e8b8 100644 --- a/Telegram/SourceFiles/history/view/history_view_element.h +++ b/Telegram/SourceFiles/history/view/history_view_element.h @@ -488,6 +488,7 @@ public: [[nodiscard]] HistoryItem *textItem() const; [[nodiscard]] Ui::Text::IsolatedEmoji isolatedEmoji() const; [[nodiscard]] Ui::Text::OnlyCustomEmoji onlyCustomEmoji() const; + void skipInactiveTextAppearing(); [[nodiscard]] OnlyEmojiAndSpaces isOnlyEmojiAndSpaces() const; diff --git a/Telegram/SourceFiles/history/view/history_view_message.cpp b/Telegram/SourceFiles/history/view/history_view_message.cpp index 994bac5fdd..6b69af99c9 100644 --- a/Telegram/SourceFiles/history/view/history_view_message.cpp +++ b/Telegram/SourceFiles/history/view/history_view_message.cpp @@ -216,12 +216,11 @@ Message::Message( } initPaidInformation(); - if (data->isTextAppearing()) { + if (data->textAppearing()) { AddComponents(TextAppearing::Bit()); - + const auto appearing = Get(); if (replacing) { if (const auto was = replacing->Get()) { - const auto appearing = Get(); *appearing = std::move(*was); appearing->widthAnimation.setCallback([=] { textAppearWidthCallback(); @@ -231,6 +230,11 @@ Message::Message( }); } } + if (data->textAppearingStarted() + && !appearing->widthAnimation.animating() + && !appearing->heightAnimation.animating()) { + skipInactiveTextAppearing(); + } } } @@ -5024,15 +5028,6 @@ int Message::resizeContentGetHeight(int newWidth) { } } - auto newHeight = minHeight(); - - if (const auto service = Get()) { - service->resizeToWidth(newWidth, delegate()->elementChatMode()); - } - - const auto botTop = item->isFakeAboutView() - ? Get() - : nullptr; const auto media = this->media(); const auto mediaDisplayed = media ? media->isDisplayed() : false; const auto bubble = drawBubble(); @@ -5076,12 +5071,34 @@ int Message::resizeContentGetHeight(int newWidth) { const auto textWidth = bubble ? bubbleTextWidth(contentWidth) : bottomInfoWidth; + + auto appearing = Get(); + if (appearing) { + if (appearing->textWidth != textWidth) { + appearing->geometryValid = false; + appearing->textWidth = textWidth; + } + // This may invalidate composer structure by removing TextAppearing. + if (!textAppearValidate(appearing)) { + appearing = nullptr; + } + } + const auto reactionsInBubble = _reactions && embedReactionsInBubble(); const auto bottomInfoHeight = _bottomInfo.resizeGetHeight( std::min( _bottomInfo.optimalSize().width(), bottomInfoWidth - 2 * st::msgDateDelta.x())); + auto newHeight = minHeight(); + + if (const auto service = Get()) { + service->resizeToWidth(newWidth, delegate()->elementChatMode()); + } + + const auto botTop = item->isFakeAboutView() + ? Get() + : nullptr; if (bubble) { auto reply = Get(); auto via = item->Get(); @@ -5095,17 +5112,6 @@ int Message::resizeContentGetHeight(int newWidth) { if (reactionsInBubble) { _reactions->resizeGetHeight(textWidth); } - - auto appearing = Get(); - if (appearing) { - if (appearing->textWidth != textWidth) { - appearing->geometryValid = false; - appearing->textWidth = textWidth; - } - if (!textAppearValidate(appearing)) { - appearing = nullptr; - } - } if (contentWidth == maxWidth() && !appearing) { if (mediaDisplayed) { if (check) { @@ -5279,10 +5285,12 @@ bool Message::textAppearCheckLine(not_null appearing) { if (data()->isRegular()) { RemoveComponents(TextAppearing::Bit()); return false; - } else if (recount) { + } else if (recount && lines) { appearing->shownLine = lines - 1; - appearing->revealedLineWidth = line ? line->width : 0; - appearing->shownHeight = line ? line->bottom : 0; + const auto &line = appearing->lines.back(); + appearing->revealedLineWidth = line.width; + appearing->shownWidth = appearing->textWidth; + appearing->shownHeight = line.bottom; appearing->widthAnimation.stop(); appearing->heightAnimation.stop(); } From 82650f54205f9d8d72466d4f5d80928b69264668 Mon Sep 17 00:00:00 2001 From: John Preston Date: Fri, 10 Apr 2026 15:32:33 +0700 Subject: [PATCH 019/184] Fix build with MSVC. --- Telegram/SourceFiles/boxes/share_box.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Telegram/SourceFiles/boxes/share_box.cpp b/Telegram/SourceFiles/boxes/share_box.cpp index 96130dc32d..1cbf028752 100644 --- a/Telegram/SourceFiles/boxes/share_box.cpp +++ b/Telegram/SourceFiles/boxes/share_box.cpp @@ -2026,7 +2026,7 @@ void FastShareMessageToSelf( std::shared_ptr show, not_null item) { const auto self = show->session().user(); - const auto &owner = self->owner(); + auto &owner = self->owner(); const auto items = owner.idsToItems(owner.itemOrItsGroup(item)); const auto donePhraseArgs = ChatHelpers::ForwardedMessagePhraseArgs{ .toCount = 1, From b4b34461356a6c198a6c851dcf830793e75e4611 Mon Sep 17 00:00:00 2001 From: John Preston Date: Fri, 10 Apr 2026 15:33:01 +0700 Subject: [PATCH 020/184] Add loading draft streaming animation. --- .../chat_helpers/stickers_lottie.cpp | 11 +++++++--- .../chat_helpers/stickers_lottie.h | 3 ++- .../history/history_streamed_drafts.cpp | 19 ++++++++++++++++-- .../history/history_streamed_drafts.h | 4 ++++ .../history/view/history_view_message.cpp | 20 +++++++++++++++---- .../history/view/history_view_message.h | 1 + .../view/history_view_translate_bar.cpp | 4 ++-- .../bot/starref/info_bot_starref_common.cpp | 4 ++-- .../inline_bots/bot_attach_web_view.cpp | 4 ++-- 9 files changed, 54 insertions(+), 16 deletions(-) diff --git a/Telegram/SourceFiles/chat_helpers/stickers_lottie.cpp b/Telegram/SourceFiles/chat_helpers/stickers_lottie.cpp index e2b9cd80e7..9b129c3ca9 100644 --- a/Telegram/SourceFiles/chat_helpers/stickers_lottie.cpp +++ b/Telegram/SourceFiles/chat_helpers/stickers_lottie.cpp @@ -349,7 +349,8 @@ not_null GenerateLocalSticker( not_null GenerateLocalTgsSticker( not_null session, - const QString &name) { + const QString &name, + bool useTextColor) { const auto cache = [&] { struct Session { base::weak_ptr session; @@ -370,7 +371,8 @@ not_null GenerateLocalTgsSticker( return &Map.back().cache; }(); - const auto i = cache->find(name); + const auto key = useTextColor ? (name + u"/:/1"_q) : name; + const auto i = cache->find(key); if (i != end(*cache)) { return i->second; } @@ -378,8 +380,11 @@ not_null GenerateLocalTgsSticker( const auto result = GenerateLocalSticker( session, u":/animations/"_q + name + u".tgs"_q); + if (useTextColor) { + result->overrideEmojiUsesTextColor(true); + } - cache->emplace(name, result); + cache->emplace(key, result); Ensures(result->sticker()->isLottie()); return result; diff --git a/Telegram/SourceFiles/chat_helpers/stickers_lottie.h b/Telegram/SourceFiles/chat_helpers/stickers_lottie.h index 4a9b3c39ba..8b67d3ebb1 100644 --- a/Telegram/SourceFiles/chat_helpers/stickers_lottie.h +++ b/Telegram/SourceFiles/chat_helpers/stickers_lottie.h @@ -137,6 +137,7 @@ bool PaintStickerThumbnailPath( [[nodiscard]] not_null GenerateLocalTgsSticker( not_null session, - const QString &name); + const QString &name, + bool useTextColor = false); } // namespace ChatHelpers diff --git a/Telegram/SourceFiles/history/history_streamed_drafts.cpp b/Telegram/SourceFiles/history/history_streamed_drafts.cpp index 4c0721ebe5..91e46265fc 100644 --- a/Telegram/SourceFiles/history/history_streamed_drafts.cpp +++ b/Telegram/SourceFiles/history/history_streamed_drafts.cpp @@ -8,6 +8,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "history/history_streamed_drafts.h" #include "api/api_text_entities.h" +#include "chat_helpers/stickers_lottie.h" +#include "data/stickers/data_custom_emoji.h" #include "data/data_forum_topic.h" #include "data/data_peer_id.h" #include "data/data_saved_sublist.h" @@ -42,6 +44,17 @@ HistoryStreamedDrafts::~HistoryStreamedDrafts() { } } +TextWithEntities HistoryStreamedDrafts::loadingEmoji() { + if (_loadingEmoji.empty()) { + _loadingEmoji = Data::SingleCustomEmoji( + ChatHelpers::GenerateLocalTgsSticker( + &_history->session(), + u"transcribe_loading"_q, + true)); + } + return _loadingEmoji; +} + void HistoryStreamedDrafts::apply( MsgId rootId, PeerId fromId, @@ -58,8 +71,10 @@ void HistoryStreamedDrafts::apply( clearByRandomId(randomId); return; } - const auto session = &_history->session(); - const auto text = Api::ParseTextWithEntities(session, data.vtext()); + const auto text = Api::ParseTextWithEntities( + &_history->session(), + data.vtext() + ).append(loadingEmoji()); if (update(randomId, text)) { return; } diff --git a/Telegram/SourceFiles/history/history_streamed_drafts.h b/Telegram/SourceFiles/history/history_streamed_drafts.h index c46f6d102b..2d8a22b99b 100644 --- a/Telegram/SourceFiles/history/history_streamed_drafts.h +++ b/Telegram/SourceFiles/history/history_streamed_drafts.h @@ -44,6 +44,8 @@ private: void check(); void scheduleDestroy(); + [[nodiscard]] TextWithEntities loadingEmoji(); + const not_null _history; base::flat_map _drafts; @@ -51,4 +53,6 @@ private: rpl::event_stream<> _destroyRequests; + TextWithEntities _loadingEmoji; + }; diff --git a/Telegram/SourceFiles/history/view/history_view_message.cpp b/Telegram/SourceFiles/history/view/history_view_message.cpp index 6b69af99c9..c1b79b138e 100644 --- a/Telegram/SourceFiles/history/view/history_view_message.cpp +++ b/Telegram/SourceFiles/history/view/history_view_message.cpp @@ -68,7 +68,9 @@ constexpr auto kPlayStatusLimit = 2; constexpr auto kMaxNiceToReadLines = 6; const auto kPsaTooltipPrefix = "cloud_lng_tooltip_psa_"; constexpr auto kFullLineAppearDuration = crl::time(300); +constexpr auto kFullLineAppearFinalDuration = crl::time(120); constexpr auto kLineHeightAppearDuration = crl::time(100); +constexpr auto kLineHeightAppearFinalDuration = crl::time(60); struct SecondRightAction { std::unique_ptr ripple; @@ -898,6 +900,7 @@ QSize Message::performCountOptimalSize() { } if (const auto appearing = Get()) { appearing->geometryValid = false; + appearing->finalizing = item->isRegular(); } return QSize(maxWidth, minHeight); } @@ -5318,9 +5321,12 @@ bool Message::textAppearCheckLine(not_null appearing) { ? 1. : (widthTarget - width) / float64(widthTarget - widthStart); const auto left = (1. - progress) * appearing->widthDuration; + const auto duration = appearing->finalizing + ? kLineHeightAppearFinalDuration + : kLineHeightAppearDuration; if (appearing->heightAnimation.animating() || !appearing->widthAnimation.animating() - || left <= kLineHeightAppearDuration) { + || left <= duration) { textAppearStartHeightAnimation(appearing); } } @@ -5335,10 +5341,13 @@ void Message::textAppearStartWidthAnimation( const auto shown = appearing->shownLine; const auto lines = int(appearing->lines.size()); const auto lineWidth = appearing->lines[shown].width; + const auto lineDuration = appearing->finalizing + ? kFullLineAppearFinalDuration + : kFullLineAppearDuration; const auto duration = (shown + 1 == lines) - ? kFullLineAppearDuration + ? lineDuration : std::max( - kFullLineAppearDuration * lineWidth / st::msgMaxWidth, + lineDuration * lineWidth / st::msgMaxWidth, crl::time(10)); appearing->widthDuration = duration; const auto from @@ -5360,9 +5369,12 @@ void Message::textAppearStartHeightAnimation( const auto to = appearing->targetHeight = appearing->lines[appearing->shownLine].bottom; + const auto duration = appearing->finalizing + ? kLineHeightAppearFinalDuration + : kLineHeightAppearDuration; appearing->heightAnimation.start([=] { textAppearHeightCallback(); - }, from, to, kLineHeightAppearDuration, anim::easeOutCubic); + }, from, to, duration, anim::easeOutCubic); } void Message::textAppearWidthCallback() { diff --git a/Telegram/SourceFiles/history/view/history_view_message.h b/Telegram/SourceFiles/history/view/history_view_message.h index 644b9346f1..88ad462005 100644 --- a/Telegram/SourceFiles/history/view/history_view_message.h +++ b/Telegram/SourceFiles/history/view/history_view_message.h @@ -96,6 +96,7 @@ struct TextAppearing : RuntimeComponent { Ui::Animations::Simple widthAnimation; Ui::Animations::Simple heightAnimation; bool geometryValid = false; + bool finalizing = false; bool use = false; }; diff --git a/Telegram/SourceFiles/history/view/history_view_translate_bar.cpp b/Telegram/SourceFiles/history/view/history_view_translate_bar.cpp index 349065bdc6..a6f3c60bf8 100644 --- a/Telegram/SourceFiles/history/view/history_view_translate_bar.cpp +++ b/Telegram/SourceFiles/history/view/history_view_translate_bar.cpp @@ -489,8 +489,8 @@ void TranslateBar::showMenu(base::unique_qptr menu) { const auto cocoon = ChatHelpers::GenerateLocalTgsSticker( &_history->session(), - u"cocoon"_q); - cocoon->overrideEmojiUsesTextColor(true); + u"cocoon"_q, + true); auto item = base::make_unique_q( _menu->menu(), st::defaultMenu, diff --git a/Telegram/SourceFiles/info/bot/starref/info_bot_starref_common.cpp b/Telegram/SourceFiles/info/bot/starref/info_bot_starref_common.cpp index e871c9b449..415e6d95f2 100644 --- a/Telegram/SourceFiles/info/bot/starref/info_bot_starref_common.cpp +++ b/Telegram/SourceFiles/info/bot/starref/info_bot_starref_common.cpp @@ -93,9 +93,9 @@ void ConnectStarRef( const auto state = raw->lifetime().make_state(State{ .icon = ChatHelpers::GenerateLocalTgsSticker( session, - u"starref_link"_q), + u"starref_link"_q, + true), }); - state->icon->overrideEmojiUsesTextColor(true); state->media = state->icon->createMediaView(); state->player = std::make_unique( ChatHelpers::LottiePlayerFromDocument( diff --git a/Telegram/SourceFiles/inline_bots/bot_attach_web_view.cpp b/Telegram/SourceFiles/inline_bots/bot_attach_web_view.cpp index 9df6493781..4b99329104 100644 --- a/Telegram/SourceFiles/inline_bots/bot_attach_web_view.cpp +++ b/Telegram/SourceFiles/inline_bots/bot_attach_web_view.cpp @@ -500,8 +500,8 @@ void ConfirmEmojiStatusAccessBox( const auto statusIcon = ChatHelpers::GenerateLocalTgsSticker( &bot->session(), - u"hello_status"_q); - statusIcon->overrideEmojiUsesTextColor(true); + u"hello_status"_q, + true); auto ownedSet = MakeEmojiSetStatusPreview( box, From 4a0b47d28692977135664f01b5b8400a4864f710 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Fri, 10 Apr 2026 09:40:33 +0300 Subject: [PATCH 021/184] Added inline update button to unsupported message fallback. --- Telegram/SourceFiles/history/history_item.cpp | 10 ++++++++++ .../SourceFiles/history/history_item_helpers.cpp | 14 ++++++++++++++ .../SourceFiles/history/history_item_helpers.h | 3 +++ 3 files changed, 27 insertions(+) diff --git a/Telegram/SourceFiles/history/history_item.cpp b/Telegram/SourceFiles/history/history_item.cpp index f84a4d1d3c..88c7bba933 100644 --- a/Telegram/SourceFiles/history/history_item.cpp +++ b/Telegram/SourceFiles/history/history_item.cpp @@ -437,6 +437,12 @@ HistoryItem::HistoryItem( _flags |= MessageFlag::Legacy; createComponents(data); setText(UnsupportedMessageText()); + if (!Has()) { + AddComponents(HistoryMessageReplyMarkup::Bit()); + } + _flags |= MessageFlag::HasReplyMarkup; + Get()->updateData( + UnsupportedMessageMarkup()); } else if (checked == MediaCheckResult::Empty) { AddComponents(HistoryServiceData::Bit()); setServiceText({ @@ -2145,6 +2151,9 @@ void HistoryItem::applyEdition(HistoryMessageEdition &&edition) { setText(std::move(updatedText)); addToSharedMediaIndex(); } + if (mediaCheck == MediaCheckResult::Unsupported) { + setReplyMarkup(UnsupportedMessageMarkup()); + } if (!edition.useSameReplies) { if (!edition.replies.isNull) { if (checkRepliesPts(edition.replies)) { @@ -2400,6 +2409,7 @@ void HistoryItem::updateSentContent( _flags &= ~MessageFlag::HasPostAuthor; _flags |= MessageFlag::Legacy; setText(UnsupportedMessageText()); + setReplyMarkup(UnsupportedMessageMarkup()); } else { if (_flags & MessageFlag::Legacy) { _flags &= ~MessageFlag::Legacy; diff --git a/Telegram/SourceFiles/history/history_item_helpers.cpp b/Telegram/SourceFiles/history/history_item_helpers.cpp index 4da07e71f4..135b607ae7 100644 --- a/Telegram/SourceFiles/history/history_item_helpers.cpp +++ b/Telegram/SourceFiles/history/history_item_helpers.cpp @@ -1310,6 +1310,20 @@ void CheckPollVoteNotificationSchedule( return result; } +HistoryMessageMarkupData UnsupportedMessageMarkup() { + using Button = HistoryMessageMarkupButton; + auto markup = HistoryMessageMarkupData(); + markup.flags = ReplyMarkupFlag::Inline; + auto row = std::vector