Merge tag 'v4.14.2' into dev

# Conflicts:
#	Telegram/Resources/winrc/Telegram.rc
#	Telegram/Resources/winrc/Updater.rc
#	Telegram/SourceFiles/core/version.h
#	Telegram/lib_ui
This commit is contained in:
ZavaruKitsu 2024-01-03 15:00:00 +03:00
commit b1e91ab746
39 changed files with 193 additions and 51 deletions

View file

@ -2087,6 +2087,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_premium_gifts_about_paid_below#one" = "They now have access to additional features.";
"lng_premium_gifts_about_paid_below#other" = "They now have access to additional features.";
"lng_premium_gifts_summary_subtitle" = "What's Included";
"lng_premium_gifts_terms" = "By gifting Telegram Premium, you agree to the Telegram {link} and {policy}.";
"lng_premium_gifts_terms_policy" = "Privacy Policy";
"lng_boost_channel_button" = "Boost Channel";
"lng_boost_again_button" = "Boost Again";

View file

@ -10,7 +10,7 @@
<Identity Name="TelegramMessengerLLP.TelegramDesktop"
ProcessorArchitecture="ARCHITECTURE"
Publisher="CN=536BC709-8EE1-4478-AF22-F0F0F26FF64A"
Version="4.14.1.0" />
Version="4.14.2.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 4,14,1,0
PRODUCTVERSION 4,14,1,0
FILEVERSION 4,14,2,0
PRODUCTVERSION 4,14,2,0
FILEFLAGSMASK 0x3fL
#ifdef _DEBUG
FILEFLAGS 0x1L
@ -62,10 +62,10 @@ BEGIN
BEGIN
VALUE "CompanyName", "Radolyn Labs"
VALUE "FileDescription", "AyuGram Desktop"
VALUE "FileVersion", "4.14.1.0"
VALUE "FileVersion", "4.14.2.0"
VALUE "LegalCopyright", "Copyright (C) 2014-2024"
VALUE "ProductName", "AyuGram Desktop"
VALUE "ProductVersion", "4.14.1.0"
VALUE "ProductVersion", "4.14.2.0"
END
END
BLOCK "VarFileInfo"

View file

@ -35,8 +35,8 @@ LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
//
VS_VERSION_INFO VERSIONINFO
FILEVERSION 4,14,1,0
PRODUCTVERSION 4,14,1,0
FILEVERSION 4,14,2,0
PRODUCTVERSION 4,14,2,0
FILEFLAGSMASK 0x3fL
#ifdef _DEBUG
FILEFLAGS 0x1L
@ -53,10 +53,10 @@ BEGIN
BEGIN
VALUE "CompanyName", "Radolyn Labs"
VALUE "FileDescription", "AyuGram Desktop Updater"
VALUE "FileVersion", "4.14.1.0"
VALUE "FileVersion", "4.14.2.0"
VALUE "LegalCopyright", "Copyright (C) 2014-2024"
VALUE "ProductName", "AyuGram Desktop"
VALUE "ProductVersion", "4.14.1.0"
VALUE "ProductVersion", "4.14.2.0"
END
END
BLOCK "VarFileInfo"

View file

@ -603,7 +603,20 @@ void GiftsBox(
box,
object_ptr<Ui::FlatLabel>(
box,
session->api().premium().statusTextValue(), // TODO.
tr::lng_premium_gifts_terms(
lt_link,
tr::lng_payments_terms_link(
) | rpl::map([](const QString &t) {
using namespace Ui::Text;
return Link(t, u"https://telegram.org/tos"_q);
}),
lt_policy,
tr::lng_premium_gifts_terms_policy(
) | rpl::map([](const QString &t) {
using namespace Ui::Text;
return Link(t, u"https://telegram.org/privacy"_q);
}),
Ui::Text::RichLangValue),
st::premiumGiftTerms),
st::defaultBoxDividerLabelPadding),
{});
@ -903,9 +916,13 @@ void GiftPremiumValidator::showChoosePeerBox(const QString &ref) {
protected:
std::unique_ptr<PeerListRow> createRow(
not_null<UserData*> user) override {
return !user->isSelf()
? ContactsBoxController::createRow(user)
: nullptr;
if (user->isSelf()
|| user->isBot()
|| user->isServiceUser()
|| user->isInaccessible()) {
return nullptr;
}
return ContactsBoxController::createRow(user);
}
void rowClicked(not_null<PeerListRow*> row) override {
@ -1037,7 +1054,7 @@ void GiftCodeBox(
state->data = session->api().premium().giftCodeValue(slug);
state->used = state->data.value(
) | rpl::map([=](const Api::GiftCode &data) {
return data.used;
return data.used != 0;
});
box->setWidth(st::boxWideWidth);

View file

@ -1265,7 +1265,7 @@ void Settings::resetOnLastLogout() {
_tabbedReplacedWithInfo = false; // per-window
_systemDarkModeEnabled = false;
_hiddenGroupCallTooltips = 0;
_storiesClickTooltipHidden = 0;
_storiesClickTooltipHidden = false;
_recentEmojiPreload.clear();
_recentEmoji.clear();

View file

@ -22,7 +22,7 @@ constexpr auto AppId = "{53F49750-6209-4FBF-9CA8-7A333C87D666}"_cs;
constexpr auto AppNameOld = "AyuGram for Windows"_cs;
constexpr auto AppName = "AyuGram Desktop"_cs;
constexpr auto AppFile = "AyuGram"_cs;
constexpr auto AppVersion = 4014001;
constexpr auto AppVersionStr = "4.14.1";
constexpr auto AppVersion = 4014002;
constexpr auto AppVersionStr = "4.14.2";
constexpr auto AppBetaVersion = false;
constexpr auto AppAlphaVersion = TDESKTOP_ALPHA_VERSION;

View file

@ -624,7 +624,7 @@ void InnerWidget::paintEvent(QPaintEvent *e) {
bool selected,
bool mayBeActive) {
const auto key = row->key();
const auto active = mayBeActive && (activeEntry.key == key);
const auto active = mayBeActive && isRowActive(row, activeEntry);
const auto forum = key.history() && key.history()->isForum();
if (forum && !_topicJumpCache) {
_topicJumpCache = std::make_unique<Ui::TopicJumpCache>();
@ -977,6 +977,14 @@ void InnerWidget::paintCollapsedRow(
});
}
bool InnerWidget::isRowActive(
not_null<Row*> row,
const RowDescriptor &entry) const {
const auto key = row->key();
return (entry.key == key)
|| (entry.key.sublist() && key.peer() && key.peer()->isSelf());
}
bool InnerWidget::isSearchResultActive(
not_null<FakeRow*> result,
const RowDescriptor &entry) const {

View file

@ -231,6 +231,7 @@ private:
void switchToFilter(FilterId filterId);
bool chooseHashtag();
ChosenRow computeChosenRow() const;
bool isRowActive(not_null<Row*> row, const RowDescriptor &entry) const;
bool isSearchResultActive(
not_null<FakeRow*> result,
const RowDescriptor &entry) const;

View file

@ -522,7 +522,7 @@ AVRational ValidateAspectRatio(AVRational aspect) {
QSize CorrectByAspect(QSize size, AVRational aspect) {
Expects(IsValidAspectRatio(aspect));
return QSize(size.width() * aspect.num / aspect.den, size.height());
return QSize(size.width() * av_q2d(aspect), size.height());
}
bool RotationSwapWidthHeight(int rotation) {

View file

@ -1267,6 +1267,22 @@ uint8 HistoryItem::colorIndex() const {
Unexpected("No displayFrom and no displayHiddenSenderInfo.");
}
PeerData *HistoryItem::contentColorsFrom() const {
if (const auto forwarded = Get<HistoryMessageForwarded>()) {
return forwarded->originalSender;
}
return displayFrom();
}
uint8 HistoryItem::contentColorIndex() const {
if (const auto forwarded = Get<HistoryMessageForwarded>()) {
return forwarded->originalSender
? forwarded->originalSender->colorIndex()
: forwarded->originalHiddenSenderInfo->colorIndex;
}
return colorIndex();
}
std::unique_ptr<HistoryView::Element> HistoryItem::createView(
not_null<HistoryView::ElementDelegate*> delegate,
HistoryView::Element *replacing) {

View file

@ -517,6 +517,11 @@ public:
[[nodiscard]] PeerData *displayFrom() const;
[[nodiscard]] uint8 colorIndex() const;
// In forwards we show name in sender's color, but the message
// content uses the color of the original sender.
[[nodiscard]] PeerData *contentColorsFrom() const;
[[nodiscard]] uint8 contentColorIndex() const;
[[nodiscard]] std::unique_ptr<HistoryView::Element> createView(
not_null<HistoryView::ElementDelegate*> delegate,
HistoryView::Element *replacing = nullptr);

View file

@ -495,6 +495,10 @@ uint8 Element::colorIndex() const {
return data()->colorIndex();
}
uint8 Element::contentColorIndex() const {
return data()->contentColorIndex();
}
QDateTime Element::dateTime() const {
return _dateTime;
}

View file

@ -317,6 +317,7 @@ public:
void refreshDataId();
[[nodiscard]] uint8 colorIndex() const;
[[nodiscard]] uint8 contentColorIndex() const;
[[nodiscard]] QDateTime dateTime() const;
[[nodiscard]] int y() const;

View file

@ -1670,7 +1670,7 @@ void Message::paintText(
.availableWidth = trect.width(),
.palette = &stm->textPalette,
.pre = stm->preCache.get(),
.blockquote = context.quoteCache(colorIndex()),
.blockquote = context.quoteCache(contentColorIndex()),
.colors = context.st->highlightColors(),
.spoiler = Ui::Text::DefaultSpoilerCache(),
.now = context.now,

View file

@ -205,7 +205,7 @@ void Reply::update(
}
}
_colorPeer = message
? message->displayFrom()
? message->contentColorsFrom()
: story
? story->peer().get()
: _externalSender
@ -412,7 +412,10 @@ void Reply::updateName(
std::optional<PeerData*> resolvedSender) const {
auto viaBotUsername = QString();
const auto message = data->resolvedMessage.get();
if (message && !message->Has<HistoryMessageForwarded>()) {
const auto forwarded = message
? message->Get<HistoryMessageForwarded>()
: nullptr;
if (message && !forwarded) {
if (const auto bot = message->viaBot()) {
viaBotUsername = bot->username();
}
@ -428,7 +431,15 @@ void Reply::updateName(
&& externalPeer
&& (externalPeer != sender)
&& (externalPeer->isChat() || externalPeer->isMegagroup());
const auto shorten = !viaBotUsername.isEmpty() || groupNameAdded;
const auto originalNameAdded = !displayAsExternal
&& forwarded
&& !message->isDiscussionPost()
&& (forwarded->forwardOfForward()
|| (!message->showForwardsFromSender(forwarded)
&& !view->data()->Has<HistoryMessageForwarded>()));
const auto shorten = !viaBotUsername.isEmpty()
|| groupNameAdded
|| originalNameAdded;
const auto name = sender
? senderName(sender, shorten)
: senderName(view, data, shorten);
@ -447,6 +458,11 @@ void Reply::updateName(
if (groupNameAdded) {
nameFull.append(' ').append(PeerEmoji(history, externalPeer));
nameFull.append(externalPeer->name());
} else if (originalNameAdded) {
nameFull.append(' ').append(ForwardEmoji(&history->owner()));
nameFull.append(forwarded->originalSender
? forwarded->originalSender->name()
: forwarded->originalHiddenSenderInfo->name);
}
if (!viaBotUsername.isEmpty()) {
nameFull.append(u" @"_q).append(viaBotUsername);
@ -844,6 +860,13 @@ TextWithEntities Reply::PeerEmoji(
icon.second));
}
TextWithEntities Reply::ForwardEmoji(not_null<Data::Session*> owner) {
return Ui::Text::SingleCustomEmoji(
owner->customEmojiManager().registerInternalEmoji(
st::historyReplyForward,
st::historyReplyForwardPadding));
}
TextWithEntities Reply::ComposePreviewName(
not_null<History*> history,
not_null<HistoryItem*> to,

View file

@ -105,6 +105,8 @@ public:
[[nodiscard]] static TextWithEntities PeerEmoji(
not_null<Data::Session*> owner,
PeerData *peer);
[[nodiscard]] static TextWithEntities ForwardEmoji(
not_null<Data::Session*> owner);
[[nodiscard]] static TextWithEntities ComposePreviewName(
not_null<History*> history,
not_null<HistoryItem*> to,

View file

@ -259,7 +259,7 @@ not_null<Data::SavedSublist*> SublistWidget::sublist() const {
Dialogs::RowDescriptor SublistWidget::activeChat() const {
return {
_history,
_sublist,
FullMsgId(_history->peer->id, ShowAtUnreadMsgId)
};
}
@ -293,6 +293,10 @@ bool SublistWidget::showInternal(
return false;
}
bool SublistWidget::sameTypeAs(not_null<Window::SectionMemento*> memento) {
return dynamic_cast<SublistMemento*>(memento.get()) != nullptr;
}
void SublistWidget::setInternalState(
const QRect &geometry,
not_null<SublistMemento*> memento) {

View file

@ -58,6 +58,8 @@ public:
bool showInternal(
not_null<Window::SectionMemento*> memento,
const Window::SectionShow &params) override;
bool sameTypeAs(not_null<Window::SectionMemento*> memento) override;
std::shared_ptr<Window::SectionMemento> createMemento() override;
bool showMessage(
PeerId peerId,

View file

@ -80,6 +80,19 @@ inline bool HasGroupCallMenu(const not_null<PeerData*> &peer) {
|| (peer->isChat() && peer->asChat()->amCreator()));
}
QString TopBarNameText(
not_null<PeerData*> peer,
Dialogs::EntryState::Section section) {
if (section == Dialogs::EntryState::Section::SavedSublist) {
if (peer->isSelf()) {
return tr::lng_my_notes(tr::now);
} else if (peer->isSavedHiddenAuthor()) {
return tr::lng_hidden_author_messages(tr::now);
}
}
return peer->topBarNameText();
}
} // namespace
struct TopBarWidget::EmojiInteractionSeenAnimation {
@ -559,7 +572,7 @@ void TopBarWidget::paintTopBar(Painter &p) {
_titleNameVersion = namePeer->nameVersion();
_title.setText(
st::msgNameStyle,
namePeer->topBarNameText(),
TopBarNameText(namePeer, _activeChat.section),
Ui::NameTextOptions());
}
const auto badgeWidth = _titleBadge.drawGetWidth(

View file

@ -888,7 +888,7 @@ void Document::draw(
.availableWidth = captionw,
.palette = &stm->textPalette,
.pre = stm->preCache.get(),
.blockquote = context.quoteCache(parent()->colorIndex()),
.blockquote = context.quoteCache(parent()->contentColorIndex()),
.colors = context.st->highlightColors(),
.spoiler = Ui::Text::DefaultSpoilerCache(),
.now = context.now,

View file

@ -237,7 +237,7 @@ void ExtendedPreview::draw(Painter &p, const PaintContext &context) const {
.availableWidth = captionw,
.palette = &stm->textPalette,
.pre = stm->preCache.get(),
.blockquote = context.quoteCache(parent()->colorIndex()),
.blockquote = context.quoteCache(parent()->contentColorIndex()),
.colors = context.st->highlightColors(),
.spoiler = Ui::Text::DefaultSpoilerCache(),
.now = context.now,

View file

@ -219,7 +219,7 @@ void Game::draw(Painter &p, const PaintContext &context) const {
auto tshift = inner.top();
auto paintw = inner.width();
const auto colorIndex = parent()->colorIndex();
const auto colorIndex = parent()->contentColorIndex();
const auto selected = context.selected();
const auto cache = context.outbg
? stm->replyCache[st->colorPatternIndex(colorIndex)].get()

View file

@ -724,7 +724,7 @@ void Gif::draw(Painter &p, const PaintContext &context) const {
.availableWidth = captionw,
.palette = &stm->textPalette,
.pre = stm->preCache.get(),
.blockquote = context.quoteCache(parent()->colorIndex()),
.blockquote = context.quoteCache(parent()->contentColorIndex()),
.colors = context.st->highlightColors(),
.spoiler = Ui::Text::DefaultSpoilerCache(),
.now = context.now,

View file

@ -405,7 +405,7 @@ void GroupedMedia::draw(Painter &p, const PaintContext &context) const {
.availableWidth = captionw,
.palette = &stm->textPalette,
.pre = stm->preCache.get(),
.blockquote = context.quoteCache(parent()->colorIndex()),
.blockquote = context.quoteCache(parent()->contentColorIndex()),
.colors = context.st->highlightColors(),
.spoiler = Ui::Text::DefaultSpoilerCache(),
.now = context.now,

View file

@ -433,7 +433,7 @@ void Photo::draw(Painter &p, const PaintContext &context) const {
.availableWidth = captionw,
.palette = &stm->textPalette,
.pre = stm->preCache.get(),
.blockquote = context.quoteCache(parent()->colorIndex()),
.blockquote = context.quoteCache(parent()->contentColorIndex()),
.colors = context.st->highlightColors(),
.spoiler = Ui::Text::DefaultSpoilerCache(),
.now = context.now,

View file

@ -588,11 +588,11 @@ void WebPage::draw(Painter &p, const PaintContext &context) const {
const auto selected = context.selected();
const auto view = parent();
const auto colorIndex = view->colorIndex();
const auto from = view->data()->contentColorsFrom();
const auto colorIndex = from ? from->colorIndex() : view->colorIndex();
const auto cache = context.outbg
? stm->replyCache[st->colorPatternIndex(colorIndex)].get()
: st->coloredReplyCache(selected, colorIndex).get();
const auto from = view->data()->displayFrom();
const auto backgroundEmojiId = from
? from->backgroundEmojiId()
: DocumentId();

View file

@ -203,12 +203,7 @@ TextWithEntities AboutWithEntities(
const auto stripExternal = peer->isChat()
|| peer->isMegagroup()
|| (user && !isBot && !isPremium);
const auto limit = Data::PremiumLimits(&peer->session())
.aboutLengthDefault();
const auto used = (!user || isPremium || value.size() <= limit)
? value
: value.mid(0, limit) + "...";
auto result = TextWithEntities{ used };
auto result = TextWithEntities{ value };
TextUtilities::ParseEntities(result, flags);
if (stripExternal) {
StripExternalLinks(result);

View file

@ -60,9 +60,12 @@ SublistsWidget::SublistsWidget(
_list->chosenRow() | rpl::start_with_next([=](Dialogs::ChosenRow row) {
if (const auto sublist = row.key.sublist()) {
using namespace Window;
auto params = SectionShow(SectionShow::Way::Forward);
params.dropSameFromStack = true;
controller->showSection(
std::make_shared<HistoryView::SublistMemento>(sublist),
Window::SectionShow::Way::Forward);
params);
}
}, _list->lifetime());

View file

@ -1379,7 +1379,7 @@ void MainWidget::showHistory(
if (!back && (way != Way::ClearStack)) {
// This may modify the current section, for example remove its contents.
saveSectionInStack();
saveSectionInStack(params);
}
if (_history->peer()
@ -1501,13 +1501,23 @@ Ui::ChatTheme *MainWidget::customChatTheme() const {
return _history->customChatTheme();
}
void MainWidget::saveSectionInStack() {
bool MainWidget::saveSectionInStack(
const SectionShow &params,
Window::SectionWidget *newMainSection) {
if (_mainSection) {
if (auto memento = _mainSection->createMemento()) {
if (params.dropSameFromStack
&& newMainSection
&& newMainSection->sameTypeAs(memento.get())) {
// When choosing saved sublist we want to save the original
// "Saved Messages" in the stack, but don't save every
// sublist in a new stack entry when clicking them through.
return false;
}
_stack.push_back(std::make_unique<StackItemSection>(
std::move(memento)));
} else {
return;
return false;
}
} else if (const auto history = _history->history()) {
_stack.push_back(std::make_unique<StackItemHistory>(
@ -1515,7 +1525,7 @@ void MainWidget::saveSectionInStack() {
_history->msgId(),
_history->replyReturns()));
} else {
return;
return false;
}
const auto raw = _stack.back().get();
raw->setThirdSectionWeak(_thirdSection.data());
@ -1528,6 +1538,7 @@ void MainWidget::saveSectionInStack() {
}
}
}, raw->lifetime());
return true;
}
void MainWidget::showSection(
@ -1730,7 +1741,11 @@ void MainWidget::showNewSection(
if (saveInStack) {
// This may modify the current section, for example remove its contents.
saveSectionInStack();
if (!saveSectionInStack(params, newMainSection)) {
saveInStack = false;
animatedShow = false;
animationParams = Window::SectionSlideParams();
}
}
auto &settingSection = newThirdSection
? _thirdSection
@ -2446,6 +2461,10 @@ auto MainWidget::thirdSectionForCurrentMainSection(
return std::make_shared<Info::Memento>(
peer,
Info::Memento::DefaultSection(peer));
} else if (const auto sublist = key.sublist()) {
return std::make_shared<Info::Memento>(
session().user(),
Info::Memento::DefaultSection(session().user()));
}
Unexpected("Key in MainWidget::thirdSectionForCurrentMainSection().");
}

View file

@ -286,7 +286,9 @@ private:
Window::SectionSlideParams prepareHistoryAnimation(PeerId historyPeerId);
Window::SectionSlideParams prepareDialogsAnimation();
void saveSectionInStack();
bool saveSectionInStack(
const SectionShow &params,
Window::SectionWidget *newMainSection = nullptr);
int getMainSectionTop() const;
int getThirdSectionTop() const;

View file

@ -20,10 +20,21 @@ OverlayWidget::RendererSW::RendererSW(not_null<OverlayWidget*> owner)
, _transparentBrush(style::TransparentPlaceholder()) {
}
bool OverlayWidget::RendererSW::handleHideWorkaround() {
// This is needed on Windows or Linux,
// because on reopen it blinks with the last shown content.
return _owner->_hideWorkaround != nullptr;
}
void OverlayWidget::RendererSW::paintFallback(
Painter &&p,
const QRegion &clip,
Ui::GL::Backend backend) {
if (handleHideWorkaround()) {
p.setCompositionMode(QPainter::CompositionMode_Source);
p.fillRect(clip.boundingRect(), Qt::transparent);
return;
}
_p = &p;
_clip = &clip;
_clipOuter = clip.boundingRect();

View file

@ -59,6 +59,7 @@ private:
QRect rect,
float64 opacity = 1.) override;
bool handleHideWorkaround();
void validateOverControlImage();
[[nodiscard]] static QRect TransformRect(QRectF geometry, int rotation);

View file

@ -38,6 +38,8 @@ historyReplyGroup: icon {{ "chat/reply_type_group", windowFg }};
historyReplyGroupPadding: margins(0px, 4px, 4px, 0px);
historyReplyChannel: icon {{ "chat/reply_type_channel", windowFg }};
historyReplyChannelPadding: margins(0px, 5px, 4px, 0px);
historyReplyForward: icon {{ "mini_forward", windowFg }};
historyReplyForwardPadding: margins(0px, 2px, 2px, 0px);
msgReplyPadding: margins(6px, 6px, 11px, 6px);
msgReplyBarPos: point(1px, 0px);

View file

@ -138,6 +138,9 @@ public:
virtual bool showInternal(
not_null<SectionMemento*> memento,
const SectionShow &params) = 0;
virtual bool sameTypeAs(not_null<SectionMemento*> memento) {
return false;
}
virtual bool showMessage(
PeerId peerId,

View file

@ -162,6 +162,7 @@ struct SectionShow {
bool childColumn = false;
bool forbidLayer = false;
bool reapplyLocalDraft = false;
bool dropSameFromStack = false;
Origin origin;
};

View file

@ -1,7 +1,7 @@
AppVersion 4014001
AppVersion 4014002
AppVersionStrMajor 4.14
AppVersionStrSmall 4.14.1
AppVersionStr 4.14.1
AppVersionStrSmall 4.14.2
AppVersionStr 4.14.2
BetaChannel 0
AlphaVersion 0
AppVersionOriginal 4.14.1
AppVersionOriginal 4.14.2

@ -1 +1 @@
Subproject commit 1bb91474c2337396673dd8a7c68e65ea317b6db5
Subproject commit 63e4ba48fd8540fa3c2949d123160a2ce3411d70

View file

@ -1,3 +1,10 @@
4.14.2 (02.01.24)
- Show original senders name in reply to forward information.
- Use original senders color / emoji pattern in forwards.
- Highlight active saved messages chat in list.
- Fix chats list scrolling on X11 (Linux).
4.14.1 (01.01.24)
- Fix crash in "Author Hidden" chat in "Saved Messages".