mirror of
https://github.com/vale981/tdesktop
synced 2025-03-06 02:01:40 -05:00
Notify about published scheduled messages.
This commit is contained in:
parent
3b76a908a4
commit
0a4f91a53d
21 changed files with 423 additions and 179 deletions
|
@ -1188,6 +1188,14 @@ rpl::producer<not_null<const ViewElement*>> Session::viewLayoutChanged() const {
|
|||
return _viewLayoutChanges.events();
|
||||
}
|
||||
|
||||
void Session::notifyUnreadItemAdded(not_null<HistoryItem*> item) {
|
||||
_unreadItemAdded.fire_copy(item);
|
||||
}
|
||||
|
||||
rpl::producer<not_null<HistoryItem*>> Session::unreadItemAdded() const {
|
||||
return _unreadItemAdded.events();
|
||||
}
|
||||
|
||||
void Session::changeMessageId(ChannelId channel, MsgId wasId, MsgId nowId) {
|
||||
const auto list = messagesListForInsert(channel);
|
||||
auto i = list->find(wasId);
|
||||
|
|
|
@ -192,6 +192,8 @@ public:
|
|||
[[nodiscard]] rpl::producer<not_null<const HistoryItem*>> itemLayoutChanged() const;
|
||||
void notifyViewLayoutChange(not_null<const ViewElement*> view);
|
||||
[[nodiscard]] rpl::producer<not_null<const ViewElement*>> viewLayoutChanged() const;
|
||||
void notifyUnreadItemAdded(not_null<HistoryItem*> item);
|
||||
[[nodiscard]] rpl::producer<not_null<HistoryItem*>> unreadItemAdded() const;
|
||||
void requestItemRepaint(not_null<const HistoryItem*> item);
|
||||
[[nodiscard]] rpl::producer<not_null<const HistoryItem*>> itemRepaintRequest() const;
|
||||
void requestViewRepaint(not_null<const ViewElement*> view);
|
||||
|
@ -841,6 +843,7 @@ private:
|
|||
rpl::event_stream<IdChange> _itemIdChanges;
|
||||
rpl::event_stream<not_null<const HistoryItem*>> _itemLayoutChanges;
|
||||
rpl::event_stream<not_null<const ViewElement*>> _viewLayoutChanges;
|
||||
rpl::event_stream<not_null<HistoryItem*>> _unreadItemAdded;
|
||||
rpl::event_stream<not_null<const HistoryItem*>> _itemRepaintRequest;
|
||||
rpl::event_stream<not_null<const ViewElement*>> _viewRepaintRequest;
|
||||
rpl::event_stream<not_null<const HistoryItem*>> _itemResizeRequest;
|
||||
|
|
|
@ -1242,19 +1242,28 @@ void History::newItemAdded(not_null<HistoryItem*> item) {
|
|||
from->madeAction(item->date());
|
||||
}
|
||||
item->contributeToSlowmode();
|
||||
if (item->out()) {
|
||||
if (item->showNotification()) {
|
||||
_notifications.push_back(item);
|
||||
owner().notifyUnreadItemAdded(item);
|
||||
const auto stillShow = item->showNotification();
|
||||
if (stillShow) {
|
||||
session().notifications().schedule(item);
|
||||
if (!item->out() && item->unread()) {
|
||||
if (unreadCountKnown()) {
|
||||
setUnreadCount(unreadCount() + 1);
|
||||
} else {
|
||||
session().api().requestDialogEntry(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (item->out()) {
|
||||
destroyUnreadBar();
|
||||
if (!item->unread()) {
|
||||
outboxRead(item);
|
||||
}
|
||||
} else if (item->unread()) {
|
||||
if (!isChannel() || peer->asChannel()->amIn()) {
|
||||
_notifications.push_back(item);
|
||||
App::main()->newUnreadMsg(this, item);
|
||||
}
|
||||
} else {
|
||||
inboxRead(item);
|
||||
}
|
||||
if (item->out() && !item->unread()) {
|
||||
outboxRead(item);
|
||||
}
|
||||
if (!folderKnown()) {
|
||||
session().api().requestDialogEntry(this);
|
||||
}
|
||||
|
|
|
@ -367,6 +367,12 @@ void HistoryItem::addLogEntryOriginal(
|
|||
content);
|
||||
}
|
||||
|
||||
PeerData *HistoryItem::specialNotificationPeer() const {
|
||||
return (mentionsMe() && !_history->peer->isUser())
|
||||
? from().get()
|
||||
: nullptr;
|
||||
}
|
||||
|
||||
UserData *HistoryItem::viaBot() const {
|
||||
if (const auto via = Get<HistoryMessageVia>()) {
|
||||
return via->bot;
|
||||
|
@ -383,7 +389,22 @@ UserData *HistoryItem::getMessageBot() const {
|
|||
bot = history()->peer->asUser();
|
||||
}
|
||||
return (bot && bot->isBot()) ? bot : nullptr;
|
||||
};
|
||||
}
|
||||
|
||||
bool HistoryItem::isHistoryEntry() const {
|
||||
return IsServerMsgId(id)
|
||||
|| (_clientFlags & MTPDmessage_ClientFlag::f_local_history_entry);
|
||||
}
|
||||
|
||||
bool HistoryItem::isFromScheduled() const {
|
||||
return isHistoryEntry()
|
||||
&& (_flags & MTPDmessage::Flag::f_from_scheduled);
|
||||
}
|
||||
|
||||
bool HistoryItem::isScheduled() const {
|
||||
return !isHistoryEntry()
|
||||
&& (_flags & MTPDmessage::Flag::f_from_scheduled);
|
||||
}
|
||||
|
||||
void HistoryItem::destroy() {
|
||||
_history->owner().destroyMessage(this);
|
||||
|
@ -737,6 +758,14 @@ bool HistoryItem::unread() const {
|
|||
return (_clientFlags & MTPDmessage_ClientFlag::f_clientside_unread);
|
||||
}
|
||||
|
||||
bool HistoryItem::showNotification() const {
|
||||
const auto channel = _history->peer->asChannel();
|
||||
if (channel && !channel->amIn()) {
|
||||
return false;
|
||||
}
|
||||
return out() ? isFromScheduled() : unread();
|
||||
}
|
||||
|
||||
void HistoryItem::markClientSideAsRead() {
|
||||
_clientFlags &= ~MTPDmessage_ClientFlag::f_clientside_unread;
|
||||
}
|
||||
|
|
|
@ -75,28 +75,20 @@ public:
|
|||
virtual MsgId dependencyMsgId() const {
|
||||
return 0;
|
||||
}
|
||||
virtual bool notificationReady() const {
|
||||
[[nodiscard]] virtual bool notificationReady() const {
|
||||
return true;
|
||||
}
|
||||
[[nodiscard]] PeerData *specialNotificationPeer() const;
|
||||
virtual void applyGroupAdminChanges(
|
||||
const base::flat_set<UserId> &changes) {
|
||||
}
|
||||
|
||||
UserData *viaBot() const;
|
||||
UserData *getMessageBot() const;
|
||||
[[nodiscard]] UserData *viaBot() const;
|
||||
[[nodiscard]] UserData *getMessageBot() const;
|
||||
[[nodiscard]] bool isHistoryEntry() const;
|
||||
[[nodiscard]] bool isFromScheduled() const;
|
||||
[[nodiscard]] bool isScheduled() const;
|
||||
|
||||
[[nodiscard]] bool isHistoryEntry() const {
|
||||
return IsServerMsgId(id)
|
||||
|| (_clientFlags & MTPDmessage_ClientFlag::f_local_history_entry);
|
||||
}
|
||||
[[nodiscard]] bool isFromScheduled() const {
|
||||
return isHistoryEntry()
|
||||
&& (_flags & MTPDmessage::Flag::f_from_scheduled);
|
||||
}
|
||||
[[nodiscard]] bool isScheduled() const {
|
||||
return !isHistoryEntry()
|
||||
&& (_flags & MTPDmessage::Flag::f_from_scheduled);
|
||||
}
|
||||
void addLogEntryOriginal(
|
||||
WebPageId localId,
|
||||
const QString &label,
|
||||
|
@ -123,6 +115,7 @@ public:
|
|||
return _flags & MTPDmessage::Flag::f_out;
|
||||
}
|
||||
[[nodiscard]] bool unread() const;
|
||||
[[nodiscard]] bool showNotification() const;
|
||||
void markClientSideAsRead();
|
||||
[[nodiscard]] bool mentionsMe() const;
|
||||
[[nodiscard]] bool isUnreadMention() const;
|
||||
|
|
|
@ -1302,7 +1302,12 @@ void HistoryMessage::dependencyItemRemoved(HistoryItem *dependency) {
|
|||
}
|
||||
|
||||
QString HistoryMessage::notificationHeader() const {
|
||||
return (!_history->peer->isUser() && !isPost()) ? from()->name : QString();
|
||||
if (out() && isFromScheduled()) {
|
||||
return tr::lng_from_you(tr::now);
|
||||
} else if (!_history->peer->isUser() && !isPost()) {
|
||||
return App::peerName(from());
|
||||
}
|
||||
return QString();
|
||||
}
|
||||
|
||||
std::unique_ptr<HistoryView::Element> HistoryMessage::createView(
|
||||
|
|
|
@ -455,6 +455,12 @@ HistoryWidget::HistoryWidget(
|
|||
update();
|
||||
}
|
||||
});
|
||||
|
||||
session().data().unreadItemAdded(
|
||||
) | rpl::start_with_next([=](not_null<HistoryItem*> item) {
|
||||
unreadMessageAdded(item);
|
||||
}, lifetime());
|
||||
|
||||
session().data().itemRemoved(
|
||||
) | rpl::start_with_next([=](not_null<const HistoryItem*> item) {
|
||||
itemRemoved(item);
|
||||
|
@ -2225,34 +2231,28 @@ void HistoryWidget::destroyUnreadBar() {
|
|||
if (_migrated) _migrated->destroyUnreadBar();
|
||||
}
|
||||
|
||||
void HistoryWidget::newUnreadMsg(
|
||||
not_null<History*> history,
|
||||
not_null<HistoryItem*> item) {
|
||||
if (_history == history) {
|
||||
// If we get here in non-resized state we can't rely on results of
|
||||
// doWeReadServerHistory() and mark chat as read.
|
||||
// If we receive N messages being not at bottom:
|
||||
// - on first message we set unreadcount += 1, firstUnreadMessage.
|
||||
// - on second we get wrong doWeReadServerHistory() and read both.
|
||||
session().data().sendHistoryChangeNotifications();
|
||||
void HistoryWidget::unreadMessageAdded(not_null<HistoryItem*> item) {
|
||||
if (_history != item->history()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (_scroll->scrollTop() + 1 > _scroll->scrollTopMax()) {
|
||||
destroyUnreadBar();
|
||||
}
|
||||
if (App::wnd()->doWeReadServerHistory()) {
|
||||
if (item->isUnreadMention() && !item->isUnreadMedia()) {
|
||||
session().api().markMediaRead(item);
|
||||
}
|
||||
session().api().readServerHistoryForce(history);
|
||||
return;
|
||||
}
|
||||
// If we get here in non-resized state we can't rely on results of
|
||||
// doWeReadServerHistory() and mark chat as read.
|
||||
// If we receive N messages being not at bottom:
|
||||
// - on first message we set unreadcount += 1, firstUnreadMessage.
|
||||
// - on second we get wrong doWeReadServerHistory() and read both.
|
||||
session().data().sendHistoryChangeNotifications();
|
||||
|
||||
if (_scroll->scrollTop() + 1 > _scroll->scrollTopMax()) {
|
||||
destroyUnreadBar();
|
||||
}
|
||||
session().notifications().schedule(history, item);
|
||||
if (history->unreadCountKnown()) {
|
||||
history->setUnreadCount(history->unreadCount() + 1);
|
||||
} else {
|
||||
session().api().requestDialogEntry(history);
|
||||
if (!App::wnd()->doWeReadServerHistory()) {
|
||||
return;
|
||||
}
|
||||
if (item->isUnreadMention() && !item->isUnreadMedia()) {
|
||||
session().api().markMediaRead(item);
|
||||
}
|
||||
session().api().readServerHistoryForce(_history);
|
||||
}
|
||||
|
||||
void HistoryWidget::historyToDown(History *history) {
|
||||
|
|
|
@ -124,9 +124,6 @@ public:
|
|||
void firstLoadMessages();
|
||||
void delayedShowAt(MsgId showAtMsgId);
|
||||
|
||||
void newUnreadMsg(
|
||||
not_null<History*> history,
|
||||
not_null<HistoryItem*> item);
|
||||
void historyToDown(History *history);
|
||||
|
||||
QRect historyRect() const;
|
||||
|
@ -423,6 +420,7 @@ private:
|
|||
void historyDownAnimationFinish();
|
||||
void unreadMentionsAnimationFinish();
|
||||
void sendButtonClicked();
|
||||
void unreadMessageAdded(not_null<HistoryItem*> item);
|
||||
|
||||
bool canSendFiles(not_null<const QMimeData*> data) const;
|
||||
bool confirmSendingFiles(
|
||||
|
|
|
@ -2198,12 +2198,6 @@ void MainWidget::dialogsToUp() {
|
|||
_dialogs->jumpToTop();
|
||||
}
|
||||
|
||||
void MainWidget::newUnreadMsg(
|
||||
not_null<History*> history,
|
||||
not_null<HistoryItem*> item) {
|
||||
_history->newUnreadMsg(history, item);
|
||||
}
|
||||
|
||||
void MainWidget::markActiveHistoryAsRead() {
|
||||
if (const auto activeHistory = _history->history()) {
|
||||
session().api().readServerHistory(activeHistory);
|
||||
|
|
|
@ -146,9 +146,6 @@ public:
|
|||
bool deleteChannelFailed(const RPCError &error);
|
||||
void historyToDown(History *hist);
|
||||
void dialogsToUp();
|
||||
void newUnreadMsg(
|
||||
not_null<History*> history,
|
||||
not_null<HistoryItem*> item);
|
||||
void markActiveHistoryAsRead();
|
||||
|
||||
PeerData *peer();
|
||||
|
|
|
@ -304,7 +304,14 @@ public:
|
|||
|
||||
void init(Manager *manager);
|
||||
|
||||
void showNotification(PeerData *peer, MsgId msgId, const QString &title, const QString &subtitle, const QString &msg, bool hideNameAndPhoto, bool hideReplyButton);
|
||||
void showNotification(
|
||||
not_null<PeerData*> peer,
|
||||
MsgId msgId,
|
||||
const QString &title,
|
||||
const QString &subtitle,
|
||||
const QString &msg,
|
||||
bool hideNameAndPhoto,
|
||||
bool hideReplyButton);
|
||||
void clearAll();
|
||||
void clearFromHistory(History *history);
|
||||
void clearNotification(PeerId peerId, MsgId msgId);
|
||||
|
@ -384,7 +391,14 @@ QString Manager::Private::escapeNotificationText(const QString &text) const {
|
|||
return _markupSupported ? escapeHtml(text) : text;
|
||||
}
|
||||
|
||||
void Manager::Private::showNotification(PeerData *peer, MsgId msgId, const QString &title, const QString &subtitle, const QString &msg, bool hideNameAndPhoto, bool hideReplyButton) {
|
||||
void Manager::Private::showNotification(
|
||||
not_null<PeerData*> peer,
|
||||
MsgId msgId,
|
||||
const QString &title,
|
||||
const QString &subtitle,
|
||||
const QString &msg,
|
||||
bool hideNameAndPhoto,
|
||||
bool hideReplyButton) {
|
||||
auto titleText = escapeNotificationText(title);
|
||||
auto subtitleText = escapeNotificationText(subtitle);
|
||||
auto msgText = escapeNotificationText(msg);
|
||||
|
@ -537,8 +551,22 @@ bool Manager::hasActionsSupport() const {
|
|||
|
||||
Manager::~Manager() = default;
|
||||
|
||||
void Manager::doShowNativeNotification(PeerData *peer, MsgId msgId, const QString &title, const QString &subtitle, const QString &msg, bool hideNameAndPhoto, bool hideReplyButton) {
|
||||
_private->showNotification(peer, msgId, title, subtitle, msg, hideNameAndPhoto, hideReplyButton);
|
||||
void Manager::doShowNativeNotification(
|
||||
not_null<PeerData*> peer,
|
||||
MsgId msgId,
|
||||
const QString &title,
|
||||
const QString &subtitle,
|
||||
const QString &msg,
|
||||
bool hideNameAndPhoto,
|
||||
bool hideReplyButton) {
|
||||
_private->showNotification(
|
||||
peer,
|
||||
msgId,
|
||||
title,
|
||||
subtitle,
|
||||
msg,
|
||||
hideNameAndPhoto,
|
||||
hideReplyButton);
|
||||
}
|
||||
|
||||
void Manager::doClearAllFast() {
|
||||
|
|
|
@ -36,7 +36,14 @@ public:
|
|||
~Manager();
|
||||
|
||||
protected:
|
||||
void doShowNativeNotification(PeerData *peer, MsgId msgId, const QString &title, const QString &subtitle, const QString &msg, bool hideNameAndPhoto, bool hideReplyButton) override;
|
||||
void doShowNativeNotification(
|
||||
not_null<PeerData*> peer,
|
||||
MsgId msgId,
|
||||
const QString &title,
|
||||
const QString &subtitle,
|
||||
const QString &msg,
|
||||
bool hideNameAndPhoto,
|
||||
bool hideReplyButton) override;
|
||||
void doClearAllFast() override;
|
||||
void doClearFromHistory(History *history) override;
|
||||
|
||||
|
|
|
@ -22,7 +22,14 @@ public:
|
|||
~Manager();
|
||||
|
||||
protected:
|
||||
void doShowNativeNotification(PeerData *peer, MsgId msgId, const QString &title, const QString &subtitle, const QString &msg, bool hideNameAndPhoto, bool hideReplyButton) override;
|
||||
void doShowNativeNotification(
|
||||
not_null<PeerData*> peer,
|
||||
MsgId msgId,
|
||||
const QString &title,
|
||||
const QString &subtitle,
|
||||
const QString &msg,
|
||||
bool hideNameAndPhoto,
|
||||
bool hideReplyButton) override;
|
||||
void doClearAllFast() override;
|
||||
void doClearFromHistory(History *history) override;
|
||||
|
||||
|
|
|
@ -160,7 +160,14 @@ class Manager::Private : public QObject, private base::Subscriber {
|
|||
public:
|
||||
Private(Manager *manager);
|
||||
|
||||
void showNotification(PeerData *peer, MsgId msgId, const QString &title, const QString &subtitle, const QString &msg, bool hideNameAndPhoto, bool hideReplyButton);
|
||||
void showNotification(
|
||||
not_null<PeerData*> peer,
|
||||
MsgId msgId,
|
||||
const QString &title,
|
||||
const QString &subtitle,
|
||||
const QString &msg,
|
||||
bool hideNameAndPhoto,
|
||||
bool hideReplyButton);
|
||||
void clearAll();
|
||||
void clearFromHistory(History *history);
|
||||
void updateDelegate();
|
||||
|
@ -208,7 +215,14 @@ Manager::Private::Private(Manager *manager)
|
|||
});
|
||||
}
|
||||
|
||||
void Manager::Private::showNotification(PeerData *peer, MsgId msgId, const QString &title, const QString &subtitle, const QString &msg, bool hideNameAndPhoto, bool hideReplyButton) {
|
||||
void Manager::Private::showNotification(
|
||||
not_null<PeerData*> peer,
|
||||
MsgId msgId,
|
||||
const QString &title,
|
||||
const QString &subtitle,
|
||||
const QString &msg,
|
||||
bool hideNameAndPhoto,
|
||||
bool hideReplyButton) {
|
||||
@autoreleasepool {
|
||||
|
||||
NSUserNotification *notification = [[[NSUserNotification alloc] init] autorelease];
|
||||
|
@ -329,8 +343,22 @@ Manager::Manager(Window::Notifications::System *system) : NativeManager(system)
|
|||
|
||||
Manager::~Manager() = default;
|
||||
|
||||
void Manager::doShowNativeNotification(PeerData *peer, MsgId msgId, const QString &title, const QString &subtitle, const QString &msg, bool hideNameAndPhoto, bool hideReplyButton) {
|
||||
_private->showNotification(peer, msgId, title, subtitle, msg, hideNameAndPhoto, hideReplyButton);
|
||||
void Manager::doShowNativeNotification(
|
||||
not_null<PeerData*> peer,
|
||||
MsgId msgId,
|
||||
const QString &title,
|
||||
const QString &subtitle,
|
||||
const QString &msg,
|
||||
bool hideNameAndPhoto,
|
||||
bool hideReplyButton) {
|
||||
_private->showNotification(
|
||||
peer,
|
||||
msgId,
|
||||
title,
|
||||
subtitle,
|
||||
msg,
|
||||
hideNameAndPhoto,
|
||||
hideReplyButton);
|
||||
}
|
||||
|
||||
void Manager::doClearAllFast() {
|
||||
|
|
|
@ -342,7 +342,14 @@ public:
|
|||
explicit Private(Manager *instance, Type type);
|
||||
bool init();
|
||||
|
||||
bool showNotification(PeerData *peer, MsgId msgId, const QString &title, const QString &subtitle, const QString &msg, bool hideNameAndPhoto, bool hideReplyButton);
|
||||
bool showNotification(
|
||||
not_null<PeerData*> peer,
|
||||
MsgId msgId,
|
||||
const QString &title,
|
||||
const QString &subtitle,
|
||||
const QString &msg,
|
||||
bool hideNameAndPhoto,
|
||||
bool hideReplyButton);
|
||||
void clearAll();
|
||||
void clearFromHistory(History *history);
|
||||
void beforeNotificationActivated(PeerId peerId, MsgId msgId);
|
||||
|
@ -447,13 +454,24 @@ void Manager::Private::clearNotification(PeerId peerId, MsgId msgId) {
|
|||
}
|
||||
}
|
||||
|
||||
bool Manager::Private::showNotification(PeerData *peer, MsgId msgId, const QString &title, const QString &subtitle, const QString &msg, bool hideNameAndPhoto, bool hideReplyButton) {
|
||||
bool Manager::Private::showNotification(
|
||||
not_null<PeerData*> peer,
|
||||
MsgId msgId,
|
||||
const QString &title,
|
||||
const QString &subtitle,
|
||||
const QString &msg,
|
||||
bool hideNameAndPhoto,
|
||||
bool hideReplyButton) {
|
||||
if (!_notificationManager || !_notifier || !_notificationFactory) return false;
|
||||
|
||||
ComPtr<IXmlDocument> toastXml;
|
||||
bool withSubtitle = !subtitle.isEmpty();
|
||||
|
||||
HRESULT hr = _notificationManager->GetTemplateContent(withSubtitle ? ToastTemplateType_ToastImageAndText04 : ToastTemplateType_ToastImageAndText02, &toastXml);
|
||||
HRESULT hr = _notificationManager->GetTemplateContent(
|
||||
(withSubtitle
|
||||
? ToastTemplateType_ToastImageAndText04
|
||||
: ToastTemplateType_ToastImageAndText02),
|
||||
&toastXml);
|
||||
if (!SUCCEEDED(hr)) return false;
|
||||
|
||||
hr = SetAudioSilent(toastXml.Get());
|
||||
|
@ -560,8 +578,22 @@ void Manager::clearNotification(PeerId peerId, MsgId msgId) {
|
|||
|
||||
Manager::~Manager() = default;
|
||||
|
||||
void Manager::doShowNativeNotification(PeerData *peer, MsgId msgId, const QString &title, const QString &subtitle, const QString &msg, bool hideNameAndPhoto, bool hideReplyButton) {
|
||||
_private->showNotification(peer, msgId, title, subtitle, msg, hideNameAndPhoto, hideReplyButton);
|
||||
void Manager::doShowNativeNotification(
|
||||
not_null<PeerData*> peer,
|
||||
MsgId msgId,
|
||||
const QString &title,
|
||||
const QString &subtitle,
|
||||
const QString &msg,
|
||||
bool hideNameAndPhoto,
|
||||
bool hideReplyButton) {
|
||||
_private->showNotification(
|
||||
peer,
|
||||
msgId,
|
||||
title,
|
||||
subtitle,
|
||||
msg,
|
||||
hideNameAndPhoto,
|
||||
hideReplyButton);
|
||||
}
|
||||
|
||||
void Manager::doClearAllFast() {
|
||||
|
|
|
@ -23,7 +23,14 @@ public:
|
|||
~Manager();
|
||||
|
||||
protected:
|
||||
void doShowNativeNotification(PeerData *peer, MsgId msgId, const QString &title, const QString &subtitle, const QString &msg, bool hideNameAndPhoto, bool hideReplyButton) override;
|
||||
void doShowNativeNotification(
|
||||
not_null<PeerData*> peer,
|
||||
MsgId msgId,
|
||||
const QString &title,
|
||||
const QString &subtitle,
|
||||
const QString &msg,
|
||||
bool hideNameAndPhoto,
|
||||
bool hideReplyButton) override;
|
||||
void doClearAllFast() override;
|
||||
void doClearFromHistory(History *history) override;
|
||||
void onBeforeNotificationActivated(PeerId peerId, MsgId msgId) override;
|
||||
|
|
|
@ -3322,7 +3322,9 @@ IsolatedEmoji String::toIsolatedEmoji() const {
|
|||
auto index = 0;
|
||||
for (const auto &block : _blocks) {
|
||||
const auto type = block->type();
|
||||
if (type == TextBlockTEmoji) {
|
||||
if (block->lnkIndex()) {
|
||||
return IsolatedEmoji();
|
||||
} else if (type == TextBlockTEmoji) {
|
||||
result.items[index++] = static_cast<EmojiBlock*>(block.get())->emoji;
|
||||
} else if (type != TextBlockTSkip) {
|
||||
return IsolatedEmoji();
|
||||
|
|
|
@ -61,45 +61,58 @@ void System::createManager() {
|
|||
}
|
||||
}
|
||||
|
||||
void System::schedule(
|
||||
not_null<History*> history,
|
||||
not_null<HistoryItem*> item) {
|
||||
if (App::quitting()
|
||||
|| !history->currentNotification()
|
||||
|| !Main::Session::Exists()) return;
|
||||
|
||||
const auto notifyBy = (!history->peer->isUser() && item->mentionsMe())
|
||||
? item->from().get()
|
||||
: nullptr;
|
||||
System::SkipState System::skipNotification(
|
||||
not_null<HistoryItem*> item) const {
|
||||
const auto history = item->history();
|
||||
const auto notifyBy = item->specialNotificationPeer();
|
||||
if (App::quitting() || !history->currentNotification()) {
|
||||
return { SkipState::Skip };
|
||||
}
|
||||
const auto scheduled = item->out() && item->isFromScheduled();
|
||||
|
||||
history->owner().requestNotifySettings(history->peer);
|
||||
if (notifyBy) {
|
||||
history->owner().requestNotifySettings(notifyBy);
|
||||
}
|
||||
auto haveSetting = !history->owner().notifyMuteUnknown(history->peer);
|
||||
if (haveSetting && history->owner().notifyIsMuted(history->peer)) {
|
||||
if (notifyBy) {
|
||||
haveSetting = !history->owner().notifyMuteUnknown(notifyBy);
|
||||
if (haveSetting) {
|
||||
if (history->owner().notifyIsMuted(notifyBy)) {
|
||||
history->popNotification(item);
|
||||
return;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
history->popNotification(item);
|
||||
return;
|
||||
}
|
||||
|
||||
if (history->owner().notifyMuteUnknown(history->peer)) {
|
||||
return { SkipState::Unknown, item->isSilent() };
|
||||
} else if (!history->owner().notifyIsMuted(history->peer)) {
|
||||
return { SkipState::DontSkip, item->isSilent() };
|
||||
} else if (!notifyBy) {
|
||||
return {
|
||||
scheduled ? SkipState::DontSkip : SkipState::Skip,
|
||||
item->isSilent() || scheduled
|
||||
};
|
||||
} else if (history->owner().notifyMuteUnknown(notifyBy)) {
|
||||
return { SkipState::Unknown, item->isSilent() };
|
||||
} else if (!history->owner().notifyIsMuted(notifyBy)) {
|
||||
return { SkipState::DontSkip, item->isSilent() };
|
||||
} else {
|
||||
return {
|
||||
scheduled ? SkipState::DontSkip : SkipState::Skip,
|
||||
item->isSilent() || scheduled
|
||||
};
|
||||
}
|
||||
if (!item->notificationReady()) {
|
||||
haveSetting = false;
|
||||
}
|
||||
|
||||
void System::schedule(not_null<HistoryItem*> item) {
|
||||
const auto history = item->history();
|
||||
const auto skip = skipNotification(item);
|
||||
if (skip.value == SkipState::Skip) {
|
||||
history->popNotification(item);
|
||||
return;
|
||||
}
|
||||
const auto notifyBy = item->specialNotificationPeer();
|
||||
const auto ready = (skip.value != SkipState::Unknown)
|
||||
&& item->notificationReady();
|
||||
|
||||
auto delay = item->Has<HistoryMessageForwarded>() ? 500 : 100;
|
||||
auto t = base::unixtime::now();
|
||||
auto ms = crl::now();
|
||||
bool isOnline = App::main()->lastWasOnline(), otherNotOld = ((cOtherOnline() * 1000LL) + Global::OnlineCloudTimeout() > t * 1000LL);
|
||||
bool otherLaterThanMe = (cOtherOnline() * 1000LL + (ms - App::main()->lastSetOnline()) > t * 1000LL);
|
||||
const auto t = base::unixtime::now();
|
||||
const auto ms = crl::now();
|
||||
const bool isOnline = App::main()->lastWasOnline();
|
||||
const auto otherNotOld = ((cOtherOnline() * 1000LL) + Global::OnlineCloudTimeout() > t * 1000LL);
|
||||
const bool otherLaterThanMe = (cOtherOnline() * 1000LL + (ms - App::main()->lastSetOnline()) > t * 1000LL);
|
||||
if (!isOnline && otherNotOld && otherLaterThanMe) {
|
||||
delay = Global::NotifyCloudDelay();
|
||||
} else if (cOtherOnline() >= t) {
|
||||
|
@ -107,7 +120,7 @@ void System::schedule(
|
|||
}
|
||||
|
||||
auto when = ms + delay;
|
||||
if (!item->isSilent()) {
|
||||
if (!skip.silent) {
|
||||
_whenAlerts[history].insert(when, notifyBy);
|
||||
}
|
||||
if (Global::DesktopNotify() && !Platform::Notifications::SkipToast()) {
|
||||
|
@ -116,13 +129,13 @@ void System::schedule(
|
|||
whenMap.insert(item->id, when);
|
||||
}
|
||||
|
||||
auto &addTo = haveSetting ? _waiters : _settingWaiters;
|
||||
auto it = addTo.constFind(history);
|
||||
auto &addTo = ready ? _waiters : _settingWaiters;
|
||||
const auto it = addTo.constFind(history);
|
||||
if (it == addTo.cend() || it->when > when) {
|
||||
addTo.insert(history, Waiter(item->id, when, notifyBy));
|
||||
}
|
||||
}
|
||||
if (haveSetting) {
|
||||
if (ready) {
|
||||
if (!_waitTimer.isActive() || _waitTimer.remainingTime() > delay) {
|
||||
_waitTimer.callOnce(delay);
|
||||
}
|
||||
|
@ -524,9 +537,12 @@ void Manager::notificationReplied(
|
|||
}
|
||||
}
|
||||
|
||||
void NativeManager::doShowNotification(HistoryItem *item, int forwardedCount) {
|
||||
void NativeManager::doShowNotification(
|
||||
not_null<HistoryItem*> item,
|
||||
int forwardedCount) {
|
||||
const auto options = getNotificationOptions(item);
|
||||
|
||||
const auto scheduled = (item->out() && item->isFromScheduled());
|
||||
const auto title = options.hideNameAndPhoto ? qsl("Telegram Desktop") : item->history()->peer->name;
|
||||
const auto subtitle = options.hideNameAndPhoto ? QString() : item->notificationHeader();
|
||||
const auto text = options.hideMessageText
|
||||
|
@ -539,7 +555,7 @@ void NativeManager::doShowNotification(HistoryItem *item, int forwardedCount) {
|
|||
item->history()->peer,
|
||||
item->id,
|
||||
title,
|
||||
subtitle,
|
||||
scheduled ? WrapFromScheduled(subtitle) : subtitle,
|
||||
text,
|
||||
options.hideNameAndPhoto,
|
||||
options.hideReplyButton);
|
||||
|
@ -547,5 +563,9 @@ void NativeManager::doShowNotification(HistoryItem *item, int forwardedCount) {
|
|||
|
||||
System::~System() = default;
|
||||
|
||||
QString WrapFromScheduled(const QString &text) {
|
||||
return QString::fromUtf8("\xF0\x9F\x93\x85 ") + text;
|
||||
}
|
||||
|
||||
} // namespace Notifications
|
||||
} // namespace Window
|
||||
|
|
|
@ -62,7 +62,7 @@ public:
|
|||
void createManager();
|
||||
|
||||
void checkDelayed();
|
||||
void schedule(not_null<History*> history, not_null<HistoryItem*> item);
|
||||
void schedule(not_null<HistoryItem*> item);
|
||||
void clearFromHistory(History *history);
|
||||
void clearFromItem(HistoryItem *item);
|
||||
void clearAll();
|
||||
|
@ -80,6 +80,18 @@ public:
|
|||
~System();
|
||||
|
||||
private:
|
||||
struct SkipState {
|
||||
enum Value {
|
||||
Unknown,
|
||||
Skip,
|
||||
DontSkip
|
||||
};
|
||||
Value value = Value::Unknown;
|
||||
bool silent = false;
|
||||
};
|
||||
|
||||
SkipState skipNotification(not_null<HistoryItem*> item) const;
|
||||
|
||||
void showNext();
|
||||
void showGrouped();
|
||||
void ensureSoundCreated();
|
||||
|
@ -122,7 +134,9 @@ public:
|
|||
explicit Manager(not_null<System*> system) : _system(system) {
|
||||
}
|
||||
|
||||
void showNotification(HistoryItem *item, int forwardedCount) {
|
||||
void showNotification(
|
||||
not_null<HistoryItem*> item,
|
||||
int forwardedCount) {
|
||||
doShowNotification(item, forwardedCount);
|
||||
}
|
||||
void updateAll() {
|
||||
|
@ -162,7 +176,9 @@ protected:
|
|||
}
|
||||
|
||||
virtual void doUpdateAll() = 0;
|
||||
virtual void doShowNotification(HistoryItem *item, int forwardedCount) = 0;
|
||||
virtual void doShowNotification(
|
||||
not_null<HistoryItem*> item,
|
||||
int forwardedCount) = 0;
|
||||
virtual void doClearAll() = 0;
|
||||
virtual void doClearAllFast() = 0;
|
||||
virtual void doClearFromItem(HistoryItem *item) = 0;
|
||||
|
@ -193,11 +209,22 @@ protected:
|
|||
}
|
||||
void doClearFromItem(HistoryItem *item) override {
|
||||
}
|
||||
void doShowNotification(HistoryItem *item, int forwardedCount) override;
|
||||
void doShowNotification(
|
||||
not_null<HistoryItem*> item,
|
||||
int forwardedCount) override;
|
||||
|
||||
virtual void doShowNativeNotification(PeerData *peer, MsgId msgId, const QString &title, const QString &subtitle, const QString &msg, bool hideNameAndPhoto, bool hideReplyButton) = 0;
|
||||
virtual void doShowNativeNotification(
|
||||
not_null<PeerData*> peer,
|
||||
MsgId msgId,
|
||||
const QString &title,
|
||||
const QString &subtitle,
|
||||
const QString &msg,
|
||||
bool hideNameAndPhoto,
|
||||
bool hideReplyButton) = 0;
|
||||
|
||||
};
|
||||
|
||||
QString WrapFromScheduled(const QString &text);
|
||||
|
||||
} // namespace Notifications
|
||||
} // namespace Window
|
||||
|
|
|
@ -12,6 +12,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
#include "lang/lang_keys.h"
|
||||
#include "ui/widgets/buttons.h"
|
||||
#include "ui/widgets/input_fields.h"
|
||||
#include "ui/text_options.h"
|
||||
#include "dialogs/dialogs_layout.h"
|
||||
#include "window/themes/window_theme.h"
|
||||
#include "styles/style_dialogs.h"
|
||||
|
@ -66,13 +67,14 @@ Manager::Manager(System *system)
|
|||
}
|
||||
|
||||
Manager::QueuedNotification::QueuedNotification(
|
||||
not_null<HistoryItem*> item
|
||||
, int forwardedCount)
|
||||
not_null<HistoryItem*> item,
|
||||
int forwardedCount)
|
||||
: history(item->history())
|
||||
, peer(history->peer)
|
||||
, author((!peer->isUser() && !item->isPost()) ? item->author().get() : nullptr)
|
||||
, author(item->notificationHeader())
|
||||
, item((forwardedCount < 2) ? item.get() : nullptr)
|
||||
, forwardedCount(forwardedCount) {
|
||||
, forwardedCount(forwardedCount)
|
||||
, fromScheduled(item->out() && item->isFromScheduled()) {
|
||||
}
|
||||
|
||||
QPixmap Manager::hiddenUserpicPlaceholder() const {
|
||||
|
@ -206,7 +208,10 @@ void Manager::showNextFromQueue() {
|
|||
queued.author,
|
||||
queued.item,
|
||||
queued.forwardedCount,
|
||||
startPosition, startShift, shiftDirection);
|
||||
queued.fromScheduled,
|
||||
startPosition,
|
||||
startShift,
|
||||
shiftDirection);
|
||||
_notifications.push_back(std::move(notification));
|
||||
--count;
|
||||
} while (count > 0 && !_queuedNotifications.empty());
|
||||
|
@ -289,7 +294,9 @@ void Manager::removeWidget(internal::Widget *remove) {
|
|||
showNextFromQueue();
|
||||
}
|
||||
|
||||
void Manager::doShowNotification(HistoryItem *item, int forwardedCount) {
|
||||
void Manager::doShowNotification(
|
||||
not_null<HistoryItem*> item,
|
||||
int forwardedCount) {
|
||||
_queuedNotifications.emplace_back(item, forwardedCount);
|
||||
showNextFromQueue();
|
||||
}
|
||||
|
@ -353,7 +360,12 @@ Manager::~Manager() {
|
|||
|
||||
namespace internal {
|
||||
|
||||
Widget::Widget(Manager *manager, QPoint startPosition, int shift, Direction shiftDirection) : TWidget(nullptr)
|
||||
Widget::Widget(
|
||||
not_null<Manager*> manager,
|
||||
QPoint startPosition,
|
||||
int shift,
|
||||
Direction shiftDirection)
|
||||
: TWidget(nullptr)
|
||||
, _manager(manager)
|
||||
, _startPosition(startPosition)
|
||||
, _direction(shiftDirection)
|
||||
|
@ -501,12 +513,13 @@ void Background::paintEvent(QPaintEvent *e) {
|
|||
}
|
||||
|
||||
Notification::Notification(
|
||||
Manager *manager,
|
||||
History *history,
|
||||
PeerData *peer,
|
||||
PeerData *author,
|
||||
HistoryItem *msg,
|
||||
not_null<Manager*> manager,
|
||||
not_null<History*> history,
|
||||
not_null<PeerData*> peer,
|
||||
const QString &author,
|
||||
HistoryItem *item,
|
||||
int forwardedCount,
|
||||
bool fromScheduled,
|
||||
QPoint startPosition,
|
||||
int shift,
|
||||
Direction shiftDirection)
|
||||
|
@ -515,8 +528,9 @@ Notification::Notification(
|
|||
, _history(history)
|
||||
, _peer(peer)
|
||||
, _author(author)
|
||||
, _item(msg)
|
||||
, _item(item)
|
||||
, _forwardedCount(forwardedCount)
|
||||
, _fromScheduled(fromScheduled)
|
||||
, _close(this, st::notifyClose)
|
||||
, _reply(this, tr::lng_notification_reply(), st::defaultBoxButton) {
|
||||
subscribe(Lang::Current().updated(), [this] { refreshLang(); });
|
||||
|
@ -683,35 +697,53 @@ void Notification::updateNotifyDisplay() {
|
|||
}
|
||||
|
||||
if (!options.hideMessageText) {
|
||||
const HistoryItem *textCachedFor = nullptr;
|
||||
Ui::Text::String itemTextCache(itemWidth);
|
||||
QRect r(st::notifyPhotoPos.x() + st::notifyPhotoSize + st::notifyTextLeft, st::notifyItemTop + st::msgNameFont->height, itemWidth, 2 * st::dialogsTextFont->height);
|
||||
if (_item) {
|
||||
auto active = false, selected = false;
|
||||
_item->drawInDialog(
|
||||
p,
|
||||
r,
|
||||
active,
|
||||
selected,
|
||||
HistoryItem::DrawInDialog::Normal,
|
||||
textCachedFor,
|
||||
itemTextCache);
|
||||
} else if (_forwardedCount > 1) {
|
||||
p.setFont(st::dialogsTextFont);
|
||||
if (_author) {
|
||||
itemTextCache.setText(st::dialogsTextStyle, _author->name);
|
||||
p.setPen(st::dialogsTextFgService);
|
||||
itemTextCache.drawElided(p, r.left(), r.top(), r.width());
|
||||
r.setTop(r.top() + st::dialogsTextFont->height);
|
||||
}
|
||||
p.setPen(st::dialogsTextFg);
|
||||
p.drawText(r.left(), r.top() + st::dialogsTextFont->ascent, tr::lng_forward_messages(tr::now, lt_count, _forwardedCount));
|
||||
}
|
||||
auto itemTextCache = Ui::Text::String(itemWidth);
|
||||
auto r = QRect(
|
||||
st::notifyPhotoPos.x() + st::notifyPhotoSize + st::notifyTextLeft,
|
||||
st::notifyItemTop + st::msgNameFont->height,
|
||||
itemWidth,
|
||||
2 * st::dialogsTextFont->height);
|
||||
p.setTextPalette(st::dialogsTextPalette);
|
||||
p.setPen(st::dialogsTextFg);
|
||||
p.setFont(st::dialogsTextFont);
|
||||
const auto text = _item
|
||||
? _item->inDialogsText(HistoryItem::DrawInDialog::Normal)
|
||||
: ((!_author.isEmpty()
|
||||
? textcmdLink(1, _author)
|
||||
: QString())
|
||||
+ (_forwardedCount > 1
|
||||
? ('\n' + tr::lng_forward_messages(
|
||||
tr::now,
|
||||
lt_count,
|
||||
_forwardedCount))
|
||||
: QString()));
|
||||
const auto Options = TextParseOptions{
|
||||
TextParseRichText
|
||||
| (_forwardedCount > 1 ? TextParseMultiline : 0),
|
||||
0,
|
||||
0,
|
||||
Qt::LayoutDirectionAuto,
|
||||
};
|
||||
itemTextCache.setText(
|
||||
st::dialogsTextStyle,
|
||||
_fromScheduled ? WrapFromScheduled(text) : text,
|
||||
Options);
|
||||
itemTextCache.drawElided(
|
||||
p,
|
||||
r.left(),
|
||||
r.top(),
|
||||
r.width(),
|
||||
r.height() / st::dialogsTextFont->height);
|
||||
p.restoreTextPalette();
|
||||
} else {
|
||||
static QString notifyText = st::dialogsTextFont->elided(tr::lng_notification_preview(tr::now), itemWidth);
|
||||
p.setFont(st::dialogsTextFont);
|
||||
p.setPen(st::dialogsTextFgService);
|
||||
p.drawText(st::notifyPhotoPos.x() + st::notifyPhotoSize + st::notifyTextLeft, st::notifyItemTop + st::msgNameFont->height + st::dialogsTextFont->ascent, notifyText);
|
||||
p.drawText(
|
||||
st::notifyPhotoPos.x() + st::notifyPhotoSize + st::notifyTextLeft,
|
||||
st::notifyItemTop + st::msgNameFont->height + st::dialogsTextFont->ascent,
|
||||
st::dialogsTextFont->elided(
|
||||
tr::lng_notification_preview(tr::now),
|
||||
itemWidth));
|
||||
}
|
||||
|
||||
p.setPen(st::dialogsNameFg);
|
||||
|
@ -839,7 +871,7 @@ void Notification::changeHeight(int newHeight) {
|
|||
}
|
||||
|
||||
bool Notification::unlinkHistory(History *history) {
|
||||
auto unlink = _history && (history == _history || !history);
|
||||
const auto unlink = _history && (history == _history || !history);
|
||||
if (unlink) {
|
||||
hideFast();
|
||||
_history = nullptr;
|
||||
|
@ -897,7 +929,12 @@ void Notification::stopHiding() {
|
|||
Widget::hideStop();
|
||||
}
|
||||
|
||||
HideAllButton::HideAllButton(Manager *manager, QPoint startPosition, int shift, Direction shiftDirection) : Widget(manager, startPosition, shift, shiftDirection) {
|
||||
HideAllButton::HideAllButton(
|
||||
not_null<Manager*> manager,
|
||||
QPoint startPosition,
|
||||
int shift,
|
||||
Direction shiftDirection)
|
||||
: Widget(manager, startPosition, shift, shiftDirection) {
|
||||
setCursor(style::cur_pointer);
|
||||
|
||||
auto position = computePosition(st::notifyHideAllHeight);
|
||||
|
|
|
@ -52,7 +52,9 @@ private:
|
|||
QPixmap hiddenUserpicPlaceholder() const;
|
||||
|
||||
void doUpdateAll() override;
|
||||
void doShowNotification(HistoryItem *item, int forwardedCount) override;
|
||||
void doShowNotification(
|
||||
not_null<HistoryItem*> item,
|
||||
int forwardedCount) override;
|
||||
void doClearAll() override;
|
||||
void doClearAllFast() override;
|
||||
void doClearFromHistory(History *history) override;
|
||||
|
@ -87,9 +89,10 @@ private:
|
|||
|
||||
not_null<History*> history;
|
||||
not_null<PeerData*> peer;
|
||||
PeerData *author;
|
||||
HistoryItem *item;
|
||||
int forwardedCount;
|
||||
QString author;
|
||||
HistoryItem *item = nullptr;
|
||||
int forwardedCount = 0;
|
||||
bool fromScheduled = false;
|
||||
};
|
||||
std::deque<QueuedNotification> _queuedNotifications;
|
||||
|
||||
|
@ -107,7 +110,11 @@ public:
|
|||
Up,
|
||||
Down,
|
||||
};
|
||||
Widget(Manager *manager, QPoint startPosition, int shift, Direction shiftDirection);
|
||||
Widget(
|
||||
not_null<Manager*> manager,
|
||||
QPoint startPosition,
|
||||
int shift,
|
||||
Direction shiftDirection);
|
||||
|
||||
bool isShowing() const {
|
||||
return _a_opacity.animating() && !_hiding;
|
||||
|
@ -131,7 +138,7 @@ protected:
|
|||
virtual void updateGeometry(int x, int y, int width, int height);
|
||||
|
||||
protected:
|
||||
Manager *manager() const {
|
||||
not_null<Manager*> manager() const {
|
||||
return _manager;
|
||||
}
|
||||
|
||||
|
@ -142,7 +149,7 @@ private:
|
|||
void hideAnimated(float64 duration, const anim::transition &func);
|
||||
bool shiftAnimationCallback(crl::time now);
|
||||
|
||||
Manager *_manager = nullptr;
|
||||
const not_null<Manager*> _manager;
|
||||
|
||||
bool _hiding = false;
|
||||
bool _deleted = false;
|
||||
|
@ -168,12 +175,13 @@ protected:
|
|||
class Notification : public Widget {
|
||||
public:
|
||||
Notification(
|
||||
Manager *manager,
|
||||
History *history,
|
||||
PeerData *peer,
|
||||
PeerData *author,
|
||||
not_null<Manager*> manager,
|
||||
not_null<History*> history,
|
||||
not_null<PeerData*> peer,
|
||||
const QString &author,
|
||||
HistoryItem *item,
|
||||
int forwardedCount,
|
||||
bool fromScheduled,
|
||||
QPoint startPosition,
|
||||
int shift,
|
||||
Direction shiftDirection);
|
||||
|
@ -228,11 +236,12 @@ private:
|
|||
|
||||
crl::time _started;
|
||||
|
||||
History *_history;
|
||||
PeerData *_peer;
|
||||
PeerData *_author;
|
||||
HistoryItem *_item;
|
||||
int _forwardedCount;
|
||||
History *_history = nullptr;
|
||||
PeerData *_peer = nullptr;
|
||||
QString _author;
|
||||
HistoryItem *_item = nullptr;
|
||||
int _forwardedCount = 0;
|
||||
bool _fromScheduled = false;
|
||||
object_ptr<Ui::IconButton> _close;
|
||||
object_ptr<Ui::RoundButton> _reply;
|
||||
object_ptr<Background> _background = { nullptr };
|
||||
|
@ -250,7 +259,11 @@ private:
|
|||
|
||||
class HideAllButton : public Widget {
|
||||
public:
|
||||
HideAllButton(Manager *manager, QPoint startPosition, int shift, Direction shiftDirection);
|
||||
HideAllButton(
|
||||
not_null<Manager*> manager,
|
||||
QPoint startPosition,
|
||||
int shift,
|
||||
Direction shiftDirection);
|
||||
|
||||
void startHiding();
|
||||
void startHidingFast();
|
||||
|
|
Loading…
Add table
Reference in a new issue