Compare commits
22 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e19180cc86 | ||
|
|
10cb891f48 | ||
|
|
c8f7a8c795 | ||
|
|
74a28ffdf7 | ||
|
|
ecedce0c2f | ||
|
|
bd4f993292 | ||
|
|
4934b026d3 | ||
|
|
11f183a79f | ||
|
|
ae426a41e0 | ||
|
|
d6edc3728d | ||
|
|
e121487170 | ||
|
|
72a093ec77 | ||
|
|
4996d90782 | ||
|
|
9a451a1423 | ||
|
|
4d11ad45db | ||
|
|
1657c2c7f2 | ||
|
|
c5e7048a3d | ||
|
|
1f194da2f0 | ||
|
|
0954b04f24 | ||
|
|
4659499340 | ||
|
|
6eb4584408 | ||
|
|
4ba4b77b95 |
|
Before Width: | Height: | Size: 305 B |
|
Before Width: | Height: | Size: 524 B |
|
Before Width: | Height: | Size: 820 B |
|
Before Width: | Height: | Size: 385 B |
|
Before Width: | Height: | Size: 873 B |
|
Before Width: | Height: | Size: 1.3 KiB |
|
Before Width: | Height: | Size: 230 B |
|
Before Width: | Height: | Size: 393 B |
|
Before Width: | Height: | Size: 616 B |
|
Before Width: | Height: | Size: 333 B |
|
Before Width: | Height: | Size: 552 B |
|
Before Width: | Height: | Size: 889 B |
|
Before Width: | Height: | Size: 173 B |
|
Before Width: | Height: | Size: 304 B |
|
Before Width: | Height: | Size: 545 B |
|
Before Width: | Height: | Size: 228 B |
|
Before Width: | Height: | Size: 420 B |
|
Before Width: | Height: | Size: 661 B |
|
Before Width: | Height: | Size: 290 B |
|
Before Width: | Height: | Size: 499 B |
|
Before Width: | Height: | Size: 846 B |
|
Before Width: | Height: | Size: 420 B |
|
Before Width: | Height: | Size: 715 B |
|
Before Width: | Height: | Size: 1.1 KiB |
|
Before Width: | Height: | Size: 823 B After Width: | Height: | Size: 3.3 KiB |
|
Before Width: | Height: | Size: 1.9 KiB After Width: | Height: | Size: 7.3 KiB |
|
Before Width: | Height: | Size: 4.2 KiB After Width: | Height: | Size: 11 KiB |
@@ -10,7 +10,7 @@
|
||||
<Identity Name="TelegramMessengerLLP.TelegramDesktop"
|
||||
ProcessorArchitecture="ARCHITECTURE"
|
||||
Publisher="CN=536BC709-8EE1-4478-AF22-F0F0F26FF64A"
|
||||
Version="3.4.5.0" />
|
||||
Version="3.4.6.0" />
|
||||
<Properties>
|
||||
<DisplayName>Telegram Desktop</DisplayName>
|
||||
<PublisherDisplayName>Telegram Messenger LLP</PublisherDisplayName>
|
||||
|
||||
@@ -44,8 +44,8 @@ IDI_ICON1 ICON "..\\art\\icon256.ico"
|
||||
//
|
||||
|
||||
VS_VERSION_INFO VERSIONINFO
|
||||
FILEVERSION 3,4,5,0
|
||||
PRODUCTVERSION 3,4,5,0
|
||||
FILEVERSION 3,4,6,0
|
||||
PRODUCTVERSION 3,4,6,0
|
||||
FILEFLAGSMASK 0x3fL
|
||||
#ifdef _DEBUG
|
||||
FILEFLAGS 0x1L
|
||||
@@ -62,10 +62,10 @@ BEGIN
|
||||
BEGIN
|
||||
VALUE "CompanyName", "Telegram FZ-LLC"
|
||||
VALUE "FileDescription", "Telegram Desktop"
|
||||
VALUE "FileVersion", "3.4.5.0"
|
||||
VALUE "FileVersion", "3.4.6.0"
|
||||
VALUE "LegalCopyright", "Copyright (C) 2014-2022"
|
||||
VALUE "ProductName", "Telegram Desktop"
|
||||
VALUE "ProductVersion", "3.4.5.0"
|
||||
VALUE "ProductVersion", "3.4.6.0"
|
||||
END
|
||||
END
|
||||
BLOCK "VarFileInfo"
|
||||
|
||||
@@ -35,8 +35,8 @@ LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
|
||||
//
|
||||
|
||||
VS_VERSION_INFO VERSIONINFO
|
||||
FILEVERSION 3,4,5,0
|
||||
PRODUCTVERSION 3,4,5,0
|
||||
FILEVERSION 3,4,6,0
|
||||
PRODUCTVERSION 3,4,6,0
|
||||
FILEFLAGSMASK 0x3fL
|
||||
#ifdef _DEBUG
|
||||
FILEFLAGS 0x1L
|
||||
@@ -53,10 +53,10 @@ BEGIN
|
||||
BEGIN
|
||||
VALUE "CompanyName", "Telegram FZ-LLC"
|
||||
VALUE "FileDescription", "Telegram Desktop Updater"
|
||||
VALUE "FileVersion", "3.4.5.0"
|
||||
VALUE "FileVersion", "3.4.6.0"
|
||||
VALUE "LegalCopyright", "Copyright (C) 2014-2022"
|
||||
VALUE "ProductName", "Telegram Desktop"
|
||||
VALUE "ProductVersion", "3.4.5.0"
|
||||
VALUE "ProductVersion", "3.4.6.0"
|
||||
END
|
||||
END
|
||||
BLOCK "VarFileInfo"
|
||||
|
||||
@@ -51,6 +51,7 @@ inline bool operator==(
|
||||
|
||||
struct PeersWithReactions {
|
||||
std::vector<PeerWithReaction> list;
|
||||
std::vector<PeerId> read;
|
||||
int fullReactionsCount = 0;
|
||||
bool unknown = false;
|
||||
};
|
||||
@@ -59,6 +60,7 @@ inline bool operator==(
|
||||
const PeersWithReactions &b) noexcept {
|
||||
return (a.fullReactionsCount == b.fullReactionsCount)
|
||||
&& (a.list == b.list)
|
||||
&& (a.read == b.read)
|
||||
&& (a.unknown == b.unknown);
|
||||
}
|
||||
|
||||
@@ -244,13 +246,15 @@ struct State {
|
||||
}
|
||||
|
||||
[[nodiscard]] PeersWithReactions WithEmptyReactions(
|
||||
const Peers &peers) {
|
||||
return PeersWithReactions{
|
||||
Peers &&peers) {
|
||||
auto result = PeersWithReactions{
|
||||
.list = peers.list | ranges::views::transform([](PeerId peer) {
|
||||
return PeerWithReaction{.peer = peer };
|
||||
}) | ranges::to_vector,
|
||||
.unknown = peers.unknown,
|
||||
};
|
||||
result.read = std::move(peers.list);
|
||||
return result;
|
||||
}
|
||||
|
||||
[[nodiscard]] rpl::producer<PeersWithReactions> WhoReactedIds(
|
||||
@@ -319,7 +323,7 @@ struct State {
|
||||
return rpl::combine(
|
||||
WhoReactedIds(item, QString(), context),
|
||||
WhoReadIds(item, context)
|
||||
) | rpl::map([=](PeersWithReactions reacted, Peers read) {
|
||||
) | rpl::map([=](PeersWithReactions &&reacted, Peers &&read) {
|
||||
if (reacted.unknown || read.unknown) {
|
||||
return PeersWithReactions{ .unknown = true };
|
||||
}
|
||||
@@ -329,7 +333,8 @@ struct State {
|
||||
list.push_back({ .peer = peer });
|
||||
}
|
||||
}
|
||||
return reacted;
|
||||
reacted.read = std::move(read.list);
|
||||
return std::move(reacted);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -438,6 +443,104 @@ void RegenerateParticipants(not_null<State*> state, int small, int large) {
|
||||
RegenerateUserpics(state, small, large);
|
||||
}
|
||||
|
||||
rpl::producer<Ui::WhoReadContent> WhoReacted(
|
||||
not_null<HistoryItem*> item,
|
||||
const QString &reaction,
|
||||
not_null<QWidget*> context,
|
||||
const style::WhoRead &st,
|
||||
std::shared_ptr<WhoReadList> whoReadIds) {
|
||||
const auto small = st.userpics.size;
|
||||
const auto large = st.photoSize;
|
||||
return [=](auto consumer) {
|
||||
auto lifetime = rpl::lifetime();
|
||||
|
||||
const auto resolveWhoRead = reaction.isEmpty()
|
||||
&& WhoReadExists(item);
|
||||
|
||||
const auto state = lifetime.make_state<State>();
|
||||
const auto pushNext = [=] {
|
||||
consumer.put_next_copy(state->current);
|
||||
};
|
||||
|
||||
const auto resolveWhoReacted = !reaction.isEmpty()
|
||||
|| item->canViewReactions();
|
||||
auto idsWithReactions = (resolveWhoRead && resolveWhoReacted)
|
||||
? WhoReadOrReactedIds(item, context)
|
||||
: resolveWhoRead
|
||||
? (WhoReadIds(item, context) | rpl::map(WithEmptyReactions))
|
||||
: WhoReactedIds(item, reaction, context);
|
||||
state->current.type = resolveWhoRead
|
||||
? DetectSeenType(item)
|
||||
: Ui::WhoReadType::Reacted;
|
||||
if (resolveWhoReacted) {
|
||||
const auto &list = item->reactions();
|
||||
state->current.fullReactionsCount = reaction.isEmpty()
|
||||
? ranges::accumulate(
|
||||
list,
|
||||
0,
|
||||
ranges::plus{},
|
||||
[](const auto &pair) { return pair.second; })
|
||||
: list.contains(reaction)
|
||||
? list.find(reaction)->second
|
||||
: 0;
|
||||
|
||||
// #TODO reactions
|
||||
state->current.singleReaction = !reaction.isEmpty()
|
||||
? reaction
|
||||
: (list.size() == 1)
|
||||
? list.front().first
|
||||
: QString();
|
||||
}
|
||||
std::move(
|
||||
idsWithReactions
|
||||
) | rpl::start_with_next([=](PeersWithReactions &&peers) {
|
||||
if (peers.unknown) {
|
||||
state->userpics.clear();
|
||||
consumer.put_next(Ui::WhoReadContent{
|
||||
.type = state->current.type,
|
||||
.fullReactionsCount = state->current.fullReactionsCount,
|
||||
.fullReadCount = state->current.fullReadCount,
|
||||
.unknown = true,
|
||||
});
|
||||
return;
|
||||
}
|
||||
state->current.fullReadCount = int(peers.read.size());
|
||||
state->current.fullReactionsCount = peers.fullReactionsCount;
|
||||
if (whoReadIds) {
|
||||
whoReadIds->list = (peers.read.size() > peers.list.size())
|
||||
? std::move(peers.read)
|
||||
: std::vector<PeerId>();
|
||||
}
|
||||
if (UpdateUserpics(state, item, peers.list)) {
|
||||
RegenerateParticipants(state, small, large);
|
||||
pushNext();
|
||||
} else if (peers.list.empty()) {
|
||||
pushNext();
|
||||
}
|
||||
}, lifetime);
|
||||
|
||||
item->history()->session().downloaderTaskFinished(
|
||||
) | rpl::filter([=] {
|
||||
return state->someUserpicsNotLoaded && !state->scheduled;
|
||||
}) | rpl::start_with_next([=] {
|
||||
for (const auto &userpic : state->userpics) {
|
||||
if (userpic.peer->userpicUniqueKey(userpic.view)
|
||||
!= userpic.uniqueKey) {
|
||||
state->scheduled = true;
|
||||
crl::on_main(&state->guard, [=] {
|
||||
state->scheduled = false;
|
||||
RegenerateUserpics(state, small, large);
|
||||
pushNext();
|
||||
});
|
||||
return;
|
||||
}
|
||||
}
|
||||
}, lifetime);
|
||||
|
||||
return lifetime;
|
||||
};
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
bool WhoReadExists(not_null<HistoryItem*> item) {
|
||||
@@ -482,8 +585,9 @@ bool WhoReactedExists(not_null<HistoryItem*> item) {
|
||||
rpl::producer<Ui::WhoReadContent> WhoReacted(
|
||||
not_null<HistoryItem*> item,
|
||||
not_null<QWidget*> context,
|
||||
const style::WhoRead &st) {
|
||||
return WhoReacted(item, QString(), context, st);
|
||||
const style::WhoRead &st,
|
||||
std::shared_ptr<WhoReadList> whoReadIds) {
|
||||
return WhoReacted(item, QString(), context, st, std::move(whoReadIds));
|
||||
}
|
||||
|
||||
rpl::producer<Ui::WhoReadContent> WhoReacted(
|
||||
@@ -491,88 +595,7 @@ rpl::producer<Ui::WhoReadContent> WhoReacted(
|
||||
const QString &reaction,
|
||||
not_null<QWidget*> context,
|
||||
const style::WhoRead &st) {
|
||||
const auto small = st.userpics.size;
|
||||
const auto large = st.photoSize;
|
||||
return [=](auto consumer) {
|
||||
auto lifetime = rpl::lifetime();
|
||||
|
||||
const auto resolveWhoRead = reaction.isEmpty() && WhoReadExists(item);
|
||||
|
||||
const auto state = lifetime.make_state<State>();
|
||||
const auto pushNext = [=] {
|
||||
consumer.put_next_copy(state->current);
|
||||
};
|
||||
|
||||
const auto resolveWhoReacted = !reaction.isEmpty()
|
||||
|| item->canViewReactions();
|
||||
auto idsWithReactions = (resolveWhoRead && resolveWhoReacted)
|
||||
? WhoReadOrReactedIds(item, context)
|
||||
: resolveWhoRead
|
||||
? (WhoReadIds(item, context) | rpl::map(WithEmptyReactions))
|
||||
: WhoReactedIds(item, reaction, context);
|
||||
state->current.type = resolveWhoRead
|
||||
? DetectSeenType(item)
|
||||
: Ui::WhoReadType::Reacted;
|
||||
if (resolveWhoReacted) {
|
||||
const auto &list = item->reactions();
|
||||
state->current.fullReactionsCount = reaction.isEmpty()
|
||||
? ranges::accumulate(
|
||||
list,
|
||||
0,
|
||||
ranges::plus{},
|
||||
[](const auto &pair) { return pair.second; })
|
||||
: list.contains(reaction)
|
||||
? list.find(reaction)->second
|
||||
: 0;
|
||||
|
||||
// #TODO reactions
|
||||
state->current.singleReaction = !reaction.isEmpty()
|
||||
? reaction
|
||||
: (list.size() == 1)
|
||||
? list.front().first
|
||||
: QString();
|
||||
}
|
||||
std::move(
|
||||
idsWithReactions
|
||||
) | rpl::start_with_next([=](const PeersWithReactions &peers) {
|
||||
if (peers.unknown) {
|
||||
state->userpics.clear();
|
||||
consumer.put_next(Ui::WhoReadContent{
|
||||
.type = state->current.type,
|
||||
.fullReactionsCount = state->current.fullReactionsCount,
|
||||
.unknown = true,
|
||||
});
|
||||
return;
|
||||
}
|
||||
state->current.fullReactionsCount = peers.fullReactionsCount;
|
||||
if (UpdateUserpics(state, item, peers.list)) {
|
||||
RegenerateParticipants(state, small, large);
|
||||
pushNext();
|
||||
} else if (peers.list.empty()) {
|
||||
pushNext();
|
||||
}
|
||||
}, lifetime);
|
||||
|
||||
item->history()->session().downloaderTaskFinished(
|
||||
) | rpl::filter([=] {
|
||||
return state->someUserpicsNotLoaded && !state->scheduled;
|
||||
}) | rpl::start_with_next([=] {
|
||||
for (const auto &userpic : state->userpics) {
|
||||
if (userpic.peer->userpicUniqueKey(userpic.view)
|
||||
!= userpic.uniqueKey) {
|
||||
state->scheduled = true;
|
||||
crl::on_main(&state->guard, [=] {
|
||||
state->scheduled = false;
|
||||
RegenerateUserpics(state, small, large);
|
||||
pushNext();
|
||||
});
|
||||
return;
|
||||
}
|
||||
}
|
||||
}, lifetime);
|
||||
|
||||
return lifetime;
|
||||
};
|
||||
return WhoReacted(item, reaction, context, st, nullptr);
|
||||
}
|
||||
|
||||
} // namespace Api
|
||||
|
||||
@@ -15,6 +15,7 @@ struct WhoRead;
|
||||
|
||||
namespace Ui {
|
||||
struct WhoReadContent;
|
||||
enum class WhoReadType;
|
||||
} // namespace Ui
|
||||
|
||||
namespace Api {
|
||||
@@ -22,15 +23,21 @@ namespace Api {
|
||||
[[nodiscard]] bool WhoReadExists(not_null<HistoryItem*> item);
|
||||
[[nodiscard]] bool WhoReactedExists(not_null<HistoryItem*> item);
|
||||
|
||||
struct WhoReadList {
|
||||
std::vector<PeerId> list;
|
||||
Ui::WhoReadType type = {};
|
||||
};
|
||||
|
||||
// The context must be destroyed before the session holding this item.
|
||||
[[nodiscard]] rpl::producer<Ui::WhoReadContent> WhoReacted(
|
||||
not_null<HistoryItem*> item,
|
||||
not_null<QWidget*> context,
|
||||
const style::WhoRead &st); // Cache results for this lifetime.
|
||||
not_null<QWidget*> context, // Cache results for this lifetime.
|
||||
const style::WhoRead &st,
|
||||
std::shared_ptr<WhoReadList> whoReadIds = nullptr);
|
||||
[[nodiscard]] rpl::producer<Ui::WhoReadContent> WhoReacted(
|
||||
not_null<HistoryItem*> item,
|
||||
const QString &reaction,
|
||||
not_null<QWidget*> context,
|
||||
const style::WhoRead &st); // Cache results for this lifetime.
|
||||
not_null<QWidget*> context, // Cache results for this lifetime.
|
||||
const style::WhoRead &st);
|
||||
|
||||
} // namespace Api
|
||||
|
||||
@@ -411,49 +411,45 @@ callBarSignalBars: CallSignalBars(callPanelSignalBars) {
|
||||
color: callBarFg;
|
||||
}
|
||||
|
||||
callTitleButton: IconButton {
|
||||
width: 34px;
|
||||
height: 30px;
|
||||
iconPosition: point(0px, 0px);
|
||||
}
|
||||
callTitleButton: windowTitleButton;
|
||||
callTitleMinimizeIcon: icon {
|
||||
{ "calls/calls_minimize_shadow", windowShadowFg },
|
||||
{ "calls/calls_minimize_main", callNameFg },
|
||||
{ "title_shadow_minimize", windowShadowFg },
|
||||
{ "title_button_minimize", callNameFg },
|
||||
};
|
||||
callTitleMinimizeIconOver: icon {
|
||||
{ size(34px, 30px), callBgButton },
|
||||
{ size(34px, 30px), callMuteRipple },
|
||||
{ "calls/calls_minimize_shadow", windowShadowFg },
|
||||
{ "calls/calls_minimize_main", callNameFg },
|
||||
{ windowTitleButtonSize, callBgButton },
|
||||
{ windowTitleButtonSize, callMuteRipple },
|
||||
{ "title_shadow_minimize", windowShadowFg },
|
||||
{ "title_button_minimize", callNameFg },
|
||||
};
|
||||
callTitleMaximizeIcon: icon {
|
||||
{ "calls/calls_maximize_shadow", windowShadowFg },
|
||||
{ "calls/calls_maximize_main", callNameFg },
|
||||
{ "title_shadow_maximize", windowShadowFg },
|
||||
{ "title_button_maximize", callNameFg },
|
||||
};
|
||||
callTitleMaximizeIconOver: icon {
|
||||
{ size(34px, 30px), callBgButton },
|
||||
{ size(34px, 30px), callMuteRipple },
|
||||
{ "calls/calls_maximize_shadow", windowShadowFg },
|
||||
{ "calls/calls_maximize_main", callNameFg },
|
||||
{ windowTitleButtonSize, callBgButton },
|
||||
{ windowTitleButtonSize, callMuteRipple },
|
||||
{ "title_shadow_maximize", windowShadowFg },
|
||||
{ "title_button_maximize", callNameFg },
|
||||
};
|
||||
callTitleRestoreIcon: icon {
|
||||
{ "calls/calls_restore_shadow", windowShadowFg },
|
||||
{ "calls/calls_restore_main", callNameFg },
|
||||
{ "title_shadow_restore", windowShadowFg },
|
||||
{ "title_button_restore", callNameFg },
|
||||
};
|
||||
callTitleRestoreIconOver: icon {
|
||||
{ size(34px, 30px), callBgButton },
|
||||
{ size(34px, 30px), callMuteRipple },
|
||||
{ "calls/calls_restore_shadow", windowShadowFg },
|
||||
{ "calls/calls_restore_main", callNameFg },
|
||||
{ windowTitleButtonSize, callBgButton },
|
||||
{ windowTitleButtonSize, callMuteRipple },
|
||||
{ "title_shadow_restore", windowShadowFg },
|
||||
{ "title_button_restore", callNameFg },
|
||||
};
|
||||
callTitleCloseIcon: icon {
|
||||
{ "calls/calls_close_shadow", windowShadowFg },
|
||||
{ "calls/calls_close_main", callNameFg },
|
||||
{ "title_shadow_close", windowShadowFg },
|
||||
{ "title_button_close", callNameFg },
|
||||
};
|
||||
callTitleCloseIconOver: icon {
|
||||
{ size(34px, 30px), titleButtonCloseBgOver },
|
||||
{ "calls/calls_close_shadow", windowShadowFg },
|
||||
{ "calls/calls_close_main", titleButtonCloseFgOver },
|
||||
{ windowTitleButtonSize, titleButtonCloseBgOver },
|
||||
{ "title_shadow_close", windowShadowFg },
|
||||
{ "title_button_close", titleButtonCloseFgOver },
|
||||
};
|
||||
callTitle: WindowTitle(defaultWindowTitle) {
|
||||
height: 0px;
|
||||
@@ -1055,37 +1051,37 @@ groupCallDelaySlider: MediaSlider(defaultContinuousSlider) {
|
||||
groupCallDelayMargin: margins(22px, 5px, 20px, 10px);
|
||||
|
||||
groupCallTitleButton: IconButton {
|
||||
width: 24px;
|
||||
height: 21px;
|
||||
width: windowTitleButtonWidth;
|
||||
height: windowTitleHeight;
|
||||
iconPosition: point(0px, 0px);
|
||||
}
|
||||
groupCallTitleMinimizeIcon: icon {
|
||||
{ "title_button_minimize", groupCallMemberNotJoinedStatus, point(4px, 4px) },
|
||||
{ "title_button_minimize", groupCallMemberNotJoinedStatus },
|
||||
};
|
||||
groupCallTitleMinimizeIconOver: icon {
|
||||
{ size(24px, 21px), groupCallMembersBgOver },
|
||||
{ "title_button_minimize", groupCallMembersFg, point(4px, 4px) },
|
||||
{ windowTitleButtonSize, groupCallMembersBgOver },
|
||||
{ "title_button_minimize", groupCallMembersFg },
|
||||
};
|
||||
groupCallTitleMaximizeIcon: icon {
|
||||
{ "title_button_maximize", groupCallMemberNotJoinedStatus, point(4px, 4px) },
|
||||
{ "title_button_maximize", groupCallMemberNotJoinedStatus },
|
||||
};
|
||||
groupCallTitleMaximizeIconOver: icon {
|
||||
{ size(24px, 21px), groupCallMembersBgOver },
|
||||
{ "title_button_maximize", groupCallMembersFg, point(4px, 4px) },
|
||||
{ windowTitleButtonSize, groupCallMembersBgOver },
|
||||
{ "title_button_maximize", groupCallMembersFg },
|
||||
};
|
||||
groupCallTitleRestoreIcon: icon {
|
||||
{ "title_button_restore", groupCallMemberNotJoinedStatus, point(4px, 4px) },
|
||||
{ "title_button_restore", groupCallMemberNotJoinedStatus },
|
||||
};
|
||||
groupCallTitleRestoreIconOver: icon {
|
||||
{ size(24px, 21px), groupCallMembersBgOver },
|
||||
{ "title_button_restore", groupCallMembersFg, point(4px, 4px) },
|
||||
{ windowTitleButtonSize, groupCallMembersBgOver },
|
||||
{ "title_button_restore", groupCallMembersFg },
|
||||
};
|
||||
groupCallTitleCloseIcon: icon {
|
||||
{ "title_button_close", groupCallMemberNotJoinedStatus, point(4px, 4px) },
|
||||
{ "title_button_close", groupCallMemberNotJoinedStatus },
|
||||
};
|
||||
groupCallTitleCloseIconOver: icon {
|
||||
{ size(24px, 21px), titleButtonCloseBgOver },
|
||||
{ "title_button_close", titleButtonCloseFgOver, point(4px, 4px) },
|
||||
{ windowTitleButtonSize, titleButtonCloseBgOver },
|
||||
{ "title_button_close", titleButtonCloseFgOver },
|
||||
};
|
||||
groupCallTitle: WindowTitle(defaultWindowTitle) {
|
||||
height: 0px;
|
||||
@@ -1199,7 +1195,7 @@ desktopCaptureSourceSkips: size(2px, 10px);
|
||||
desktopCaptureSourceTitle: WindowTitle(groupCallTitle) {
|
||||
bg: groupCallMembersBgOver;
|
||||
bgActive: groupCallMembersBgOver;
|
||||
height: 21px;
|
||||
height: windowTitleHeight;
|
||||
}
|
||||
desktopCapturePadding: margins(7px, 7px, 7px, 33px);
|
||||
desktopCaptureLabelBottom: 7px;
|
||||
|
||||
@@ -61,8 +61,8 @@ Panel::Panel(not_null<Call*> call)
|
||||
, _user(call->user())
|
||||
, _layerBg(std::make_unique<Ui::LayerManager>(widget()))
|
||||
#ifndef Q_OS_MAC
|
||||
, _controls(std::make_unique<Ui::Platform::TitleControls>(
|
||||
widget(),
|
||||
, _controls(Ui::Platform::SetupSeparateTitleControls(
|
||||
window(),
|
||||
st::callTitle,
|
||||
[=](bool maximized) { toggleFullScreen(maximized); }))
|
||||
#endif // !Q_OS_MAC
|
||||
@@ -144,7 +144,7 @@ void Panel::initWindow() {
|
||||
return Flag::None | Flag(0);
|
||||
}
|
||||
#ifndef Q_OS_MAC
|
||||
if (_controls->geometry().contains(widgetPoint)) {
|
||||
if (_controls->controls.geometry().contains(widgetPoint)) {
|
||||
return Flag::None | Flag(0);
|
||||
}
|
||||
#endif // !Q_OS_MAC
|
||||
@@ -550,7 +550,7 @@ void Panel::initLayout() {
|
||||
}, widget()->lifetime());
|
||||
|
||||
#ifndef Q_OS_MAC
|
||||
_controls->raise();
|
||||
_controls->wrap.raise();
|
||||
#endif // !Q_OS_MAC
|
||||
}
|
||||
|
||||
@@ -628,7 +628,7 @@ void Panel::updateControlsGeometry() {
|
||||
}
|
||||
if (_fingerprint) {
|
||||
#ifndef Q_OS_MAC
|
||||
const auto controlsGeometry = _controls->geometry();
|
||||
const auto controlsGeometry = _controls->controls.geometry();
|
||||
const auto halfWidth = widget()->width() / 2;
|
||||
const auto minLeft = (controlsGeometry.center().x() < halfWidth)
|
||||
? (controlsGeometry.width() + st::callFingerprintTop)
|
||||
|
||||
@@ -10,6 +10,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "base/weak_ptr.h"
|
||||
#include "base/timer.h"
|
||||
#include "base/object_ptr.h"
|
||||
#include "base/unique_qptr.h"
|
||||
#include "calls/calls_call.h"
|
||||
#include "calls/group/ui/desktop_capture_choose_source.h"
|
||||
#include "ui/effects/animations.h"
|
||||
@@ -37,7 +38,7 @@ namespace GL {
|
||||
enum class Backend;
|
||||
} // namespace GL
|
||||
namespace Platform {
|
||||
class TitleControls;
|
||||
struct SeparateTitleControls;
|
||||
} // namespace Platform
|
||||
} // namespace Ui
|
||||
|
||||
@@ -126,7 +127,7 @@ private:
|
||||
std::unique_ptr<Incoming> _incoming;
|
||||
|
||||
#ifndef Q_OS_MAC
|
||||
std::unique_ptr<Ui::Platform::TitleControls> _controls;
|
||||
std::unique_ptr<Ui::Platform::SeparateTitleControls> _controls;
|
||||
#endif // !Q_OS_MAC
|
||||
|
||||
QSize _incomingFrameSize;
|
||||
|
||||
@@ -88,8 +88,8 @@ Panel::Panel(not_null<GroupCall*> call)
|
||||
, _peer(call->peer())
|
||||
, _layerBg(std::make_unique<Ui::LayerManager>(widget()))
|
||||
#ifndef Q_OS_MAC
|
||||
, _controls(std::make_unique<Ui::Platform::TitleControls>(
|
||||
widget(),
|
||||
, _controls(Ui::Platform::SetupSeparateTitleControls(
|
||||
window(),
|
||||
st::groupCallTitle))
|
||||
#endif // !Q_OS_MAC
|
||||
, _viewport(
|
||||
@@ -302,7 +302,7 @@ void Panel::initWidget() {
|
||||
updateControlsGeometry();
|
||||
}
|
||||
|
||||
// title geometry depends on _controls->geometry,
|
||||
// title geometry depends on _controls->controls.geometry,
|
||||
// which is not updated here yet.
|
||||
crl::on_main(widget(), [=] { refreshTitle(); });
|
||||
}, lifetime());
|
||||
@@ -1368,7 +1368,7 @@ void Panel::initLayout() {
|
||||
initGeometry();
|
||||
|
||||
#ifndef Q_OS_MAC
|
||||
_controls->raise();
|
||||
_controls->wrap.raise();
|
||||
|
||||
Ui::Platform::TitleControlsLayoutChanged(
|
||||
) | rpl::start_with_next([=] {
|
||||
@@ -1413,7 +1413,7 @@ QRect Panel::computeTitleRect() const {
|
||||
#ifdef Q_OS_MAC
|
||||
return QRect(70, 0, width - remove - 70, 28);
|
||||
#else // Q_OS_MAC
|
||||
const auto controls = _controls->geometry();
|
||||
const auto controls = _controls->controls.geometry();
|
||||
const auto right = controls.x() + controls.width() + skip;
|
||||
return (controls.center().x() < width / 2)
|
||||
? QRect(right, 0, width - right - remove, controls.height())
|
||||
@@ -1884,7 +1884,8 @@ void Panel::updateControlsGeometry() {
|
||||
#ifdef Q_OS_MAC
|
||||
const auto controlsOnTheLeft = true;
|
||||
#else // Q_OS_MAC
|
||||
const auto controlsOnTheLeft = _controls->geometry().center().x()
|
||||
const auto center = _controls->controls.geometry().center();
|
||||
const auto controlsOnTheLeft = center.x()
|
||||
< widget()->width() / 2;
|
||||
#endif // Q_OS_MAC
|
||||
const auto menux = st::groupCallMenuTogglePosition.x();
|
||||
|
||||
@@ -11,6 +11,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "base/timer.h"
|
||||
#include "base/flags.h"
|
||||
#include "base/object_ptr.h"
|
||||
#include "base/unique_qptr.h"
|
||||
#include "calls/group/calls_group_call.h"
|
||||
#include "calls/group/calls_group_common.h"
|
||||
#include "calls/group/calls_choose_join_as.h"
|
||||
@@ -51,7 +52,7 @@ namespace Toast {
|
||||
class Instance;
|
||||
} // namespace Toast
|
||||
namespace Platform {
|
||||
class TitleControls;
|
||||
struct SeparateTitleControls;
|
||||
} // namespace Platform
|
||||
} // namespace Ui
|
||||
|
||||
@@ -194,7 +195,7 @@ private:
|
||||
rpl::variable<PanelMode> _mode;
|
||||
|
||||
#ifndef Q_OS_MAC
|
||||
std::unique_ptr<Ui::Platform::TitleControls> _controls;
|
||||
std::unique_ptr<Ui::Platform::SeparateTitleControls> _controls;
|
||||
#endif // !Q_OS_MAC
|
||||
|
||||
rpl::lifetime _callLifetime;
|
||||
|
||||
@@ -72,6 +72,12 @@ std::map<int, const char*> BetaLogs() {
|
||||
"- Fix crash in monospace blocks processing.\n"
|
||||
|
||||
"- Fix reaction animations stopping after an hour uptime.\n"
|
||||
},
|
||||
{
|
||||
3004006,
|
||||
"- Add snap layouts support on Windows 11.\n"
|
||||
|
||||
"- Fix crash in drafts after accounts switching.\n"
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
@@ -10,8 +10,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "ui/basic_click_handlers.h"
|
||||
|
||||
constexpr auto kPeerLinkPeerIdProperty = 0x01;
|
||||
constexpr auto kPhotoLinkMediaIdProperty = 0x02;
|
||||
constexpr auto kDocumentLinkMediaIdProperty = 0x03;
|
||||
constexpr auto kPhotoLinkMediaProperty = 0x02;
|
||||
constexpr auto kDocumentLinkMediaProperty = 0x03;
|
||||
constexpr auto kSendReactionEmojiProperty = 0x04;
|
||||
constexpr auto kReactionsCountEmojiProperty = 0x05;
|
||||
|
||||
|
||||
@@ -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 = 3004005;
|
||||
constexpr auto AppVersionStr = "3.4.5";
|
||||
constexpr auto AppVersion = 3004006;
|
||||
constexpr auto AppVersionStr = "3.4.6";
|
||||
constexpr auto AppBetaVersion = true;
|
||||
constexpr auto AppAlphaVersion = TDESKTOP_ALPHA_VERSION;
|
||||
|
||||
@@ -46,8 +46,8 @@ DocumentClickHandler::DocumentClickHandler(
|
||||
: FileClickHandler(context)
|
||||
, _document(document) {
|
||||
setProperty(
|
||||
kDocumentLinkMediaIdProperty,
|
||||
QVariant(qulonglong(_document->id)));
|
||||
kDocumentLinkMediaProperty,
|
||||
reinterpret_cast<qulonglong>(_document.get()));
|
||||
}
|
||||
|
||||
DocumentOpenClickHandler::DocumentOpenClickHandler(
|
||||
@@ -150,7 +150,9 @@ PhotoClickHandler::PhotoClickHandler(
|
||||
: FileClickHandler(context)
|
||||
, _photo(photo)
|
||||
, _peer(peer) {
|
||||
setProperty(kPhotoLinkMediaIdProperty, QVariant(qulonglong(_photo->id)));
|
||||
setProperty(
|
||||
kPhotoLinkMediaProperty,
|
||||
reinterpret_cast<qulonglong>(_photo.get()));
|
||||
}
|
||||
|
||||
not_null<PhotoData*> PhotoClickHandler::photo() const {
|
||||
|
||||
@@ -500,7 +500,8 @@ void MessageReactions::add(const QString &reaction) {
|
||||
_chosen = reaction;
|
||||
if (!reaction.isEmpty()) {
|
||||
if (_item->canViewReactions()) {
|
||||
_recent[reaction].push_back(self);
|
||||
auto &list = _recent[reaction];
|
||||
list.insert(begin(list), self);
|
||||
}
|
||||
++_list[reaction];
|
||||
}
|
||||
|
||||
@@ -46,7 +46,7 @@ namespace {
|
||||
.entities = (history->unreadCount() > 0)
|
||||
? EntitiesInText{
|
||||
{ EntityType::Semibold, 0, int(name.size()), QString() },
|
||||
{ EntityType::CustomUrl, 0, int(name.size()), QString() },
|
||||
{ EntityType::PlainLink, 0, int(name.size()), QString() },
|
||||
}
|
||||
: EntitiesInText{}
|
||||
};
|
||||
@@ -293,7 +293,8 @@ void Row::validateListEntryCache() const {
|
||||
_listEntryCache.setMarkedText(
|
||||
st::dialogsTextStyle,
|
||||
ComposeFolderListEntryText(folder),
|
||||
Ui::DialogTextOptions());
|
||||
// Use rich options as long as the entry text does not have user text.
|
||||
Ui::ItemTextDefaultOptions());
|
||||
}
|
||||
|
||||
FakeRow::FakeRow(Key searchInChat, not_null<HistoryItem*> item)
|
||||
|
||||
@@ -1169,17 +1169,13 @@ void InnerWidget::showContextMenu(QContextMenuEvent *e, bool showFromTouch) {
|
||||
auto view = App::hoveredItem()
|
||||
? App::hoveredItem()
|
||||
: App::hoveredLinkItem();
|
||||
const auto lnkPhotoId = PhotoId(link
|
||||
? link->property(kPhotoLinkMediaIdProperty).toULongLong()
|
||||
: 0);
|
||||
const auto lnkDocumentId = DocumentId(link
|
||||
? link->property(kDocumentLinkMediaIdProperty).toULongLong()
|
||||
: 0);
|
||||
const auto lnkPhoto = lnkPhotoId
|
||||
? session().data().photo(lnkPhotoId).get()
|
||||
const auto lnkPhoto = link
|
||||
? reinterpret_cast<PhotoData*>(
|
||||
link->property(kPhotoLinkMediaProperty).toULongLong())
|
||||
: nullptr;
|
||||
const auto lnkDocument = lnkDocumentId
|
||||
? session().data().document(lnkDocumentId).get()
|
||||
const auto lnkDocument = link
|
||||
? reinterpret_cast<DocumentData*>(
|
||||
link->property(kDocumentLinkMediaProperty).toULongLong())
|
||||
: nullptr;
|
||||
auto lnkIsVideo = lnkDocument ? lnkDocument->isVideoFile() : false;
|
||||
auto lnkIsVoice = lnkDocument ? lnkDocument->isVoiceMessage() : false;
|
||||
|
||||
@@ -1852,7 +1852,10 @@ void HistoryInner::showContextMenu(QContextMenuEvent *e, bool showFromTouch) {
|
||||
const auto link = ClickHandler::getActive();
|
||||
if (link
|
||||
&& !link->property(kSendReactionEmojiProperty).toString().isEmpty()
|
||||
&& _reactionsManager->showContextMenu(this, e)) {
|
||||
&& _reactionsManager->showContextMenu(
|
||||
this,
|
||||
e,
|
||||
session().data().reactions().favorite())) {
|
||||
return;
|
||||
}
|
||||
auto selectedState = getSelectionState();
|
||||
@@ -2066,13 +2069,15 @@ void HistoryInner::showContextMenu(QContextMenuEvent *e, bool showFromTouch) {
|
||||
}
|
||||
};
|
||||
|
||||
const auto lnkPhotoId = PhotoId(link
|
||||
? link->property(kPhotoLinkMediaIdProperty).toULongLong()
|
||||
: 0);
|
||||
const auto lnkDocumentId = DocumentId(link
|
||||
? link->property(kDocumentLinkMediaIdProperty).toULongLong()
|
||||
: 0);
|
||||
if (lnkPhotoId || lnkDocumentId) {
|
||||
const auto lnkPhoto = link
|
||||
? reinterpret_cast<PhotoData*>(
|
||||
link->property(kPhotoLinkMediaProperty).toULongLong())
|
||||
: nullptr;
|
||||
const auto lnkDocument = link
|
||||
? reinterpret_cast<DocumentData*>(
|
||||
link->property(kDocumentLinkMediaProperty).toULongLong())
|
||||
: nullptr;
|
||||
if (lnkPhoto || lnkDocument) {
|
||||
const auto item = _dragStateItem;
|
||||
const auto itemId = item ? item->fullId() : FullMsgId();
|
||||
if (isUponSelected > 0 && !hasCopyRestrictionForSelected()) {
|
||||
@@ -2084,10 +2089,10 @@ void HistoryInner::showContextMenu(QContextMenuEvent *e, bool showFromTouch) {
|
||||
&st::menuIconCopy);
|
||||
}
|
||||
addItemActions(item, item);
|
||||
if (lnkPhotoId) {
|
||||
addPhotoActions(session->data().photo(lnkPhotoId), item);
|
||||
if (lnkPhoto) {
|
||||
addPhotoActions(lnkPhoto, item);
|
||||
} else {
|
||||
addDocumentActions(session->data().document(lnkDocumentId), item);
|
||||
addDocumentActions(lnkDocument, item);
|
||||
}
|
||||
if (item && item->hasDirectLink() && isUponSelected != 2 && isUponSelected != -2) {
|
||||
_menu->addAction(item->history()->peer->isMegagroup() ? tr::lng_context_copy_message_link(tr::now) : tr::lng_context_copy_post_link(tr::now), [=] {
|
||||
|
||||
@@ -794,11 +794,17 @@ void HistoryItem::addReaction(const QString &reaction) {
|
||||
void HistoryItem::toggleReaction(const QString &reaction) {
|
||||
if (!_reactions) {
|
||||
_reactions = std::make_unique<Data::MessageReactions>(this);
|
||||
const auto canViewReactions = !isDiscussionPost()
|
||||
&& (history()->peer->isChat() || history()->peer->isMegagroup());
|
||||
if (canViewReactions) {
|
||||
_flags |= MessageFlag::CanViewReactions;
|
||||
}
|
||||
_reactions->add(reaction);
|
||||
} else if (_reactions->chosen() == reaction) {
|
||||
_reactions->remove();
|
||||
if (_reactions->empty()) {
|
||||
_reactions = nullptr;
|
||||
_flags &= ~MessageFlag::CanViewReactions;
|
||||
history()->owner().notifyItemDataChange(this);
|
||||
}
|
||||
} else {
|
||||
|
||||
@@ -2030,7 +2030,6 @@ void HistoryWidget::showHistory(
|
||||
_membersDropdown.destroy();
|
||||
_scrollToAnimation.stop();
|
||||
|
||||
clearAllLoadRequests();
|
||||
setHistory(nullptr);
|
||||
_list = nullptr;
|
||||
_peer = nullptr;
|
||||
@@ -2241,10 +2240,6 @@ void HistoryWidget::setHistory(History *history) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto wasHistory = base::take(_history);
|
||||
const auto wasMigrated = base::take(_migrated);
|
||||
|
||||
// Unload lottie animations.
|
||||
const auto unloadHeavyViewParts = [](History *history) {
|
||||
if (history) {
|
||||
history->owner().unloadHeavyViewParts(
|
||||
@@ -2252,13 +2247,19 @@ void HistoryWidget::setHistory(History *history) {
|
||||
history->forceFullResize();
|
||||
}
|
||||
};
|
||||
unloadHeavyViewParts(wasHistory);
|
||||
unloadHeavyViewParts(wasMigrated);
|
||||
|
||||
unregisterDraftSources();
|
||||
_history = history;
|
||||
_migrated = _history ? _history->migrateFrom() : nullptr;
|
||||
registerDraftSource();
|
||||
if (_history) {
|
||||
unregisterDraftSources();
|
||||
clearAllLoadRequests();
|
||||
const auto wasHistory = base::take(_history);
|
||||
const auto wasMigrated = base::take(_migrated);
|
||||
unloadHeavyViewParts(wasHistory);
|
||||
unloadHeavyViewParts(wasMigrated);
|
||||
}
|
||||
if (history) {
|
||||
_history = history;
|
||||
_migrated = _history ? _history->migrateFrom() : nullptr;
|
||||
registerDraftSource();
|
||||
}
|
||||
}
|
||||
|
||||
void HistoryWidget::unregisterDraftSources() {
|
||||
@@ -7412,6 +7413,7 @@ HistoryWidget::~HistoryWidget() {
|
||||
session().api().saveDraftToCloudDelayed(_history);
|
||||
|
||||
clearAllLoadRequests();
|
||||
setHistory(nullptr);
|
||||
unregisterDraftSources();
|
||||
}
|
||||
setTabbedPanel(nullptr);
|
||||
|
||||
@@ -923,17 +923,13 @@ base::unique_qptr<Ui::PopupMenu> FillContextMenu(
|
||||
const auto view = request.view;
|
||||
const auto item = request.item;
|
||||
const auto itemId = item ? item->fullId() : FullMsgId();
|
||||
const auto lnkPhotoId = PhotoId(link
|
||||
? link->property(kPhotoLinkMediaIdProperty).toULongLong()
|
||||
: 0);
|
||||
const auto lnkDocumentId = DocumentId(link
|
||||
? link->property(kDocumentLinkMediaIdProperty).toULongLong()
|
||||
: 0);
|
||||
const auto photo = lnkPhotoId
|
||||
? list->session().data().photo(lnkPhotoId).get()
|
||||
const auto lnkPhoto = link
|
||||
? reinterpret_cast<PhotoData*>(
|
||||
link->property(kPhotoLinkMediaProperty).toULongLong())
|
||||
: nullptr;
|
||||
const auto document = lnkDocumentId
|
||||
? list->session().data().document(lnkDocumentId).get()
|
||||
const auto lnkDocument = link
|
||||
? reinterpret_cast<DocumentData*>(
|
||||
link->property(kDocumentLinkMediaProperty).toULongLong())
|
||||
: nullptr;
|
||||
const auto poll = item
|
||||
? (item->media() ? item->media()->poll() : nullptr)
|
||||
@@ -958,10 +954,10 @@ base::unique_qptr<Ui::PopupMenu> FillContextMenu(
|
||||
}
|
||||
|
||||
AddTopMessageActions(result, request, list);
|
||||
if (photo) {
|
||||
AddPhotoActions(result, photo, item, list);
|
||||
} else if (document) {
|
||||
AddDocumentActions(result, document, item, list);
|
||||
if (lnkPhoto) {
|
||||
AddPhotoActions(result, lnkPhoto, item, list);
|
||||
} else if (lnkDocument) {
|
||||
AddDocumentActions(result, lnkDocument, item, list);
|
||||
} else if (poll) {
|
||||
AddPollActions(result, poll, item, list->elementContext());
|
||||
} else if (!request.overSelection && view && !hasSelection) {
|
||||
@@ -1082,6 +1078,7 @@ void AddWhoReactedAction(
|
||||
not_null<QWidget*> context,
|
||||
not_null<HistoryItem*> item,
|
||||
not_null<Window::SessionController*> controller) {
|
||||
const auto whoReadIds = std::make_shared<Api::WhoReadList>();
|
||||
const auto participantChosen = [=](uint64 id) {
|
||||
controller->showPeerInfo(PeerId(id));
|
||||
};
|
||||
@@ -1095,7 +1092,8 @@ void AddWhoReactedAction(
|
||||
controller->window().show(ReactionsListBox(
|
||||
controller,
|
||||
item,
|
||||
QString()));
|
||||
QString(),
|
||||
whoReadIds));
|
||||
}
|
||||
};
|
||||
if (!menu->empty()) {
|
||||
@@ -1103,7 +1101,7 @@ void AddWhoReactedAction(
|
||||
}
|
||||
menu->addAction(Ui::WhoReactedContextAction(
|
||||
menu.get(),
|
||||
Api::WhoReacted(item, context, st::defaultWhoRead),
|
||||
Api::WhoReacted(item, context, st::defaultWhoRead, whoReadIds),
|
||||
participantChosen,
|
||||
showAllChosen));
|
||||
}
|
||||
@@ -1130,10 +1128,8 @@ void ShowWhoReactedMenu(
|
||||
const auto reactions = &controller->session().data().reactions();
|
||||
const auto &list = reactions->list(
|
||||
Data::Reactions::Type::Active);
|
||||
const auto active = ranges::contains(
|
||||
list,
|
||||
emoji,
|
||||
&Data::Reaction::emoji);
|
||||
const auto activeNonQuick = (emoji != reactions->favorite())
|
||||
&& ranges::contains(list, emoji, &Data::Reaction::emoji);
|
||||
const auto filler = lifetime.make_state<Ui::WhoReactedListMenu>(
|
||||
participantChosen,
|
||||
showAllChosen);
|
||||
@@ -1147,7 +1143,7 @@ void ShowWhoReactedMenu(
|
||||
}) | rpl::start_with_next([=, &lifetime](Ui::WhoReadContent &&content) {
|
||||
const auto creating = !*menu;
|
||||
const auto refill = [=] {
|
||||
if (active) {
|
||||
if (activeNonQuick) {
|
||||
(*menu)->addAction(tr::lng_context_set_as_quick(tr::now), [=] {
|
||||
reactions->setFavorite(emoji);
|
||||
}, &st::menuIconFave);
|
||||
|
||||
@@ -1025,6 +1025,10 @@ Reactions::ButtonParameters Element::reactionButtonParameters(
|
||||
return {};
|
||||
}
|
||||
|
||||
int Element::reactionsOptimalWidth() const {
|
||||
return 0;
|
||||
}
|
||||
|
||||
void Element::clickHandlerActiveChanged(
|
||||
const ClickHandlerPtr &handler,
|
||||
bool active) {
|
||||
|
||||
@@ -335,6 +335,7 @@ public:
|
||||
[[nodiscard]] virtual auto reactionButtonParameters(
|
||||
QPoint position,
|
||||
const TextState &reactionState) const -> Reactions::ButtonParameters;
|
||||
[[nodiscard]] virtual int reactionsOptimalWidth() const;
|
||||
|
||||
// ClickHandlerHost interface.
|
||||
void clickHandlerActiveChanged(
|
||||
|
||||
@@ -2096,7 +2096,10 @@ void ListWidget::showContextMenu(QContextMenuEvent *e, bool showFromTouch) {
|
||||
const auto link = ClickHandler::getActive();
|
||||
if (link
|
||||
&& !link->property(kSendReactionEmojiProperty).toString().isEmpty()
|
||||
&& _reactionsManager->showContextMenu(this, e)) {
|
||||
&& _reactionsManager->showContextMenu(
|
||||
this,
|
||||
e,
|
||||
session().data().reactions().favorite())) {
|
||||
return;
|
||||
}
|
||||
const auto overItem = _overItemExact
|
||||
|
||||
@@ -2019,6 +2019,10 @@ Reactions::ButtonParameters Message::reactionButtonParameters(
|
||||
return result;
|
||||
}
|
||||
|
||||
int Message::reactionsOptimalWidth() const {
|
||||
return _reactions ? _reactions->countNiceWidth() : 0;
|
||||
}
|
||||
|
||||
void Message::drawInfo(
|
||||
Painter &p,
|
||||
const PaintContext &context,
|
||||
|
||||
@@ -91,6 +91,7 @@ public:
|
||||
Reactions::ButtonParameters reactionButtonParameters(
|
||||
QPoint position,
|
||||
const TextState &reactionState) const override;
|
||||
int reactionsOptimalWidth() const override;
|
||||
|
||||
bool hasHeavyPart() const override;
|
||||
void unloadHeavyPart() override;
|
||||
|
||||
@@ -1553,19 +1553,26 @@ void Manager::recordCurrentReactionEffect(FullMsgId itemId, QPoint origin) {
|
||||
}
|
||||
}
|
||||
|
||||
bool Manager::showContextMenu(QWidget *parent, QContextMenuEvent *e) {
|
||||
bool Manager::showContextMenu(
|
||||
QWidget *parent,
|
||||
QContextMenuEvent *e,
|
||||
const QString &favorite) {
|
||||
if (_icons.empty() || _selectedIcon < 0) {
|
||||
return false;
|
||||
}
|
||||
const auto lookupSelectedEmoji = [&] {
|
||||
const auto i = ranges::find(_icons, true, &ReactionIcons::selected);
|
||||
return (i != end(_icons)) ? (*i)->emoji : QString();
|
||||
};
|
||||
if (!favorite.isEmpty() && lookupSelectedEmoji() == favorite) {
|
||||
return true;
|
||||
}
|
||||
_menu = base::make_unique_q<Ui::PopupMenu>(
|
||||
parent,
|
||||
st::popupMenuWithIcons);
|
||||
const auto callback = [=] {
|
||||
for (const auto &icon : _icons) {
|
||||
if (icon->selected) {
|
||||
_faveRequests.fire_copy(icon->emoji);
|
||||
return;
|
||||
}
|
||||
if (const auto emoji = lookupSelectedEmoji(); !emoji.isEmpty()) {
|
||||
_faveRequests.fire_copy(emoji);
|
||||
}
|
||||
};
|
||||
_menu->addAction(
|
||||
|
||||
@@ -177,7 +177,10 @@ public:
|
||||
-> not_null<Ui::ReactionEffectPainter*>;
|
||||
void recordCurrentReactionEffect(FullMsgId itemId, QPoint origin);
|
||||
|
||||
bool showContextMenu(QWidget *parent, QContextMenuEvent *e);
|
||||
bool showContextMenu(
|
||||
QWidget *parent,
|
||||
QContextMenuEvent *e,
|
||||
const QString &favorite);
|
||||
[[nodiscard]] rpl::producer<QString> faveRequests() const;
|
||||
|
||||
[[nodiscard]] rpl::lifetime &lifetime() {
|
||||
|
||||
@@ -26,6 +26,7 @@ namespace {
|
||||
constexpr auto kInNonChosenOpacity = 0.12;
|
||||
constexpr auto kOutNonChosenOpacity = 0.18;
|
||||
constexpr auto kMaxRecentUserpics = 3;
|
||||
constexpr auto kMaxNicePerRow = 5;
|
||||
|
||||
[[nodiscard]] QColor AdaptChosenServiceFg(QColor serviceBg) {
|
||||
serviceBg.setAlpha(std::max(serviceBg.alpha(), 192));
|
||||
@@ -128,7 +129,7 @@ void InlineList::setButtonUserpics(
|
||||
if (!button.userpics) {
|
||||
button.userpics = std::make_unique<Userpics>();
|
||||
}
|
||||
const auto count = int(users.size());
|
||||
const auto count = button.count = int(users.size());
|
||||
auto &list = button.userpics->list;
|
||||
const auto regenerate = [&] {
|
||||
if (list.size() != count) {
|
||||
@@ -200,6 +201,7 @@ QSize InlineList::countOptimalSize() {
|
||||
}
|
||||
|
||||
QSize InlineList::countCurrentSize(int newWidth) {
|
||||
_data.flags &= ~Data::Flag::Flipped;
|
||||
if (_buttons.empty()) {
|
||||
return optimalSize();
|
||||
}
|
||||
@@ -225,7 +227,27 @@ QSize InlineList::countCurrentSize(int newWidth) {
|
||||
return { newWidth, height + add };
|
||||
}
|
||||
|
||||
int InlineList::countNiceWidth() const {
|
||||
const auto count = _data.reactions.size();
|
||||
const auto rows = (count + kMaxNicePerRow - 1) / kMaxNicePerRow;
|
||||
const auto columns = (count + rows - 1) / rows;
|
||||
const auto between = st::reactionInlineBetween;
|
||||
auto result = 0;
|
||||
auto inrow = 0;
|
||||
auto x = 0;
|
||||
for (auto &button : _buttons) {
|
||||
if (inrow++ >= columns) {
|
||||
x = 0;
|
||||
inrow = 0;
|
||||
}
|
||||
x += button.geometry.width() + between;
|
||||
accumulate_max(result, x - between);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
void InlineList::flipToRight() {
|
||||
_data.flags |= Data::Flag::Flipped;
|
||||
for (auto &button : _buttons) {
|
||||
button.geometry.moveLeft(
|
||||
width() - button.geometry.x() - button.geometry.width());
|
||||
@@ -254,6 +276,7 @@ void InlineList::paint(
|
||||
const auto animated = (_animation && context.reactionEffects)
|
||||
? _animation->playingAroundEmoji()
|
||||
: QString();
|
||||
const auto flipped = (_data.flags & Data::Flag::Flipped);
|
||||
if (_animation && context.reactionEffects && animated.isEmpty()) {
|
||||
_animation = nullptr;
|
||||
}
|
||||
@@ -295,7 +318,12 @@ void InlineList::paint(
|
||||
p.setBrush(chosen ? st->msgServiceFg() : st->msgServiceBg());
|
||||
}
|
||||
const auto radius = geometry.height() / 2.;
|
||||
const auto fill = geometry.marginsAdded({ 0, 0, bubbleSkip, 0 });
|
||||
const auto fill = geometry.marginsAdded({
|
||||
flipped ? bubbleSkip : 0,
|
||||
0,
|
||||
flipped ? 0 : bubbleSkip,
|
||||
0,
|
||||
});
|
||||
p.drawRoundedRect(fill, radius, radius);
|
||||
if (inbubble && !chosen) {
|
||||
p.setOpacity(bubbleProgress);
|
||||
@@ -321,7 +349,7 @@ void InlineList::paint(
|
||||
continue;
|
||||
}
|
||||
resolveUserpicsImage(button);
|
||||
const auto left = inner.x() + bubbleSkip;
|
||||
const auto left = inner.x() + (flipped ? 0 : bubbleSkip);
|
||||
if (button.userpics) {
|
||||
p.drawImage(
|
||||
left + size + st::reactionInlineUserpicsPadding.left(),
|
||||
|
||||
@@ -34,6 +34,7 @@ struct InlineListData {
|
||||
enum class Flag : uchar {
|
||||
InBubble = 0x01,
|
||||
OutLayout = 0x02,
|
||||
Flipped = 0x04,
|
||||
};
|
||||
friend inline constexpr bool is_flag_type(Flag) { return true; };
|
||||
using Flags = base::flags<Flag>;
|
||||
@@ -55,6 +56,7 @@ public:
|
||||
|
||||
void update(Data &&data, int availableWidth);
|
||||
QSize countCurrentSize(int newWidth) override;
|
||||
[[nodiscard]] int countNiceWidth() const;
|
||||
[[nodiscard]] int placeAndResizeGetHeight(QRect available);
|
||||
void flipToRight();
|
||||
|
||||
|
||||
@@ -180,6 +180,7 @@ QSize Gif::countOptimalSize() {
|
||||
forwarded->create(via);
|
||||
}
|
||||
maxWidth += additionalWidth(via, reply, forwarded);
|
||||
accumulate_max(maxWidth, _parent->reactionsOptimalWidth());
|
||||
}
|
||||
return { maxWidth, minHeight };
|
||||
}
|
||||
@@ -232,6 +233,8 @@ QSize Gif::countCurrentSize(int newWidth) {
|
||||
}
|
||||
}
|
||||
} else if (isSeparateRoundVideo()) {
|
||||
accumulate_max(newWidth, _parent->reactionsOptimalWidth());
|
||||
|
||||
const auto item = _parent->data();
|
||||
auto via = item->Get<HistoryMessageVia>();
|
||||
auto reply = _parent->displayedReply();
|
||||
@@ -1191,9 +1194,10 @@ QRect Gif::contentRectForReactions() const {
|
||||
const auto forwarded = item->Get<HistoryMessageForwarded>();
|
||||
if (via || reply || forwarded) {
|
||||
usew = maxWidth() - additionalWidth(via, reply, forwarded);
|
||||
if (rightAligned) {
|
||||
usex = width() - usew;
|
||||
}
|
||||
}
|
||||
accumulate_max(usew, _parent->reactionsOptimalWidth());
|
||||
if (rightAligned) {
|
||||
usex = width() - usew;
|
||||
}
|
||||
if (rtl()) usex = width() - usex - usew;
|
||||
return style::rtlrect(usex + paintx, painty, usew, painth, width());
|
||||
|
||||
@@ -57,6 +57,7 @@ QSize UnwrappedMedia::countOptimalSize() {
|
||||
}
|
||||
const auto additional = additionalWidth(via, reply, forwarded);
|
||||
maxWidth += additional;
|
||||
accumulate_max(maxWidth, _parent->reactionsOptimalWidth());
|
||||
if (const auto surrounding = surroundingInfo(via, reply, forwarded, additional - st::msgReplyPadding.left())) {
|
||||
const auto infoHeight = st::msgDateImgPadding.y() * 2
|
||||
+ st::msgDateFont->height;
|
||||
@@ -77,6 +78,7 @@ QSize UnwrappedMedia::countOptimalSize() {
|
||||
QSize UnwrappedMedia::countCurrentSize(int newWidth) {
|
||||
const auto item = _parent->data();
|
||||
accumulate_min(newWidth, maxWidth());
|
||||
accumulate_max(newWidth, _parent->reactionsOptimalWidth());
|
||||
const auto isPageAttach = (_parent->media() != this);
|
||||
if (!isPageAttach) {
|
||||
const auto via = item->Get<HistoryMessageVia>();
|
||||
@@ -416,9 +418,10 @@ QRect UnwrappedMedia::contentRectForReactions() const {
|
||||
auto usew = maxWidth();
|
||||
if (!inWebPage) {
|
||||
usew -= additionalWidth(via, reply, forwarded);
|
||||
if (rightAligned) {
|
||||
usex = width() - usew;
|
||||
}
|
||||
}
|
||||
accumulate_max(usew, _parent->reactionsOptimalWidth());
|
||||
if (rightAligned) {
|
||||
usex = width() - usew;
|
||||
}
|
||||
if (rtl()) {
|
||||
usex = width() - usex - usew;
|
||||
|
||||
@@ -135,7 +135,7 @@ private:
|
||||
const PaintContext &context,
|
||||
QPoint photoPosition) const;
|
||||
|
||||
not_null<PhotoData*> _data;
|
||||
const not_null<PhotoData*> _data;
|
||||
int _serviceWidth = 0;
|
||||
int _pixw = 1;
|
||||
int _pixh = 1;
|
||||
|
||||
@@ -13,6 +13,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "window/window_session_controller.h"
|
||||
#include "history/history_item.h"
|
||||
#include "history/history.h"
|
||||
#include "api/api_who_reacted.h"
|
||||
#include "ui/controls/who_reacted_context_action.h"
|
||||
#include "main/main_session.h"
|
||||
#include "data/data_session.h"
|
||||
#include "data/data_user.h"
|
||||
@@ -50,7 +52,8 @@ public:
|
||||
not_null<Window::SessionController*> window,
|
||||
not_null<HistoryItem*> item,
|
||||
const QString &selected,
|
||||
rpl::producer<QString> switches);
|
||||
rpl::producer<QString> switches,
|
||||
std::shared_ptr<Api::WhoReadList> whoReadIds);
|
||||
|
||||
Main::Session &session() const override;
|
||||
void prepare() override;
|
||||
@@ -60,7 +63,8 @@ public:
|
||||
private:
|
||||
using AllEntry = std::pair<not_null<UserData*>, QString>;
|
||||
|
||||
void loadMore(const QString &offset);
|
||||
void fillWhoRead();
|
||||
void loadMore(const QString &reaction);
|
||||
bool appendRow(not_null<UserData*> user, QString reaction);
|
||||
std::unique_ptr<PeerListRow> createRow(
|
||||
not_null<UserData*> user,
|
||||
@@ -72,6 +76,8 @@ private:
|
||||
MTP::Sender _api;
|
||||
|
||||
QString _shownReaction;
|
||||
std::shared_ptr<Api::WhoReadList> _whoReadIds;
|
||||
std::vector<not_null<UserData*>> _whoRead;
|
||||
|
||||
std::vector<AllEntry> _all;
|
||||
QString _allOffset;
|
||||
@@ -127,11 +133,13 @@ Controller::Controller(
|
||||
not_null<Window::SessionController*> window,
|
||||
not_null<HistoryItem*> item,
|
||||
const QString &selected,
|
||||
rpl::producer<QString> switches)
|
||||
rpl::producer<QString> switches,
|
||||
std::shared_ptr<Api::WhoReadList> whoReadIds)
|
||||
: _window(window)
|
||||
, _item(item)
|
||||
, _api(&window->session().mtp())
|
||||
, _shownReaction(selected) {
|
||||
, _shownReaction(selected)
|
||||
, _whoReadIds(whoReadIds) {
|
||||
std::move(
|
||||
switches
|
||||
) | rpl::filter([=](const QString &reaction) {
|
||||
@@ -146,10 +154,14 @@ Main::Session &Controller::session() const {
|
||||
}
|
||||
|
||||
void Controller::prepare() {
|
||||
setDescriptionText(tr::lng_contacts_loading(tr::now));
|
||||
if (_shownReaction == u"read"_q) {
|
||||
fillWhoRead();
|
||||
setDescriptionText(QString());
|
||||
} else {
|
||||
setDescriptionText(tr::lng_contacts_loading(tr::now));
|
||||
}
|
||||
delegate()->peerListRefreshRows();
|
||||
|
||||
loadMore(QString());
|
||||
loadMore(_shownReaction);
|
||||
}
|
||||
|
||||
void Controller::showReaction(const QString &reaction) {
|
||||
@@ -163,7 +175,9 @@ void Controller::showReaction(const QString &reaction) {
|
||||
}
|
||||
|
||||
_shownReaction = reaction;
|
||||
if (_shownReaction.isEmpty()) {
|
||||
if (_shownReaction == u"read"_q) {
|
||||
fillWhoRead();
|
||||
} else if (_shownReaction.isEmpty()) {
|
||||
_filtered.clear();
|
||||
for (const auto &[user, reaction] : _all) {
|
||||
appendRow(user, reaction);
|
||||
@@ -177,14 +191,29 @@ void Controller::showReaction(const QString &reaction) {
|
||||
for (const auto user : _filtered) {
|
||||
appendRow(user, _shownReaction);
|
||||
}
|
||||
loadMore(QString());
|
||||
_filteredOffset = QString();
|
||||
}
|
||||
loadMore(_shownReaction);
|
||||
setDescriptionText(delegate()->peerListFullRowsCount()
|
||||
? QString()
|
||||
: tr::lng_contacts_loading(tr::now));
|
||||
delegate()->peerListRefreshRows();
|
||||
}
|
||||
|
||||
void Controller::fillWhoRead() {
|
||||
if (_whoReadIds && !_whoReadIds->list.empty() && _whoRead.empty()) {
|
||||
auto &owner = _window->session().data();
|
||||
for (const auto &peerId : _whoReadIds->list) {
|
||||
if (const auto user = owner.userLoaded(peerToUser(peerId))) {
|
||||
_whoRead.push_back(user);
|
||||
}
|
||||
}
|
||||
}
|
||||
for (const auto &user : _whoRead) {
|
||||
appendRow(user, QString());
|
||||
}
|
||||
}
|
||||
|
||||
void Controller::loadMoreRows() {
|
||||
const auto &offset = _shownReaction.isEmpty()
|
||||
? _allOffset
|
||||
@@ -192,26 +221,37 @@ void Controller::loadMoreRows() {
|
||||
if (_loadRequestId || offset.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
loadMore(offset);
|
||||
loadMore(_shownReaction);
|
||||
}
|
||||
|
||||
void Controller::loadMore(const QString &offset) {
|
||||
void Controller::loadMore(const QString &reaction) {
|
||||
if (reaction == u"read"_q) {
|
||||
loadMore(QString());
|
||||
return;
|
||||
} else if (reaction.isEmpty() && _allOffset.isEmpty() && !_all.empty()) {
|
||||
return;
|
||||
}
|
||||
_api.request(_loadRequestId).cancel();
|
||||
|
||||
const auto &offset = reaction.isEmpty()
|
||||
? _allOffset
|
||||
: _filteredOffset;
|
||||
|
||||
using Flag = MTPmessages_GetMessageReactionsList::Flag;
|
||||
const auto flags = Flag(0)
|
||||
| (offset.isEmpty() ? Flag(0) : Flag::f_offset)
|
||||
| (_shownReaction.isEmpty() ? Flag(0) : Flag::f_reaction);
|
||||
| (reaction.isEmpty() ? Flag(0) : Flag::f_reaction);
|
||||
_loadRequestId = _api.request(MTPmessages_GetMessageReactionsList(
|
||||
MTP_flags(flags),
|
||||
_item->history()->peer->input,
|
||||
MTP_int(_item->id),
|
||||
MTP_string(_shownReaction),
|
||||
MTP_string(reaction),
|
||||
MTP_string(offset),
|
||||
MTP_int(kPerPageFirst)
|
||||
)).done([=](const MTPmessages_MessageReactionsList &result) {
|
||||
_loadRequestId = 0;
|
||||
const auto filtered = !_shownReaction.isEmpty();
|
||||
const auto filtered = !reaction.isEmpty();
|
||||
const auto shown = (reaction == _shownReaction);
|
||||
result.match([&](const MTPDmessages_messageReactionsList &data) {
|
||||
const auto sessionData = &session().data();
|
||||
sessionData->processUsers(data.vusers());
|
||||
@@ -222,7 +262,7 @@ void Controller::loadMore(const QString &offset) {
|
||||
const auto user = sessionData->userLoaded(
|
||||
data.vuser_id().v);
|
||||
const auto reaction = qs(data.vreaction());
|
||||
if (user && appendRow(user, reaction)) {
|
||||
if (user && (!shown || appendRow(user, reaction))) {
|
||||
if (filtered) {
|
||||
_filtered.emplace_back(user);
|
||||
} else {
|
||||
@@ -232,8 +272,10 @@ void Controller::loadMore(const QString &offset) {
|
||||
});
|
||||
}
|
||||
});
|
||||
setDescriptionText(QString());
|
||||
delegate()->peerListRefreshRows();
|
||||
if (shown) {
|
||||
setDescriptionText(QString());
|
||||
delegate()->peerListRefreshRows();
|
||||
}
|
||||
}).send();
|
||||
}
|
||||
|
||||
@@ -264,20 +306,29 @@ std::unique_ptr<PeerListRow> Controller::createRow(
|
||||
object_ptr<Ui::BoxContent> ReactionsListBox(
|
||||
not_null<Window::SessionController*> window,
|
||||
not_null<HistoryItem*> item,
|
||||
QString selected) {
|
||||
QString selected,
|
||||
std::shared_ptr<Api::WhoReadList> whoReadIds) {
|
||||
Expects(IsServerMsgId(item->id));
|
||||
|
||||
if (!item->reactions().contains(selected)) {
|
||||
selected = QString();
|
||||
}
|
||||
if (selected.isEmpty() && whoReadIds && !whoReadIds->list.empty()) {
|
||||
selected = u"read"_q;
|
||||
}
|
||||
const auto tabRequests = std::make_shared<rpl::event_stream<QString>>();
|
||||
const auto initBox = [=](not_null<PeerListBox*> box) {
|
||||
box->setNoContentMargin(true);
|
||||
|
||||
auto map = item->reactions();
|
||||
if (whoReadIds && !whoReadIds->list.empty()) {
|
||||
map.emplace(u"read"_q, int(whoReadIds->list.size()));
|
||||
}
|
||||
const auto selector = CreateReactionSelector(
|
||||
box,
|
||||
item->reactions(),
|
||||
selected);
|
||||
map,
|
||||
selected,
|
||||
whoReadIds ? whoReadIds->type : Ui::WhoReadType::Reacted);
|
||||
selector->changes(
|
||||
) | rpl::start_to_stream(*tabRequests, box->lifetime());
|
||||
|
||||
@@ -299,7 +350,8 @@ object_ptr<Ui::BoxContent> ReactionsListBox(
|
||||
window,
|
||||
item,
|
||||
selected,
|
||||
tabRequests->events()),
|
||||
tabRequests->events(),
|
||||
whoReadIds),
|
||||
initBox);
|
||||
}
|
||||
|
||||
|
||||
@@ -11,6 +11,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
|
||||
class HistoryItem;
|
||||
|
||||
namespace Api {
|
||||
struct WhoReadList;
|
||||
} // namespace Api
|
||||
|
||||
namespace Window {
|
||||
class SessionController;
|
||||
} // namespace Window
|
||||
@@ -24,6 +28,7 @@ namespace HistoryView {
|
||||
object_ptr<Ui::BoxContent> ReactionsListBox(
|
||||
not_null<Window::SessionController*> window,
|
||||
not_null<HistoryItem*> item,
|
||||
QString selected);
|
||||
QString selected,
|
||||
std::shared_ptr<Api::WhoReadList> whoReadIds = nullptr);
|
||||
|
||||
} // namespace HistoryView
|
||||
|
||||
@@ -9,6 +9,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
|
||||
#include "ui/rp_widget.h"
|
||||
#include "ui/abstract_button.h"
|
||||
#include "ui/controls/who_reacted_context_action.h"
|
||||
#include "styles/style_widgets.h"
|
||||
#include "styles/style_chat.h"
|
||||
|
||||
@@ -19,6 +20,7 @@ not_null<Ui::AbstractButton*> CreateTab(
|
||||
not_null<QWidget*> parent,
|
||||
const style::MultiSelect &st,
|
||||
const QString &reaction,
|
||||
Ui::WhoReadType whoReadType,
|
||||
int count,
|
||||
rpl::producer<bool> selected) {
|
||||
struct State {
|
||||
@@ -71,9 +73,19 @@ not_null<Ui::AbstractButton*> CreateTab(
|
||||
const auto shift = (height - (size / factor)) / 2;
|
||||
Ui::Emoji::Draw(p, emoji, size, icon.x() + shift, shift);
|
||||
} else {
|
||||
(state->selected
|
||||
? st::reactionsTabAllSelected
|
||||
: st::reactionsTabAll).paintInCenter(p, icon);
|
||||
using Type = Ui::WhoReadType;
|
||||
(reaction.isEmpty()
|
||||
? (state->selected
|
||||
? st::reactionsTabAllSelected
|
||||
: st::reactionsTabAll)
|
||||
: (whoReadType == Type::Watched
|
||||
|| whoReadType == Type::Listened)
|
||||
? (state->selected
|
||||
? st::reactionsTabPlayedSelected
|
||||
: st::reactionsTabPlayed)
|
||||
: (state->selected
|
||||
? st::reactionsTabChecksSelected
|
||||
: st::reactionsTabChecks)).paintInCenter(p, icon);
|
||||
}
|
||||
|
||||
const auto textLeft = height + stm->padding.left();
|
||||
@@ -91,23 +103,14 @@ not_null<Ui::AbstractButton*> CreateTab(
|
||||
not_null<Selector*> CreateReactionSelector(
|
||||
not_null<QWidget*> parent,
|
||||
const base::flat_map<QString, int> &items,
|
||||
const QString &selected) {
|
||||
const QString &selected,
|
||||
Ui::WhoReadType whoReadType) {
|
||||
struct State {
|
||||
rpl::variable<QString> selected;
|
||||
std::vector<not_null<Ui::AbstractButton*>> tabs;
|
||||
};
|
||||
const auto result = Ui::CreateChild<Selector>(parent.get());
|
||||
using Entry = std::pair<int, QString>;
|
||||
auto sorted = std::vector<Entry>();
|
||||
for (const auto &[reaction, count] : items) {
|
||||
sorted.emplace_back(count, reaction);
|
||||
}
|
||||
ranges::sort(sorted, std::greater<>(), &Entry::first);
|
||||
const auto count = ranges::accumulate(
|
||||
sorted,
|
||||
0,
|
||||
std::plus<>(),
|
||||
&Entry::first);
|
||||
auto tabs = Ui::CreateChild<Ui::RpWidget>(parent.get());
|
||||
const auto st = &st::reactionsTabs;
|
||||
const auto state = tabs->lifetime().make_state<State>();
|
||||
@@ -118,6 +121,7 @@ not_null<Selector*> CreateReactionSelector(
|
||||
tabs,
|
||||
*st,
|
||||
reaction,
|
||||
whoReadType,
|
||||
count,
|
||||
state->selected.value() | rpl::map(_1 == reaction));
|
||||
tab->setClickedCallback([=] {
|
||||
@@ -125,6 +129,20 @@ not_null<Selector*> CreateReactionSelector(
|
||||
});
|
||||
state->tabs.push_back(tab);
|
||||
};
|
||||
auto sorted = std::vector<Entry>();
|
||||
for (const auto &[reaction, count] : items) {
|
||||
if (reaction == u"read"_q) {
|
||||
append(reaction, count);
|
||||
} else {
|
||||
sorted.emplace_back(count, reaction);
|
||||
}
|
||||
}
|
||||
ranges::sort(sorted, std::greater<>(), &Entry::first);
|
||||
const auto count = ranges::accumulate(
|
||||
sorted,
|
||||
0,
|
||||
std::plus<>(),
|
||||
&Entry::first);
|
||||
append(QString(), count);
|
||||
for (const auto &[count, reaction] : sorted) {
|
||||
append(reaction, count);
|
||||
|
||||
@@ -7,6 +7,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
namespace Ui {
|
||||
enum class WhoReadType;
|
||||
} // namespace Ui
|
||||
|
||||
namespace HistoryView {
|
||||
|
||||
struct Selector {
|
||||
@@ -19,6 +23,7 @@ struct Selector {
|
||||
not_null<Selector*> CreateReactionSelector(
|
||||
not_null<QWidget*> parent,
|
||||
const base::flat_map<QString, int> &items,
|
||||
const QString &selected);
|
||||
const QString &selected,
|
||||
Ui::WhoReadType whoReadType);
|
||||
|
||||
} // namespace HistoryView
|
||||
|
||||
@@ -1593,17 +1593,13 @@ void ListWidget::showContextMenu(
|
||||
},
|
||||
&st::menuIconShowInChat);
|
||||
|
||||
const auto lnkPhotoId = PhotoId(link
|
||||
? link->property(kPhotoLinkMediaIdProperty).toULongLong()
|
||||
: 0);
|
||||
const auto lnkDocumentId = DocumentId(link
|
||||
? link->property(kDocumentLinkMediaIdProperty).toULongLong()
|
||||
: 0);
|
||||
const auto lnkPhoto = lnkPhotoId
|
||||
? session().data().photo(lnkPhotoId).get()
|
||||
const auto lnkPhoto = link
|
||||
? reinterpret_cast<PhotoData*>(
|
||||
link->property(kPhotoLinkMediaProperty).toULongLong())
|
||||
: nullptr;
|
||||
const auto lnkDocument = lnkDocumentId
|
||||
? session().data().document(lnkDocumentId).get()
|
||||
const auto lnkDocument = link
|
||||
? reinterpret_cast<DocumentData*>(
|
||||
link->property(kDocumentLinkMediaProperty).toULongLong())
|
||||
: nullptr;
|
||||
if (lnkPhoto || lnkDocument) {
|
||||
auto [isVideo, isVoice, isAudio] = [&] {
|
||||
|
||||
@@ -52,21 +52,27 @@ void PreviewWindowTitle(Painter &p, const style::palette &palette, QRect body, i
|
||||
p.fillRect(titleRect, st::titleBgActive[palette]);
|
||||
p.fillRect(titleRect.x(), titleRect.y() + titleRect.height() - st::lineWidth, titleRect.width(), st::lineWidth, st::titleShadow[palette]);
|
||||
|
||||
auto useSystemFont = false;
|
||||
QFont font;
|
||||
QStringList families = { qsl(".SF NS Text"), qsl("Helvetica Neue") };
|
||||
const auto families = QStringList{
|
||||
u".AppleSystemUIFont"_q,
|
||||
u".SF NS Text"_q,
|
||||
u"Helvetica Neue"_q,
|
||||
};
|
||||
for (auto family : families) {
|
||||
font.setFamily(family);
|
||||
if (QFontInfo(font).family() == font.family()) {
|
||||
useSystemFont = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (useSystemFont) {
|
||||
font.setPixelSize((titleHeight * 15) / 24);
|
||||
if (QFontInfo(font).family() != font.family()) {
|
||||
font = st::semiboldFont;
|
||||
font.setPixelSize(13);
|
||||
} else if (font.family() == u".AppleSystemUIFont"_q) {
|
||||
font.setBold(true);
|
||||
font.setPixelSize(13);
|
||||
} else {
|
||||
font = st::normalFont;
|
||||
font.setPixelSize((titleHeight * 15) / 24);
|
||||
}
|
||||
|
||||
p.setPen(st::titleFgActive[palette]);
|
||||
|
||||
@@ -926,6 +926,10 @@ whoReadReactionsDisabled: icon{{ "menu/read_reactions", menuFgDisabled }};
|
||||
|
||||
reactionsTabAll: icon {{ "menu/read_reactions", windowFg }};
|
||||
reactionsTabAllSelected: icon {{ "menu/read_reactions", activeButtonFg }};
|
||||
reactionsTabPlayed: icon {{ "menu/read_audio", windowFg }};
|
||||
reactionsTabPlayedSelected: icon {{ "menu/read_audio", activeButtonFg }};
|
||||
reactionsTabChecks: icon {{ "menu/read_ticks", windowFg }};
|
||||
reactionsTabChecksSelected: icon {{ "menu/read_ticks", activeButtonFg }};
|
||||
reactionsTabs: MultiSelect(defaultMultiSelect) {
|
||||
padding: margins(12px, 10px, 12px, 10px);
|
||||
}
|
||||
|
||||
@@ -16,6 +16,54 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "styles/style_chat.h"
|
||||
#include "styles/style_menu_icons.h"
|
||||
|
||||
namespace Lang {
|
||||
namespace {
|
||||
|
||||
struct StringWithReacted {
|
||||
QString text;
|
||||
int seen = 0;
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
template <typename ResultString>
|
||||
struct StartReplacements;
|
||||
|
||||
template <>
|
||||
struct StartReplacements<StringWithReacted> {
|
||||
static inline StringWithReacted Call(QString &&langString) {
|
||||
return { std::move(langString) };
|
||||
}
|
||||
};
|
||||
|
||||
template <typename ResultString>
|
||||
struct ReplaceTag;
|
||||
|
||||
template <>
|
||||
struct ReplaceTag<StringWithReacted> {
|
||||
static StringWithReacted Call(
|
||||
StringWithReacted &&original,
|
||||
ushort tag,
|
||||
const StringWithReacted &replacement);
|
||||
};
|
||||
|
||||
StringWithReacted ReplaceTag<StringWithReacted>::Call(
|
||||
StringWithReacted &&original,
|
||||
ushort tag,
|
||||
const StringWithReacted &replacement) {
|
||||
const auto offset = FindTagReplacementPosition(original.text, tag);
|
||||
if (offset < 0) {
|
||||
return std::move(original);
|
||||
}
|
||||
original.text = ReplaceTag<QString>::Call(
|
||||
std::move(original.text),
|
||||
tag,
|
||||
replacement.text + '/' + QString::number(original.seen));
|
||||
return std::move(original);
|
||||
}
|
||||
|
||||
} // namespace Lang
|
||||
|
||||
namespace Ui {
|
||||
namespace {
|
||||
|
||||
@@ -80,6 +128,18 @@ TextParseOptions MenuTextOptions = {
|
||||
Qt::LayoutDirectionAuto, // dir
|
||||
};
|
||||
|
||||
[[nodiscard]] QString FormatReactedString(int reacted, int seen) {
|
||||
const auto projection = [&](const QString &text) {
|
||||
return Lang::StringWithReacted{ text, seen };
|
||||
};
|
||||
return tr::lng_context_seen_reacted(
|
||||
tr::now,
|
||||
lt_count_short,
|
||||
reacted,
|
||||
projection
|
||||
).text;
|
||||
}
|
||||
|
||||
Action::Action(
|
||||
not_null<PopupMenu*> parentMenu,
|
||||
rpl::producer<WhoReadContent> content,
|
||||
@@ -177,10 +237,12 @@ void Action::resolveMinWidth() {
|
||||
? tr::lng_context_seen_text(tr::now, lt_count, 999)
|
||||
: QString();
|
||||
const auto maxReacted = (_content.fullReactionsCount > 0)
|
||||
? tr::lng_context_seen_reacted(
|
||||
tr::now,
|
||||
lt_count_short,
|
||||
_content.fullReactionsCount)
|
||||
? (!maxText.isEmpty()
|
||||
? FormatReactedString(_content.fullReactionsCount, 999)
|
||||
: tr::lng_context_seen_reacted(
|
||||
tr::now,
|
||||
lt_count_short,
|
||||
_content.fullReactionsCount))
|
||||
: QString();
|
||||
const auto maxTextWidth = std::max(width(maxText), width(maxReacted));
|
||||
const auto maxWidth = st::defaultWhoRead.itemPadding.left()
|
||||
@@ -290,6 +352,10 @@ void Action::paint(Painter &p) {
|
||||
|
||||
void Action::refreshText() {
|
||||
const auto usersCount = int(_content.participants.size());
|
||||
const auto onlySeenCount = ranges::count(
|
||||
_content.participants,
|
||||
QString(),
|
||||
&WhoReadParticipant::reaction);
|
||||
const auto count = std::max(_content.fullReactionsCount, usersCount);
|
||||
_text.setMarkedText(
|
||||
_st.itemStyle,
|
||||
@@ -297,8 +363,14 @@ void Action::refreshText() {
|
||||
? tr::lng_context_seen_loading(tr::now)
|
||||
: (usersCount == 1)
|
||||
? _content.participants.front().name
|
||||
: (_content.fullReactionsCount > 0
|
||||
&& _content.fullReactionsCount <= _content.fullReadCount)
|
||||
? FormatReactedString(
|
||||
_content.fullReactionsCount,
|
||||
_content.fullReadCount)
|
||||
: (_content.type == WhoReadType::Reacted
|
||||
|| (count > 0 && _content.fullReactionsCount > usersCount))
|
||||
|| (count > 0 && _content.fullReactionsCount > usersCount)
|
||||
|| (count > 0 && onlySeenCount == 0))
|
||||
? (count
|
||||
? tr::lng_context_seen_reacted(
|
||||
tr::now,
|
||||
|
||||
@@ -42,6 +42,7 @@ struct WhoReadContent {
|
||||
WhoReadType type = WhoReadType::Seen;
|
||||
QString singleReaction;
|
||||
int fullReactionsCount = 0;
|
||||
int fullReadCount = 0;
|
||||
bool unknown = false;
|
||||
};
|
||||
|
||||
|
||||
@@ -351,6 +351,6 @@ macAlwaysThisAppTop: 4;
|
||||
macAppHintTop: 8;
|
||||
macCautionIconSize: 16;
|
||||
|
||||
macWindowRoundRadius: 5;
|
||||
macWindowRoundRadius: 10;
|
||||
macWindowShadowTopLeft: icon {{ "mac_window_shadow_top_left", windowShadowFg }};
|
||||
macTrayIcon: icon {{ "mac_tray_icon", windowFg }};
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
AppVersion 3004005
|
||||
AppVersion 3004006
|
||||
AppVersionStrMajor 3.4
|
||||
AppVersionStrSmall 3.4.5
|
||||
AppVersionStr 3.4.5
|
||||
AppVersionStrSmall 3.4.6
|
||||
AppVersionStr 3.4.6
|
||||
BetaChannel 1
|
||||
AlphaVersion 0
|
||||
AppVersionOriginal 3.4.5.beta
|
||||
AppVersionOriginal 3.4.6.beta
|
||||
|
||||
@@ -1,3 +1,8 @@
|
||||
3.4.6 beta (18.01.21)
|
||||
|
||||
- Add snap layouts support on Windows 11.
|
||||
- Fix crash in drafts after accounts switching.
|
||||
|
||||
3.4.5 beta (16.01.21)
|
||||
|
||||
- Fix crash in monospace blocks processing.
|
||||
|
||||