Update story mention layout, add outline.

Also use uint32 for bool-bitfields, otherwise:

int a : 1 = 0;
...
const auto test = true;
const auto b = test ? 1 : 0;
if (a != b) {
    a = b;
    ...
}
Assert(a == b); // Violation, because a == -1, not 1 (after a = b).
This commit is contained in:
John Preston 2023-07-05 11:55:16 +04:00
parent d7d8847c1d
commit a0ffa15885
15 changed files with 151 additions and 52 deletions

View file

@ -163,7 +163,7 @@ StoriesRow::StoriesRow(
void StoriesRow::applySegments(const Dialogs::Stories::Element &element) { void StoriesRow::applySegments(const Dialogs::Stories::Element &element) {
Expects(element.unreadCount <= element.count); Expects(element.unreadCount <= element.count);
_count = std::max(element.count, 1); _count = int(std::max(element.count, 1U));
_unreadCount = element.unreadCount; _unreadCount = element.unreadCount;
refreshSegments(); refreshSegments();
} }

View file

@ -75,9 +75,9 @@ StoriesSourceInfo StoriesSource::info() const {
return { return {
.id = user->id, .id = user->id,
.last = ids.empty() ? 0 : ids.back().date, .last = ids.empty() ? 0 : ids.back().date,
.count = std::min(int(ids.size()), kMaxSegmentsCount), .count = uint32(std::min(int(ids.size()), kMaxSegmentsCount)),
.unreadCount = std::min(unreadCount(), kMaxSegmentsCount), .unreadCount = uint32(std::min(unreadCount(), kMaxSegmentsCount)),
.premium = user->isPremium() ? 1 : 0, .premium = user->isPremium() ? 1U : 0U,
}; };
} }
@ -913,9 +913,25 @@ void Stories::markAsRead(FullStoryId id, bool viewed) {
bool Stories::bumpReadTill(PeerId peerId, StoryId maxReadTill) { bool Stories::bumpReadTill(PeerId peerId, StoryId maxReadTill) {
auto &till = _readTill[peerId]; auto &till = _readTill[peerId];
auto refreshItems = std::vector<StoryId>();
const auto guard = gsl::finally([&] {
for (const auto id : refreshItems) {
_owner->refreshStoryItemViews({ peerId, id });
}
});
if (till < maxReadTill) { if (till < maxReadTill) {
const auto from = till;
till = maxReadTill; till = maxReadTill;
updateUserStoriesState(_owner->peer(peerId)); updateUserStoriesState(_owner->peer(peerId));
const auto i = _stories.find(peerId);
if (i != end(_stories)) {
refreshItems = ranges::make_subrange(
i->second.lower_bound(from + 1),
i->second.lower_bound(till + 1)
) | ranges::views::transform([](const auto &pair) {
return pair.first;
}) | ranges::to_vector;
}
} }
const auto i = _all.find(peerId); const auto i = _all.find(peerId);
if (i == end(_all) || i->second.readTill >= maxReadTill) { if (i == end(_all) || i->second.readTill >= maxReadTill) {
@ -1443,21 +1459,40 @@ std::optional<Stories::PeerSourceState> Stories::peerSourceState(
(i != end(_readTill)) ? i->second : 0), (i != end(_readTill)) ? i->second : 0),
}; };
} }
if (!_readTillsRequestId) { requestReadTills();
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; _pendingUserStateMaxId[peer] = storyMaxId;
return std::nullopt; return std::nullopt;
} }
void Stories::requestReadTills() {
if (_readTillReceived || _readTillsRequestId) {
return;
}
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);
}
for (const auto &storyId : base::take(_pendingReadTillItems)) {
_owner->refreshStoryItemViews(storyId);
}
}).send();
}
bool Stories::isUnread(not_null<Story*> story) {
const auto till = _readTill.find(story->peer()->id);
if (till == end(_readTill) && !_readTillReceived) {
requestReadTills();
_pendingReadTillItems.emplace(story->fullId());
return false;
}
const auto readTill = (till != end(_readTill)) ? till->second : 0;
return (story->id() > readTill);
}
void Stories::updateUserStoriesState(not_null<PeerData*> peer) { void Stories::updateUserStoriesState(not_null<PeerData*> peer) {
const auto till = _readTill.find(peer->id); const auto till = _readTill.find(peer->id);
const auto readTill = (till != end(_readTill)) ? till->second : 0; const auto readTill = (till != end(_readTill)) ? till->second : 0;

View file

@ -41,9 +41,9 @@ struct StoriesIds {
struct StoriesSourceInfo { struct StoriesSourceInfo {
PeerId id = 0; PeerId id = 0;
TimeId last = 0; TimeId last = 0;
int count : 15 = 0; uint32 count : 15 = 0;
int unreadCount : 15 = 0; uint32 unreadCount : 15 = 0;
int premium : 1 = 0; uint32 premium : 1 = 0;
friend inline bool operator==( friend inline bool operator==(
StoriesSourceInfo, StoriesSourceInfo,
@ -207,6 +207,7 @@ public:
[[nodiscard]] std::optional<PeerSourceState> peerSourceState( [[nodiscard]] std::optional<PeerSourceState> peerSourceState(
not_null<PeerData*> peer, not_null<PeerData*> peer,
StoryId storyMaxId); StoryId storyMaxId);
[[nodiscard]] bool isUnread(not_null<Story*> story);
private: private:
struct Saved { struct Saved {
@ -241,6 +242,7 @@ private:
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); bool bumpReadTill(PeerId peerId, StoryId maxReadTill);
void requestReadTills();
[[nodiscard]] std::shared_ptr<HistoryItem> lookupItem( [[nodiscard]] std::shared_ptr<HistoryItem> lookupItem(
not_null<Story*> story); not_null<Story*> story);
@ -329,6 +331,7 @@ private:
int _preloadingMainSourcesCounter = 0; int _preloadingMainSourcesCounter = 0;
base::flat_map<PeerId, StoryId> _readTill; base::flat_map<PeerId, StoryId> _readTill;
base::flat_set<FullStoryId> _pendingReadTillItems;
base::flat_map<not_null<PeerData*>, StoryId> _pendingUserStateMaxId; base::flat_map<not_null<PeerData*>, StoryId> _pendingUserStateMaxId;
mtpRequestId _readTillsRequestId = 0; mtpRequestId _readTillsRequestId = 0;
bool _readTillReceived = false; bool _readTillReceived = false;

View file

@ -367,6 +367,9 @@ void Row::PaintCornerBadgeFrame(
const auto st = context.st; const auto st = context.st;
const auto storiesUnreadBrush = [&] { const auto storiesUnreadBrush = [&] {
if (context.active) {
return st::dialogsUnreadBgActive->b;
}
const auto left = st->padding.left(); const auto left = st->padding.left();
const auto top = st->padding.top(); const auto top = st->padding.top();
auto gradient = QLinearGradient( auto gradient = QLinearGradient(

View file

@ -170,10 +170,10 @@ private:
QImage frame; QImage frame;
QImage cacheTTL; QImage cacheTTL;
int frameIndex = -1; int frameIndex = -1;
int paletteVersion : 24 = 0; uint32 paletteVersion : 24 = 0;
int storiesShown : 1 = 0; uint32 storiesShown : 1 = 0;
int storiesUnread : 1 = 0; uint32 storiesUnread : 1 = 0;
int active : 1 = 0; uint32 active : 1 = 0;
}; };
void setCornerBadgeShown( void setCornerBadgeShown(
@ -192,9 +192,9 @@ private:
mutable std::unique_ptr<CornerBadgeUserpic> _cornerBadgeUserpic; mutable std::unique_ptr<CornerBadgeUserpic> _cornerBadgeUserpic;
int _top = 0; int _top = 0;
int _height = 0; int _height = 0;
int _index : 30 = 0; uint32 _index : 30 = 0;
int _cornerBadgeShown : 1 = 0; uint32 _cornerBadgeShown : 1 = 0;
int _topicJumpRipple : 1 = 0; uint32 _topicJumpRipple : 1 = 0;
}; };

View file

@ -353,7 +353,7 @@ Content State::next() {
.thumbnail = std::move(userpic), .thumbnail = std::move(userpic),
.count = info.count, .count = info.count,
.unreadCount = info.unreadCount, .unreadCount = info.unreadCount,
.skipSmall = user->isSelf() ? 1 : 0, .skipSmall = user->isSelf() ? 1U : 0U,
}); });
} }
return result; return result;
@ -424,8 +424,8 @@ rpl::producer<Content> LastForPeer(not_null<PeerData*> peer) {
result.elements.push_back({ result.elements.push_back({
.id = uint64(id), .id = uint64(id),
.thumbnail = MakeStoryThumbnail(*maybe), .thumbnail = MakeStoryThumbnail(*maybe),
.count = 1, .count = 1U,
.unreadCount = (id > readTill) ? 1 : 0, .unreadCount = (id > readTill) ? 1U : 0U,
}); });
} }
} else if (maybe.error() == Data::NoStory::Unknown) { } else if (maybe.error() == Data::NoStory::Unknown) {

View file

@ -36,9 +36,9 @@ struct Element {
uint64 id = 0; uint64 id = 0;
QString name; QString name;
std::shared_ptr<Thumbnail> thumbnail; std::shared_ptr<Thumbnail> thumbnail;
int count : 15 = 0; uint32 count : 15 = 0;
int unreadCount : 15 = 0; uint32 unreadCount : 15 = 0;
int skipSmall : 1 = 0; uint32 skipSmall : 1 = 0;
friend inline bool operator==( friend inline bool operator==(
const Element &a, const Element &a,

View file

@ -164,7 +164,7 @@ void Photo::unloadHeavyPart() {
QSize Photo::countOptimalSize() { QSize Photo::countOptimalSize() {
if (_serviceWidth > 0) { if (_serviceWidth > 0) {
return { _serviceWidth, _serviceWidth }; return { int(_serviceWidth), int(_serviceWidth) };
} }
if (_parent->media() != this) { if (_parent->media() != this) {
@ -209,7 +209,7 @@ QSize Photo::countOptimalSize() {
QSize Photo::countCurrentSize(int newWidth) { QSize Photo::countCurrentSize(int newWidth) {
if (_serviceWidth) { if (_serviceWidth) {
return { _serviceWidth, _serviceWidth }; return { int(_serviceWidth), int(_serviceWidth) };
} }
const auto thumbMaxWidth = qMin(newWidth, st::maxMediaSize); const auto thumbMaxWidth = qMin(newWidth, st::maxMediaSize);
const auto minWidth = std::clamp( const auto minWidth = std::clamp(

View file

@ -167,10 +167,10 @@ private:
const std::unique_ptr<MediaSpoiler> _spoiler; const std::unique_ptr<MediaSpoiler> _spoiler;
mutable QImage _imageCache; mutable QImage _imageCache;
mutable std::optional<Ui::BubbleRounding> _imageCacheRounding; mutable std::optional<Ui::BubbleRounding> _imageCacheRounding;
int _serviceWidth : 29 = 0; uint32 _serviceWidth : 29 = 0;
mutable int _imageCacheForum : 1 = 0; mutable uint32 _imageCacheForum : 1 = 0;
mutable int _imageCacheBlurred : 1 = 0; mutable uint32 _imageCacheBlurred : 1 = 0;
mutable int _story : 1 = 0; mutable uint32 _story : 1 = 0;
}; };

View file

@ -81,8 +81,7 @@ ServiceBox::ServiceBox(
+ _subtitle.countHeight(_maxWidth) + _subtitle.countHeight(_maxWidth)
+ (_button.empty() + (_button.empty()
? 0 ? 0
: (st::msgServiceGiftBoxButtonMargins.top() : (_content->buttonSkip() + _button.size.height()))
+ _button.size.height()))
+ st::msgServiceGiftBoxButtonMargins.bottom())) + st::msgServiceGiftBoxButtonMargins.bottom()))
, _innerSize(_size - QSize(0, st::msgServiceGiftBoxTopSkip)) { , _innerSize(_size - QSize(0, st::msgServiceGiftBoxTopSkip)) {
} }
@ -165,6 +164,11 @@ TextState ServiceBox::textState(QPoint point, StateRequest request) const {
if (rect.contains(point)) { if (rect.contains(point)) {
result.link = _button.link; result.link = _button.link;
_button.lastPoint = point - rect.topLeft(); _button.lastPoint = point - rect.topLeft();
} else if (contentRect().contains(point)) {
if (!_contentLink) {
_contentLink = _content->createViewLink();
}
result.link = _contentLink;
} }
} }
return result; return result;

View file

@ -23,6 +23,9 @@ public:
[[nodiscard]] virtual QSize size() = 0; [[nodiscard]] virtual QSize size() = 0;
[[nodiscard]] virtual QString title() = 0; [[nodiscard]] virtual QString title() = 0;
[[nodiscard]] virtual TextWithEntities subtitle() = 0; [[nodiscard]] virtual TextWithEntities subtitle() = 0;
[[nodiscard]] virtual int buttonSkip() {
return top();
}
[[nodiscard]] virtual QString button() = 0; [[nodiscard]] virtual QString button() = 0;
virtual void draw( virtual void draw(
Painter &p, Painter &p,
@ -84,6 +87,7 @@ private:
const not_null<Element*> _parent; const not_null<Element*> _parent;
const std::unique_ptr<ServiceBoxContent> _content; const std::unique_ptr<ServiceBoxContent> _content;
mutable ClickHandlerPtr _contentLink;
struct Button { struct Button {
void drawBg(QPainter &p) const; void drawBg(QPainter &p) const;

View file

@ -27,6 +27,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "main/main_session.h" #include "main/main_session.h"
#include "window/window_session_controller.h" #include "window/window_session_controller.h"
#include "ui/boxes/confirm_box.h" #include "ui/boxes/confirm_box.h"
#include "ui/chat/chat_style.h"
#include "ui/text/text_utilities.h" #include "ui/text/text_utilities.h"
#include "ui/toast/toast.h" #include "ui/toast/toast.h"
#include "ui/painter.h" #include "ui/painter.h"
@ -39,13 +40,16 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
namespace HistoryView { namespace HistoryView {
namespace { namespace {
constexpr auto kReadOutlineAlpha = 0.5;
} // namespace } // namespace
StoryMention::StoryMention( StoryMention::StoryMention(
not_null<Element*> parent, not_null<Element*> parent,
not_null<Data::Story*> story) not_null<Data::Story*> story)
: _parent(parent) : _parent(parent)
, _story(story) { , _story(story)
, _unread(story->owner().stories().isUnread(story) ? 1 : 0) {
} }
StoryMention::~StoryMention() = default; StoryMention::~StoryMention() = default;
@ -62,6 +66,10 @@ QString StoryMention::title() {
return QString(); return QString();
} }
int StoryMention::buttonSkip() {
return st::storyMentionButtonSkip;
}
QString StoryMention::button() { QString StoryMention::button() {
return tr::lng_action_story_mention_button(tr::now); return tr::lng_action_story_mention_button(tr::now);
} }
@ -86,7 +94,7 @@ void StoryMention::draw(
Painter &p, Painter &p,
const PaintContext &context, const PaintContext &context,
const QRect &geometry) { const QRect &geometry) {
const auto showStory = !_story->forbidsForward(); const auto showStory = _story->forbidsForward() ? 0 : 1;
if (!_thumbnail || _thumbnailFromStory != showStory) { if (!_thumbnail || _thumbnailFromStory != showStory) {
using namespace Dialogs::Stories; using namespace Dialogs::Stories;
const auto item = _parent->data(); const auto item = _parent->data();
@ -97,18 +105,49 @@ void StoryMention::draw(
? history->session().user() ? history->session().user()
: history->peer); : history->peer);
_thumbnailFromStory = showStory; _thumbnailFromStory = showStory;
_subscribed = false; _subscribed = 0;
} }
if (!_subscribed) { if (!_subscribed) {
_thumbnail->subscribeToUpdates([=] { _thumbnail->subscribeToUpdates([=] {
_parent->data()->history()->owner().requestViewRepaint(_parent); _parent->data()->history()->owner().requestViewRepaint(_parent);
}); });
_subscribed = true; _subscribed = 1;
} }
const auto padding = (geometry.width() - st::storyMentionSize) / 2;
const auto size = geometry.width() - 2 * padding;
p.drawImage( p.drawImage(
geometry.topLeft(), geometry.topLeft() + QPoint(padding, padding),
_thumbnail->image(st::msgServicePhotoWidth)); _thumbnail->image(size));
const auto thumbnail = geometry.marginsRemoved(
QMargins(padding, padding, padding, padding));
const auto added = 0.5 * (_unread
? st::storyMentionUnreadSkipTwice
: st::storyMentionReadSkipTwice);
const auto outline = thumbnail.marginsAdded(
QMargins(added, added, added, added));
if (_unread && _paletteVersion != style::PaletteVersion()) {
_paletteVersion = style::PaletteVersion();
auto gradient = QLinearGradient(
outline.topRight(),
outline.bottomLeft());
gradient.setStops({
{ 0., st::groupCallLive1->c },
{ 1., st::groupCallMuted1->c },
});
_unreadBrush = QBrush(gradient);
}
auto readColor = context.st->msgServiceFg()->c;
readColor.setAlphaF(std::min(readColor.alphaF(), kReadOutlineAlpha));
p.setPen(QPen(
_unread ? _unreadBrush : QBrush(readColor),
0.5 * (_unread
? st::storyMentionUnreadStrokeTwice
: st::storyMentionReadStrokeTwice)));
p.setBrush(Qt::NoBrush);
auto hq = PainterHighQualityEnabler(p);
p.drawEllipse(outline);
} }
void StoryMention::stickerClearLoopPlayed() { void StoryMention::stickerClearLoopPlayed() {
@ -121,12 +160,12 @@ std::unique_ptr<StickerPlayer> StoryMention::stickerTakePlayer(
} }
bool StoryMention::hasHeavyPart() { bool StoryMention::hasHeavyPart() {
return _subscribed; return _subscribed != 0;
} }
void StoryMention::unloadHeavyPart() { void StoryMention::unloadHeavyPart() {
if (_subscribed) { if (_subscribed) {
_subscribed = false; _subscribed = 0;
_thumbnail->subscribeToUpdates(nullptr); _thumbnail->subscribeToUpdates(nullptr);
} }
} }

View file

@ -32,6 +32,7 @@ public:
QSize size() override; QSize size() override;
QString title() override; QString title() override;
TextWithEntities subtitle() override; TextWithEntities subtitle() override;
int buttonSkip() override;
QString button() override; QString button() override;
void draw( void draw(
Painter &p, Painter &p,
@ -57,8 +58,11 @@ private:
const not_null<Element*> _parent; const not_null<Element*> _parent;
const not_null<Data::Story*> _story; const not_null<Data::Story*> _story;
std::shared_ptr<Thumbnail> _thumbnail; std::shared_ptr<Thumbnail> _thumbnail;
bool _thumbnailFromStory = false; QBrush _unreadBrush;
bool _subscribed = false; uint32 _paletteVersion : 29 = 0;
uint32 _thumbnailFromStory : 1 = 0;
uint32 _subscribed : 1 = 0;
uint32 _unread : 1 = 0;
}; };

View file

@ -841,7 +841,7 @@ searchInChatPeerList: PeerList(defaultPeerList) {
} }
msgServiceGiftBoxSize: size(206px, 231px); // Plus msgServiceGiftBoxTopSkip. msgServiceGiftBoxSize: size(206px, 231px); // Plus msgServiceGiftBoxTopSkip.
msgServiceGiftBoxRadius: 12px; msgServiceGiftBoxRadius: 20px;
msgServiceGiftBoxTopSkip: 4px; msgServiceGiftBoxTopSkip: 4px;
msgServiceGiftBoxButtonHeight: 32px; msgServiceGiftBoxButtonHeight: 32px;
msgServiceGiftBoxButtonPadding: margins(2px, 0px, 2px, 0px); msgServiceGiftBoxButtonPadding: margins(2px, 0px, 2px, 0px);
@ -915,3 +915,10 @@ backgroundSwitchToLight: IconButton(backgroundSwitchToDark) {
icon: icon {{ "menu/header_mode_day", boxTitleCloseFg }}; icon: icon {{ "menu/header_mode_day", boxTitleCloseFg }};
iconOver: icon {{ "menu/header_mode_day", boxTitleCloseFgOver }}; iconOver: icon {{ "menu/header_mode_day", boxTitleCloseFgOver }};
} }
storyMentionSize: 80px;
storyMentionUnreadSkipTwice: 8px;
storyMentionUnreadStrokeTwice: 6px;
storyMentionReadSkipTwice: 7px;
storyMentionReadStrokeTwice: 3px;
storyMentionButtonSkip: 5px;

View file

@ -25,8 +25,8 @@ struct PeerUserpicView {
QImage cached; QImage cached;
std::shared_ptr<QImage> cloud; std::shared_ptr<QImage> cloud;
base::weak_ptr<const EmptyUserpic> empty; base::weak_ptr<const EmptyUserpic> empty;
int paletteVersion : 31 = 0; uint32 paletteVersion : 31 = 0;
int forum : 1 = 0; uint32 forum : 1 = 0;
}; };
[[nodiscard]] bool PeerUserpicLoading(const PeerUserpicView &view); [[nodiscard]] bool PeerUserpicLoading(const PeerUserpicView &view);