From ea8f01bd7425241bde4f474169fe46bbf43242f8 Mon Sep 17 00:00:00 2001 From: John Preston Date: Fri, 3 Jun 2016 10:20:24 +0300 Subject: [PATCH] Members block almost done in the new profile implementation. Member kicking, upgrade to supergroup and testing is left. --- Telegram/Resources/basic.style | 5 +- Telegram/SourceFiles/apiwrap.cpp | 12 +- Telegram/SourceFiles/app.cpp | 50 +- Telegram/SourceFiles/app.h | 11 +- Telegram/SourceFiles/boxes/contactsbox.cpp | 6 + Telegram/SourceFiles/history.cpp | 9 + Telegram/SourceFiles/mainwidget.cpp | 7 +- Telegram/SourceFiles/observer_peer.h | 1 + Telegram/SourceFiles/profile/profile.style | 31 +- .../profile/profile_actions_widget.cpp | 1 - .../profile/profile_block_widget.h | 3 + .../SourceFiles/profile/profile_cover.cpp | 83 +-- Telegram/SourceFiles/profile/profile_cover.h | 6 +- .../profile/profile_inner_widget.cpp | 19 +- .../profile/profile_members_widget.cpp | 533 +++++++++++++++++- .../profile/profile_members_widget.h | 101 ++++ .../SourceFiles/profile/profile_widget.cpp | 1 - Telegram/SourceFiles/structs.cpp | 2 + Telegram/SourceFiles/structs.h | 14 + 19 files changed, 779 insertions(+), 116 deletions(-) diff --git a/Telegram/Resources/basic.style b/Telegram/Resources/basic.style index 57203df5d..95739e874 100644 --- a/Telegram/Resources/basic.style +++ b/Telegram/Resources/basic.style @@ -58,6 +58,7 @@ adaptiveWideWidth: 1366px; windowBg: #fff; // fallback for background: white windowTextFg: #000; // fallback for text color: black windowSubTextFg: #8a8a8a; // fallback for subtext color: gray +windowActiveTextFg: #1485c2; // fallback for active color: blue online windowShadowFg: #000; // fallback for shadow color wndMinHeight: 480px; @@ -153,8 +154,8 @@ defaultLeftOutlineButton: OutlineButton { textBg: windowBg; textBgOver: #f2f7fa; - textFg: #1485c2; - textFgOver: #1485c2; + textFg: windowActiveTextFg; + textFgOver: windowActiveTextFg; font: normalFont; padding: margins(11px, 6px, 11px, 6px); diff --git a/Telegram/SourceFiles/apiwrap.cpp b/Telegram/SourceFiles/apiwrap.cpp index 7d7ac9b5b..aec56ea5e 100644 --- a/Telegram/SourceFiles/apiwrap.cpp +++ b/Telegram/SourceFiles/apiwrap.cpp @@ -549,9 +549,15 @@ void ApiWrap::lastParticipantsDone(ChannelData *peer, const MTPchannels_ChannelP if (newMembersCount > peer->membersCount()) { peer->setMembersCount(newMembersCount); } - if (!bots && v.isEmpty()) { - peer->setMembersCount(peer->mgInfo->lastParticipants.size()); + if (!bots) { + if (v.isEmpty()) { + peer->setMembersCount(peer->mgInfo->lastParticipants.size()); + } + Notify::PeerUpdate update(peer); + update.flags |= Notify::PeerUpdate::Flag::MembersChanged | Notify::PeerUpdate::Flag::AdminsChanged; + Notify::peerUpdatedDelayed(update); } + peer->mgInfo->botStatus = botStatus; if (App::main()) emit fullPeerUpdated(peer); } @@ -651,12 +657,14 @@ void ApiWrap::kickParticipantDone(KickRequest kick, const MTPUpdates &result, mt if (channel->adminsCount() > 1) { channel->setAdminsCount(channel->adminsCount() - 1); } + Notify::peerUpdatedDelayed(channel, Notify::PeerUpdate::Flag::AdminsChanged); } megagroupInfo->bots.remove(kick.second); if (megagroupInfo->bots.isEmpty() && megagroupInfo->botStatus > 0) { megagroupInfo->botStatus = -1; } } + Notify::peerUpdatedDelayed(kick.first, Notify::PeerUpdate::Flag::MembersChanged); emit fullPeerUpdated(kick.first); } diff --git a/Telegram/SourceFiles/app.cpp b/Telegram/SourceFiles/app.cpp index b159f8e54..ef0e4859e 100644 --- a/Telegram/SourceFiles/app.cpp +++ b/Telegram/SourceFiles/app.cpp @@ -223,11 +223,11 @@ namespace { } } - int32 onlineForSort(UserData *user, int32 now) { + TimeId onlineForSort(UserData *user, TimeId now) { if (isServiceUser(user->id) || user->botInfo) { return -1; } - int32 online = user->onlineTill; + TimeId online = user->onlineTill; if (online <= 0) { switch (online) { case 0: @@ -253,11 +253,14 @@ namespace { return online; } - int32 onlineWillChangeIn(UserData *user, int32 now) { + int32 onlineWillChangeIn(UserData *user, TimeId now) { if (isServiceUser(user->id) || user->botInfo) { return 86400; } - int32 online = user->onlineTill; + return onlineWillChangeIn(user->onlineTill, now); + } + + int32 onlineWillChangeIn(TimeId online, TimeId now) { if (online <= 0) { if (-online > now) return -online - now; return 86400; @@ -277,7 +280,7 @@ namespace { return dNow.secsTo(dTomorrow); } - QString onlineText(UserData *user, int32 now, bool precise) { + QString onlineText(UserData *user, TimeId now, bool precise) { if (isNotificationsUser(user->id)) { return lang(lng_status_service_notifications); } else if (isServiceUser(user->id)) { @@ -285,7 +288,10 @@ namespace { } else if (user->botInfo) { return lang(lng_status_bot); } - int32 online = user->onlineTill; + return onlineText(user->onlineTill, now, precise); + } + + QString onlineText(TimeId online, TimeId now, bool precise) { if (online <= 0) { switch (online) { case 0: return lang(lng_status_offline); @@ -347,11 +353,14 @@ namespace { } } - bool onlineColorUse(UserData *user, int32 now) { + bool onlineColorUse(UserData *user, TimeId now) { if (isServiceUser(user->id) || user->botInfo) { return false; } - int32 online = user->onlineTill; + return onlineColorUse(user->onlineTill, now); + } + + bool onlineColorUse(TimeId online, TimeId now) { if (online <= 0) { switch (online) { case 0: @@ -394,12 +403,8 @@ namespace { status = &emptyStatus; data->contact = -1; - if (canShareThisContact != data->canShareThisContactFast()) { - update.flags |= UpdateFlag::UserCanShareContact; - } - if (wasContact != data->isContact()) { - update.flags |= UpdateFlag::UserIsContact; - } + if (canShareThisContact != data->canShareThisContactFast()) update.flags |= UpdateFlag::UserCanShareContact; + if (wasContact != data->isContact()) update.flags |= UpdateFlag::UserIsContact; } break; case mtpc_user: { const auto &d(user.c_user()); @@ -496,12 +501,8 @@ namespace { } } - if (canShareThisContact != data->canShareThisContactFast()) { - update.flags |= UpdateFlag::UserCanShareContact; - } - if (wasContact != data->isContact()) { - update.flags |= UpdateFlag::UserIsContact; - } + if (canShareThisContact != data->canShareThisContactFast()) update.flags |= UpdateFlag::UserCanShareContact; + if (wasContact != data->isContact()) update.flags |= UpdateFlag::UserIsContact; } break; } @@ -514,6 +515,8 @@ namespace { } else if (data->loadedStatus != PeerData::FullLoaded) { data->loadedStatus = PeerData::FullLoaded; } + + auto oldOnlineTill = data->onlineTill; if (status && !minimal) switch (status->type()) { case mtpc_userStatusEmpty: data->onlineTill = 0; break; case mtpc_userStatusRecently: @@ -526,6 +529,9 @@ namespace { case mtpc_userStatusOffline: data->onlineTill = status->c_userStatusOffline().vwas_online.v; break; case mtpc_userStatusOnline: data->onlineTill = status->c_userStatusOnline().vexpires.v; break; } + if (oldOnlineTill != data->onlineTill) { + update.flags |= UpdateFlag::UserOnlineChanged; + } if (data->contact < 0 && !data->phone().isEmpty() && peerToUser(data->id) != MTP::authedId()) { data->contact = 0; @@ -832,6 +838,7 @@ namespace { } } break; } + Notify::peerUpdatedDelayed(chat, Notify::PeerUpdate::Flag::MembersChanged); if (chat && App::main()) { if (emitPeerUpdated) { App::main()->peerUpdated(chat); @@ -878,6 +885,7 @@ namespace { chat->invalidateParticipants(); chat->count++; } + Notify::peerUpdatedDelayed(chat, Notify::PeerUpdate::Flag::MembersChanged); if (App::main()) { if (emitPeerUpdated) { App::main()->peerUpdated(chat); @@ -894,6 +902,7 @@ namespace { chat->version = d.vversion.v; chat->invalidateParticipants(); App::api()->requestPeer(chat); + Notify::peerUpdatedDelayed(chat, Notify::PeerUpdate::Flag::MembersChanged); if (App::main()) { if (emitPeerUpdated) { App::main()->peerUpdated(chat); @@ -944,6 +953,7 @@ namespace { chat->invalidateParticipants(); chat->count--; } + Notify::peerUpdatedDelayed(chat, Notify::PeerUpdate::Flag::MembersChanged); if (App::main()) { if (emitPeerUpdated) { App::main()->peerUpdated(chat); diff --git a/Telegram/SourceFiles/app.h b/Telegram/SourceFiles/app.h index f0150dbde..e9d344c7e 100644 --- a/Telegram/SourceFiles/app.h +++ b/Telegram/SourceFiles/app.h @@ -59,10 +59,13 @@ namespace App { QString formatPhone(QString phone); - int32 onlineForSort(UserData *user, int32 now); - int32 onlineWillChangeIn(UserData *user, int32 nowOnServer); - QString onlineText(UserData *user, int32 nowOnServer, bool precise = false); - bool onlineColorUse(UserData *user, int32 now); + TimeId onlineForSort(UserData *user, TimeId now); + int32 onlineWillChangeIn(UserData *user, TimeId now); + int32 onlineWillChangeIn(TimeId online, TimeId now); + QString onlineText(UserData *user, TimeId now, bool precise = false); + QString onlineText(TimeId online, TimeId now, bool precise = false); + bool onlineColorUse(UserData *user, TimeId now); + bool onlineColorUse(TimeId online, TimeId now); UserData *feedUsers(const MTPVector &users); // returns last user PeerData *feedChats(const MTPVector &chats); // returns last chat diff --git a/Telegram/SourceFiles/boxes/contactsbox.cpp b/Telegram/SourceFiles/boxes/contactsbox.cpp index aea2517ec..10d7da7c4 100644 --- a/Telegram/SourceFiles/boxes/contactsbox.cpp +++ b/Telegram/SourceFiles/boxes/contactsbox.cpp @@ -202,16 +202,20 @@ void ContactsInner::addAdminDone(const MTPUpdates &result, mtpRequestId req) { _addAdminRequestId = 0; if (_addAdmin && _channel && _channel->isMegagroup()) { + Notify::PeerUpdate update(_channel); if (_channel->mgInfo->lastParticipants.indexOf(_addAdmin) < 0) { _channel->mgInfo->lastParticipants.push_front(_addAdmin); + update.flags |= Notify::PeerUpdate::Flag::MembersChanged; } _channel->mgInfo->lastAdmins.insert(_addAdmin); + update.flags |= Notify::PeerUpdate::Flag::AdminsChanged; if (_addAdmin->botInfo) { _channel->mgInfo->bots.insert(_addAdmin); if (_channel->mgInfo->botStatus != 0 && _channel->mgInfo->botStatus < 2) { _channel->mgInfo->botStatus = 2; } } + Notify::peerUpdatedDelayed(update); } if (_addAdminBox) _addAdminBox->onClose(); emit adminAdded(); @@ -2176,6 +2180,8 @@ void MembersInner::membersReceived(const MTPchannels_ChannelParticipants &result _channel->mgInfo->lastAdmins.insert(_rows.at(i)); } } + + Notify::peerUpdatedDelayed(_channel, Notify::PeerUpdate::Flag::AdminsChanged); } } if (_rows.isEmpty()) { diff --git a/Telegram/SourceFiles/history.cpp b/Telegram/SourceFiles/history.cpp index 061a54eb7..b341b4f93 100644 --- a/Telegram/SourceFiles/history.cpp +++ b/Telegram/SourceFiles/history.cpp @@ -36,6 +36,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #include "apiwrap.h" #include "window/top_bar_widget.h" #include "playerwidget.h" +#include "observer_peer.h" namespace { @@ -1056,6 +1057,7 @@ HistoryItem *History::createItem(const MTPMessage &msg, bool applyServiceAction, if (peer->asChannel()->mgInfo->lastParticipants.indexOf(user) < 0) { peer->asChannel()->mgInfo->lastParticipants.push_front(user); peer->asChannel()->mgInfo->lastParticipantsStatus |= MegagroupInfo::LastParticipantsAdminsOutdated; + Notify::peerUpdatedDelayed(peer, Notify::PeerUpdate::Flag::MembersChanged); } if (user->botInfo) { peer->asChannel()->mgInfo->bots.insert(user); @@ -1074,6 +1076,7 @@ HistoryItem *History::createItem(const MTPMessage &msg, bool applyServiceAction, if (result->from()->isUser()) { if (peer->asChannel()->mgInfo->lastParticipants.indexOf(result->from()->asUser()) < 0) { peer->asChannel()->mgInfo->lastParticipants.push_front(result->from()->asUser()); + Notify::peerUpdatedDelayed(peer, Notify::PeerUpdate::Flag::MembersChanged); } if (result->from()->asUser()->botInfo) { peer->asChannel()->mgInfo->bots.insert(result->from()->asUser()); @@ -1105,6 +1108,7 @@ HistoryItem *History::createItem(const MTPMessage &msg, bool applyServiceAction, int32 index = megagroupInfo->lastParticipants.indexOf(user); if (index >= 0) { megagroupInfo->lastParticipants.removeAt(index); + Notify::peerUpdatedDelayed(peer, Notify::PeerUpdate::Flag::MembersChanged); } if (peer->asChannel()->membersCount() > 1) { peer->asChannel()->setMembersCount(channel->membersCount() - 1); @@ -1117,6 +1121,7 @@ HistoryItem *History::createItem(const MTPMessage &msg, bool applyServiceAction, if (channel->adminsCount() > 1) { channel->setAdminsCount(channel->adminsCount() - 1); } + Notify::peerUpdatedDelayed(peer, Notify::PeerUpdate::Flag::AdminsChanged); } megagroupInfo->bots.remove(user); if (megagroupInfo->bots.isEmpty() && megagroupInfo->botStatus > 0) { @@ -1328,6 +1333,9 @@ HistoryItem *History::addNewItem(HistoryItem *adding, bool newMsg) { if (prev) { lastAuthors->push_front(adding->from()->asUser()); } + if (peer->isMegagroup()) { + Notify::peerUpdatedDelayed(peer, Notify::PeerUpdate::Flag::MembersChanged); + } } } if (adding->definesReplyKeyboard()) { @@ -1520,6 +1528,7 @@ void History::addOlderSlice(const QVector &slice, const QVectorpush_back(item->from()->asUser()); if (peer->isMegagroup()) { peer->asChannel()->mgInfo->lastParticipantsStatus |= MegagroupInfo::LastParticipantsAdminsOutdated; + Notify::peerUpdatedDelayed(peer, Notify::PeerUpdate::Flag::MembersChanged); } } } diff --git a/Telegram/SourceFiles/mainwidget.cpp b/Telegram/SourceFiles/mainwidget.cpp index 325484da5..eed803087 100644 --- a/Telegram/SourceFiles/mainwidget.cpp +++ b/Telegram/SourceFiles/mainwidget.cpp @@ -1681,7 +1681,6 @@ void MainWidget::onParentResize(const QSize &newSize) { void MainWidget::updateOnlineDisplay() { if (this != App::main()) return; _history->updateOnlineDisplay(_history->x(), width() - _history->x() - st::sysBtnDelta * 2 - st::sysCls.img.pxWidth() - st::sysRes.img.pxWidth() - st::sysMin.img.pxWidth()); -// if (_profile) _profile->updateOnlineDisplay(); TODO if (App::wnd()->settingsWidget()) App::wnd()->settingsWidget()->updateOnlineDisplay(); } @@ -3790,7 +3789,10 @@ void MainWidget::updateOnline(bool gotOtherOffline) { _lastSetOnline = ms; _onlineRequest = MTP::send(MTPaccount_UpdateStatus(MTP_bool(!isOnline))); - if (App::self()) App::self()->onlineTill = unixtime() + (isOnline ? (Global::OnlineUpdatePeriod() / 1000) : -1); + if (App::self()) { + App::self()->onlineTill = unixtime() + (isOnline ? (Global::OnlineUpdatePeriod() / 1000) : -1); + Notify::peerUpdatedDelayed(App::self(), Notify::PeerUpdate::Flag::UserOnlineChanged); + } _lastSetOnline = getms(true); @@ -4280,6 +4282,7 @@ void MainWidget::feedUpdate(const MTPUpdate &update) { case mtpc_userStatusOnline: user->onlineTill = d.vstatus.c_userStatusOnline().vexpires.v; break; } App::markPeerUpdated(user); + Notify::peerUpdatedDelayed(user, Notify::PeerUpdate::Flag::UserOnlineChanged); } if (d.vuser_id.v == MTP::authedId()) { if (d.vstatus.type() == mtpc_userStatusOffline || d.vstatus.type() == mtpc_userStatusEmpty) { diff --git a/Telegram/SourceFiles/observer_peer.h b/Telegram/SourceFiles/observer_peer.h index de12a9727..871399021 100644 --- a/Telegram/SourceFiles/observer_peer.h +++ b/Telegram/SourceFiles/observer_peer.h @@ -51,6 +51,7 @@ struct PeerUpdate { UserPhoneChanged = 0x00040000U, UserIsBlocked = 0x00080000U, BotCommandsChanged = 0x00100000U, + UserOnlineChanged = 0x00200000U, ChatCanEdit = 0x00010000U, diff --git a/Telegram/SourceFiles/profile/profile.style b/Telegram/SourceFiles/profile/profile.style index b26eb6f21..2a365c8ea 100644 --- a/Telegram/SourceFiles/profile/profile.style +++ b/Telegram/SourceFiles/profile/profile.style @@ -56,14 +56,15 @@ profileStatusFont: normalFont; profileStatusFg: windowSubTextFg; profileMarginBottom: 30px; +profileActiveBg: #3fb0e4; profileButtonLeft: 27px; profileButtonTop: 88px; profileButtonSkip: 10px; profilePrimaryButton: BoxButton { textFg: #ffffff; textFgOver: #ffffff; - textBg: #3fb0e4; - textBgOver: #3fb0e4; + textBg: profileActiveBg; + textBgOver: profileActiveBg; width: -34px; height: 34px; @@ -80,7 +81,7 @@ profileSecondaryButton: BoxButton(profilePrimaryButton) { textBgOver: #f2f7fa; } profileAddMemberIcon: icon { - { "profile_add_member", #3fb0e4, point(20px, 10px) }, + { "profile_add_member", profileActiveBg, point(20px, 10px) }, }; profileAddMemberButton: BoxButton(profileSecondaryButton) { width: 62px; @@ -88,7 +89,7 @@ profileAddMemberButton: BoxButton(profileSecondaryButton) { } profileDropAreaBg: profileBg; -profileDropAreaFg: #3fb0e4; +profileDropAreaFg: profileActiveBg; profileDropAreaPadding: margins(25px, 3px, 25px, 20px); profileDropAreaTitleFont: font(24px); profileDropAreaTitleTop: 30px; @@ -113,13 +114,13 @@ profileBlockLeftMax: 25px; profileBlockNarrowWidthMin: 220px; profileBlockWideWidthMin: 300px; profileBlockWideWidthMax: 340px; -profileBlockMarginTop: 21px; +profileBlockMarginTop: 14px; profileBlockMarginRight: 10px; profileBlockMarginBottom: 4px; -profileBlockTitleHeight: 22px; +profileBlockTitleHeight: 25px; profileBlockTitleFont: font(14px semibold); profileBlockTitleFg: black; -profileBlockTitlePosition: point(24px, -7px); +profileBlockTitlePosition: point(24px, 0px); profileBlockLabel: flatLabel(labelDefFlat) { textFg: windowSubTextFg; } @@ -137,3 +138,19 @@ profileBlockOneLineWidthMax: 240px; profileInviteLinkText: flatLabel(profileBlockTextPart) { width: 1px; // Required for BreakEverywhere } + +profileLimitReachedSkip: 6px; + +profileMemberHeight: 58px; +profileMemberPaddingLeft: 16px; +profileMemberPhotoSize: 46px; +profileMemberPhotoPosition: point(12px, 6px); +profileMemberNamePosition: point(68px, 11px); +profileMemberNameFg: windowTextFg; +profileMemberStatusPosition: point(68px, 31px); +profileMemberStatusFg: windowSubTextFg; +profileMemberStatusFgOver: windowSubTextFg; +profileMemberStatusFgActive: windowActiveTextFg; +profileMemberAdminIcon: icon { + { "profile_admin_star", profileActiveBg, point(4px, 2px) }, +}; diff --git a/Telegram/SourceFiles/profile/profile_actions_widget.cpp b/Telegram/SourceFiles/profile/profile_actions_widget.cpp index b40b35ad5..df424a4b9 100644 --- a/Telegram/SourceFiles/profile/profile_actions_widget.cpp +++ b/Telegram/SourceFiles/profile/profile_actions_widget.cpp @@ -77,7 +77,6 @@ void ActionsWidget::notifyPeerUpdated(const Notify::PeerUpdate &update) { void ActionsWidget::validateBlockStatus() const { auto needFullPeer = [this]() { - return true; if (auto user = peer()->asUser()) { if (user->blockStatus() == UserData::BlockStatus::Unknown) { return true; diff --git a/Telegram/SourceFiles/profile/profile_block_widget.h b/Telegram/SourceFiles/profile/profile_block_widget.h index fd2230f2d..da93aad75 100644 --- a/Telegram/SourceFiles/profile/profile_block_widget.h +++ b/Telegram/SourceFiles/profile/profile_block_widget.h @@ -37,6 +37,9 @@ public: virtual void setVisibleTopBottom(int visibleTop, int visibleBottom) { } + virtual ~BlockWidget() { + } + signals: void heightUpdated(); diff --git a/Telegram/SourceFiles/profile/profile_cover.cpp b/Telegram/SourceFiles/profile/profile_cover.cpp index e31a83135..7b039d9c8 100644 --- a/Telegram/SourceFiles/profile/profile_cover.cpp +++ b/Telegram/SourceFiles/profile/profile_cover.cpp @@ -38,33 +38,6 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org namespace Profile { namespace { -class OnlineCounter { -public: - OnlineCounter() : _currentTime(unixtime()), _self(App::self()) { - } - void feedUser(UserData *user) { - if (App::onlineForSort(user, _currentTime) > _currentTime) { - ++_result; - if (user != _self) { - _onlyMe = false; - } - } - } - QString result(int fullCount) const { - if (_result > 0 && !_onlyMe) { - return lng_chat_status_members_online(lt_count, fullCount, lt_count_online, _result); - } - return lng_chat_status_members(lt_count, fullCount); - } - -private: - bool _onlyMe = true; - int _result = 0; - int _currentTime; - UserData *_self; - -}; - using UpdateFlag = Notify::PeerUpdate::Flag; const auto ButtonsUpdateFlags = UpdateFlag::UserCanShareContact | UpdateFlag::ChatCanEdit @@ -345,27 +318,17 @@ void CoverWidget::refreshStatusText() { if (_peerUser) { _statusText = App::onlineText(_peerUser, currentTime, true); } else if (_peerChat && _peerChat->amIn()) { - if (_peerChat->noParticipantInfo()) { - App::api()->requestFullPeer(_peer); - if (_statusText.isEmpty()) { - _statusText = lng_chat_status_members(lt_count, _peerChat->count); - } + int fullCount = qMax(_peerChat->count, _peerChat->participants.size()); + if (_onlineCount > 0 && _onlineCount <= fullCount) { + _statusText = lng_chat_status_members_online(lt_count, fullCount, lt_count_online, _onlineCount); } else { - OnlineCounter counter; - auto &participants = _peerChat->participants; - for (auto i = participants.cbegin(), e = participants.cend(); i != e; ++i) { - counter.feedUser(i.key()); - } - _statusText = counter.result(participants.size()); + _statusText = lng_chat_status_members(lt_count, _peerChat->count); } - } else if (isUsingMegagroupOnlineCount()) { - OnlineCounter counter; - for_const (auto user, _peerMegagroup->mgInfo->lastParticipants) { - counter.feedUser(user); - } - _statusText = counter.result(_peerMegagroup->membersCount()); } else if (_peerChannel) { - if (_peerChannel->membersCount() > 0) { + int fullCount = _peerChannel->membersCount(); + if (_onlineCount > 0 && _onlineCount <= fullCount) { + _statusText = lng_chat_status_members_online(lt_count, fullCount, lt_count_online, _onlineCount); + } else if (fullCount > 0 ) { _statusText = lng_chat_status_members(lt_count, _peerChannel->membersCount()); } else { _statusText = lang(_peerChannel->isMegagroup() ? lng_group_status : lng_channel_status); @@ -376,23 +339,6 @@ void CoverWidget::refreshStatusText() { update(); } -bool CoverWidget::isUsingMegagroupOnlineCount() const { - if (!_peerMegagroup || !_peerMegagroup->amIn()) { - return false; - } - - if (_peerMegagroup->membersCount() <= 0 || _peerMegagroup->membersCount() > Global::ChatSizeMax()) { - return false; - } - - if (_peerMegagroup->mgInfo->lastParticipants.isEmpty() || _peerMegagroup->lastParticipantsCountOutdated()) { - App::api()->requestLastParticipants(_peerMegagroup); - return false; - } - - return true; -} - void CoverWidget::refreshButtons() { clearButtons(); if (_peerUser) { @@ -422,8 +368,12 @@ void CoverWidget::setChatButtons() { } void CoverWidget::setMegagroupButtons() { - if (_peerMegagroup->canEditPhoto()) { - addButton(lang(lng_profile_set_group_photo), SLOT(onSetPhoto())); + if (_peerMegagroup->amIn()) { + if (_peerMegagroup->canEditPhoto()) { + addButton(lang(lng_profile_set_group_photo), SLOT(onSetPhoto())); + } + } else { + addButton(lang(lng_profile_join_channel), SLOT(onJoin())); } if (_peerMegagroup->canAddMembers()) { addButton(lang(lng_profile_add_participant), SLOT(onAddMember()), &st::profileAddMemberButton); @@ -463,6 +413,11 @@ void CoverWidget::addButton(const QString &text, const char *slot, const style:: _buttons.push_back({ button, replacement }); } +void CoverWidget::onOnlineCountUpdated(int onlineCount) { + _onlineCount = onlineCount; + refreshStatusText(); +} + void CoverWidget::onSendMessage() { Ui::showPeerHistory(_peer, ShowAtUnreadMsgId); } diff --git a/Telegram/SourceFiles/profile/profile_cover.h b/Telegram/SourceFiles/profile/profile_cover.h index 3a313c223..54fd59dab 100644 --- a/Telegram/SourceFiles/profile/profile_cover.h +++ b/Telegram/SourceFiles/profile/profile_cover.h @@ -54,6 +54,9 @@ public: // It should show it only if it is hidden in the cover. bool shareContactButtonShown() const; +public slots: + void onOnlineCountUpdated(int onlineCount); + private slots: void onPhotoShow(); @@ -83,7 +86,6 @@ private: void moveAndToggleButtons(int newWiddth); void refreshNameText(); void refreshStatusText(); - bool isUsingMegagroupOnlineCount() const; void refreshButtons(); void setUserButtons(); @@ -125,6 +127,8 @@ private: int _photoLeft = 0; // Caching countPhotoLeft() result. int _dividerTop = 0; + int _onlineCount = 0; + FileDialog::QueryId _setPhotoFileQueryId = 0; }; diff --git a/Telegram/SourceFiles/profile/profile_inner_widget.cpp b/Telegram/SourceFiles/profile/profile_inner_widget.cpp index e0423f428..90e7cafd4 100644 --- a/Telegram/SourceFiles/profile/profile_inner_widget.cpp +++ b/Telegram/SourceFiles/profile/profile_inner_widget.cpp @@ -59,7 +59,10 @@ void InnerWidget::createBlocks() { } _blocks.push_back({ new ActionsWidget(this, _peer), BlockSide::Right }); if (chat || megagroup) { - _blocks.push_back({ new MembersWidget(this, _peer), BlockSide::Left }); + auto membersWidget = new MembersWidget(this, _peer); + connect(membersWidget, SIGNAL(onlineCountUpdated(int)), _cover, SLOT(onOnlineCountUpdated(int))); + _cover->onOnlineCountUpdated(membersWidget->onlineCount()); + _blocks.push_back({ membersWidget, BlockSide::Left }); } for_const (auto &blockData, _blocks) { connect(blockData.block, SIGNAL(heightUpdated()), this, SLOT(onBlockHeightUpdated())); @@ -81,11 +84,9 @@ void InnerWidget::setVisibleTopBottom(int visibleTop, int visibleBottom) { decreaseAdditionalHeight(notDisplayedAtBottom); } - //loadProfilePhotos(_visibleTop); - if (peer()->isMegagroup() && !peer()->asChannel()->mgInfo->lastParticipants.isEmpty() && peer()->asChannel()->mgInfo->lastParticipants.size() < peer()->asChannel()->membersCount()) { - if (_visibleTop + (PreloadHeightsCount + 1) * (_visibleBottom - _visibleTop) > height()) { - App::api()->requestLastParticipants(peer()->asChannel(), false); - } + for_const (auto blockData, _blocks) { + int blockY = blockData.block->y(); + blockData.block->setVisibleTopBottom(visibleTop - blockY, visibleBottom - blockY); } } @@ -109,11 +110,11 @@ void InnerWidget::paintEvent(QPaintEvent *e) { if (_mode == Mode::TwoColumn) { int leftHeight = countBlocksHeight(BlockSide::Left); int rightHeight = countBlocksHeight(BlockSide::Right); - int minHeight = qMin(leftHeight, rightHeight); + int shadowHeight = rightHeight;// qMin(leftHeight, rightHeight); int shadowLeft = _blocksLeft + _leftColumnWidth + _columnDivider; int shadowTop = _blocksTop + st::profileBlockMarginTop; - p.fillRect(rtlrect(shadowLeft, shadowTop, st::lineWidth, minHeight - st::profileBlockMarginTop, width()), st::shadowColor); + p.fillRect(rtlrect(shadowLeft, shadowTop, st::lineWidth, shadowHeight - st::profileBlockMarginTop, width()), st::shadowColor); } } @@ -216,7 +217,7 @@ int InnerWidget::resizeGetHeight(int newWidth) { _blocksTop = _cover->y() + _cover->height() + st::profileBlocksTop; _blocksLeft = countBlocksLeft(newWidth); - _columnDivider = _blocksLeft; + _columnDivider = st::profileMemberPaddingLeft; _mode = countBlocksMode(newWidth); _leftColumnWidth = countLeftColumnWidth(newWidth); resizeBlocks(newWidth); diff --git a/Telegram/SourceFiles/profile/profile_members_widget.cpp b/Telegram/SourceFiles/profile/profile_members_widget.cpp index 57dca8f2c..0c84ea26c 100644 --- a/Telegram/SourceFiles/profile/profile_members_widget.cpp +++ b/Telegram/SourceFiles/profile/profile_members_widget.cpp @@ -22,8 +22,12 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #include "profile/profile_members_widget.h" #include "styles/style_profile.h" +#include "mtproto/file_download.h" #include "ui/buttons/left_outline_button.h" +#include "ui/flatlabel.h" #include "boxes/contactsbox.h" +#include "boxes/confirmbox.h" +#include "apiwrap.h" #include "observer_peer.h" #include "lang.h" @@ -31,17 +35,540 @@ namespace Profile { using UpdateFlag = Notify::PeerUpdate::Flag; -MembersWidget::MembersWidget(QWidget *parent, PeerData *peer) : BlockWidget(parent, peer, lang(lng_profile_participants_section)) -{ - show(); +MembersWidget::MembersWidget(QWidget *parent, PeerData *peer) : BlockWidget(parent, peer, lang(lng_profile_participants_section)) { + setMouseTracking(true); + + _removeWidth = st::normalFont->width(lang(lng_profile_kick)); + + _updateOnlineTimer.setSingleShot(true); + connect(&_updateOnlineTimer, SIGNAL(timeout()), this, SLOT(onUpdateOnlineDisplay())); + + auto observeEvents = UpdateFlag::AdminsChanged | UpdateFlag::MembersChanged | UpdateFlag::UserOnlineChanged; + Notify::registerPeerObserver(observeEvents, this, &MembersWidget::notifyPeerUpdated); + FileDownload::registerImageLoadedObserver(this, &MembersWidget::repaintCallback); + + refreshMembers(); +} + +void MembersWidget::repaintCallback() { + update(); +} + +void MembersWidget::notifyPeerUpdated(const Notify::PeerUpdate &update) { + if (update.peer != peer()) { + if (update.flags & UpdateFlag::UserOnlineChanged) { + if (auto user = update.peer->asUser()) { + refreshUserOnline(user); + } + } + return; + } + + if (update.flags & UpdateFlag::MembersChanged) { + refreshMembers(); + contentSizeUpdated(); + } else if (update.flags & UpdateFlag::AdminsChanged) { + if (auto chat = peer()->asChat()) { + for_const (auto member, _list) { + setMemberFlags(member, chat); + } + } else if (auto megagroup = peer()->asMegagroup()) { + for_const (auto member, _list) { + setMemberFlags(member, megagroup); + } + } + } + repaintCallback(); +} + +void MembersWidget::refreshUserOnline(UserData *user) { + auto it = _membersByUser.find(user); + if (it == _membersByUser.cend()) return; + + _now = unixtime(); + + auto member = it.value(); + member->online = !user->botInfo && App::onlineColorUse(user->onlineTill, _now); + member->onlineTill = user->onlineTill; + member->onlineForSort = user->isSelf() ? INT_MAX : App::onlineForSort(user, _now); + member->onlineText = QString(); + + sortMembers(); + update(); +} + +void MembersWidget::setVisibleTopBottom(int visibleTop, int visibleBottom) { + _visibleTop = visibleTop; + _visibleBottom = visibleBottom; + + if (auto megagroup = peer()->asMegagroup()) { + auto megagroupInfo = megagroup->mgInfo; + if (!megagroupInfo->lastParticipants.isEmpty() && megagroupInfo->lastParticipants.size() < megagroup->membersCount()) { + if (_visibleTop + PreloadHeightsCount * (_visibleBottom - _visibleTop) > height()) { + App::api()->requestLastParticipants(megagroup, false); + } + } + } + + preloadUserPhotos(); + updateSelection(); } int MembersWidget::resizeGetHeight(int newWidth) { int newHeight = contentTop(); + if (_limitReachedInfo) { + int limitReachedInfoWidth = newWidth - getListLeft(); + _limitReachedInfo->resizeToWidth(limitReachedInfoWidth); + newHeight = getListTop(); + } + + newHeight += _list.size() * st::profileMemberHeight; + return newHeight; } +void MembersWidget::paintContents(Painter &p) { + int left = getListLeft(); + int top = getListTop(); + if (_limitReachedInfo) { + int infoTop = contentTop(); + int infoHeight = top - infoTop - st::profileLimitReachedSkip; + paintOutlinedRect(p, left, infoTop, width() - left, infoHeight); + } + + _now = unixtime(); + int from = floorclamp(_visibleTop - top, st::profileMemberHeight, 0, _list.size()); + int to = ceilclamp(_visibleBottom - top, st::profileMemberHeight, 0, _list.size()); + for (int i = from; i < to; ++i) { + int y = top + i * st::profileMemberHeight; + bool selected = (i == _selected); + bool selectedKick = selected && _selectedKick; + if (_pressed >= 0) { + if (_pressed != _selected) { + selected = selectedKick = false; + } else if (!_pressedKick) { + _selectedKick = false; + } + } + paintMember(p, left, y, _list.at(i), selected, selectedKick); + } +} + +void MembersWidget::paintOutlinedRect(Painter &p, int x, int y, int w, int h) const { + int outlineWidth = st::defaultLeftOutlineButton.outlineWidth; + p.fillRect(rtlrect(x, y, outlineWidth, h, width()), st::defaultLeftOutlineButton.outlineFgOver); + p.fillRect(rtlrect(x + outlineWidth, y, w - outlineWidth, h, width()), st::defaultLeftOutlineButton.textBgOver); +} + +void MembersWidget::mouseMoveEvent(QMouseEvent *e) { + _mousePosition = e->globalPos(); + updateSelection(); +} + +void MembersWidget::mousePressEvent(QMouseEvent *e) { + _mousePosition = e->globalPos(); + updateSelection(); + + _pressed = _selected; + _pressedKick = _selectedKick; +} + +void MembersWidget::mouseReleaseEvent(QMouseEvent *e) { + _mousePosition = e->globalPos(); + updateSelection(); + + auto pressed = _pressed; + auto pressedKick = _pressedKick; + _pressed = -1; + _pressedKick = false; + if (pressed >= 0 && pressed < _list.size() && pressed == _selected && pressedKick == _selectedKick) { + auto member = _list.at(pressed); + if (pressedKick) { + _kicking = member->user; + ConfirmBox *box = new ConfirmBox(lng_profile_sure_kick(lt_user, _kicking->firstName), lang(lng_box_remove)); + connect(box, SIGNAL(confirmed()), this, SLOT(onKickConfirm())); + Ui::showLayer(box); + } else { + Ui::showPeerProfile(member->user); + } + } + setCursor(_selectedKick ? style::cur_pointer : style::cur_default); + repaintSelectedRow(); +} + +void MembersWidget::enterEvent(QEvent *e) { + _mousePosition = QCursor::pos(); + updateSelection(); +} + +void MembersWidget::leaveEvent(QEvent *e) { + _mousePosition = QPoint(-1, -1); + updateSelection(); +} + +void MembersWidget::updateSelection() { + int selected = -1; + bool selectedKick = false; + + auto mouse = mapFromGlobal(_mousePosition); + if (rtl()) mouse.setX(width() - mouse.x()); + int left = getListLeft(); + int top = getListTop(); + if (mouse.x() >= left && mouse.x() < width() && mouse.y() >= top) { + selected = (mouse.y() - top) / st::profileMemberHeight; + if (selected >= _list.size()) { + selected = -1; + } else if (_list.at(selected)->canBeKicked) { + int skip = st::profileMemberPhotoPosition.x(); + int nameLeft = left + st::profileMemberNamePosition.x(); + int nameTop = top + _selected * st::profileMemberHeight + st::profileMemberNamePosition.y(); + int nameWidth = width() - nameLeft - skip; + if (mouse.x() >= nameLeft + nameWidth - _removeWidth && mouse.x() < nameLeft + nameWidth) { + if (mouse.y() >= nameTop && mouse.y() < nameTop + st::normalFont->height) { + selectedKick = true; + } + } + } + } + + setSelected(selected, selectedKick); +} + +void MembersWidget::setSelected(int selected, bool selectedKick) { + if (_selected == selected && _selectedKick == selectedKick) { + return; + } + + repaintSelectedRow(); + if (_selectedKick != selectedKick) { + _selectedKick = selectedKick; + if (_pressed < 0) { + setCursor(_selectedKick ? style::cur_pointer : style::cur_default); + } + } + if (_selected != selected) { + _selected = selected; + repaintSelectedRow(); + } +} + +void MembersWidget::repaintSelectedRow() { + if (_selected >= 0) { + int left = getListLeft(); + rtlupdate(left, getListTop() + _selected * st::profileMemberHeight, width() - left, st::profileMemberHeight); + } +} + +int MembersWidget::getListLeft() const { + return st::profileBlockTitlePosition.x() - st::profileMemberPaddingLeft; +} + +int MembersWidget::getListTop() const { + int result = contentTop(); + if (_limitReachedInfo) { + result += st::defaultLeftOutlineButton.padding.top(); + result += _limitReachedInfo->height(); + result += st::defaultLeftOutlineButton.padding.bottom(); + result += st::profileLimitReachedSkip; + } + return result; +} + +void MembersWidget::refreshMembers() { + _now = unixtime(); + if (auto chat = peer()->asChat()) { + checkSelfAdmin(chat); + if (chat->noParticipantInfo()) { + App::api()->requestFullPeer(chat); + } + fillChatMembers(chat); + } else if (auto megagroup = peer()->asMegagroup()) { + checkSelfAdmin(megagroup); + auto megagroupInfo = megagroup->mgInfo; + if (megagroupInfo->lastParticipants.isEmpty() || megagroup->lastParticipantsCountOutdated()) { + App::api()->requestLastParticipants(megagroup); + } + fillMegagroupMembers(megagroup); + } + sortMembers(); + + refreshVisibility(); +} + +void MembersWidget::checkSelfAdmin(ChatData *chat) { + if (chat->participants.isEmpty()) return; + + auto self = App::self(); + if (chat->amAdmin() && !chat->admins.contains(self)) { + chat->admins.insert(self); + } else if (!chat->amAdmin() && chat->admins.contains(self)) { + chat->admins.remove(self); + } +} + +void MembersWidget::checkSelfAdmin(ChannelData *megagroup) { + if (megagroup->mgInfo->lastParticipants.isEmpty()) return; + + bool amAdmin = (megagroup->amCreator() || megagroup->amEditor()); + auto self = App::self(); + if (amAdmin && !megagroup->mgInfo->lastAdmins.contains(self)) { + megagroup->mgInfo->lastAdmins.insert(self); + } else if (!amAdmin && megagroup->mgInfo->lastAdmins.contains(self)) { + megagroup->mgInfo->lastAdmins.remove(self); + } +} + +void MembersWidget::preloadUserPhotos() { + int top = getListTop(); + int preloadFor = (_visibleBottom - _visibleTop) * PreloadHeightsCount; + int from = floorclamp(_visibleTop - top, st::profileMemberHeight, 0, _list.size()); + int to = ceilclamp(_visibleBottom + preloadFor - top, st::profileMemberHeight, 0, _list.size()); + for (int i = from; i < to; ++i) { + _list.at(i)->user->loadUserpic(); + } +} + +void MembersWidget::refreshVisibility() { + setVisible(!_list.isEmpty()); +} + +void MembersWidget::sortMembers() { + if (!_sortByOnline || _list.isEmpty()) return; + + qSort(_list.begin(), _list.end(), [](Member *a, Member *b) -> bool { + return a->onlineForSort > b->onlineForSort; + }); + + updateOnlineCount(); +} + +void MembersWidget::updateOnlineCount() { + bool onlyMe = true; + int newOnlineCount = 0; + for_const (auto member, _list) { + bool isOnline = !member->user->botInfo && App::onlineColorUse(member->onlineTill, _now); + if (member->online != isOnline) { + member->online = isOnline; + member->onlineText = QString(); + } + if (member->online) { + ++newOnlineCount; + if (!member->user->isSelf()) { + onlyMe = false; + } + } + } + if (newOnlineCount == 1 && onlyMe) { + newOnlineCount = 0; + } + if (_onlineCount != newOnlineCount) { + _onlineCount = newOnlineCount; + emit onlineCountUpdated(_onlineCount); + } +} + +MembersWidget::Member *MembersWidget::addUser(ChatData *chat, UserData *user) { + auto member = getMember(user); + setMemberFlags(member, chat); + _list.push_back(member); + return member; +} + +void MembersWidget::fillChatMembers(ChatData *chat) { + if (chat->participants.isEmpty()) return; + + _list.clear(); + if (!chat->amIn()) return; + + _sortByOnline = true; + + _list.reserve(chat->participants.size()); + addUser(chat, App::self())->onlineForSort = INT_MAX; // Put me on the first place. + for (auto i = chat->participants.cbegin(), e = chat->participants.cend(); i != e; ++i) { + auto user = i.key(); + if (!user->isSelf()) { + addUser(chat, user); + } + } +} + +void MembersWidget::setMemberFlags(Member *member, ChatData *chat) { + if (member->user->id == peerFromUser(MTP::authedId())) { + member->canBeKicked = false; + } else if (chat->amCreator() || (chat->amAdmin() && !member->isAdmin)) { + member->canBeKicked = true; + } else { + member->canBeKicked = chat->invitedByMe.contains(member->user); + } + member->isAdmin = chat->admins.contains(member->user); +} + +MembersWidget::Member *MembersWidget::addUser(ChannelData *megagroup, UserData *user) { + auto member = getMember(user); + setMemberFlags(member, megagroup); + _list.push_back(member); + return member; +} + +void MembersWidget::fillMegagroupMembers(ChannelData *megagroup) { + t_assert(megagroup->mgInfo != nullptr); + if (megagroup->mgInfo->lastParticipants.isEmpty()) return; + + if (!megagroup->amIn()) { + _list.clear(); + return; + } + + _sortByOnline = (megagroup->membersCount() > 0 && megagroup->membersCount() <= Global::ChatSizeMax()); + + auto &membersList = megagroup->mgInfo->lastParticipants; + if (_sortByOnline) { + _list.clear(); + _list.reserve(membersList.size()); + addUser(megagroup, App::self())->onlineForSort = INT_MAX; + } else if (membersList.size() >= _list.size()) { + if (addUsersToEnd(megagroup)) { + return; + } + } + if (!_sortByOnline) { + _list.clear(); + _list.reserve(membersList.size()); + } + for_const (auto user, membersList) { + if (!_sortByOnline || !user->isSelf()) { + addUser(megagroup, user); + } + } +} + +bool MembersWidget::addUsersToEnd(ChannelData *megagroup) { + auto &membersList = megagroup->mgInfo->lastParticipants; + for (int i = 0, count = _list.size(); i < count; ++i) { + if (_list.at(i)->user != membersList.at(i)) { + return false; + } + } + _list.reserve(membersList.size()); + for (int i = _list.size(), count = membersList.size(); i < count; ++i) { + addUser(megagroup, membersList.at(i)); + } + return true; +} + +void MembersWidget::setMemberFlags(Member *member, ChannelData *megagroup) { + if (member->user->isSelf()) { + member->canBeKicked = false; + } else if (megagroup->amCreator() || (megagroup->amEditor() && !member->isAdmin)) { + member->canBeKicked = true; + } else { + member->canBeKicked = false; + } + member->isAdmin = megagroup->mgInfo->lastAdmins.contains(member->user); +} + +MembersWidget::Member *MembersWidget::getMember(UserData *user) { + auto it = _membersByUser.constFind(user); + if (it == _membersByUser.cend()) { + auto member = new Member(user); + it = _membersByUser.insert(user, member); + member->online = !user->botInfo && App::onlineColorUse(user->onlineTill, _now); + member->onlineTill = user->onlineTill; + member->onlineForSort = App::onlineForSort(user, _now); + } + return it.value(); +} + +void MembersWidget::paintMember(Painter &p, int x, int y, Member *member, bool selected, bool selectedKick) { + if (selected) { + paintOutlinedRect(p, x, y, width() - x, st::profileMemberHeight); + } + int skip = st::profileMemberPhotoPosition.x(); + + member->user->paintUserpicLeft(p, st::profileMemberPhotoSize, x + st::profileMemberPhotoPosition.x(), y + st::profileMemberPhotoPosition.y(), width()); + + if (member->name.isEmpty()) { + member->name.setText(st::semiboldFont, App::peerName(member->user), _textNameOptions); + } + int nameLeft = x + st::profileMemberNamePosition.x(); + int nameTop = y + st::profileMemberNamePosition.y(); + int nameWidth = width() - nameLeft - skip; + if (member->canBeKicked && selected) { + p.setFont(selectedKick ? st::normalFont->underline() : st::normalFont); + p.setPen(st::windowActiveTextFg); + p.drawTextLeft(nameLeft + nameWidth - _removeWidth, nameTop, width(), lang(lng_profile_kick), _removeWidth); + nameWidth -= _removeWidth + skip; + } + if (member->isAdmin) { + nameWidth -= st::profileMemberAdminIcon.width(); + int iconLeft = nameLeft + qMin(nameWidth, member->name.maxWidth()); + st::profileMemberAdminIcon.paint(p, QPoint(iconLeft, nameTop), width()); + } + p.setPen(st::profileMemberNameFg); + member->name.drawLeftElided(p, nameLeft, nameTop, nameWidth, width()); + + if (member->onlineText.isEmpty() || (member->onlineTextTill <= _now)) { + if (member->user->botInfo) { + bool seesAllMessages = (member->user->botInfo->readsAllHistory || member->isAdmin); + member->onlineText = lang(seesAllMessages ? lng_status_bot_reads_all : lng_status_bot_not_reads_all); + member->onlineTextTill = _now + 86400; + } else { + member->online = App::onlineColorUse(member->onlineTill, _now); + member->onlineText = App::onlineText(member->onlineTill, _now); + member->onlineTextTill = _now + App::onlineWillChangeIn(member->onlineTill, _now); + } + } + if (_updateOnlineAt <= _now || _updateOnlineAt > member->onlineTextTill) { + _updateOnlineAt = member->onlineTextTill; + _updateOnlineTimer.start((_updateOnlineAt - _now + 1) * 1000); + } + + if (member->online) { + p.setPen(st::profileMemberStatusFgActive); + } else { + p.setPen(selected ? st::profileMemberStatusFgOver : st::profileMemberStatusFg); + } + p.setFont(st::normalFont); + p.drawTextLeft(x + st::profileMemberStatusPosition.x(), y + st::profileMemberStatusPosition.y(), width(), member->onlineText); +} + +void MembersWidget::onKickConfirm() { + +} + +void MembersWidget::onUpdateOnlineDisplay() { + if (_sortByOnline) { + _now = unixtime(); + + bool changed = false; + for_const (auto member, _list) { + if (!member->online) { + if (!member->user->isSelf()) { + continue; + } else { + break; + } + } + bool isOnline = !member->user->botInfo && App::onlineColorUse(member->onlineTill, _now); + if (!isOnline) { + changed = true; + } + } + if (changed) { + updateOnlineCount(); + } + } + update(); +} + +MembersWidget::~MembersWidget() { + auto members = createAndSwap(_membersByUser); + for_const (auto member, members) { + delete member; + } +} + ChannelMembersWidget::ChannelMembersWidget(QWidget *parent, PeerData *peer) : BlockWidget(parent, peer, lang(lng_profile_participants_section)) { auto observeEvents = UpdateFlag::ChannelCanViewAdmins | UpdateFlag::ChannelCanViewMembers diff --git a/Telegram/SourceFiles/profile/profile_members_widget.h b/Telegram/SourceFiles/profile/profile_members_widget.h index ef82b90cd..8cd729244 100644 --- a/Telegram/SourceFiles/profile/profile_members_widget.h +++ b/Telegram/SourceFiles/profile/profile_members_widget.h @@ -22,6 +22,8 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #include "profile/profile_block_widget.h" +class FlatLabel; + namespace Ui { class LeftOutlineButton; } // namespace Ui @@ -33,13 +35,112 @@ struct PeerUpdate; namespace Profile { class MembersWidget : public BlockWidget { + Q_OBJECT + public: MembersWidget(QWidget *parent, PeerData *peer); + void setVisibleTopBottom(int visibleTop, int visibleBottom) override; + int onlineCount() const { + return _onlineCount; + } + + ~MembersWidget(); + protected: // Resizes content and counts natural widget height for the desired width. int resizeGetHeight(int newWidth) override; + void paintContents(Painter &p) override; + + void mouseMoveEvent(QMouseEvent *e) override; + void mousePressEvent(QMouseEvent *e) override; + void mouseReleaseEvent(QMouseEvent *e) override; + void enterEvent(QEvent *e) override; + void enterFromChildEvent(QEvent *e) override { + enterEvent(e); + } + void leaveEvent(QEvent *e) override; + void leaveToChildEvent(QEvent *e) override { + leaveEvent(e); + } + +signals: + void onlineCountUpdated(int onlineCount); + +private slots: + void onKickConfirm(); + void onUpdateOnlineDisplay(); + +private: + // Observed notifications. + void notifyPeerUpdated(const Notify::PeerUpdate &update); + void repaintCallback(); + + void preloadUserPhotos(); + + void refreshMembers(); + void fillChatMembers(ChatData *chat); + void fillMegagroupMembers(ChannelData *megagroup); + void sortMembers(); + void updateOnlineCount(); + void checkSelfAdmin(ChatData *chat); + void checkSelfAdmin(ChannelData *megagroup); + + void refreshVisibility(); + void updateSelection(); + void setSelected(int selected, bool selectedKick); + void repaintSelectedRow(); + void refreshUserOnline(UserData *user); + + int getListLeft() const; + int getListTop() const; + + struct Member { + Member(UserData *user) : user(user) { + } + UserData *user; + Text name; + QString onlineText; + TimeId onlineTextTill = 0; + TimeId onlineTill = 0; + TimeId onlineForSort = 0; + bool online = false; + bool isAdmin = false; + bool canBeKicked = false; + }; + Member *getMember(UserData *user); + void paintMember(Painter &p, int x, int y, Member *member, bool selected, bool selectedKick); + void paintOutlinedRect(Painter &p, int x, int y, int w, int h) const; + void setMemberFlags(Member *member, ChatData *chat); + Member *addUser(ChatData *chat, UserData *user); + void setMemberFlags(Member *member, ChannelData *megagroup); + Member *addUser(ChannelData *megagroup, UserData *user); + bool addUsersToEnd(ChannelData *megagroup); + + ChildWidget _limitReachedInfo = { nullptr }; + + QList _list; + QMap _membersByUser; + bool _sortByOnline = false; + TimeId _now = 0; + + int _visibleTop = 0; + int _visibleBottom = 0; + + int _selected = -1; + int _pressed = -1; + bool _selectedKick = false; + bool _pressedKick = false; + UserData *_kicking = nullptr; + QPoint _mousePosition; + + int _onlineCount = 0; + TimeId _updateOnlineAt = 0; + QTimer _updateOnlineTimer; + + int _removeWidth = 0; + }; class ChannelMembersWidget : public BlockWidget { diff --git a/Telegram/SourceFiles/profile/profile_widget.cpp b/Telegram/SourceFiles/profile/profile_widget.cpp index 2ee66562f..c4a98ba00 100644 --- a/Telegram/SourceFiles/profile/profile_widget.cpp +++ b/Telegram/SourceFiles/profile/profile_widget.cpp @@ -46,7 +46,6 @@ Widget::Widget(QWidget *parent, PeerData *peer) : Window::SectionWidget(parent) _scroll->move(0, _fixedBar->height()); _scroll->show(); - connect(_scroll, SIGNAL(scrolled()), _inner, SLOT(updateSelected())); connect(_scroll, SIGNAL(scrolled()), this, SLOT(onScroll())); connect(_inner, SIGNAL(cancelled()), _fixedBar, SLOT(onBack())); } diff --git a/Telegram/SourceFiles/structs.cpp b/Telegram/SourceFiles/structs.cpp index 3494471a4..e1bd58de1 100644 --- a/Telegram/SourceFiles/structs.cpp +++ b/Telegram/SourceFiles/structs.cpp @@ -380,9 +380,11 @@ void UserData::madeAction() { if (onlineTill <= 0 && -onlineTill < t) { onlineTill = -t - SetOnlineAfterActivity; App::markPeerUpdated(this); + Notify::peerUpdatedDelayed(this, Notify::PeerUpdate::Flag::UserOnlineChanged); } else if (onlineTill > 0 && onlineTill < t + 1) { onlineTill = t + SetOnlineAfterActivity; App::markPeerUpdated(this); + Notify::peerUpdatedDelayed(this, Notify::PeerUpdate::Flag::UserOnlineChanged); } } diff --git a/Telegram/SourceFiles/structs.h b/Telegram/SourceFiles/structs.h index 3cf2906d8..6432c00be 100644 --- a/Telegram/SourceFiles/structs.h +++ b/Telegram/SourceFiles/structs.h @@ -270,6 +270,8 @@ public: const ChatData *asChat() const; ChannelData *asChannel(); const ChannelData *asChannel() const; + ChannelData *asMegagroup(); + const ChannelData *asMegagroup() const; ChatData *migrateFrom() const; ChannelData *migrateTo() const; @@ -882,6 +884,18 @@ inline const ChannelData *PeerData::asChannel() const { inline const ChannelData *asChannel(const PeerData *peer) { return peer ? peer->asChannel() : nullptr; } +inline ChannelData *PeerData::asMegagroup() { + return isMegagroup() ? static_cast(this) : nullptr; +} +inline ChannelData *asMegagroup(PeerData *peer) { + return peer ? peer->asMegagroup() : nullptr; +} +inline const ChannelData *PeerData::asMegagroup() const { + return isMegagroup() ? static_cast(this) : nullptr; +} +inline const ChannelData *asMegagroup(const PeerData *peer) { + return peer ? peer->asMegagroup() : nullptr; +} inline bool isMegagroup(const PeerData *peer) { return peer ? peer->isMegagroup() : false; }