Extract chat preview as a SessionController part.

This commit is contained in:
John Preston 2024-05-31 13:15:51 +04:00
parent 5cfd86b829
commit ad342a5324
7 changed files with 267 additions and 106 deletions

View file

@ -1516,6 +1516,8 @@ PRIVATE
window/section_widget.h
window/window_adaptive.cpp
window/window_adaptive.h
window/window_chat_preview.cpp
window/window_chat_preview.h
window/window_connecting_widget.cpp
window/window_connecting_widget.h
window/window_controller.cpp

View file

@ -86,7 +86,6 @@ namespace {
constexpr auto kHashtagResultsLimit = 5;
constexpr auto kStartReorderThreshold = 30;
constexpr auto kChatPreviewDelay = crl::time(1000);
[[nodiscard]] int FixedOnTopDialogsCount(not_null<Dialogs::IndexedList*> list) {
auto result = 0;
@ -216,7 +215,6 @@ InnerWidget::InnerWidget(
+ st::defaultDialogRow.photoSize
+ st::defaultDialogRow.padding.left())
, _cancelSearchFromUser(this, st::dialogsCancelSearchInPeer)
, _chatPreviewTimer([=] { showChatPreview(true); })
, _childListShown(std::move(childListShown)) {
setAttribute(Qt::WA_OpaquePaintEvent, true);
@ -714,8 +712,8 @@ void InnerWidget::paintEvent(QPaintEvent *e) {
context.active = active;
context.selected = _menuRow.key
? (row->key() == _menuRow.key)
: _chatPreviewKey
? (row->key() == _chatPreviewKey)
: _chatPreviewRow.key
? (row->key() == _chatPreviewRow.key)
: selected;
context.topicJumpSelected = selected
&& _selectedTopicJump
@ -981,6 +979,8 @@ void InnerWidget::paintEvent(QPaintEvent *e) {
const auto active = isSearchResultActive(result.get(), activeEntry);
const auto selected = _menuRow.key
? isSearchResultActive(result.get(), _menuRow)
: _chatPreviewRow.key
? isSearchResultActive(result.get(), _chatPreviewRow)
: (from == (isPressed()
? _searchedPressed
: _searchedSelected));
@ -1330,15 +1330,18 @@ void InnerWidget::mouseMoveEvent(QMouseEvent *e) {
return;
}
selectByMouse(globalPosition);
if (!isUserpicPress()) {
if (_chatPreviewScheduled && !isUserpicPress()) {
cancelChatPreview();
}
}
void InnerWidget::cancelChatPreview() {
_chatPreviewTimer.cancel();
_chatPreviewWillBeFor = {};
_chatPreviewTouchGlobal = {};
_chatPreviewScheduled = false;
if (_chatPreviewRow.key) {
updateDialogRow(base::take(_chatPreviewRow));
}
_controller->cancelScheduledPreview();
}
void InnerWidget::clearIrrelevantState() {
@ -1484,26 +1487,24 @@ void InnerWidget::selectByMouse(QPoint globalPosition) {
}
}
Key InnerWidget::computeChatPreviewRow() const {
auto result = computeChosenRow().key;
if (const auto peer = result.peer()) {
RowDescriptor InnerWidget::computeChatPreviewRow() const {
auto result = computeChosenRow();
if (const auto peer = result.key.peer()) {
const auto topicId = _pressedTopicJump
? _pressedTopicJumpRootId
: 0;
if (const auto topic = peer->forumTopicFor(topicId)) {
return topic;
return { topic, FullMsgId() };
}
}
return result;
return { result.key, result.message.fullId };
}
void InnerWidget::processGlobalForceClick(QPoint globalPosition) {
const auto parent = parentWidget();
if (_pressButton == Qt::LeftButton
&& parent->rect().contains(parent->mapFromGlobal(globalPosition))
&& pressShowsPreview(false)) {
_chatPreviewWillBeFor = computeChatPreviewRow();
showChatPreview(false);
&& parent->rect().contains(parent->mapFromGlobal(globalPosition))) {
showChatPreview();
}
}
@ -1520,14 +1521,10 @@ void InnerWidget::mousePressEvent(QMouseEvent *e) {
setSearchedPressed(_searchedSelected);
const auto alt = (e->modifiers() & Qt::AltModifier);
const auto onlyUserpic = !alt;
if (pressShowsPreview(onlyUserpic)) {
_chatPreviewWillBeFor = computeChatPreviewRow();
if (alt) {
showChatPreview(onlyUserpic);
return;
}
_chatPreviewTimer.callOnce(kChatPreviewDelay);
if (alt && showChatPreview()) {
return;
} else if (!alt && isUserpicPress()) {
scheduleChatPreview();
}
if (base::in_range(_collapsedSelected, 0, _collapsedRows.size())) {
@ -1599,7 +1596,7 @@ void InnerWidget::mousePressEvent(QMouseEvent *e) {
}
ClickHandler::pressed();
if (anim::Disabled()
&& !_chatPreviewTimer.isActive()
&& !_chatPreviewScheduled
&& (!_pressed || !_pressed->entry()->isPinnedDialog(_filterId))) {
mousePressReleased(e->globalPos(), e->button(), e->modifiers());
}
@ -1866,7 +1863,9 @@ void InnerWidget::mousePressReleased(
QPoint globalPosition,
Qt::MouseButton button,
Qt::KeyboardModifiers modifiers) {
_chatPreviewTimer.cancel();
if (_chatPreviewScheduled) {
_controller->cancelScheduledPreview();
}
_pressButton = Qt::NoButton;
const auto wasDragging = finishReorderOnRelease();
@ -2412,67 +2411,40 @@ void InnerWidget::fillArchiveSearchMenu(not_null<Ui::PopupMenu*> menu) {
});
}
void InnerWidget::showChatPreview(bool onlyUserpic) {
const auto key = base::take(_chatPreviewWillBeFor);
const auto touchGlobal = base::take(_chatPreviewTouchGlobal);
cancelChatPreview();
if (!pressShowsPreview(onlyUserpic) || key != computeChatPreviewRow()) {
return;
}
if (onlyUserpic && touchGlobal) {
_touchCancelRequests.fire({});
}
ClickHandler::unpressed();
mousePressReleased(QCursor::pos(), Qt::NoButton, Qt::NoModifier);
bool InnerWidget::showChatPreview() {
const auto row = computeChatPreviewRow();
const auto callback = crl::guard(this, [=](bool shown) {
chatPreviewShown(shown, row);
});
return _controller->showChatPreview(row, callback);
}
_chatPreviewKey = key;
auto preview = HistoryView::MakeChatPreview(this, key.entry());
if (!preview.menu) {
return;
}
_menu = std::move(preview.menu);
const auto weakMenu = Ui::MakeWeak(_menu.get());
const auto weakThread = base::make_weak(key.entry()->asThread());
const auto weakController = base::make_weak(_controller);
std::move(
preview.actions
) | rpl::start_with_next([=](HistoryView::ChatPreviewAction action) {
if (const auto controller = weakController.get()) {
if (const auto thread = weakThread.get()) {
const auto itemId = action.openItemId;
const auto owner = &thread->owner();
if (action.markRead) {
Window::MarkAsReadThread(thread);
} else if (action.markUnread) {
if (const auto history = thread->asHistory()) {
history->owner().histories().changeDialogUnreadMark(
history,
true);
}
} else if (action.openInfo) {
controller->showPeerInfo(thread);
} else if (const auto item = owner->message(itemId)) {
controller->showMessage(item);
} else {
controller->showThread(thread);
}
}
}
if (const auto strong = weakMenu.data()) {
strong->hideMenu();
}
}, _menu->lifetime());
QObject::connect(_menu.get(), &QObject::destroyed, [=] {
if (_chatPreviewKey) {
updateDialogRow(RowDescriptor(base::take(_chatPreviewKey), {}));
void InnerWidget::chatPreviewShown(bool shown, RowDescriptor row) {
_chatPreviewScheduled = false;
if (shown) {
_chatPreviewRow = row;
if (base::take(_chatPreviewTouchGlobal)) {
_touchCancelRequests.fire({});
}
ClickHandler::unpressed();
mousePressReleased(QCursor::pos(), Qt::NoButton, Qt::NoModifier);
} else {
cancelChatPreview();
const auto globalPosition = QCursor::pos();
if (rect().contains(mapFromGlobal(globalPosition))) {
setMouseTracking(true);
selectByMouse(globalPosition);
}
}
}
bool InnerWidget::scheduleChatPreview() {
const auto row = computeChatPreviewRow();
const auto callback = crl::guard(this, [=](bool shown) {
chatPreviewShown(shown, row);
});
_menu->popup(_lastMousePosition.value_or(QCursor::pos()));
_chatPreviewScheduled = _controller->scheduleChatPreview(row, callback);
return _chatPreviewScheduled;
}
void InnerWidget::contextMenuEvent(QContextMenuEvent *e) {
@ -2571,10 +2543,8 @@ bool InnerWidget::processTouchEvent(not_null<QTouchEvent*> e) {
}
selectByMouse(*point);
const auto onlyUserpic = true;
if (pressShowsPreview(onlyUserpic)) {
if (isUserpicPress() && scheduleChatPreview()) {
_chatPreviewTouchGlobal = point;
_chatPreviewWillBeFor = computeChatPreviewRow();
_chatPreviewTimer.callOnce(kChatPreviewDelay);
} else if (!_dragging) {
_touchDragStartGlobal = point;
_touchDragPinnedTimer.callOnce(QApplication::startDragTime());
@ -2894,11 +2864,8 @@ void InnerWidget::trackSearchResultsHistory(not_null<History*> history) {
refresh();
clearMouseSelection(true);
}
if (_chatPreviewWillBeFor.topic() == topic) {
_chatPreviewWillBeFor = {};
}
if (_chatPreviewKey.topic() == topic) {
_chatPreviewKey = {};
if (_chatPreviewRow.key.topic() == topic) {
_chatPreviewRow = {};
}
}, _searchResultsLifetime);
}
@ -3793,18 +3760,6 @@ bool InnerWidget::isUserpicPressOnWide() const {
return isUserpicPress() && (width() > _narrowWidth);
}
bool InnerWidget::pressShowsPreview(bool onlyUserpic) const {
if (onlyUserpic && !isUserpicPress()) {
return false;
}
const auto key = computeChosenRow().key;
if (const auto history = key.history()) {
return !history->peer->isForum()
|| (_pressedTopicJump && _pressedTopicJumpRootId);
}
return key.topic() != nullptr;
}
bool InnerWidget::chooseRow(
Qt::KeyboardModifiers modifiers,
MsgId pressedTopicRootId) {
@ -4157,7 +4112,7 @@ void InnerWidget::setupShortcuts() {
&& !_controller->isLayerShown()
&& !_controller->window().locked()
&& !_childListShown.current().shown
&& !_chatPreviewKey;
&& !_chatPreviewRow.key;
}) | rpl::start_with_next([=](not_null<Shortcuts::Request*> request) {
using Command = Shortcuts::Command;

View file

@ -125,9 +125,10 @@ public:
[[nodiscard]] bool isUserpicPress() const;
[[nodiscard]] bool isUserpicPressOnWide() const;
[[nodiscard]] bool pressShowsPreview(bool onlyUserpic) const;
void cancelChatPreview();
void showChatPreview(bool onlyUserpic);
bool scheduleChatPreview();
bool showChatPreview();
void chatPreviewShown(bool shown, RowDescriptor row = {});
bool chooseRow(
Qt::KeyboardModifiers modifiers = {},
MsgId pressedTopicRootId = {});
@ -400,7 +401,7 @@ private:
void trackSearchResultsHistory(not_null<History*> history);
[[nodiscard]] QBrush currentBg() const;
[[nodiscard]] Key computeChatPreviewRow() const;
[[nodiscard]] RowDescriptor computeChatPreviewRow() const;
[[nodiscard]] const std::vector<Key> &pinnedChatsOrder() const;
void checkReorderPinnedStart(QPoint localPosition);
@ -525,9 +526,8 @@ private:
rpl::event_stream<QString> _completeHashtagRequests;
rpl::event_stream<> _refreshHashtagsRequests;
base::Timer _chatPreviewTimer;
Key _chatPreviewWillBeFor;
Key _chatPreviewKey;
RowDescriptor _chatPreviewRow;
bool _chatPreviewScheduled = false;
std::optional<QPoint> _chatPreviewTouchGlobal;
base::Timer _touchDragPinnedTimer;
std::optional<QPoint> _touchDragStartGlobal;

View file

@ -0,0 +1,126 @@
/*
This file is part of Telegram Desktop,
the official desktop application for the Telegram messaging service.
For license and copyright information please follow this link:
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#include "window/window_chat_preview.h"
#include "data/data_forum_topic.h"
#include "data/data_histories.h"
#include "data/data_peer.h"
#include "data/data_session.h"
#include "history/history.h"
#include "history/view/history_view_chat_preview.h"
#include "mainwidget.h"
#include "ui/widgets/popup_menu.h"
#include "window/window_peer_menu.h"
#include "window/window_session_controller.h"
namespace Window {
namespace {
constexpr auto kChatPreviewDelay = crl::time(1000);
} // namespace
ChatPreviewManager::ChatPreviewManager(
not_null<SessionController*> controller)
: _controller(controller)
, _timer([=] { showScheduled(); }) {
}
bool ChatPreviewManager::show(
Dialogs::RowDescriptor row,
Fn<void(bool shown)> callback) {
cancelScheduled();
_topicLifetime.destroy();
if (const auto topic = row.key.topic()) {
_topicLifetime = topic->destroyed() | rpl::start_with_next([=] {
_menu = nullptr;
});
}
const auto parent = _controller->content();
auto preview = HistoryView::MakeChatPreview(parent, row.key.entry());
if (!preview.menu) {
return false;
}
_menu = std::move(preview.menu);
const auto weakMenu = Ui::MakeWeak(_menu.get());
const auto weakThread = base::make_weak(row.key.entry()->asThread());
const auto weakController = base::make_weak(_controller);
std::move(
preview.actions
) | rpl::start_with_next([=](HistoryView::ChatPreviewAction action) {
if (const auto controller = weakController.get()) {
if (const auto thread = weakThread.get()) {
const auto itemId = action.openItemId;
const auto owner = &thread->owner();
if (action.markRead) {
MarkAsReadThread(thread);
} else if (action.markUnread) {
if (const auto history = thread->asHistory()) {
history->owner().histories().changeDialogUnreadMark(
history,
true);
}
} else if (action.openInfo) {
controller->showPeerInfo(thread);
} else if (const auto item = owner->message(itemId)) {
controller->showMessage(item);
} else {
controller->showThread(thread);
}
}
}
if (const auto strong = weakMenu.data()) {
strong->hideMenu();
}
}, _menu->lifetime());
QObject::connect(_menu.get(), &QObject::destroyed, [=] {
_topicLifetime.destroy();
callback(false);
});
callback(true);
_menu->popup(QCursor::pos());
return true;
}
bool ChatPreviewManager::schedule(
Dialogs::RowDescriptor row,
Fn<void(bool shown)> callback) {
cancelScheduled();
_topicLifetime.destroy();
if (const auto topic = row.key.topic()) {
_topicLifetime = topic->destroyed() | rpl::start_with_next([=] {
cancelScheduled();
_menu = nullptr;
});
} else if (const auto history = row.key.history()) {
if (history->peer->isForum()) {
return false;
}
} else {
return false;
}
_scheduled = row;
_scheduledCallback = std::move(callback);
_timer.callOnce(kChatPreviewDelay);
return true;
}
void ChatPreviewManager::showScheduled() {
show(base::take(_scheduled), base::take(_scheduledCallback));
}
void ChatPreviewManager::cancelScheduled() {
_scheduled = {};
_scheduledCallback = nullptr;
_timer.cancel();
}
} // namespace Window

View file

@ -0,0 +1,48 @@
/*
This file is part of Telegram Desktop,
the official desktop application for the Telegram messaging service.
For license and copyright information please follow this link:
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#pragma once
#include "base/timer.h"
#include "base/unique_qptr.h"
#include "dialogs/dialogs_key.h"
namespace Ui {
class PopupMenu;
} // namespace Ui
namespace Window {
class SessionController;
class ChatPreviewManager final {
public:
ChatPreviewManager(not_null<SessionController*> controller);
bool show(
Dialogs::RowDescriptor row,
Fn<void(bool shown)> callback = nullptr);
bool schedule(
Dialogs::RowDescriptor row,
Fn<void(bool shown)> callback = nullptr);
void cancelScheduled();
private:
void showScheduled();
const not_null<SessionController*> _controller;
Dialogs::RowDescriptor _scheduled;
Fn<void(bool)> _scheduledCallback;
base::Timer _timer;
rpl::lifetime _topicLifetime;
base::unique_qptr<Ui::PopupMenu> _menu;
};
} // namespace Window

View file

@ -13,6 +13,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "boxes/peers/edit_peer_info_box.h"
#include "boxes/peers/replace_boost_box.h"
#include "boxes/delete_messages_box.h"
#include "window/window_chat_preview.h"
#include "window/window_controller.h"
#include "window/window_filters_menu.h"
#include "info/channel_statistics/earn/info_earn_inner_widget.h"
@ -1179,6 +1180,7 @@ SessionController::SessionController(
, _window(window)
, _emojiInteractions(
std::make_unique<ChatHelpers::EmojiInteractions>(session))
, _chatPreviewManager(std::make_unique<ChatPreviewManager>(this))
, _isPrimary(window->isPrimary())
, _sendingAnimation(
std::make_unique<Ui::MessageSendingAnimationController>(this))
@ -2974,6 +2976,24 @@ QString SessionController::premiumRef() const {
return _premiumRef;
}
bool SessionController::showChatPreview(
Dialogs::RowDescriptor row,
Fn<void(bool shown)> callback) {
return _chatPreviewManager->show(std::move(row), std::move(callback));
}
bool SessionController::scheduleChatPreview(
Dialogs::RowDescriptor row,
Fn<void(bool shown)> callback) {
return _chatPreviewManager->schedule(
std::move(row),
std::move(callback));
}
void SessionController::cancelScheduledPreview() {
_chatPreviewManager->cancelScheduled();
}
bool SessionController::contentOverlapped(QWidget *w, QPaintEvent *e) const {
return widget()->contentOverlapped(w, e);
}

View file

@ -88,6 +88,7 @@ using GifPauseReasons = ChatHelpers::PauseReasons;
class SectionMemento;
class Controller;
class FiltersMenu;
class ChatPreviewManager;
struct PeerByLinkInfo;
@ -600,6 +601,14 @@ public:
void setPremiumRef(const QString &ref);
[[nodiscard]] QString premiumRef() const;
bool showChatPreview(
Dialogs::RowDescriptor row,
Fn<void(bool shown)> callback = nullptr);
bool scheduleChatPreview(
Dialogs::RowDescriptor row,
Fn<void(bool shown)> callback = nullptr);
void cancelScheduledPreview();
[[nodiscard]] bool contentOverlapped(QWidget *w, QPaintEvent *e) const;
[[nodiscard]] std::shared_ptr<ChatHelpers::Show> uiShow() override;
@ -656,6 +665,7 @@ private:
const not_null<Controller*> _window;
const std::unique_ptr<ChatHelpers::EmojiInteractions> _emojiInteractions;
const std::unique_ptr<ChatPreviewManager> _chatPreviewManager;
const bool _isPrimary = false;
mutable std::shared_ptr<ChatHelpers::Show> _cachedShow;