From c46eb8a8a16c6b7c44c5bc0805bb8a501b6eff0f Mon Sep 17 00:00:00 2001 From: John Preston Date: Fri, 17 Jul 2026 16:24:06 +0400 Subject: [PATCH] Improve initial visible scroll handling. --- .../history/history_inner_widget.cpp | 43 +++++++++++++------ .../history/history_inner_widget.h | 1 + .../SourceFiles/history/history_widget.cpp | 29 +++++++++++-- 3 files changed, 55 insertions(+), 18 deletions(-) diff --git a/Telegram/SourceFiles/history/history_inner_widget.cpp b/Telegram/SourceFiles/history/history_inner_widget.cpp index f444d21e58..e775cfc3ca 100644 --- a/Telegram/SourceFiles/history/history_inner_widget.cpp +++ b/Telegram/SourceFiles/history/history_inner_widget.cpp @@ -458,6 +458,14 @@ HistoryInner::HistoryInner( ) | rpl::on_next( [this](auto item) { itemRemoved(item); }, lifetime()); + session().data().newItemAdded( + ) | rpl::filter([=](not_null item) { + const auto history = item->history(); + return (history == _history) + || (_migrated && history == _migrated); + }) | rpl::on_next([=] { + checkAnnounceFirstMessages(); + }, lifetime()); setupThanosEffect(); session().data().viewRemoved( ) | rpl::on_next( @@ -840,13 +848,7 @@ void HistoryInner::messagesReceived( _migrated->addNewerSlice(QVector()); } } - if (_announceFirstMessages && hasFocus()) { - InvokeQueued(this, [=] { - if (_announceFirstMessages && hasFocus()) { - announceAccessibilityFocusedChild(); - } - }); - } + checkAnnounceFirstMessages(); } void HistoryInner::messagesReceivedDown( @@ -6626,17 +6628,30 @@ auto HistoryInner::computeActiveColumns(int row) const return _activeColumns; } +void HistoryInner::checkAnnounceFirstMessages() { + if (_announceFirstMessages && hasFocus()) { + InvokeQueued(this, [=] { + if (_announceFirstMessages && hasFocus()) { + announceAccessibilityFocusedChild(); + } + }); + } +} + void HistoryInner::announceAccessibilityFocusedChild() { const auto count = accessibilityChildCount(); if (count <= 0) { - // One-shot for chats focused before their first page arrived: + // One-shot for chats focused before their first messages arrived: // while the empty list holds focus, remember that the first - // received slice should announce the focused message. Fired - // (queued) from messagesReceived and disarmed by any real - // announcement below or in a later focus-in. - if (Ui::ScreenReaderModeActive()) { - _announceFirstMessages = true; - } + // received slice (or the first live-added message in a genuinely + // empty chat) should announce the focused message. Fired (queued) + // from checkAnnounceFirstMessages and disarmed by any real + // announcement below or in a later focus-in. Deliberately not + // gated by the screen-reader-mode detector: it may still be false + // during startup or for valid clients that are not on its + // allowlist, while the deferred announcement is a no-op without + // an accessibility client and never moves ordinary keyboard focus. + _announceFirstMessages = true; return; } _announceFirstMessages = false; diff --git a/Telegram/SourceFiles/history/history_inner_widget.h b/Telegram/SourceFiles/history/history_inner_widget.h index b500735ade..12cb3eb786 100644 --- a/Telegram/SourceFiles/history/history_inner_widget.h +++ b/Telegram/SourceFiles/history/history_inner_widget.h @@ -323,6 +323,7 @@ private: void playPauseFocusedMedia(); void setAccessibilityFocusedItem(int index, HistoryItem *item); void announceAccessibilityFocus(int index); + void checkAnnounceFirstMessages(); void announceAccessibilityFocusedChild(); void applyAccessibilityFocus(int index, bool announceAlways); [[nodiscard]] auto computeActiveColumns(int row) const diff --git a/Telegram/SourceFiles/history/history_widget.cpp b/Telegram/SourceFiles/history/history_widget.cpp index 10fda20956..1f4fa855f5 100644 --- a/Telegram/SourceFiles/history/history_widget.cpp +++ b/Telegram/SourceFiles/history/history_widget.cpp @@ -393,7 +393,9 @@ HistoryWidget::HistoryWidget( }), lifetime()); _scroll->setBottomContentRequest([=] { - if (!_history || !_history->loadedAtBottom()) { + if (!_history + || _firstLoadRequest + || !_history->loadedAtBottom()) { return false; } using Result = Data::SponsoredMessages::AppendResult; @@ -4797,6 +4799,8 @@ void HistoryWidget::loadMessages() { bool HistoryWidget::historyLoadedAtTop() const { if (!_history) { return true; + } else if (_firstLoadRequest) { + return false; } const auto loadMigrated = _migrated && (_history->isEmpty() @@ -4807,8 +4811,16 @@ bool HistoryWidget::historyLoadedAtTop() const { } bool HistoryWidget::historyLoadedAtBottom() const { + // While the first load request is pending the (visible) scroll area + // shows a blank list, but getReadyFor() may have already marked the + // history as loaded at bottom before any server page arrived. Report + // unloaded edges for that interval: it keeps the elastic overscroll + // (and the pull-to-next-channel gesture riding on it) away from the + // blank list until the requested messages are actually shown. if (!_history) { return true; + } else if (_firstLoadRequest) { + return false; } const auto loadMigrated = _migrated && !(_migrated->isEmpty() @@ -8109,12 +8121,11 @@ void HistoryWidget::updateHistoryGeometry( }); if (!_history || (initial && _historyInited) - || (!initial && !_historyInited)) { + || (!initial && !_historyInited && !_firstLoadRequest)) { return; } - if (_firstLoadRequest || _showAnimation) { + if (_showAnimation) { _updateHistoryGeometryRequired = true; - // scrollTopMax etc are not working after recountHistoryGeometry() return; } @@ -8211,6 +8222,16 @@ void HistoryWidget::updateHistoryGeometry( _subsectionTabs->setBoundingRect( { 0, subsectionTabsTop, width(), areaHeight }); } + if (_firstLoadRequest) { + // The scroll area stays visible (and possibly focused) while the + // first messages are being loaded, so its viewport geometry above + // is maintained even now. The list layout and the scroll position + // are still deferred until the requested messages arrive: + // scrollTopMax etc are not working after recountHistoryGeometry() + // and the initial scroll position can not be counted yet. + _updateHistoryGeometryRequired = true; + return; + } updateListSize(); _updateHistoryGeometryRequired = false;