Add unread unmuted counter to main menu cover.

This commit is contained in:
John Preston 2020-06-23 17:29:02 +04:00
parent 34ef54e40b
commit 7b0a32b607
12 changed files with 179 additions and 71 deletions

View file

@ -615,9 +615,7 @@ Main::Account &Application::activeAccount() const {
} }
Main::Session *Application::maybeActiveSession() const { Main::Session *Application::maybeActiveSession() const {
return (_domain->started() && activeAccount().sessionExists()) return _domain->started() ? activeAccount().maybeSession() : nullptr;
? &activeAccount().session()
: nullptr;
} }
bool Application::exportPreventsQuit() { bool Application::exportPreventsQuit() {

View file

@ -161,9 +161,8 @@ rpl::producer<> UiIntegration::forcePopupMenuHideRequests() {
QString UiIntegration::convertTagToMimeTag(const QString &tagId) { QString UiIntegration::convertTagToMimeTag(const QString &tagId) {
if (TextUtilities::IsMentionLink(tagId)) { if (TextUtilities::IsMentionLink(tagId)) {
const auto &account = Core::App().activeAccount(); if (const auto session = Core::App().activeAccount().maybeSession()) {
if (account.sessionExists()) { return tagId + ':' + QString::number(session->userId());
return tagId + ':' + QString::number(account.session().userId());
} }
} }
return tagId; return tagId;

View file

@ -1393,9 +1393,8 @@ Updater::~Updater() {
UpdateChecker::UpdateChecker() UpdateChecker::UpdateChecker()
: _updater(GetUpdaterInstance()) { : _updater(GetUpdaterInstance()) {
if (IsAppLaunched() && Core::App().domain().started()) { if (IsAppLaunched() && Core::App().domain().started()) {
const auto &account = Core::App().activeAccount(); if (const auto session = Core::App().activeAccount().maybeSession()) {
if (account.sessionExists()) { _updater->setMtproto(session);
_updater->setMtproto(&account.session());
} }
} }
} }

View file

@ -588,13 +588,8 @@ void paintUnreadBadge(Painter &p, const QRect &rect, const UnreadBadgeStyle &st)
} }
UnreadBadgeStyle::UnreadBadgeStyle() UnreadBadgeStyle::UnreadBadgeStyle()
: align(style::al_right) : size(st::dialogsUnreadHeight)
, active(false)
, selected(false)
, muted(false)
, size(st::dialogsUnreadHeight)
, padding(st::dialogsUnreadPadding) , padding(st::dialogsUnreadPadding)
, sizeId(UnreadBadgeInDialogs)
, font(st::dialogsUnreadFont) { , font(st::dialogsUnreadFont) {
} }

View file

@ -71,14 +71,14 @@ enum UnreadBadgeSize {
struct UnreadBadgeStyle { struct UnreadBadgeStyle {
UnreadBadgeStyle(); UnreadBadgeStyle();
style::align align; style::align align = style::al_right;
bool active; bool active = false;
bool selected; bool selected = false;
bool muted; bool muted = false;
int textTop = 0; int textTop = 0;
int size; int size = 0;
int padding; int padding = 0;
UnreadBadgeSize sizeId; UnreadBadgeSize sizeId = UnreadBadgeInDialogs;
style::font font; style::font font;
}; };
void paintUnreadCount( void paintUnreadCount(

View file

@ -54,8 +54,8 @@ Account::Account(not_null<Domain*> domain, const QString &dataName, int index)
} }
Account::~Account() { Account::~Account() {
if (sessionExists()) { if (const auto session = maybeSession()) {
session().saveSettingsNowIfNeeded(); session->saveSettingsNowIfNeeded();
} }
destroySession(); destroySession();
} }
@ -205,6 +205,10 @@ Session &Account::session() const {
return *_sessionValue.current(); return *_sessionValue.current();
} }
Session *Account::maybeSession() const {
return _sessionValue.current();
}
rpl::producer<Session*> Account::sessionValue() const { rpl::producer<Session*> Account::sessionValue() const {
return _sessionValue.value(); return _sessionValue.value();
} }
@ -316,8 +320,8 @@ SessionSettings *Account::getSessionSettings() {
return _storedSessionSettings return _storedSessionSettings
? _storedSessionSettings.get() ? _storedSessionSettings.get()
: nullptr; : nullptr;
} else if (sessionExists()) { } else if (const auto session = maybeSession()) {
return &session().settings(); return &session->settings();
} }
return nullptr; return nullptr;
} }
@ -412,8 +416,8 @@ void Account::startMtp(std::unique_ptr<MTP::Config> config) {
|| checkForNewSession(from, end); || checkForNewSession(from, end);
})); }));
_mtp->setGlobalFailHandler(::rpcFail([=](const RPCError &error) { _mtp->setGlobalFailHandler(::rpcFail([=](const RPCError &error) {
if (sessionExists()) { if (const auto session = maybeSession()) {
crl::on_main(&session(), [=] { logOut(); }); crl::on_main(session, [=] { logOut(); });
} }
return true; return true;
})); }));
@ -423,9 +427,9 @@ void Account::startMtp(std::unique_ptr<MTP::Config> config) {
} }
}); });
_mtp->setSessionResetHandler([=](MTP::ShiftedDcId shiftedDcId) { _mtp->setSessionResetHandler([=](MTP::ShiftedDcId shiftedDcId) {
if (sessionExists()) { if (const auto session = maybeSession()) {
if (shiftedDcId == _mtp->mainDcId()) { if (shiftedDcId == _mtp->mainDcId()) {
session().updates().getDifference(); session->updates().getDifference();
} }
} }
}); });
@ -445,9 +449,9 @@ void Account::startMtp(std::unique_ptr<MTP::Config> config) {
} }
_storedSessionSettings = nullptr; _storedSessionSettings = nullptr;
if (sessionExists()) { if (const auto session = maybeSession()) {
// Skip all pending self updates so that we won't local().writeSelf. // Skip all pending self updates so that we won't local().writeSelf.
session().changes().sendNotifications(); session->changes().sendNotifications();
} }
_mtpValue = _mtp.get(); _mtpValue = _mtp.get();

View file

@ -73,6 +73,7 @@ public:
[[nodiscard]] bool sessionExists() const; [[nodiscard]] bool sessionExists() const;
[[nodiscard]] Session &session() const; [[nodiscard]] Session &session() const;
[[nodiscard]] Session *maybeSession() const;
[[nodiscard]] rpl::producer<Session*> sessionValue() const; [[nodiscard]] rpl::producer<Session*> sessionValue() const;
[[nodiscard]] rpl::producer<Session*> sessionChanges() const; [[nodiscard]] rpl::producer<Session*> sessionChanges() const;

View file

@ -125,9 +125,9 @@ rpl::producer<Session*> Domain::activeSessionChanges() const {
} }
rpl::producer<Session*> Domain::activeSessionValue() const { rpl::producer<Session*> Domain::activeSessionValue() const {
const auto current = (_accounts.empty() || !active().sessionExists()) const auto current = _accounts.empty()
? nullptr ? nullptr
: &active().session(); : active().maybeSession();
return rpl::single(current) | rpl::then(_activeSessions.events()); return rpl::single(current) | rpl::then(_activeSessions.events());
} }
@ -145,8 +145,8 @@ rpl::producer<> Domain::unreadBadgeChanges() const {
void Domain::notifyUnreadBadgeChanged() { void Domain::notifyUnreadBadgeChanged() {
for (const auto &[index, account] : _accounts) { for (const auto &[index, account] : _accounts) {
if (account->sessionExists()) { if (const auto session = account->maybeSession()) {
account->session().data().notifyUnreadBadgeChanged(); session->data().notifyUnreadBadgeChanged();
} }
} }
} }
@ -155,8 +155,8 @@ void Domain::updateUnreadBadge() {
_unreadBadge = 0; _unreadBadge = 0;
_unreadBadgeMuted = true; _unreadBadgeMuted = true;
for (const auto &[index, account] : _accounts) { for (const auto &[index, account] : _accounts) {
if (account->sessionExists()) { if (const auto session = account->maybeSession()) {
const auto data = &account->session().data(); const auto data = &session->data();
_unreadBadge += data->unreadBadge(); _unreadBadge += data->unreadBadge();
if (!data->unreadBadgeMuted()) { if (!data->unreadBadgeMuted()) {
_unreadBadgeMuted = false; _unreadBadgeMuted = false;

View file

@ -631,9 +631,9 @@ void MainWindow::updateTrayMenu(bool force) {
void MainWindow::onShowAddContact() { void MainWindow::onShowAddContact() {
if (isHidden()) showFromTray(); if (isHidden()) showFromTray();
if (account().sessionExists()) { if (const auto session = account().maybeSession()) {
Ui::show( Ui::show(
Box<AddContactBox>(&account().session()), Box<AddContactBox>(session),
Ui::LayerOption::KeepOther); Ui::LayerOption::KeepOther);
} }
} }
@ -641,11 +641,9 @@ void MainWindow::onShowAddContact() {
void MainWindow::onShowNewGroup() { void MainWindow::onShowNewGroup() {
if (isHidden()) showFromTray(); if (isHidden()) showFromTray();
if (account().sessionExists()) { if (const auto controller = sessionController()) {
Ui::show( Ui::show(
Box<GroupInfoBox>( Box<GroupInfoBox>(controller, GroupInfoBox::Type::Group),
sessionController(),
GroupInfoBox::Type::Group),
Ui::LayerOption::KeepOther); Ui::LayerOption::KeepOther);
} }
} }
@ -653,11 +651,9 @@ void MainWindow::onShowNewGroup() {
void MainWindow::onShowNewChannel() { void MainWindow::onShowNewChannel() {
if (isHidden()) showFromTray(); if (isHidden()) showFromTray();
if (account().sessionExists()) { if (const auto controller = sessionController()) {
Ui::show( Ui::show(
Box<GroupInfoBox>( Box<GroupInfoBox>(controller, GroupInfoBox::Type::Channel),
sessionController(),
GroupInfoBox::Type::Channel),
Ui::LayerOption::KeepOther); Ui::LayerOption::KeepOther);
} }
} }

View file

@ -193,8 +193,8 @@ void MainWindow::checkLockByTerms() {
box->agreeClicks( box->agreeClicks(
) | rpl::start_with_next([=] { ) | rpl::start_with_next([=] {
const auto mention = box ? box->lastClickedMention() : QString(); const auto mention = box ? box->lastClickedMention() : QString();
if (account().sessionExists()) { if (const auto session = account().maybeSession()) {
account().session().api().acceptTerms(id); session->api().acceptTerms(id);
if (!mention.isEmpty()) { if (!mention.isEmpty()) {
MentionClickHandler(mention).onClick({}); MentionClickHandler(mention).onClick({});
} }
@ -242,8 +242,8 @@ void MainWindow::showTermsDecline() {
void MainWindow::showTermsDelete() { void MainWindow::showTermsDelete() {
const auto box = std::make_shared<QPointer<Ui::BoxContent>>(); const auto box = std::make_shared<QPointer<Ui::BoxContent>>();
const auto deleteByTerms = [=] { const auto deleteByTerms = [=] {
if (account().sessionExists()) { if (const auto session = account().maybeSession()) {
account().session().termsDeleteNow(); session->termsDeleteNow();
} else { } else {
Ui::hideLayer(); Ui::hideLayer();
} }

View file

@ -76,9 +76,10 @@ void System::createManager() {
Main::Session *System::findSession(UserId selfId) const { Main::Session *System::findSession(UserId selfId) const {
for (const auto &[index, account] : Core::App().domain().accounts()) { for (const auto &[index, account] : Core::App().domain().accounts()) {
if (account->sessionExists() if (const auto session = account->maybeSession()) {
&& account->session().userId() == selfId) { if (session->userId() == selfId) {
return &account->session(); return session;
}
} }
} }
return nullptr; return nullptr;

View file

@ -60,13 +60,13 @@ namespace {
constexpr auto kMinDiffIntensity = 0.25; constexpr auto kMinDiffIntensity = 0.25;
float64 IntensityOfColor(QColor color) { [[nodicard]] float64 IntensityOfColor(QColor color) {
return (0.299 * color.red() return (0.299 * color.red()
+ 0.587 * color.green() + 0.587 * color.green()
+ 0.114 * color.blue()) / 255.0; + 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 x = r.x(); x < r.x() + r.width(); x++) {
for (auto y = r.y(); y < r.y() + r.height(); y++) { for (auto y = r.y(); y < r.y() + r.height(); y++) {
const auto intensity = IntensityOfColor(QColor(img.pixel(x, 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; 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
namespace Window { namespace Window {
class MainMenu::AccountButton final : public Ui::RippleButton { class MainMenu::AccountButton final
: public Ui::RippleButton
, public base::Subscriber {
public: public:
AccountButton(QWidget *parent, not_null<Main::Account*> account); AccountButton(QWidget *parent, not_null<Main::Account*> account);
@ -106,17 +117,25 @@ class MainMenu::ToggleAccountsButton final : public Ui::AbstractButton {
public: public:
ToggleAccountsButton(QWidget *parent, rpl::producer<bool> toggled); ToggleAccountsButton(QWidget *parent, rpl::producer<bool> toggled);
[[nodiscard]] rpl::producer<int> rightSkip() const { [[nodiscard]] int rightSkip() const {
return _rightSkip.value(); return _rightSkip.current();
} }
private: private:
void paintEvent(QPaintEvent *e) override; void paintEvent(QPaintEvent *e) override;
void paintUnreadBadge(QPainter &p);
void validateUnreadBadge();
[[nodiscard]] QString computeUnreadBadge() const;
rpl::variable<int> _rightSkip; rpl::variable<int> _rightSkip;
Ui::Animations::Simple _toggledAnimation; Ui::Animations::Simple _toggledAnimation;
bool _toggled = false; bool _toggled = false;
QString _unreadBadge;
int _unreadBadgeWidth = 0;
bool _unreadBadgeStale = false;
}; };
class MainMenu::ResetScaleButton final : public Ui::AbstractButton { class MainMenu::ResetScaleButton final : public Ui::AbstractButton {
@ -141,6 +160,13 @@ MainMenu::AccountButton::AccountButton(
+ _st.itemPadding.bottom(); + _st.itemPadding.bottom();
resize(width(), height); resize(width(), height);
subscribe(Window::Theme::Background(), [=](
const Window::Theme::BackgroundUpdate &update) {
if (update.paletteChanged()) {
_userpicKey = {};
}
});
_account->sessionValue( _account->sessionValue(
) | rpl::filter([=](Main::Session *session) { ) | rpl::filter([=](Main::Session *session) {
return (session != nullptr); return (session != nullptr);
@ -252,6 +278,18 @@ MainMenu::ToggleAccountsButton::ToggleAccountsButton(
QWidget *parent, QWidget *parent,
rpl::producer<bool> toggled) rpl::producer<bool> toggled)
: AbstractButton(parent) { : 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( std::move(
toggled toggled
) | rpl::filter([=](bool value) { ) | rpl::filter([=](bool value) {
@ -263,12 +301,13 @@ MainMenu::ToggleAccountsButton::ToggleAccountsButton(
_toggled ? 0. : 1., _toggled ? 0. : 1.,
_toggled ? 1. : 0., _toggled ? 1. : 0.,
st::slideWrapDuration); st::slideWrapDuration);
validateUnreadBadge();
}, lifetime()); }, lifetime());
_toggledAnimation.stop(); _toggledAnimation.stop();
} }
void MainMenu::ToggleAccountsButton::paintEvent(QPaintEvent *e) { void MainMenu::ToggleAccountsButton::paintEvent(QPaintEvent *e) {
auto p = QPainter(this); auto p = Painter(this);
const auto toggled = _toggledAnimation.value(_toggled ? 1. : 0.); const auto toggled = _toggledAnimation.value(_toggled ? 1. : 0.);
const auto x = 0. + width() - st::mainMenuTogglePosition.x(); const auto x = 0. + width() - st::mainMenuTogglePosition.x();
@ -290,7 +329,7 @@ void MainMenu::ToggleAccountsButton::paintEvent(QPaintEvent *e) {
{ x, bottom + stroke - size + stroke }, { x, bottom + stroke - size + stroke },
{ left + stroke, bottom + 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 cosalpha = cos(alpha);
const auto sinalpha = sin(alpha); const auto sinalpha = sin(alpha);
for (auto &point : points) { for (auto &point : points) {
@ -308,6 +347,80 @@ void MainMenu::ToggleAccountsButton::paintEvent(QPaintEvent *e) {
auto hq = PainterHighQualityEnabler(p); auto hq = PainterHighQualityEnabler(p);
p.fillPath(path, st::mainMenuCoverFg); 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) MainMenu::ResetScaleButton::ResetScaleButton(QWidget *parent)
@ -447,7 +560,13 @@ MainMenu::MainMenu(
subscribe(Window::Theme::Background(), [this](const Window::Theme::BackgroundUpdate &update) { subscribe(Window::Theme::Background(), [this](const Window::Theme::BackgroundUpdate &update) {
if (update.type == Window::Theme::BackgroundUpdate::Type::ApplyingTheme) { 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) { if (update.type == Window::Theme::BackgroundUpdate::Type::New) {
refreshBackground(); refreshBackground();
@ -860,20 +979,16 @@ void MainMenu::paintEvent(QPaintEvent *e) {
const auto cover = QRect(0, 0, width(), st::mainMenuCoverHeight) const auto cover = QRect(0, 0, width(), st::mainMenuCoverHeight)
.intersected(e->rect()); .intersected(e->rect());
const auto background = Window::Theme::Background(); const auto isFill = IsFilledCover();
const auto isFill = background->tile()
|| background->colorForFill().has_value()
|| background->isMonoColorImage()
|| background->paper().isPattern()
|| Data::IsLegacy1DefaultWallPaper(background->paper());
if (!isFill && !_background.isNull()) { if (!isFill && !_background.isNull()) {
PainterHighQualityEnabler hq(p); PainterHighQualityEnabler hq(p);
p.drawImage(0, 0, _background); p.drawImage(0, 0, _background);
} }
if (!cover.isEmpty()) { if (!cover.isEmpty()) {
const auto widthText = width() - 2 * st::mainMenuCoverTextLeft; const auto widthText = width()
- st::mainMenuCoverTextLeft
- _toggleAccounts->rightSkip();
if (isFill) { if (isFill) {
p.fillRect(cover, st::mainMenuCoverBg); p.fillRect(cover, st::mainMenuCoverBg);