Show toast/tooltip info on video processing.

This commit is contained in:
John Preston 2024-10-29 22:06:24 +04:00
parent 3137c9f3f7
commit 66be2ac6ca
17 changed files with 480 additions and 65 deletions

View file

@ -3304,6 +3304,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_scheduled_send_now" = "Send message now?"; "lng_scheduled_send_now" = "Send message now?";
"lng_scheduled_send_now_many#one" = "Send {count} message now?"; "lng_scheduled_send_now_many#one" = "Send {count} message now?";
"lng_scheduled_send_now_many#other" = "Send {count} messages 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#one" = "View {count} Reply";
"lng_replies_view#other" = "View {count} Replies"; "lng_replies_view#other" = "View {count} Replies";

View file

@ -332,6 +332,15 @@ void Updates::feedUpdateVector(
session().data().sendHistoryChangeNotifications(); 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<MTPUpdate> &updates) { void Updates::feedMessageIds(const MTPVector<MTPUpdate> &updates) {
for (const auto &update : updates.v) { for (const auto &update : updates.v) {
if (update.type() == mtpc_updateMessageID) { if (update.type() == mtpc_updateMessageID) {
@ -887,23 +896,35 @@ void Updates::mtpUpdateReceived(const MTPUpdates &updates) {
} }
void Updates::applyConvertToScheduledOnSend( void Updates::applyConvertToScheduledOnSend(
const MTPVector<MTPUpdate> &other) { const MTPVector<MTPUpdate> &other,
bool skipScheduledCheck) {
for (const auto &update : other.v) { for (const auto &update : other.v) {
update.match([&](const MTPDupdateNewScheduledMessage &data) { 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 scheduledMessages = &_session->scheduledMessages();
const auto scheduledId = scheduledMessages->localMessageId(id); const auto scheduledId = scheduledMessages->localMessageId(id);
for (const auto &updateId : other.v) { for (const auto &updateId : other.v) {
updateId.match([&](const MTPDupdateMessageID &dataId) { updateId.match([&](const MTPDupdateMessageID &dataId) {
if (dataId.vid().v == id) { if (dataId.vid().v == id) {
const auto rand = dataId.vrandom_id().v;
auto &owner = session().data(); 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); const auto localId = owner.messageIdByRandomId(rand);
if (const auto local = owner.message(localId)) { if (const auto local = owner.message(localId)) {
if (!local->isScheduled()) { if (!local->isScheduled()) {
using Flag = Data::MessageUpdate::Flag;
_session->data().sentToScheduled({ _session->data().sentToScheduled({
.item = local, .history = local->history(),
.scheduledId = scheduledId, .scheduledId = scheduledId,
}); });

View file

@ -40,6 +40,8 @@ public:
void applyUpdatesNoPtsCheck(const MTPUpdates &updates); void applyUpdatesNoPtsCheck(const MTPUpdates &updates);
void applyUpdateNoPtsCheck(const MTPUpdate &update); void applyUpdateNoPtsCheck(const MTPUpdate &update);
void checkForSentToScheduled(const MTPUpdates &updates);
[[nodiscard]] int32 pts() const; [[nodiscard]] int32 pts() const;
void updateOnline(crl::time lastNonIdleTime = 0); void updateOnline(crl::time lastNonIdleTime = 0);
@ -131,7 +133,9 @@ private:
// Doesn't call sendHistoryChangeNotifications itself. // Doesn't call sendHistoryChangeNotifications itself.
void feedUpdate(const MTPUpdate &update); void feedUpdate(const MTPUpdate &update);
void applyConvertToScheduledOnSend(const MTPVector<MTPUpdate> &other); void applyConvertToScheduledOnSend(
const MTPVector<MTPUpdate> &other,
bool skipScheduledCheck = false);
void applyGroupCallParticipantUpdates(const MTPUpdates &updates); void applyGroupCallParticipantUpdates(const MTPUpdates &updates);
bool whenGetDiffChanged( bool whenGetDiffChanged(

View file

@ -3329,6 +3329,7 @@ void ApiWrap::forwardMessages(
} }
const auto requestType = Data::Histories::RequestType::Send; const auto requestType = Data::Histories::RequestType::Send;
const auto idsCopy = localIds; const auto idsCopy = localIds;
const auto scheduled = action.options.scheduled;
histories.sendRequest(history, requestType, [=](Fn<void()> finish) { histories.sendRequest(history, requestType, [=](Fn<void()> finish) {
history->sendRequestId = request(MTPmessages_ForwardMessages( history->sendRequestId = request(MTPmessages_ForwardMessages(
MTP_flags(sendFlags), MTP_flags(sendFlags),
@ -3341,6 +3342,9 @@ void ApiWrap::forwardMessages(
(sendAs ? sendAs->input : MTP_inputPeerEmpty()), (sendAs ? sendAs->input : MTP_inputPeerEmpty()),
Data::ShortcutIdToMTP(_session, action.options.shortcutId) Data::ShortcutIdToMTP(_session, action.options.shortcutId)
)).done([=](const MTPUpdates &result) { )).done([=](const MTPUpdates &result) {
if (!scheduled) {
this->updates().checkForSentToScheduled(result);
}
applyUpdates(result); applyUpdates(result);
if (shared && !--shared->requestsLeft) { if (shared && !--shared->requestsLeft) {
shared->callback(); shared->callback();

View file

@ -1519,3 +1519,22 @@ pickLocationChooseOnMap: RoundButton(defaultActiveButton) {
sendGifBox: Box(defaultBox) { sendGifBox: Box(defaultBox) {
shadowIgnoreBottomSkip: true; 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;
}

View file

@ -343,10 +343,20 @@ void ScheduledMessages::apply(
if (i == end(_data)) { if (i == end(_data)) {
return; 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 &list = i->second;
const auto j = list.itemById.find(id.v); const auto j = list.itemById.find(id);
if (j != end(list.itemById)) { 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(); j->second->destroy();
i = _data.find(history); i = _data.find(history);
if (i == end(_data)) { if (i == end(_data)) {

View file

@ -4821,6 +4821,14 @@ rpl::producer<SentToScheduled> Session::sentToScheduled() const {
return _sentToScheduled.events(); return _sentToScheduled.events();
} }
void Session::sentFromScheduled(SentFromScheduled value) {
_sentFromScheduled.fire(std::move(value));
}
rpl::producer<SentFromScheduled> Session::sentFromScheduled() const {
return _sentFromScheduled.events();
}
void Session::clearLocalStorage() { void Session::clearLocalStorage() {
_cache->close(); _cache->close();
_cache->clear(); _cache->clear();

View file

@ -90,9 +90,13 @@ struct GiftUpdate {
}; };
struct SentToScheduled { struct SentToScheduled {
not_null<HistoryItem*> item; not_null<History*> history;
MsgId scheduledId = 0; MsgId scheduledId = 0;
}; };
struct SentFromScheduled {
not_null<HistoryItem*> item;
MsgId sentId = 0;
};
class Session final { class Session final {
public: public:
@ -798,6 +802,8 @@ public:
void sentToScheduled(SentToScheduled value); void sentToScheduled(SentToScheduled value);
[[nodiscard]] rpl::producer<SentToScheduled> sentToScheduled() const; [[nodiscard]] rpl::producer<SentToScheduled> sentToScheduled() const;
void sentFromScheduled(SentFromScheduled value);
[[nodiscard]] rpl::producer<SentFromScheduled> sentFromScheduled() const;
void clearLocalStorage(); void clearLocalStorage();
@ -972,6 +978,7 @@ private:
rpl::event_stream<> _unreadBadgeChanges; rpl::event_stream<> _unreadBadgeChanges;
rpl::event_stream<RepliesReadTillUpdate> _repliesReadTillUpdates; rpl::event_stream<RepliesReadTillUpdate> _repliesReadTillUpdates;
rpl::event_stream<SentToScheduled> _sentToScheduled; rpl::event_stream<SentToScheduled> _sentToScheduled;
rpl::event_stream<SentFromScheduled> _sentFromScheduled;
Dialogs::MainList _chatsList; Dialogs::MainList _chatsList;
Dialogs::IndexedList _contactsList; Dialogs::IndexedList _contactsList;

View file

@ -725,19 +725,33 @@ HistoryWidget::HistoryWidget(
session().data().sentToScheduled( session().data().sentToScheduled(
) | rpl::start_with_next([=](const Data::SentToScheduled &value) { ) | rpl::start_with_next([=](const Data::SentToScheduled &value) {
if (value.item->history() == _history) { const auto history = value.history;
const auto history = value.item->history(); if (history == _history) {
const auto id = value.scheduledId; const auto id = value.scheduledId;
crl::on_main(this, [=] { crl::on_main(this, [=] {
controller->showSection( if (history == _history) {
std::make_shared<HistoryView::ScheduledMemento>( controller->showSection(
history, std::make_shared<HistoryView::ScheduledMemento>(
id)); history,
id));
}
}); });
return; return;
} }
}, lifetime()); }, 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; using MediaSwitch = Media::Player::Instance::Switch;
Media::Player::instance()->switchToNextEvents( Media::Player::instance()->switchToNextEvents(
) | rpl::filter([=](const MediaSwitch &pair) { ) | rpl::filter([=](const MediaSwitch &pair) {

View file

@ -702,6 +702,7 @@ private:
bool _preserveScrollTop = false; bool _preserveScrollTop = false;
bool _repaintFieldScheduled = false; bool _repaintFieldScheduled = false;
bool _sentFromScheduledTip = false;
mtpRequestId _saveEditMsgRequestId = 0; mtpRequestId _saveEditMsgRequestId = 0;

View file

@ -16,10 +16,15 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "history/history_drag_area.h" #include "history/history_drag_area.h"
#include "history/history_item_helpers.h" // GetErrorTextForSending. #include "history/history_item_helpers.h" // GetErrorTextForSending.
#include "menu/menu_send.h" // SendMenu::Type. #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/scroll_area.h"
#include "ui/widgets/shadow.h" #include "ui/widgets/shadow.h"
#include "ui/chat/chat_style.h" #include "ui/chat/chat_style.h"
#include "ui/text/text_utilities.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 "ui/ui_utility.h"
#include "api/api_editing.h" #include "api/api_editing.h"
#include "api/api_sending.h" #include "api/api_sending.h"
@ -34,7 +39,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "core/mime_type.h" #include "core/mime_type.h"
#include "chat_helpers/tabbed_selector.h" #include "chat_helpers/tabbed_selector.h"
#include "main/main_session.h" #include "main/main_session.h"
#include "mainwindow.h"
#include "data/components/scheduled_messages.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.h"
#include "data/data_forum_topic.h" #include "data/data_forum_topic.h"
#include "data/data_session.h" #include "data/data_session.h"
@ -55,6 +63,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include <QtCore/QMimeData> #include <QtCore/QMimeData>
namespace HistoryView { namespace HistoryView {
namespace {
constexpr auto kVideoProcessingInfoDuration = 4 * crl::time(1000);
} // namespace
ScheduledMemento::ScheduledMemento( ScheduledMemento::ScheduledMemento(
not_null<History*> history, not_null<History*> history,
@ -104,33 +117,33 @@ ScheduledWidget::ScheduledWidget(
not_null<Window::SessionController*> controller, not_null<Window::SessionController*> controller,
not_null<History*> history, not_null<History*> history,
const Data::ForumTopic *forumTopic) const Data::ForumTopic *forumTopic)
: Window::SectionWidget(parent, controller, history->peer) : Window::SectionWidget(parent, controller, history->peer)
, WindowListDelegate(controller) , WindowListDelegate(controller)
, _show(controller->uiShow()) , _show(controller->uiShow())
, _history(history) , _history(history)
, _forumTopic(forumTopic) , _forumTopic(forumTopic)
, _scroll( , _scroll(
this, this,
controller->chatStyle()->value(lifetime(), st::historyScroll), controller->chatStyle()->value(lifetime(), st::historyScroll),
false) false)
, _topBar(this, controller) , _topBar(this, controller)
, _topBarShadow(this) , _topBarShadow(this)
, _composeControls(std::make_unique<ComposeControls>( , _composeControls(std::make_unique<ComposeControls>(
this, this,
ComposeControlsDescriptor{ ComposeControlsDescriptor{
.show = controller->uiShow(), .show = controller->uiShow(),
.unavailableEmojiPasted = [=](not_null<DocumentData*> emoji) { .unavailableEmojiPasted = [=](not_null<DocumentData*> emoji) {
listShowPremiumToast(emoji); listShowPremiumToast(emoji);
}, },
.mode = ComposeControls::Mode::Scheduled, .mode = ComposeControls::Mode::Scheduled,
.sendMenuDetails = [] { return SendMenu::Details(); }, .sendMenuDetails = [] { return SendMenu::Details(); },
.regularWindow = controller, .regularWindow = controller,
.stickerOrEmojiChosen = controller->stickerOrEmojiChosen(), .stickerOrEmojiChosen = controller->stickerOrEmojiChosen(),
})) }))
, _cornerButtons( , _cornerButtons(
_scroll.data(), _scroll.data(),
controller->chatStyle(), controller->chatStyle(),
static_cast<HistoryView::CornerButtonsDelegate*>(this)) { static_cast<HistoryView::CornerButtonsDelegate*>(this)) {
controller->chatStyle()->paletteChanged( controller->chatStyle()->paletteChanged(
) | rpl::start_with_next([=] { ) | rpl::start_with_next([=] {
_scroll->updateBars(); _scroll->updateBars();
@ -1070,6 +1083,24 @@ void ScheduledWidget::saveState(not_null<ScheduledMemento*> memento) {
void ScheduledWidget::restoreState(not_null<ScheduledMemento*> memento) { void ScheduledWidget::restoreState(not_null<ScheduledMemento*> memento) {
_inner->restoreState(memento->list()); _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) { void ScheduledWidget::resizeEvent(QResizeEvent *e) {
@ -1141,9 +1172,153 @@ void ScheduledWidget::updateInnerVisibleArea() {
checkReplyReturns(); checkReplyReturns();
} }
const auto scrollTop = _scroll->scrollTop(); const auto scrollTop = _scroll->scrollTop();
_inner->setVisibleTopBottom(scrollTop, scrollTop + _scroll->height()); const auto scrollBottom = scrollTop + _scroll->height();
_inner->setVisibleTopBottom(scrollTop, scrollBottom);
_cornerButtons.updateJumpDownVisibility(); _cornerButtons.updateJumpDownVisibility();
_cornerButtons.updateUnreadThingsVisibility(); _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<Element*> 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<const Element*> 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<const Element*> 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<Ui::ImportantTooltip>(
_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<QWidget>(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( void ScheduledWidget::showAnimatedHook(
@ -1495,4 +1670,114 @@ void ScheduledWidget::setupDragArea() {
areas.photo->setDroppedCallback(droppedCallback(true)); areas.photo->setDroppedCallback(droppedCallback(true));
} }
bool ShowScheduledVideoPublished(
not_null<Window::SessionController*> controller,
const Data::SentFromScheduled &info,
Fn<void()> 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<Ui::AbstractButton>(
widget.get());
clickableBackground->setPointerCursor(false);
clickableBackground->setAcceptBoth();
clickableBackground->show();
clickableBackground->addClickHandler([=](Qt::MouseButton button) {
if (button == Qt::RightButton) {
hideToast();
}
});
const auto button = Ui::CreateChild<Ui::RoundButton>(
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<Ui::RpWidget>(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 } // namespace HistoryView

View file

@ -21,6 +21,10 @@ namespace ChatHelpers {
class Show; class Show;
} // namespace ChatHelpers } // namespace ChatHelpers
namespace Data {
struct SentFromScheduled;
} // namespace Data
namespace SendMenu { namespace SendMenu {
struct Details; struct Details;
} // namespace SendMenu } // namespace SendMenu
@ -37,6 +41,7 @@ class PlainShadow;
class FlatButton; class FlatButton;
struct PreparedList; struct PreparedList;
class SendFilesWay; class SendFilesWay;
class ImportantTooltip;
} // namespace Ui } // namespace Ui
namespace Profile { namespace Profile {
@ -51,6 +56,10 @@ namespace HistoryView::Controls {
struct VoiceToSend; struct VoiceToSend;
} // namespace HistoryView::Controls } // namespace HistoryView::Controls
namespace Window {
class SessionController;
} // namespace Window
namespace HistoryView { namespace HistoryView {
class Element; class Element;
@ -199,6 +208,12 @@ private:
Data::MessagePosition position, Data::MessagePosition position,
FullMsgId originId = {}); FullMsgId originId = {});
void initProcessingVideoView(not_null<Element*> view);
void checkProcessingVideoTooltip(int visibleTop, int visibleBottom);
void showProcessingVideoTooltip();
void updateProcessingVideoTooltipPosition();
void clearProcessingVideoTracking(bool fast);
void setupComposeControls(); void setupComposeControls();
void setupDragArea(); void setupDragArea();
@ -277,7 +292,16 @@ private:
std::unique_ptr<ComposeControls> _composeControls; std::unique_ptr<ComposeControls> _composeControls;
bool _skipScrollEvent = false; bool _skipScrollEvent = false;
Data::MessagePosition _processingVideoPosition;
base::weak_ptr<Element> _processingVideoView;
rpl::lifetime _processingVideoLifetime;
std::unique_ptr<HistoryView::StickerToast> _stickerToast; std::unique_ptr<HistoryView::StickerToast> _stickerToast;
std::unique_ptr<Ui::ImportantTooltip> _processingVideoTooltip;
base::Timer _processingVideoTipTimer;
bool _processingVideoUpdateScheduled = false;
bool _processingVideoTooltipShown = false;
bool _processingVideoCanShow = false;
CornerButtons _cornerButtons; CornerButtons _cornerButtons;
@ -299,20 +323,29 @@ public:
Window::Column column, Window::Column column,
const QRect &geometry) override; const QRect &geometry) override;
not_null<History*> getHistory() const { [[nodiscard]] not_null<History*> getHistory() const {
return _history; return _history;
} }
not_null<ListMemento*> list() { [[nodiscard]] not_null<ListMemento*> list() {
return &_list; return &_list;
} }
[[nodiscard]] MsgId sentToScheduledId() const {
return _sentToScheduledId;
}
private: private:
const not_null<History*> _history; const not_null<History*> _history;
const Data::ForumTopic *_forumTopic; const Data::ForumTopic *_forumTopic;
ListMemento _list; ListMemento _list;
MsgId _sentToScheduledId; MsgId _sentToScheduledId = 0;
}; };
bool ShowScheduledVideoPublished(
not_null<Window::SessionController*> controller,
const Data::SentFromScheduled &info,
Fn<void()> hidden = nullptr);
} // namespace HistoryView } // namespace HistoryView

View file

@ -1982,10 +1982,12 @@ bool Gif::dataLoaded() const {
} }
bool Gif::needInfoDisplay() const { bool Gif::needInfoDisplay() const {
if (_parent->data()->isFakeAboutView()) { const auto item = _parent->data();
if (item->isFakeAboutView()) {
return false; return false;
} }
return _parent->data()->isSending() return item->isSending()
|| item->awaitingVideoProcessing()
|| _data->uploading() || _data->uploading()
|| _parent->isUnderCursor() || _parent->isUnderCursor()
|| (_parent->delegate()->elementContext() == Context::ChatPreview) || (_parent->delegate()->elementContext() == Context::ChatPreview)

View file

@ -893,9 +893,11 @@ bool GroupedMedia::computeNeedBubble() const {
} }
bool GroupedMedia::needInfoDisplay() const { bool GroupedMedia::needInfoDisplay() const {
const auto item = _parent->data();
return (_mode != Mode::Column) return (_mode != Mode::Column)
&& (_parent->data()->isSending() && (item->isSending()
|| _parent->data()->hasFailed() || item->awaitingVideoProcessing()
|| item->hasFailed()
|| _parent->isUnderCursor() || _parent->isUnderCursor()
|| (_parent->delegate()->elementContext() == Context::ChatPreview) || (_parent->delegate()->elementContext() == Context::ChatPreview)
|| _parent->isLastAndSelfMessage()); || _parent->isLastAndSelfMessage());

View file

@ -820,7 +820,7 @@ rpl::producer<uint64> AddCurrencyAction(
) | rpl::start_with_error_done([=](const QString &error) { ) | rpl::start_with_error_done([=](const QString &error) {
currencyLoadLifetime->destroy(); currencyLoadLifetime->destroy();
}, [=] { }, [=] {
if (const auto strong = weak.get()) { if (const auto strong = weak.data()) {
state->balance = currencyLoad->data().currentBalance; state->balance = currencyLoad->data().currentBalance;
currencyLoadLifetime->destroy(); currencyLoadLifetime->destroy();
} }

View file

@ -59,9 +59,9 @@ private:
}; };
class StoryThumbnail : public DynamicImage { class MediaThumbnail : public DynamicImage {
public: public:
explicit StoryThumbnail(Data::FileOrigin origin, bool forceRound); explicit MediaThumbnail(Data::FileOrigin origin, bool forceRound);
QImage image(int size) override; QImage image(int size) override;
void subscribeToUpdates(Fn<void()> callback) override; void subscribeToUpdates(Fn<void()> callback) override;
@ -89,7 +89,7 @@ private:
}; };
class PhotoThumbnail final : public StoryThumbnail { class PhotoThumbnail final : public MediaThumbnail {
public: public:
PhotoThumbnail( PhotoThumbnail(
not_null<PhotoData*> photo, not_null<PhotoData*> photo,
@ -108,7 +108,7 @@ private:
}; };
class VideoThumbnail final : public StoryThumbnail { class VideoThumbnail final : public MediaThumbnail {
public: public:
VideoThumbnail( VideoThumbnail(
not_null<DocumentData*> video, not_null<DocumentData*> video,
@ -304,12 +304,12 @@ void PeerUserpic::processNewPhoto() {
}, _subscribed->downloadLifetime); }, _subscribed->downloadLifetime);
} }
StoryThumbnail::StoryThumbnail(Data::FileOrigin origin, bool forceRound) MediaThumbnail::MediaThumbnail(Data::FileOrigin origin, bool forceRound)
: _origin(origin) : _origin(origin)
, _forceRound(forceRound) { , _forceRound(forceRound) {
} }
QImage StoryThumbnail::image(int size) { QImage MediaThumbnail::image(int size) {
const auto ratio = style::DevicePixelRatio(); const auto ratio = style::DevicePixelRatio();
if (_prepared.width() != size * ratio) { if (_prepared.width() != size * ratio) {
if (_full.isNull()) { if (_full.isNull()) {
@ -333,7 +333,7 @@ QImage StoryThumbnail::image(int size) {
return _prepared; return _prepared;
} }
void StoryThumbnail::subscribeToUpdates(Fn<void()> callback) { void MediaThumbnail::subscribeToUpdates(Fn<void()> callback) {
_subscription.destroy(); _subscription.destroy();
if (!callback) { if (!callback) {
clear(); clear();
@ -363,11 +363,11 @@ void StoryThumbnail::subscribeToUpdates(Fn<void()> callback) {
} }
} }
Data::FileOrigin StoryThumbnail::origin() const { Data::FileOrigin MediaThumbnail::origin() const {
return _origin; return _origin;
} }
bool StoryThumbnail::forceRound() const { bool MediaThumbnail::forceRound() const {
return _forceRound; return _forceRound;
} }
@ -375,7 +375,7 @@ PhotoThumbnail::PhotoThumbnail(
not_null<PhotoData*> photo, not_null<PhotoData*> photo,
Data::FileOrigin origin, Data::FileOrigin origin,
bool forceRound) bool forceRound)
: StoryThumbnail(origin, forceRound) : MediaThumbnail(origin, forceRound)
, _photo(photo) { , _photo(photo) {
} }
@ -387,7 +387,7 @@ Main::Session &PhotoThumbnail::session() {
return _photo->session(); return _photo->session();
} }
StoryThumbnail::Thumb PhotoThumbnail::loaded(Data::FileOrigin origin) { MediaThumbnail::Thumb PhotoThumbnail::loaded(Data::FileOrigin origin) {
if (!_media) { if (!_media) {
_media = _photo->createMediaView(); _media = _photo->createMediaView();
_media->wanted(Data::PhotoSize::Small, origin); _media->wanted(Data::PhotoSize::Small, origin);
@ -406,7 +406,7 @@ VideoThumbnail::VideoThumbnail(
not_null<DocumentData*> video, not_null<DocumentData*> video,
Data::FileOrigin origin, Data::FileOrigin origin,
bool forceRound) bool forceRound)
: StoryThumbnail(origin, forceRound) : MediaThumbnail(origin, forceRound)
, _video(video) { , _video(video) {
} }
@ -418,7 +418,7 @@ Main::Session &VideoThumbnail::session() {
return _video->session(); return _video->session();
} }
StoryThumbnail::Thumb VideoThumbnail::loaded(Data::FileOrigin origin) { MediaThumbnail::Thumb VideoThumbnail::loaded(Data::FileOrigin origin) {
if (!_media) { if (!_media) {
_media = _video->createMediaView(); _media = _video->createMediaView();
_media->thumbnailWanted(origin); _media->thumbnailWanted(origin);

View file

@ -14,6 +14,7 @@ class PhotoData;
namespace Data { namespace Data {
class Story; class Story;
class Session; class Session;
struct FileOrigin;
} // namespace Data } // namespace Data
namespace Ui { namespace Ui {
@ -33,7 +34,6 @@ class DynamicImage;
[[nodiscard]] std::shared_ptr<DynamicImage> MakeEmojiThumbnail( [[nodiscard]] std::shared_ptr<DynamicImage> MakeEmojiThumbnail(
not_null<Data::Session*> owner, not_null<Data::Session*> owner,
const QString &data); const QString &data);
[[nodiscard]] std::shared_ptr<DynamicImage> MakePhotoThumbnail( [[nodiscard]] std::shared_ptr<DynamicImage> MakePhotoThumbnail(
not_null<PhotoData*> photo, not_null<PhotoData*> photo,
FullMsgId fullId); FullMsgId fullId);