From 7b0a32b60718568b4cd4cedfca4e218ec0e5e3c4 Mon Sep 17 00:00:00 2001 From: John Preston Date: Tue, 23 Jun 2020 17:29:02 +0400 Subject: [PATCH] Add unread unmuted counter to main menu cover. --- Telegram/SourceFiles/core/application.cpp | 4 +- Telegram/SourceFiles/core/ui_integration.cpp | 5 +- Telegram/SourceFiles/core/update_checker.cpp | 5 +- .../SourceFiles/dialogs/dialogs_layout.cpp | 7 +- Telegram/SourceFiles/dialogs/dialogs_layout.h | 14 +- Telegram/SourceFiles/main/main_account.cpp | 24 +-- Telegram/SourceFiles/main/main_account.h | 1 + Telegram/SourceFiles/main/main_domain.cpp | 12 +- Telegram/SourceFiles/mainwindow.cpp | 16 +- Telegram/SourceFiles/window/main_window.cpp | 8 +- .../window/notifications_manager.cpp | 7 +- .../SourceFiles/window/window_main_menu.cpp | 147 ++++++++++++++++-- 12 files changed, 179 insertions(+), 71 deletions(-) diff --git a/Telegram/SourceFiles/core/application.cpp b/Telegram/SourceFiles/core/application.cpp index 09c2eea64..0e9e81a04 100644 --- a/Telegram/SourceFiles/core/application.cpp +++ b/Telegram/SourceFiles/core/application.cpp @@ -615,9 +615,7 @@ Main::Account &Application::activeAccount() const { } Main::Session *Application::maybeActiveSession() const { - return (_domain->started() && activeAccount().sessionExists()) - ? &activeAccount().session() - : nullptr; + return _domain->started() ? activeAccount().maybeSession() : nullptr; } bool Application::exportPreventsQuit() { diff --git a/Telegram/SourceFiles/core/ui_integration.cpp b/Telegram/SourceFiles/core/ui_integration.cpp index bfb7ad09e..c148b286a 100644 --- a/Telegram/SourceFiles/core/ui_integration.cpp +++ b/Telegram/SourceFiles/core/ui_integration.cpp @@ -161,9 +161,8 @@ rpl::producer<> UiIntegration::forcePopupMenuHideRequests() { QString UiIntegration::convertTagToMimeTag(const QString &tagId) { if (TextUtilities::IsMentionLink(tagId)) { - const auto &account = Core::App().activeAccount(); - if (account.sessionExists()) { - return tagId + ':' + QString::number(account.session().userId()); + if (const auto session = Core::App().activeAccount().maybeSession()) { + return tagId + ':' + QString::number(session->userId()); } } return tagId; diff --git a/Telegram/SourceFiles/core/update_checker.cpp b/Telegram/SourceFiles/core/update_checker.cpp index 9f908c4ce..c291bd209 100644 --- a/Telegram/SourceFiles/core/update_checker.cpp +++ b/Telegram/SourceFiles/core/update_checker.cpp @@ -1393,9 +1393,8 @@ Updater::~Updater() { UpdateChecker::UpdateChecker() : _updater(GetUpdaterInstance()) { if (IsAppLaunched() && Core::App().domain().started()) { - const auto &account = Core::App().activeAccount(); - if (account.sessionExists()) { - _updater->setMtproto(&account.session()); + if (const auto session = Core::App().activeAccount().maybeSession()) { + _updater->setMtproto(session); } } } diff --git a/Telegram/SourceFiles/dialogs/dialogs_layout.cpp b/Telegram/SourceFiles/dialogs/dialogs_layout.cpp index 894fedb5f..3832846f6 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_layout.cpp +++ b/Telegram/SourceFiles/dialogs/dialogs_layout.cpp @@ -588,13 +588,8 @@ void paintUnreadBadge(Painter &p, const QRect &rect, const UnreadBadgeStyle &st) } UnreadBadgeStyle::UnreadBadgeStyle() -: align(style::al_right) -, active(false) -, selected(false) -, muted(false) -, size(st::dialogsUnreadHeight) +: size(st::dialogsUnreadHeight) , padding(st::dialogsUnreadPadding) -, sizeId(UnreadBadgeInDialogs) , font(st::dialogsUnreadFont) { } diff --git a/Telegram/SourceFiles/dialogs/dialogs_layout.h b/Telegram/SourceFiles/dialogs/dialogs_layout.h index 2ef8f29e9..6dcf5eec1 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_layout.h +++ b/Telegram/SourceFiles/dialogs/dialogs_layout.h @@ -71,14 +71,14 @@ enum UnreadBadgeSize { struct UnreadBadgeStyle { UnreadBadgeStyle(); - style::align align; - bool active; - bool selected; - bool muted; + style::align align = style::al_right; + bool active = false; + bool selected = false; + bool muted = false; int textTop = 0; - int size; - int padding; - UnreadBadgeSize sizeId; + int size = 0; + int padding = 0; + UnreadBadgeSize sizeId = UnreadBadgeInDialogs; style::font font; }; void paintUnreadCount( diff --git a/Telegram/SourceFiles/main/main_account.cpp b/Telegram/SourceFiles/main/main_account.cpp index 66f7e8d18..31af4a8aa 100644 --- a/Telegram/SourceFiles/main/main_account.cpp +++ b/Telegram/SourceFiles/main/main_account.cpp @@ -54,8 +54,8 @@ Account::Account(not_null domain, const QString &dataName, int index) } Account::~Account() { - if (sessionExists()) { - session().saveSettingsNowIfNeeded(); + if (const auto session = maybeSession()) { + session->saveSettingsNowIfNeeded(); } destroySession(); } @@ -205,6 +205,10 @@ Session &Account::session() const { return *_sessionValue.current(); } +Session *Account::maybeSession() const { + return _sessionValue.current(); +} + rpl::producer Account::sessionValue() const { return _sessionValue.value(); } @@ -316,8 +320,8 @@ SessionSettings *Account::getSessionSettings() { return _storedSessionSettings ? _storedSessionSettings.get() : nullptr; - } else if (sessionExists()) { - return &session().settings(); + } else if (const auto session = maybeSession()) { + return &session->settings(); } return nullptr; } @@ -412,8 +416,8 @@ void Account::startMtp(std::unique_ptr config) { || checkForNewSession(from, end); })); _mtp->setGlobalFailHandler(::rpcFail([=](const RPCError &error) { - if (sessionExists()) { - crl::on_main(&session(), [=] { logOut(); }); + if (const auto session = maybeSession()) { + crl::on_main(session, [=] { logOut(); }); } return true; })); @@ -423,9 +427,9 @@ void Account::startMtp(std::unique_ptr config) { } }); _mtp->setSessionResetHandler([=](MTP::ShiftedDcId shiftedDcId) { - if (sessionExists()) { + if (const auto session = maybeSession()) { if (shiftedDcId == _mtp->mainDcId()) { - session().updates().getDifference(); + session->updates().getDifference(); } } }); @@ -445,9 +449,9 @@ void Account::startMtp(std::unique_ptr config) { } _storedSessionSettings = nullptr; - if (sessionExists()) { + if (const auto session = maybeSession()) { // Skip all pending self updates so that we won't local().writeSelf. - session().changes().sendNotifications(); + session->changes().sendNotifications(); } _mtpValue = _mtp.get(); diff --git a/Telegram/SourceFiles/main/main_account.h b/Telegram/SourceFiles/main/main_account.h index 551baf55b..99318ae25 100644 --- a/Telegram/SourceFiles/main/main_account.h +++ b/Telegram/SourceFiles/main/main_account.h @@ -73,6 +73,7 @@ public: [[nodiscard]] bool sessionExists() const; [[nodiscard]] Session &session() const; + [[nodiscard]] Session *maybeSession() const; [[nodiscard]] rpl::producer sessionValue() const; [[nodiscard]] rpl::producer sessionChanges() const; diff --git a/Telegram/SourceFiles/main/main_domain.cpp b/Telegram/SourceFiles/main/main_domain.cpp index b84ca49ae..a8db8e4e3 100644 --- a/Telegram/SourceFiles/main/main_domain.cpp +++ b/Telegram/SourceFiles/main/main_domain.cpp @@ -125,9 +125,9 @@ rpl::producer Domain::activeSessionChanges() const { } rpl::producer Domain::activeSessionValue() const { - const auto current = (_accounts.empty() || !active().sessionExists()) + const auto current = _accounts.empty() ? nullptr - : &active().session(); + : active().maybeSession(); return rpl::single(current) | rpl::then(_activeSessions.events()); } @@ -145,8 +145,8 @@ rpl::producer<> Domain::unreadBadgeChanges() const { void Domain::notifyUnreadBadgeChanged() { for (const auto &[index, account] : _accounts) { - if (account->sessionExists()) { - account->session().data().notifyUnreadBadgeChanged(); + if (const auto session = account->maybeSession()) { + session->data().notifyUnreadBadgeChanged(); } } } @@ -155,8 +155,8 @@ void Domain::updateUnreadBadge() { _unreadBadge = 0; _unreadBadgeMuted = true; for (const auto &[index, account] : _accounts) { - if (account->sessionExists()) { - const auto data = &account->session().data(); + if (const auto session = account->maybeSession()) { + const auto data = &session->data(); _unreadBadge += data->unreadBadge(); if (!data->unreadBadgeMuted()) { _unreadBadgeMuted = false; diff --git a/Telegram/SourceFiles/mainwindow.cpp b/Telegram/SourceFiles/mainwindow.cpp index 215e23cfd..12d9b4453 100644 --- a/Telegram/SourceFiles/mainwindow.cpp +++ b/Telegram/SourceFiles/mainwindow.cpp @@ -631,9 +631,9 @@ void MainWindow::updateTrayMenu(bool force) { void MainWindow::onShowAddContact() { if (isHidden()) showFromTray(); - if (account().sessionExists()) { + if (const auto session = account().maybeSession()) { Ui::show( - Box(&account().session()), + Box(session), Ui::LayerOption::KeepOther); } } @@ -641,11 +641,9 @@ void MainWindow::onShowAddContact() { void MainWindow::onShowNewGroup() { if (isHidden()) showFromTray(); - if (account().sessionExists()) { + if (const auto controller = sessionController()) { Ui::show( - Box( - sessionController(), - GroupInfoBox::Type::Group), + Box(controller, GroupInfoBox::Type::Group), Ui::LayerOption::KeepOther); } } @@ -653,11 +651,9 @@ void MainWindow::onShowNewGroup() { void MainWindow::onShowNewChannel() { if (isHidden()) showFromTray(); - if (account().sessionExists()) { + if (const auto controller = sessionController()) { Ui::show( - Box( - sessionController(), - GroupInfoBox::Type::Channel), + Box(controller, GroupInfoBox::Type::Channel), Ui::LayerOption::KeepOther); } } diff --git a/Telegram/SourceFiles/window/main_window.cpp b/Telegram/SourceFiles/window/main_window.cpp index ebe6dad15..e27c69b71 100644 --- a/Telegram/SourceFiles/window/main_window.cpp +++ b/Telegram/SourceFiles/window/main_window.cpp @@ -193,8 +193,8 @@ void MainWindow::checkLockByTerms() { box->agreeClicks( ) | rpl::start_with_next([=] { const auto mention = box ? box->lastClickedMention() : QString(); - if (account().sessionExists()) { - account().session().api().acceptTerms(id); + if (const auto session = account().maybeSession()) { + session->api().acceptTerms(id); if (!mention.isEmpty()) { MentionClickHandler(mention).onClick({}); } @@ -242,8 +242,8 @@ void MainWindow::showTermsDecline() { void MainWindow::showTermsDelete() { const auto box = std::make_shared>(); const auto deleteByTerms = [=] { - if (account().sessionExists()) { - account().session().termsDeleteNow(); + if (const auto session = account().maybeSession()) { + session->termsDeleteNow(); } else { Ui::hideLayer(); } diff --git a/Telegram/SourceFiles/window/notifications_manager.cpp b/Telegram/SourceFiles/window/notifications_manager.cpp index ba728b09f..0eb9aa4d4 100644 --- a/Telegram/SourceFiles/window/notifications_manager.cpp +++ b/Telegram/SourceFiles/window/notifications_manager.cpp @@ -76,9 +76,10 @@ void System::createManager() { Main::Session *System::findSession(UserId selfId) const { for (const auto &[index, account] : Core::App().domain().accounts()) { - if (account->sessionExists() - && account->session().userId() == selfId) { - return &account->session(); + if (const auto session = account->maybeSession()) { + if (session->userId() == selfId) { + return session; + } } } return nullptr; diff --git a/Telegram/SourceFiles/window/window_main_menu.cpp b/Telegram/SourceFiles/window/window_main_menu.cpp index 56c5ab5b5..2494dd5c8 100644 --- a/Telegram/SourceFiles/window/window_main_menu.cpp +++ b/Telegram/SourceFiles/window/window_main_menu.cpp @@ -60,13 +60,13 @@ namespace { constexpr auto kMinDiffIntensity = 0.25; -float64 IntensityOfColor(QColor color) { +[[nodicard]] float64 IntensityOfColor(QColor color) { return (0.299 * color.red() + 0.587 * color.green() + 0.114 * color.blue()) / 255.0; } -bool IsShadowShown(const QImage &img, const QRect r, float64 intensityText) { +[[nodiscard]] bool IsShadowShown(const QImage &img, const QRect r, float64 intensityText) { for (auto x = r.x(); x < r.x() + r.width(); x++) { for (auto y = r.y(); y < r.y() + r.height(); y++) { const auto intensity = IntensityOfColor(QColor(img.pixel(x, y))); @@ -78,11 +78,22 @@ bool IsShadowShown(const QImage &img, const QRect r, float64 intensityText) { return false; } +[[nodiscard]] bool IsFilledCover() { + const auto background = Window::Theme::Background(); + return background->tile() + || background->colorForFill().has_value() + || background->isMonoColorImage() + || background->paper().isPattern() + || Data::IsLegacy1DefaultWallPaper(background->paper()); +} + } // namespace namespace Window { -class MainMenu::AccountButton final : public Ui::RippleButton { +class MainMenu::AccountButton final + : public Ui::RippleButton + , public base::Subscriber { public: AccountButton(QWidget *parent, not_null account); @@ -106,17 +117,25 @@ class MainMenu::ToggleAccountsButton final : public Ui::AbstractButton { public: ToggleAccountsButton(QWidget *parent, rpl::producer toggled); - [[nodiscard]] rpl::producer rightSkip() const { - return _rightSkip.value(); + [[nodiscard]] int rightSkip() const { + return _rightSkip.current(); } private: void paintEvent(QPaintEvent *e) override; + void paintUnreadBadge(QPainter &p); + + void validateUnreadBadge(); + [[nodiscard]] QString computeUnreadBadge() const; rpl::variable _rightSkip; Ui::Animations::Simple _toggledAnimation; bool _toggled = false; + QString _unreadBadge; + int _unreadBadgeWidth = 0; + bool _unreadBadgeStale = false; + }; class MainMenu::ResetScaleButton final : public Ui::AbstractButton { @@ -141,6 +160,13 @@ MainMenu::AccountButton::AccountButton( + _st.itemPadding.bottom(); resize(width(), height); + subscribe(Window::Theme::Background(), [=]( + const Window::Theme::BackgroundUpdate &update) { + if (update.paletteChanged()) { + _userpicKey = {}; + } + }); + _account->sessionValue( ) | rpl::filter([=](Main::Session *session) { return (session != nullptr); @@ -252,6 +278,18 @@ MainMenu::ToggleAccountsButton::ToggleAccountsButton( QWidget *parent, rpl::producer toggled) : AbstractButton(parent) { + rpl::single( + rpl::empty_value() + ) | rpl::then( + Core::App().unreadBadgeChanges() + ) | rpl::start_with_next([=] { + _unreadBadgeStale = true; + if (!_toggled) { + validateUnreadBadge(); + update(); + } + }, lifetime()); + std::move( toggled ) | rpl::filter([=](bool value) { @@ -263,12 +301,13 @@ MainMenu::ToggleAccountsButton::ToggleAccountsButton( _toggled ? 0. : 1., _toggled ? 1. : 0., st::slideWrapDuration); + validateUnreadBadge(); }, lifetime()); _toggledAnimation.stop(); } void MainMenu::ToggleAccountsButton::paintEvent(QPaintEvent *e) { - auto p = QPainter(this); + auto p = Painter(this); const auto toggled = _toggledAnimation.value(_toggled ? 1. : 0.); const auto x = 0. + width() - st::mainMenuTogglePosition.x(); @@ -290,7 +329,7 @@ void MainMenu::ToggleAccountsButton::paintEvent(QPaintEvent *e) { { x, bottom + stroke - size + stroke }, { left + stroke, bottom + stroke } } }; - const auto alpha = -toggled * M_PI; + const auto alpha = (toggled - 1.) * M_PI; const auto cosalpha = cos(alpha); const auto sinalpha = sin(alpha); for (auto &point : points) { @@ -308,6 +347,80 @@ void MainMenu::ToggleAccountsButton::paintEvent(QPaintEvent *e) { auto hq = PainterHighQualityEnabler(p); p.fillPath(path, st::mainMenuCoverFg); + + if (!_toggled) { + paintUnreadBadge(p); + } +} + +void MainMenu::ToggleAccountsButton::paintUnreadBadge(QPainter &p) { + validateUnreadBadge(); + if (_unreadBadge.isEmpty()) { + return; + } + Dialogs::Layout::UnreadBadgeStyle st; + + const auto right = width() - st::mainMenuTogglePosition.x() - st::mainMenuToggleSize * 2; + const auto top = height() - st::mainMenuTogglePosition.y() - st::mainMenuToggleSize; + const auto width = _unreadBadgeWidth; + const auto rectHeight = st.size; + const auto rectWidth = std::max(width + 2 * st.padding, rectHeight); + const auto left = right - rectWidth; + const auto textLeft = left + (rectWidth - width) / 2; + const auto textTop = top + (st.textTop ? st.textTop : (rectHeight - st.font->height) / 2); + + const auto isFill = IsFilledCover(); + + auto hq = PainterHighQualityEnabler(p); + p.setBrush(isFill ? st::mainMenuCloudBg : st::msgServiceBg); + p.setPen(Qt::NoPen); + p.drawRoundedRect(left, top, rectWidth, rectHeight, rectHeight / 2, rectHeight / 2); + + p.setFont(st.font); + p.setPen(isFill ? st::mainMenuCloudFg : st::msgServiceFg); + p.drawText(textLeft, textTop + st.font->ascent, _unreadBadge); +} + +void MainMenu::ToggleAccountsButton::validateUnreadBadge() { + const auto base = st::mainMenuTogglePosition.x() + + 2 * st::mainMenuToggleSize; + if (_toggled) { + _rightSkip = base; + return; + } else if (!_unreadBadgeStale) { + return; + } + _unreadBadge = computeUnreadBadge(); + + Dialogs::Layout::UnreadBadgeStyle st; + _unreadBadgeWidth = st.font->width(_unreadBadge); + const auto rectHeight = st.size; + const auto rectWidth = std::max( + _unreadBadgeWidth + 2 * st.padding, + rectHeight); + _rightSkip = base + rectWidth + st::mainMenuToggleSize; +} + +QString MainMenu::ToggleAccountsButton::computeUnreadBadge() const { + auto count = 0; + const auto active = &Core::App().activeAccount(); + const auto countMessages = Core::App().settings().countUnreadMessages(); + for (const auto &[index, account] : Core::App().domain().accounts()) { + if (account.get() == active) { + continue; + } else if (const auto session = account->maybeSession()) { + const auto state = account->session().data().chatsList()->unreadState(); + count += std::max(state.marks - state.marksMuted, 0) + + (countMessages + ? std::max(state.messages - state.messagesMuted, 0) + : std::max(state.chats - state.chatsMuted, 0)); + } + } + return (count > 99) + ? u"99+"_q + : (count > 0) + ? QString::number(count) + : QString(); } MainMenu::ResetScaleButton::ResetScaleButton(QWidget *parent) @@ -447,7 +560,13 @@ MainMenu::MainMenu( subscribe(Window::Theme::Background(), [this](const Window::Theme::BackgroundUpdate &update) { if (update.type == Window::Theme::BackgroundUpdate::Type::ApplyingTheme) { - refreshMenu(); + if (const auto action = *_nightThemeAction) { + const auto nightMode = Window::Theme::IsNightMode(); + if (action->isChecked() != nightMode) { + action->setChecked(nightMode); + _menu->finishAnimating(); + } + } } if (update.type == Window::Theme::BackgroundUpdate::Type::New) { refreshBackground(); @@ -860,20 +979,16 @@ void MainMenu::paintEvent(QPaintEvent *e) { const auto cover = QRect(0, 0, width(), st::mainMenuCoverHeight) .intersected(e->rect()); - const auto background = Window::Theme::Background(); - const auto isFill = background->tile() - || background->colorForFill().has_value() - || background->isMonoColorImage() - || background->paper().isPattern() - || Data::IsLegacy1DefaultWallPaper(background->paper()); - + const auto isFill = IsFilledCover(); if (!isFill && !_background.isNull()) { PainterHighQualityEnabler hq(p); p.drawImage(0, 0, _background); } if (!cover.isEmpty()) { - const auto widthText = width() - 2 * st::mainMenuCoverTextLeft; + const auto widthText = width() + - st::mainMenuCoverTextLeft + - _toggleAccounts->rightSkip(); if (isFill) { p.fillRect(cover, st::mainMenuCoverBg);