Compare commits
14 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
da8db0157f | ||
|
|
6188268afd | ||
|
|
cd0db53bac | ||
|
|
5bb90679a8 | ||
|
|
72df3a8f91 | ||
|
|
5fe2e649fb | ||
|
|
9eba8ccc73 | ||
|
|
bb3c91aa44 | ||
|
|
9f1268b6c8 | ||
|
|
a6f1a1bd62 | ||
|
|
1b2642b017 | ||
|
|
e722645e7c | ||
|
|
9486c266b5 | ||
|
|
dc21491099 |
@@ -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>
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -1336,7 +1336,7 @@ bool DocumentData::isSongWithCover() const {
|
||||
}
|
||||
|
||||
bool DocumentData::isAudioFile() const {
|
||||
if (isVoiceMessage()) {
|
||||
if (isVoiceMessage() || isVideoFile()) {
|
||||
return false;
|
||||
} else if (isSong()) {
|
||||
return true;
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -417,6 +417,7 @@ private:
|
||||
CursorState _mouseCursorState = CursorState();
|
||||
uint16 _mouseTextSymbol = 0;
|
||||
bool _pressWasInactive = false;
|
||||
bool _recountedAfterPendingResizedItems = false;
|
||||
|
||||
QPoint _trippleClickPoint;
|
||||
base::Timer _trippleClickTimer;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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 ¶meters) {
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -53,7 +53,6 @@ public:
|
||||
bool customInfoLayout() const override {
|
||||
return true;
|
||||
}
|
||||
QRect contentRectForReactionButton() const override;
|
||||
|
||||
bool skipBubbleTail() const override {
|
||||
return isRoundedInBubbleBottom();
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -82,7 +82,6 @@ public:
|
||||
bool customInfoLayout() const override {
|
||||
return _caption.isEmpty();
|
||||
}
|
||||
QRect contentRectForReactionButton() const override;
|
||||
bool skipBubbleTail() const override {
|
||||
return isRoundedInBubbleBottom() && _caption.isEmpty();
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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
|
||||
|
||||
Submodule Telegram/lib_base updated: d985476a6c...161918a0b5
Submodule Telegram/lib_ui updated: 9da4e1e731...1f845ea919
@@ -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
2
cmake
Submodule cmake updated: 0c57e24529...ed7cf04191
Reference in New Issue
Block a user