mirror of
https://github.com/AyuGram/AyuGramDesktop.git
synced 2025-04-14 13:17:08 +02:00
Show toast/tooltip info on video processing.
This commit is contained in:
parent
3137c9f3f7
commit
66be2ac6ca
17 changed files with 480 additions and 65 deletions
|
@ -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";
|
||||
|
|
|
@ -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<MTPUpdate> &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<MTPUpdate> &other) {
|
||||
const MTPVector<MTPUpdate> &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,
|
||||
});
|
||||
|
||||
|
|
|
@ -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<MTPUpdate> &other);
|
||||
void applyConvertToScheduledOnSend(
|
||||
const MTPVector<MTPUpdate> &other,
|
||||
bool skipScheduledCheck = false);
|
||||
void applyGroupCallParticipantUpdates(const MTPUpdates &updates);
|
||||
|
||||
bool whenGetDiffChanged(
|
||||
|
|
|
@ -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<void()> 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();
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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)) {
|
||||
|
|
|
@ -4821,6 +4821,14 @@ rpl::producer<SentToScheduled> Session::sentToScheduled() const {
|
|||
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() {
|
||||
_cache->close();
|
||||
_cache->clear();
|
||||
|
|
|
@ -90,9 +90,13 @@ struct GiftUpdate {
|
|||
};
|
||||
|
||||
struct SentToScheduled {
|
||||
not_null<HistoryItem*> item;
|
||||
not_null<History*> history;
|
||||
MsgId scheduledId = 0;
|
||||
};
|
||||
struct SentFromScheduled {
|
||||
not_null<HistoryItem*> item;
|
||||
MsgId sentId = 0;
|
||||
};
|
||||
|
||||
class Session final {
|
||||
public:
|
||||
|
@ -798,6 +802,8 @@ public:
|
|||
|
||||
void sentToScheduled(SentToScheduled value);
|
||||
[[nodiscard]] rpl::producer<SentToScheduled> sentToScheduled() const;
|
||||
void sentFromScheduled(SentFromScheduled value);
|
||||
[[nodiscard]] rpl::producer<SentFromScheduled> sentFromScheduled() const;
|
||||
|
||||
void clearLocalStorage();
|
||||
|
||||
|
@ -972,6 +978,7 @@ private:
|
|||
rpl::event_stream<> _unreadBadgeChanges;
|
||||
rpl::event_stream<RepliesReadTillUpdate> _repliesReadTillUpdates;
|
||||
rpl::event_stream<SentToScheduled> _sentToScheduled;
|
||||
rpl::event_stream<SentFromScheduled> _sentFromScheduled;
|
||||
|
||||
Dialogs::MainList _chatsList;
|
||||
Dialogs::IndexedList _contactsList;
|
||||
|
|
|
@ -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<HistoryView::ScheduledMemento>(
|
||||
history,
|
||||
id));
|
||||
if (history == _history) {
|
||||
controller->showSection(
|
||||
std::make_shared<HistoryView::ScheduledMemento>(
|
||||
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) {
|
||||
|
|
|
@ -702,6 +702,7 @@ private:
|
|||
|
||||
bool _preserveScrollTop = false;
|
||||
bool _repaintFieldScheduled = false;
|
||||
bool _sentFromScheduledTip = false;
|
||||
|
||||
mtpRequestId _saveEditMsgRequestId = 0;
|
||||
|
||||
|
|
|
@ -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 <QtCore/QMimeData>
|
||||
|
||||
namespace HistoryView {
|
||||
namespace {
|
||||
|
||||
constexpr auto kVideoProcessingInfoDuration = 4 * crl::time(1000);
|
||||
|
||||
} // namespace
|
||||
|
||||
ScheduledMemento::ScheduledMemento(
|
||||
not_null<History*> history,
|
||||
|
@ -104,33 +117,33 @@ ScheduledWidget::ScheduledWidget(
|
|||
not_null<Window::SessionController*> controller,
|
||||
not_null<History*> 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<ComposeControls>(
|
||||
this,
|
||||
ComposeControlsDescriptor{
|
||||
.show = controller->uiShow(),
|
||||
.unavailableEmojiPasted = [=](not_null<DocumentData*> emoji) {
|
||||
listShowPremiumToast(emoji);
|
||||
},
|
||||
.mode = ComposeControls::Mode::Scheduled,
|
||||
.sendMenuDetails = [] { return SendMenu::Details(); },
|
||||
.regularWindow = controller,
|
||||
.stickerOrEmojiChosen = controller->stickerOrEmojiChosen(),
|
||||
}))
|
||||
, _cornerButtons(
|
||||
_scroll.data(),
|
||||
controller->chatStyle(),
|
||||
static_cast<HistoryView::CornerButtonsDelegate*>(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<ComposeControls>(
|
||||
this,
|
||||
ComposeControlsDescriptor{
|
||||
.show = controller->uiShow(),
|
||||
.unavailableEmojiPasted = [=](not_null<DocumentData*> emoji) {
|
||||
listShowPremiumToast(emoji);
|
||||
},
|
||||
.mode = ComposeControls::Mode::Scheduled,
|
||||
.sendMenuDetails = [] { return SendMenu::Details(); },
|
||||
.regularWindow = controller,
|
||||
.stickerOrEmojiChosen = controller->stickerOrEmojiChosen(),
|
||||
}))
|
||||
, _cornerButtons(
|
||||
_scroll.data(),
|
||||
controller->chatStyle(),
|
||||
static_cast<HistoryView::CornerButtonsDelegate*>(this)) {
|
||||
controller->chatStyle()->paletteChanged(
|
||||
) | rpl::start_with_next([=] {
|
||||
_scroll->updateBars();
|
||||
|
@ -1070,6 +1083,24 @@ void ScheduledWidget::saveState(not_null<ScheduledMemento*> memento) {
|
|||
|
||||
void ScheduledWidget::restoreState(not_null<ScheduledMemento*> 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<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(
|
||||
|
@ -1495,4 +1670,114 @@ void ScheduledWidget::setupDragArea() {
|
|||
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
|
||||
|
|
|
@ -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<Element*> 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> _composeControls;
|
||||
bool _skipScrollEvent = false;
|
||||
|
||||
Data::MessagePosition _processingVideoPosition;
|
||||
base::weak_ptr<Element> _processingVideoView;
|
||||
rpl::lifetime _processingVideoLifetime;
|
||||
|
||||
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;
|
||||
|
||||
|
@ -299,20 +323,29 @@ public:
|
|||
Window::Column column,
|
||||
const QRect &geometry) override;
|
||||
|
||||
not_null<History*> getHistory() const {
|
||||
[[nodiscard]] not_null<History*> getHistory() const {
|
||||
return _history;
|
||||
}
|
||||
|
||||
not_null<ListMemento*> list() {
|
||||
[[nodiscard]] not_null<ListMemento*> list() {
|
||||
return &_list;
|
||||
}
|
||||
|
||||
[[nodiscard]] MsgId sentToScheduledId() const {
|
||||
return _sentToScheduledId;
|
||||
}
|
||||
|
||||
private:
|
||||
const not_null<History*> _history;
|
||||
const Data::ForumTopic *_forumTopic;
|
||||
ListMemento _list;
|
||||
MsgId _sentToScheduledId;
|
||||
MsgId _sentToScheduledId = 0;
|
||||
|
||||
};
|
||||
|
||||
bool ShowScheduledVideoPublished(
|
||||
not_null<Window::SessionController*> controller,
|
||||
const Data::SentFromScheduled &info,
|
||||
Fn<void()> hidden = nullptr);
|
||||
|
||||
} // namespace HistoryView
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -820,7 +820,7 @@ rpl::producer<uint64> 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();
|
||||
}
|
||||
|
|
|
@ -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<void()> callback) override;
|
||||
|
@ -89,7 +89,7 @@ private:
|
|||
|
||||
};
|
||||
|
||||
class PhotoThumbnail final : public StoryThumbnail {
|
||||
class PhotoThumbnail final : public MediaThumbnail {
|
||||
public:
|
||||
PhotoThumbnail(
|
||||
not_null<PhotoData*> photo,
|
||||
|
@ -108,7 +108,7 @@ private:
|
|||
|
||||
};
|
||||
|
||||
class VideoThumbnail final : public StoryThumbnail {
|
||||
class VideoThumbnail final : public MediaThumbnail {
|
||||
public:
|
||||
VideoThumbnail(
|
||||
not_null<DocumentData*> 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<void()> callback) {
|
||||
void MediaThumbnail::subscribeToUpdates(Fn<void()> callback) {
|
||||
_subscription.destroy();
|
||||
if (!callback) {
|
||||
clear();
|
||||
|
@ -363,11 +363,11 @@ void StoryThumbnail::subscribeToUpdates(Fn<void()> 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<PhotoData*> 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<DocumentData*> 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);
|
||||
|
|
|
@ -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<DynamicImage> MakeEmojiThumbnail(
|
||||
not_null<Data::Session*> owner,
|
||||
const QString &data);
|
||||
|
||||
[[nodiscard]] std::shared_ptr<DynamicImage> MakePhotoThumbnail(
|
||||
not_null<PhotoData*> photo,
|
||||
FullMsgId fullId);
|
||||
|
|
Loading…
Add table
Reference in a new issue