Compare commits
52 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5c9c836857 | ||
|
|
31b7fe6ba0 | ||
|
|
102c0a96ed | ||
|
|
9a0be43ef5 | ||
|
|
c1d948ef63 | ||
|
|
9df229a230 | ||
|
|
a1c342c822 | ||
|
|
c313cfb4ec | ||
|
|
8d4a658d0b | ||
|
|
86f53d3eff | ||
|
|
3cb89339c8 | ||
|
|
ba98a8df32 | ||
|
|
f3faa52bc7 | ||
|
|
3dac08d34f | ||
|
|
deba090cbd | ||
|
|
5b01f9530b | ||
|
|
df66162bca | ||
|
|
d413080f83 | ||
|
|
38ee57f852 | ||
|
|
c632316ad7 | ||
|
|
bba7010e74 | ||
|
|
edf93b0031 | ||
|
|
611be90880 | ||
|
|
68886e1b61 | ||
|
|
67319c1612 | ||
|
|
da8db0157f | ||
|
|
6188268afd | ||
|
|
cd0db53bac | ||
|
|
5bb90679a8 | ||
|
|
72df3a8f91 | ||
|
|
5fe2e649fb | ||
|
|
9eba8ccc73 | ||
|
|
bb3c91aa44 | ||
|
|
9f1268b6c8 | ||
|
|
a6f1a1bd62 | ||
|
|
1b2642b017 | ||
|
|
e722645e7c | ||
|
|
9486c266b5 | ||
|
|
dc21491099 | ||
|
|
2f48bbd317 | ||
|
|
450f9ca91e | ||
|
|
f86e2d98cc | ||
|
|
0cf85be86b | ||
|
|
a15ef8bbc2 | ||
|
|
ecca60afe4 | ||
|
|
1ab0f840f3 | ||
|
|
3623fb1f9a | ||
|
|
4cbfcc8dbc | ||
|
|
90821428d3 | ||
|
|
c5468a1111 | ||
|
|
505ef04134 | ||
|
|
f70c2adbdd |
BIN
Telegram/Resources/icons/info/edit/group_manage_reactions.png
Normal file
BIN
Telegram/Resources/icons/info/edit/group_manage_reactions.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1016 B |
BIN
Telegram/Resources/icons/info/edit/group_manage_reactions@2x.png
Normal file
BIN
Telegram/Resources/icons/info/edit/group_manage_reactions@2x.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.0 KiB |
BIN
Telegram/Resources/icons/info/edit/group_manage_reactions@3x.png
Normal file
BIN
Telegram/Resources/icons/info/edit/group_manage_reactions@3x.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.9 KiB |
@@ -440,7 +440,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_settings_phone_label" = "Phone number";
|
||||
"lng_settings_username_add" = "Add username";
|
||||
"lng_settings_close_sure" = "Are you sure you want to close this page? You didn't save your changes.";
|
||||
//"lng_settings_peer_to_peer" = "Peer-to-Peer";
|
||||
"lng_settings_peer_to_peer_about" = "Disabling peer-to-peer will relay all calls through Telegram servers to avoid revealing your IP address, but may slightly decrease audio quality.";
|
||||
"lng_settings_advanced" = "Advanced";
|
||||
"lng_settings_stickers_emoji" = "Stickers and emoji";
|
||||
@@ -917,8 +916,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_profile_files_header" = "Files";
|
||||
"lng_profile_audios#one" = "{count} voice message";
|
||||
"lng_profile_audios#other" = "{count} voice messages";
|
||||
//"lng_profile_rounds#one" = "{count} video message";
|
||||
//"lng_profile_rounds#other" = "{count} video messages";
|
||||
"lng_profile_audios_header" = "Voice messages";
|
||||
"lng_profile_shared_links#one" = "{count} shared link";
|
||||
"lng_profile_shared_links#other" = "{count} shared links";
|
||||
@@ -978,8 +975,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_media_selected_file#other" = "{count} Files";
|
||||
"lng_media_selected_audio#one" = "{count} Voice message";
|
||||
"lng_media_selected_audio#other" = "{count} Voice messages";
|
||||
//"lng_media_selected_round#one" = "{count} Video message";
|
||||
//"lng_media_selected_round#other" = "{count} Video messages";
|
||||
"lng_media_selected_link#one" = "{count} Shared link";
|
||||
"lng_media_selected_link#other" = "{count} Shared links";
|
||||
"lng_media_photo_empty" = "No photos here yet";
|
||||
@@ -1729,7 +1724,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_context_send_message" = "Send message";
|
||||
"lng_context_view_group" = "View group info";
|
||||
"lng_context_view_channel" = "View channel info";
|
||||
//"lng_context_view_feed_info" = "View feed info";
|
||||
"lng_context_hide_psa" = "Hide this announcement";
|
||||
"lng_context_pin_to_top" = "Pin to top";
|
||||
"lng_context_unpin_from_top" = "Unpin from top";
|
||||
@@ -1745,6 +1739,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_context_archive_to_list" = "Move to chats list";
|
||||
"lng_context_archive_to_menu_info" = "Archive moved to the main menu!\nYou can return it from the context menu of the archive button.";
|
||||
|
||||
"lng_context_mute" = "Mute notifications";
|
||||
"lng_context_unmute" = "Unmute";
|
||||
|
||||
"lng_context_promote_admin" = "Promote to admin";
|
||||
"lng_context_edit_permissions" = "Edit permissions";
|
||||
"lng_context_restrict_user" = "Restrict user";
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
<Identity Name="TelegramMessengerLLP.TelegramDesktop"
|
||||
ProcessorArchitecture="ARCHITECTURE"
|
||||
Publisher="CN=536BC709-8EE1-4478-AF22-F0F0F26FF64A"
|
||||
Version="3.3.2.0" />
|
||||
Version="3.4.3.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,3,2,0
|
||||
PRODUCTVERSION 3,3,2,0
|
||||
FILEVERSION 3,4,3,0
|
||||
PRODUCTVERSION 3,4,3,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.3.2.0"
|
||||
VALUE "FileVersion", "3.4.3.0"
|
||||
VALUE "LegalCopyright", "Copyright (C) 2014-2021"
|
||||
VALUE "ProductName", "Telegram Desktop"
|
||||
VALUE "ProductVersion", "3.3.2.0"
|
||||
VALUE "ProductVersion", "3.4.3.0"
|
||||
END
|
||||
END
|
||||
BLOCK "VarFileInfo"
|
||||
|
||||
@@ -35,8 +35,8 @@ LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
|
||||
//
|
||||
|
||||
VS_VERSION_INFO VERSIONINFO
|
||||
FILEVERSION 3,3,2,0
|
||||
PRODUCTVERSION 3,3,2,0
|
||||
FILEVERSION 3,4,3,0
|
||||
PRODUCTVERSION 3,4,3,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.3.2.0"
|
||||
VALUE "FileVersion", "3.4.3.0"
|
||||
VALUE "LegalCopyright", "Copyright (C) 2014-2021"
|
||||
VALUE "ProductName", "Telegram Desktop"
|
||||
VALUE "ProductVersion", "3.3.2.0"
|
||||
VALUE "ProductVersion", "3.4.3.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;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -163,21 +179,6 @@ struct State {
|
||||
return result;
|
||||
}
|
||||
|
||||
[[nodiscard]] bool ListUnknown(
|
||||
const std::vector<PeerId> &list,
|
||||
not_null<HistoryItem*> item) {
|
||||
return (list.size() == 1)
|
||||
&& (list.front() == item->history()->session().userPeerId());
|
||||
}
|
||||
|
||||
[[nodiscard]] bool ListUnknown(
|
||||
const std::vector<PeerWithReaction> &list,
|
||||
not_null<HistoryItem*> item) {
|
||||
return (list.size() == 1)
|
||||
&& list.front().reaction.isEmpty()
|
||||
&& (list.front().peer == item->history()->session().userPeerId());
|
||||
}
|
||||
|
||||
[[nodiscard]] Ui::WhoReadType DetectSeenType(not_null<HistoryItem*> item) {
|
||||
if (const auto media = item->media()) {
|
||||
if (!media->webpage()) {
|
||||
@@ -193,7 +194,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 +214,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 +271,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;
|
||||
@@ -493,23 +498,26 @@ rpl::producer<Ui::WhoReadContent> WhoReacted(
|
||||
[](const auto &pair) { return pair.second; });
|
||||
|
||||
// #TODO reactions
|
||||
state->current.mostPopularReaction = item->reactions().front().first;
|
||||
state->current.singleReaction = (list.size() == 1)
|
||||
? list.front().first
|
||||
: QString();
|
||||
}
|
||||
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);
|
||||
|
||||
@@ -522,7 +522,7 @@ void ProxyRow::showMenu() {
|
||||
const QString &text,
|
||||
Fn<void()> callback,
|
||||
const style::icon *icon) {
|
||||
return _menu->addAction(text, std::move(callback));
|
||||
return _menu->addAction(text, std::move(callback), icon);
|
||||
};
|
||||
addAction(tr::lng_proxy_menu_edit(tr::now), [=] {
|
||||
_editClicks.fire({});
|
||||
|
||||
@@ -247,6 +247,7 @@ private:
|
||||
int32 _rowHeight;
|
||||
|
||||
std::vector<std::unique_ptr<Row>> _rows;
|
||||
std::vector<std::unique_ptr<Row>> _oldRows;
|
||||
std::vector<crl::time> _shiftingStartTimes;
|
||||
crl::time _aboveShadowFadeStart = 0;
|
||||
anim::value _aboveShadowFadeOpacity;
|
||||
@@ -547,7 +548,9 @@ void StickersBox::prepare() {
|
||||
}
|
||||
setNoContentMargin(true);
|
||||
_tabs->sectionActivated(
|
||||
) | rpl::start_with_next(
|
||||
) | rpl::filter([=] {
|
||||
return !_ignoreTabActivation;
|
||||
}) | rpl::start_with_next(
|
||||
[this] { switchTab(); },
|
||||
lifetime());
|
||||
refreshTabs();
|
||||
@@ -665,12 +668,16 @@ void StickersBox::refreshTabs() {
|
||||
|| (_tab == &_featured && !_tabIndices.contains(Section::Featured))
|
||||
|| (_tab == &_masks && !_tabIndices.contains(Section::Masks))) {
|
||||
switchTab();
|
||||
} else if (_tab == &_archived) {
|
||||
_tabs->setActiveSectionFast(_tabIndices.indexOf(Section::Archived));
|
||||
} else if (_tab == &_featured) {
|
||||
_tabs->setActiveSectionFast(_tabIndices.indexOf(Section::Featured));
|
||||
} else if (_tab == &_masks) {
|
||||
_tabs->setActiveSectionFast(_tabIndices.indexOf(Section::Masks));
|
||||
} else {
|
||||
_ignoreTabActivation = true;
|
||||
_tabs->setActiveSectionFast(_tabIndices.indexOf((_tab == &_archived)
|
||||
? Section::Archived
|
||||
: (_tab == &_featured)
|
||||
? Section::Featured
|
||||
: (_tab == &_masks)
|
||||
? Section::Masks
|
||||
: Section::Installed));
|
||||
_ignoreTabActivation = false;
|
||||
}
|
||||
updateTabsGeometry();
|
||||
}
|
||||
@@ -1987,6 +1994,7 @@ void StickersBox::Inner::rebuild(bool masks) {
|
||||
|
||||
auto maxNameWidth = countMaxNameWidth();
|
||||
|
||||
_oldRows = std::move(_rows);
|
||||
clear();
|
||||
const auto &order = ([&]() -> const StickersSetsOrder & {
|
||||
if (_section == Section::Installed) {
|
||||
@@ -2038,6 +2046,7 @@ void StickersBox::Inner::rebuild(bool masks) {
|
||||
set->accessHash);
|
||||
}
|
||||
}
|
||||
_oldRows.clear();
|
||||
session().api().requestStickerSets();
|
||||
updateSize();
|
||||
}
|
||||
@@ -2150,19 +2159,55 @@ void StickersBox::Inner::rebuildAppendSet(
|
||||
QString title = fillSetTitle(set, maxNameWidth, &titleWidth);
|
||||
int count = fillSetCount(set);
|
||||
|
||||
_rows.push_back(std::make_unique<Row>(
|
||||
set,
|
||||
sticker,
|
||||
count,
|
||||
title,
|
||||
titleWidth,
|
||||
installed,
|
||||
official,
|
||||
unread,
|
||||
archived,
|
||||
removed,
|
||||
pixw,
|
||||
pixh));
|
||||
const auto existing = [&]{
|
||||
const auto now = int(_rows.size());
|
||||
const auto setProj = [](const std::unique_ptr<Row> &row) {
|
||||
return row ? row->set.get() : nullptr;
|
||||
};
|
||||
if (_oldRows.size() > now
|
||||
&& setProj(_oldRows[now]) == set.get()) {
|
||||
return _oldRows.begin() + now;
|
||||
}
|
||||
return ranges::find(_oldRows, set.get(), setProj);
|
||||
}();
|
||||
if (existing != end(_oldRows)) {
|
||||
const auto raw = existing->get();
|
||||
raw->sticker = sticker;
|
||||
raw->count = count;
|
||||
raw->title = title;
|
||||
raw->titleWidth = titleWidth;
|
||||
raw->installed = installed;
|
||||
raw->official = official;
|
||||
raw->unread = unread;
|
||||
raw->archived = archived;
|
||||
raw->removed = removed;
|
||||
raw->pixw = pixw;
|
||||
raw->pixh = pixh;
|
||||
raw->yadd = {};
|
||||
auto oldStickerMedia = std::move(raw->stickerMedia);
|
||||
auto oldThumbnailMedia = std::move(raw->thumbnailMedia);
|
||||
raw->stickerMedia = sticker->activeMediaView();
|
||||
raw->thumbnailMedia = set->activeThumbnailView();
|
||||
if (raw->thumbnailMedia != oldThumbnailMedia
|
||||
|| (!raw->thumbnailMedia && raw->stickerMedia != oldStickerMedia)) {
|
||||
raw->lottie = nullptr;
|
||||
}
|
||||
_rows.push_back(std::move(*existing));
|
||||
} else {
|
||||
_rows.push_back(std::make_unique<Row>(
|
||||
set,
|
||||
sticker,
|
||||
count,
|
||||
title,
|
||||
titleWidth,
|
||||
installed,
|
||||
official,
|
||||
unread,
|
||||
archived,
|
||||
removed,
|
||||
pixw,
|
||||
pixh));
|
||||
}
|
||||
_shiftingStartTimes.push_back(0);
|
||||
}
|
||||
|
||||
|
||||
@@ -143,6 +143,7 @@ private:
|
||||
|
||||
object_ptr<Ui::SettingsSlider> _tabs = { nullptr };
|
||||
QList<Section> _tabIndices;
|
||||
bool _ignoreTabActivation = false;
|
||||
|
||||
class CounterWidget;
|
||||
object_ptr<CounterWidget> _unreadBadge = { nullptr };
|
||||
|
||||
@@ -9,6 +9,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
|
||||
#include "platform/platform_launcher.h"
|
||||
#include "platform/platform_specific.h"
|
||||
#include "platform/linux/linux_desktop_environment.h"
|
||||
#include "base/platform/base_platform_info.h"
|
||||
#include "base/platform/base_platform_file_utilities.h"
|
||||
#include "ui/main_queue_processor.h"
|
||||
@@ -59,7 +60,12 @@ FilteredCommandLineArguments::FilteredCommandLineArguments(
|
||||
pushArgument("cocoa:fontengine=freetype");
|
||||
#endif // !Q_OS_WIN
|
||||
}
|
||||
#endif // Q_OS_WIN || Q_OS_MAC
|
||||
#elif defined Q_OS_UNIX
|
||||
if (Platform::DesktopEnvironment::IsGnome()) {
|
||||
pushArgument("-platform");
|
||||
pushArgument("xcb;wayland");
|
||||
}
|
||||
#endif // Q_OS_WIN || Q_OS_MAC || Q_OS_UNIX
|
||||
|
||||
pushArgument(nullptr);
|
||||
}
|
||||
|
||||
@@ -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 = 3003002;
|
||||
constexpr auto AppVersionStr = "3.3.2";
|
||||
constexpr auto AppBetaVersion = true;
|
||||
constexpr auto AppVersion = 3004003;
|
||||
constexpr auto AppVersionStr = "3.4.3";
|
||||
constexpr auto AppBetaVersion = false;
|
||||
constexpr auto AppAlphaVersion = TDESKTOP_ALPHA_VERSION;
|
||||
|
||||
@@ -172,11 +172,25 @@ void UpdateCloudFile(
|
||||
return;
|
||||
}
|
||||
|
||||
const auto needStickerThumbnailUpdate = [&] {
|
||||
const auto was = std::get_if<StorageFileLocation>(
|
||||
&file.location.file().data);
|
||||
const auto now = std::get_if<StorageFileLocation>(
|
||||
&data.location.file().data);
|
||||
using Type = StorageFileLocation::Type;
|
||||
if (!was || !now || was->type() != Type::StickerSetThumb) {
|
||||
return false;
|
||||
}
|
||||
return now->valid()
|
||||
&& (now->type() != Type::StickerSetThumb
|
||||
|| now->cacheKey() != was->cacheKey());
|
||||
};
|
||||
const auto update = !file.location.valid()
|
||||
|| (data.location.file().cacheKey()
|
||||
&& (!file.location.file().cacheKey()
|
||||
|| (file.location.width() < data.location.width())
|
||||
|| (file.location.height() < data.location.height())));
|
||||
|| (file.location.height() < data.location.height())
|
||||
|| needStickerThumbnailUpdate()));
|
||||
if (!update) {
|
||||
return;
|
||||
}
|
||||
@@ -204,6 +218,8 @@ void UpdateCloudFile(
|
||||
} else if (file.loader) {
|
||||
const auto origin = base::take(file.loader)->fileOrigin();
|
||||
restartLoader(origin);
|
||||
} else if (file.flags & CloudFile::Flag::Failed) {
|
||||
file.flags &= ~CloudFile::Flag::Failed;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1336,7 +1336,7 @@ bool DocumentData::isSongWithCover() const {
|
||||
}
|
||||
|
||||
bool DocumentData::isAudioFile() const {
|
||||
if (isVoiceMessage()) {
|
||||
if (isVoiceMessage() || isVideoFile()) {
|
||||
return false;
|
||||
} else if (isSong()) {
|
||||
return true;
|
||||
|
||||
@@ -87,6 +87,7 @@ void Reactions::preloadImageFor(const QString &emoji) {
|
||||
if (document) {
|
||||
loadImage(set, document);
|
||||
} else if (!_waitingForList) {
|
||||
_waitingForList = true;
|
||||
refresh();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,6 +42,8 @@ class CloudImageView;
|
||||
int PeerColorIndex(PeerId peerId);
|
||||
int PeerColorIndex(BareId bareId);
|
||||
style::color PeerUserpicColor(PeerId peerId);
|
||||
|
||||
// Must be used only for PeerColor-s.
|
||||
PeerId FakePeerIdForJustName(const QString &name);
|
||||
|
||||
class RestrictionCheckResult {
|
||||
|
||||
@@ -10,6 +10,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "api/api_text_entities.h"
|
||||
#include "apiwrap.h"
|
||||
#include "base/unixtime.h"
|
||||
#include "data/data_user.h"
|
||||
#include "data/data_channel.h"
|
||||
#include "data/data_peer_id.h"
|
||||
#include "data/data_session.h"
|
||||
@@ -71,23 +72,10 @@ bool SponsoredMessages::append(not_null<History*> history) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto flags = MessageFlags(0)
|
||||
| (history->peer->isChannel() ? MessageFlag::Post : MessageFlags(0))
|
||||
| MessageFlag::HasFromId
|
||||
| MessageFlag::IsSponsored
|
||||
| MessageFlag::Local;
|
||||
auto local = history->addNewLocalMessage(
|
||||
entryIt->item.reset(history->addNewLocalMessage(
|
||||
_session->data().nextLocalMessageId(),
|
||||
flags,
|
||||
UserId(0),
|
||||
MsgId(0),
|
||||
HistoryItem::NewMessageDate(0),
|
||||
entryIt->sponsored.fromId,
|
||||
QString(),
|
||||
entryIt->sponsored.textWithEntities,
|
||||
MTP_messageMediaEmpty(),
|
||||
HistoryMessageMarkupData());
|
||||
entryIt->item.reset(std::move(local));
|
||||
entryIt->sponsored.from,
|
||||
entryIt->sponsored.textWithEntities));
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -163,36 +151,53 @@ void SponsoredMessages::append(
|
||||
});
|
||||
const auto randomId = data.vrandom_id().v;
|
||||
const auto hash = qs(data.vchat_invite_hash().value_or_empty());
|
||||
const auto fromId = [&] {
|
||||
const auto makeFrom = [](
|
||||
not_null<PeerData*> peer,
|
||||
bool exactPost = false) {
|
||||
const auto channel = peer->asChannel();
|
||||
return SponsoredFrom{
|
||||
.peer = peer,
|
||||
.title = peer->name,
|
||||
.isBroadcast = (channel && channel->isBroadcast()),
|
||||
.isMegagroup = (channel && channel->isMegagroup()),
|
||||
.isChannel = (channel != nullptr),
|
||||
.isPublic = (channel && channel->isPublic()),
|
||||
.isBot = (peer->isUser() && peer->asUser()->isBot()),
|
||||
.isExactPost = exactPost,
|
||||
};
|
||||
};
|
||||
const auto from = [&]() -> SponsoredFrom {
|
||||
if (data.vfrom_id()) {
|
||||
return peerFromMTP(*data.vfrom_id());
|
||||
return makeFrom(
|
||||
_session->data().peer(peerFromMTP(*data.vfrom_id())),
|
||||
(data.vchannel_post() != nullptr));
|
||||
}
|
||||
Assert(data.vchat_invite());
|
||||
return data.vchat_invite()->match([](const MTPDchatInvite &data) {
|
||||
return Data::FakePeerIdForJustName(qs(data.vtitle()));
|
||||
return SponsoredFrom{
|
||||
.title = qs(data.vtitle()),
|
||||
.isBroadcast = data.is_broadcast(),
|
||||
.isMegagroup = data.is_megagroup(),
|
||||
.isChannel = data.is_channel(),
|
||||
.isPublic = data.is_public(),
|
||||
};
|
||||
}, [&](const MTPDchatInviteAlready &data) {
|
||||
const auto chat = _session->data().processChat(data.vchat());
|
||||
if (!chat) {
|
||||
return PeerId(0);
|
||||
}
|
||||
if (const auto channel = chat->asChannel()) {
|
||||
channel->clearInvitePeek();
|
||||
}
|
||||
return chat->id;
|
||||
return makeFrom(chat);
|
||||
}, [&](const MTPDchatInvitePeek &data) {
|
||||
const auto chat = _session->data().processChat(data.vchat());
|
||||
if (!chat) {
|
||||
return PeerId(0);
|
||||
}
|
||||
if (const auto channel = chat->asChannel()) {
|
||||
channel->setInvitePeek(hash, data.vexpires().v);
|
||||
}
|
||||
return chat->id;
|
||||
return makeFrom(chat);
|
||||
});
|
||||
}();
|
||||
auto sharedMessage = SponsoredMessage{
|
||||
.randomId = randomId,
|
||||
.fromId = fromId,
|
||||
.from = from,
|
||||
.textWithEntities = {
|
||||
.text = qs(data.vmessage()),
|
||||
.entities = Api::EntitiesFromMTP(
|
||||
@@ -263,17 +268,17 @@ void SponsoredMessages::view(const FullMsgId &fullId) {
|
||||
}).send();
|
||||
}
|
||||
|
||||
SponsoredMessages::ChannelPost SponsoredMessages::channelPost(
|
||||
SponsoredMessages::Details SponsoredMessages::lookupDetails(
|
||||
const FullMsgId &fullId) const {
|
||||
const auto entryPtr = find(fullId);
|
||||
if (!entryPtr) {
|
||||
return { .msgId = ShowAtUnreadMsgId, .hash = std::nullopt };
|
||||
return {};
|
||||
}
|
||||
const auto msgId = entryPtr->sponsored.msgId;
|
||||
const auto hash = entryPtr->sponsored.chatInviteHash;
|
||||
const auto &hash = entryPtr->sponsored.chatInviteHash;
|
||||
return {
|
||||
.msgId = msgId ? msgId : ShowAtUnreadMsgId,
|
||||
.hash = hash.isEmpty() ? std::nullopt : std::make_optional(hash),
|
||||
.peer = entryPtr->sponsored.from.peer,
|
||||
.msgId = entryPtr->sponsored.msgId,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -20,9 +20,20 @@ namespace Data {
|
||||
|
||||
class Session;
|
||||
|
||||
struct SponsoredMessage final {
|
||||
struct SponsoredFrom {
|
||||
PeerData *peer = nullptr;
|
||||
QString title;
|
||||
bool isBroadcast = false;
|
||||
bool isMegagroup = false;
|
||||
bool isChannel = false;
|
||||
bool isPublic = false;
|
||||
bool isBot = false;
|
||||
bool isExactPost = false;
|
||||
};
|
||||
|
||||
struct SponsoredMessage {
|
||||
QByteArray randomId;
|
||||
PeerId fromId;
|
||||
SponsoredFrom from;
|
||||
TextWithEntities textWithEntities;
|
||||
History *history = nullptr;
|
||||
MsgId msgId;
|
||||
@@ -31,9 +42,10 @@ struct SponsoredMessage final {
|
||||
|
||||
class SponsoredMessages final {
|
||||
public:
|
||||
struct ChannelPost {
|
||||
MsgId msgId;
|
||||
struct Details {
|
||||
std::optional<QString> hash;
|
||||
PeerData *peer = nullptr;
|
||||
MsgId msgId;
|
||||
};
|
||||
using RandomId = QByteArray;
|
||||
explicit SponsoredMessages(not_null<Session*> owner);
|
||||
@@ -45,7 +57,7 @@ public:
|
||||
void request(not_null<History*> history);
|
||||
[[nodiscard]] bool append(not_null<History*> history);
|
||||
void clearItems(not_null<History*> history);
|
||||
[[nodiscard]] ChannelPost channelPost(const FullMsgId &fullId) const;
|
||||
[[nodiscard]] Details lookupDetails(const FullMsgId &fullId) const;
|
||||
|
||||
void view(const FullMsgId &fullId);
|
||||
|
||||
|
||||
@@ -280,9 +280,6 @@ enum class MessageFlag : uint32 {
|
||||
|
||||
// Contact sign-up message, notification should be skipped for Silent.
|
||||
IsContactSignUp = (1U << 30),
|
||||
|
||||
// In channels.
|
||||
IsSponsored = (1U << 31),
|
||||
};
|
||||
inline constexpr bool is_flag_type(MessageFlag) { return true; }
|
||||
using MessageFlags = base::flags<MessageFlag>;
|
||||
|
||||
@@ -593,6 +593,7 @@ void Widget::checkUpdateStatus() {
|
||||
Core::checkReadyUpdate();
|
||||
App::restart();
|
||||
});
|
||||
_connecting->raise();
|
||||
} else {
|
||||
if (!_updateTelegram) return;
|
||||
_updateTelegram.destroy();
|
||||
|
||||
@@ -22,6 +22,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "data/data_changes.h"
|
||||
#include "data/data_chat_filters.h"
|
||||
#include "data/data_scheduled_messages.h"
|
||||
#include "data/data_sponsored_messages.h"
|
||||
#include "data/data_send_action.h"
|
||||
#include "data/data_folder.h"
|
||||
#include "data/data_photo.h"
|
||||
@@ -673,6 +674,18 @@ not_null<HistoryItem*> History::addNewLocalMessage(
|
||||
true);
|
||||
}
|
||||
|
||||
not_null<HistoryItem*> History::addNewLocalMessage(
|
||||
MsgId id,
|
||||
Data::SponsoredFrom from,
|
||||
const TextWithEntities &textWithEntities) {
|
||||
return addNewItem(
|
||||
makeMessage(
|
||||
id,
|
||||
from,
|
||||
textWithEntities),
|
||||
true);
|
||||
}
|
||||
|
||||
void History::setUnreadMentionsCount(int count) {
|
||||
const auto had = _unreadMentionsCount && (*_unreadMentionsCount > 0);
|
||||
if (_unreadMentions.size() > count) {
|
||||
|
||||
@@ -35,6 +35,7 @@ struct Draft;
|
||||
class Session;
|
||||
class Folder;
|
||||
class ChatFilter;
|
||||
struct SponsoredFrom;
|
||||
|
||||
enum class ForwardOptions {
|
||||
PreserveInfo,
|
||||
@@ -190,6 +191,10 @@ public:
|
||||
const QString &postAuthor,
|
||||
not_null<GameData*> game,
|
||||
HistoryMessageMarkupData &&markup);
|
||||
not_null<HistoryItem*> addNewLocalMessage(
|
||||
MsgId id,
|
||||
Data::SponsoredFrom from,
|
||||
const TextWithEntities &textWithEntities); // sponsored
|
||||
|
||||
// Used only internally and for channel admin log.
|
||||
not_null<HistoryItem*> createItem(
|
||||
|
||||
@@ -679,6 +679,9 @@ void HistoryInner::paintEvent(QPaintEvent *e) {
|
||||
}
|
||||
if (hasPendingResizedItems()) {
|
||||
return;
|
||||
} else if (_recountedAfterPendingResizedItems) {
|
||||
_recountedAfterPendingResizedItems = false;
|
||||
mouseActionUpdate();
|
||||
}
|
||||
|
||||
const auto guard = gsl::finally([&] {
|
||||
@@ -893,7 +896,7 @@ void HistoryInner::paintEvent(QPaintEvent *e) {
|
||||
userpicTop,
|
||||
width(),
|
||||
st::msgPhotoSize);
|
||||
} else if (const auto info = view->data()->hiddenForwardedInfo()) {
|
||||
} else if (const auto info = view->data()->hiddenSenderInfo()) {
|
||||
info->userpic.paint(
|
||||
p,
|
||||
st::historyPhotoLeft,
|
||||
@@ -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;
|
||||
|
||||
@@ -45,6 +45,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "data/data_channel.h"
|
||||
#include "data/data_chat.h"
|
||||
#include "data/data_user.h"
|
||||
#include "data/data_sponsored_messages.h"
|
||||
#include "styles/style_dialogs.h"
|
||||
#include "styles/style_chat.h"
|
||||
|
||||
@@ -294,6 +295,10 @@ HistoryItem *HistoryItem::lookupDiscussionPostOriginal() const {
|
||||
PeerData *HistoryItem::displayFrom() const {
|
||||
if (const auto sender = discussionPostOriginalSender()) {
|
||||
return sender;
|
||||
} else if (const auto sponsored = Get<HistoryMessageSponsored>()) {
|
||||
if (sponsored->sender) {
|
||||
return nullptr;
|
||||
}
|
||||
} else if (const auto forwarded = Get<HistoryMessageForwarded>()) {
|
||||
if (history()->peer->isSelf() || history()->peer->isRepliesChat() || forwarded->imported) {
|
||||
return forwarded->originalSender;
|
||||
@@ -377,7 +382,7 @@ void HistoryItem::setIsPinned(bool pinned) {
|
||||
id));
|
||||
}
|
||||
if (changed) {
|
||||
history()->owner().requestItemResize(this);
|
||||
history()->owner().notifyItemDataChange(this);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -484,7 +489,7 @@ bool HistoryItem::isScheduled() const {
|
||||
}
|
||||
|
||||
bool HistoryItem::isSponsored() const {
|
||||
return (_flags & MessageFlag::IsSponsored);
|
||||
return Has<HistoryMessageSponsored>();
|
||||
}
|
||||
|
||||
bool HistoryItem::skipNotification() const {
|
||||
@@ -767,7 +772,14 @@ bool HistoryItem::suggestDeleteAllReport() const {
|
||||
}
|
||||
|
||||
bool HistoryItem::canReact() const {
|
||||
return isRegular() && !isService();
|
||||
if (!isRegular() || isService()) {
|
||||
return false;
|
||||
} else if (const auto media = this->media()) {
|
||||
if (media->call()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void HistoryItem::addReaction(const QString &reaction) {
|
||||
@@ -893,8 +905,10 @@ PeerData *HistoryItem::senderOriginal() const {
|
||||
return (peer->isChannel() && !peer->isMegagroup()) ? peer : from();
|
||||
}
|
||||
|
||||
const HiddenSenderInfo *HistoryItem::hiddenForwardedInfo() const {
|
||||
if (const auto forwarded = Get<HistoryMessageForwarded>()) {
|
||||
const HiddenSenderInfo *HistoryItem::hiddenSenderInfo() const {
|
||||
if (const auto sponsored = Get<HistoryMessageSponsored>()) {
|
||||
return sponsored->sender.get();
|
||||
} else if (const auto forwarded = Get<HistoryMessageForwarded>()) {
|
||||
return forwarded->hiddenSenderInfo.get();
|
||||
}
|
||||
return nullptr;
|
||||
@@ -1111,6 +1125,8 @@ ItemPreview HistoryItem::toPreview(ToPreviewOptions options) const {
|
||||
const auto sender = [&]() -> std::optional<QString> {
|
||||
if (options.hideSender || isPost() || isEmpty()) {
|
||||
return {};
|
||||
} else if (const auto sponsored = Get<HistoryMessageSponsored>()) {
|
||||
return sponsored->sender->name;
|
||||
} else if (!_history->peer->isUser()) {
|
||||
if (const auto from = displayFrom()) {
|
||||
return fromSender(from);
|
||||
|
||||
@@ -387,7 +387,7 @@ public:
|
||||
|
||||
[[nodiscard]] TimeId dateOriginal() const;
|
||||
[[nodiscard]] PeerData *senderOriginal() const;
|
||||
[[nodiscard]] const HiddenSenderInfo *hiddenForwardedInfo() const;
|
||||
[[nodiscard]] const HiddenSenderInfo *hiddenSenderInfo() const;
|
||||
[[nodiscard]] not_null<PeerData*> fromOriginal() const;
|
||||
[[nodiscard]] QString authorOriginal() const;
|
||||
[[nodiscard]] MsgId idOriginal() const;
|
||||
|
||||
@@ -102,6 +102,18 @@ struct HistoryMessageForwarded : public RuntimeComponent<HistoryMessageForwarded
|
||||
bool imported = false;
|
||||
};
|
||||
|
||||
struct HistoryMessageSponsored : public RuntimeComponent<HistoryMessageSponsored, HistoryItem> {
|
||||
enum class Type : uchar {
|
||||
User,
|
||||
Group,
|
||||
Broadcast,
|
||||
Post,
|
||||
Bot,
|
||||
};
|
||||
std::unique_ptr<HiddenSenderInfo> sender;
|
||||
Type type = Type::User;
|
||||
};
|
||||
|
||||
struct HistoryMessageReply : public RuntimeComponent<HistoryMessageReply, HistoryItem> {
|
||||
HistoryMessageReply() = default;
|
||||
HistoryMessageReply(const HistoryMessageReply &other) = delete;
|
||||
|
||||
@@ -47,6 +47,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "data/data_user.h"
|
||||
#include "data/data_histories.h"
|
||||
#include "data/data_web_page.h"
|
||||
#include "data/data_sponsored_messages.h"
|
||||
#include "styles/style_dialogs.h"
|
||||
#include "styles/style_widgets.h"
|
||||
#include "styles/style_chat.h"
|
||||
@@ -582,7 +583,7 @@ HistoryMessage::HistoryMessage(
|
||||
&& (!originalMedia || !originalMedia->forceForwardedInfo()));
|
||||
if (!dropForwardInfo) {
|
||||
config.originalDate = original->dateOriginal();
|
||||
if (const auto info = original->hiddenForwardedInfo()) {
|
||||
if (const auto info = original->hiddenSenderInfo()) {
|
||||
config.senderNameOriginal = info->name;
|
||||
} else if (const auto senderOriginal = original->senderOriginal()) {
|
||||
config.senderOriginal = senderOriginal->id;
|
||||
@@ -768,6 +769,29 @@ HistoryMessage::HistoryMessage(
|
||||
setEmptyText();
|
||||
}
|
||||
|
||||
HistoryMessage::HistoryMessage(
|
||||
not_null<History*> history,
|
||||
MsgId id,
|
||||
Data::SponsoredFrom from,
|
||||
const TextWithEntities &textWithEntities)
|
||||
: HistoryItem(
|
||||
history,
|
||||
id,
|
||||
((history->peer->isChannel() ? MessageFlag::Post : MessageFlag(0))
|
||||
//| (from.peer ? MessageFlag::HasFromId : MessageFlag(0))
|
||||
| MessageFlag::Local),
|
||||
HistoryItem::NewMessageDate(0),
|
||||
/*from.peer ? from.peer->id : */PeerId(0)) {
|
||||
createComponentsHelper(
|
||||
_flags,
|
||||
MsgId(0), // replyTo
|
||||
UserId(0), // viaBotId
|
||||
QString(), // postAuthor
|
||||
HistoryMessageMarkupData());
|
||||
setText(textWithEntities);
|
||||
setSponsoredFrom(from);
|
||||
}
|
||||
|
||||
void HistoryMessage::createComponentsHelper(
|
||||
MessageFlags flags,
|
||||
MsgId replyTo,
|
||||
@@ -1915,6 +1939,25 @@ void HistoryMessage::setUnreadRepliesCount(
|
||||
Data::MessageUpdate::Flag::RepliesUnreadCount);
|
||||
}
|
||||
|
||||
void HistoryMessage::setSponsoredFrom(const Data::SponsoredFrom &from) {
|
||||
AddComponents(HistoryMessageSponsored::Bit());
|
||||
const auto sponsored = Get<HistoryMessageSponsored>();
|
||||
sponsored->sender = std::make_unique<HiddenSenderInfo>(
|
||||
from.title,
|
||||
false);
|
||||
|
||||
using Type = HistoryMessageSponsored::Type;
|
||||
sponsored->type = from.isExactPost
|
||||
? Type::Post
|
||||
: from.isBot
|
||||
? Type::Bot
|
||||
: from.isBroadcast
|
||||
? Type::Broadcast
|
||||
: (from.peer && from.peer->isUser())
|
||||
? Type::User
|
||||
: Type::Group;
|
||||
}
|
||||
|
||||
void HistoryMessage::setReplyToTop(MsgId replyToTop) {
|
||||
const auto reply = Get<HistoryMessageReply>();
|
||||
if (!reply
|
||||
|
||||
@@ -14,6 +14,10 @@ struct SendAction;
|
||||
struct SendOptions;
|
||||
} // namespace Api
|
||||
|
||||
namespace Data {
|
||||
struct SponsoredFrom;
|
||||
} // namespace Data
|
||||
|
||||
namespace HistoryView {
|
||||
class Message;
|
||||
} // namespace HistoryView
|
||||
@@ -115,6 +119,11 @@ public:
|
||||
const QString &postAuthor,
|
||||
not_null<GameData*> game,
|
||||
HistoryMessageMarkupData &&markup); // local game
|
||||
HistoryMessage(
|
||||
not_null<History*> history,
|
||||
MsgId id,
|
||||
Data::SponsoredFrom from,
|
||||
const TextWithEntities &textWithEntities); // sponsored
|
||||
|
||||
void refreshMedia(const MTPMessageMedia *media);
|
||||
void refreshSentMedia(const MTPMessageMedia *media);
|
||||
@@ -251,6 +260,7 @@ private:
|
||||
void setUnreadRepliesCount(
|
||||
not_null<HistoryMessageViews*> views,
|
||||
int count);
|
||||
void setSponsoredFrom(const Data::SponsoredFrom &from);
|
||||
|
||||
static void FillForwardedInfo(
|
||||
CreateConfig &config,
|
||||
|
||||
@@ -479,18 +479,6 @@ HistoryWidget::HistoryWidget(
|
||||
Window::ActivateWindow(controller);
|
||||
});
|
||||
|
||||
controller->adaptive().changes(
|
||||
) | rpl::start_with_next([=] {
|
||||
if (_history) {
|
||||
_history->forceFullResize();
|
||||
if (_migrated) {
|
||||
_migrated->forceFullResize();
|
||||
}
|
||||
updateHistoryGeometry();
|
||||
update();
|
||||
}
|
||||
}, lifetime());
|
||||
|
||||
session().data().newItemAdded(
|
||||
) | rpl::start_with_next([=](not_null<HistoryItem*> item) {
|
||||
newItemAdded(item);
|
||||
@@ -2151,6 +2139,16 @@ void HistoryWidget::showHistory(
|
||||
object_ptr<HistoryInner>(this, _scroll, controller(), _history));
|
||||
_list->show();
|
||||
|
||||
controller()->adaptive().changes(
|
||||
) | rpl::start_with_next([=] {
|
||||
_history->forceFullResize();
|
||||
if (_migrated) {
|
||||
_migrated->forceFullResize();
|
||||
}
|
||||
updateHistoryGeometry();
|
||||
update();
|
||||
}, _list->lifetime());
|
||||
|
||||
if (_chooseForReport && _chooseForReport->active) {
|
||||
_list->setChooseReportReason(_chooseForReport->reason);
|
||||
}
|
||||
@@ -2243,6 +2241,12 @@ void HistoryWidget::setHistory(History *history) {
|
||||
return;
|
||||
}
|
||||
unregisterDraftSources();
|
||||
if (_history) {
|
||||
_history->forceFullResize();
|
||||
}
|
||||
if (_migrated) {
|
||||
_migrated->forceFullResize();
|
||||
}
|
||||
_history = history;
|
||||
_migrated = _history ? _history->migrateFrom() : nullptr;
|
||||
registerDraftSource();
|
||||
@@ -7006,7 +7010,7 @@ void HistoryWidget::updateForwardingTexts() {
|
||||
fullname = from->name;
|
||||
}
|
||||
version += from->nameVersion;
|
||||
} else if (const auto info = item->hiddenForwardedInfo()) {
|
||||
} else if (const auto info = item->hiddenSenderInfo()) {
|
||||
if (!insertedNames.contains(info->name)) {
|
||||
insertedNames.emplace(info->name);
|
||||
names.push_back(info->firstName);
|
||||
@@ -7058,7 +7062,7 @@ void HistoryWidget::checkForwardingInfo() {
|
||||
for (const auto item : _toForward.items) {
|
||||
if (const auto from = item->senderOriginal()) {
|
||||
version += from->nameVersion;
|
||||
} else if (const auto info = item->hiddenForwardedInfo()) {
|
||||
} else if (const auto info = item->hiddenSenderInfo()) {
|
||||
++version;
|
||||
} else {
|
||||
Unexpected("Corrupt forwarded information in message.");
|
||||
|
||||
@@ -159,6 +159,17 @@ void BottomInfo::paint(
|
||||
authorEditedWidth,
|
||||
outerWidth);
|
||||
|
||||
if (_data.flags & Data::Flag::Pinned) {
|
||||
const auto &icon = inverted
|
||||
? st->historyPinInvertedIcon()
|
||||
: stm->historyPinIcon;
|
||||
right -= st::historyPinWidth;
|
||||
icon.paint(
|
||||
p,
|
||||
right,
|
||||
firstLineBottom + st::historyPinTop,
|
||||
outerWidth);
|
||||
}
|
||||
if (!_views.isEmpty()) {
|
||||
const auto viewsWidth = _views.maxWidth();
|
||||
right -= st::historyViewsSpace + viewsWidth;
|
||||
@@ -167,9 +178,10 @@ void BottomInfo::paint(
|
||||
const auto &icon = inverted
|
||||
? st->historyViewsInvertedIcon()
|
||||
: stm->historyViewsIcon;
|
||||
right -= st::historyViewsWidth;
|
||||
icon.paint(
|
||||
p,
|
||||
right - st::historyViewsWidth,
|
||||
right,
|
||||
firstLineBottom + st::historyViewsTop,
|
||||
outerWidth);
|
||||
}
|
||||
@@ -181,9 +193,10 @@ void BottomInfo::paint(
|
||||
const auto &icon = inverted
|
||||
? st->historyRepliesInvertedIcon()
|
||||
: stm->historyRepliesIcon;
|
||||
right -= st::historyViewsWidth;
|
||||
icon.paint(
|
||||
p,
|
||||
right - st::historyViewsWidth,
|
||||
right,
|
||||
firstLineBottom + st::historyViewsTop,
|
||||
outerWidth);
|
||||
}
|
||||
@@ -279,7 +292,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;
|
||||
@@ -365,6 +378,9 @@ QSize BottomInfo::countOptimalSize() {
|
||||
+ _replies.maxWidth()
|
||||
+ st::historyViewsWidth;
|
||||
}
|
||||
if (_data.flags & Data::Flag::Pinned) {
|
||||
width += st::historyPinWidth;
|
||||
}
|
||||
_reactionsMaxWidth = countReactionsMaxWidth();
|
||||
width += _reactionsMaxWidth;
|
||||
return QSize(width, st::msgDateFont->height);
|
||||
@@ -408,6 +424,9 @@ BottomInfo::Data BottomInfoDataFromMessage(not_null<Message*> message) {
|
||||
if (item->isSponsored()) {
|
||||
result.flags |= Flag::Sponsored;
|
||||
}
|
||||
if (item->isPinned() && message->context() != Context::Pinned) {
|
||||
result.flags |= Flag::Pinned;
|
||||
}
|
||||
if (const auto msgsigned = item->Get<HistoryMessageSigned>()) {
|
||||
if (!msgsigned->isAnonymousRank) {
|
||||
result.author = msgsigned->author;
|
||||
|
||||
@@ -35,6 +35,7 @@ public:
|
||||
Sending = 0x04,
|
||||
RepliesContext = 0x08,
|
||||
Sponsored = 0x10,
|
||||
Pinned = 0x20,
|
||||
//Unread, // We don't want to pass and update it in Date for now.
|
||||
};
|
||||
friend inline constexpr bool is_flag_type(Flag) { return true; };
|
||||
|
||||
@@ -45,7 +45,7 @@ namespace {
|
||||
// A new message from the same sender is attached to previous within 15 minutes.
|
||||
constexpr int kAttachMessageToPreviousSecondsDelta = 900;
|
||||
|
||||
bool IsAttachedToPreviousInSavedMessages(
|
||||
[[nodiscard]] bool IsAttachedToPreviousInSavedMessages(
|
||||
not_null<HistoryItem*> previous,
|
||||
HistoryMessageForwarded *prevForwarded,
|
||||
not_null<HistoryItem*> item,
|
||||
@@ -65,6 +65,22 @@ bool IsAttachedToPreviousInSavedMessages(
|
||||
return (*previousInfo == *itemInfo);
|
||||
}
|
||||
|
||||
[[nodiscard]] Window::SessionController *ContextOrSessionWindow(
|
||||
const ClickHandlerContext &context,
|
||||
not_null<Main::Session*> session) {
|
||||
if (const auto controller = context.sessionWindow.get()) {
|
||||
return controller;
|
||||
}
|
||||
const auto &windows = session->windows();
|
||||
if (windows.empty()) {
|
||||
session->domain().activate(&session->account());
|
||||
if (windows.empty()) {
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
return windows.front();
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
std::unique_ptr<Ui::PathShiftGradient> MakePathShiftGradient(
|
||||
@@ -599,7 +615,26 @@ ClickHandlerPtr Element::fromLink() const {
|
||||
return _fromLink;
|
||||
}
|
||||
const auto item = data();
|
||||
if (const auto from = item->displayFrom()) {
|
||||
if (item->isSponsored()) {
|
||||
const auto session = &item->history()->session();
|
||||
_fromLink = std::make_shared<LambdaClickHandler>([=](
|
||||
ClickContext context) {
|
||||
if (context.button != Qt::LeftButton) {
|
||||
return;
|
||||
}
|
||||
const auto my = context.other.value<ClickHandlerContext>();
|
||||
if (const auto window = ContextOrSessionWindow(my, session)) {
|
||||
auto &sponsored = session->data().sponsoredMessages();
|
||||
const auto details = sponsored.lookupDetails(my.itemId);
|
||||
if (const auto &hash = details.hash) {
|
||||
Api::CheckChatInvite(window, *hash);
|
||||
} else if (const auto peer = details.peer) {
|
||||
window->showPeerInfo(peer);
|
||||
}
|
||||
}
|
||||
});
|
||||
return _fromLink;
|
||||
} else if (const auto from = item->displayFrom()) {
|
||||
_fromLink = std::make_shared<LambdaClickHandler>([=](
|
||||
ClickContext context) {
|
||||
if (context.button != Qt::LeftButton) {
|
||||
@@ -607,29 +642,8 @@ ClickHandlerPtr Element::fromLink() const {
|
||||
}
|
||||
const auto my = context.other.value<ClickHandlerContext>();
|
||||
const auto session = &from->session();
|
||||
const auto window = [&]() -> Window::SessionController* {
|
||||
if (const auto controller = my.sessionWindow.get()) {
|
||||
return controller;
|
||||
}
|
||||
const auto &windows = session->windows();
|
||||
if (windows.empty()) {
|
||||
session->domain().activate(&session->account());
|
||||
if (windows.empty()) {
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
return windows.front();
|
||||
}();
|
||||
if (window) {
|
||||
const auto inviteHash = item->isSponsored()
|
||||
? session->data().sponsoredMessages().channelPost(
|
||||
my.itemId).hash
|
||||
: std::nullopt;
|
||||
if (inviteHash) {
|
||||
Api::CheckChatInvite(window, *inviteHash);
|
||||
} else {
|
||||
window->showPeerInfo(from);
|
||||
}
|
||||
if (const auto window = ContextOrSessionWindow(my, session)) {
|
||||
window->showPeerInfo(from);
|
||||
}
|
||||
});
|
||||
_fromLink->setProperty(kPeerLinkPeerIdProperty, from->id.value);
|
||||
|
||||
@@ -1753,7 +1753,7 @@ void ListWidget::paintEvent(QPaintEvent *e) {
|
||||
userpicTop,
|
||||
view->width(),
|
||||
st::msgPhotoSize);
|
||||
} else if (const auto info = view->data()->hiddenForwardedInfo()) {
|
||||
} else if (const auto info = view->data()->hiddenSenderInfo()) {
|
||||
info->userpic.paint(
|
||||
p,
|
||||
st::historyPhotoLeft,
|
||||
|
||||
@@ -30,6 +30,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "data/data_user.h"
|
||||
#include "data/data_channel.h"
|
||||
#include "data/data_message_reactions.h"
|
||||
#include "data/data_sponsored_messages.h"
|
||||
#include "lang/lang_keys.h"
|
||||
#include "mainwidget.h"
|
||||
#include "main/main_session.h"
|
||||
@@ -415,7 +416,7 @@ QSize Message::performCountOptimalSize() {
|
||||
const auto from = item->displayFrom();
|
||||
const auto &name = from
|
||||
? from->nameText()
|
||||
: item->hiddenForwardedInfo()->nameText;
|
||||
: item->hiddenSenderInfo()->nameText;
|
||||
auto namew = st::msgPadding.left()
|
||||
+ name.maxWidth()
|
||||
+ st::msgPadding.right();
|
||||
@@ -605,7 +606,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);
|
||||
@@ -935,17 +936,24 @@ void Message::paintFromName(
|
||||
|
||||
const auto nameText = [&]() -> const Ui::Text::String * {
|
||||
const auto from = item->displayFrom();
|
||||
if (context.outbg || item->isPost()) {
|
||||
p.setPen(stm->msgServiceFg);
|
||||
const auto service = (context.outbg || item->isPost());
|
||||
const auto st = context.st;
|
||||
if (from) {
|
||||
p.setPen(!service
|
||||
? FromNameFg(context, from->id)
|
||||
: item->isSponsored()
|
||||
? st->boxTextFgGood()
|
||||
: stm->msgServiceFg);
|
||||
return &from->nameText();
|
||||
} else if (from) {
|
||||
p.setPen(FromNameFg(context, from->id));
|
||||
return &from->nameText();
|
||||
} else if (const auto info = item->hiddenForwardedInfo()) {
|
||||
p.setPen(FromNameFg(context, info->colorPeerId));
|
||||
} else if (const auto info = item->hiddenSenderInfo()) {
|
||||
p.setPen(!service
|
||||
? FromNameFg(context, info->colorPeerId)
|
||||
: item->isSponsored()
|
||||
? st->boxTextFgGood()
|
||||
: stm->msgServiceFg);
|
||||
return &info->nameText;
|
||||
} else {
|
||||
Unexpected("Corrupt forwarded information in message.");
|
||||
Unexpected("Corrupt sender information in message.");
|
||||
}
|
||||
}();
|
||||
nameText->drawElided(p, availableLeft, trect.top(), availableWidth);
|
||||
@@ -1281,7 +1289,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);
|
||||
@@ -1524,7 +1532,7 @@ bool Message::getStateFromName(
|
||||
const auto nameText = [&]() -> const Ui::Text::String * {
|
||||
if (from) {
|
||||
return &from->nameText();
|
||||
} else if (const auto info = item->hiddenForwardedInfo()) {
|
||||
} else if (const auto info = item->hiddenSenderInfo()) {
|
||||
return &info->nameText;
|
||||
} else {
|
||||
Unexpected("Corrupt forwarded information in message.");
|
||||
@@ -1837,11 +1845,7 @@ Reactions::ButtonParameters Message::reactionButtonParameters(
|
||||
using namespace Reactions;
|
||||
auto result = ButtonParameters{ .context = data()->fullId() };
|
||||
const auto outbg = hasOutLayout();
|
||||
result.style = (!_comments && !embedReactionsInBubble())
|
||||
? ButtonStyle::Service
|
||||
: outbg
|
||||
? ButtonStyle::Outgoing
|
||||
: ButtonStyle::Incoming;
|
||||
const auto outsideBubble = (!_comments && !embedReactionsInBubble());
|
||||
const auto geometry = countGeometry();
|
||||
result.pointer = position;
|
||||
const auto onTheLeft = (outbg && !delegate()->elementIsChatWide());
|
||||
@@ -1856,23 +1860,31 @@ 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) {
|
||||
const auto top = marginTop();
|
||||
if (!QRect(0, top, width(), height() - top).contains(position)) {
|
||||
return {};
|
||||
}
|
||||
const auto maybeRelativeCenter = outsideBubble
|
||||
? media()->reactionButtonCenterOverride()
|
||||
: std::nullopt;
|
||||
const auto addOnTheRight = [&] {
|
||||
return (maybeRelativeCenter
|
||||
|| !(displayFastShare() || displayGoToOriginal()))
|
||||
? st::reactionCornerCenter.x()
|
||||
: 0;
|
||||
};
|
||||
const auto relativeCenter = QPoint(
|
||||
maybeRelativeCenter.value_or(onTheLeft
|
||||
? -st::reactionCornerCenter.x()
|
||||
: (geometry.width() + addOnTheRight())),
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -2074,14 +2086,11 @@ int Message::viewButtonHeight() const {
|
||||
}
|
||||
|
||||
void Message::updateViewButtonExistence() {
|
||||
const auto has = [&] {
|
||||
const auto item = data();
|
||||
if (item->isSponsored()) {
|
||||
return true;
|
||||
}
|
||||
const auto media = item->media();
|
||||
return media && ViewButton::MediaHasViewButton(media);
|
||||
}();
|
||||
const auto item = data();
|
||||
const auto sponsored = item->Get<HistoryMessageSponsored>();
|
||||
const auto media = sponsored ? nullptr : item->media();
|
||||
const auto has = sponsored
|
||||
|| (media && ViewButton::MediaHasViewButton(media));
|
||||
if (!has) {
|
||||
_viewButton = nullptr;
|
||||
return;
|
||||
@@ -2089,13 +2098,9 @@ void Message::updateViewButtonExistence() {
|
||||
return;
|
||||
}
|
||||
auto callback = [=] { history()->owner().requestViewRepaint(this); };
|
||||
_viewButton = data()->isSponsored()
|
||||
? std::make_unique<ViewButton>(
|
||||
data()->displayFrom(),
|
||||
std::move(callback))
|
||||
: std::make_unique<ViewButton>(
|
||||
data()->media(),
|
||||
std::move(callback));
|
||||
_viewButton = sponsored
|
||||
? std::make_unique<ViewButton>(sponsored, std::move(callback))
|
||||
: std::make_unique<ViewButton>(media, std::move(callback));
|
||||
}
|
||||
|
||||
void Message::initLogEntryOriginal() {
|
||||
@@ -2143,10 +2148,6 @@ bool Message::toggleSelectionByHandlerClick(
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Message::displayPinIcon() const {
|
||||
return data()->isPinned() && !isPinnedContext();
|
||||
}
|
||||
|
||||
bool Message::hasFromName() const {
|
||||
switch (context()) {
|
||||
case Context::AdminLog:
|
||||
@@ -2570,7 +2571,7 @@ void Message::fromNameUpdated(int width) const {
|
||||
const auto nameText = [&]() -> const Ui::Text::String * {
|
||||
if (from) {
|
||||
return &from->nameText();
|
||||
} else if (const auto info = item->hiddenForwardedInfo()) {
|
||||
} else if (const auto info = item->hiddenSenderInfo()) {
|
||||
return &info->nameText;
|
||||
} else {
|
||||
Unexpected("Corrupted forwarded information in message.");
|
||||
@@ -2781,6 +2782,9 @@ int Message::resizeContentGetHeight(int newWidth) {
|
||||
|
||||
if (item->repliesAreComments() || item->externalReply()) {
|
||||
newHeight += st::historyCommentsButtonHeight;
|
||||
} else if (_comments) {
|
||||
_comments = nullptr;
|
||||
checkHeavyPart();
|
||||
}
|
||||
newHeight += viewButtonHeight();
|
||||
} else if (mediaDisplayed) {
|
||||
@@ -2790,10 +2794,13 @@ 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);
|
||||
if (hasOutLayout() && !delegate()->elementIsChatWide()) {
|
||||
_reactions->flipToRight();
|
||||
}
|
||||
}
|
||||
|
||||
if (const auto keyboard = item->inlineReplyKeyboard()) {
|
||||
|
||||
@@ -221,7 +221,6 @@ private:
|
||||
[[nodiscard]] bool displayFastShare() const;
|
||||
[[nodiscard]] bool displayGoToOriginal() const;
|
||||
[[nodiscard]] ClickHandlerPtr fastReplyLink() const;
|
||||
[[nodiscard]] bool displayPinIcon() const;
|
||||
|
||||
void refreshInfoSkipBlock();
|
||||
[[nodiscard]] int plainMaxWidth() const;
|
||||
|
||||
@@ -23,15 +23,14 @@ namespace {
|
||||
constexpr auto kToggleDuration = crl::time(80);
|
||||
constexpr auto kActivateDuration = crl::time(150);
|
||||
constexpr auto kExpandDuration = crl::time(150);
|
||||
constexpr auto kInCacheIndex = 0;
|
||||
constexpr auto kOutCacheIndex = 1;
|
||||
constexpr auto kServiceCacheIndex = 2;
|
||||
constexpr auto kBgCacheIndex = 0;
|
||||
constexpr auto kShadowCacheIndex = 0;
|
||||
constexpr auto kEmojiCacheIndex = 1;
|
||||
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,38 +41,47 @@ 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);
|
||||
}
|
||||
|
||||
Button::~Button() = default;
|
||||
|
||||
ButtonStyle Button::style() const {
|
||||
return _style;
|
||||
}
|
||||
|
||||
bool Button::isHidden() const {
|
||||
return (_state == State::Hidden) && !_scaleAnimation.animating();
|
||||
}
|
||||
@@ -146,11 +154,10 @@ void Button::applyParameters(
|
||||
? State::Active
|
||||
: State::Shown;
|
||||
applyState(state, update);
|
||||
if (_style != parameters.style) {
|
||||
_style = parameters.style;
|
||||
if (update) {
|
||||
update(_geometry);
|
||||
}
|
||||
if (parameters.outside && _state == State::Shown) {
|
||||
_hideTimer.callOnce(kButtonHideDelay);
|
||||
} else {
|
||||
_hideTimer.cancel();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -202,6 +209,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,36 +263,28 @@ 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(
|
||||
_outer.width() * 3 * ratio,
|
||||
_cacheBg = QImage(
|
||||
_outer.width() * ratio,
|
||||
_outer.height() * kFramesCount * ratio,
|
||||
QImage::Format_ARGB32_Premultiplied);
|
||||
_cacheInOutService.setDevicePixelRatio(ratio);
|
||||
_cacheInOutService.fill(Qt::transparent);
|
||||
_cacheBg.setDevicePixelRatio(ratio);
|
||||
_cacheBg.fill(Qt::transparent);
|
||||
_cacheParts = QImage(
|
||||
_outer.width() * kCacheColumsCount * ratio,
|
||||
_outer.height() * kFramesCount * ratio,
|
||||
QImage::Format_ARGB32_Premultiplied);
|
||||
_cacheParts.setDevicePixelRatio(ratio);
|
||||
_cacheParts.fill(Qt::transparent);
|
||||
_cacheForPattern = QImage(
|
||||
QSize(
|
||||
_outer.width(),
|
||||
_outer.height() + st::reactionCornerAddedHeightMax) * ratio,
|
||||
QImage::Format_ARGB32_Premultiplied);
|
||||
_cacheForPattern.setDevicePixelRatio(ratio);
|
||||
_shadowBuffer = QImage(
|
||||
_outer * ratio,
|
||||
QImage::Format_ARGB32_Premultiplied);
|
||||
_shadowBuffer.setDevicePixelRatio(ratio);
|
||||
|
||||
if (wheelEventsTarget) {
|
||||
stealWheelEvents(wheelEventsTarget);
|
||||
@@ -331,6 +331,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);
|
||||
@@ -344,7 +348,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) {
|
||||
@@ -379,13 +386,12 @@ void Manager::applyList(std::vector<Data::Reaction> list) {
|
||||
|
||||
void Manager::setMainReactionImage(QImage image) {
|
||||
_mainReactionImage = std::move(image);
|
||||
ranges::fill(_validIn, false);
|
||||
ranges::fill(_validOut, false);
|
||||
ranges::fill(_validBg, false);
|
||||
ranges::fill(_validEmoji, false);
|
||||
loadOtherReactions();
|
||||
}
|
||||
|
||||
QMarginsF Manager::innerMargins() const {
|
||||
QMargins Manager::innerMargins() const {
|
||||
return {
|
||||
_inner.x(),
|
||||
_inner.y(),
|
||||
@@ -394,12 +400,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() {
|
||||
@@ -412,7 +418,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(
|
||||
@@ -428,7 +434,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;
|
||||
@@ -551,38 +557,17 @@ void Manager::paintButton(
|
||||
const auto geometry = button->geometry();
|
||||
const auto position = geometry.topLeft();
|
||||
const auto size = geometry.size();
|
||||
const auto style = button->style();
|
||||
const auto patterned = (style == ButtonStyle::Outgoing)
|
||||
&& context.bubblesPattern
|
||||
&& !context.viewport.isEmpty()
|
||||
&& !context.bubblesPattern->pixmap.size().isEmpty();
|
||||
const auto shadow = context.st->shadowFg()->c;
|
||||
if (opacity != 1.) {
|
||||
p.setOpacity(opacity);
|
||||
}
|
||||
if (patterned) {
|
||||
const auto source = validateShadow(frameIndex, scale, shadow);
|
||||
paintLongImage(p, geometry, _cacheParts, source);
|
||||
validateCacheForPattern(frameIndex, scale, geometry, context);
|
||||
p.drawImage(
|
||||
geometry,
|
||||
_cacheForPattern,
|
||||
QRect(QPoint(), geometry.size() * style::DevicePixelRatio()));
|
||||
} else {
|
||||
const auto background = (style == ButtonStyle::Service)
|
||||
? context.st->msgServiceFg()->c
|
||||
: context.st->messageStyle(
|
||||
(style == ButtonStyle::Outgoing),
|
||||
false
|
||||
).msgBg->c;
|
||||
const auto source = validateFrame(
|
||||
style,
|
||||
frameIndex,
|
||||
scale,
|
||||
background,
|
||||
shadow);
|
||||
paintLongImage(p, geometry, _cacheInOutService, source);
|
||||
}
|
||||
const auto background = context.st->windowBg()->c;
|
||||
const auto source = validateFrame(
|
||||
frameIndex,
|
||||
scale,
|
||||
background,
|
||||
shadow);
|
||||
paintLongImage(p, geometry, _cacheBg, source);
|
||||
|
||||
const auto mainEmojiPosition = position + (button->expandUp()
|
||||
? QPoint(0, size.height() - _outer.height())
|
||||
@@ -592,10 +577,8 @@ void Manager::paintButton(
|
||||
paintAllEmoji(p, button, scale, mainEmojiPosition);
|
||||
p.restore();
|
||||
} else {
|
||||
p.drawImage(
|
||||
mainEmojiPosition,
|
||||
_cacheParts,
|
||||
validateEmoji(frameIndex, scale));
|
||||
const auto source = validateEmoji(frameIndex, scale);
|
||||
p.drawImage(mainEmojiPosition, _cacheParts, source);
|
||||
}
|
||||
|
||||
if (opacity != 1.) {
|
||||
@@ -648,14 +631,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,
|
||||
@@ -670,32 +653,12 @@ void Manager::paintAllEmoji(
|
||||
}
|
||||
}
|
||||
|
||||
void Manager::validateCacheForPattern(
|
||||
int frameIndex,
|
||||
float64 scale,
|
||||
const QRect &geometry,
|
||||
const PaintContext &context) {
|
||||
auto q = QPainter(&_cacheForPattern);
|
||||
|
||||
q.setCompositionMode(QPainter::CompositionMode_Source);
|
||||
const auto source = validateMask(frameIndex, scale);
|
||||
paintLongImage(q, QRect(QPoint(), geometry.size()), _cacheParts, source);
|
||||
|
||||
q.setCompositionMode(QPainter::CompositionMode_SourceIn);
|
||||
Ui::PaintPatternBubblePart(
|
||||
q,
|
||||
context.viewport.translated(-geometry.topLeft()),
|
||||
context.bubblesPattern->pixmap,
|
||||
QRect(QPoint(), geometry.size()));
|
||||
}
|
||||
|
||||
void Manager::applyPatternedShadow(const QColor &shadow) {
|
||||
if (_shadow == shadow) {
|
||||
return;
|
||||
}
|
||||
_shadow = shadow;
|
||||
ranges::fill(_validIn, false);
|
||||
ranges::fill(_validOut, false);
|
||||
ranges::fill(_validBg, false);
|
||||
ranges::fill(_validShadow, false);
|
||||
}
|
||||
|
||||
@@ -724,7 +687,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);
|
||||
@@ -754,16 +717,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;
|
||||
@@ -771,40 +736,24 @@ QRect Manager::validateEmoji(int frameIndex, float64 scale) {
|
||||
}
|
||||
|
||||
QRect Manager::validateFrame(
|
||||
ButtonStyle style,
|
||||
int frameIndex,
|
||||
float64 scale,
|
||||
const QColor &background,
|
||||
const QColor &shadow) {
|
||||
applyPatternedShadow(shadow);
|
||||
auto &valid = (style == ButtonStyle::Service)
|
||||
? _validService
|
||||
: (style == ButtonStyle::Outgoing)
|
||||
? _validOut
|
||||
: _validIn;
|
||||
auto &color = (style == ButtonStyle::Service)
|
||||
? _backgroundService
|
||||
: (style == ButtonStyle::Outgoing)
|
||||
? _backgroundOut
|
||||
: _backgroundIn;
|
||||
if (color != background) {
|
||||
color = background;
|
||||
ranges::fill(valid, false);
|
||||
if (_background != background) {
|
||||
_background = background;
|
||||
ranges::fill(_validBg, false);
|
||||
}
|
||||
|
||||
const auto columnIndex = (style == ButtonStyle::Service)
|
||||
? kServiceCacheIndex
|
||||
: (style == ButtonStyle::Outgoing)
|
||||
? kOutCacheIndex
|
||||
: kInCacheIndex;
|
||||
const auto result = cacheRect(frameIndex, columnIndex);
|
||||
if (valid[frameIndex]) {
|
||||
const auto result = cacheRect(frameIndex, kBgCacheIndex);
|
||||
if (_validBg[frameIndex]) {
|
||||
return result;
|
||||
}
|
||||
|
||||
const auto shadowSource = validateShadow(frameIndex, scale, shadow);
|
||||
const auto position = result.topLeft() / style::DevicePixelRatio();
|
||||
auto p = QPainter(&_cacheInOutService);
|
||||
auto p = QPainter(&_cacheBg);
|
||||
p.setCompositionMode(QPainter::CompositionMode_Source);
|
||||
p.drawImage(position, _cacheParts, shadowSource);
|
||||
p.setCompositionMode(QPainter::CompositionMode_SourceOver);
|
||||
@@ -822,31 +771,7 @@ QRect Manager::validateFrame(
|
||||
p.drawRoundedRect(inner, radius, radius);
|
||||
p.restore();
|
||||
p.end();
|
||||
valid[frameIndex] = true;
|
||||
return result;
|
||||
}
|
||||
|
||||
QRect Manager::validateMask(int frameIndex, float64 scale) {
|
||||
const auto result = cacheRect(frameIndex, kMaskCacheIndex);
|
||||
if (_validMask[frameIndex]) {
|
||||
return result;
|
||||
}
|
||||
|
||||
auto p = QPainter(&_cacheParts);
|
||||
auto hq = PainterHighQualityEnabler(p);
|
||||
const auto position = result.topLeft() / style::DevicePixelRatio();
|
||||
const auto inner = _inner.translated(position);
|
||||
const auto radius = st::reactionCornerRadius;
|
||||
const auto center = inner.center();
|
||||
p.setPen(Qt::NoPen);
|
||||
p.setBrush(Qt::white);
|
||||
p.save();
|
||||
p.translate(center);
|
||||
p.scale(scale, scale);
|
||||
p.translate(-center);
|
||||
p.drawRoundedRect(inner, radius, radius);
|
||||
|
||||
_validMask[frameIndex] = true;
|
||||
_validBg[frameIndex] = true;
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
@@ -26,12 +26,6 @@ struct TextState;
|
||||
|
||||
namespace HistoryView::Reactions {
|
||||
|
||||
enum class ButtonStyle {
|
||||
Incoming,
|
||||
Outgoing,
|
||||
Service,
|
||||
};
|
||||
|
||||
enum class ExpandDirection {
|
||||
Up,
|
||||
Down,
|
||||
@@ -49,10 +43,10 @@ struct ButtonParameters {
|
||||
QPoint center;
|
||||
QPoint pointer;
|
||||
QPoint globalPointer;
|
||||
ButtonStyle style = ButtonStyle::Incoming;
|
||||
int reactionsCount = 1;
|
||||
int visibleTop = 0;
|
||||
int visibleBottom = 0;
|
||||
bool outside = false;
|
||||
};
|
||||
|
||||
enum class ButtonState {
|
||||
@@ -64,7 +58,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);
|
||||
@@ -72,7 +69,6 @@ public:
|
||||
using State = ButtonState;
|
||||
void applyState(State state);
|
||||
|
||||
[[nodiscard]] ButtonStyle style() const;
|
||||
[[nodiscard]] bool expandUp() const;
|
||||
[[nodiscard]] bool isHidden() const;
|
||||
[[nodiscard]] QRect geometry() const;
|
||||
@@ -103,9 +99,9 @@ private:
|
||||
int _finalHeight = 0;
|
||||
int _scroll = 0;
|
||||
ExpandDirection _expandDirection = ExpandDirection::Up;
|
||||
ButtonStyle _style = ButtonStyle::Incoming;
|
||||
|
||||
base::Timer _expandTimer;
|
||||
base::Timer _hideTimer;
|
||||
std::optional<QPoint> _lastGlobalPosition;
|
||||
|
||||
};
|
||||
@@ -177,21 +173,14 @@ private:
|
||||
const QColor &shadow);
|
||||
QRect validateEmoji(int frameIndex, float64 scale);
|
||||
QRect validateFrame(
|
||||
ButtonStyle style,
|
||||
int frameIndex,
|
||||
float64 scale,
|
||||
const QColor &background,
|
||||
const QColor &shadow);
|
||||
QRect validateMask(int frameIndex, float64 scale);
|
||||
void validateCacheForPattern(
|
||||
int frameIndex,
|
||||
float64 scale,
|
||||
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,21 +191,14 @@ private:
|
||||
std::vector<Data::Reaction> _list;
|
||||
mutable std::vector<ClickHandlerPtr> _links;
|
||||
QSize _outer;
|
||||
QRectF _inner;
|
||||
QRect _innerActive;
|
||||
QImage _cacheInOutService;
|
||||
QRect _inner;
|
||||
QImage _cacheBg;
|
||||
QImage _cacheParts;
|
||||
QImage _cacheForPattern;
|
||||
QImage _shadowBuffer;
|
||||
std::array<bool, kFramesCount> _validIn = { { false } };
|
||||
std::array<bool, kFramesCount> _validOut = { { false } };
|
||||
std::array<bool, kFramesCount> _validService = { { false } };
|
||||
std::array<bool, kFramesCount> _validBg = { { false } };
|
||||
std::array<bool, kFramesCount> _validShadow = { { false } };
|
||||
std::array<bool, kFramesCount> _validEmoji = { { false } };
|
||||
std::array<bool, kFramesCount> _validMask = { { false } };
|
||||
QColor _backgroundIn;
|
||||
QColor _backgroundOut;
|
||||
QColor _backgroundService;
|
||||
QColor _background;
|
||||
QColor _shadow;
|
||||
|
||||
std::shared_ptr<Data::DocumentMedia> _mainReactionMedia;
|
||||
|
||||
@@ -127,7 +127,7 @@ QSize InlineList::countOptimalSize() {
|
||||
const auto height = padding.top() + size + padding.bottom();
|
||||
for (auto &button : _buttons) {
|
||||
const auto width = widthBase + button.countTextWidth;
|
||||
button.geometry = QRect(x, 0, width, height);
|
||||
button.geometry.setSize({ width, height });
|
||||
x += width + between;
|
||||
}
|
||||
return QSize(
|
||||
@@ -136,13 +136,13 @@ QSize InlineList::countOptimalSize() {
|
||||
}
|
||||
|
||||
QSize InlineList::countCurrentSize(int newWidth) {
|
||||
if (newWidth >= maxWidth() || _buttons.empty()) {
|
||||
if (_buttons.empty()) {
|
||||
return optimalSize();
|
||||
}
|
||||
using Flag = InlineListData::Flag;
|
||||
const auto between = st::reactionBottomBetween;
|
||||
const auto left = (_data.flags & InlineListData::Flag::InBubble)
|
||||
? st::reactionBottomInBubbleLeft
|
||||
: 0;
|
||||
const auto inBubble = (_data.flags & Flag::InBubble);
|
||||
const auto left = inBubble ? st::reactionBottomInBubbleLeft : 0;
|
||||
auto x = left;
|
||||
auto y = 0;
|
||||
for (auto &button : _buttons) {
|
||||
@@ -161,6 +161,13 @@ QSize InlineList::countCurrentSize(int newWidth) {
|
||||
return { newWidth, height + add };
|
||||
}
|
||||
|
||||
void InlineList::flipToRight() {
|
||||
for (auto &button : _buttons) {
|
||||
button.geometry.moveLeft(
|
||||
width() - button.geometry.x() - button.geometry.width());
|
||||
}
|
||||
}
|
||||
|
||||
int InlineList::placeAndResizeGetHeight(QRect available) {
|
||||
const auto result = resizeGetHeight(available.width());
|
||||
for (auto &button : _buttons) {
|
||||
|
||||
@@ -49,6 +49,7 @@ public:
|
||||
void update(Data &&data, int availableWidth);
|
||||
QSize countCurrentSize(int newWidth) override;
|
||||
[[nodiscard]] int placeAndResizeGetHeight(QRect available);
|
||||
void flipToRight();
|
||||
|
||||
void updateSkipBlock(int width, int height);
|
||||
void removeSkipBlock();
|
||||
|
||||
@@ -15,6 +15,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "data/data_sponsored_messages.h"
|
||||
#include "data/data_user.h"
|
||||
#include "data/data_web_page.h"
|
||||
#include "history/history_item_components.h"
|
||||
#include "history/view/history_view_cursor_state.h"
|
||||
#include "lang/lang_keys.h"
|
||||
#include "main/main_session.h"
|
||||
@@ -30,20 +31,20 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
namespace HistoryView {
|
||||
namespace {
|
||||
|
||||
inline auto PeerToPhrase(not_null<PeerData*> peer) {
|
||||
using SponsoredType = HistoryMessageSponsored::Type;
|
||||
|
||||
inline auto SponsoredPhrase(SponsoredType type) {
|
||||
const auto phrase = [&] {
|
||||
if (const auto user = peer->asUser()) {
|
||||
return user->isBot()
|
||||
? tr::lng_view_button_bot
|
||||
: tr::lng_view_button_user;
|
||||
} else if (peer->isChat()) {
|
||||
return tr::lng_view_button_group;
|
||||
} else if (peer->isChannel()) {
|
||||
return tr::lng_view_button_channel;
|
||||
switch (type) {
|
||||
case SponsoredType::User: return tr::lng_view_button_user;
|
||||
case SponsoredType::Group: return tr::lng_view_button_group;
|
||||
case SponsoredType::Broadcast: return tr::lng_view_button_channel;
|
||||
case SponsoredType::Post: return tr::lng_view_button_message;
|
||||
case SponsoredType::Bot: return tr::lng_view_button_bot;
|
||||
}
|
||||
Unexpected("Invalid peer in ViewButton.");
|
||||
}()(tr::now);
|
||||
return Ui::Text::Upper(phrase);
|
||||
Unexpected("SponsoredType in SponsoredPhrase.");
|
||||
}();
|
||||
return Ui::Text::Upper(phrase(tr::now));
|
||||
}
|
||||
|
||||
inline auto WebPageToPhrase(not_null<WebPageData*> webpage) {
|
||||
@@ -75,8 +76,11 @@ inline auto WebPageToPhrase(not_null<WebPageData*> webpage) {
|
||||
} // namespace
|
||||
|
||||
struct ViewButton::Inner {
|
||||
Inner(not_null<PeerData*> peer, Fn<void()> updateCallback);
|
||||
Inner(
|
||||
not_null<HistoryMessageSponsored*> sponsored,
|
||||
Fn<void()> updateCallback);
|
||||
Inner(not_null<Data::Media*> media, Fn<void()> updateCallback);
|
||||
|
||||
void updateMask(int height);
|
||||
void toggleRipple(bool pressed);
|
||||
|
||||
@@ -114,22 +118,28 @@ bool ViewButton::MediaHasViewButton(
|
||||
&& webpage->document->isWallPaper());
|
||||
}
|
||||
|
||||
ViewButton::Inner::Inner(not_null<PeerData*> peer, Fn<void()> updateCallback)
|
||||
ViewButton::Inner::Inner(
|
||||
not_null<HistoryMessageSponsored*> sponsored,
|
||||
Fn<void()> updateCallback)
|
||||
: margins(st::historyViewButtonMargins)
|
||||
, link(std::make_shared<LambdaClickHandler>([=](ClickContext context) {
|
||||
const auto my = context.other.value<ClickHandlerContext>();
|
||||
if (const auto controller = my.sessionWindow.get()) {
|
||||
const auto &data = controller->session().data();
|
||||
const auto link = data.sponsoredMessages().channelPost(my.itemId);
|
||||
if (link.hash) {
|
||||
Api::CheckChatInvite(controller, *link.hash);
|
||||
} else {
|
||||
controller->showPeer(peer, link.msgId);
|
||||
const auto itemId = my.itemId;
|
||||
const auto details = data.sponsoredMessages().lookupDetails(itemId);
|
||||
if (details.hash) {
|
||||
Api::CheckChatInvite(controller, *details.hash);
|
||||
} else if (details.peer) {
|
||||
controller->showPeerHistory(
|
||||
details.peer,
|
||||
Window::SectionShow::Way::Forward,
|
||||
details.msgId);
|
||||
}
|
||||
}
|
||||
}))
|
||||
, updateCallback(std::move(updateCallback))
|
||||
, text(st::historyViewButtonTextStyle, PeerToPhrase(peer)) {
|
||||
, text(st::historyViewButtonTextStyle, SponsoredPhrase(sponsored->type)) {
|
||||
}
|
||||
|
||||
ViewButton::Inner::Inner(
|
||||
@@ -170,8 +180,10 @@ void ViewButton::Inner::toggleRipple(bool pressed) {
|
||||
}
|
||||
}
|
||||
|
||||
ViewButton::ViewButton(not_null<PeerData*> peer, Fn<void()> updateCallback)
|
||||
: _inner(std::make_unique<Inner>(peer, std::move(updateCallback))) {
|
||||
ViewButton::ViewButton(
|
||||
not_null<HistoryMessageSponsored*> sponsored,
|
||||
Fn<void()> updateCallback)
|
||||
: _inner(std::make_unique<Inner>(sponsored, std::move(updateCallback))) {
|
||||
}
|
||||
|
||||
ViewButton::ViewButton(
|
||||
|
||||
@@ -9,6 +9,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
|
||||
#include "ui/chat/chat_style.h"
|
||||
|
||||
struct HistoryMessageSponsored;
|
||||
|
||||
namespace Data {
|
||||
class Media;
|
||||
} // namespace Data
|
||||
@@ -21,7 +23,9 @@ struct TextState;
|
||||
|
||||
class ViewButton {
|
||||
public:
|
||||
ViewButton(not_null<PeerData*> peer, Fn<void()> updateCallback);
|
||||
ViewButton(
|
||||
not_null<HistoryMessageSponsored*> sponsored,
|
||||
Fn<void()> updateCallback);
|
||||
ViewButton(not_null<Data::Media*> media, Fn<void()> updateCallback);
|
||||
~ViewButton();
|
||||
|
||||
|
||||
@@ -360,9 +360,12 @@ void Gif::draw(Painter &p, const PaintContext &context) const {
|
||||
auto via = separateRoundVideo ? item->Get<HistoryMessageVia>() : nullptr;
|
||||
auto reply = separateRoundVideo ? _parent->displayedReply() : nullptr;
|
||||
auto forwarded = separateRoundVideo ? item->Get<HistoryMessageForwarded>() : nullptr;
|
||||
const auto rightAligned = separateRoundVideo
|
||||
&& outbg
|
||||
&& !_parent->delegate()->elementIsChatWide();
|
||||
if (via || reply || forwarded) {
|
||||
usew = maxWidth() - additionalWidth(via, reply, forwarded);
|
||||
if (outbg) {
|
||||
if (rightAligned) {
|
||||
usex = width() - usew;
|
||||
}
|
||||
}
|
||||
@@ -588,7 +591,7 @@ void Gif::draw(Painter &p, const PaintContext &context) const {
|
||||
if (reply) {
|
||||
recth += st::msgReplyBarSize.height();
|
||||
}
|
||||
int rectx = outbg ? 0 : (usew + st::msgReplyPadding.left());
|
||||
int rectx = rightAligned ? 0 : (usew + st::msgReplyPadding.left());
|
||||
int recty = painty;
|
||||
if (rtl()) rectx = width() - rectx - rectw;
|
||||
|
||||
@@ -624,7 +627,7 @@ void Gif::draw(Painter &p, const PaintContext &context) const {
|
||||
} else {
|
||||
maxRight -= st::msgMargin.left();
|
||||
}
|
||||
if (isRound && !outbg) {
|
||||
if (isRound && !rightAligned) {
|
||||
auto infoWidth = _parent->infoWidth();
|
||||
|
||||
// This is just some arbitrary point,
|
||||
@@ -766,9 +769,12 @@ TextState Gif::textState(QPoint point, StateRequest request) const {
|
||||
auto via = separateRoundVideo ? item->Get<HistoryMessageVia>() : nullptr;
|
||||
auto reply = separateRoundVideo ? _parent->displayedReply() : nullptr;
|
||||
auto forwarded = separateRoundVideo ? item->Get<HistoryMessageForwarded>() : nullptr;
|
||||
const auto rightAligned = separateRoundVideo
|
||||
&& outbg
|
||||
&& !_parent->delegate()->elementIsChatWide();
|
||||
if (via || reply || forwarded) {
|
||||
usew = maxWidth() - additionalWidth(via, reply, forwarded);
|
||||
if (outbg) {
|
||||
if (rightAligned) {
|
||||
usex = width() - usew;
|
||||
}
|
||||
}
|
||||
@@ -788,7 +794,7 @@ TextState Gif::textState(QPoint point, StateRequest request) const {
|
||||
if (reply) {
|
||||
recth += st::msgReplyBarSize.height();
|
||||
}
|
||||
auto rectx = outbg ? 0 : (usew + st::msgReplyPadding.left());
|
||||
auto rectx = rightAligned ? 0 : (usew + st::msgReplyPadding.left());
|
||||
auto recty = painty;
|
||||
if (rtl()) rectx = width() - rectx - rectw;
|
||||
|
||||
@@ -857,7 +863,7 @@ TextState Gif::textState(QPoint point, StateRequest request) const {
|
||||
} else {
|
||||
maxRight -= st::msgMargin.left();
|
||||
}
|
||||
if (isRound && !outbg) {
|
||||
if (isRound && !rightAligned) {
|
||||
auto infoWidth = _parent->infoWidth();
|
||||
|
||||
// This is just some arbitrary point,
|
||||
@@ -1170,20 +1176,22 @@ bool Gif::needsBubble() const {
|
||||
return false;
|
||||
}
|
||||
|
||||
QRect Gif::contentRectForReactionButton() const {
|
||||
QRect Gif::contentRectForReactions() const {
|
||||
if (!isSeparateRoundVideo()) {
|
||||
return QRect(0, 0, width(), height());
|
||||
}
|
||||
auto paintx = 0, painty = 0, paintw = width(), painth = height();
|
||||
auto usex = 0, usew = paintw;
|
||||
const auto outbg = _parent->hasOutLayout();
|
||||
const auto rightAligned = outbg
|
||||
&& !_parent->delegate()->elementIsChatWide();
|
||||
const auto item = _parent->data();
|
||||
const auto via = item->Get<HistoryMessageVia>();
|
||||
const auto reply = _parent->displayedReply();
|
||||
const auto forwarded = item->Get<HistoryMessageForwarded>();
|
||||
if (via || reply || forwarded) {
|
||||
usew = maxWidth() - additionalWidth(via, reply, forwarded);
|
||||
if (outbg) {
|
||||
if (rightAligned) {
|
||||
usex = width() - usew;
|
||||
}
|
||||
}
|
||||
@@ -1191,6 +1199,34 @@ 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();
|
||||
const auto outbg = _parent->hasOutLayout();
|
||||
const auto rightAligned = outbg
|
||||
&& !_parent->delegate()->elementIsChatWide();
|
||||
if (!rightAligned) {
|
||||
// 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;
|
||||
}
|
||||
|
||||
@@ -112,7 +112,7 @@ void UnwrappedMedia::draw(Painter &p, const PaintContext &context) const {
|
||||
if (width() < st::msgPadding.left() + st::msgPadding.right() + 1) {
|
||||
return;
|
||||
}
|
||||
const auto rightAligned = _parent->hasOutLayout()
|
||||
const auto rightAligned = context.outbg
|
||||
&& !_parent->delegate()->elementIsChatWide();
|
||||
const auto inWebPage = (_parent->media() != this);
|
||||
const auto item = _parent->data();
|
||||
@@ -183,7 +183,7 @@ void UnwrappedMedia::drawSurrounding(
|
||||
const HistoryMessageForwarded *forwarded) const {
|
||||
const auto st = context.st;
|
||||
const auto sti = context.imageStyle();
|
||||
const auto rightAligned = _parent->hasOutLayout()
|
||||
const auto rightAligned = context.outbg
|
||||
&& !_parent->delegate()->elementIsChatWide();
|
||||
const auto rightActionSize = _parent->rightActionSize();
|
||||
const auto fullRight = calculateFullRight(inner);
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
|
||||
#include "history/view/reactions/message_reactions_selector.h"
|
||||
#include "boxes/peer_list_box.h"
|
||||
#include "boxes/peers/prepare_short_info_box.h"
|
||||
#include "window/window_session_controller.h"
|
||||
#include "history/history_item.h"
|
||||
#include "history/history.h"
|
||||
@@ -23,6 +24,26 @@ namespace {
|
||||
constexpr auto kPerPageFirst = 20;
|
||||
constexpr auto kPerPage = 200;
|
||||
|
||||
class Row final : public PeerListRow {
|
||||
public:
|
||||
Row(not_null<PeerData*> peer, const QString &reaction);
|
||||
|
||||
QSize rightActionSize() const override;
|
||||
QMargins rightActionMargins() const override;
|
||||
bool rightActionDisabled() const override;
|
||||
void rightActionPaint(
|
||||
Painter &p,
|
||||
int x,
|
||||
int y,
|
||||
int outerWidth,
|
||||
bool selected,
|
||||
bool actionSelected) override;
|
||||
|
||||
private:
|
||||
EmojiPtr _emoji = nullptr;
|
||||
|
||||
};
|
||||
|
||||
class Controller final : public PeerListController {
|
||||
public:
|
||||
Controller(
|
||||
@@ -40,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;
|
||||
@@ -62,6 +83,46 @@ private:
|
||||
|
||||
};
|
||||
|
||||
Row::Row(not_null<PeerData*> peer, const QString &reaction)
|
||||
: PeerListRow(peer)
|
||||
, _emoji(Ui::Emoji::Find(reaction)) {
|
||||
}
|
||||
|
||||
QSize Row::rightActionSize() const {
|
||||
const auto size = Ui::Emoji::GetSizeNormal() / style::DevicePixelRatio();
|
||||
return _emoji ? QSize(size, size) : QSize();
|
||||
}
|
||||
|
||||
QMargins Row::rightActionMargins() const {
|
||||
if (!_emoji) {
|
||||
return QMargins();
|
||||
}
|
||||
const auto size = Ui::Emoji::GetSizeNormal() / style::DevicePixelRatio();
|
||||
return QMargins(
|
||||
size / 2,
|
||||
(st::defaultPeerList.item.height - size) / 2,
|
||||
(size * 3) / 2,
|
||||
0);
|
||||
}
|
||||
|
||||
bool Row::rightActionDisabled() const {
|
||||
return true;
|
||||
}
|
||||
|
||||
void Row::rightActionPaint(
|
||||
Painter &p,
|
||||
int x,
|
||||
int y,
|
||||
int outerWidth,
|
||||
bool selected,
|
||||
bool actionSelected) {
|
||||
if (!_emoji) {
|
||||
return;
|
||||
}
|
||||
// #TODO reactions
|
||||
Ui::Emoji::Draw(p, _emoji, Ui::Emoji::GetSizeNormal(), x, y);
|
||||
}
|
||||
|
||||
Controller::Controller(
|
||||
not_null<Window::SessionController*> window,
|
||||
not_null<HistoryItem*> item,
|
||||
@@ -114,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());
|
||||
}
|
||||
@@ -160,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);
|
||||
@@ -177,9 +238,10 @@ void Controller::loadMore(const QString &offset) {
|
||||
}
|
||||
|
||||
void Controller::rowClicked(not_null<PeerListRow*> row) {
|
||||
const auto peerId = row->peer()->id;
|
||||
crl::on_main(&session(), [=] {
|
||||
_window->showPeerHistory(peerId);
|
||||
const auto window = _window;
|
||||
const auto peer = row->peer();
|
||||
crl::on_main(window, [=] {
|
||||
window->show(PrepareShortInfoBox(peer, window));
|
||||
});
|
||||
}
|
||||
|
||||
@@ -191,66 +253,6 @@ bool Controller::appendRow(not_null<UserData*> user, QString reaction) {
|
||||
return true;
|
||||
}
|
||||
|
||||
class Row final : public PeerListRow {
|
||||
public:
|
||||
Row(not_null<PeerData*> peer, const QString &reaction);
|
||||
|
||||
QSize rightActionSize() const override;
|
||||
QMargins rightActionMargins() const override;
|
||||
bool rightActionDisabled() const override;
|
||||
void rightActionPaint(
|
||||
Painter &p,
|
||||
int x,
|
||||
int y,
|
||||
int outerWidth,
|
||||
bool selected,
|
||||
bool actionSelected) override;
|
||||
|
||||
private:
|
||||
EmojiPtr _emoji = nullptr;
|
||||
|
||||
};
|
||||
|
||||
Row::Row(not_null<PeerData*> peer, const QString &reaction)
|
||||
: PeerListRow(peer)
|
||||
, _emoji(Ui::Emoji::Find(reaction)) {
|
||||
}
|
||||
|
||||
QSize Row::rightActionSize() const {
|
||||
const auto size = Ui::Emoji::GetSizeNormal() / style::DevicePixelRatio();
|
||||
return _emoji ? QSize(size, size) : QSize();
|
||||
}
|
||||
|
||||
QMargins Row::rightActionMargins() const {
|
||||
if (!_emoji) {
|
||||
return QMargins();
|
||||
}
|
||||
const auto size = Ui::Emoji::GetSizeNormal() / style::DevicePixelRatio();
|
||||
return QMargins(
|
||||
size / 2,
|
||||
(st::defaultPeerList.item.height - size) / 2,
|
||||
(size * 3) / 2,
|
||||
0);
|
||||
}
|
||||
|
||||
bool Row::rightActionDisabled() const {
|
||||
return true;
|
||||
}
|
||||
|
||||
void Row::rightActionPaint(
|
||||
Painter &p,
|
||||
int x,
|
||||
int y,
|
||||
int outerWidth,
|
||||
bool selected,
|
||||
bool actionSelected) {
|
||||
if (!_emoji) {
|
||||
return;
|
||||
}
|
||||
// #TODO reactions
|
||||
Ui::Emoji::Draw(p, _emoji, Ui::Emoji::GetSizeNormal(), x, y);
|
||||
}
|
||||
|
||||
std::unique_ptr<PeerListRow> Controller::createRow(
|
||||
not_null<UserData*> user,
|
||||
QString reaction) const {
|
||||
|
||||
@@ -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 {{ "menu/read_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);
|
||||
|
||||
@@ -1643,7 +1643,7 @@ void ListWidget::showContextMenu(
|
||||
document,
|
||||
DocumentSaveClickHandler::Mode::ToNewFile);
|
||||
});
|
||||
if (_peer->allowsForwarding()) {
|
||||
if (_peer->allowsForwarding() && !item->forbidsForward()) {
|
||||
_contextMenu->addAction(
|
||||
(isVideo
|
||||
? tr::lng_context_save_video(tr::now)
|
||||
|
||||
@@ -1515,6 +1515,9 @@ void OverlayWidget::notifyFileDialogShown(bool shown) {
|
||||
}
|
||||
|
||||
void OverlayWidget::saveAs() {
|
||||
if (showCopyRestriction()) {
|
||||
return;
|
||||
}
|
||||
QString file;
|
||||
if (_document) {
|
||||
const auto &location = _document->location(true);
|
||||
@@ -2127,7 +2130,7 @@ void OverlayWidget::refreshMediaViewer() {
|
||||
void OverlayWidget::refreshFromLabel() {
|
||||
if (_message) {
|
||||
_from = _message->senderOriginal();
|
||||
if (const auto info = _message->hiddenForwardedInfo()) {
|
||||
if (const auto info = _message->hiddenSenderInfo()) {
|
||||
_fromName = info->name;
|
||||
} else {
|
||||
Assert(_from != nullptr);
|
||||
@@ -4451,12 +4454,16 @@ bool OverlayWidget::handleContextMenu(std::optional<QPoint> position) {
|
||||
const style::icon *icon) {
|
||||
_menu->addAction(text, std::move(handler), icon);
|
||||
});
|
||||
_menu->setDestroyedCallback(crl::guard(_widget, [=] {
|
||||
activateControls();
|
||||
_receiveMouse = false;
|
||||
InvokeQueued(_widget, [=] { receiveMouse(); });
|
||||
}));
|
||||
_menu->popup(QCursor::pos());
|
||||
if (_menu->empty()) {
|
||||
_menu = nullptr;
|
||||
} else {
|
||||
_menu->setDestroyedCallback(crl::guard(_widget, [=] {
|
||||
activateControls();
|
||||
_receiveMouse = false;
|
||||
InvokeQueued(_widget, [=] { receiveMouse(); });
|
||||
}));
|
||||
_menu->popup(QCursor::pos());
|
||||
}
|
||||
activateControls();
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -10,6 +10,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "base/platform/mac/base_utilities_mac.h"
|
||||
#include "logs.h"
|
||||
|
||||
#include <QtCore/QMutex>
|
||||
|
||||
#include <Cocoa/Cocoa.h>
|
||||
#include <CoreFoundation/CFURL.h>
|
||||
|
||||
|
||||
@@ -920,6 +920,9 @@ whoReadChecksDisabled: icon{{ "menu/read_ticks", menuFgDisabled }};
|
||||
whoReadPlayed: icon{{ "menu/read_audio", menuSubmenuArrowFg }};
|
||||
whoReadPlayedOver: icon{{ "menu/read_audio", menuSubmenuArrowFg }};
|
||||
whoReadPlayedDisabled: icon {{ "menu/read_audio", menuFgDisabled }};
|
||||
whoReadReactions: icon{{ "menu/read_reactions", menuSubmenuArrowFg }};
|
||||
whoReadReactionsOver: icon{{ "menu/read_reactions", menuSubmenuArrowFg }};
|
||||
whoReadReactionsDisabled: icon{{ "menu/read_reactions", menuFgDisabled }};
|
||||
|
||||
reactionsTabAll: icon {{ "menu/read_reactions", windowFg }};
|
||||
reactionsTabAllSelected: icon {{ "menu/read_reactions", activeButtonFg }};
|
||||
@@ -973,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;
|
||||
|
||||
|
||||
@@ -400,7 +400,7 @@ void Action::paint(Painter &p) {
|
||||
if (enabled) {
|
||||
paintRipple(p, 0, 0);
|
||||
}
|
||||
if (const auto emoji = Emoji::Find(_content.mostPopularReaction)) {
|
||||
if (const auto emoji = Emoji::Find(_content.singleReaction)) {
|
||||
// #TODO reactions
|
||||
const auto ratio = style::DevicePixelRatio();
|
||||
const auto size = Emoji::GetSizeNormal();
|
||||
@@ -409,7 +409,13 @@ void Action::paint(Painter &p) {
|
||||
const auto y = (_height - (size / ratio)) / 2;
|
||||
Emoji::Draw(p, emoji, size, x, y);
|
||||
} else {
|
||||
const auto &icon = (_content.type == WhoReadType::Seen)
|
||||
const auto &icon = (_content.fullReactionsCount)
|
||||
? (!enabled
|
||||
? st::whoReadReactionsDisabled
|
||||
: selected
|
||||
? st::whoReadReactionsOver
|
||||
: st::whoReadReactions)
|
||||
: (_content.type == WhoReadType::Seen)
|
||||
? (!enabled
|
||||
? st::whoReadChecksDisabled
|
||||
: selected
|
||||
@@ -449,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))
|
||||
|
||||
@@ -40,7 +40,7 @@ enum class WhoReadType {
|
||||
struct WhoReadContent {
|
||||
std::vector<WhoReadParticipant> participants;
|
||||
WhoReadType type = WhoReadType::Seen;
|
||||
QString mostPopularReaction;
|
||||
QString singleReaction;
|
||||
int fullReactionsCount = 0;
|
||||
bool unknown = false;
|
||||
};
|
||||
|
||||
@@ -121,23 +121,40 @@ public:
|
||||
private:
|
||||
using Section = Dialogs::EntryState::Section;
|
||||
|
||||
[[nodiscard]] bool showInfo();
|
||||
[[nodiscard]] bool showHidePromotion();
|
||||
[[nodiscard]] bool showToggleArchived();
|
||||
[[nodiscard]] bool showTogglePin();
|
||||
void fillChatsListActions();
|
||||
void fillHistoryActions();
|
||||
void fillProfileActions();
|
||||
void fillRepliesActions();
|
||||
void fillScheduledActions();
|
||||
void fillArchiveActions();
|
||||
|
||||
void addHidePromotion();
|
||||
void addTogglePin();
|
||||
void addToggleMute();
|
||||
void addSupportInfo();
|
||||
void addInfo();
|
||||
//void addSearch();
|
||||
//void addToFolder();
|
||||
void addToggleUnreadMark();
|
||||
void addToggleArchive();
|
||||
void addUserActions(not_null<UserData*> user);
|
||||
void addBlockUser(not_null<UserData*> user);
|
||||
void addClearHistory();
|
||||
void addDeleteChat();
|
||||
void addLeaveChat();
|
||||
void addManageChat();
|
||||
void addCreatePoll();
|
||||
void addThemeEdit();
|
||||
void addBlockUser();
|
||||
void addViewDiscussion();
|
||||
void addExportChat();
|
||||
void addReport();
|
||||
void addNewContact();
|
||||
void addShareContact();
|
||||
void addEditContact();
|
||||
void addBotToGroup();
|
||||
void addNewMembers();
|
||||
void addDeleteContact();
|
||||
|
||||
void addChatActions(not_null<ChatData*> chat);
|
||||
void addChannelActions(not_null<ChannelData*> channel);
|
||||
void addTogglesForArchive();
|
||||
|
||||
void addPollAction(not_null<PeerData*> peer);
|
||||
|
||||
not_null<SessionController*> _controller;
|
||||
Dialogs::EntryState _request;
|
||||
@@ -271,55 +288,13 @@ Filler::Filler(
|
||||
, _addAction(addAction) {
|
||||
}
|
||||
|
||||
bool Filler::showInfo() {
|
||||
if (_request.section == Section::Profile
|
||||
|| _peer->isSelf()
|
||||
|| _peer->isRepliesChat()) {
|
||||
return false;
|
||||
} else if (_controller->activeChatCurrent().peer() != _peer) {
|
||||
return true;
|
||||
} else if (!_controller->adaptive().isThreeColumn()) {
|
||||
return true;
|
||||
} else if (!Core::App().settings().thirdSectionInfoEnabled()
|
||||
&& !Core::App().settings().tabbedReplacedWithInfo()) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Filler::showHidePromotion() {
|
||||
if (_request.section != Section::ChatsList) {
|
||||
return false;
|
||||
}
|
||||
const auto history = _peer->owner().historyLoaded(_peer);
|
||||
return history
|
||||
&& history->useTopPromotion()
|
||||
&& !history->topPromotionType().isEmpty();
|
||||
}
|
||||
|
||||
bool Filler::showToggleArchived() {
|
||||
if (_request.section != Section::ChatsList) {
|
||||
return false;
|
||||
}
|
||||
const auto history = _peer->owner().historyLoaded(_peer);
|
||||
if (history && history->useTopPromotion()) {
|
||||
return false;
|
||||
} else if (!_peer->isNotificationsUser() && !_peer->isSelf()) {
|
||||
return true;
|
||||
}
|
||||
return history && (history->folder() != nullptr);
|
||||
}
|
||||
|
||||
bool Filler::showTogglePin() {
|
||||
if (_request.section != Section::ChatsList) {
|
||||
return false;
|
||||
}
|
||||
const auto history = _peer->owner().historyLoaded(_peer);
|
||||
return history && !history->fixedOnTopIndex();
|
||||
}
|
||||
|
||||
void Filler::addHidePromotion() {
|
||||
const auto history = _peer->owner().history(_peer);
|
||||
const auto history = _peer->owner().historyLoaded(_peer);
|
||||
if (!history
|
||||
|| !history->useTopPromotion()
|
||||
|| history->topPromotionType().isEmpty()) {
|
||||
return;
|
||||
}
|
||||
_addAction(tr::lng_context_hide_psa(tr::now), [=] {
|
||||
history->cacheTopPromotion(false, QString(), QString());
|
||||
history->session().api().request(MTPhelp_HidePromoData(
|
||||
@@ -332,7 +307,10 @@ void Filler::addTogglePin() {
|
||||
const auto controller = _controller;
|
||||
const auto filterId = _request.filterId;
|
||||
const auto peer = _peer;
|
||||
const auto history = peer->owner().history(peer);
|
||||
const auto history = peer->owner().historyLoaded(peer);
|
||||
if (!history || history->fixedOnTopIndex()) {
|
||||
return;
|
||||
}
|
||||
const auto pinText = [=] {
|
||||
return history->isPinnedDialog(filterId)
|
||||
? tr::lng_context_unpin_from_top(tr::now)
|
||||
@@ -355,7 +333,36 @@ void Filler::addTogglePin() {
|
||||
SetActionText(pinAction, std::move(actionText));
|
||||
}
|
||||
|
||||
void Filler::addToggleMute() {
|
||||
if (_peer->isSelf()) {
|
||||
return;
|
||||
}
|
||||
PeerMenuAddMuteAction(_peer, _addAction);
|
||||
}
|
||||
|
||||
void Filler::addSupportInfo() {
|
||||
if (!_peer->session().supportMode()) {
|
||||
return;
|
||||
}
|
||||
const auto user = _peer->asUser();
|
||||
if (!user) {
|
||||
return;
|
||||
}
|
||||
const auto controller = _controller;
|
||||
_addAction("Edit support info", [=] {
|
||||
user->session().supportHelper().editInfo(controller, user);
|
||||
}, &st::menuIconEdit);
|
||||
}
|
||||
|
||||
void Filler::addInfo() {
|
||||
if (_peer->isSelf() || _peer->isRepliesChat()) {
|
||||
return;
|
||||
} else if (_controller->adaptive().isThreeColumn()) {
|
||||
if (Core::App().settings().thirdSectionInfoEnabled()
|
||||
|| Core::App().settings().tabbedReplacedWithInfo()) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
const auto controller = _controller;
|
||||
const auto peer = _peer;
|
||||
const auto text = (peer->isChat() || peer->isMegagroup())
|
||||
@@ -368,12 +375,7 @@ void Filler::addInfo() {
|
||||
}, peer->isUser() ? &st::menuIconProfile : &st::menuIconInfo);
|
||||
}
|
||||
|
||||
//void Filler::addSearch() {
|
||||
// const auto controller = _controller;
|
||||
// const auto peer = _peer;
|
||||
// _addAction(tr::lng_profile_search_messages(tr::now), [=] {
|
||||
// controller->content()->searchInChat(peer->owner().history(peer));
|
||||
// }, &st::menuIconSearch);
|
||||
//void Filler::addToFolder() {
|
||||
//}
|
||||
|
||||
void Filler::addToggleUnreadMark() {
|
||||
@@ -406,7 +408,14 @@ void Filler::addToggleUnreadMark() {
|
||||
|
||||
void Filler::addToggleArchive() {
|
||||
const auto peer = _peer;
|
||||
const auto history = peer->owner().history(peer);
|
||||
const auto history = peer->owner().historyLoaded(peer);
|
||||
if (history && history->useTopPromotion()) {
|
||||
return;
|
||||
} else if (peer->isNotificationsUser() || peer->isSelf()) {
|
||||
if (!history || !history->folder()) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
const auto isArchived = [=] {
|
||||
return (history->folder() != nullptr);
|
||||
};
|
||||
@@ -430,7 +439,56 @@ void Filler::addToggleArchive() {
|
||||
SetActionText(archiveAction, std::move(actionText));
|
||||
}
|
||||
|
||||
void Filler::addBlockUser(not_null<UserData*> user) {
|
||||
void Filler::addClearHistory() {
|
||||
const auto channel = _peer->asChannel();
|
||||
const auto isGroup = _peer->isChat() || _peer->isMegagroup();
|
||||
if (channel) {
|
||||
if (!channel->amIn()) {
|
||||
return;
|
||||
} else if (!channel->canDeleteMessages()
|
||||
&& (!isGroup || channel->isPublic())) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
_addAction(
|
||||
tr::lng_profile_clear_history(tr::now),
|
||||
ClearHistoryHandler(_peer),
|
||||
&st::menuIconClear);
|
||||
}
|
||||
|
||||
void Filler::addDeleteChat() {
|
||||
if (_peer->isChannel()) {
|
||||
return;
|
||||
}
|
||||
_addAction(
|
||||
(_peer->isUser()
|
||||
? tr::lng_profile_delete_conversation(tr::now)
|
||||
: tr::lng_profile_clear_and_exit(tr::now)),
|
||||
DeleteAndLeaveHandler(_peer),
|
||||
&st::menuIconDelete);
|
||||
}
|
||||
|
||||
void Filler::addLeaveChat() {
|
||||
const auto channel = _peer->asChannel();
|
||||
if (!channel || !channel->amIn()) {
|
||||
return;
|
||||
}
|
||||
_addAction(
|
||||
(_peer->isMegagroup()
|
||||
? tr::lng_profile_leave_group(tr::now)
|
||||
: tr::lng_profile_leave_channel(tr::now)),
|
||||
DeleteAndLeaveHandler(_peer),
|
||||
&st::menuIconLeave);
|
||||
}
|
||||
|
||||
void Filler::addBlockUser() {
|
||||
const auto user = _peer->asUser();
|
||||
if (!user
|
||||
|| user->isInaccessible()
|
||||
|| user->isSelf()
|
||||
|| user->isRepliesChat()) {
|
||||
return;
|
||||
}
|
||||
const auto window = &_controller->window();
|
||||
const auto blockText = [](not_null<UserData*> user) {
|
||||
return user->isBlocked()
|
||||
@@ -471,197 +529,153 @@ void Filler::addBlockUser(not_null<UserData*> user) {
|
||||
}
|
||||
}
|
||||
|
||||
void Filler::addUserActions(not_null<UserData*> user) {
|
||||
const auto controller = _controller;
|
||||
const auto window = &_controller->window();
|
||||
if (_request.section != Section::ChatsList) {
|
||||
if (user->session().supportMode()) {
|
||||
_addAction("Edit support info", [=] {
|
||||
user->session().supportHelper().editInfo(controller, user);
|
||||
}, &st::menuIconEdit);
|
||||
}
|
||||
if (!user->isContact() && !user->isSelf() && !user->isBot()) {
|
||||
_addAction(
|
||||
tr::lng_info_add_as_contact(tr::now),
|
||||
[=] { window->show(Box(EditContactBox, controller, user)); },
|
||||
&st::menuIconInvite);
|
||||
}
|
||||
if (user->canShareThisContact()) {
|
||||
_addAction(
|
||||
tr::lng_info_share_contact(tr::now),
|
||||
[=] { PeerMenuShareContactBox(controller, user); },
|
||||
&st::menuIconShare);
|
||||
}
|
||||
if (user->isContact() && !user->isSelf()) {
|
||||
_addAction(
|
||||
tr::lng_info_edit_contact(tr::now),
|
||||
[=] { window->show(Box(EditContactBox, controller, user)); },
|
||||
&st::menuIconEdit);
|
||||
_addAction(
|
||||
tr::lng_info_delete_contact(tr::now),
|
||||
[=] { PeerMenuDeleteContact(user); },
|
||||
&st::menuIconRemove);
|
||||
}
|
||||
if (user->isBot()
|
||||
&& !user->isRepliesChat()
|
||||
&& !user->botInfo->cantJoinGroups) {
|
||||
using AddBotToGroup = AddBotToGroupBoxController;
|
||||
_addAction(
|
||||
tr::lng_profile_invite_to_group(tr::now),
|
||||
[=] { AddBotToGroup::Start(user); },
|
||||
&st::menuIconInvite);
|
||||
}
|
||||
addPollAction(user);
|
||||
if (!user->isBot() && _request.section == Section::History) {
|
||||
_addAction(
|
||||
tr::lng_chat_theme_change(tr::now),
|
||||
[=] { controller->toggleChooseChatTheme(user); },
|
||||
&st::menuIconChangeColors);
|
||||
}
|
||||
if (user->canExportChatHistory()) {
|
||||
_addAction(
|
||||
tr::lng_profile_export_chat(tr::now),
|
||||
[=] { PeerMenuExportChat(user); },
|
||||
&st::menuIconExport);
|
||||
}
|
||||
}
|
||||
_addAction(
|
||||
tr::lng_profile_delete_conversation(tr::now),
|
||||
DeleteAndLeaveHandler(user),
|
||||
&st::menuIconDelete);
|
||||
_addAction(
|
||||
tr::lng_profile_clear_history(tr::now),
|
||||
ClearHistoryHandler(user),
|
||||
&st::menuIconClear);
|
||||
if (!user->isInaccessible()
|
||||
&& user != user->session().user()
|
||||
&& !user->isRepliesChat()
|
||||
&& _request.section != Section::ChatsList) {
|
||||
addBlockUser(user);
|
||||
}
|
||||
}
|
||||
|
||||
void Filler::addChatActions(not_null<ChatData*> chat) {
|
||||
const auto navigation = _controller;
|
||||
if (_request.section != Section::ChatsList) {
|
||||
if (EditPeerInfoBox::Available(chat)) {
|
||||
const auto text = tr::lng_manage_group_title(tr::now);
|
||||
_addAction(text, [=] {
|
||||
navigation->showEditPeerBox(chat);
|
||||
}, &st::menuIconManage);
|
||||
}
|
||||
if (chat->canAddMembers()) {
|
||||
_addAction(
|
||||
tr::lng_channel_add_members(tr::now),
|
||||
[=] { AddChatMembers(navigation, chat); },
|
||||
&st::menuIconInvite);
|
||||
}
|
||||
addPollAction(chat);
|
||||
if (chat->canExportChatHistory()) {
|
||||
_addAction(
|
||||
tr::lng_profile_export_chat(tr::now),
|
||||
[=] { PeerMenuExportChat(chat); },
|
||||
&st::menuIconExport);
|
||||
}
|
||||
}
|
||||
_addAction(
|
||||
tr::lng_profile_clear_and_exit(tr::now),
|
||||
DeleteAndLeaveHandler(_peer),
|
||||
&st::menuIconDelete);
|
||||
_addAction(
|
||||
tr::lng_profile_clear_history(tr::now),
|
||||
ClearHistoryHandler(_peer),
|
||||
&st::menuIconClear);
|
||||
if (_request.section != Section::ChatsList) {
|
||||
if (!chat->amCreator()) {
|
||||
_addAction(tr::lng_profile_report(tr::now), [=] {
|
||||
HistoryView::ShowReportPeerBox(navigation, chat);
|
||||
}, &st::menuIconReport);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Filler::addChannelActions(not_null<ChannelData*> channel) {
|
||||
const auto isGroup = channel->isMegagroup();
|
||||
const auto navigation = _controller;
|
||||
if (_request.section != Section::ChatsList) {
|
||||
if (channel->isBroadcast()) {
|
||||
if (const auto chat = channel->linkedChat()) {
|
||||
_addAction(tr::lng_profile_view_discussion(tr::now), [=] {
|
||||
if (channel->invitePeekExpires()) {
|
||||
Ui::Toast::Show(
|
||||
tr::lng_channel_invite_private(tr::now));
|
||||
return;
|
||||
}
|
||||
navigation->showPeerHistory(
|
||||
chat,
|
||||
Window::SectionShow::Way::Forward);
|
||||
}, &st::menuIconDiscussion);
|
||||
}
|
||||
}
|
||||
if (EditPeerInfoBox::Available(channel)) {
|
||||
const auto text = isGroup
|
||||
? tr::lng_manage_group_title(tr::now)
|
||||
: tr::lng_manage_channel_title(tr::now);
|
||||
_addAction(text, [=] {
|
||||
navigation->showEditPeerBox(channel);
|
||||
}, &st::menuIconManage);
|
||||
}
|
||||
if (channel->canAddMembers()) {
|
||||
_addAction(
|
||||
(channel->isMegagroup()
|
||||
? tr::lng_channel_add_members(tr::now)
|
||||
: tr::lng_channel_add_users(tr::now)),
|
||||
[=] { PeerMenuAddChannelMembers(navigation, channel); },
|
||||
&st::menuIconInvite);
|
||||
}
|
||||
addPollAction(channel);
|
||||
if (channel->canExportChatHistory()) {
|
||||
_addAction(
|
||||
(isGroup
|
||||
? tr::lng_profile_export_chat(tr::now)
|
||||
: tr::lng_profile_export_channel(tr::now)),
|
||||
[=] { PeerMenuExportChat(channel); },
|
||||
&st::menuIconExport);
|
||||
}
|
||||
}
|
||||
if (channel->amIn()) {
|
||||
auto text = isGroup
|
||||
? tr::lng_profile_leave_group(tr::now)
|
||||
: tr::lng_profile_leave_channel(tr::now);
|
||||
_addAction(
|
||||
text,
|
||||
DeleteAndLeaveHandler(channel),
|
||||
&st::menuIconLeave);
|
||||
if ((isGroup && !channel->isPublic())
|
||||
|| channel->canDeleteMessages()) {
|
||||
_addAction(
|
||||
tr::lng_profile_clear_history(tr::now),
|
||||
ClearHistoryHandler(channel),
|
||||
&st::menuIconClear);
|
||||
}
|
||||
} else {
|
||||
auto text = isGroup
|
||||
? tr::lng_profile_join_group(tr::now)
|
||||
: tr::lng_profile_join_channel(tr::now);
|
||||
_addAction(
|
||||
text,
|
||||
[=] { channel->session().api().joinChannel(channel); },
|
||||
&st::menuIconInvite); // #TODO icons
|
||||
}
|
||||
if (_request.section != Section::ChatsList) {
|
||||
if (!channel->amCreator()) {
|
||||
_addAction(tr::lng_profile_report(tr::now), [=] {
|
||||
HistoryView::ShowReportPeerBox(navigation, channel);
|
||||
}, &st::menuIconReport);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Filler::addPollAction(not_null<PeerData*> peer) {
|
||||
if (!peer->canSendPolls()) {
|
||||
void Filler::addViewDiscussion() {
|
||||
const auto channel = _peer->asBroadcast();
|
||||
if (!channel) {
|
||||
return;
|
||||
}
|
||||
const auto chat = channel->linkedChat();
|
||||
if (!chat) {
|
||||
return;
|
||||
}
|
||||
const auto navigation = _controller;
|
||||
_addAction(tr::lng_profile_view_discussion(tr::now), [=] {
|
||||
if (channel->invitePeekExpires()) {
|
||||
Ui::Toast::Show(
|
||||
tr::lng_channel_invite_private(tr::now));
|
||||
return;
|
||||
}
|
||||
navigation->showPeerHistory(
|
||||
chat,
|
||||
Window::SectionShow::Way::Forward);
|
||||
}, &st::menuIconDiscussion);
|
||||
}
|
||||
|
||||
void Filler::addExportChat() {
|
||||
if (!_peer->canExportChatHistory()) {
|
||||
return;
|
||||
}
|
||||
const auto peer = _peer;
|
||||
_addAction(
|
||||
tr::lng_profile_export_chat(tr::now),
|
||||
[=] { PeerMenuExportChat(peer); },
|
||||
&st::menuIconExport);
|
||||
}
|
||||
|
||||
void Filler::addReport() {
|
||||
const auto chat = _peer->asChat();
|
||||
const auto channel = _peer->asChannel();
|
||||
if ((!chat || chat->amCreator())
|
||||
&& (!channel || channel->amCreator())) {
|
||||
return;
|
||||
}
|
||||
const auto peer = _peer;
|
||||
const auto navigation = _controller;
|
||||
_addAction(tr::lng_profile_report(tr::now), [=] {
|
||||
HistoryView::ShowReportPeerBox(navigation, peer);
|
||||
}, &st::menuIconReport);
|
||||
}
|
||||
|
||||
void Filler::addNewContact() {
|
||||
const auto user = _peer->asUser();
|
||||
if (!user || user->isContact() || user->isSelf() || user->isBot()) {
|
||||
return;
|
||||
}
|
||||
const auto controller = _controller;
|
||||
_addAction(
|
||||
tr::lng_info_add_as_contact(tr::now),
|
||||
[=] { controller->show(Box(EditContactBox, controller, user)); },
|
||||
&st::menuIconInvite);
|
||||
}
|
||||
|
||||
void Filler::addShareContact() {
|
||||
const auto user = _peer->asUser();
|
||||
if (!user || !user->canShareThisContact()) {
|
||||
return;
|
||||
}
|
||||
const auto controller = _controller;
|
||||
_addAction(
|
||||
tr::lng_info_share_contact(tr::now),
|
||||
[=] { PeerMenuShareContactBox(controller, user); },
|
||||
&st::menuIconShare);
|
||||
}
|
||||
|
||||
void Filler::addEditContact() {
|
||||
const auto user = _peer->asUser();
|
||||
if (!user || !user->isContact() || user->isSelf()) {
|
||||
return;
|
||||
}
|
||||
const auto controller = _controller;
|
||||
_addAction(
|
||||
tr::lng_info_edit_contact(tr::now),
|
||||
[=] { controller->show(Box(EditContactBox, controller, user)); },
|
||||
&st::menuIconEdit);
|
||||
}
|
||||
|
||||
void Filler::addBotToGroup() {
|
||||
const auto user = _peer->asUser();
|
||||
if (!user
|
||||
|| !user->isBot()
|
||||
|| user->isRepliesChat()
|
||||
|| user->botInfo->cantJoinGroups) {
|
||||
return;
|
||||
}
|
||||
using AddBotToGroup = AddBotToGroupBoxController;
|
||||
_addAction(
|
||||
tr::lng_profile_invite_to_group(tr::now),
|
||||
[=] { AddBotToGroup::Start(user); },
|
||||
&st::menuIconInvite);
|
||||
}
|
||||
|
||||
void Filler::addNewMembers() {
|
||||
const auto chat = _peer->asChat();
|
||||
const auto channel = _peer->asChannel();
|
||||
if ((!chat || !chat->canAddMembers())
|
||||
&& (!channel || !channel->canAddMembers())) {
|
||||
return;
|
||||
}
|
||||
const auto navigation = _controller;
|
||||
const auto callback = chat
|
||||
? Fn<void()>([=] { AddChatMembers(navigation, chat); })
|
||||
: [=] { PeerMenuAddChannelMembers(navigation, channel); };
|
||||
_addAction(
|
||||
((chat || channel->isMegagroup())
|
||||
? tr::lng_channel_add_members(tr::now)
|
||||
: tr::lng_channel_add_users(tr::now)),
|
||||
callback,
|
||||
&st::menuIconInvite);
|
||||
}
|
||||
|
||||
void Filler::addDeleteContact() {
|
||||
const auto user = _peer->asUser();
|
||||
if (!user || !user->isContact() || user->isSelf()) {
|
||||
return;
|
||||
}
|
||||
_addAction(
|
||||
tr::lng_info_delete_contact(tr::now),
|
||||
[=] { PeerMenuDeleteContact(user); },
|
||||
&st::menuIconDelete);
|
||||
}
|
||||
|
||||
void Filler::addManageChat() {
|
||||
if (!EditPeerInfoBox::Available(_peer)) {
|
||||
return;
|
||||
}
|
||||
const auto peer = _peer;
|
||||
const auto navigation = _controller;
|
||||
const auto text = (peer->isChat() || peer->isMegagroup())
|
||||
? tr::lng_manage_group_title(tr::now)
|
||||
: tr::lng_manage_channel_title(tr::now);
|
||||
_addAction(text, [=] {
|
||||
navigation->showEditPeerBox(peer);
|
||||
}, &st::menuIconManage);
|
||||
}
|
||||
|
||||
void Filler::addCreatePoll() {
|
||||
if (!_peer->canSendPolls()) {
|
||||
return;
|
||||
}
|
||||
const auto peer = _peer;
|
||||
const auto controller = _controller;
|
||||
const auto source = (_request.section == Section::Scheduled)
|
||||
? Api::SendType::Scheduled
|
||||
@@ -691,45 +705,90 @@ void Filler::addPollAction(not_null<PeerData*> peer) {
|
||||
&st::menuIconCreatePoll);
|
||||
}
|
||||
|
||||
void Filler::addThemeEdit() {
|
||||
const auto user = _peer->asUser();
|
||||
if (!user || user->isBot()) {
|
||||
return;
|
||||
}
|
||||
const auto controller = _controller;
|
||||
_addAction(
|
||||
tr::lng_chat_theme_change(tr::now),
|
||||
[=] { controller->toggleChooseChatTheme(user); },
|
||||
&st::menuIconChangeColors);
|
||||
}
|
||||
|
||||
void Filler::fill() {
|
||||
if (_folder) {
|
||||
addTogglesForArchive();
|
||||
return;
|
||||
} else if (_request.section == Section::Scheduled
|
||||
|| _request.section == Section::Replies) {
|
||||
addPollAction(_peer);
|
||||
fillArchiveActions();
|
||||
return;
|
||||
}
|
||||
if (showHidePromotion()) {
|
||||
addHidePromotion();
|
||||
}
|
||||
if (showToggleArchived()) {
|
||||
addToggleArchive();
|
||||
}
|
||||
if (showTogglePin()) {
|
||||
addTogglePin();
|
||||
}
|
||||
if (showInfo()) {
|
||||
addInfo();
|
||||
}
|
||||
if (_request.section != Section::Profile && !_peer->isSelf()) {
|
||||
PeerMenuAddMuteAction(_peer, _addAction);
|
||||
}
|
||||
if (_request.section == Section::ChatsList) {
|
||||
//addSearch();
|
||||
addToggleUnreadMark();
|
||||
}
|
||||
|
||||
if (const auto user = _peer->asUser()) {
|
||||
addUserActions(user);
|
||||
} else if (const auto chat = _peer->asChat()) {
|
||||
addChatActions(chat);
|
||||
} else if (const auto channel = _peer->asChannel()) {
|
||||
addChannelActions(channel);
|
||||
switch (_request.section) {
|
||||
case Section::ChatsList: fillChatsListActions(); break;
|
||||
case Section::History: fillHistoryActions(); break;
|
||||
case Section::Profile: fillProfileActions(); break;
|
||||
case Section::Replies: fillRepliesActions(); break;
|
||||
case Section::Scheduled: fillScheduledActions(); break;
|
||||
default: Unexpected("_request.section in Filler::fill.");
|
||||
}
|
||||
}
|
||||
|
||||
void Filler::addTogglesForArchive() {
|
||||
void Filler::fillChatsListActions() {
|
||||
addHidePromotion();
|
||||
addToggleArchive();
|
||||
addTogglePin();
|
||||
addToggleMute();
|
||||
addToggleUnreadMark();
|
||||
// addToFolder();
|
||||
if (const auto user = _peer->asUser()) {
|
||||
if (!user->isContact()) {
|
||||
addBlockUser();
|
||||
}
|
||||
}
|
||||
addClearHistory();
|
||||
addDeleteChat();
|
||||
addLeaveChat();
|
||||
}
|
||||
|
||||
void Filler::fillHistoryActions() {
|
||||
addInfo();
|
||||
addToggleMute();
|
||||
addSupportInfo();
|
||||
addManageChat();
|
||||
addCreatePoll();
|
||||
addThemeEdit();
|
||||
addViewDiscussion();
|
||||
addExportChat();
|
||||
addReport();
|
||||
addClearHistory();
|
||||
addDeleteChat();
|
||||
addLeaveChat();
|
||||
}
|
||||
|
||||
void Filler::fillProfileActions() {
|
||||
addSupportInfo();
|
||||
addNewContact();
|
||||
addShareContact();
|
||||
addEditContact();
|
||||
addBotToGroup();
|
||||
addNewMembers();
|
||||
addManageChat();
|
||||
addViewDiscussion();
|
||||
addExportChat();
|
||||
addBlockUser();
|
||||
addReport();
|
||||
addLeaveChat();
|
||||
addDeleteContact();
|
||||
}
|
||||
|
||||
void Filler::fillRepliesActions() {
|
||||
addCreatePoll();
|
||||
}
|
||||
|
||||
void Filler::fillScheduledActions() {
|
||||
addCreatePoll();
|
||||
}
|
||||
|
||||
void Filler::fillArchiveActions() {
|
||||
Expects(_folder != nullptr);
|
||||
|
||||
if (_folder->id() != Data::Folder::kId) {
|
||||
@@ -1244,8 +1303,8 @@ void PeerMenuAddMuteAction(
|
||||
peer->owner().requestNotifySettings(peer);
|
||||
const auto muteText = [](bool isUnmuted) {
|
||||
return isUnmuted
|
||||
? tr::lng_disable_notifications_from_tray(tr::now)
|
||||
: tr::lng_enable_notifications_from_tray(tr::now);
|
||||
? tr::lng_context_mute(tr::now)
|
||||
: tr::lng_context_unmute(tr::now);
|
||||
};
|
||||
const auto muteAction = addAction(QString("-"), [=] {
|
||||
if (!peer->owner().notifyIsMuted(peer)) {
|
||||
|
||||
2
Telegram/ThirdParty/nimf
vendored
2
Telegram/ThirdParty/nimf
vendored
Submodule Telegram/ThirdParty/nimf updated: 797cbc201d...181f9467fc
@@ -4,6 +4,7 @@ ARG QT6=true
|
||||
FROM centos:7 AS builder
|
||||
|
||||
ENV GIT https://github.com
|
||||
ENV GIT_FREEDESKTOP ${GIT}/gitlab-freedesktop-mirrors
|
||||
ENV PKG_CONFIG_PATH /usr/local/lib64/pkgconfig:/usr/local/lib/pkgconfig:/usr/local/share/pkgconfig
|
||||
ENV QT5_VER 5_15_2
|
||||
ENV QT5_TAG v5.15.2
|
||||
@@ -176,7 +177,7 @@ WORKDIR ..
|
||||
RUN rm -rf rnnoise
|
||||
|
||||
FROM builder AS xcb-proto
|
||||
RUN git clone -b xcb-proto-1.14.1 --depth=1 https://gitlab.freedesktop.org/xorg/proto/xcbproto.git
|
||||
RUN git clone -b xcb-proto-1.14.1 --depth=1 $GIT_FREEDESKTOP/xcbproto.git
|
||||
|
||||
WORKDIR xcbproto
|
||||
RUN ./autogen.sh
|
||||
@@ -189,7 +190,7 @@ RUN rm -rf xcbproto
|
||||
FROM builder AS xcb
|
||||
COPY --from=xcb-proto ${LibrariesPath}/xcb-proto-cache /
|
||||
|
||||
RUN git clone -b libxcb-1.14 --depth=1 https://gitlab.freedesktop.org/xorg/lib/libxcb.git
|
||||
RUN git clone -b libxcb-1.14 --depth=1 $GIT_FREEDESKTOP/libxcb.git
|
||||
|
||||
WORKDIR libxcb
|
||||
RUN CFLAGS="-g -O2 $HFLAGS" ./autogen.sh --enable-static
|
||||
@@ -201,7 +202,7 @@ RUN rm -rf libxcb
|
||||
|
||||
FROM builder AS xcb-wm
|
||||
|
||||
RUN git clone -b 0.4.1 --depth=1 --recursive https://gitlab.freedesktop.org/xorg/lib/libxcb-wm.git
|
||||
RUN git clone -b 0.4.1 --depth=1 --recursive $GIT_FREEDESKTOP/libxcb-wm.git
|
||||
|
||||
WORKDIR libxcb-wm
|
||||
RUN ./autogen.sh --enable-static
|
||||
@@ -210,7 +211,7 @@ RUN make DESTDIR="$LibrariesPath/xcb-wm-cache" install
|
||||
|
||||
FROM builder AS xcb-util
|
||||
|
||||
RUN git clone -b 0.4.0 --depth=1 --recursive https://gitlab.freedesktop.org/xorg/lib/libxcb-util.git
|
||||
RUN git clone -b 0.4.0 --depth=1 --recursive $GIT_FREEDESKTOP/libxcb-util.git
|
||||
|
||||
WORKDIR libxcb-util
|
||||
RUN ./autogen.sh --enable-static
|
||||
@@ -220,7 +221,7 @@ RUN make DESTDIR="$LibrariesPath/xcb-util-cache" install
|
||||
FROM builder AS xcb-image
|
||||
COPY --from=xcb-util ${LibrariesPath}/xcb-util-cache /
|
||||
|
||||
RUN git clone -b 0.4.0 --depth=1 --recursive https://gitlab.freedesktop.org/xorg/lib/libxcb-image.git
|
||||
RUN git clone -b 0.4.0 --depth=1 --recursive $GIT_FREEDESKTOP/libxcb-image.git
|
||||
|
||||
WORKDIR libxcb-image
|
||||
RUN CFLAGS="-g -O2 $HFLAGS" ./autogen.sh --enable-static
|
||||
@@ -229,7 +230,7 @@ RUN make DESTDIR="$LibrariesPath/xcb-image-cache" install
|
||||
|
||||
FROM builder AS xcb-keysyms
|
||||
|
||||
RUN git clone -b 0.4.0 --depth=1 --recursive https://gitlab.freedesktop.org/xorg/lib/libxcb-keysyms.git
|
||||
RUN git clone -b 0.4.0 --depth=1 --recursive $GIT_FREEDESKTOP/libxcb-keysyms.git
|
||||
|
||||
WORKDIR libxcb-keysyms
|
||||
RUN CFLAGS="-g -O2 $HFLAGS" ./autogen.sh --enable-static
|
||||
@@ -238,7 +239,7 @@ RUN make DESTDIR="$LibrariesPath/xcb-keysyms-cache" install
|
||||
|
||||
FROM builder AS xcb-render-util
|
||||
|
||||
RUN git clone -b 0.3.9 --depth=1 --recursive https://gitlab.freedesktop.org/xorg/lib/libxcb-render-util.git
|
||||
RUN git clone -b 0.3.9 --depth=1 --recursive $GIT_FREEDESKTOP/libxcb-render-util.git
|
||||
|
||||
WORKDIR libxcb-render-util
|
||||
RUN CFLAGS="-g -O2 $HFLAGS" ./autogen.sh --enable-static
|
||||
@@ -246,7 +247,7 @@ RUN make -j$(nproc)
|
||||
RUN make DESTDIR="$LibrariesPath/xcb-render-util-cache" install
|
||||
|
||||
FROM builder AS libXext
|
||||
RUN git clone -b libXext-1.3.4 --depth=1 https://gitlab.freedesktop.org/xorg/lib/libxext.git
|
||||
RUN git clone -b libXext-1.3.4 --depth=1 $GIT_FREEDESKTOP/libxext.git
|
||||
|
||||
WORKDIR libxext
|
||||
RUN CFLAGS="-g -O2 $HFLAGS" ./autogen.sh --enable-static
|
||||
@@ -257,7 +258,7 @@ WORKDIR ..
|
||||
RUN rm -rf libxext
|
||||
|
||||
FROM builder AS libXtst
|
||||
RUN git clone -b libXtst-1.2.3 --depth=1 https://gitlab.freedesktop.org/xorg/lib/libxtst.git
|
||||
RUN git clone -b libXtst-1.2.3 --depth=1 $GIT_FREEDESKTOP/libxtst.git
|
||||
|
||||
WORKDIR libxtst
|
||||
RUN CFLAGS="-g -O2 $HFLAGS" ./autogen.sh --enable-static
|
||||
@@ -268,7 +269,7 @@ WORKDIR ..
|
||||
RUN rm -rf libxtst
|
||||
|
||||
FROM builder AS libXfixes
|
||||
RUN git clone -b libXfixes-5.0.3 --depth=1 https://gitlab.freedesktop.org/xorg/lib/libxfixes.git
|
||||
RUN git clone -b libXfixes-5.0.3 --depth=1 $GIT_FREEDESKTOP/libxfixes.git
|
||||
|
||||
WORKDIR libxfixes
|
||||
RUN CFLAGS="-g -O2 $HFLAGS" ./autogen.sh --enable-static
|
||||
@@ -281,7 +282,7 @@ RUN rm -rf libxfixes
|
||||
FROM builder AS libXv
|
||||
COPY --from=libXext ${LibrariesPath}/libXext-cache /
|
||||
|
||||
RUN git clone -b libXv-1.0.11 --depth=1 https://gitlab.freedesktop.org/xorg/lib/libxv.git
|
||||
RUN git clone -b libXv-1.0.11 --depth=1 $GIT_FREEDESKTOP/libxv.git
|
||||
|
||||
WORKDIR libxv
|
||||
RUN ./autogen.sh --enable-static
|
||||
@@ -292,7 +293,7 @@ WORKDIR ..
|
||||
RUN rm -rf libxv
|
||||
|
||||
FROM builder AS libXrandr
|
||||
RUN git clone -b libXrandr-1.5.2 --depth=1 https://gitlab.freedesktop.org/xorg/lib/libxrandr.git
|
||||
RUN git clone -b libXrandr-1.5.2 --depth=1 $GIT_FREEDESKTOP/libxrandr.git
|
||||
|
||||
WORKDIR libxrandr
|
||||
RUN CFLAGS="-g -O2 $HFLAGS" ./autogen.sh --enable-static
|
||||
@@ -303,7 +304,7 @@ WORKDIR ..
|
||||
RUN rm -rf libxrandr
|
||||
|
||||
FROM builder AS libXrender
|
||||
RUN git clone -b libXrender-0.9.10 --depth=1 https://gitlab.freedesktop.org/xorg/lib/libxrender.git
|
||||
RUN git clone -b libXrender-0.9.10 --depth=1 $GIT_FREEDESKTOP/libxrender.git
|
||||
|
||||
WORKDIR libxrender
|
||||
RUN CFLAGS="-g -O2 $HFLAGS" ./autogen.sh --enable-static
|
||||
@@ -314,7 +315,7 @@ WORKDIR ..
|
||||
RUN rm -rf libxrender
|
||||
|
||||
FROM builder AS libXdamage
|
||||
RUN git clone -b libXdamage-1.1.5 --depth=1 https://gitlab.freedesktop.org/xorg/lib/libxdamage.git
|
||||
RUN git clone -b libXdamage-1.1.5 --depth=1 $GIT_FREEDESKTOP/libxdamage.git
|
||||
|
||||
WORKDIR libxdamage
|
||||
RUN CFLAGS="-g -O2 $HFLAGS" ./autogen.sh --enable-static
|
||||
@@ -325,7 +326,7 @@ WORKDIR ..
|
||||
RUN rm -rf libxdamage
|
||||
|
||||
FROM builder AS libXcomposite
|
||||
RUN git clone -b libXcomposite-0.4.5 --depth=1 https://gitlab.freedesktop.org/xorg/lib/libxcomposite.git
|
||||
RUN git clone -b libXcomposite-0.4.5 --depth=1 $GIT_FREEDESKTOP/libxcomposite.git
|
||||
|
||||
WORKDIR libxcomposite
|
||||
RUN CFLAGS="-g -O2 $HFLAGS" ./autogen.sh --enable-static
|
||||
@@ -338,7 +339,7 @@ RUN rm -rf libxcomposite
|
||||
FROM builder AS wayland
|
||||
COPY --from=libffi ${LibrariesPath}/libffi-cache /
|
||||
|
||||
RUN git clone -b 1.20.0 --depth=1 https://gitlab.freedesktop.org/wayland/wayland.git
|
||||
RUN git clone -b 1.20.0 --depth=1 $GIT_FREEDESKTOP/wayland.git
|
||||
|
||||
WORKDIR wayland
|
||||
RUN meson build \
|
||||
@@ -358,7 +359,7 @@ RUN rm -rf wayland
|
||||
FROM builder AS wayland-protocols
|
||||
COPY --from=wayland ${LibrariesPath}/wayland-cache /
|
||||
|
||||
RUN git clone -b 1.24 --depth=1 https://gitlab.freedesktop.org/wayland/wayland-protocols.git
|
||||
RUN git clone -b 1.24 --depth=1 $GIT_FREEDESKTOP/wayland-protocols.git
|
||||
|
||||
WORKDIR wayland-protocols
|
||||
RUN meson build \
|
||||
@@ -386,7 +387,7 @@ WORKDIR ..
|
||||
RUN rm -rf plasma-wayland-protocols
|
||||
|
||||
FROM builder AS libpciaccess
|
||||
RUN git clone -b libpciaccess-0.16 --depth=1 https://gitlab.freedesktop.org/xorg/lib/libpciaccess.git
|
||||
RUN git clone -b libpciaccess-0.16 --depth=1 $GIT_FREEDESKTOP/libpciaccess.git
|
||||
|
||||
WORKDIR libpciaccess
|
||||
RUN ./autogen.sh --enable-static
|
||||
@@ -400,7 +401,7 @@ RUN rm -rf libpciaccess
|
||||
FROM builder AS drm
|
||||
COPY --from=libpciaccess ${LibrariesPath}/libpciaccess-cache /
|
||||
|
||||
RUN git clone -b libdrm-2.4.109 --depth=1 https://gitlab.freedesktop.org/mesa/drm.git
|
||||
RUN git clone -b libdrm-2.4.109 --depth=1 $GIT_FREEDESKTOP/drm.git
|
||||
|
||||
WORKDIR drm
|
||||
RUN meson build \
|
||||
@@ -436,7 +437,7 @@ WORKDIR ..
|
||||
RUN rm -rf libva
|
||||
|
||||
FROM builder AS libvdpau
|
||||
RUN git clone -b 1.4 --depth=1 https://gitlab.freedesktop.org/vdpau/libvdpau.git
|
||||
RUN git clone -b 1.4 --depth=1 $GIT_FREEDESKTOP/libvdpau.git
|
||||
|
||||
WORKDIR libvdpau
|
||||
RUN sed -i 's/shared_library/library/g' src/meson.build
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
AppVersion 3003002
|
||||
AppVersionStrMajor 3.3
|
||||
AppVersionStrSmall 3.3.2
|
||||
AppVersionStr 3.3.2
|
||||
BetaChannel 1
|
||||
AppVersion 3004003
|
||||
AppVersionStrMajor 3.4
|
||||
AppVersionStrSmall 3.4.3
|
||||
AppVersionStr 3.4.3
|
||||
BetaChannel 0
|
||||
AlphaVersion 0
|
||||
AppVersionOriginal 3.3.2.beta
|
||||
AppVersionOriginal 3.4.3
|
||||
|
||||
Submodule Telegram/lib_base updated: d985476a6c...161918a0b5
Submodule Telegram/lib_ui updated: a3b3745d7c...633648074a
@@ -1,3 +1,23 @@
|
||||
3.4.3 (03.01.21)
|
||||
|
||||
- Bug fixes and other minor improvements.
|
||||
|
||||
3.4.2 (31.12.21)
|
||||
|
||||
- Bug fixes and other minor improvements.
|
||||
|
||||
3.4.1 (31.12.21)
|
||||
|
||||
- Bug fixes and other minor improvements.
|
||||
|
||||
3.4 (30.12.21)
|
||||
|
||||
- Send reactions to messages.
|
||||
- Group and Channel admins can enable reactions in their chat via '...' menu > Manage > Reactions.
|
||||
- Select text when typing and choose 'Formatting > Spoiler' in the context menu to hide some or all of the contents of a message.
|
||||
- Click on the spoiler in chat to reveal its hidden text.
|
||||
- Spoiler formatting hides text in chat, as well as in the chat list and notifications.
|
||||
|
||||
3.3.2 beta (29.12.21)
|
||||
|
||||
- Select text when typing and choose 'Formatting > Spoiler' in the context menu to hide some or all of the contents of a message.
|
||||
|
||||
2
cmake
2
cmake
Submodule cmake updated: 0c57e24529...ed7cf04191
Reference in New Issue
Block a user