Compare commits

...

14 Commits

Author SHA1 Message Date
John Preston
da8db0157f Version 3.4.1: Fix build without DBus. 2021-12-31 10:37:54 +03:00
John Preston
6188268afd Version 3.4.1.
- Bug fixes and other minor improvements.
2021-12-31 02:42:44 +03:00
John Preston
cd0db53bac For non-bubble messages reaction to the left of info. 2021-12-31 02:40:03 +03:00
John Preston
5bb90679a8 Attempt to fix a weird assertion violation. 2021-12-31 01:20:28 +03:00
John Preston
72df3a8f91 Don't show reaction button while selecting text. 2021-12-31 01:03:45 +03:00
John Preston
5fe2e649fb Attempt to fix a crash in reactions list view. 2021-12-31 00:59:29 +03:00
John Preston
9eba8ccc73 Always show reaction emoji in reactions view box. 2021-12-31 00:58:59 +03:00
John Preston
bb3c91aa44 Scale reaction images explicitly.
Fixes #17459.
2021-12-31 00:28:44 +03:00
John Preston
9f1268b6c8 Move kwayland-qt6 patch to kwayland build rule folder.
Fixes #17460.
2021-12-31 00:25:31 +03:00
John Preston
a6f1a1bd62 Fix bottom info with author signature.
Fixes #17464.
2021-12-30 23:57:12 +03:00
John Preston
1b2642b017 Fix spoilers with disabled animations.
Fixes #17458.
2021-12-30 23:38:28 +03:00
John Preston
e722645e7c Try to show the reaction button outside of the bubble. 2021-12-30 23:38:06 +03:00
John Preston
9486c266b5 Use context menu background for sticker reaction dropdown. 2021-12-30 23:36:52 +03:00
John Preston
dc21491099 Fix reactions icon in Manage Group / Channel. 2021-12-30 18:24:12 +03:00
32 changed files with 255 additions and 154 deletions

View File

@@ -10,7 +10,7 @@
<Identity Name="TelegramMessengerLLP.TelegramDesktop"
ProcessorArchitecture="ARCHITECTURE"
Publisher="CN=536BC709-8EE1-4478-AF22-F0F0F26FF64A"
Version="3.4.0.0" />
Version="3.4.1.0" />
<Properties>
<DisplayName>Telegram Desktop</DisplayName>
<PublisherDisplayName>Telegram Messenger LLP</PublisherDisplayName>

View File

@@ -44,8 +44,8 @@ IDI_ICON1 ICON "..\\art\\icon256.ico"
//
VS_VERSION_INFO VERSIONINFO
FILEVERSION 3,4,0,0
PRODUCTVERSION 3,4,0,0
FILEVERSION 3,4,1,0
PRODUCTVERSION 3,4,1,0
FILEFLAGSMASK 0x3fL
#ifdef _DEBUG
FILEFLAGS 0x1L
@@ -62,10 +62,10 @@ BEGIN
BEGIN
VALUE "CompanyName", "Telegram FZ-LLC"
VALUE "FileDescription", "Telegram Desktop"
VALUE "FileVersion", "3.4.0.0"
VALUE "FileVersion", "3.4.1.0"
VALUE "LegalCopyright", "Copyright (C) 2014-2021"
VALUE "ProductName", "Telegram Desktop"
VALUE "ProductVersion", "3.4.0.0"
VALUE "ProductVersion", "3.4.1.0"
END
END
BLOCK "VarFileInfo"

View File

@@ -35,8 +35,8 @@ LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
//
VS_VERSION_INFO VERSIONINFO
FILEVERSION 3,4,0,0
PRODUCTVERSION 3,4,0,0
FILEVERSION 3,4,1,0
PRODUCTVERSION 3,4,1,0
FILEFLAGSMASK 0x3fL
#ifdef _DEBUG
FILEFLAGS 0x1L
@@ -53,10 +53,10 @@ BEGIN
BEGIN
VALUE "CompanyName", "Telegram FZ-LLC"
VALUE "FileDescription", "Telegram Desktop Updater"
VALUE "FileVersion", "3.4.0.0"
VALUE "FileVersion", "3.4.1.0"
VALUE "LegalCopyright", "Copyright (C) 2014-2021"
VALUE "ProductName", "Telegram Desktop"
VALUE "ProductVersion", "3.4.0.0"
VALUE "ProductVersion", "3.4.1.0"
END
END
BLOCK "VarFileInfo"

View File

@@ -31,28 +31,50 @@ namespace {
constexpr auto kContextReactionsLimit = 50;
struct Peers {
std::vector<PeerId> list;
bool unknown = false;
};
inline bool operator==(const Peers &a, const Peers &b) noexcept {
return (a.list == b.list) && (a.unknown == b.unknown);
}
struct PeerWithReaction {
PeerId peer = 0;
QString reaction;
};
bool operator==(const PeerWithReaction &a, const PeerWithReaction &b) {
inline bool operator==(
const PeerWithReaction &a,
const PeerWithReaction &b) noexcept {
return (a.peer == b.peer) && (a.reaction == b.reaction);
}
struct PeersWithReactions {
std::vector<PeerWithReaction> list;
int fullReactionsCount = 0;
bool unknown = false;
};
inline bool operator==(
const PeersWithReactions &a,
const PeersWithReactions &b) noexcept {
return (a.fullReactionsCount == b.fullReactionsCount)
&& (a.list == b.list)
&& (a.unknown == b.unknown);
}
struct CachedRead {
explicit CachedRead(PeerId unknownFlag)
: list(std::vector<PeerId>{ unknownFlag }) {
CachedRead()
: data(Peers{ .unknown = true }) {
}
rpl::variable<std::vector<PeerId>> list;
rpl::variable<Peers> data;
mtpRequestId requestId = 0;
};
struct CachedReacted {
explicit CachedReacted(PeerId unknownFlag)
: list(
std::vector<PeerWithReaction>{ PeerWithReaction{ unknownFlag } }) {
CachedReacted()
: data(PeersWithReactions{ .unknown = true }) {
}
rpl::variable<std::vector<PeerWithReaction>> list;
rpl::variable<PeersWithReactions> data;
mtpRequestId requestId = 0;
};
@@ -66,10 +88,7 @@ struct Context {
if (i != end(cachedRead)) {
return i->second;
}
return cachedRead.emplace(
item,
CachedRead(item->history()->session().userPeerId())
).first->second;
return cachedRead.emplace(item, CachedRead()).first->second;
}
[[nodiscard]] CachedReacted &cacheReacted(not_null<HistoryItem*> item) {
@@ -77,10 +96,7 @@ struct Context {
if (i != end(cachedReacted)) {
return i->second;
}
return cachedReacted.emplace(
item,
CachedReacted(item->history()->session().userPeerId())
).first->second;
return cachedReacted.emplace(item, CachedReacted()).first->second;
}
};
@@ -193,7 +209,7 @@ struct State {
return Ui::WhoReadType::Seen;
}
[[nodiscard]] rpl::producer<std::vector<PeerId>> WhoReadIds(
[[nodiscard]] rpl::producer<Peers> WhoReadIds(
not_null<HistoryItem*> item,
not_null<QWidget*> context) {
auto weak = QPointer<QWidget>(context.get());
@@ -213,32 +229,35 @@ struct State {
).done([=](const MTPVector<MTPlong> &result) {
auto &entry = context->cacheRead(item);
entry.requestId = 0;
auto peers = std::vector<PeerId>();
peers.reserve(std::max(int(result.v.size()), 1));
auto parsed = Peers();
parsed.list.reserve(result.v.size());
for (const auto &id : result.v) {
peers.push_back(UserId(id));
parsed.list.push_back(UserId(id));
}
entry.list = std::move(peers);
entry.data = std::move(parsed);
}).fail([=] {
auto &entry = context->cacheRead(item);
entry.requestId = 0;
if (ListUnknown(entry.list.current(), item)) {
entry.list = std::vector<PeerId>();
if (entry.data.current().unknown) {
entry.data = Peers();
}
}).send();
}
return entry.list.value().start_existing(consumer);
return entry.data.value().start_existing(consumer);
};
}
[[nodiscard]] std::vector < PeerWithReaction> WithEmptyReactions(
const std::vector<PeerId> &peers) {
return peers | ranges::views::transform([](PeerId peer) {
return PeerWithReaction{ .peer = peer };
}) | ranges::to_vector;
[[nodiscard]] PeersWithReactions WithEmptyReactions(
const Peers &peers) {
return PeersWithReactions{
.list = peers.list | ranges::views::transform([](PeerId peer) {
return PeerWithReaction{.peer = peer };
}) | ranges::to_vector,
.unknown = peers.unknown,
};
}
[[nodiscard]] rpl::producer<std::vector<PeerWithReaction>> WhoReactedIds(
[[nodiscard]] rpl::producer<PeersWithReactions> WhoReactedIds(
not_null<HistoryItem*> item,
not_null<QWidget*> context) {
auto weak = QPointer<QWidget>(context.get());
@@ -267,46 +286,47 @@ struct State {
const MTPDmessages_messageReactionsList &data) {
session->data().processUsers(data.vusers());
auto peers = std::vector<PeerWithReaction>();
peers.reserve(data.vreactions().v.size());
auto parsed = PeersWithReactions{
.fullReactionsCount = data.vcount().v,
};
parsed.list.reserve(data.vreactions().v.size());
for (const auto &vote : data.vreactions().v) {
vote.match([&](const auto &data) {
peers.push_back(PeerWithReaction{
parsed.list.push_back(PeerWithReaction{
.peer = peerFromUser(data.vuser_id()),
.reaction = qs(data.vreaction()),
});
});
}
entry.list = std::move(peers);
entry.data = std::move(parsed);
});
}).fail([=] {
auto &entry = context->cacheReacted(item);
entry.requestId = 0;
if (ListUnknown(entry.list.current(), item)) {
entry.list = std::vector<PeerWithReaction>();
if (entry.data.current().unknown) {
entry.data = PeersWithReactions();
}
}).send();
}
return entry.list.value().start_existing(consumer);
return entry.data.value().start_existing(consumer);
};
}
[[nodiscard]] auto WhoReadOrReactedIds(
not_null<HistoryItem*> item,
not_null<QWidget*> context)
-> rpl::producer<std::vector<PeerWithReaction>> {
-> rpl::producer<PeersWithReactions> {
return rpl::combine(
WhoReactedIds(item, context),
WhoReadIds(item, context)
) | rpl::map([=](
std::vector<PeerWithReaction> reacted,
std::vector<PeerId> read) {
if (ListUnknown(reacted, item) || ListUnknown(read, item)) {
return reacted;
) | rpl::map([=](PeersWithReactions reacted, Peers read) {
if (reacted.unknown || read.unknown) {
return PeersWithReactions{ .unknown = true };
}
for (const auto &peer : read) {
if (!ranges::contains(reacted, peer, &PeerWithReaction::peer)) {
reacted.push_back({ .peer = peer });
auto &list = reacted.list;
for (const auto &peer : read.list) {
if (!ranges::contains(list, peer, &PeerWithReaction::peer)) {
list.push_back({ .peer = peer });
}
}
return reacted;
@@ -499,19 +519,20 @@ rpl::producer<Ui::WhoReadContent> WhoReacted(
}
std::move(
idsWithReactions
) | rpl::start_with_next([=](
const std::vector<PeerWithReaction> &peers) {
if (ListUnknown(peers, item)) {
) | rpl::start_with_next([=](const PeersWithReactions &peers) {
if (peers.unknown) {
state->userpics.clear();
consumer.put_next(Ui::WhoReadContent{
.type = state->current.type,
.unknown = true,
});
return;
} else if (UpdateUserpics(state, item, peers)) {
}
state->current.fullReactionsCount = peers.fullReactionsCount;
if (UpdateUserpics(state, item, peers.list)) {
RegenerateParticipants(state, small, large);
pushNext();
} else if (peers.empty()) {
} else if (peers.list.empty()) {
pushNext();
}
}, lifetime);

View File

@@ -22,7 +22,7 @@ constexpr auto AppId = "{53F49750-6209-4FBF-9CA8-7A333C87D1ED}"_cs;
constexpr auto AppNameOld = "Telegram Win (Unofficial)"_cs;
constexpr auto AppName = "Telegram Desktop"_cs;
constexpr auto AppFile = "Telegram"_cs;
constexpr auto AppVersion = 3004000;
constexpr auto AppVersionStr = "3.4";
constexpr auto AppVersion = 3004001;
constexpr auto AppVersionStr = "3.4.1";
constexpr auto AppBetaVersion = false;
constexpr auto AppAlphaVersion = TDESKTOP_ALPHA_VERSION;

View File

@@ -1336,7 +1336,7 @@ bool DocumentData::isSongWithCover() const {
}
bool DocumentData::isAudioFile() const {
if (isVoiceMessage()) {
if (isVoiceMessage() || isVideoFile()) {
return false;
} else if (isSong()) {
return true;

View File

@@ -679,6 +679,9 @@ void HistoryInner::paintEvent(QPaintEvent *e) {
}
if (hasPendingResizedItems()) {
return;
} else if (_recountedAfterPendingResizedItems) {
_recountedAfterPendingResizedItems = false;
mouseActionUpdate();
}
const auto guard = gsl::finally([&] {
@@ -2370,6 +2373,11 @@ void HistoryInner::checkHistoryActivation() {
void HistoryInner::recountHistoryGeometry() {
_contentWidth = _scroll->width();
if (_history->hasPendingResizedItems()
|| (_migrated && _migrated->hasPendingResizedItems())) {
_recountedAfterPendingResizedItems = true;
}
const auto visibleHeight = _scroll->height();
int oldHistoryPaddingTop = qMax(visibleHeight - historyHeight() - st::historyPaddingBottom, 0);
if (_botAbout && !_botAbout->info->text.isEmpty()) {
@@ -2966,6 +2974,7 @@ auto HistoryInner::reactionButtonParameters(
if (top < 0
|| !view->data()->canReact()
|| _mouseAction == MouseAction::Dragging
|| _mouseAction == MouseAction::Selecting
|| inSelectionMode()) {
return {};
}
@@ -3002,7 +3011,11 @@ void HistoryInner::mouseActionUpdate() {
: nullptr;
const auto item = view ? view->data().get() : nullptr;
if (view) {
App::mousedItem(view);
if (App::mousedItem() != view) {
repaintItem(App::mousedItem());
App::mousedItem(view);
repaintItem(App::mousedItem());
}
m = mapPointToItem(point, view);
_reactionsManager->updateButton(reactionButtonParameters(
view,
@@ -3019,6 +3032,10 @@ void HistoryInner::mouseActionUpdate() {
App::hoveredItem(nullptr);
}
} else {
if (App::mousedItem()) {
repaintItem(App::mousedItem());
App::mousedItem(nullptr);
}
_reactionsManager->updateButton({});
}
if (_mouseActionItem && !_mouseActionItem->mainView()) {
@@ -3744,7 +3761,7 @@ not_null<HistoryView::ElementDelegate*> HistoryInner::ElementDelegate() {
}
bool elementUnderCursor(
not_null<const Element*> view) override {
return (App::hoveredItem() == view);
return (App::mousedItem() == view);
}
crl::time elementHighlightTime(
not_null<const HistoryItem*> item) override {

View File

@@ -417,6 +417,7 @@ private:
CursorState _mouseCursorState = CursorState();
uint16 _mouseTextSymbol = 0;
bool _pressWasInactive = false;
bool _recountedAfterPendingResizedItems = false;
QPoint _trippleClickPoint;
base::Timer _trippleClickTimer;

View File

@@ -279,7 +279,7 @@ void BottomInfo::layoutDateText() {
? (tr::lng_edited(tr::now) + ' ')
: QString();
const auto author = _data.author;
const auto prefix = author.isEmpty() ? qsl(", ") : QString();
const auto prefix = !author.isEmpty() ? qsl(", ") : QString();
const auto date = edited + _data.date.toString(cTimeFormat());
_dateWidth = st::msgDateFont->width(date);
const auto afterAuthor = prefix + date;

View File

@@ -605,7 +605,7 @@ void Message::draw(Painter &p, const PaintContext &context) const {
if (_reactions && !reactionsInBubble) {
const auto reactionsHeight = st::mediaInBubbleSkip + _reactions->height();
const auto reactionsLeft = (!bubble && mediaDisplayed)
? media->contentRectForReactionButton().x()
? media->contentRectForReactions().x()
: 0;
g.setHeight(g.height() - reactionsHeight);
const auto reactionsPosition = QPoint(reactionsLeft + g.left(), g.top() + g.height() + st::mediaInBubbleSkip);
@@ -1281,7 +1281,7 @@ TextState Message::textState(
if (_reactions && !reactionsInBubble) {
const auto reactionsHeight = st::mediaInBubbleSkip + _reactions->height();
const auto reactionsLeft = (!bubble && mediaDisplayed)
? media->contentRectForReactionButton().x()
? media->contentRectForReactions().x()
: 0;
g.setHeight(g.height() - reactionsHeight);
const auto reactionsPosition = QPoint(reactionsLeft + g.left(), g.top() + g.height() + st::mediaInBubbleSkip);
@@ -1856,22 +1856,25 @@ Reactions::ButtonParameters Message::reactionButtonParameters(
const auto innerHeight = geometry.height()
- keyboardHeight
- reactionsHeight;
const auto contentRect = (result.style == ButtonStyle::Service
&& !drawBubble())
? media()->contentRectForReactionButton().translated(
geometry.topLeft())
: geometry;
result.center = contentRect.topLeft() + (onTheLeft
? (QPoint(0, innerHeight) + QPoint(
-st::reactionCornerCenter.x(),
st::reactionCornerCenter.y()))
: (QPoint(contentRect.width(), innerHeight)
+ st::reactionCornerCenter));
if (reactionState.itemId != result.context) {
if (!contentRect.contains(position)) {
return {};
}
const auto maybeRelativeCenter = (result.style == ButtonStyle::Service)
? media()->reactionButtonCenterOverride()
: std::nullopt;
const auto relativeCenter = QPoint(
maybeRelativeCenter.value_or(onTheLeft
? -st::reactionCornerCenter.x()
: (geometry.width() + st::reactionCornerCenter.x())),
innerHeight + st::reactionCornerCenter.y());
result.center = geometry.topLeft() + relativeCenter;
if (reactionState.itemId != result.context
&& !geometry.contains(position)) {
result.outside = true;
}
const auto minSkip = (st::reactionCornerShadow.left()
+ st::reactionCornerSize.width()
+ st::reactionCornerShadow.right()) / 2;
result.center = QPoint(
std::min(std::max(result.center.x(), minSkip), width() - minSkip),
result.center.y());
return result;
}
@@ -2789,7 +2792,7 @@ int Message::resizeContentGetHeight(int newWidth) {
}
if (_reactions && !reactionsInBubble) {
const auto reactionsWidth = (!bubble && mediaDisplayed)
? media->contentRectForReactionButton().width()
? media->contentRectForReactions().width()
: contentWidth;
newHeight += st::mediaInBubbleSkip
+ _reactions->resizeGetHeight(reactionsWidth);

View File

@@ -32,6 +32,7 @@ constexpr auto kMaskCacheIndex = 2;
constexpr auto kCacheColumsCount = 3;
constexpr auto kButtonShowDelay = crl::time(300);
constexpr auto kButtonExpandDelay = crl::time(300);
constexpr auto kButtonHideDelay = crl::time(200);
[[nodiscard]] QPoint LocalPosition(not_null<QWheelEvent*> e) {
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
@@ -42,29 +43,42 @@ constexpr auto kButtonExpandDelay = crl::time(300);
}
[[nodiscard]] QSize CountMaxSizeWithMargins(style::margins margins) {
const auto extended = QRect(
return QRect(
QPoint(),
st::reactionCornerSize
).marginsAdded(margins);
const auto scale = Button::ScaleForState(ButtonState::Active);
return QSize(
int(base::SafeRound(extended.width() * scale)),
int(base::SafeRound(extended.height() * scale)));
).marginsAdded(margins).size();
}
[[nodiscard]] QSize CountOuterSize() {
return CountMaxSizeWithMargins(st::reactionCornerShadow);
}
[[nodiscard]] int CornerImageSize(float64 scale) {
return int(base::SafeRound(st::reactionCornerImage * scale));
}
[[nodiscard]] QImage PrepareMaxOtherReaction(QImage image) {
const auto size = CornerImageSize(1.);
const auto factor = style::DevicePixelRatio();
auto result = image.scaled(
QSize(size, size) * factor,
Qt::IgnoreAspectRatio,
Qt::SmoothTransformation);
result.setDevicePixelRatio(factor);
return result;
}
} // namespace
Button::Button(
Fn<void(QRect)> update,
ButtonParameters parameters)
ButtonParameters parameters,
Fn<void()> hideMe)
: _update(std::move(update))
, _collapsed(QPoint(), CountOuterSize())
, _finalHeight(_collapsed.height())
, _expandTimer([=] { applyState(State::Inside, _update); }) {
, _expandTimer([=] { applyState(State::Inside, _update); })
, _hideTimer(hideMe) {
applyParameters(parameters, nullptr);
}
@@ -152,6 +166,11 @@ void Button::applyParameters(
update(_geometry);
}
}
if (parameters.outside && _state == State::Shown) {
_hideTimer.callOnce(kButtonHideDelay);
} else {
_hideTimer.cancel();
}
}
void Button::updateExpandDirection(const ButtonParameters &parameters) {
@@ -202,6 +221,7 @@ void Button::applyState(State state) {
void Button::applyState(State state, Fn<void(QRect)> update) {
if (state == State::Hidden) {
_expandTimer.cancel();
_hideTimer.cancel();
}
const auto finalHeight = (state == State::Inside)
? _expandedHeight
@@ -255,13 +275,10 @@ Manager::Manager(
QWidget *wheelEventsTarget,
Fn<void(QRect)> buttonUpdate)
: _outer(CountOuterSize())
, _inner(QRectF({}, st::reactionCornerSize))
, _innerActive(QRect({}, CountMaxSizeWithMargins({})))
, _inner(QRect({}, st::reactionCornerSize))
, _buttonShowTimer([=] { showButtonDelayed(); })
, _buttonUpdate(std::move(buttonUpdate)) {
_inner.translate(QRectF({}, _outer).center() - _inner.center());
_innerActive.translate(
QRect({}, _outer).center() - _innerActive.center());
_inner.translate(QRect({}, _outer).center() - _inner.center());
const auto ratio = style::DevicePixelRatio();
_cacheInOutService = QImage(
@@ -332,6 +349,10 @@ void Manager::updateButton(ButtonParameters parameters) {
} else if (_button) {
_button->applyParameters(parameters);
return;
} else if (parameters.outside) {
_buttonShowTimer.cancel();
_scheduledParameters = std::nullopt;
return;
}
const auto globalPositionChanged = _scheduledParameters
&& (_scheduledParameters->globalPointer != parameters.globalPointer);
@@ -345,7 +366,10 @@ void Manager::updateButton(ButtonParameters parameters) {
}
void Manager::showButtonDelayed() {
_button = std::make_unique<Button>(_buttonUpdate, *_scheduledParameters);
_button = std::make_unique<Button>(
_buttonUpdate,
*_scheduledParameters,
[=]{ updateButton({}); });
}
void Manager::applyList(std::vector<Data::Reaction> list) {
@@ -386,7 +410,7 @@ void Manager::setMainReactionImage(QImage image) {
loadOtherReactions();
}
QMarginsF Manager::innerMargins() const {
QMargins Manager::innerMargins() const {
return {
_inner.x(),
_inner.y(),
@@ -395,12 +419,12 @@ QMarginsF Manager::innerMargins() const {
};
}
QRectF Manager::buttonInner() const {
QRect Manager::buttonInner() const {
return buttonInner(_button.get());
}
QRectF Manager::buttonInner(not_null<Button*> button) const {
return QRectF(button->geometry()).marginsRemoved(innerMargins());
QRect Manager::buttonInner(not_null<Button*> button) const {
return button->geometry().marginsRemoved(innerMargins());
}
void Manager::loadOtherReactions() {
@@ -413,7 +437,7 @@ void Manager::loadOtherReactions() {
.media = icon->createMediaView(),
}).first->second;
if (const auto image = entry.media->getStickerLarge()) {
entry.image = image->original();
entry.image = PrepareMaxOtherReaction(image->original());
entry.media = nullptr;
} else if (!_otherReactionsLifetime) {
icon->session().downloaderTaskFinished(
@@ -429,7 +453,7 @@ void Manager::checkOtherReactions() {
for (auto &[icon, entry] : _otherReactions) {
if (entry.media) {
if (const auto image = entry.media->getStickerLarge()) {
entry.image = image->original();
entry.image = PrepareMaxOtherReaction(image->original());
entry.media = nullptr;
} else {
all = false;
@@ -571,7 +595,7 @@ void Manager::paintButton(
QRect(QPoint(), geometry.size() * style::DevicePixelRatio()));
} else {
const auto background = (style == ButtonStyle::Service)
? context.st->msgServiceFg()->c
? context.st->windowBg()->c
: context.st->messageStyle(
(style == ButtonStyle::Outgoing),
false
@@ -647,14 +671,14 @@ void Manager::paintAllEmoji(
auto hq = PainterHighQualityEnabler(p);
const auto between = st::reactionCornerSkip;
const auto oneHeight = st::reactionCornerSize.height() + between;
const auto oneSize = st::reactionCornerImage * scale;
const auto oneSize = CornerImageSize(scale);
const auto expandUp = button->expandUp();
const auto shift = QPoint(0, oneHeight * (expandUp ? -1 : 1));
auto emojiPosition = mainEmojiPosition
+ QPoint(0, button->scroll() * (expandUp ? 1 : -1));
for (const auto &reaction : _list) {
const auto inner = QRectF(_inner).translated(emojiPosition);
const auto target = QRectF(
const auto inner = _inner.translated(emojiPosition);
const auto target = QRect(
inner.x() + (inner.width() - oneSize) / 2,
inner.y() + (inner.height() - oneSize) / 2,
oneSize,
@@ -723,7 +747,7 @@ QRect Manager::validateShadow(
const auto center = _inner.center();
const auto add = style::ConvertScale(2.);
const auto shift = style::ConvertScale(1.);
const auto extended = _inner.marginsAdded({ add, add, add, add });
const auto extended = QRectF(_inner).marginsAdded({add, add, add, add});
p.setPen(Qt::NoPen);
p.setBrush(shadow);
p.translate(center);
@@ -753,16 +777,18 @@ QRect Manager::validateEmoji(int frameIndex, float64 scale) {
p.setCompositionMode(QPainter::CompositionMode_Source);
p.fillRect(QRect(position, result.size() / ratio), Qt::transparent);
if (!_mainReactionImage.isNull()) {
const auto size = st::reactionCornerImage * scale;
const auto size = CornerImageSize(scale);
const auto inner = _inner.translated(position);
const auto target = QRectF(
const auto target = QRect(
inner.x() + (inner.width() - size) / 2,
inner.y() + (inner.height() - size) / 2,
size,
size);
auto hq = PainterHighQualityEnabler(p);
p.drawImage(target, _mainReactionImage);
p.drawImage(target, _mainReactionImage.scaled(
target.size() * ratio,
Qt::IgnoreAspectRatio,
Qt::SmoothTransformation));
}
_validEmoji[frameIndex] = true;

View File

@@ -53,6 +53,7 @@ struct ButtonParameters {
int reactionsCount = 1;
int visibleTop = 0;
int visibleBottom = 0;
bool outside = false;
};
enum class ButtonState {
@@ -64,7 +65,10 @@ enum class ButtonState {
class Button final {
public:
Button(Fn<void(QRect)> update, ButtonParameters parameters);
Button(
Fn<void(QRect)> update,
ButtonParameters parameters,
Fn<void()> hideMe);
~Button();
void applyParameters(ButtonParameters parameters);
@@ -106,6 +110,7 @@ private:
ButtonStyle _style = ButtonStyle::Incoming;
base::Timer _expandTimer;
base::Timer _hideTimer;
std::optional<QPoint> _lastGlobalPosition;
};
@@ -189,9 +194,9 @@ private:
const QRect &geometry,
const PaintContext &context);
[[nodiscard]] QMarginsF innerMargins() const;
[[nodiscard]] QRectF buttonInner() const;
[[nodiscard]] QRectF buttonInner(not_null<Button*> button) const;
[[nodiscard]] QMargins innerMargins() const;
[[nodiscard]] QRect buttonInner() const;
[[nodiscard]] QRect buttonInner(not_null<Button*> button) const;
void loadOtherReactions();
void checkOtherReactions();
[[nodiscard]] ClickHandlerPtr computeButtonLink(QPoint position) const;
@@ -202,8 +207,7 @@ private:
std::vector<Data::Reaction> _list;
mutable std::vector<ClickHandlerPtr> _links;
QSize _outer;
QRectF _inner;
QRect _innerActive;
QRect _inner;
QImage _cacheInOutService;
QImage _cacheParts;
QImage _cacheForPattern;

View File

@@ -1170,7 +1170,7 @@ bool Gif::needsBubble() const {
return false;
}
QRect Gif::contentRectForReactionButton() const {
QRect Gif::contentRectForReactions() const {
if (!isSeparateRoundVideo()) {
return QRect(0, 0, width(), height());
}
@@ -1191,6 +1191,31 @@ QRect Gif::contentRectForReactionButton() const {
return style::rtlrect(usex + paintx, painty, usew, painth, width());
}
std::optional<int> Gif::reactionButtonCenterOverride() const {
if (!isSeparateRoundVideo()) {
return std::nullopt;
}
const auto inner = contentRectForReactions();
auto fullRight = inner.x() + inner.width();
auto maxRight = _parent->width() - st::msgMargin.left();
if (_parent->hasFromPhoto()) {
maxRight -= st::msgMargin.right();
} else {
maxRight -= st::msgMargin.left();
}
const auto infoWidth = _parent->infoWidth();
if (!_parent->hasOutLayout()) {
// This is just some arbitrary point,
// the main idea is to make info left aligned here.
fullRight += infoWidth - st::normalFont->height;
if (fullRight > maxRight) {
fullRight = maxRight;
}
}
const auto right = fullRight - infoWidth - 3 * st::msgDateImgPadding.x();
return right - st::reactionCornerSize.width() / 2;
}
int Gif::additionalWidth() const {
const auto item = _parent->data();
return additionalWidth(

View File

@@ -96,7 +96,8 @@ public:
bool customInfoLayout() const override {
return _caption.isEmpty();
}
QRect contentRectForReactionButton() const override;
QRect contentRectForReactions() const override;
std::optional<int> reactionButtonCenterOverride() const override;
QString additionalInfoString() const override;
bool skipBubbleTail() const override {

View File

@@ -345,10 +345,6 @@ bool Location::needsBubble() const {
|| _parent->displayFromName();
}
QRect Location::contentRectForReactionButton() const {
return QRect(0, 0, width(), height());
}
int Location::fullWidth() const {
return st::locationSize.width();
}

View File

@@ -53,7 +53,6 @@ public:
bool customInfoLayout() const override {
return true;
}
QRect contentRectForReactionButton() const override;
bool skipBubbleTail() const override {
return isRoundedInBubbleBottom();

View File

@@ -205,8 +205,12 @@ public:
}
[[nodiscard]] virtual bool needsBubble() const = 0;
[[nodiscard]] virtual bool customInfoLayout() const = 0;
[[nodiscard]] virtual QRect contentRectForReactionButton() const {
Unexpected("Media::contentRectForReactionButton");
[[nodiscard]] virtual QRect contentRectForReactions() const {
return QRect(0, 0, width(), height());
}
[[nodiscard]] virtual auto reactionButtonCenterOverride() const
-> std::optional<int> {
return std::nullopt;
}
[[nodiscard]] virtual QMargins bubbleMargins() const {
return QMargins();

View File

@@ -730,10 +730,6 @@ bool GroupedMedia::needsBubble() const {
return _needBubble;
}
QRect GroupedMedia::contentRectForReactionButton() const {
return QRect(0, 0, width(), height());
}
bool GroupedMedia::computeNeedBubble() const {
if (!_caption.isEmpty() || _mode == Mode::Column) {
return true;

View File

@@ -84,7 +84,6 @@ public:
bool customInfoLayout() const override {
return _caption.isEmpty() && (_mode != Mode::Column);
}
QRect contentRectForReactionButton() const override;
bool allowsFastShare() const override {
return true;
}

View File

@@ -401,7 +401,7 @@ TextState UnwrappedMedia::textState(QPoint point, StateRequest request) const {
return result;
}
QRect UnwrappedMedia::contentRectForReactionButton() const {
QRect UnwrappedMedia::contentRectForReactions() const {
const auto inWebPage = (_parent->media() != this);
if (inWebPage) {
return QRect(0, 0, width(), height());
@@ -432,6 +432,15 @@ QRect UnwrappedMedia::contentRectForReactionButton() const {
return QRect(usex, usey, usew, useh);
}
std::optional<int> UnwrappedMedia::reactionButtonCenterOverride() const {
const auto fullRight = calculateFullRight(contentRectForReactions());
const auto right = fullRight
- _parent->infoWidth()
- st::msgDateImgPadding.x() * 2
- st::msgReplyPadding.left();
return right - st::reactionCornerSize.width() / 2;
}
std::unique_ptr<Lottie::SinglePlayer> UnwrappedMedia::stickerTakeLottie(
not_null<DocumentData*> data,
const Lottie::ColorReplacements *replacements) {

View File

@@ -81,7 +81,8 @@ public:
bool customInfoLayout() const override {
return true;
}
QRect contentRectForReactionButton() const override;
QRect contentRectForReactions() const override;
std::optional<int> reactionButtonCenterOverride() const override;
void stickerClearLoopPlayed() override {
_content->stickerClearLoopPlayed();
}

View File

@@ -831,10 +831,6 @@ bool Photo::needsBubble() const {
|| _parent->displayFromName());
}
QRect Photo::contentRectForReactionButton() const {
return QRect(0, 0, width(), height());
}
bool Photo::isReadyForOpen() const {
ensureDataMediaCreated();
return _dataMedia->loaded();

View File

@@ -82,7 +82,6 @@ public:
bool customInfoLayout() const override {
return _caption.isEmpty();
}
QRect contentRectForReactionButton() const override;
bool skipBubbleTail() const override {
return isRoundedInBubbleBottom() && _caption.isEmpty();
}

View File

@@ -61,7 +61,7 @@ private:
using AllEntry = std::pair<not_null<UserData*>, QString>;
void loadMore(const QString &offset);
bool appendRow(not_null<UserData*> user, QString reaction = QString());
bool appendRow(not_null<UserData*> user, QString reaction);
std::unique_ptr<PeerListRow> createRow(
not_null<UserData*> user,
QString reaction) const;
@@ -175,7 +175,7 @@ void Controller::showReaction(const QString &reaction) {
&AllEntry::first
) | ranges::to_vector;
for (const auto user : _filtered) {
appendRow(user);
appendRow(user, _shownReaction);
}
loadMore(QString());
}
@@ -221,7 +221,7 @@ void Controller::loadMore(const QString &offset) {
reaction.match([&](const MTPDmessageUserReaction &data) {
const auto user = sessionData->userLoaded(
data.vuser_id().v);
const auto reaction = filtered ? QString() : qs(data.vreaction());
const auto reaction = qs(data.vreaction());
if (user && appendRow(user, reaction)) {
if (filtered) {
_filtered.emplace_back(user);

View File

@@ -362,7 +362,7 @@ infoIconAdministrators: icon {{ "info/edit/group_manage_admins", infoIconFg, poi
infoIconBlacklist: icon {{ "info_blacklist", infoIconFg, point(-2px, -2px) }};
infoIconPermissions: icon {{ "info/edit/group_manage_permissions", infoIconFg, point(0px, -2px) }};
infoIconInviteLinks: icon {{ "info/edit/group_manage_links", infoIconFg, point(-2px, 0px) }};
infoIconReactions: icon {{ "info/edit/group_manage_reactions", infoIconFg, point(2px, 4px) }};
infoIconReactions: icon {{ "info/edit/group_manage_reactions", infoIconFg }};
infoInformationIconPosition: point(25px, 12px);
infoNotificationsIconPosition: point(20px, 5px);
infoSharedMediaIconPosition: point(20px, 24px);

View File

@@ -976,9 +976,9 @@ reactionInfoBetween: 3px;
reactionCornerSize: size(40px, 32px);
reactionCornerRadius: 14px;
reactionCornerCenter: point(-10px, -8px);
reactionCornerCenter: point(12px, -12px);
reactionCornerImage: 24px;
reactionCornerShadow: margins(4px, 4px, 4px, 8px);
reactionCornerShadow: margins(4px, 8px, 4px, 8px);
reactionCornerActiveAreaPadding: margins(10px, 10px, 10px, 10px);
reactionCornerAddedHeightMax: 120px;

View File

@@ -455,7 +455,7 @@ void Action::refreshText() {
_st.itemStyle,
{ (_content.unknown
? tr::lng_context_seen_loading(tr::now)
: (count == 1)
: (usersCount == 1)
? _content.participants.front().name
: (_content.type == WhoReadType::Reacted
|| (count > 0 && _content.fullReactionsCount > usersCount))

View File

@@ -1,7 +1,7 @@
AppVersion 3004000
AppVersion 3004001
AppVersionStrMajor 3.4
AppVersionStrSmall 3.4
AppVersionStr 3.4.0
AppVersionStrSmall 3.4.1
AppVersionStr 3.4.1
BetaChannel 0
AlphaVersion 0
AppVersionOriginal 3.4
AppVersionOriginal 3.4.1

View File

@@ -1,3 +1,7 @@
3.4.1 (31.12.21)
- Bug fixes and other minor improvements.
3.4 (30.12.21)
- Send reactions to messages.

2
cmake

Submodule cmake updated: 0c57e24529...ed7cf04191