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.