diff --git a/Telegram/SourceFiles/history/view/history_view_chat_section.cpp b/Telegram/SourceFiles/history/view/history_view_chat_section.cpp index bf4e36770e..c84c878959 100644 --- a/Telegram/SourceFiles/history/view/history_view_chat_section.cpp +++ b/Telegram/SourceFiles/history/view/history_view_chat_section.cpp @@ -35,6 +35,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "history/history_item_reply_markup.h" #include "iv/iv_rich_message_serializer.h" #include "iv/iv_rich_page.h" +#include "ui/chat/choose_theme_controller.h" #include "ui/chat/pinned_bar.h" #include "ui/chat/chat_style.h" #include "ui/controls/swipe_handler.h" @@ -138,6 +139,27 @@ enum class SendPermission { return false; } +[[nodiscard]] std::optional ShowAtMsgIdFromPosition( + not_null history, + Data::MessagePosition position) { + const auto migrated = history->migrateFrom(); + if (position == Data::UnreadMessagePosition) { + return ShowAtUnreadMsgId; + } else if (position == Data::MaxMessagePosition) { + return ShowAtTheEndMsgId; + } + const auto fullId = position.fullId; + if ((fullId.peer == history->peer->id) + && (IsServerMsgId(fullId.msg) || IsClientMsgId(fullId.msg))) { + return fullId.msg; + } else if (migrated + && (fullId.peer == migrated->peer->id) + && IsServerMsgId(fullId.msg)) { + return -fullId.msg; + } + return std::nullopt; +} + } // namespace ChatMemento::ChatMemento( @@ -156,6 +178,9 @@ ChatMemento::ChatMemento( .date = TimeId(0), }); } + if (!_highlightId && !_id.repliesRootId && !_id.sublist) { + setFromHistory(_id.history); + } } ChatMemento::ChatMemento( @@ -175,6 +200,37 @@ void ChatMemento::setFromTopic(not_null topic) { } } +void ChatMemento::setFromHistory(not_null history) { + const auto migrated = history->migrateFrom(); + const auto showAtMsgId = history->showAtMsgId; + if (showAtMsgId == ShowAtUnreadMsgId) { + _list.setAroundPosition(Data::UnreadMessagePosition); + } else if (showAtMsgId == ShowAtTheEndMsgId) { + _list.setAroundPosition(Data::MaxMessagePosition); + } else if (IsServerMsgId(showAtMsgId) || IsClientMsgId(showAtMsgId)) { + _list.setAroundPosition({ + .fullId = FullMsgId(history->peer->id, showAtMsgId), + .date = TimeId(0), + }); + } else if (migrated && IsServerMsgId(-showAtMsgId)) { + _list.setAroundPosition({ + .fullId = FullMsgId(migrated->peer->id, -showAtMsgId), + .date = TimeId(0), + }); + } + if (const auto scrollTopItem = history->scrollTopItem) { + _list.setScrollTopState(ListMemento::ScrollTopState{ + .item = scrollTopItem->data()->position(), + .shift = history->scrollTopOffset, + }); + } else if (migrated && migrated->scrollTopItem) { + _list.setScrollTopState(ListMemento::ScrollTopState{ + .item = migrated->scrollTopItem->data()->position(), + .shift = migrated->scrollTopOffset, + }); + } +} + Data::ForumTopic *ChatMemento::topicForRemoveRequests() const { return _id.repliesRootId @@ -669,8 +725,23 @@ ChatWidget::ChatWidget( session().changes().historyUpdates( _history, HistoryUpdateFlag::BotKeyboard - ) | rpl::on_next([=] { - updateBotKeyboard(); + | HistoryUpdateFlag::UnreadMentions + | HistoryUpdateFlag::UnreadReactions + | HistoryUpdateFlag::UnreadPollVotes + | HistoryUpdateFlag::UnreadView + ) | rpl::on_next([=](const Data::HistoryUpdate &update) { + const auto flags = update.flags; + if (flags & HistoryUpdateFlag::BotKeyboard) { + updateBotKeyboard(); + } + if ((flags & HistoryUpdateFlag::UnreadMentions) + || (flags & HistoryUpdateFlag::UnreadReactions) + || (flags & HistoryUpdateFlag::UnreadPollVotes)) { + _cornerButtons.updateUnreadThingsVisibility(); + } + if (flags & HistoryUpdateFlag::UnreadView) { + unreadCountUpdated(); + } }, lifetime()); } @@ -731,6 +802,7 @@ ChatWidget::~ChatWidget() { } _aboutView = nullptr; _suggestOptions = nullptr; + _chooseTheme = nullptr; base::take(_sendAction); if (_repliesRootId) { controller()->sendingAnimation().clear(); @@ -739,6 +811,11 @@ ChatWidget::~ChatWidget() { _subsectionTabsLifetime.destroy(); controller()->saveSubsectionTabs(base::take(_subsectionTabs)); } + if (mode() == Mode::History) { + auto state = ListMemento(); + _inner->saveState(&state); + saveHistoryScrollState(state); + } if (_topic) { if (_topic->creating()) { if (controller()->activeChatCurrent().topic() == _topic) { @@ -772,6 +849,9 @@ void ChatWidget::orderWidgets() { } _bottom->raise(); _composeControls->raisePanels(); + if (_chooseTheme) { + _chooseTheme->raise(); + } } void ChatWidget::setupRoot() { @@ -2274,6 +2354,49 @@ bool ChatWidget::clearChooseReportMessages() { return true; } +bool ChatWidget::toggleChooseChatTheme( + not_null peer, + std::optional show) { + const auto update = [=] { + updateControlsVisibility(); + updateControlsGeometry(); + }; + if (peer != _peer) { + return false; + } else if (_chooseTheme) { + if (isChoosingTheme() && !show.value_or(false)) { + const auto was = base::take(_chooseTheme); + if (Ui::InFocusChain(this)) { + setInnerFocus(); + } + update(); + updateControlsVisibility(); + updateControlsGeometry(); + } + return true; + } else if (!show.value_or(true)) { + return true; + } else if (_composeControls->isRecording()) { + controller()->showToast(tr::lng_chat_theme_cant_voice(tr::now)); + return true; + } + _chooseTheme = std::make_unique( + this, + controller(), + peer); + _chooseTheme->shouldBeShownValue( + ) | rpl::on_next(update, _chooseTheme->lifetime()); + orderWidgets(); + updateControlsVisibility(); + updateControlsGeometry(); + update(); + return true; +} + +Ui::ChatTheme *ChatWidget::customChatTheme() const { + return _theme.get(); +} + void ChatWidget::updateTopBarChooseForReport() { if (_chooseForReport && _chooseForReport->active) { _topBar->showChooseMessagesForReport( @@ -2306,9 +2429,22 @@ void ChatWidget::reportSelectedMessages() { void ChatWidget::updateControlsVisibility() { _bottom->updateControlsVisibility(); const auto active = _bottom->isButtonActive(); + const auto choosingTheme = isChoosingTheme(); const auto hasSublistReplacement = _bottom->hasOpenChatButton() || _bottom->hasAboutHiddenAuthor(); - if (active) { + if (choosingTheme) { + _chooseTheme->show(); + setInnerFocus(); + } else if (_chooseTheme) { + _chooseTheme->hide(); + } + if (choosingTheme && _kbScroll) { + _kbScroll->hide(); + _kbShown = false; + setKeyboardReplyTo(nullptr); + hideKeyboardReplyToExternal(); + } + if (active || choosingTheme) { if (!hasSublistReplacement) { _composeControls->hide(); } @@ -3030,6 +3166,8 @@ void ChatWidget::doSetInnerFocus() { && _inner->getSelectedText().rich.text.isEmpty() && _inner->getSelectedItems().empty()) { _composeSearch->setInnerFocus(); + } else if (isChoosingTheme()) { + _chooseTheme->setFocus(); } else if (!_inner->getSelectedText().rich.text.isEmpty() || !_inner->getSelectedItems().empty() || !_composeControls->focus()) { @@ -3048,9 +3186,6 @@ bool ChatWidget::showInternal( refreshSuggestFromDraft(); } else { restoreState(logMemento); - if (!logMemento->highlightId()) { - showAtPosition(Data::UnreadMessagePosition); - } } return true; } @@ -3191,10 +3326,62 @@ void ChatWidget::replyToMessage(FullReplyTo id) { refreshTopBarActiveChat(); } +void ChatWidget::saveHistoryScrollState(const ListMemento &state) { + if (mode() != Mode::History || !_inner) { + return; + } + const auto migrated = _history->migrateFrom(); + const auto aroundPosition = state.aroundPosition(); + const auto scrollTopState = state.scrollTopState(); + const auto useAroundPosition = scrollTopState.item + || (aroundPosition == Data::UnreadMessagePosition) + || (aroundPosition == Data::MaxMessagePosition); + _history->showAtMsgId = (useAroundPosition + ? ShowAtMsgIdFromPosition(_history, aroundPosition) + : std::nullopt).value_or((_lastShownAt + && migrated + && (_lastShownAt.peer == migrated->peer->id) + && IsServerMsgId(_lastShownAt.msg)) + ? -_lastShownAt.msg + : _lastShownAt + ? _lastShownAt.msg + : ShowAtUnreadMsgId); + const auto atBottom = (_scroll->scrollTop() >= _scroll->scrollTopMax()) + && _inner->loadedAtBottomKnown() + && _inner->loadedAtBottom(); + if (atBottom) { + _history->forgetScrollState(); + if (migrated) { + migrated->forgetScrollState(); + } + } else { + const auto useMigrated = migrated + && (scrollTopState.item.fullId.peer == migrated->peer->id); + if (useMigrated) { + _history->forgetScrollState(); + if (const auto item = session().data().message(scrollTopState.item.fullId)) { + migrated->scrollTopItem = item->mainView(); + migrated->scrollTopOffset = scrollTopState.shift; + if (!migrated->scrollTopItem) { + migrated->forgetScrollState(); + } + } else { + migrated->forgetScrollState(); + } + } else { + _history->countScrollState(_scroll->scrollTop()); + if (migrated) { + migrated->forgetScrollState(); + } + } + } +} + void ChatWidget::saveState(not_null memento) { memento->setReplies(_replies); memento->setReplyReturns(_cornerButtons.replyReturns()); _inner->saveState(memento->list()); + saveHistoryScrollState(*memento->list()); } void ChatWidget::refreshReplies() { @@ -3299,7 +3486,15 @@ void ChatWidget::subscribeToSublist() { } void ChatWidget::unreadCountUpdated() { - if (_sublist && _sublist->unreadMark()) { + if (mode() == Mode::History) { + const auto hideCounter = _history->isForum() + || !_history->trackUnreadMessages(); + refreshUnreadCountBadge(hideCounter + ? 0 + : _history->amMonoforumAdmin() + ? _history->chatListUnreadState().messages + : _history->chatListBadgesState().unreadCounter); + } else if (_sublist && _sublist->unreadMark()) { crl::on_main(this, [=] { const auto guard = base::make_weak(this); controller()->showPeerHistory(_sublist->owningHistory()); @@ -3362,6 +3557,9 @@ void ChatWidget::restoreState(not_null memento) { activateChooseForReport(); } updateBotKeyboard(); + if (mode() == Mode::History) { + unreadCountUpdated(); + } } void ChatWidget::resizeEvent(QResizeEvent *e) { @@ -3413,36 +3611,40 @@ void ChatWidget::updateControlsGeometry() { bottom -= bottomHeight; } bottom -= tabsBottomSkip; - if (_suggestOptions) { - bottom -= st::historyReplyHeight; - } - const auto maxFieldHeight = computeMaxFieldHeightForKeyboard( - top, - bottom); - if (_kbScroll && keyboardRowsVisible() && _keyboard) { - _keyboard->resizeToWidth(innerWidth, maxFieldHeight); - const auto keyboardReserve = std::min( - _keyboard->height(), - maxFieldHeight - (maxFieldHeight / 2)); - _composeControls->setFieldMaxHeight( - maxFieldHeight - keyboardReserve); - const auto maxKbHeight = keyboardReserve; - _keyboard->resizeToWidth(innerWidth, maxKbHeight); - const auto kbHeight = std::min( - _keyboard->height(), - maxKbHeight); - _kbScroll->setGeometry( - tabsLeftSkip, - bottom - kbHeight, - innerWidth, - kbHeight); - bottom -= kbHeight; + if (isChoosingTheme()) { + bottom -= _chooseTheme->height(); } else { - _composeControls->setFieldMaxHeight( - st::historyComposeFieldMaxHeight); - } - if (!bottomHeight) { - bottom -= _composeControls->heightCurrent(); + if (_suggestOptions) { + bottom -= st::historyReplyHeight; + } + const auto maxFieldHeight = computeMaxFieldHeightForKeyboard( + top, + bottom); + if (_kbScroll && keyboardRowsVisible() && _keyboard) { + _keyboard->resizeToWidth(innerWidth, maxFieldHeight); + const auto keyboardReserve = std::min( + _keyboard->height(), + maxFieldHeight - (maxFieldHeight / 2)); + _composeControls->setFieldMaxHeight( + maxFieldHeight - keyboardReserve); + const auto maxKbHeight = keyboardReserve; + _keyboard->resizeToWidth(innerWidth, maxKbHeight); + const auto kbHeight = std::min( + _keyboard->height(), + maxKbHeight); + _kbScroll->setGeometry( + tabsLeftSkip, + bottom - kbHeight, + innerWidth, + kbHeight); + bottom -= kbHeight; + } else { + _composeControls->setFieldMaxHeight( + st::historyComposeFieldMaxHeight); + } + if (!bottomHeight) { + bottom -= _composeControls->heightCurrent(); + } } const auto composeTop = bottom; @@ -3525,6 +3727,10 @@ bool ChatWidget::emptyShown() const { || (_topic->lastKnownServerMessageId() == _repliesRootId)); } +bool ChatWidget::isChoosingTheme() const { + return _chooseTheme && _chooseTheme->shouldBeShown(); +} + void ChatWidget::onScroll() { if (_skipScrollEvent) { return; @@ -3542,6 +3748,11 @@ void ChatWidget::updateInnerVisibleArea() { _topControls->updatePinnedViewer(); _cornerButtons.updateJumpDownVisibility(); _cornerButtons.updateUnreadThingsVisibility(); + if (mode() == Mode::History) { + auto state = ListMemento(); + _inner->saveState(&state); + saveHistoryScrollState(state); + } if (_lastScrollTop != scrollTop) { if (!_synteticScrollEvent) { _topControls->checkLastPinnedClickedIdReset( @@ -3682,6 +3893,10 @@ void ChatWidget::listCancelRequest() { } return; } + if (isChoosingTheme()) { + toggleChooseChatTheme(_peer, false); + return; + } if (_inner && !_inner->getSelectedItems().empty()) { clearSelected(); return; @@ -4216,6 +4431,16 @@ void ChatWidget::updateBotKeyboard(History *h, bool force) { const auto forceReply = _keyboard->forceReply() && !realReplySource; const auto canShow = _canSendMessages && !_bottom->isButtonActive(); + if (isChoosingTheme()) { + _kbScroll->hide(); + _kbShown = false; + setKeyboardReplyTo(nullptr); + hideKeyboardReplyToExternal(); + updateKeyboardUiState(hasMarkup, true); + updateControlsGeometry(); + update(); + return; + } const auto rowsFromRealReply = realReplySource && hasMarkup; const auto suppressKeyboardUi = editing || (reply && !rowsFromRealReply); const auto shouldShowRows = canShow diff --git a/Telegram/SourceFiles/history/view/history_view_chat_section.h b/Telegram/SourceFiles/history/view/history_view_chat_section.h index 718805244b..c7c4b81308 100644 --- a/Telegram/SourceFiles/history/view/history_view_chat_section.h +++ b/Telegram/SourceFiles/history/view/history_view_chat_section.h @@ -43,6 +43,7 @@ class ScrollArea; struct PreparedList; struct PreparedBundle; class SendFilesWay; +class ChooseThemeController; } // namespace Ui namespace Profile { @@ -149,6 +150,10 @@ public: Data::ReportInput &&reportInput, Fn)> &&done) override; bool clearChooseReportMessages() override; + bool toggleChooseChatTheme( + not_null peer, + std::optional show = std::nullopt) override; + [[nodiscard]] Ui::ChatTheme *customChatTheme() const override; void setInternalState( const QRect &geometry, @@ -291,6 +296,7 @@ private: void updateInnerVisibleArea(); void updateControlsGeometry(); void updateAdaptiveLayout(); + void saveHistoryScrollState(const ListMemento &state); void saveState(not_null memento); void restoreState(not_null memento); void setReplies(std::shared_ptr replies); @@ -474,6 +480,7 @@ private: void toggleMuteUnmute(); void reportSelectedMessages(); void updateTopBarChooseForReport(); + [[nodiscard]] bool isChoosingTheme() const; [[nodiscard]] bool emptyShown() const; [[nodiscard]] bool showSlowmodeError(); @@ -500,6 +507,7 @@ private: bool active = false; }; std::unique_ptr _chooseForReport; + std::unique_ptr _chooseTheme; Data::SavedSublist *_sublist = nullptr; PeerId _monoforumPeerId; @@ -602,6 +610,7 @@ public: } void setFromTopic(not_null topic); + void setFromHistory(not_null history); void setReplyReturns(const QVector &list) { _replyReturns = list; diff --git a/Telegram/SourceFiles/mainwidget.cpp b/Telegram/SourceFiles/mainwidget.cpp index 9166c3925f..066c0b168c 100644 --- a/Telegram/SourceFiles/mainwidget.cpp +++ b/Telegram/SourceFiles/mainwidget.cpp @@ -1363,7 +1363,9 @@ void MainWidget::clearChooseReportMessages() { void MainWidget::toggleChooseChatTheme( not_null peer, std::optional show) { - _history->toggleChooseChatTheme(peer, show); + if (!_mainSection || !_mainSection->toggleChooseChatTheme(peer, show)) { + _history->toggleChooseChatTheme(peer, show); + } } bool MainWidget::showHistoryInDifferentWindow( @@ -1713,6 +1715,11 @@ PeerData *MainWidget::peer() const { } Ui::ChatTheme *MainWidget::customChatTheme() const { + if (_mainSection) { + if (const auto custom = _mainSection->customChatTheme()) { + return custom; + } + } return _history->customChatTheme(); } diff --git a/Telegram/SourceFiles/window/section_widget.h b/Telegram/SourceFiles/window/section_widget.h index fa60616f74..a838da2f73 100644 --- a/Telegram/SourceFiles/window/section_widget.h +++ b/Telegram/SourceFiles/window/section_widget.h @@ -14,6 +14,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "base/object_ptr.h" #include "window/window_section_common.h" +#include + class PeerData; namespace ChatHelpers { @@ -176,6 +178,14 @@ public: virtual bool clearChooseReportMessages() { return false; } + virtual bool toggleChooseChatTheme( + not_null peer, + std::optional show = std::nullopt) { + return false; + } + [[nodiscard]] virtual Ui::ChatTheme *customChatTheme() const { + return nullptr; + } [[nodiscard]] virtual bool preventsClose( Fn &&continueCallback) const {