From 815a18be94f34503f8a19700c243ed7a05c1c510 Mon Sep 17 00:00:00 2001 From: John Preston Date: Thu, 8 Aug 2019 18:07:55 +0100 Subject: [PATCH] Add initial scheduled list implementation. --- .../data/data_scheduled_messages.cpp | 37 +- .../data/data_scheduled_messages.h | 8 +- Telegram/SourceFiles/data/data_session.cpp | 4 +- Telegram/SourceFiles/data/data_session.h | 10 +- .../history/feed/history_feed_section.cpp | 1 + Telegram/SourceFiles/history/history.style | 4 + .../SourceFiles/history/history_widget.cpp | 59 ++- Telegram/SourceFiles/history/history_widget.h | 4 + .../history/view/history_view_list_widget.h | 3 +- .../view/history_view_scheduled_section.cpp | 423 ++++++++++++++++++ .../view/history_view_scheduled_section.h | 161 +++++++ Telegram/SourceFiles/mainwidget.cpp | 1 + Telegram/gyp/telegram_sources.txt | 2 + 13 files changed, 692 insertions(+), 25 deletions(-) create mode 100644 Telegram/SourceFiles/history/view/history_view_scheduled_section.cpp create mode 100644 Telegram/SourceFiles/history/view/history_view_scheduled_section.h diff --git a/Telegram/SourceFiles/data/data_scheduled_messages.cpp b/Telegram/SourceFiles/data/data_scheduled_messages.cpp index 8d5b2a0ec1..50eef99c43 100644 --- a/Telegram/SourceFiles/data/data_scheduled_messages.cpp +++ b/Telegram/SourceFiles/data/data_scheduled_messages.cpp @@ -77,7 +77,7 @@ ScheduledMessages::~ScheduledMessages() { } } -MsgId ScheduledMessages::scheduledId(not_null item) { +MsgId ScheduledMessages::lookupId(not_null item) const { if (const auto i = _data.find(item->history()); i != end(_data)) { const auto &list = i->second.idByItem; if (const auto j = list.find(item); j != end(list)) { @@ -87,16 +87,9 @@ MsgId ScheduledMessages::scheduledId(not_null item) { return MsgId(0); } -HistoryItem *ScheduledMessages::scheduledMessage( - not_null history, - MsgId id) { - if (const auto i = _data.find(history); i != end(_data)) { - const auto &list = i->second.itemById; - if (const auto j = list.find(id); j != end(list)) { - return j->second; - } - } - return nullptr; +int ScheduledMessages::count(not_null history) const { + const auto i = _data.find(history); + return (i != end(_data)) ? i->second.items.size() : 0; } void ScheduledMessages::apply(const MTPDupdateNewScheduledMessage &update) { @@ -152,6 +145,28 @@ rpl::producer<> ScheduledMessages::updates(not_null history) { }); } +Data::MessagesSlice ScheduledMessages::list(not_null history) { + auto result = Data::MessagesSlice(); + const auto i = _data.find(history); + if (i == end(_data)) { + const auto i = _requests.find(history); + if (i == end(_requests)) { + return result; + } + result.fullCount = result.skippedAfter = result.skippedBefore = 0; + return result; + } + const auto &list = i->second.items; + result.skippedAfter = result.skippedBefore = 0; + result.fullCount = int(list.size()); + result.ids = ranges::view::all( + list + ) | ranges::view::transform( + &HistoryItem::fullId + ) | ranges::view::reverse | ranges::to_vector; + return result; +} + void ScheduledMessages::request(not_null history) { auto &request = _requests[history]; if (request.requestId) { diff --git a/Telegram/SourceFiles/data/data_scheduled_messages.h b/Telegram/SourceFiles/data/data_scheduled_messages.h index 305b78e447..51719a2036 100644 --- a/Telegram/SourceFiles/data/data_scheduled_messages.h +++ b/Telegram/SourceFiles/data/data_scheduled_messages.h @@ -18,6 +18,7 @@ class Session; namespace Data { class Session; +struct MessagesSlice; class ScheduledMessages final { public: @@ -26,15 +27,14 @@ public: ScheduledMessages &operator=(const ScheduledMessages &other) = delete; ~ScheduledMessages(); - [[nodiscard]] MsgId scheduledId(not_null item); - [[nodiscard]] HistoryItem *scheduledMessage( - not_null history, - MsgId id); + [[nodiscard]] MsgId lookupId(not_null item) const; + [[nodiscard]] int count(not_null history) const; void apply(const MTPDupdateNewScheduledMessage &update); void apply(const MTPDupdateDeleteScheduledMessages &update); [[nodiscard]] rpl::producer<> updates(not_null history); + [[nodiscard]] Data::MessagesSlice list(not_null history); private: using OwnedItem = std::unique_ptr; diff --git a/Telegram/SourceFiles/data/data_session.cpp b/Telegram/SourceFiles/data/data_session.cpp index 79084dd14e..899099fee6 100644 --- a/Telegram/SourceFiles/data/data_session.cpp +++ b/Telegram/SourceFiles/data/data_session.cpp @@ -45,6 +45,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_web_page.h" #include "data/data_game.h" #include "data/data_poll.h" +#include "data/data_scheduled_messages.h" #include "base/unixtime.h" #include "styles/style_boxes.h" // st::backgroundSize @@ -207,7 +208,7 @@ Session::Session(not_null session) }) , _unmuteByFinishedTimer([=] { unmuteByFinished(); }) , _groups(this) -, _scheduledMessages(this) { +, _scheduledMessages(std::make_unique(this)) { _cache->open(Local::cacheKey()); _bigFileCache->open(Local::cacheBigFileKey()); @@ -231,6 +232,7 @@ void Session::clear() { for (const auto &[peerId, history] : _histories) { history->clear(History::ClearType::Unload); } + _scheduledMessages = nullptr; _dependentMessages.clear(); base::take(_messages); base::take(_channelMessages); diff --git a/Telegram/SourceFiles/data/data_session.h b/Telegram/SourceFiles/data/data_session.h index 02a673be34..ca9d4c3f37 100644 --- a/Telegram/SourceFiles/data/data_session.h +++ b/Telegram/SourceFiles/data/data_session.h @@ -14,7 +14,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "dialogs/dialogs_main_list.h" #include "data/data_groups.h" #include "data/data_notify_settings.h" -#include "data/data_scheduled_messages.h" #include "history/history_location_manager.h" #include "base/timer.h" #include "base/flags.h" @@ -89,11 +88,8 @@ public: return _groups; } - [[nodiscard]] ScheduledMessages &scheduledMessages() { - return _scheduledMessages; - } - [[nodiscard]] const ScheduledMessages &scheduledMessages() const { - return _scheduledMessages; + [[nodiscard]] ScheduledMessages &scheduledMessages() const { + return *_scheduledMessages; } void clear(); @@ -989,7 +985,7 @@ private: int32 _wallpapersHash = 0; Groups _groups; - ScheduledMessages _scheduledMessages; + std::unique_ptr _scheduledMessages; rpl::lifetime _lifetime; diff --git a/Telegram/SourceFiles/history/feed/history_feed_section.cpp b/Telegram/SourceFiles/history/feed/history_feed_section.cpp index 76ec2c0bac..ceb360c093 100644 --- a/Telegram/SourceFiles/history/feed/history_feed_section.cpp +++ b/Telegram/SourceFiles/history/feed/history_feed_section.cpp @@ -82,6 +82,7 @@ Widget::Widget( _topBar->move(0, 0); _topBar->resizeToWidth(width()); _topBar->show(); + _topBar->forwardSelectionRequest( ) | rpl::start_with_next([=] { forwardSelected(); diff --git a/Telegram/SourceFiles/history/history.style b/Telegram/SourceFiles/history/history.style index 100bc89228..73cc577cd9 100644 --- a/Telegram/SourceFiles/history/history.style +++ b/Telegram/SourceFiles/history/history.style @@ -267,6 +267,10 @@ historyBotCommandStart: IconButton(historyAttach) { icon: icon {{ "send_control_bot_command", historyComposeIconFg }}; iconOver: icon {{ "send_control_bot_command", historyComposeIconFgOver }}; } +historyScheduledToggle: IconButton(historyAttach) { + icon: icon {{ "dialogs_calendar", historyComposeIconFg }}; + iconOver: icon {{ "dialogs_calendar", historyComposeIconFgOver }}; +} historyRecordVoiceFg: historyComposeIconFg; historyRecordVoiceFgOver: historyComposeIconFgOver; historyRecordVoiceFgActive: windowBgActive; diff --git a/Telegram/SourceFiles/history/history_widget.cpp b/Telegram/SourceFiles/history/history_widget.cpp index f031ce37a5..25b9e45e7e 100644 --- a/Telegram/SourceFiles/history/history_widget.cpp +++ b/Telegram/SourceFiles/history/history_widget.cpp @@ -36,6 +36,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_channel.h" #include "data/data_chat.h" #include "data/data_user.h" +#include "data/data_scheduled_messages.h" #include "history/history.h" #include "history/history_item.h" #include "history/history_message.h" @@ -46,6 +47,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL //#include "history/feed/history_feed_section.h" // #feed #include "history/view/history_view_service_message.h" #include "history/view/history_view_element.h" +#include "history/view/history_view_scheduled_section.h" #include "profile/profile_block_group_members.h" #include "info/info_memento.h" #include "core/click_handler_types.h" @@ -644,6 +646,7 @@ HistoryWidget::HistoryWidget( } }, lifetime()); + setupScheduledToggle(); orderWidgets(); setupShortcuts(); } @@ -1766,6 +1769,7 @@ void HistoryWidget::showHistory( session().data().requestNotifySettings(_peer); refreshSilentToggle(); } + refreshScheduledToggle(); if (_showAtMsgId == ShowAtUnreadMsgId) { if (_history->scrollTopItem) { @@ -1902,6 +1906,37 @@ void HistoryWidget::refreshSilentToggle() { } } +void HistoryWidget::setupScheduledToggle() { + controller()->activeChatValue( + ) | rpl::map([=](const Dialogs::Key &key) -> rpl::producer<> { + if (const auto history = key.history()) { + return session().data().scheduledMessages().updates(history); + } + return rpl::never(); + }) | rpl::flatten_latest( + ) | rpl::start_with_next([=] { + refreshScheduledToggle(); + updateControlsVisibility(); + updateControlsGeometry(); + }, lifetime()); +} + +void HistoryWidget::refreshScheduledToggle() { + const auto has = _history + && _peer->canWrite() + && (session().data().scheduledMessages().count(_history) > 0); + if (!_scheduled && has) { + _scheduled.create(this, st::historyScheduledToggle); + _scheduled->show(); + _scheduled->addClickHandler([=] { + controller()->showSection( + HistoryView::ScheduledMemento(_history)); + }); + } else if (_scheduled && !has) { + _scheduled.destroy(); + } +} + bool HistoryWidget::contentOverlapped(const QRect &globalRect) { return (_attachDragDocument->overlaps(globalRect) || _attachDragPhoto->overlaps(globalRect) @@ -2004,6 +2039,9 @@ void HistoryWidget::updateControlsVisibility() { if (_silent) { _silent->hide(); } + if (_scheduled) { + _scheduled->hide(); + } _kbScroll->hide(); _fieldBarCancel->hide(); _attachToggle->hide(); @@ -2041,6 +2079,9 @@ void HistoryWidget::updateControlsVisibility() { if (_silent) { _silent->hide(); } + if (_scheduled) { + _scheduled->hide(); + } if (_kbShown) { _kbScroll->show(); } else { @@ -2080,6 +2121,9 @@ void HistoryWidget::updateControlsVisibility() { if (_silent) { _silent->show(); } + if (_scheduled) { + _scheduled->show(); + } updateFieldPlaceholder(); } if (_editMsgId || _replyToId || readyToForward() || (_previewData && _previewData->pendingTill >= 0) || _kbReplyTo) { @@ -2106,6 +2150,9 @@ void HistoryWidget::updateControlsVisibility() { if (_silent) { _silent->hide(); } + if (_scheduled) { + _scheduled->hide(); + } _kbScroll->hide(); _fieldBarCancel->hide(); _attachToggle->hide(); @@ -3841,7 +3888,7 @@ void HistoryWidget::moveFieldControls() { } // _attachToggle --------- _inlineResults -------------------------------------- _tabbedPanel --------- _fieldBarCancel -// (_attachDocument|_attachPhoto) _field (_silent|_cmdStart|_kbShow) (_kbHide|_tabbedSelectorToggle) [_broadcast] _send +// (_attachDocument|_attachPhoto) _field (_scheduled) (_silent|_cmdStart|_kbShow) (_kbHide|_tabbedSelectorToggle) [_broadcast] _send // (_botStart|_unblock|_joinChannel|{_muteUnmute&_discuss}) auto buttonsBottom = bottom - _attachToggle->height(); @@ -3858,6 +3905,13 @@ void HistoryWidget::moveFieldControls() { if (_silent) { _silent->moveToRight(right, buttonsBottom); } + const auto kbShowShown = _history && !_kbShown && _keyboard->hasMarkup(); + if (kbShowShown || _cmdStartShown || _silent) { + right += _botCommandStart->width(); + } + if (_scheduled) { + _scheduled->moveToRight(right, buttonsBottom); + } _fieldBarCancel->moveToRight(0, _field->y() - st::historySendPadding - _fieldBarCancel->height()); if (_inlineResults) { @@ -3915,6 +3969,7 @@ void HistoryWidget::updateFieldSize() { if (kbShowShown) fieldWidth -= _botKeyboardShow->width(); if (_cmdStartShown) fieldWidth -= _botCommandStart->width(); if (_silent) fieldWidth -= _silent->width(); + if (_scheduled) fieldWidth -= _scheduled->width(); if (_field->width() != fieldWidth) { _field->resize(fieldWidth, _field->height()); @@ -6089,6 +6144,7 @@ void HistoryWidget::fullPeerUpdated(PeerData *peer) { if (!_canSendMessages) { cancelReply(); } + refreshScheduledToggle(); refreshSilentToggle(); refresh = true; } @@ -6135,6 +6191,7 @@ void HistoryWidget::handlePeerUpdate() { if (!_canSendMessages) { cancelReply(); } + refreshScheduledToggle(); refreshSilentToggle(); resize = true; } diff --git a/Telegram/SourceFiles/history/history_widget.h b/Telegram/SourceFiles/history/history_widget.h index 070205feeb..ee7937ad75 100644 --- a/Telegram/SourceFiles/history/history_widget.h +++ b/Telegram/SourceFiles/history/history_widget.h @@ -711,6 +711,9 @@ private: bool showInlineBotCancel() const; void refreshSilentToggle(); + void setupScheduledToggle(); + void refreshScheduledToggle(); + std::unique_ptr _contactStatus; object_ptr _send; @@ -728,6 +731,7 @@ private: object_ptr _botKeyboardHide; object_ptr _botCommandStart; object_ptr _silent = { nullptr }; + object_ptr _scheduled = { nullptr }; bool _cmdStartShown = false; object_ptr _field; bool _recording = false; diff --git a/Telegram/SourceFiles/history/view/history_view_list_widget.h b/Telegram/SourceFiles/history/view/history_view_list_widget.h index 47125454a1..83ff6e0d75 100644 --- a/Telegram/SourceFiles/history/view/history_view_list_widget.h +++ b/Telegram/SourceFiles/history/view/history_view_list_widget.h @@ -92,7 +92,8 @@ public: int shift = 0; }; - explicit ListMemento(Data::MessagePosition position) + explicit ListMemento( + Data::MessagePosition position = Data::UnreadMessagePosition) : _aroundPosition(position) { } void setAroundPosition(Data::MessagePosition position) { diff --git a/Telegram/SourceFiles/history/view/history_view_scheduled_section.cpp b/Telegram/SourceFiles/history/view/history_view_scheduled_section.cpp new file mode 100644 index 0000000000..7bd672442c --- /dev/null +++ b/Telegram/SourceFiles/history/view/history_view_scheduled_section.cpp @@ -0,0 +1,423 @@ +/* +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 "history/view/history_view_scheduled_section.h" + +#include "history/view/history_view_top_bar_widget.h" +#include "history/view/history_view_list_widget.h" +#include "history/history.h" +#include "history/history_item.h" +#include "ui/widgets/scroll_area.h" +#include "ui/widgets/shadow.h" +#include "ui/special_buttons.h" +#include "window/window_session_controller.h" +#include "core/event_filter.h" +#include "main/main_session.h" +#include "data/data_session.h" +#include "data/data_scheduled_messages.h" +#include "lang/lang_keys.h" +#include "styles/style_history.h" +#include "styles/style_window.h" +#include "styles/style_info.h" + +namespace HistoryView { + +object_ptr ScheduledMemento::createWidget( + QWidget *parent, + not_null controller, + Window::Column column, + const QRect &geometry) { + if (column == Window::Column::Third) { + return nullptr; + } + auto result = object_ptr(parent, controller, _history); + result->setInternalState(geometry, this); + return std::move(result); +} + +ScheduledWidget::ScheduledWidget( + QWidget *parent, + not_null controller, + not_null history) +: Window::SectionWidget(parent, controller) +, _history(history) +, _scroll(this, st::historyScroll, false) +, _topBar(this, controller) +, _topBarShadow(this) +, _scrollDown(_scroll, st::historyToDown) { + _topBar->setActiveChat(_history); + + _topBar->move(0, 0); + _topBar->resizeToWidth(width()); + _topBar->show(); + + _topBar->deleteSelectionRequest( + ) | rpl::start_with_next([=] { + confirmDeleteSelected(); + }, _topBar->lifetime()); + _topBar->clearSelectionRequest( + ) | rpl::start_with_next([=] { + clearSelected(); + }, _topBar->lifetime()); + + _topBarShadow->raise(); + updateAdaptiveLayout(); + subscribe(Adaptive::Changed(), [=] { updateAdaptiveLayout(); }); + + _inner = _scroll->setOwnedWidget(object_ptr( + this, + controller, + static_cast(this))); + _scroll->move(0, _topBar->height()); + _scroll->show(); + connect(_scroll, &Ui::ScrollArea::scrolled, [=] { onScroll(); }); + + setupScrollDownButton(); +} + +void ScheduledWidget::setupScrollDownButton() { + _scrollDown->setClickedCallback([=] { + scrollDownClicked(); + }); + Core::InstallEventFilter(_scrollDown, [=](not_null event) { + if (event->type() == QEvent::Wheel) { + return _scroll->viewportEvent(event); + } + return false; + }); + updateScrollDownVisibility(); +} + +void ScheduledWidget::scrollDownClicked() { + showAtPosition(Data::MaxMessagePosition); +} + +void ScheduledWidget::showAtPosition(Data::MessagePosition position) { + if (showAtPositionNow(position)) { + if (const auto highlight = base::take(_highlightMessageId)) { + _inner->highlightMessage(highlight); + } + } else { + _nextAnimatedScrollPosition = position; + _nextAnimatedScrollDelta = _inner->isBelowPosition(position) + ? -_scroll->height() + : _inner->isAbovePosition(position) + ? _scroll->height() + : 0; + auto memento = HistoryView::ListMemento(position); + _inner->restoreState(&memento); + } +} + +bool ScheduledWidget::showAtPositionNow(Data::MessagePosition position) { + if (const auto scrollTop = _inner->scrollTopForPosition(position)) { + const auto currentScrollTop = _scroll->scrollTop(); + const auto wanted = snap(*scrollTop, 0, _scroll->scrollTopMax()); + const auto fullDelta = (wanted - currentScrollTop); + const auto limit = _scroll->height(); + const auto scrollDelta = snap(fullDelta, -limit, limit); + _inner->animatedScrollTo( + wanted, + position, + scrollDelta, + (std::abs(fullDelta) > limit + ? HistoryView::ListWidget::AnimatedScroll::Part + : HistoryView::ListWidget::AnimatedScroll::Full)); + return true; + } + return false; +} + +void ScheduledWidget::updateScrollDownVisibility() { + if (animating()) { + return; + } + + const auto scrollDownIsVisible = [&]() -> std::optional { + const auto top = _scroll->scrollTop() + st::historyToDownShownAfter; + if (top < _scroll->scrollTopMax()) { + return true; + } + if (_inner->loadedAtBottomKnown()) { + return !_inner->loadedAtBottom(); + } + return std::nullopt; + }; + const auto scrollDownIsShown = scrollDownIsVisible(); + if (!scrollDownIsShown) { + return; + } + if (_scrollDownIsShown != *scrollDownIsShown) { + _scrollDownIsShown = *scrollDownIsShown; + _scrollDownShown.start( + [=] { updateScrollDownPosition(); }, + _scrollDownIsShown ? 0. : 1., + _scrollDownIsShown ? 1. : 0., + st::historyToDownDuration); + } +} + +void ScheduledWidget::updateScrollDownPosition() { + // _scrollDown is a child widget of _scroll, not me. + auto top = anim::interpolate( + 0, + _scrollDown->height() + st::historyToDownPosition.y(), + _scrollDownShown.value(_scrollDownIsShown ? 1. : 0.)); + _scrollDown->moveToRight( + st::historyToDownPosition.x(), + _scroll->height() - top); + auto shouldBeHidden = !_scrollDownIsShown && !_scrollDownShown.animating(); + if (shouldBeHidden != _scrollDown->isHidden()) { + _scrollDown->setVisible(!shouldBeHidden); + } +} + +void ScheduledWidget::scrollDownAnimationFinish() { + _scrollDownShown.stop(); + updateScrollDownPosition(); +} + +void ScheduledWidget::updateAdaptiveLayout() { + _topBarShadow->moveToLeft( + Adaptive::OneColumn() ? 0 : st::lineWidth, + _topBar->height()); +} + +not_null ScheduledWidget::history() const { + return _history; +} + +Dialogs::RowDescriptor ScheduledWidget::activeChat() const { + return { + _history, + FullMsgId(_history->channelId(), ShowAtUnreadMsgId) + }; +} + +QPixmap ScheduledWidget::grabForShowAnimation(const Window::SectionSlideParams ¶ms) { + if (params.withTopBarShadow) _topBarShadow->hide(); + auto result = Ui::GrabWidget(this); + if (params.withTopBarShadow) _topBarShadow->show(); + return result; +} + +void ScheduledWidget::doSetInnerFocus() { + _inner->setFocus(); +} + +bool ScheduledWidget::showInternal( + not_null memento, + const Window::SectionShow ¶ms) { + if (auto logMemento = dynamic_cast(memento.get())) { + if (logMemento->getHistory() == history()) { + restoreState(logMemento); + return true; + } + } + return false; +} + +void ScheduledWidget::setInternalState( + const QRect &geometry, + not_null memento) { + setGeometry(geometry); + Ui::SendPendingMoveResizeEvents(this); + restoreState(memento); +} + +std::unique_ptr ScheduledWidget::createMemento() { + auto result = std::make_unique(history()); + saveState(result.get()); + return std::move(result); +} + +void ScheduledWidget::saveState(not_null memento) { + _inner->saveState(memento->list()); +} + +void ScheduledWidget::restoreState(not_null memento) { + _inner->restoreState(memento->list()); +} + +void ScheduledWidget::resizeEvent(QResizeEvent *e) { + if (!width() || !height()) { + return; + } + updateControlsGeometry(); +} + +void ScheduledWidget::updateControlsGeometry() { + const auto contentWidth = width(); + + const auto newScrollTop = _scroll->isHidden() + ? std::nullopt + : base::make_optional(_scroll->scrollTop() + topDelta()); + _topBar->resizeToWidth(contentWidth); + _topBarShadow->resize(contentWidth, st::lineWidth); + + const auto bottom = height(); + const auto scrollHeight = bottom + - _topBar->height(); +// - _showNext->height(); + const auto scrollSize = QSize(contentWidth, scrollHeight); + if (_scroll->size() != scrollSize) { + _skipScrollEvent = true; + _scroll->resize(scrollSize); + _inner->resizeToWidth(scrollSize.width(), _scroll->height()); + _skipScrollEvent = false; + } + if (!_scroll->isHidden()) { + if (newScrollTop) { + _scroll->scrollToY(*newScrollTop); + } + updateInnerVisibleArea(); + } + + updateScrollDownPosition(); +} + +void ScheduledWidget::paintEvent(QPaintEvent *e) { + if (animating()) { + SectionWidget::paintEvent(e); + return; + } + if (Ui::skipPaintEvent(this, e)) { + return; + } + //if (hasPendingResizedItems()) { + // updateListSize(); + //} + + //auto ms = crl::now(); + //_historyDownShown.step(ms); + + SectionWidget::PaintBackground(this, e->rect()); +} + +void ScheduledWidget::onScroll() { + if (_skipScrollEvent) { + return; + } + updateInnerVisibleArea(); +} + +void ScheduledWidget::updateInnerVisibleArea() { + const auto scrollTop = _scroll->scrollTop(); + _inner->setVisibleTopBottom(scrollTop, scrollTop + _scroll->height()); + updateScrollDownVisibility(); +} + +void ScheduledWidget::showAnimatedHook( + const Window::SectionSlideParams ¶ms) { + _topBar->setAnimatingMode(true); + if (params.withTopBarShadow) _topBarShadow->show(); +} + +void ScheduledWidget::showFinishedHook() { + _topBar->setAnimatingMode(false); +} + +bool ScheduledWidget::wheelEventFromFloatPlayer(QEvent *e) { + return _scroll->viewportEvent(e); +} + +QRect ScheduledWidget::rectForFloatPlayer() const { + return mapToGlobal(_scroll->geometry()); +} + +Context ScheduledWidget::listContext() { + return Context::History; +} + +void ScheduledWidget::listScrollTo(int top) { + if (_scroll->scrollTop() != top) { + _scroll->scrollToY(top); + } else { + updateInnerVisibleArea(); + } +} + +void ScheduledWidget::listCancelRequest() { + controller()->showBackFromStack(); +} + +void ScheduledWidget::listDeleteRequest() { + confirmDeleteSelected(); +} + +rpl::producer ScheduledWidget::listSource( + Data::MessagePosition aroundId, + int limitBefore, + int limitAfter) { + const auto data = &controller()->session().data(); + return rpl::single( + rpl::empty_value() + ) | rpl::then( + data->scheduledMessages().updates(_history) + ) | rpl::map([=] { + return data->scheduledMessages().list(_history); + }); +} + +bool ScheduledWidget::listAllowsMultiSelect() { + return true; +} + +bool ScheduledWidget::listIsLessInOrder( + not_null first, + not_null second) { + return first->position() < second->position(); +} + +void ScheduledWidget::listSelectionChanged(SelectedItems &&items) { + HistoryView::TopBarWidget::SelectedState state; + state.count = items.size(); + for (const auto item : items) { + if (item.canForward) { + ++state.canForwardCount; + } + if (item.canDelete) { + ++state.canDeleteCount; + } + } + _topBar->showSelected(state); +} + +void ScheduledWidget::listVisibleItemsChanged(HistoryItemsList &&items) { +} + +std::optional ScheduledWidget::listUnreadBarView( + const std::vector> &elements) { + return std::nullopt; +} + +void ScheduledWidget::listContentRefreshed() { +} + +ClickHandlerPtr ScheduledWidget::listDateLink(not_null view) { + return nullptr; +} + +void ScheduledWidget::confirmDeleteSelected() { + auto items = _inner->getSelectedItems(); + if (items.empty()) { + return; + } + //const auto weak = make_weak(this); + //const auto box = Ui::show(Box(std::move(items))); + //box->setDeleteConfirmedCallback([=] { + // if (const auto strong = weak.data()) { + // strong->clearSelected(); + // } + //}); +} + +void ScheduledWidget::clearSelected() { + _inner->cancelSelection(); +} + +} // namespace HistoryView diff --git a/Telegram/SourceFiles/history/view/history_view_scheduled_section.h b/Telegram/SourceFiles/history/view/history_view_scheduled_section.h new file mode 100644 index 0000000000..21c9a13106 --- /dev/null +++ b/Telegram/SourceFiles/history/view/history_view_scheduled_section.h @@ -0,0 +1,161 @@ +/* +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 "window/section_widget.h" +#include "window/section_memento.h" +#include "history/view/history_view_list_widget.h" + +class History; + +namespace Notify { +struct PeerUpdate; +} // namespace Notify + +namespace Ui { +class ScrollArea; +class PlainShadow; +class FlatButton; +class HistoryDownButton; +} // namespace Ui + +namespace Profile { +class BackButton; +} // namespace Profile + +namespace HistoryView { + +class Element; +class TopBarWidget; +class ScheduledMemento; + +class ScheduledWidget final + : public Window::SectionWidget + , private ListDelegate { +public: + ScheduledWidget( + QWidget *parent, + not_null controller, + not_null history); + + not_null history() const; + Dialogs::RowDescriptor activeChat() const override; + + bool hasTopBarShadow() const override { + return true; + } + + QPixmap grabForShowAnimation( + const Window::SectionSlideParams ¶ms) override; + + bool showInternal( + not_null memento, + const Window::SectionShow ¶ms) override; + std::unique_ptr createMemento() override; + + void setInternalState( + const QRect &geometry, + not_null memento); + + // Float player interface. + bool wheelEventFromFloatPlayer(QEvent *e) override; + QRect rectForFloatPlayer() const override; + + // ListDelegate interface. + Context listContext() override; + void listScrollTo(int top) override; + void listCancelRequest() override; + void listDeleteRequest() override; + rpl::producer listSource( + Data::MessagePosition aroundId, + int limitBefore, + int limitAfter) override; + bool listAllowsMultiSelect() override; + bool listIsLessInOrder( + not_null first, + not_null second) override; + void listSelectionChanged(SelectedItems &&items) override; + void listVisibleItemsChanged(HistoryItemsList &&items) override; + std::optional listUnreadBarView( + const std::vector> &elements) override; + void listContentRefreshed() override; + ClickHandlerPtr listDateLink(not_null view) override; + +protected: + void resizeEvent(QResizeEvent *e) override; + void paintEvent(QPaintEvent *e) override; + + void showAnimatedHook( + const Window::SectionSlideParams ¶ms) override; + void showFinishedHook() override; + void doSetInnerFocus() override; + +private: + void onScroll(); + void updateInnerVisibleArea(); + void updateControlsGeometry(); + void updateAdaptiveLayout(); + void saveState(not_null memento); + void restoreState(not_null memento); + void showAtPosition(Data::MessagePosition position); + bool showAtPositionNow(Data::MessagePosition position); + + void setupScrollDownButton(); + void scrollDownClicked(); + void scrollDownAnimationFinish(); + void updateScrollDownVisibility(); + void updateScrollDownPosition(); + + void confirmDeleteSelected(); + void clearSelected(); + + const not_null _history; + object_ptr _scroll; + QPointer _inner; + object_ptr _topBar; + object_ptr _topBarShadow; + bool _skipScrollEvent = false; + + FullMsgId _highlightMessageId; + std::optional _nextAnimatedScrollPosition; + int _nextAnimatedScrollDelta = 0; + + Ui::Animations::Simple _scrollDownShown; + bool _scrollDownIsShown = false; + object_ptr _scrollDown; + +}; + +class ScheduledMemento : public Window::SectionMemento { +public: + ScheduledMemento(not_null history) + : _history(history) + , _list({}) { + } + + object_ptr createWidget( + QWidget *parent, + not_null controller, + Window::Column column, + const QRect &geometry) override; + + not_null getHistory() const { + return _history; + } + + not_null list() { + return &_list; + } + +private: + const not_null _history; + ListMemento _list; + +}; + +} // namespace HistoryScheduled diff --git a/Telegram/SourceFiles/mainwidget.cpp b/Telegram/SourceFiles/mainwidget.cpp index 50ab792059..b16ca13d45 100644 --- a/Telegram/SourceFiles/mainwidget.cpp +++ b/Telegram/SourceFiles/mainwidget.cpp @@ -22,6 +22,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_channel.h" #include "data/data_chat.h" #include "data/data_user.h" +#include "data/data_scheduled_messages.h" #include "ui/special_buttons.h" #include "ui/widgets/buttons.h" #include "ui/widgets/shadow.h" diff --git a/Telegram/gyp/telegram_sources.txt b/Telegram/gyp/telegram_sources.txt index fb48628b62..056a5a7797 100644 --- a/Telegram/gyp/telegram_sources.txt +++ b/Telegram/gyp/telegram_sources.txt @@ -330,6 +330,8 @@ <(src_loc)/history/view/history_view_message.cpp <(src_loc)/history/view/history_view_message.h <(src_loc)/history/view/history_view_object.h +<(src_loc)/history/view/history_view_scheduled_section.cpp +<(src_loc)/history/view/history_view_scheduled_section.h <(src_loc)/history/view/history_view_service_message.cpp <(src_loc)/history/view/history_view_service_message.h <(src_loc)/history/view/history_view_top_bar_widget.cpp