diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index c48da093a..beb351b41 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -3304,6 +3304,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_scheduled_send_now" = "Send message now?"; "lng_scheduled_send_now_many#one" = "Send {count} message now?"; "lng_scheduled_send_now_many#other" = "Send {count} messages now?"; +"lng_scheduled_video_tip_title" = "Improving video..."; +"lng_scheduled_video_tip_text" = "The video will be published after it's optimized for the bast viewing experience."; +"lng_scheduled_video_tip" = "Processing video may take a few minutes."; +"lng_scheduled_video_published" = "Video Published."; +"lng_scheduled_video_view" = "View"; "lng_replies_view#one" = "View {count} Reply"; "lng_replies_view#other" = "View {count} Replies"; diff --git a/Telegram/SourceFiles/api/api_updates.cpp b/Telegram/SourceFiles/api/api_updates.cpp index fc4644aae..9e4250c6e 100644 --- a/Telegram/SourceFiles/api/api_updates.cpp +++ b/Telegram/SourceFiles/api/api_updates.cpp @@ -332,6 +332,15 @@ void Updates::feedUpdateVector( session().data().sendHistoryChangeNotifications(); } +void Updates::checkForSentToScheduled(const MTPUpdates &updates) { + updates.match([&](const MTPDupdates &data) { + applyConvertToScheduledOnSend(data.vupdates(), true); + }, [&](const MTPDupdatesCombined &data) { + applyConvertToScheduledOnSend(data.vupdates(), true); + }, [](const auto &) { + }); +} + void Updates::feedMessageIds(const MTPVector &updates) { for (const auto &update : updates.v) { if (update.type() == mtpc_updateMessageID) { @@ -887,23 +896,35 @@ void Updates::mtpUpdateReceived(const MTPUpdates &updates) { } void Updates::applyConvertToScheduledOnSend( - const MTPVector &other) { + const MTPVector &other, + bool skipScheduledCheck) { for (const auto &update : other.v) { update.match([&](const MTPDupdateNewScheduledMessage &data) { - const auto id = IdFromMessage(data.vmessage()); + const auto &message = data.vmessage(); + const auto id = IdFromMessage(message); const auto scheduledMessages = &_session->scheduledMessages(); const auto scheduledId = scheduledMessages->localMessageId(id); for (const auto &updateId : other.v) { updateId.match([&](const MTPDupdateMessageID &dataId) { if (dataId.vid().v == id) { - const auto rand = dataId.vrandom_id().v; auto &owner = session().data(); + if (skipScheduledCheck) { + const auto peerId = PeerFromMessage(message); + const auto history = owner.historyLoaded(peerId); + if (history) { + _session->data().sentToScheduled({ + .history = history, + .scheduledId = scheduledId, + }); + } + return; + } + const auto rand = dataId.vrandom_id().v; const auto localId = owner.messageIdByRandomId(rand); if (const auto local = owner.message(localId)) { if (!local->isScheduled()) { - using Flag = Data::MessageUpdate::Flag; _session->data().sentToScheduled({ - .item = local, + .history = local->history(), .scheduledId = scheduledId, }); diff --git a/Telegram/SourceFiles/api/api_updates.h b/Telegram/SourceFiles/api/api_updates.h index 41bec419c..b1ecb870f 100644 --- a/Telegram/SourceFiles/api/api_updates.h +++ b/Telegram/SourceFiles/api/api_updates.h @@ -40,6 +40,8 @@ public: void applyUpdatesNoPtsCheck(const MTPUpdates &updates); void applyUpdateNoPtsCheck(const MTPUpdate &update); + void checkForSentToScheduled(const MTPUpdates &updates); + [[nodiscard]] int32 pts() const; void updateOnline(crl::time lastNonIdleTime = 0); @@ -131,7 +133,9 @@ private: // Doesn't call sendHistoryChangeNotifications itself. void feedUpdate(const MTPUpdate &update); - void applyConvertToScheduledOnSend(const MTPVector &other); + void applyConvertToScheduledOnSend( + const MTPVector &other, + bool skipScheduledCheck = false); void applyGroupCallParticipantUpdates(const MTPUpdates &updates); bool whenGetDiffChanged( diff --git a/Telegram/SourceFiles/apiwrap.cpp b/Telegram/SourceFiles/apiwrap.cpp index 4f23323ab..884ad6b6d 100644 --- a/Telegram/SourceFiles/apiwrap.cpp +++ b/Telegram/SourceFiles/apiwrap.cpp @@ -3329,6 +3329,7 @@ void ApiWrap::forwardMessages( } const auto requestType = Data::Histories::RequestType::Send; const auto idsCopy = localIds; + const auto scheduled = action.options.scheduled; histories.sendRequest(history, requestType, [=](Fn finish) { history->sendRequestId = request(MTPmessages_ForwardMessages( MTP_flags(sendFlags), @@ -3341,6 +3342,9 @@ void ApiWrap::forwardMessages( (sendAs ? sendAs->input : MTP_inputPeerEmpty()), Data::ShortcutIdToMTP(_session, action.options.shortcutId) )).done([=](const MTPUpdates &result) { + if (!scheduled) { + this->updates().checkForSentToScheduled(result); + } applyUpdates(result); if (shared && !--shared->requestsLeft) { shared->callback(); diff --git a/Telegram/SourceFiles/chat_helpers/chat_helpers.style b/Telegram/SourceFiles/chat_helpers/chat_helpers.style index a7da478ef..cb2a2f081 100644 --- a/Telegram/SourceFiles/chat_helpers/chat_helpers.style +++ b/Telegram/SourceFiles/chat_helpers/chat_helpers.style @@ -1519,3 +1519,22 @@ pickLocationChooseOnMap: RoundButton(defaultActiveButton) { sendGifBox: Box(defaultBox) { shadowIgnoreBottomSkip: true; } + +processingVideoTipMaxWidth: 364px; +processingVideoTipShift: 10px; +processingVideoToast: Toast(defaultToast) { + minWidth: 32px; + maxWidth: 380px; + padding: margins(19px, 17px, 19px, 16px); +} +processingVideoPreviewSkip: 0px; +processingVideoView: RoundButton(defaultActiveButton) { + width: -24px; + height: 68px; + textTop: 26px; + textFg: mediaviewTextLinkFg; + textFgOver: mediaviewTextLinkFg; + textBg: transparent; + textBgOver: transparent; + ripple: emptyRippleAnimation; +} diff --git a/Telegram/SourceFiles/data/components/scheduled_messages.cpp b/Telegram/SourceFiles/data/components/scheduled_messages.cpp index 9e69dd35b..460a4df58 100644 --- a/Telegram/SourceFiles/data/components/scheduled_messages.cpp +++ b/Telegram/SourceFiles/data/components/scheduled_messages.cpp @@ -343,10 +343,20 @@ void ScheduledMessages::apply( if (i == end(_data)) { return; } - for (const auto &id : update.vmessages().v) { + const auto sent = update.vsent_messages(); + const auto &ids = update.vmessages().v; + for (auto k = 0, count = int(ids.size()); k != count; ++k) { + const auto id = ids[k].v; const auto &list = i->second; - const auto j = list.itemById.find(id.v); + const auto j = list.itemById.find(id); if (j != end(list.itemById)) { + if (sent && k < sent->v.size()) { + const auto &sentId = sent->v[k]; + _session->data().sentFromScheduled({ + .item = j->second, + .sentId = sentId.v, + }); + } j->second->destroy(); i = _data.find(history); if (i == end(_data)) { diff --git a/Telegram/SourceFiles/data/data_session.cpp b/Telegram/SourceFiles/data/data_session.cpp index b45bb0bed..d8782132e 100644 --- a/Telegram/SourceFiles/data/data_session.cpp +++ b/Telegram/SourceFiles/data/data_session.cpp @@ -4821,6 +4821,14 @@ rpl::producer Session::sentToScheduled() const { return _sentToScheduled.events(); } +void Session::sentFromScheduled(SentFromScheduled value) { + _sentFromScheduled.fire(std::move(value)); +} + +rpl::producer Session::sentFromScheduled() const { + return _sentFromScheduled.events(); +} + void Session::clearLocalStorage() { _cache->close(); _cache->clear(); diff --git a/Telegram/SourceFiles/data/data_session.h b/Telegram/SourceFiles/data/data_session.h index b1b550d37..b9d048062 100644 --- a/Telegram/SourceFiles/data/data_session.h +++ b/Telegram/SourceFiles/data/data_session.h @@ -90,9 +90,13 @@ struct GiftUpdate { }; struct SentToScheduled { - not_null item; + not_null history; MsgId scheduledId = 0; }; +struct SentFromScheduled { + not_null item; + MsgId sentId = 0; +}; class Session final { public: @@ -798,6 +802,8 @@ public: void sentToScheduled(SentToScheduled value); [[nodiscard]] rpl::producer sentToScheduled() const; + void sentFromScheduled(SentFromScheduled value); + [[nodiscard]] rpl::producer sentFromScheduled() const; void clearLocalStorage(); @@ -972,6 +978,7 @@ private: rpl::event_stream<> _unreadBadgeChanges; rpl::event_stream _repliesReadTillUpdates; rpl::event_stream _sentToScheduled; + rpl::event_stream _sentFromScheduled; Dialogs::MainList _chatsList; Dialogs::IndexedList _contactsList; diff --git a/Telegram/SourceFiles/history/history_widget.cpp b/Telegram/SourceFiles/history/history_widget.cpp index 31d2a1362..16b0035e5 100644 --- a/Telegram/SourceFiles/history/history_widget.cpp +++ b/Telegram/SourceFiles/history/history_widget.cpp @@ -725,19 +725,33 @@ HistoryWidget::HistoryWidget( session().data().sentToScheduled( ) | rpl::start_with_next([=](const Data::SentToScheduled &value) { - if (value.item->history() == _history) { - const auto history = value.item->history(); + const auto history = value.history; + if (history == _history) { const auto id = value.scheduledId; crl::on_main(this, [=] { - controller->showSection( - std::make_shared( - history, - id)); + if (history == _history) { + controller->showSection( + std::make_shared( + history, + id)); + } }); return; } }, lifetime()); + session().data().sentFromScheduled( + ) | rpl::start_with_next([=](const Data::SentFromScheduled &value) { + if (value.item->awaitingVideoProcessing() + && !_sentFromScheduledTip + && HistoryView::ShowScheduledVideoPublished( + controller, + value, + crl::guard(this, [=] { _sentFromScheduledTip = false; }))) { + _sentFromScheduledTip = true; + } + }, lifetime()); + using MediaSwitch = Media::Player::Instance::Switch; Media::Player::instance()->switchToNextEvents( ) | rpl::filter([=](const MediaSwitch &pair) { diff --git a/Telegram/SourceFiles/history/history_widget.h b/Telegram/SourceFiles/history/history_widget.h index 4b621626d..86f4c9617 100644 --- a/Telegram/SourceFiles/history/history_widget.h +++ b/Telegram/SourceFiles/history/history_widget.h @@ -702,6 +702,7 @@ private: bool _preserveScrollTop = false; bool _repaintFieldScheduled = false; + bool _sentFromScheduledTip = false; mtpRequestId _saveEditMsgRequestId = 0; diff --git a/Telegram/SourceFiles/history/view/history_view_scheduled_section.cpp b/Telegram/SourceFiles/history/view/history_view_scheduled_section.cpp index 0d56c275b..0b20db1b7 100644 --- a/Telegram/SourceFiles/history/view/history_view_scheduled_section.cpp +++ b/Telegram/SourceFiles/history/view/history_view_scheduled_section.cpp @@ -16,10 +16,15 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "history/history_drag_area.h" #include "history/history_item_helpers.h" // GetErrorTextForSending. #include "menu/menu_send.h" // SendMenu::Type. +#include "ui/widgets/buttons.h" +#include "ui/widgets/tooltip.h" #include "ui/widgets/scroll_area.h" #include "ui/widgets/shadow.h" #include "ui/chat/chat_style.h" #include "ui/text/text_utilities.h" +#include "ui/toast/toast.h" +#include "ui/dynamic_image.h" +#include "ui/dynamic_thumbnails.h" #include "ui/ui_utility.h" #include "api/api_editing.h" #include "api/api_sending.h" @@ -34,7 +39,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "core/mime_type.h" #include "chat_helpers/tabbed_selector.h" #include "main/main_session.h" +#include "mainwindow.h" #include "data/components/scheduled_messages.h" +#include "data/data_document.h" +#include "data/data_file_origin.h" #include "data/data_forum.h" #include "data/data_forum_topic.h" #include "data/data_session.h" @@ -55,6 +63,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include namespace HistoryView { +namespace { + +constexpr auto kVideoProcessingInfoDuration = 4 * crl::time(1000); + +} // namespace ScheduledMemento::ScheduledMemento( not_null history, @@ -104,33 +117,33 @@ ScheduledWidget::ScheduledWidget( not_null controller, not_null history, const Data::ForumTopic *forumTopic) - : Window::SectionWidget(parent, controller, history->peer) - , WindowListDelegate(controller) - , _show(controller->uiShow()) - , _history(history) - , _forumTopic(forumTopic) - , _scroll( - this, - controller->chatStyle()->value(lifetime(), st::historyScroll), - false) - , _topBar(this, controller) - , _topBarShadow(this) - , _composeControls(std::make_unique( - this, - ComposeControlsDescriptor{ - .show = controller->uiShow(), - .unavailableEmojiPasted = [=](not_null emoji) { - listShowPremiumToast(emoji); - }, - .mode = ComposeControls::Mode::Scheduled, - .sendMenuDetails = [] { return SendMenu::Details(); }, - .regularWindow = controller, - .stickerOrEmojiChosen = controller->stickerOrEmojiChosen(), - })) - , _cornerButtons( - _scroll.data(), - controller->chatStyle(), - static_cast(this)) { +: Window::SectionWidget(parent, controller, history->peer) +, WindowListDelegate(controller) +, _show(controller->uiShow()) +, _history(history) +, _forumTopic(forumTopic) +, _scroll( + this, + controller->chatStyle()->value(lifetime(), st::historyScroll), + false) +, _topBar(this, controller) +, _topBarShadow(this) +, _composeControls(std::make_unique( + this, + ComposeControlsDescriptor{ + .show = controller->uiShow(), + .unavailableEmojiPasted = [=](not_null emoji) { + listShowPremiumToast(emoji); + }, + .mode = ComposeControls::Mode::Scheduled, + .sendMenuDetails = [] { return SendMenu::Details(); }, + .regularWindow = controller, + .stickerOrEmojiChosen = controller->stickerOrEmojiChosen(), + })) +, _cornerButtons( + _scroll.data(), + controller->chatStyle(), + static_cast(this)) { controller->chatStyle()->paletteChanged( ) | rpl::start_with_next([=] { _scroll->updateBars(); @@ -1070,6 +1083,24 @@ void ScheduledWidget::saveState(not_null memento) { void ScheduledWidget::restoreState(not_null memento) { _inner->restoreState(memento->list()); + if (const auto id = memento->sentToScheduledId()) { + const auto item = _history->owner().message(_history->peer, id); + if (item) { + controller()->showToast({ + .title = tr::lng_scheduled_video_tip_title(tr::now), + .text = { tr::lng_scheduled_video_tip_text(tr::now) }, + .attach = RectPart::Top, + .duration = kVideoProcessingInfoDuration, + }); + clearProcessingVideoTracking(false); + _processingVideoPosition = item->position(); + _processingVideoTipTimer.setCallback([=] { + _processingVideoCanShow = true; + updateInnerVisibleArea(); + }); + _processingVideoTipTimer.callOnce(kVideoProcessingInfoDuration); + } + } } void ScheduledWidget::resizeEvent(QResizeEvent *e) { @@ -1141,9 +1172,153 @@ void ScheduledWidget::updateInnerVisibleArea() { checkReplyReturns(); } const auto scrollTop = _scroll->scrollTop(); - _inner->setVisibleTopBottom(scrollTop, scrollTop + _scroll->height()); + const auto scrollBottom = scrollTop + _scroll->height(); + _inner->setVisibleTopBottom(scrollTop, scrollBottom); _cornerButtons.updateJumpDownVisibility(); _cornerButtons.updateUnreadThingsVisibility(); + if (!_processingVideoLifetime) { + if (const auto &position = _processingVideoPosition) { + if (const auto view = _inner->viewByPosition(position)) { + initProcessingVideoView(view); + } + } + } + checkProcessingVideoTooltip(scrollTop, scrollBottom); +} + +void ScheduledWidget::initProcessingVideoView(not_null view) { + _processingVideoView = view; + + controller()->session().data().sentFromScheduled( + ) | rpl::start_with_next([=](const Data::SentFromScheduled &value) { + if (value.item->position() == _processingVideoPosition) { + controller()->showPeerHistory( + value.item->history(), + Window::SectionShow::Way::Backward, + value.sentId); + } + }, _processingVideoLifetime); + + controller()->session().data().viewRemoved( + ) | rpl::start_with_next([=](not_null view) { + if (view == _processingVideoView.get()) { + const auto position = _processingVideoPosition; + if (const auto now = _inner->viewByPosition(position)) { + _processingVideoView = now; + updateProcessingVideoTooltipPosition(); + } else { + clearProcessingVideoTracking(true); + } + } + }, _processingVideoLifetime); + + controller()->session().data().viewResizeRequest( + ) | rpl::start_with_next([this](not_null view) { + if (view->delegate() == _inner.data()) { + if (!_processingVideoUpdateScheduled) { + if (const auto tooltip = _processingVideoTooltip.get()) { + _processingVideoUpdateScheduled = true; + crl::on_main(tooltip, [=] { + _processingVideoUpdateScheduled = false; + updateProcessingVideoTooltipPosition(); + }); + } + } + } + }, _processingVideoLifetime); +} + +void ScheduledWidget::clearProcessingVideoTracking(bool fast) { + if (const auto tooltip = _processingVideoTooltip.release()) { + tooltip->toggleAnimated(false); + } + _processingVideoPosition = {}; + if (const auto tooltip = _processingVideoTooltip.release()) { + if (fast) { + tooltip->toggleFast(false); + } else { + tooltip->toggleAnimated(false); + } + } + _processingVideoTooltipShown = false; + _processingVideoCanShow = false; + _processingVideoView = nullptr; + _processingVideoTipTimer.cancel(); + _processingVideoLifetime.destroy(); +} + +void ScheduledWidget::checkProcessingVideoTooltip( + int visibleTop, + int visibleBottom) { + if (_processingVideoTooltip + || _processingVideoTooltipShown + || !_processingVideoCanShow) { + return; + } + const auto view = _processingVideoView.get(); + if (!view) { + _processingVideoCanShow = false; + return; + } + const auto rect = view->effectIconGeometry(); + if (rect.top() > visibleTop + && rect.top() + rect.height() <= visibleBottom) { + showProcessingVideoTooltip(); + } +} + +void ScheduledWidget::updateProcessingVideoTooltipPosition() { + const auto tooltip = _processingVideoTooltip.get(); + if (!tooltip) { + return; + } + const auto view = _processingVideoView.get(); + if (!view) { + clearProcessingVideoTracking(true); + return; + } + const auto shift = view->skipBlockWidth() / 2; + const auto rect = view->effectIconGeometry().translated(shift, 0); + const auto countPosition = [=](QSize size) { + const auto origin = rect.bottomLeft(); + return origin - QPoint( + size.width() / 2, + size.height() + st::processingVideoTipShift); + }; + tooltip->pointAt(rect, RectPart::Top, countPosition); +} + +void ScheduledWidget::showProcessingVideoTooltip() { + _processingVideoTooltipShown = true; + _processingVideoTooltip = std::make_unique( + _inner.data(), + Ui::MakeNiceTooltipLabel( + _inner.data(), + tr::lng_scheduled_video_tip(Ui::Text::WithEntities), + st::processingVideoTipMaxWidth, + st::defaultImportantTooltipLabel), + st::defaultImportantTooltip); + const auto tooltip = _processingVideoTooltip.get(); + const auto weak = QPointer(tooltip); + const auto destroy = [=] { + delete weak.data(); + }; + tooltip->setAttribute(Qt::WA_TransparentForMouseEvents); + tooltip->setHiddenCallback([=] { + const auto tip = _processingVideoTooltip.get(); + if (tooltip == tip) { + _processingVideoTooltip.release(); + } + crl::on_main(tip, [=] { + delete tip; + }); + }); + updateProcessingVideoTooltipPosition(); + tooltip->toggleAnimated(true); + _processingVideoTipTimer.setCallback(crl::guard(tooltip, [=] { + tooltip->toggleAnimated(false); + })); + _processingVideoTipTimer.callOnce(kVideoProcessingInfoDuration); } void ScheduledWidget::showAnimatedHook( @@ -1495,4 +1670,114 @@ void ScheduledWidget::setupDragArea() { areas.photo->setDroppedCallback(droppedCallback(true)); } +bool ShowScheduledVideoPublished( + not_null controller, + const Data::SentFromScheduled &info, + Fn hidden) { + if (!controller->widget()->isActive()) { + return false; + } + const auto media = info.item->media(); + const auto document = media ? media->document() : nullptr; + if (!document->isVideoFile()) { + return false; + } + const auto history = info.item->history(); + const auto itemId = info.sentId; + + const auto text = tr::lng_scheduled_video_published( + tr::now, + Ui::Text::Bold); + const auto &st = st::processingVideoToast; + const auto skip = st::processingVideoPreviewSkip; + const auto size = st.style.font->height * 2; + const auto view = tr::lng_scheduled_video_view(tr::now); + const auto additional = QMargins( + skip + size, + 0, + (st::processingVideoView.style.font->width(view) + - (st::processingVideoView.width / 2)), + 0); + + const auto parent = controller->uiShow()->toastParent(); + const auto weak = Ui::Toast::Show(parent, Ui::Toast::Config{ + .text = text, + .padding = rpl::single(additional), + .st = &st, + .attach = RectPart::Top, + .acceptinput = true, + .duration = kVideoProcessingInfoDuration, + }); + const auto strong = weak.get(); + if (!strong) { + return false; + } + const auto widget = strong->widget(); + const auto hideToast = [weak] { + if (const auto strong = weak.get()) { + strong->hideAnimated(); + } + }; + + const auto clickableBackground = Ui::CreateChild( + widget.get()); + clickableBackground->setPointerCursor(false); + clickableBackground->setAcceptBoth(); + clickableBackground->show(); + clickableBackground->addClickHandler([=](Qt::MouseButton button) { + if (button == Qt::RightButton) { + hideToast(); + } + }); + + const auto button = Ui::CreateChild( + widget.get(), + rpl::single(view), + st::processingVideoView); + button->setTextTransform(Ui::RoundButton::TextTransform::NoTransform); + button->show(); + rpl::combine( + widget->sizeValue(), + button->sizeValue() + ) | rpl::start_with_next([=](QSize outer, QSize inner) { + button->moveToRight( + 0, + (outer.height() - inner.height()) / 2, + outer.width()); + clickableBackground->resize(outer); + }, widget->lifetime()); + const auto preview = Ui::CreateChild(widget.get()); + preview->moveToLeft(skip, skip); + preview->resize(size, size); + preview->show(); + + const auto thumbnail = Ui::MakeDocumentThumbnail(document, FullMsgId( + history->peer->id, + itemId)); + thumbnail->subscribeToUpdates([=] { + preview->update(); + }); + preview->paintRequest( + ) | rpl::start_with_next([=] { + auto p = QPainter(preview); + const auto image = Images::Round( + thumbnail->image(size), + ImageRoundRadius::Small); + p.drawImage(QRect(0, 0, size, size), image); + }, preview->lifetime()); + + button->setClickedCallback([=] { + controller->showPeerHistory( + history, + Window::SectionShow::Way::Forward, + itemId); + hideToast(); + }); + + if (hidden) { + widget->lifetime().add(std::move(hidden)); + } + return true; +} + } // namespace HistoryView diff --git a/Telegram/SourceFiles/history/view/history_view_scheduled_section.h b/Telegram/SourceFiles/history/view/history_view_scheduled_section.h index b761c7a40..a2e3a5438 100644 --- a/Telegram/SourceFiles/history/view/history_view_scheduled_section.h +++ b/Telegram/SourceFiles/history/view/history_view_scheduled_section.h @@ -21,6 +21,10 @@ namespace ChatHelpers { class Show; } // namespace ChatHelpers +namespace Data { +struct SentFromScheduled; +} // namespace Data + namespace SendMenu { struct Details; } // namespace SendMenu @@ -37,6 +41,7 @@ class PlainShadow; class FlatButton; struct PreparedList; class SendFilesWay; +class ImportantTooltip; } // namespace Ui namespace Profile { @@ -51,6 +56,10 @@ namespace HistoryView::Controls { struct VoiceToSend; } // namespace HistoryView::Controls +namespace Window { +class SessionController; +} // namespace Window + namespace HistoryView { class Element; @@ -199,6 +208,12 @@ private: Data::MessagePosition position, FullMsgId originId = {}); + void initProcessingVideoView(not_null view); + void checkProcessingVideoTooltip(int visibleTop, int visibleBottom); + void showProcessingVideoTooltip(); + void updateProcessingVideoTooltipPosition(); + void clearProcessingVideoTracking(bool fast); + void setupComposeControls(); void setupDragArea(); @@ -277,7 +292,16 @@ private: std::unique_ptr _composeControls; bool _skipScrollEvent = false; + Data::MessagePosition _processingVideoPosition; + base::weak_ptr _processingVideoView; + rpl::lifetime _processingVideoLifetime; + std::unique_ptr _stickerToast; + std::unique_ptr _processingVideoTooltip; + base::Timer _processingVideoTipTimer; + bool _processingVideoUpdateScheduled = false; + bool _processingVideoTooltipShown = false; + bool _processingVideoCanShow = false; CornerButtons _cornerButtons; @@ -299,20 +323,29 @@ public: Window::Column column, const QRect &geometry) override; - not_null getHistory() const { + [[nodiscard]] not_null getHistory() const { return _history; } - not_null list() { + [[nodiscard]] not_null list() { return &_list; } + [[nodiscard]] MsgId sentToScheduledId() const { + return _sentToScheduledId; + } + private: const not_null _history; const Data::ForumTopic *_forumTopic; ListMemento _list; - MsgId _sentToScheduledId; + MsgId _sentToScheduledId = 0; }; +bool ShowScheduledVideoPublished( + not_null controller, + const Data::SentFromScheduled &info, + Fn hidden = nullptr); + } // namespace HistoryView diff --git a/Telegram/SourceFiles/history/view/media/history_view_gif.cpp b/Telegram/SourceFiles/history/view/media/history_view_gif.cpp index 6a3aebfcb..beb58b982 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_gif.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_gif.cpp @@ -1982,10 +1982,12 @@ bool Gif::dataLoaded() const { } bool Gif::needInfoDisplay() const { - if (_parent->data()->isFakeAboutView()) { + const auto item = _parent->data(); + if (item->isFakeAboutView()) { return false; } - return _parent->data()->isSending() + return item->isSending() + || item->awaitingVideoProcessing() || _data->uploading() || _parent->isUnderCursor() || (_parent->delegate()->elementContext() == Context::ChatPreview) diff --git a/Telegram/SourceFiles/history/view/media/history_view_media_grouped.cpp b/Telegram/SourceFiles/history/view/media/history_view_media_grouped.cpp index 5cf80255f..2b7d2cf99 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_media_grouped.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_media_grouped.cpp @@ -893,9 +893,11 @@ bool GroupedMedia::computeNeedBubble() const { } bool GroupedMedia::needInfoDisplay() const { + const auto item = _parent->data(); return (_mode != Mode::Column) - && (_parent->data()->isSending() - || _parent->data()->hasFailed() + && (item->isSending() + || item->awaitingVideoProcessing() + || item->hasFailed() || _parent->isUnderCursor() || (_parent->delegate()->elementContext() == Context::ChatPreview) || _parent->isLastAndSelfMessage()); diff --git a/Telegram/SourceFiles/info/profile/info_profile_actions.cpp b/Telegram/SourceFiles/info/profile/info_profile_actions.cpp index a4a84021c..40611897a 100644 --- a/Telegram/SourceFiles/info/profile/info_profile_actions.cpp +++ b/Telegram/SourceFiles/info/profile/info_profile_actions.cpp @@ -820,7 +820,7 @@ rpl::producer AddCurrencyAction( ) | rpl::start_with_error_done([=](const QString &error) { currencyLoadLifetime->destroy(); }, [=] { - if (const auto strong = weak.get()) { + if (const auto strong = weak.data()) { state->balance = currencyLoad->data().currentBalance; currencyLoadLifetime->destroy(); } diff --git a/Telegram/SourceFiles/ui/dynamic_thumbnails.cpp b/Telegram/SourceFiles/ui/dynamic_thumbnails.cpp index 67cda874c..f6538867a 100644 --- a/Telegram/SourceFiles/ui/dynamic_thumbnails.cpp +++ b/Telegram/SourceFiles/ui/dynamic_thumbnails.cpp @@ -59,9 +59,9 @@ private: }; -class StoryThumbnail : public DynamicImage { +class MediaThumbnail : public DynamicImage { public: - explicit StoryThumbnail(Data::FileOrigin origin, bool forceRound); + explicit MediaThumbnail(Data::FileOrigin origin, bool forceRound); QImage image(int size) override; void subscribeToUpdates(Fn callback) override; @@ -89,7 +89,7 @@ private: }; -class PhotoThumbnail final : public StoryThumbnail { +class PhotoThumbnail final : public MediaThumbnail { public: PhotoThumbnail( not_null photo, @@ -108,7 +108,7 @@ private: }; -class VideoThumbnail final : public StoryThumbnail { +class VideoThumbnail final : public MediaThumbnail { public: VideoThumbnail( not_null video, @@ -304,12 +304,12 @@ void PeerUserpic::processNewPhoto() { }, _subscribed->downloadLifetime); } -StoryThumbnail::StoryThumbnail(Data::FileOrigin origin, bool forceRound) +MediaThumbnail::MediaThumbnail(Data::FileOrigin origin, bool forceRound) : _origin(origin) , _forceRound(forceRound) { } -QImage StoryThumbnail::image(int size) { +QImage MediaThumbnail::image(int size) { const auto ratio = style::DevicePixelRatio(); if (_prepared.width() != size * ratio) { if (_full.isNull()) { @@ -333,7 +333,7 @@ QImage StoryThumbnail::image(int size) { return _prepared; } -void StoryThumbnail::subscribeToUpdates(Fn callback) { +void MediaThumbnail::subscribeToUpdates(Fn callback) { _subscription.destroy(); if (!callback) { clear(); @@ -363,11 +363,11 @@ void StoryThumbnail::subscribeToUpdates(Fn callback) { } } -Data::FileOrigin StoryThumbnail::origin() const { +Data::FileOrigin MediaThumbnail::origin() const { return _origin; } -bool StoryThumbnail::forceRound() const { +bool MediaThumbnail::forceRound() const { return _forceRound; } @@ -375,7 +375,7 @@ PhotoThumbnail::PhotoThumbnail( not_null photo, Data::FileOrigin origin, bool forceRound) -: StoryThumbnail(origin, forceRound) +: MediaThumbnail(origin, forceRound) , _photo(photo) { } @@ -387,7 +387,7 @@ Main::Session &PhotoThumbnail::session() { return _photo->session(); } -StoryThumbnail::Thumb PhotoThumbnail::loaded(Data::FileOrigin origin) { +MediaThumbnail::Thumb PhotoThumbnail::loaded(Data::FileOrigin origin) { if (!_media) { _media = _photo->createMediaView(); _media->wanted(Data::PhotoSize::Small, origin); @@ -406,7 +406,7 @@ VideoThumbnail::VideoThumbnail( not_null video, Data::FileOrigin origin, bool forceRound) -: StoryThumbnail(origin, forceRound) +: MediaThumbnail(origin, forceRound) , _video(video) { } @@ -418,7 +418,7 @@ Main::Session &VideoThumbnail::session() { return _video->session(); } -StoryThumbnail::Thumb VideoThumbnail::loaded(Data::FileOrigin origin) { +MediaThumbnail::Thumb VideoThumbnail::loaded(Data::FileOrigin origin) { if (!_media) { _media = _video->createMediaView(); _media->thumbnailWanted(origin); diff --git a/Telegram/SourceFiles/ui/dynamic_thumbnails.h b/Telegram/SourceFiles/ui/dynamic_thumbnails.h index 3f4bd4337..08ae74052 100644 --- a/Telegram/SourceFiles/ui/dynamic_thumbnails.h +++ b/Telegram/SourceFiles/ui/dynamic_thumbnails.h @@ -14,6 +14,7 @@ class PhotoData; namespace Data { class Story; class Session; +struct FileOrigin; } // namespace Data namespace Ui { @@ -33,7 +34,6 @@ class DynamicImage; [[nodiscard]] std::shared_ptr MakeEmojiThumbnail( not_null owner, const QString &data); - [[nodiscard]] std::shared_ptr MakePhotoThumbnail( not_null photo, FullMsgId fullId);