Compare commits

...

22 Commits

Author SHA1 Message Date
John Preston
e19180cc86 Beta version 3.4.6: Fix build with Xcode. 2022-01-18 21:56:20 +03:00
John Preston
10cb891f48 Beta version 3.4.6.
- Add snap layouts support on Windows 11.
- Fix crash in drafts after accounts switching.
2022-01-18 21:47:13 +03:00
John Preston
c8f7a8c795 Add a tab with "Who Seen" to "Who Reacted" box. 2022-01-18 21:44:59 +03:00
John Preston
74a28ffdf7 Use correct string for reacted / seen item. 2022-01-18 19:55:24 +03:00
John Preston
ecedce0c2f Fix crash in drafts saving.
Regression was introduced in 43559fb6b7.
2022-01-18 19:05:31 +03:00
John Preston
bd4f993292 Add Windows 11 snap layouts to call / voicechat. 2022-01-18 18:39:55 +03:00
John Preston
4934b026d3 Improve call / voicechat title controls on Windows 11. 2022-01-18 15:53:04 +03:00
John Preston
11f183a79f Better animate sent reaction in flipped context. 2022-01-18 14:45:28 +03:00
John Preston
ae426a41e0 Better reaction layout outside of a bubble. 2022-01-18 14:37:14 +03:00
John Preston
d6edc3728d Workaround selection glitches on macOS. 2022-01-18 14:11:15 +03:00
John Preston
e121487170 Fix appear animation when sending a reaction in a group. 2022-01-18 13:09:42 +03:00
John Preston
72a093ec77 Support Windows 11 snap layouts in the main window. 2022-01-18 12:59:54 +03:00
John Preston
4996d90782 Fix first my reaction userpic in groups. 2022-01-17 21:29:28 +03:00
John Preston
9a451a1423 Don't suggest "Set As Quick" on already quick. 2022-01-17 19:21:34 +03:00
John Preston
4d11ad45db Use common title bar buttons in Call Panel. 2022-01-17 19:21:34 +03:00
John Preston
1657c2c7f2 Fix context menu on sent images / documents. 2022-01-17 19:21:34 +03:00
John Preston
c5e7048a3d Don't copy-on-click "pre" entities. 2022-01-17 16:49:14 +03:00
John Preston
1f194da2f0 Improve macOS title bar font and rounding. 2022-01-17 16:48:32 +03:00
John Preston
0954b04f24 Fix title controls on Windows 11. 2022-01-17 13:39:14 +03:00
John Preston
4659499340 Update window title icons. 2022-01-17 11:18:12 +03:00
John Preston
6eb4584408 Fix links before monospace formatting. 2022-01-17 10:51:35 +03:00
23rd
4ba4b77b95 Fixed formatting of text in entry of archived folder.
Fixed #23906.
2022-01-17 10:12:18 +03:00
74 changed files with 574 additions and 306 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 305 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 524 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 820 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 385 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 873 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 230 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 393 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 616 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 333 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 552 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 889 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 173 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 304 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 545 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 228 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 420 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 661 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 290 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 499 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 846 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 420 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 715 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 823 B

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

After

Width:  |  Height:  |  Size: 7.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.2 KiB

After

Width:  |  Height:  |  Size: 11 KiB

View File

@@ -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>

View File

@@ -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"

View File

@@ -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"

View File

@@ -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

View File

@@ -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

View File

@@ -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;

View File

@@ -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)

View File

@@ -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;

View File

@@ -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();

View File

@@ -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;

View File

@@ -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"
}
};
};

View File

@@ -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;

View File

@@ -22,7 +22,7 @@ constexpr auto AppId = "{53F49750-6209-4FBF-9CA8-7A333C87D1ED}"_cs;
constexpr auto AppNameOld = "Telegram Win (Unofficial)"_cs;
constexpr auto AppName = "Telegram Desktop"_cs;
constexpr auto AppFile = "Telegram"_cs;
constexpr auto AppVersion = 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;

View File

@@ -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 {

View File

@@ -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];
}

View File

@@ -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)

View File

@@ -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;

View File

@@ -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), [=] {

View File

@@ -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 {

View File

@@ -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);

View File

@@ -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);

View File

@@ -1025,6 +1025,10 @@ Reactions::ButtonParameters Element::reactionButtonParameters(
return {};
}
int Element::reactionsOptimalWidth() const {
return 0;
}
void Element::clickHandlerActiveChanged(
const ClickHandlerPtr &handler,
bool active) {

View File

@@ -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(

View File

@@ -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

View File

@@ -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,

View File

@@ -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;

View File

@@ -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(

View File

@@ -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() {

View File

@@ -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(),

View File

@@ -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();

View File

@@ -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());

View File

@@ -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;

View File

@@ -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;

View File

@@ -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);
}

View File

@@ -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

View File

@@ -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);

View File

@@ -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

View File

@@ -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] = [&] {

View File

@@ -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]);

View File

@@ -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);
}

View File

@@ -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,

View File

@@ -42,6 +42,7 @@ struct WhoReadContent {
WhoReadType type = WhoReadType::Seen;
QString singleReaction;
int fullReactionsCount = 0;
int fullReadCount = 0;
bool unknown = false;
};

View File

@@ -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 }};

View File

@@ -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

View File

@@ -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.