diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index 56decef54..ece3d18e0 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -297,6 +297,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_notification_reply" = "Reply"; "lng_notification_hide_all" = "Hide all"; "lng_notification_sample" = "This is a sample notification"; +"lng_notification_reminder" = "Reminder"; "lng_settings_section_general" = "General"; "lng_settings_change_lang" = "Change language"; diff --git a/Telegram/SourceFiles/data/data_session.cpp b/Telegram/SourceFiles/data/data_session.cpp index 7626ab991..cb081870b 100644 --- a/Telegram/SourceFiles/data/data_session.cpp +++ b/Telegram/SourceFiles/data/data_session.cpp @@ -1202,7 +1202,9 @@ void Session::changeMessageId(ChannelId channel, MsgId wasId, MsgId nowId) { Assert(i != list->end()); auto owned = std::move(i->second); list->erase(i); - list->emplace(nowId, std::move(owned)); + const auto [j, ok] = list->emplace(nowId, std::move(owned)); + + Ensures(ok); } void Session::notifyItemIdChange(IdChange event) { diff --git a/Telegram/SourceFiles/data/data_session.h b/Telegram/SourceFiles/data/data_session.h index 17e6eb8a1..9a8379579 100644 --- a/Telegram/SourceFiles/data/data_session.h +++ b/Telegram/SourceFiles/data/data_session.h @@ -91,6 +91,9 @@ public: [[nodiscard]] ScheduledMessages &scheduledMessages() const { return *_scheduledMessages; } + [[nodiscard]] MsgId nextNonHistoryEntryId() { + return ++_nonHistoryEntryId; + } void clear(); @@ -991,6 +994,7 @@ private: Groups _groups; std::unique_ptr _scheduledMessages; + MsgId _nonHistoryEntryId = ServerMaxMsgId; rpl::lifetime _lifetime; diff --git a/Telegram/SourceFiles/history/history.cpp b/Telegram/SourceFiles/history/history.cpp index f00ef46a8..3106c226c 100644 --- a/Telegram/SourceFiles/history/history.cpp +++ b/Telegram/SourceFiles/history/history.cpp @@ -1658,7 +1658,10 @@ void History::inboxRead(MsgId upTo, std::optional stillUnread) { } _firstUnreadView = nullptr; - session().notifications().clearFromHistory(this); + if (!peer->isSelf()) { + // Only reminders generate notifications in Saved Messages. + session().notifications().clearFromHistory(this); + } } void History::inboxRead(not_null wasRead) { @@ -1832,7 +1835,7 @@ void History::getNextFirstUnreadMessage() { } MsgId History::nextNonHistoryEntryId() { - return ++_nonHistoryEntryId; + return owner().nextNonHistoryEntryId(); } bool History::folderKnown() const { diff --git a/Telegram/SourceFiles/history/history.h b/Telegram/SourceFiles/history/history.h index 48db8369e..b97378b0e 100644 --- a/Telegram/SourceFiles/history/history.h +++ b/Telegram/SourceFiles/history/history.h @@ -532,8 +532,6 @@ private: std::deque> _notifications; - MsgId _nonHistoryEntryId = ServerMaxMsgId; - }; class HistoryBlock { diff --git a/Telegram/SourceFiles/history/history_item.cpp b/Telegram/SourceFiles/history/history_item.cpp index 2c17f3bfc..898cf6394 100644 --- a/Telegram/SourceFiles/history/history_item.cpp +++ b/Telegram/SourceFiles/history/history_item.cpp @@ -763,7 +763,7 @@ bool HistoryItem::showNotification() const { if (channel && !channel->amIn()) { return false; } - return out() ? isFromScheduled() : unread(); + return (out() || _history->peer->isSelf()) ? isFromScheduled() : unread(); } void HistoryItem::markClientSideAsRead() { diff --git a/Telegram/SourceFiles/history/history_message.cpp b/Telegram/SourceFiles/history/history_message.cpp index 7c37ce8e7..a67ea6bec 100644 --- a/Telegram/SourceFiles/history/history_message.cpp +++ b/Telegram/SourceFiles/history/history_message.cpp @@ -1302,7 +1302,7 @@ void HistoryMessage::dependencyItemRemoved(HistoryItem *dependency) { } QString HistoryMessage::notificationHeader() const { - if (out() && isFromScheduled()) { + if (out() && isFromScheduled() && !_history->peer->isSelf()) { return tr::lng_from_you(tr::now); } else if (!_history->peer->isUser() && !isPost()) { return App::peerName(from()); diff --git a/Telegram/SourceFiles/history/history_widget.cpp b/Telegram/SourceFiles/history/history_widget.cpp index 0de17bf5d..29c101e7d 100644 --- a/Telegram/SourceFiles/history/history_widget.cpp +++ b/Telegram/SourceFiles/history/history_widget.cpp @@ -2256,6 +2256,9 @@ void HistoryWidget::unreadMessageAdded(not_null item) { session().api().markMediaRead(item); } session().api().readServerHistoryForce(_history); + + // Also clear possible scheduled messages notifications. + session().notifications().clearFromHistory(_history); } void HistoryWidget::historyToDown(History *history) { @@ -2688,19 +2691,23 @@ bool HistoryWidget::isItemCompletelyHidden(HistoryItem *item) const { void HistoryWidget::visibleAreaUpdated() { if (_list && !_scroll->isHidden()) { - auto scrollTop = _scroll->scrollTop(); - auto scrollBottom = scrollTop + _scroll->height(); + const auto scrollTop = _scroll->scrollTop(); + const auto scrollBottom = scrollTop + _scroll->height(); _list->visibleAreaUpdated(scrollTop, scrollBottom); + const auto atBottom = (scrollTop >= _scroll->scrollTopMax()); if (_history->loadedAtBottom() && (_history->unreadCount() > 0 || (_migrated && _migrated->unreadCount() > 0))) { const auto unread = firstUnreadMessage(); const auto unreadVisible = unread && (scrollBottom > _list->itemTop(unread)); - const auto atBottom = (scrollTop >= _scroll->scrollTopMax()); if ((unreadVisible || atBottom) && App::wnd()->doWeReadServerHistory()) { session().api().readServerHistory(_history); } } + if (_history->loadedAtBottom() && atBottom) { + // Clear possible scheduled messages notifications. + session().notifications().clearFromHistory(_history); + } controller()->floatPlayerAreaUpdated().notify(true); } } diff --git a/Telegram/SourceFiles/platform/mac/notifications_manager_mac.mm b/Telegram/SourceFiles/platform/mac/notifications_manager_mac.mm index dc88b7c8c..7482b0afd 100644 --- a/Telegram/SourceFiles/platform/mac/notifications_manager_mac.mm +++ b/Telegram/SourceFiles/platform/mac/notifications_manager_mac.mm @@ -11,6 +11,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "platform/platform_info.h" #include "platform/mac/mac_utilities.h" #include "history/history.h" +#include "ui/empty_userpic.h" #include "mainwindow.h" #include "styles/style_window.h" @@ -237,7 +238,9 @@ void Manager::Private::showNotification( [notification setSubtitle:Q2NSString(subtitle)]; [notification setInformativeText:Q2NSString(msg)]; if (!hideNameAndPhoto && [notification respondsToSelector:@selector(setContentImage:)]) { - auto userpic = peer->genUserpic(st::notifyMacPhotoSize); + auto userpic = peer->isSelf() + ? Ui::EmptyUserpic::GenerateSavedMessages(st::notifyMacPhotoSize) + : peer->genUserpic(st::notifyMacPhotoSize); NSImage *img = [qt_mac_create_nsimage(userpic) autorelease]; [notification setContentImage:img]; } diff --git a/Telegram/SourceFiles/ui/empty_userpic.cpp b/Telegram/SourceFiles/ui/empty_userpic.cpp index fdef72b68..5acd0c9df 100644 --- a/Telegram/SourceFiles/ui/empty_userpic.cpp +++ b/Telegram/SourceFiles/ui/empty_userpic.cpp @@ -12,86 +12,15 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "styles/style_history.h" namespace Ui { +namespace { -EmptyUserpic::EmptyUserpic(const style::color &color, const QString &name) -: _color(color) { - fillString(name); -} - -template -void EmptyUserpic::paint( +void PaintSavedMessagesInner( Painter &p, int x, int y, - int outerWidth, - int size, - Callback paintBackground) const { - x = rtl() ? (outerWidth - x - size) : x; - - const auto fontsize = (size * 13) / 33; - auto font = st::historyPeerUserpicFont->f; - font.setPixelSize(fontsize); - - PainterHighQualityEnabler hq(p); - p.setBrush(_color); - p.setPen(Qt::NoPen); - paintBackground(); - - p.setFont(font); - p.setBrush(Qt::NoBrush); - p.setPen(st::historyPeerUserpicFg); - p.drawText(QRect(x, y, size, size), _string, QTextOption(style::al_center)); -} - -void EmptyUserpic::paint( - Painter &p, - int x, - int y, - int outerWidth, - int size) const { - paint(p, x, y, outerWidth, size, [&p, x, y, size] { - p.drawEllipse(x, y, size, size); - }); -} - -void EmptyUserpic::paintRounded(Painter &p, int x, int y, int outerWidth, int size) const { - paint(p, x, y, outerWidth, size, [&p, x, y, size] { - p.drawRoundedRect(x, y, size, size, st::buttonRadius, st::buttonRadius); - }); -} - -void EmptyUserpic::paintSquare(Painter &p, int x, int y, int outerWidth, int size) const { - paint(p, x, y, outerWidth, size, [&p, x, y, size] { - p.fillRect(x, y, size, size, p.brush()); - }); -} - -void EmptyUserpic::PaintSavedMessages( - Painter &p, - int x, - int y, - int outerWidth, - int size) { - const auto &bg = st::historyPeerSavedMessagesBg; - const auto &fg = st::historyPeerUserpicFg; - PaintSavedMessages(p, x, y, outerWidth, size, bg, fg); -} - -void EmptyUserpic::PaintSavedMessages( - Painter &p, - int x, - int y, - int outerWidth, int size, const style::color &bg, const style::color &fg) { - x = rtl() ? (outerWidth - x - size) : x; - - PainterHighQualityEnabler hq(p); - p.setBrush(bg); - p.setPen(Qt::NoPen); - p.drawEllipse(x, y, size, size); - // |<----width----->| // // XXXXXXXXXXXXXXXXXX --- @@ -160,6 +89,145 @@ void EmptyUserpic::PaintSavedMessages( } } +template +[[nodiscard]] QPixmap Generate(int size, Callback callback) { + auto result = QImage( + QSize(size, size) * cIntRetinaFactor(), + QImage::Format_ARGB32_Premultiplied); + result.setDevicePixelRatio(cRetinaFactor()); + result.fill(Qt::transparent); + { + Painter p(&result); + callback(p); + } + return App::pixmapFromImageInPlace(std::move(result)); +} + +} // namespace + +EmptyUserpic::EmptyUserpic(const style::color &color, const QString &name) +: _color(color) { + fillString(name); +} + +template +void EmptyUserpic::paint( + Painter &p, + int x, + int y, + int outerWidth, + int size, + Callback paintBackground) const { + x = rtl() ? (outerWidth - x - size) : x; + + const auto fontsize = (size * 13) / 33; + auto font = st::historyPeerUserpicFont->f; + font.setPixelSize(fontsize); + + PainterHighQualityEnabler hq(p); + p.setBrush(_color); + p.setPen(Qt::NoPen); + paintBackground(); + + p.setFont(font); + p.setBrush(Qt::NoBrush); + p.setPen(st::historyPeerUserpicFg); + p.drawText(QRect(x, y, size, size), _string, QTextOption(style::al_center)); +} + +void EmptyUserpic::paint( + Painter &p, + int x, + int y, + int outerWidth, + int size) const { + paint(p, x, y, outerWidth, size, [&p, x, y, size] { + p.drawEllipse(x, y, size, size); + }); +} + +void EmptyUserpic::paintRounded(Painter &p, int x, int y, int outerWidth, int size) const { + paint(p, x, y, outerWidth, size, [&p, x, y, size] { + p.drawRoundedRect(x, y, size, size, st::buttonRadius, st::buttonRadius); + }); +} + +void EmptyUserpic::paintSquare(Painter &p, int x, int y, int outerWidth, int size) const { + paint(p, x, y, outerWidth, size, [&p, x, y, size] { + p.fillRect(x, y, size, size, p.brush()); + }); +} + +void EmptyUserpic::PaintSavedMessages( + Painter &p, + int x, + int y, + int outerWidth, + int size) { + const auto &bg = st::historyPeerSavedMessagesBg; + const auto &fg = st::historyPeerUserpicFg; + PaintSavedMessages(p, x, y, outerWidth, size, bg, fg); +} + +void EmptyUserpic::PaintSavedMessagesRounded( + Painter &p, + int x, + int y, + int outerWidth, + int size) { + const auto &bg = st::historyPeerSavedMessagesBg; + const auto &fg = st::historyPeerUserpicFg; + PaintSavedMessagesRounded(p, x, y, outerWidth, size, bg, fg); +} + +void EmptyUserpic::PaintSavedMessages( + Painter &p, + int x, + int y, + int outerWidth, + int size, + const style::color &bg, + const style::color &fg) { + x = rtl() ? (outerWidth - x - size) : x; + + PainterHighQualityEnabler hq(p); + p.setBrush(bg); + p.setPen(Qt::NoPen); + p.drawEllipse(x, y, size, size); + + PaintSavedMessagesInner(p, x, y, size, bg, fg); +} + +void EmptyUserpic::PaintSavedMessagesRounded( + Painter &p, + int x, + int y, + int outerWidth, + int size, + const style::color &bg, + const style::color &fg) { + x = rtl() ? (outerWidth - x - size) : x; + + PainterHighQualityEnabler hq(p); + p.setBrush(bg); + p.setPen(Qt::NoPen); + p.drawRoundedRect(x, y, size, size, st::buttonRadius, st::buttonRadius); + + PaintSavedMessagesInner(p, x, y, size, bg, fg); +} + +QPixmap EmptyUserpic::GenerateSavedMessages(int size) { + return Generate(size, [&](Painter &p) { + PaintSavedMessages(p, 0, 0, size, size); + }); +} + +QPixmap EmptyUserpic::GenerateSavedMessagesRounded(int size) { + return Generate(size, [&](Painter &p) { + PaintSavedMessagesRounded(p, 0, 0, size, size); + }); +} + InMemoryKey EmptyUserpic::uniqueKey() const { const auto first = (uint64(0xFFFFFFFFU) << 32) | anim::getPremultiplied(_color->c); diff --git a/Telegram/SourceFiles/ui/empty_userpic.h b/Telegram/SourceFiles/ui/empty_userpic.h index 4c8033750..e177baf4e 100644 --- a/Telegram/SourceFiles/ui/empty_userpic.h +++ b/Telegram/SourceFiles/ui/empty_userpic.h @@ -40,6 +40,12 @@ public: int y, int outerWidth, int size); + static void PaintSavedMessagesRounded( + Painter &p, + int x, + int y, + int outerWidth, + int size); static void PaintSavedMessages( Painter &p, int x, @@ -48,6 +54,16 @@ public: int size, const style::color &bg, const style::color &fg); + static void PaintSavedMessagesRounded( + Painter &p, + int x, + int y, + int outerWidth, + int size, + const style::color &bg, + const style::color &fg); + static QPixmap GenerateSavedMessages(int size); + static QPixmap GenerateSavedMessagesRounded(int size); ~EmptyUserpic(); diff --git a/Telegram/SourceFiles/window/notifications_manager.cpp b/Telegram/SourceFiles/window/notifications_manager.cpp index d2cb7de85..2fde58508 100644 --- a/Telegram/SourceFiles/window/notifications_manager.cpp +++ b/Telegram/SourceFiles/window/notifications_manager.cpp @@ -464,6 +464,8 @@ Manager::DisplayOptions Manager::getNotificationOptions(HistoryItem *item) { || (Global::NotifyView() > dbinvShowPreview); result.hideReplyButton = result.hideMessageText || !item + || ((item->out() || item->history()->peer->isSelf()) + && item->isFromScheduled()) || !item->history()->peer->canWrite() || (item->history()->peer->slowmodeSecondsLeft() > 0); return result; @@ -542,20 +544,31 @@ void NativeManager::doShowNotification( 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 peer = item->history()->peer; + const auto scheduled = !options.hideNameAndPhoto + && (item->out() || peer->isSelf()) + && item->isFromScheduled(); + const auto title = options.hideNameAndPhoto + ? qsl("Telegram Desktop") + : (scheduled && peer->isSelf()) + ? tr::lng_notification_reminder(tr::now) + : App::peerName(peer); + const auto subtitle = options.hideNameAndPhoto + ? QString() + : item->notificationHeader(); const auto text = options.hideMessageText ? tr::lng_notification_preview(tr::now) : (forwardedCount < 2 - ? (item->groupId() ? tr::lng_in_dlg_album(tr::now) : item->notificationText()) + ? (item->groupId() + ? tr::lng_in_dlg_album(tr::now) + : item->notificationText()) : tr::lng_forward_messages(tr::now, lt_count, forwardedCount)); doShowNativeNotification( item->history()->peer, item->id, - title, - scheduled ? WrapFromScheduled(subtitle) : subtitle, + scheduled ? WrapFromScheduled(title) : title, + subtitle, text, options.hideNameAndPhoto, options.hideReplyButton); diff --git a/Telegram/SourceFiles/window/notifications_manager_default.cpp b/Telegram/SourceFiles/window/notifications_manager_default.cpp index cafb34912..e2b859684 100644 --- a/Telegram/SourceFiles/window/notifications_manager_default.cpp +++ b/Telegram/SourceFiles/window/notifications_manager_default.cpp @@ -13,6 +13,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/widgets/buttons.h" #include "ui/widgets/input_fields.h" #include "ui/text_options.h" +#include "ui/emoji_config.h" +#include "ui/empty_userpic.h" #include "dialogs/dialogs_layout.h" #include "window/themes/window_theme.h" #include "styles/style_dialogs.h" @@ -74,7 +76,7 @@ Manager::QueuedNotification::QueuedNotification( , author(item->notificationHeader()) , item((forwardedCount < 2) ? item.get() : nullptr) , forwardedCount(forwardedCount) -, fromScheduled(item->out() && item->isFromScheduled()) { +, fromScheduled((item->out() || peer->isSelf()) && item->isFromScheduled()) { } QPixmap Manager::hiddenUserpicPlaceholder() const { @@ -680,8 +682,12 @@ void Notification::updateNotifyDisplay() { p.fillRect(0, st::notifyBorderWidth, st::notifyBorderWidth, h - st::notifyBorderWidth, st::notifyBorder); if (!options.hideNameAndPhoto) { - _history->peer->loadUserpic(); - _history->peer->paintUserpicLeft(p, st::notifyPhotoPos.x(), st::notifyPhotoPos.y(), width(), st::notifyPhotoSize); + if (_fromScheduled && _history->peer->isSelf()) { + Ui::EmptyUserpic::PaintSavedMessages(p, st::notifyPhotoPos.x(), st::notifyPhotoPos.y(), width(), st::notifyPhotoSize); + } else { + _history->peer->loadUserpic(); + _history->peer->paintUserpicLeft(p, st::notifyPhotoPos.x(), st::notifyPhotoPos.y(), width(), st::notifyPhotoSize); + } } else { p.drawPixmap(st::notifyPhotoPos.x(), st::notifyPhotoPos.y(), manager()->hiddenUserpicPlaceholder()); } @@ -689,7 +695,15 @@ void Notification::updateNotifyDisplay() { int32 itemWidth = w - st::notifyPhotoPos.x() - st::notifyPhotoSize - st::notifyTextLeft - st::notifyClosePos.x() - st::notifyClose.width; QRect rectForName(st::notifyPhotoPos.x() + st::notifyPhotoSize + st::notifyTextLeft, st::notifyTextTop, itemWidth, st::msgNameFont->height); + const auto reminder = _fromScheduled && _history->peer->isSelf(); if (!options.hideNameAndPhoto) { + if (_fromScheduled) { + static const auto emoji = Ui::Emoji::Find(QString::fromUtf8("\xF0\x9F\x93\x85")); + const auto size = Ui::Emoji::GetSizeNormal() / cIntRetinaFactor(); + const auto top = rectForName.top() + (st::msgNameFont->height - size) / 2; + Ui::Emoji::Draw(p, emoji, Ui::Emoji::GetSizeNormal(), rectForName.left(), top); + rectForName.setLeft(rectForName.left() + size + st::msgNameFont->spacew); + } if (const auto chatTypeIcon = Dialogs::Layout::ChatTypeIcon(_history->peer, false, false)) { chatTypeIcon->paint(p, rectForName.topLeft(), w); rectForName.setLeft(rectForName.left() + st::dialogsChatTypeSkip); @@ -707,7 +721,9 @@ void Notification::updateNotifyDisplay() { p.setPen(st::dialogsTextFg); p.setFont(st::dialogsTextFont); const auto text = _item - ? _item->inDialogsText(HistoryItem::DrawInDialog::Normal) + ? _item->inDialogsText(reminder + ? HistoryItem::DrawInDialog::WithoutSender + : HistoryItem::DrawInDialog::Normal) : ((!_author.isEmpty() ? textcmdLink(1, _author) : QString()) @@ -724,10 +740,7 @@ void Notification::updateNotifyDisplay() { 0, Qt::LayoutDirectionAuto, }; - itemTextCache.setText( - st::dialogsTextStyle, - _fromScheduled ? WrapFromScheduled(text) : text, - Options); + itemTextCache.setText(st::dialogsTextStyle, text, Options); itemTextCache.drawElided( p, r.left(), @@ -747,12 +760,15 @@ void Notification::updateNotifyDisplay() { } p.setPen(st::dialogsNameFg); - if (!options.hideNameAndPhoto) { - _history->peer->nameText().drawElided(p, rectForName.left(), rectForName.top(), rectForName.width()); - } else { + if (options.hideNameAndPhoto) { p.setFont(st::msgNameFont); static QString notifyTitle = st::msgNameFont->elided(qsl("Telegram Desktop"), rectForName.width()); p.drawText(rectForName.left(), rectForName.top() + st::msgNameFont->ascent, notifyTitle); + } else if (reminder) { + p.setFont(st::msgNameFont); + p.drawText(rectForName.left(), rectForName.top() + st::msgNameFont->ascent, tr::lng_notification_reminder(tr::now)); + } else { + _history->peer->nameText().drawElided(p, rectForName.left(), rectForName.top(), rectForName.width()); } } diff --git a/Telegram/SourceFiles/window/notifications_utilities.cpp b/Telegram/SourceFiles/window/notifications_utilities.cpp index 6bfd7a745..43faaf1dd 100644 --- a/Telegram/SourceFiles/window/notifications_utilities.cpp +++ b/Telegram/SourceFiles/window/notifications_utilities.cpp @@ -10,6 +10,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "platform/platform_specific.h" #include "core/application.h" #include "data/data_peer.h" +#include "ui/empty_userpic.h" #include "styles/style_window.h" namespace Window { @@ -45,7 +46,12 @@ QString CachedUserpics::get(const InMemoryKey &key, PeerData *peer) { } v.path = cWorkingDir() + qsl("tdata/temp/") + QString::number(rand_value(), 16) + qsl(".png"); if (key.first || key.second) { - if (_type == Type::Rounded) { + if (peer->isSelf()) { + const auto method = _type == Type::Rounded + ? Ui::EmptyUserpic::GenerateSavedMessagesRounded + : Ui::EmptyUserpic::GenerateSavedMessages; + method(st::notifyMacPhotoSize).save(v.path, "PNG"); + } else if (_type == Type::Rounded) { peer->saveUserpicRounded(v.path, st::notifyMacPhotoSize); } else { peer->saveUserpic(v.path, st::notifyMacPhotoSize);