diff --git a/Telegram/Resources/uwp/AppX/AppxManifest.xml b/Telegram/Resources/uwp/AppX/AppxManifest.xml index b39491fa45..f602bb2280 100644 --- a/Telegram/Resources/uwp/AppX/AppxManifest.xml +++ b/Telegram/Resources/uwp/AppX/AppxManifest.xml @@ -10,7 +10,7 @@ + Version="6.7.5.0" /> Telegram Desktop Telegram Messenger LLP diff --git a/Telegram/Resources/winrc/Telegram.rc b/Telegram/Resources/winrc/Telegram.rc index daf7047b67..fd9ae9da39 100644 --- a/Telegram/Resources/winrc/Telegram.rc +++ b/Telegram/Resources/winrc/Telegram.rc @@ -44,8 +44,8 @@ IDI_ICON1 ICON "..\\art\\icon256.ico" // VS_VERSION_INFO VERSIONINFO - FILEVERSION 6,7,4,0 - PRODUCTVERSION 6,7,4,0 + FILEVERSION 6,7,5,0 + PRODUCTVERSION 6,7,5,0 FILEFLAGSMASK 0x3fL #ifdef _DEBUG FILEFLAGS 0x1L @@ -62,10 +62,10 @@ BEGIN BEGIN VALUE "CompanyName", "Radolyn Labs" VALUE "FileDescription", "AyuGram Desktop" - VALUE "FileVersion", "6.7.4.0" + VALUE "FileVersion", "6.7.5.0" VALUE "LegalCopyright", "Copyright (C) 2014-2026" VALUE "ProductName", "AyuGram Desktop" - VALUE "ProductVersion", "6.7.4.0" + VALUE "ProductVersion", "6.7.5.0" END END BLOCK "VarFileInfo" diff --git a/Telegram/Resources/winrc/Updater.rc b/Telegram/Resources/winrc/Updater.rc index 9df16f35e5..c7ddd59011 100644 --- a/Telegram/Resources/winrc/Updater.rc +++ b/Telegram/Resources/winrc/Updater.rc @@ -35,8 +35,8 @@ LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US // VS_VERSION_INFO VERSIONINFO - FILEVERSION 6,7,4,0 - PRODUCTVERSION 6,7,4,0 + FILEVERSION 6,7,5,0 + PRODUCTVERSION 6,7,5,0 FILEFLAGSMASK 0x3fL #ifdef _DEBUG FILEFLAGS 0x1L @@ -53,10 +53,10 @@ BEGIN BEGIN VALUE "CompanyName", "Radolyn Labs" VALUE "FileDescription", "AyuGram Desktop Updater" - VALUE "FileVersion", "6.7.4.0" + VALUE "FileVersion", "6.7.5.0" VALUE "LegalCopyright", "Copyright (C) 2014-2026" VALUE "ProductName", "AyuGram Desktop" - VALUE "ProductVersion", "6.7.4.0" + VALUE "ProductVersion", "6.7.5.0" END END BLOCK "VarFileInfo" diff --git a/Telegram/SourceFiles/core/version.h b/Telegram/SourceFiles/core/version.h index 53b3e716bb..ba20f42354 100644 --- a/Telegram/SourceFiles/core/version.h +++ b/Telegram/SourceFiles/core/version.h @@ -22,7 +22,7 @@ constexpr auto AppId = "{53F49750-6209-4FBF-9CA8-7A333C87D666}"_cs; constexpr auto AppNameOld = "AyuGram for Windows"_cs; constexpr auto AppName = "AyuGram Desktop"_cs; constexpr auto AppFile = "AyuGram"_cs; -constexpr auto AppVersion = 6007004; -constexpr auto AppVersionStr = "6.7.4"; +constexpr auto AppVersion = 6007005; +constexpr auto AppVersionStr = "6.7.5"; constexpr auto AppBetaVersion = false; constexpr auto AppAlphaVersion = TDESKTOP_ALPHA_VERSION; diff --git a/Telegram/SourceFiles/data/data_session.cpp b/Telegram/SourceFiles/data/data_session.cpp index 7e0bc748b3..57fe22d4f2 100644 --- a/Telegram/SourceFiles/data/data_session.cpp +++ b/Telegram/SourceFiles/data/data_session.cpp @@ -2065,6 +2065,16 @@ rpl::producer> Session::viewResizeRequest() const { return _viewResizeRequest.events(); } +void Session::notifyViewHeightAdjusted( + not_null view, + int delta) { + _viewHeightAdjusted.fire({ view, delta }); +} + +rpl::producer Session::viewHeightAdjusted() const { + return _viewHeightAdjusted.events(); +} + void Session::requestItemShowHighlight(not_null item) { _itemShowHighlightRequest.fire_copy(item); } @@ -2107,6 +2117,14 @@ 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 874d126ed0..1e54a52ab4 100644 --- a/Telegram/SourceFiles/data/data_session.h +++ b/Telegram/SourceFiles/data/data_session.h @@ -395,11 +395,18 @@ public: [[nodiscard]] rpl::producer> itemResizeRequest() const; void requestViewResize(not_null view); [[nodiscard]] rpl::producer> viewResizeRequest() const; + struct ViewHeightAdjusted { + not_null view; + int delta = 0; + }; + void notifyViewHeightAdjusted(not_null view, int delta); + [[nodiscard]] rpl::producer viewHeightAdjusted() const; void requestItemShowHighlight(not_null item); [[nodiscard]] rpl::producer> itemShowHighlightRequest() const; 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; @@ -1137,6 +1144,7 @@ private: rpl::event_stream _viewRepaintRequest; rpl::event_stream> _itemResizeRequest; rpl::event_stream> _viewResizeRequest; + rpl::event_stream _viewHeightAdjusted; rpl::event_stream> _itemShowHighlightRequest; rpl::event_stream> _itemViewRefreshRequest; rpl::event_stream> _itemTextRefreshRequest; diff --git a/Telegram/SourceFiles/data/data_types.h b/Telegram/SourceFiles/data/data_types.h index 68d01f4ce4..fe3a3c9f40 100644 --- a/Telegram/SourceFiles/data/data_types.h +++ b/Telegram/SourceFiles/data/data_types.h @@ -365,6 +365,8 @@ enum class MessageFlag : uint64 { CanBeSummarized = (1ULL << 58), HasUnreadPollVote = (1ULL << 59), + TextAppearing = (1ULL << 60), + AyuNoForwards = (1ULL << 63), }; inline constexpr bool is_flag_type(MessageFlag) { return true; } diff --git a/Telegram/SourceFiles/history/history.cpp b/Telegram/SourceFiles/history/history.cpp index c6a730c5c9..bf9abbea69 100644 --- a/Telegram/SourceFiles/history/history.cpp +++ b/Telegram/SourceFiles/history/history.cpp @@ -1494,6 +1494,38 @@ void History::applyServiceChanges( }); } +void History::viewHeightAdjusted(not_null view, int delta) { + if (view->data()->mainView() == view) { + mainViewHeightAdjusted(view, delta); + } + owner().notifyViewHeightAdjusted(view, delta); +} + +void History::mainViewHeightAdjusted(not_null view, int delta) { + const auto viewInBlock = view->indexInBlock(); + if (viewInBlock < 0 || _width <= 0) { + return; + } + const auto block = view->block(); + for (auto i = viewInBlock + 1, count = int(block->messages.size()) + ; i != count + ; ++i) { + const auto view = block->messages[i].get(); + view->setY(view->y() + delta); + } + block->resizeGetHeight( + _width, + HistoryBlock::ResizeRequest::ResizePending); + const auto blockInHistory = block->indexInHistory(); + for (auto i = blockInHistory + 1, count = int(blocks.size()) + ; i != count + ; ++i) { + const auto block = blocks[i].get(); + block->setY(block->y() + delta); + } + _height += delta; +} + void History::mainViewRemoved( not_null block, not_null view) { diff --git a/Telegram/SourceFiles/history/history.h b/Telegram/SourceFiles/history/history.h index aec389e3f8..e8848ecae3 100644 --- a/Telegram/SourceFiles/history/history.h +++ b/Telegram/SourceFiles/history/history.h @@ -420,6 +420,7 @@ public: PeerId dataPeerId, const MTPmessages_Messages &data); + void viewHeightAdjusted(not_null view, int delta); void forgetScrollState() { scrollTopItem = nullptr; } @@ -558,6 +559,7 @@ private: void mainViewRemoved( not_null block, not_null view); + void mainViewHeightAdjusted(not_null view, int delta); TimeId adjustedChatListTimeId() const override; void changedChatListPinHook() override; diff --git a/Telegram/SourceFiles/history/history_item.cpp b/Telegram/SourceFiles/history/history_item.cpp index 11736577e6..afc3df8135 100644 --- a/Telegram/SourceFiles/history/history_item.cpp +++ b/Telegram/SourceFiles/history/history_item.cpp @@ -4113,7 +4113,8 @@ FullReplyTo HistoryItem::replyTo() const { return result; } -void HistoryItem::setText(const TextWithEntities &textWithEntities) { +void HistoryItem::detectTextLinks( + const TextWithEntities &textWithEntities) { auto text = textWithEntities; const auto &settings = AyuSettings::getInstance(); if (settings.filterZalgo()) { @@ -4133,6 +4134,10 @@ void HistoryItem::setText(const TextWithEntities &textWithEntities) { break; } } +} + +void HistoryItem::setText(const TextWithEntities &textWithEntities) { + detectTextLinks(textWithEntities); setTextValue((_media && _media->consumeMessageText(text)) ? TextWithEntities() : std::move(text)); @@ -4151,6 +4156,13 @@ 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 7af88e051f..559df56263 100644 --- a/Telegram/SourceFiles/history/history_item.h +++ b/Telegram/SourceFiles/history/history_item.h @@ -345,6 +345,9 @@ public: [[nodiscard]] bool canBeSummarized() const { return _flags & MessageFlag::CanBeSummarized; } + [[nodiscard]] bool isTextAppearing() const { + return _flags & MessageFlag::TextAppearing; + } [[nodiscard]] bool hasRealFromId() const; [[nodiscard]] bool isPostHidingAuthor() const; [[nodiscard]] bool isPostShowingAuthor() const; @@ -528,6 +531,7 @@ public: } [[nodiscard]] bool computeDropForwardedInfo() const; void setText(const TextWithEntities &textWithEntities); + void setTextStreaming(TextWithEntities text); [[nodiscard]] MsgId replyToId() const; [[nodiscard]] FullMsgId replyToFullId() const; @@ -642,6 +646,7 @@ private: [[nodiscard]] bool generateLocalEntitiesByReply() const; [[nodiscard]] TextWithEntities withLocalEntities( const TextWithEntities &textWithEntities) const; + void detectTextLinks(const TextWithEntities &textWithEntities); void setTextValue(TextWithEntities text, bool force = false); [[nodiscard]] bool isTooOldForEdit(TimeId now) const; [[nodiscard]] bool isLegacyMessage() const { diff --git a/Telegram/SourceFiles/history/history_streamed_drafts.cpp b/Telegram/SourceFiles/history/history_streamed_drafts.cpp index 7459825658..5b84c4e516 100644 --- a/Telegram/SourceFiles/history/history_streamed_drafts.cpp +++ b/Telegram/SourceFiles/history/history_streamed_drafts.cpp @@ -56,7 +56,9 @@ void HistoryStreamedDrafts::apply( _drafts.emplace(rootId, Draft{ .message = _history->addNewLocalMessage({ .id = _history->owner().nextLocalMessageId(), - .flags = MessageFlag::Local | MessageFlag::HasReplyInfo, + .flags = (MessageFlag::Local + | MessageFlag::HasReplyInfo + | MessageFlag::TextAppearing), .from = fromId, .replyTo = { .messageId = replyToId, @@ -80,7 +82,7 @@ bool HistoryStreamedDrafts::update( if (i == end(_drafts) || i->second.randomId != randomId) { return false; } - i->second.message->setText(text); + i->second.message->setTextStreaming(text); i->second.updated = crl::now(); return true; } @@ -94,6 +96,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); diff --git a/Telegram/SourceFiles/history/history_streamed_drafts.h b/Telegram/SourceFiles/history/history_streamed_drafts.h index 657a0177ed..d521ccc80c 100644 --- a/Telegram/SourceFiles/history/history_streamed_drafts.h +++ b/Telegram/SourceFiles/history/history_streamed_drafts.h @@ -25,6 +25,7 @@ public: TimeId when, const MTPDsendMessageTextDraftAction &data); + [[nodiscard]] bool hasFor(not_null item) const; void applyItemAdded(not_null item); void applyItemRemoved(not_null item); diff --git a/Telegram/SourceFiles/history/history_widget.cpp b/Telegram/SourceFiles/history/history_widget.cpp index e4ecaec11c..798a02db17 100644 --- a/Telegram/SourceFiles/history/history_widget.cpp +++ b/Telegram/SourceFiles/history/history_widget.cpp @@ -101,6 +101,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "history/history_drag_area.h" #include "history/history_inner_widget.h" #include "history/history_item_components.h" +#include "history/history_streamed_drafts.h" #include "history/history_unread_things.h" #include "history/admin_log/history_admin_log_section.h" #include "history/view/controls/history_view_characters_limit.h" @@ -665,6 +666,15 @@ HistoryWidget::HistoryWidget( updateHistoryGeometry(); } }, lifetime()); + session().data().viewHeightAdjusted( + ) | rpl::on_next([=](Data::Session::ViewHeightAdjusted data) { + const auto item = data.view->data(); + const auto history = item->history(); + if (item->mainView() == data.view + && (history == _history || history == _migrated)) { + updateHistoryGeometry(); + } + }, lifetime()); session().data().itemShowHighlightRequest( ) | rpl::on_next([=](not_null item) { @@ -4013,7 +4023,9 @@ void HistoryWidget::newItemAdded(not_null item) { } return; } - _itemRevealPending.emplace(item); + if (!item->history()->streamedDrafts().hasFor(item)) { + _itemRevealPending.emplace(item); + } } void HistoryWidget::maybeMarkReactionsRead(not_null item) { diff --git a/Telegram/SourceFiles/history/view/history_view_chat_preview.cpp b/Telegram/SourceFiles/history/view/history_view_chat_preview.cpp index fe2e362e63..64314bb749 100644 --- a/Telegram/SourceFiles/history/view/history_view_chat_preview.cpp +++ b/Telegram/SourceFiles/history/view/history_view_chat_preview.cpp @@ -99,7 +99,8 @@ private: void listMarkContentsRead( const base::flat_set> &items) override; MessagesBarData listMessagesBar( - const std::vector> &elements) override; + const std::vector> &elements, + bool markLastAsRead) override; void listContentRefreshed() override; void listUpdateDateLink( ClickHandlerPtr &link, @@ -648,7 +649,8 @@ void Item::listMarkContentsRead( } MessagesBarData Item::listMessagesBar( - const std::vector> &elements) { + const std::vector> &elements, + bool markLastAsRead) { if (elements.empty()) { return {}; } else if (!_replies && !_sublist && !_history->unreadCount()) { diff --git a/Telegram/SourceFiles/history/view/history_view_chat_section.cpp b/Telegram/SourceFiles/history/view/history_view_chat_section.cpp index cd1ad8a9d5..b2d7449e8c 100644 --- a/Telegram/SourceFiles/history/view/history_view_chat_section.cpp +++ b/Telegram/SourceFiles/history/view/history_view_chat_section.cpp @@ -3132,7 +3132,8 @@ void ChatWidget::listMarkContentsRead( } MessagesBarData ChatWidget::listMessagesBar( - const std::vector> &elements) { + const std::vector> &elements, + bool markLastAsRead) { if ((!_sublist && !_replies) || elements.empty()) { return {}; } @@ -3143,7 +3144,15 @@ MessagesBarData ChatWidget::listMessagesBar( for (auto i = 0, count = int(elements.size()); i != count; ++i) { const auto item = elements[i]->data(); if (item->isRegular() && item->id > till) { - if (item->out() || (_replies && !item->replyToId())) { + if (markLastAsRead + || item->out() + || (_replies && !item->replyToId())) { + if (markLastAsRead) { + if (item->isUnreadMention() && !item->isUnreadMedia()) { + session().api().markContentsRead(item); + } + item->markClientSideAsRead(); + } if (_replies) { _replies->readTill(item); } else { diff --git a/Telegram/SourceFiles/history/view/history_view_chat_section.h b/Telegram/SourceFiles/history/view/history_view_chat_section.h index 7b47e792b1..1abbd70664 100644 --- a/Telegram/SourceFiles/history/view/history_view_chat_section.h +++ b/Telegram/SourceFiles/history/view/history_view_chat_section.h @@ -163,7 +163,8 @@ public: void listMarkContentsRead( const base::flat_set> &items) override; MessagesBarData listMessagesBar( - const std::vector> &elements) override; + const std::vector> &elements, + bool markLastAsRead) override; void listContentRefreshed() override; void listUpdateDateLink( ClickHandlerPtr &link, diff --git a/Telegram/SourceFiles/history/view/history_view_element.cpp b/Telegram/SourceFiles/history/view/history_view_element.cpp index c3ef393533..5b1b4bdfb3 100644 --- a/Telegram/SourceFiles/history/view/history_view_element.cpp +++ b/Telegram/SourceFiles/history/view/history_view_element.cpp @@ -2672,6 +2672,12 @@ 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 94706f9938..556ee6405d 100644 --- a/Telegram/SourceFiles/history/view/history_view_element.h +++ b/Telegram/SourceFiles/history/view/history_view_element.h @@ -626,6 +626,7 @@ public: virtual void itemDataChanged(); void itemTextUpdated(); + void itemTextUpdatedStreaming(); void blockquoteExpandChanged(); virtual void unloadHeavyPart(); diff --git a/Telegram/SourceFiles/history/view/history_view_list_widget.cpp b/Telegram/SourceFiles/history/view/history_view_list_widget.cpp index a400bac4ee..1e3beb8551 100644 --- a/Telegram/SourceFiles/history/view/history_view_list_widget.cpp +++ b/Telegram/SourceFiles/history/view/history_view_list_widget.cpp @@ -15,6 +15,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "history/history_item_components.h" #include "history/history_item_helpers.h" #include "history/history_item_text.h" +#include "history/history_streamed_drafts.h" #include "history/view/media/history_view_media.h" #include "history/view/media/history_view_sticker.h" #include "history/view/reactions/history_view_reactions.h" @@ -467,6 +468,12 @@ ListWidget::ListWidget( resizeItem(view); } }, lifetime()); + _session->data().viewHeightAdjusted( + ) | rpl::on_next([this](Data::Session::ViewHeightAdjusted data) { + if (data.view->delegate() == this) { + viewHeightAdjusted(data.view); + } + }, lifetime()); _session->data().itemViewRefreshRequest( ) | rpl::on_next([this](auto item) { if (const auto view = viewForItem(item)) { @@ -645,6 +652,10 @@ void ListWidget::refreshRows(const Data::MessagesSlice &old) { saveScrollState(); + const auto scrolledTillEnd = _itemsKnownTillEnd + && (_visibleBottom == height()) + && (_visibleBottom > _visibleTop); + const auto addedToEndFrom = (old.skippedAfter == 0 && (_slice.skippedAfter == 0) && !old.ids.empty()) @@ -682,7 +693,10 @@ void ListWidget::refreshRows(const Data::MessagesSlice &old) { _translateTracker->addBunchFrom(_items); } for (auto e = end(_items), i = e - addedToEndCount; i != e; ++i) { - _itemRevealPending.emplace(*i); + const auto item = (*i)->data(); + if (!item->history()->streamedDrafts().hasFor(item)) { + _itemRevealPending.emplace(*i); + } } updateAroundPositionFromNearest(nearestIndex); @@ -703,7 +717,8 @@ void ListWidget::refreshRows(const Data::MessagesSlice &old) { } _viewsCapacity.clear(); - checkUnreadBarCreation(); + const auto markLastAsRead = (scrolledTillEnd && markingMessagesRead()); + checkUnreadBarCreation(markLastAsRead); restoreScrollState(); if (!_itemsRevealHeight) { mouseActionUpdate(QCursor::pos()); @@ -984,18 +999,21 @@ void ListWidget::computeScrollTo( scrollTo(wanted, position, scrollDelta, type); } -void ListWidget::checkUnreadBarCreation() { - if (!_bar.element) { - if (auto data = _delegate->listMessagesBar(_items); data.bar.element) { - _bar = std::move(data.bar); - _barText = std::move(data.text); - if (!_bar.hidden) { - _bar.element->createUnreadBar(_barText.value()); - const auto i = ranges::find(_items, not_null{ _bar.element }); - Assert(i != end(_items)); - refreshAttachmentsAtIndex(i - begin(_items)); - } - } +void ListWidget::checkUnreadBarCreation(bool markLastAsRead) { + if (_bar.element) { + return; + } + auto data = _delegate->listMessagesBar(_items, markLastAsRead); + if (!data.bar.element) { + return; + } + _bar = std::move(data.bar); + _barText = std::move(data.text); + if (!_bar.hidden) { + _bar.element->createUnreadBar(_barText.value()); + const auto i = ranges::find(_items, not_null{ _bar.element }); + Assert(i != end(_items)); + refreshAttachmentsAtIndex(i - begin(_items)); } } @@ -4360,6 +4378,30 @@ void ListWidget::refreshAttachmentsFromTill(int from, int till) { } } +void ListWidget::viewHeightAdjusted(not_null view) { + const auto i = ranges::find(_items, view); + if (i == end(_items)) { + return; + } + auto next = i + 1; + const auto was = (next != end(_items)) + ? (*next)->y() + : _itemsHeight; + const auto now = view->y() + view->height(); + const auto delta = now - was; + for (; next != end(_items); ++next) { + (*next)->setY((*next)->y() + delta); + } + _itemsHeight += delta; + _itemsTop = (_minHeight > _itemsHeight + st::historyPaddingBottom) + ? (_minHeight - _itemsHeight - st::historyPaddingBottom) + : 0; + resize(width(), _itemsTop + _itemsHeight + st::historyPaddingBottom); + restoreScrollPosition(); + updateVisibleTopItem(); + update(); +} + void ListWidget::refreshItem(not_null view) { const auto i = ranges::find(_items, view); const auto index = i - begin(_items); diff --git a/Telegram/SourceFiles/history/view/history_view_list_widget.h b/Telegram/SourceFiles/history/view/history_view_list_widget.h index effc5f36c0..a3dcdb83c3 100644 --- a/Telegram/SourceFiles/history/view/history_view_list_widget.h +++ b/Telegram/SourceFiles/history/view/history_view_list_widget.h @@ -126,7 +126,8 @@ public: virtual void listMarkContentsRead( const base::flat_set> &items) = 0; virtual MessagesBarData listMessagesBar( - const std::vector> &elements) = 0; + const std::vector> &elements, + bool markLastAsRead) = 0; virtual void listContentRefreshed() = 0; virtual void listUpdateDateLink( ClickHandlerPtr &link, @@ -596,6 +597,7 @@ private: void repaintItem(const Element *view, QRect rect); void resizeItem(not_null view); void refreshItem(not_null view); + void viewHeightAdjusted(not_null view); void showItemHighlight(not_null item); void itemRemoved(not_null item); QPoint mapPointToItem(QPoint point, const Element *view) const; @@ -716,7 +718,7 @@ private: TextSelection computeRenderSelection( not_null selected, not_null view) const; - void checkUnreadBarCreation(); + void checkUnreadBarCreation(bool markLastAsRead = false); void applyUpdatedScrollState(); void scrollToAnimationCallback(FullMsgId attachToId, int relativeTo); void startItemRevealAnimations(); diff --git a/Telegram/SourceFiles/history/view/history_view_message.cpp b/Telegram/SourceFiles/history/view/history_view_message.cpp index 2e14bfa91d..9ae8696964 100644 --- a/Telegram/SourceFiles/history/view/history_view_message.cpp +++ b/Telegram/SourceFiles/history/view/history_view_message.cpp @@ -74,6 +74,8 @@ constexpr auto kSummarizeThreshold = 512; constexpr auto kPlayStatusLimit = 12; constexpr auto kMaxNiceToReadLines = 6; const auto kPsaTooltipPrefix = "cloud_lng_tooltip_psa_"; +constexpr auto kFullLineAppearDuration = crl::time(300); +constexpr auto kLineHeightAppearDuration = crl::time(100); struct SecondRightAction { std::unique_ptr ripple; @@ -668,6 +670,11 @@ QSize Message::performCountOptimalSize() { } else { RemoveComponents(Factcheck::Bit()); } + if (item->isTextAppearing()) { + AddComponents(TextAppearing::Bit()); + } else { + RemoveComponents(TextAppearing::Bit()); + } refreshRightBadge(); const auto markup = item->inlineReplyMarkup(); @@ -901,6 +908,9 @@ QSize Message::performCountOptimalSize() { minHeight -= text().minHeight(); minHeight += textHeightFor(bubbleTextWidth(maxWidth)); } + if (const auto appearing = Get()) { + appearing->geometryValid = false; + } return QSize(maxWidth, minHeight); } @@ -2260,6 +2270,37 @@ void Message::paintText( .outPath = &ripplePath, }; + const auto appearing = Get(); + if (appearing + && (appearing->shownLines > 0) + && (appearing->shownLines <= int(appearing->lines.size()))) { + const auto lastLineIndex = appearing->shownLines - 1; + p.save(); + auto clipPath = QPainterPath(); + const auto prevBottom = (lastLineIndex > 0) + ? appearing->lines[lastLineIndex - 1].top + : 0; + if (prevBottom > 0) { + clipPath.addRect( + trect.x(), + trect.y(), + trect.width(), + prevBottom); + } + const auto lastLineHeight = appearing->lines[lastLineIndex].top + - prevBottom; + const auto &lastLine = appearing->lines[lastLineIndex]; + const auto clipLeft = lastLine.rtl + ? (trect.x() + trect.width() - appearing->revealedLineWidth) + : trect.x(); + clipPath.addRect( + clipLeft, + trect.y() + prevBottom, + appearing->revealedLineWidth, + lastLineHeight); + p.setClipPath(clipPath); + } + auto highlightRequest = context.computeHighlightCache(); text().draw(p, { .position = trect.topLeft(), @@ -2290,6 +2331,10 @@ void Message::paintText( } else if (needRippleMask) { _linkRipple = nullptr; } + if (appearing && appearing->shownLines > 0 + && appearing->shownLines <= int(appearing->lines.size())) { + p.restore(); + } } PointState Message::pointState(QPoint point) const { @@ -4958,6 +5003,19 @@ 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 (contentWidth < availableWidth && delegate()->elementChatMode() != ElementChatMode::Wide) { if (outbg) { @@ -5117,7 +5175,8 @@ int Message::resizeContentGetHeight(int newWidth) { _reactions->resizeGetHeight(textWidth); } - if (contentWidth == maxWidth()) { + const auto appearing = Get(); + if (contentWidth == maxWidth() && !appearing) { if (mediaDisplayed) { if (check) { newHeight += check->resizeGetHeight(contentWidth) + st::mediaInBubbleSkip; @@ -5141,7 +5200,31 @@ int Message::resizeContentGetHeight(int newWidth) { if (botTop) { newHeight += botTop->height; } - newHeight += textHeightFor(textWidth); + 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; + } } if (!mediaOnBottom && (!_viewButton || !reactionsInBubble)) { newHeight += st::msgPadding.bottom(); @@ -5255,6 +5338,119 @@ void Message::invalidateTextDependentCache() { _bubbleTextualWidthCache = 0; } +void Message::startTextAppearingWidthAnimation() { + const auto appearing = Get(); + if (!appearing || appearing->lines.empty()) { + return; + } + const auto lineIndex = appearing->shownLines - 1; + if (lineIndex < 0 + || lineIndex >= int(appearing->lines.size())) { + return; + } + 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)); + appearing->widthDuration = duration; + appearing->widthAnimation.start( + [=] { textAppearingTick(); }, + 0., + 1., + duration, + anim::linear); +} + +void Message::startTextAppearingHeightAnimation() { + const auto appearing = Get(); + if (!appearing + || appearing->heightStarted + || (appearing->shownLines >= int(appearing->lines.size()))) { + return; + } + 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); +} + +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) { + adjustHeight(delta); + history()->viewHeightAdjusted(this, delta); + } + + 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(); + } +} + bool Message::needInfoDisplay() const { const auto media = this->media(); const auto mediaDisplayed = media ? media->isDisplayed() : false; diff --git a/Telegram/SourceFiles/history/view/history_view_message.h b/Telegram/SourceFiles/history/view/history_view_message.h index c4fb3627f4..aa338f830c 100644 --- a/Telegram/SourceFiles/history/view/history_view_message.h +++ b/Telegram/SourceFiles/history/view/history_view_message.h @@ -83,6 +83,20 @@ struct RightBadge : RuntimeComponent { mutable QPoint lastPoint; }; +struct TextAppearing : RuntimeComponent { + std::vector lines; + int textWidth = 0; + int shownLines = 0; + int revealedLineWidth = 0; + int shownWidth = 0; + int shownHeight = 0; + crl::time widthDuration = 0; + Ui::Animations::Simple widthAnimation; + Ui::Animations::Simple heightAnimation; + bool geometryValid = false; + bool heightStarted = false; +}; + struct BottomRippleMask { QImage image; int shift = 0; @@ -373,6 +387,11 @@ private: [[nodiscard]] ClickHandlerPtr psaTooltipLink() const; void psaTooltipToggled(bool shown) const; void invalidateTextDependentCache() override; + void startTextAppearingWidthAnimation(); + void startTextAppearingHeightAnimation(); + void textAppearingTick(); + void textAppearingHeightTick(); + void tryAdvanceTextAppearing(); void refreshRightBadge(); [[nodiscard]] int rightBadgeWidth() const; diff --git a/Telegram/SourceFiles/history/view/history_view_object.h b/Telegram/SourceFiles/history/view/history_view_object.h index 9072b90bed..c61847c1bf 100644 --- a/Telegram/SourceFiles/history/view/history_view_object.h +++ b/Telegram/SourceFiles/history/view/history_view_object.h @@ -54,6 +54,9 @@ protected: _width = size.width(); _height = size.height(); } + void adjustHeight(int delta) { + _height += delta; + } private: virtual QSize countOptimalSize() = 0; diff --git a/Telegram/SourceFiles/history/view/history_view_pinned_section.cpp b/Telegram/SourceFiles/history/view/history_view_pinned_section.cpp index db70e44aa6..62c03cd6e2 100644 --- a/Telegram/SourceFiles/history/view/history_view_pinned_section.cpp +++ b/Telegram/SourceFiles/history/view/history_view_pinned_section.cpp @@ -608,7 +608,8 @@ void PinnedWidget::listMarkContentsRead( } MessagesBarData PinnedWidget::listMessagesBar( - const std::vector> &elements) { + const std::vector> &elements, + bool markLastAsRead) { return {}; } diff --git a/Telegram/SourceFiles/history/view/history_view_pinned_section.h b/Telegram/SourceFiles/history/view/history_view_pinned_section.h index be7a515d72..23b7f0a137 100644 --- a/Telegram/SourceFiles/history/view/history_view_pinned_section.h +++ b/Telegram/SourceFiles/history/view/history_view_pinned_section.h @@ -97,7 +97,8 @@ public: void listMarkContentsRead( const base::flat_set> &items) override; MessagesBarData listMessagesBar( - const std::vector> &elements) override; + const std::vector> &elements, + bool markLastAsRead) override; void listContentRefreshed() override; void listUpdateDateLink( ClickHandlerPtr &link, diff --git a/Telegram/SourceFiles/history/view/history_view_scheduled_section.cpp b/Telegram/SourceFiles/history/view/history_view_scheduled_section.cpp index 035c4003b6..db83e545fc 100644 --- a/Telegram/SourceFiles/history/view/history_view_scheduled_section.cpp +++ b/Telegram/SourceFiles/history/view/history_view_scheduled_section.cpp @@ -1472,7 +1472,8 @@ void ScheduledWidget::listMarkContentsRead( } MessagesBarData ScheduledWidget::listMessagesBar( - const std::vector> &elements) { + const std::vector> &elements, + bool markLastAsRead) { return {}; } diff --git a/Telegram/SourceFiles/history/view/history_view_scheduled_section.h b/Telegram/SourceFiles/history/view/history_view_scheduled_section.h index d5239a2e86..3d03c52a37 100644 --- a/Telegram/SourceFiles/history/view/history_view_scheduled_section.h +++ b/Telegram/SourceFiles/history/view/history_view_scheduled_section.h @@ -139,7 +139,8 @@ public: void listMarkContentsRead( const base::flat_set> &items) override; MessagesBarData listMessagesBar( - const std::vector> &elements) override; + const std::vector> &elements, + bool markLastAsRead) override; void listContentRefreshed() override; void listUpdateDateLink( ClickHandlerPtr &link, diff --git a/Telegram/SourceFiles/info/polls/info_polls_list_widget.cpp b/Telegram/SourceFiles/info/polls/info_polls_list_widget.cpp index 0c848e506f..0ccfc8b4a2 100644 --- a/Telegram/SourceFiles/info/polls/info_polls_list_widget.cpp +++ b/Telegram/SourceFiles/info/polls/info_polls_list_widget.cpp @@ -106,8 +106,8 @@ private: void listMarkContentsRead( const base::flat_set> &items) override; HistoryView::MessagesBarData listMessagesBar( - const std::vector> &elements) - override; + const std::vector> &elements, + bool markLastAsRead) override; void listContentRefreshed() override; void listUpdateDateLink( ClickHandlerPtr &link, @@ -522,7 +522,8 @@ void ListWidget::Inner::listMarkContentsRead( } HistoryView::MessagesBarData ListWidget::Inner::listMessagesBar( - const std::vector> &elements) { + const std::vector> &elements, + bool markLastAsRead) { return {}; } diff --git a/Telegram/SourceFiles/settings/business/settings_shortcut_messages.cpp b/Telegram/SourceFiles/settings/business/settings_shortcut_messages.cpp index 2a7fee1680..ecddedae0a 100644 --- a/Telegram/SourceFiles/settings/business/settings_shortcut_messages.cpp +++ b/Telegram/SourceFiles/settings/business/settings_shortcut_messages.cpp @@ -128,7 +128,8 @@ private: void listMarkContentsRead( const base::flat_set> &items) override; MessagesBarData listMessagesBar( - const std::vector> &elements) override; + const std::vector> &elements, + bool markLastAsRead) override; void listContentRefreshed() override; void listUpdateDateLink( ClickHandlerPtr &link, @@ -911,7 +912,8 @@ void ShortcutMessages::listMarkContentsRead( } MessagesBarData ShortcutMessages::listMessagesBar( - const std::vector> &elements) { + const std::vector> &elements, + bool markLastAsRead) { return {}; } diff --git a/Telegram/build/version b/Telegram/build/version index ad6d505e3f..3dcc371bbf 100644 --- a/Telegram/build/version +++ b/Telegram/build/version @@ -1,7 +1,7 @@ -AppVersion 6007004 +AppVersion 6007005 AppVersionStrMajor 6.7 -AppVersionStrSmall 6.7.4 -AppVersionStr 6.7.4 +AppVersionStrSmall 6.7.5 +AppVersionStr 6.7.5 BetaChannel 0 AlphaVersion 0 -AppVersionOriginal 6.7.4 +AppVersionOriginal 6.7.5 diff --git a/changelog.txt b/changelog.txt index 94170bcb3e..7c2d45400b 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,3 +1,7 @@ +6.7.5 (07.04.26) + +- Support for gradual appearing of bot reply messages. + 6.7.4 (07.04.26) - Add experimental option for "AI Tools" button.