Show stories in chats list userpics.

This commit is contained in:
John Preston 2023-07-04 20:13:56 +04:00
parent 9a29807276
commit d7d8847c1d
11 changed files with 259 additions and 33 deletions

View file

@ -2524,6 +2524,10 @@ void Updates::feedUpdate(const MTPUpdate &update) {
_session->data().stories().apply(update.c_updateStory()); _session->data().stories().apply(update.c_updateStory());
} break; } break;
case mtpc_updateReadStories: {
_session->data().stories().apply(update.c_updateReadStories());
} break;
} }
} }

View file

@ -525,6 +525,13 @@ not_null<UserData*> Session::processUser(const MTPUser &data) {
| Flag::DiscardMinPhoto | Flag::DiscardMinPhoto
| Flag::StoriesHidden | Flag::StoriesHidden
: Flag()); : Flag());
const auto storiesState = minimal
? std::optional<Data::Stories::PeerSourceState>()
: data.is_stories_unavailable()
? Data::Stories::PeerSourceState()
: !data.vstories_max_id()
? std::optional<Data::Stories::PeerSourceState>()
: stories().peerSourceState(result, data.vstories_max_id()->v);
const auto flagsSet = (data.is_deleted() ? Flag::Deleted : Flag()) const auto flagsSet = (data.is_deleted() ? Flag::Deleted : Flag())
| (data.is_verified() ? Flag::Verified : Flag()) | (data.is_verified() ? Flag::Verified : Flag())
| (data.is_scam() ? Flag::Scam : Flag()) | (data.is_scam() ? Flag::Scam : Flag())
@ -551,6 +558,13 @@ not_null<UserData*> Session::processUser(const MTPUser &data) {
MTP_long(data.vaccess_hash().value_or_empty())); MTP_long(data.vaccess_hash().value_or_empty()));
} }
} else { } else {
if (storiesState) {
result->setStoriesState(!storiesState->maxId
? UserData::StoriesState::None
: (storiesState->maxId > storiesState->readTill)
? UserData::StoriesState::HasUnread
: UserData::StoriesState::HasRead);
}
if (data.is_self()) { if (data.is_self()) {
result->input = MTP_inputPeerSelf(); result->input = MTP_inputPeerSelf();
result->inputUser = MTP_inputUserSelf(); result->inputUser = MTP_inputUserSelf();

View file

@ -136,7 +136,7 @@ void Stories::apply(const MTPDupdateStory &data) {
i->second.ids.emplace(idDates); i->second.ids.emplace(idDates);
const auto nowInfo = i->second.info(); const auto nowInfo = i->second.info();
if (user->isSelf() && i->second.readTill < idDates.id) { if (user->isSelf() && i->second.readTill < idDates.id) {
i->second.readTill = idDates.id; _readTill[user->id] = i->second.readTill = idDates.id;
} }
if (wasInfo == nowInfo) { if (wasInfo == nowInfo) {
return; return;
@ -158,6 +158,11 @@ void Stories::apply(const MTPDupdateStory &data) {
refreshInList(StorySourcesList::NotHidden); refreshInList(StorySourcesList::NotHidden);
} }
_sourceChanged.fire_copy(peerId); _sourceChanged.fire_copy(peerId);
updateUserStoriesState(user);
}
void Stories::apply(const MTPDupdateReadStories &data) {
bumpReadTill(peerFromUser(data.vuser_id()), data.vmax_id().v);
} }
void Stories::apply(not_null<PeerData*> peer, const MTPUserStories *data) { void Stories::apply(not_null<PeerData*> peer, const MTPUserStories *data) {
@ -166,6 +171,7 @@ void Stories::apply(not_null<PeerData*> peer, const MTPUserStories *data) {
applyDeletedFromSources(peer->id, StorySourcesList::Hidden); applyDeletedFromSources(peer->id, StorySourcesList::Hidden);
_all.erase(peer->id); _all.erase(peer->id);
_sourceChanged.fire_copy(peer->id); _sourceChanged.fire_copy(peer->id);
updateUserStoriesState(peer);
} else { } else {
parseAndApply(*data); parseAndApply(*data);
} }
@ -281,6 +287,7 @@ void Stories::parseAndApply(const MTPUserStories &stories) {
} else if (user->isSelf()) { } else if (user->isSelf()) {
result.readTill = result.ids.back().id; result.readTill = result.ids.back().id;
} }
_readTill[peerId] = result.readTill;
const auto info = result.info(); const auto info = result.info();
const auto i = _all.find(peerId); const auto i = _all.find(peerId);
if (i != end(_all)) { if (i != end(_all)) {
@ -317,6 +324,7 @@ void Stories::parseAndApply(const MTPUserStories &stories) {
applyDeletedFromSources(peerId, StorySourcesList::Hidden); applyDeletedFromSources(peerId, StorySourcesList::Hidden);
} }
_sourceChanged.fire_copy(peerId); _sourceChanged.fire_copy(peerId);
updateUserStoriesState(result.user);
} }
Story *Stories::parseAndApply( Story *Stories::parseAndApply(
@ -684,12 +692,14 @@ void Stories::applyRemovedFromActive(FullStoryId id) {
const auto j = i->second.ids.lower_bound(StoryIdDates{ id.story }); const auto j = i->second.ids.lower_bound(StoryIdDates{ id.story });
if (j != end(i->second.ids) && j->id == id.story) { if (j != end(i->second.ids) && j->id == id.story) {
i->second.ids.erase(j); i->second.ids.erase(j);
const auto user = i->second.user;
if (i->second.ids.empty()) { if (i->second.ids.empty()) {
_all.erase(i); _all.erase(i);
removeFromList(StorySourcesList::NotHidden); removeFromList(StorySourcesList::NotHidden);
removeFromList(StorySourcesList::Hidden); removeFromList(StorySourcesList::Hidden);
} }
_sourceChanged.fire_copy(id.peer); _sourceChanged.fire_copy(id.peer);
updateUserStoriesState(user);
} }
} }
} }
@ -891,22 +901,35 @@ void Stories::markAsRead(FullStoryId id, bool viewed) {
_incrementViewsTimer.callOnce(kIncrementViewsDelay); _incrementViewsTimer.callOnce(kIncrementViewsDelay);
} }
} }
const auto i = _all.find(id.peer); if (!bumpReadTill(id.peer, id.story)) {
if (i == end(_all) || i->second.readTill >= id.story) {
return; return;
} else if (!_markReadPending.contains(id.peer)) { }
if (!_markReadPending.contains(id.peer)) {
sendMarkAsReadRequests(); sendMarkAsReadRequests();
} }
_markReadPending.emplace(id.peer); _markReadPending.emplace(id.peer);
_markReadTimer.callOnce(kMarkAsReadDelay);
}
bool Stories::bumpReadTill(PeerId peerId, StoryId maxReadTill) {
auto &till = _readTill[peerId];
if (till < maxReadTill) {
till = maxReadTill;
updateUserStoriesState(_owner->peer(peerId));
}
const auto i = _all.find(peerId);
if (i == end(_all) || i->second.readTill >= maxReadTill) {
return false;
}
const auto wasUnreadCount = i->second.unreadCount(); const auto wasUnreadCount = i->second.unreadCount();
i->second.readTill = id.story; i->second.readTill = maxReadTill;
const auto nowUnreadCount = i->second.unreadCount(); const auto nowUnreadCount = i->second.unreadCount();
if (wasUnreadCount != nowUnreadCount) { if (wasUnreadCount != nowUnreadCount) {
const auto refreshInList = [&](StorySourcesList list) { const auto refreshInList = [&](StorySourcesList list) {
auto &sources = _sources[static_cast<int>(list)]; auto &sources = _sources[static_cast<int>(list)];
const auto i = ranges::find( const auto i = ranges::find(
sources, sources,
id.peer, peerId,
&StoriesSourceInfo::id); &StoriesSourceInfo::id);
if (i != end(sources)) { if (i != end(sources)) {
i->unreadCount = nowUnreadCount; i->unreadCount = nowUnreadCount;
@ -916,7 +939,7 @@ void Stories::markAsRead(FullStoryId id, bool viewed) {
refreshInList(StorySourcesList::NotHidden); refreshInList(StorySourcesList::NotHidden);
refreshInList(StorySourcesList::Hidden); refreshInList(StorySourcesList::Hidden);
} }
_markReadTimer.callOnce(kMarkAsReadDelay); return true;
} }
void Stories::toggleHidden( void Stories::toggleHidden(
@ -1408,6 +1431,53 @@ void Stories::setPreloadingInViewer(std::vector<FullStoryId> ids) {
} }
} }
std::optional<Stories::PeerSourceState> Stories::peerSourceState(
not_null<PeerData*> peer,
StoryId storyMaxId) {
const auto i = _readTill.find(peer->id);
if (_readTillReceived || (i != end(_readTill))) {
return PeerSourceState{
.maxId = storyMaxId,
.readTill = std::min(
storyMaxId,
(i != end(_readTill)) ? i->second : 0),
};
}
if (!_readTillsRequestId) {
const auto api = &_owner->session().api();
_readTillsRequestId = api->request(MTPstories_GetAllReadUserStories(
)).done([=](const MTPUpdates &result) {
_readTillReceived = true;
api->applyUpdates(result);
for (auto &[peer, maxId] : base::take(_pendingUserStateMaxId)) {
updateUserStoriesState(peer);
}
}).send();
}
_pendingUserStateMaxId[peer] = storyMaxId;
return std::nullopt;
}
void Stories::updateUserStoriesState(not_null<PeerData*> peer) {
const auto till = _readTill.find(peer->id);
const auto readTill = (till != end(_readTill)) ? till->second : 0;
const auto pendingMaxId = [&] {
const auto j = _pendingUserStateMaxId.find(peer);
return (j != end(_pendingUserStateMaxId)) ? j->second : 0;
};
const auto i = _all.find(peer->id);
const auto max = (i != end(_all))
? (i->second.ids.empty() ? 0 : i->second.ids.back().id)
: pendingMaxId();
if (const auto user = peer->asUser()) {
user->setStoriesState(!max
? UserData::StoriesState::None
: (max <= readTill)
? UserData::StoriesState::HasRead
: UserData::StoriesState::HasUnread);
}
}
void Stories::preloadSourcesChanged(StorySourcesList list) { void Stories::preloadSourcesChanged(StorySourcesList list) {
if (rebuildPreloadSources(list)) { if (rebuildPreloadSources(list)) {
continuePreloading(); continuePreloading();

View file

@ -137,6 +137,7 @@ public:
void loadMore(StorySourcesList list); void loadMore(StorySourcesList list);
void apply(const MTPDupdateStory &data); void apply(const MTPDupdateStory &data);
void apply(const MTPDupdateReadStories &data);
void apply(not_null<PeerData*> peer, const MTPUserStories *data); void apply(not_null<PeerData*> peer, const MTPUserStories *data);
Story *applyFromWebpage(PeerId peerId, const MTPstoryItem &story); Story *applyFromWebpage(PeerId peerId, const MTPstoryItem &story);
void loadAround(FullStoryId id, StoriesContext context); void loadAround(FullStoryId id, StoriesContext context);
@ -199,6 +200,14 @@ public:
void decrementPreloadingHiddenSources(); void decrementPreloadingHiddenSources();
void setPreloadingInViewer(std::vector<FullStoryId> ids); void setPreloadingInViewer(std::vector<FullStoryId> ids);
struct PeerSourceState {
StoryId maxId = 0;
StoryId readTill = 0;
};
[[nodiscard]] std::optional<PeerSourceState> peerSourceState(
not_null<PeerData*> peer,
StoryId storyMaxId);
private: private:
struct Saved { struct Saved {
StoriesIds ids; StoriesIds ids;
@ -222,6 +231,7 @@ private:
const QVector<MTPStoryItem> &list); const QVector<MTPStoryItem> &list);
void sendResolveRequests(); void sendResolveRequests();
void finalizeResolve(FullStoryId id); void finalizeResolve(FullStoryId id);
void updateUserStoriesState(not_null<PeerData*> peer);
void applyDeleted(FullStoryId id); void applyDeleted(FullStoryId id);
void applyExpired(FullStoryId id); void applyExpired(FullStoryId id);
@ -230,6 +240,7 @@ private:
void removeDependencyStory(not_null<Story*> story); void removeDependencyStory(not_null<Story*> story);
void savedStateUpdated(not_null<Story*> story); void savedStateUpdated(not_null<Story*> story);
void sort(StorySourcesList list); void sort(StorySourcesList list);
bool bumpReadTill(PeerId peerId, StoryId maxReadTill);
[[nodiscard]] std::shared_ptr<HistoryItem> lookupItem( [[nodiscard]] std::shared_ptr<HistoryItem> lookupItem(
not_null<Story*> story); not_null<Story*> story);
@ -317,6 +328,11 @@ private:
int _preloadingHiddenSourcesCounter = 0; int _preloadingHiddenSourcesCounter = 0;
int _preloadingMainSourcesCounter = 0; int _preloadingMainSourcesCounter = 0;
base::flat_map<PeerId, StoryId> _readTill;
base::flat_map<not_null<PeerData*>, StoryId> _pendingUserStateMaxId;
mtpRequestId _readTillsRequestId = 0;
bool _readTillReceived = false;
}; };
} // namespace Data } // namespace Data

View file

@ -19,6 +19,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_user_names.h" #include "data/data_user_names.h"
#include "data/data_wall_paper.h" #include "data/data_wall_paper.h"
#include "data/notify/data_notify_settings.h" #include "data/notify/data_notify_settings.h"
#include "history/history.h"
#include "api/api_peer_photo.h" #include "api/api_peer_photo.h"
#include "apiwrap.h" #include "apiwrap.h"
#include "ui/text/text_options.h" #include "ui/text/text_options.h"
@ -125,6 +126,38 @@ void UserData::setPrivateForwardName(const QString &name) {
_privateForwardName = name; _privateForwardName = name;
} }
bool UserData::hasActiveStories() const {
return flags() & UserDataFlag::HasActiveStories;
}
bool UserData::hasUnreadStories() const {
return flags() & UserDataFlag::HasUnreadStories;
}
void UserData::setStoriesState(StoriesState state) {
Expects(state != StoriesState::Unknown);
const auto was = flags();
using Flag = UserDataFlag;
switch (state) {
case StoriesState::None:
_flags.remove(Flag::HasActiveStories | Flag::HasUnreadStories);
break;
case StoriesState::HasRead:
_flags.set(
(flags() & ~Flag::HasUnreadStories) | Flag::HasActiveStories);
break;
case StoriesState::HasUnread:
_flags.add(Flag::HasActiveStories | Flag::HasUnreadStories);
break;
}
if (flags() != was) {
if (const auto history = owner().historyLoaded(this)) {
history->updateChatListEntryPostponed();
}
}
}
void UserData::setName(const QString &newFirstName, const QString &newLastName, const QString &newPhoneName, const QString &newUsername) { void UserData::setName(const QString &newFirstName, const QString &newLastName, const QString &newPhoneName, const QString &newUsername) {
bool changeName = !newFirstName.isEmpty() || !newLastName.isEmpty(); bool changeName = !newFirstName.isEmpty() || !newLastName.isEmpty();

View file

@ -63,6 +63,8 @@ enum class UserDataFlag {
VoiceMessagesForbidden = (1 << 16), VoiceMessagesForbidden = (1 << 16),
PersonalPhoto = (1 << 17), PersonalPhoto = (1 << 17),
StoriesHidden = (1 << 18), StoriesHidden = (1 << 18),
HasActiveStories = (1 << 19),
HasUnreadStories = (1 << 20),
}; };
inline constexpr bool is_flag_type(UserDataFlag) { return true; }; inline constexpr bool is_flag_type(UserDataFlag) { return true; };
using UserDataFlags = base::flags<UserDataFlag>; using UserDataFlags = base::flags<UserDataFlag>;
@ -174,6 +176,16 @@ public:
[[nodiscard]] QString privateForwardName() const; [[nodiscard]] QString privateForwardName() const;
void setPrivateForwardName(const QString &name); void setPrivateForwardName(const QString &name);
enum class StoriesState {
Unknown,
None,
HasRead,
HasUnread,
};
[[nodiscard]] bool hasActiveStories() const;
[[nodiscard]] bool hasUnreadStories() const;
void setStoriesState(StoriesState state);
private: private:
auto unavailableReasons() const auto unavailableReasons() const
-> const std::vector<Data::UnavailableReason> & override; -> const std::vector<Data::UnavailableReason> & override;

View file

@ -1294,6 +1294,7 @@ void InnerWidget::selectByMouse(QPoint globalPosition) {
} }
_mouseSelection = true; _mouseSelection = true;
_lastMousePosition = globalPosition; _lastMousePosition = globalPosition;
_lastRowLocalMouseX = local.x();
const auto w = width(); const auto w = width();
const auto mouseY = local.y(); const auto mouseY = local.y();
@ -2209,6 +2210,7 @@ FilterId InnerWidget::filterId() const {
void InnerWidget::clearSelection() { void InnerWidget::clearSelection() {
_mouseSelection = false; _mouseSelection = false;
_lastMousePosition = std::nullopt; _lastMousePosition = std::nullopt;
_lastRowLocalMouseX = -1;
if (isSelected()) { if (isSelected()) {
updateSelectedRow(); updateSelectedRow();
_collapsedSelected = -1; _collapsedSelected = -1;
@ -2907,6 +2909,7 @@ void InnerWidget::resizeEmptyLabel() {
void InnerWidget::clearMouseSelection(bool clearSelection) { void InnerWidget::clearMouseSelection(bool clearSelection) {
_mouseSelection = false; _mouseSelection = false;
_lastMousePosition = std::nullopt; _lastMousePosition = std::nullopt;
_lastRowLocalMouseX = -1;
if (clearSelection) { if (clearSelection) {
if (_state == WidgetState::Default) { if (_state == WidgetState::Default) {
_collapsedSelected = -1; _collapsedSelected = -1;
@ -3375,29 +3378,30 @@ ChosenRow InnerWidget::computeChosenRow() const {
if (_state == WidgetState::Default) { if (_state == WidgetState::Default) {
if (_selected) { if (_selected) {
return { return {
_selected->key(), .key = _selected->key(),
Data::UnreadMessagePosition .message = Data::UnreadMessagePosition,
}; };
} }
} else if (_state == WidgetState::Filtered) { } else if (_state == WidgetState::Filtered) {
if (base::in_range(_filteredSelected, 0, _filterResults.size())) { if (base::in_range(_filteredSelected, 0, _filterResults.size())) {
return { return {
_filterResults[_filteredSelected].key(), .key = _filterResults[_filteredSelected].key(),
Data::UnreadMessagePosition, .message = Data::UnreadMessagePosition,
true .filteredRow = true,
}; };
} else if (base::in_range(_peerSearchSelected, 0, _peerSearchResults.size())) { } else if (base::in_range(_peerSearchSelected, 0, _peerSearchResults.size())) {
const auto peer = _peerSearchResults[_peerSearchSelected]->peer;
return { return {
session().data().history(_peerSearchResults[_peerSearchSelected]->peer), .key = session().data().history(peer),
Data::UnreadMessagePosition .message = Data::UnreadMessagePosition
}; };
} else if (base::in_range(_searchedSelected, 0, _searchResults.size())) { } else if (base::in_range(_searchedSelected, 0, _searchResults.size())) {
const auto result = _searchResults[_searchedSelected].get(); const auto result = _searchResults[_searchedSelected].get();
const auto topic = result->topic(); const auto topic = result->topic();
const auto item = result->item(); const auto item = result->item();
return { return {
(topic ? (Entry*)topic : (Entry*)item->history()), .key = (topic ? (Entry*)topic : (Entry*)item->history()),
item->position() .message = item->position()
}; };
} }
} }
@ -3413,10 +3417,12 @@ bool InnerWidget::chooseRow(
} else if (chooseHashtag()) { } else if (chooseHashtag()) {
return true; return true;
} }
const auto modifyChosenRow = []( const auto modifyChosenRow = [&](
ChosenRow row, ChosenRow row,
Qt::KeyboardModifiers modifiers) { Qt::KeyboardModifiers modifiers) {
row.newWindow = (modifiers & Qt::ControlModifier); row.newWindow = (modifiers & Qt::ControlModifier);
row.userpicClick = (_lastRowLocalMouseX >= 0)
&& (_lastRowLocalMouseX < _st->nameLeft);
return row; return row;
}; };
auto chosen = modifyChosenRow(computeChosenRow(), modifiers); auto chosen = modifyChosenRow(computeChosenRow(), modifiers);

View file

@ -65,8 +65,9 @@ class IndexedList;
struct ChosenRow { struct ChosenRow {
Key key; Key key;
Data::MessagePosition message; Data::MessagePosition message;
bool filteredRow = false; bool userpicClick : 1 = false;
bool newWindow = false; bool filteredRow : 1 = false;
bool newWindow : 1 = false;
}; };
enum class SearchRequestType { enum class SearchRequestType {
@ -416,6 +417,7 @@ private:
FilterId _filterId = 0; FilterId _filterId = 0;
bool _mouseSelection = false; bool _mouseSelection = false;
std::optional<QPoint> _lastMousePosition; std::optional<QPoint> _lastMousePosition;
int _lastRowLocalMouseX = -1;
Qt::MouseButton _pressButton = Qt::LeftButton; Qt::MouseButton _pressButton = Qt::LeftButton;
Data::Folder *_openedFolder = nullptr; Data::Folder *_openedFolder = nullptr;

View file

@ -22,6 +22,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_forum.h" #include "data/data_forum.h"
#include "data/data_session.h" #include "data/data_session.h"
#include "data/data_peer_values.h" #include "data/data_peer_values.h"
#include "data/data_user.h"
#include "history/history.h" #include "history/history.h"
#include "history/history_item.h" #include "history/history_item.h"
#include "lang/lang_keys.h" #include "lang/lang_keys.h"
@ -330,6 +331,7 @@ void Row::ensureCornerBadgeUserpic() const {
void Row::PaintCornerBadgeFrame( void Row::PaintCornerBadgeFrame(
not_null<CornerBadgeUserpic*> data, not_null<CornerBadgeUserpic*> data,
int framePadding,
not_null<PeerData*> peer, not_null<PeerData*> peer,
Ui::VideoUserpic *videoUserpic, Ui::VideoUserpic *videoUserpic,
Ui::PeerUserpicView &view, Ui::PeerUserpicView &view,
@ -337,6 +339,19 @@ void Row::PaintCornerBadgeFrame(
data->frame.fill(Qt::transparent); data->frame.fill(Qt::transparent);
Painter q(&data->frame); Painter q(&data->frame);
q.translate(framePadding, framePadding);
auto hq = std::optional<PainterHighQualityEnabler>();
if (data->storiesShown) {
hq.emplace(q);
const auto line = st::dialogsStoriesFull.lineTwice / 2.;
const auto skip = line * 3 / 2.;
const auto scale = 1. - (2 * skip / context.st->photoSize);
const auto center = context.st->photoSize / 2.;
q.save();
q.translate(center, center);
q.scale(scale, scale);
q.translate(-center, -center);
}
PaintUserpic( PaintUserpic(
q, q,
peer, peer,
@ -347,9 +362,37 @@ void Row::PaintCornerBadgeFrame(
data->frame.width() / data->frame.devicePixelRatio(), data->frame.width() / data->frame.devicePixelRatio(),
context.st->photoSize, context.st->photoSize,
context.paused); context.paused);
if (data->storiesShown) {
q.restore();
const auto st = context.st;
const auto storiesUnreadBrush = [&] {
const auto left = st->padding.left();
const auto top = st->padding.top();
auto gradient = QLinearGradient(
QPoint(left + st->photoSize, top),
QPoint(left, top + st->photoSize));
gradient.setStops({
{ 0., st::groupCallLive1->c },
{ 1., st::groupCallMuted1->c },
});
return QBrush(gradient);
};
const auto storiesBrush = data->storiesUnread
? storiesUnreadBrush()
: context.active
? st::dialogsUnreadBgMutedActive->b
: st::dialogsUnreadBgMuted->b;
const auto storiesLine = data->storiesUnread
? (st::dialogsStoriesFull.lineTwice / 2.)
: (st::dialogsStoriesFull.lineReadTwice / 2.);
const auto pen = QPen(storiesBrush, storiesLine);
q.setPen(pen);
q.drawEllipse(0, 0, st->photoSize, st->photoSize);
}
const auto &manager = data->layersManager; const auto &manager = data->layersManager;
if (const auto p = manager.progressForLayer(kBottomLayer); p) { if (const auto p = manager.progressForLayer(kBottomLayer); p > 0.) {
const auto size = context.st->photoSize; const auto size = context.st->photoSize;
if (data->cacheTTL.isNull() && peer->messagesTTL()) { if (data->cacheTTL.isNull() && peer->messagesTTL()) {
data->cacheTTL = CornerBadgeTTL(peer, view, size); data->cacheTTL = CornerBadgeTTL(peer, view, size);
@ -364,7 +407,9 @@ void Row::PaintCornerBadgeFrame(
return; return;
} }
PainterHighQualityEnabler hq(q); if (!hq) {
hq.emplace(q);
}
q.setCompositionMode(QPainter::CompositionMode_Source); q.setCompositionMode(QPainter::CompositionMode_Source);
const auto size = peer->isUser() const auto size = peer->isUser()
@ -401,7 +446,14 @@ void Row::paintUserpic(
const auto cornerBadgeShown = !_cornerBadgeUserpic const auto cornerBadgeShown = !_cornerBadgeUserpic
? _cornerBadgeShown ? _cornerBadgeShown
: !_cornerBadgeUserpic->layersManager.isDisplayedNone(); : !_cornerBadgeUserpic->layersManager.isDisplayedNone();
if (!historyForCornerBadge || !cornerBadgeShown) { const auto storiesUser = historyForCornerBadge
? historyForCornerBadge->peer->asUser()
: nullptr;
const auto storiesShown = (storiesUser
&& storiesUser->hasActiveStories()) ? 1 : 0;
const auto storiesUnread = (storiesShown
&& storiesUser->hasUnreadStories()) ? 1 : 0;
if (!historyForCornerBadge || (!cornerBadgeShown && !storiesShown)) {
BasicRow::paintUserpic( BasicRow::paintUserpic(
p, p,
peer, peer,
@ -415,12 +467,12 @@ void Row::paintUserpic(
} }
ensureCornerBadgeUserpic(); ensureCornerBadgeUserpic();
const auto ratio = style::DevicePixelRatio(); const auto ratio = style::DevicePixelRatio();
const auto added = std::max({ const auto framePadding = std::max({
-st::dialogsCallBadgeSkip.x(), -st::dialogsCallBadgeSkip.x(),
-st::dialogsCallBadgeSkip.y(), -st::dialogsCallBadgeSkip.y(),
0 }); st::lineWidth * 2 });
const auto frameSide = (context.st->photoSize + added) const auto frameSide = (2 * framePadding + context.st->photoSize)
* style::DevicePixelRatio(); * ratio;
const auto frameSize = QSize(frameSide, frameSide); const auto frameSize = QSize(frameSide, frameSide);
if (_cornerBadgeUserpic->frame.size() != frameSize) { if (_cornerBadgeUserpic->frame.size() != frameSize) {
_cornerBadgeUserpic->frame = QImage( _cornerBadgeUserpic->frame = QImage(
@ -432,6 +484,7 @@ void Row::paintUserpic(
key.first += peer->messagesTTL(); key.first += peer->messagesTTL();
const auto frameIndex = videoUserpic ? videoUserpic->frameIndex() : -1; const auto frameIndex = videoUserpic ? videoUserpic->frameIndex() : -1;
const auto paletteVersion = style::PaletteVersion(); const auto paletteVersion = style::PaletteVersion();
const auto active = context.active ? 1 : 0;
const auto keyChanged = (_cornerBadgeUserpic->key != key) const auto keyChanged = (_cornerBadgeUserpic->key != key)
|| (_cornerBadgeUserpic->paletteVersion != paletteVersion); || (_cornerBadgeUserpic->paletteVersion != paletteVersion);
if (keyChanged) { if (keyChanged) {
@ -439,24 +492,29 @@ void Row::paintUserpic(
} }
if (keyChanged if (keyChanged
|| !_cornerBadgeUserpic->layersManager.isFinished() || !_cornerBadgeUserpic->layersManager.isFinished()
|| _cornerBadgeUserpic->active != context.active || _cornerBadgeUserpic->active != active
|| _cornerBadgeUserpic->frameIndex != frameIndex || _cornerBadgeUserpic->frameIndex != frameIndex
|| _cornerBadgeUserpic->storiesShown != storiesShown
|| _cornerBadgeUserpic->storiesUnread != storiesUnread
|| videoUserpic) { || videoUserpic) {
_cornerBadgeUserpic->key = key; _cornerBadgeUserpic->key = key;
_cornerBadgeUserpic->paletteVersion = paletteVersion; _cornerBadgeUserpic->paletteVersion = paletteVersion;
_cornerBadgeUserpic->active = context.active; _cornerBadgeUserpic->active = active;
_cornerBadgeUserpic->storiesShown = storiesShown;
_cornerBadgeUserpic->storiesUnread = storiesUnread;
_cornerBadgeUserpic->frameIndex = frameIndex; _cornerBadgeUserpic->frameIndex = frameIndex;
_cornerBadgeUserpic->layersManager.markFrameShown(); _cornerBadgeUserpic->layersManager.markFrameShown();
PaintCornerBadgeFrame( PaintCornerBadgeFrame(
_cornerBadgeUserpic.get(), _cornerBadgeUserpic.get(),
framePadding,
peer, peer,
videoUserpic, videoUserpic,
userpicView(), userpicView(),
context); context);
} }
p.drawImage( p.drawImage(
context.st->padding.left(), context.st->padding.left() - framePadding,
context.st->padding.top(), context.st->padding.top() - framePadding,
_cornerBadgeUserpic->frame); _cornerBadgeUserpic->frame);
if (historyForCornerBadge->peer->isUser()) { if (historyForCornerBadge->peer->isUser()) {
return; return;

View file

@ -167,11 +167,13 @@ private:
struct CornerBadgeUserpic { struct CornerBadgeUserpic {
InMemoryKey key; InMemoryKey key;
CornerLayersManager layersManager; CornerLayersManager layersManager;
int paletteVersion = 0;
int frameIndex = -1;
bool active = false;
QImage frame; QImage frame;
QImage cacheTTL; QImage cacheTTL;
int frameIndex = -1;
int paletteVersion : 24 = 0;
int storiesShown : 1 = 0;
int storiesUnread : 1 = 0;
int active : 1 = 0;
}; };
void setCornerBadgeShown( void setCornerBadgeShown(
@ -180,6 +182,7 @@ private:
void ensureCornerBadgeUserpic() const; void ensureCornerBadgeUserpic() const;
static void PaintCornerBadgeFrame( static void PaintCornerBadgeFrame(
not_null<CornerBadgeUserpic*> data, not_null<CornerBadgeUserpic*> data,
int framePadding,
not_null<PeerData*> peer, not_null<PeerData*> peer,
Ui::VideoUserpic *videoUserpic, Ui::VideoUserpic *videoUserpic,
Ui::PeerUserpicView &view, Ui::PeerUserpicView &view,

View file

@ -503,6 +503,14 @@ void Widget::chosenRow(const ChosenRow &row) {
return; return;
} else if (history) { } else if (history) {
const auto peer = history->peer; const auto peer = history->peer;
if (const auto user = history->peer->asUser()) {
if (row.message.fullId.msg == ShowAtUnreadMsgId) {
if (row.userpicClick && user->hasActiveStories()) {
controller()->openPeerStories(user->id);
return;
}
}
}
const auto showAtMsgId = controller()->uniqueChatsInSearchResults() const auto showAtMsgId = controller()->uniqueChatsInSearchResults()
? ShowAtUnreadMsgId ? ShowAtUnreadMsgId
: row.message.fullId.msg; : row.message.fullId.msg;