Merge tag 'v5.1.7' into dev

# Conflicts:
#	Telegram/Resources/winrc/Telegram.rc
#	Telegram/Resources/winrc/Updater.rc
#	Telegram/SourceFiles/core/version.h
#	Telegram/lib_ui
#	snap/snapcraft.yaml
This commit is contained in:
AlexeyZavar 2024-06-14 21:43:44 +03:00
commit 68ad4aeaf8
69 changed files with 1525 additions and 934 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 766 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

View file

@ -2316,6 +2316,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_credits_summary_options_more" = "More Options"; "lng_credits_summary_options_more" = "More Options";
"lng_credits_summary_options_about" = "By proceeding and purchasing Stars, you agree with the {link}."; "lng_credits_summary_options_about" = "By proceeding and purchasing Stars, you agree with the {link}.";
"lng_credits_summary_options_about_link" = "Terms and Conditions"; "lng_credits_summary_options_about_link" = "Terms and Conditions";
"lng_credits_summary_options_about_url" = "https://telegram.org/tos/stars";
"lng_credits_summary_history_tab_full" = "All Transactions"; "lng_credits_summary_history_tab_full" = "All Transactions";
"lng_credits_summary_history_tab_in" = "Incoming"; "lng_credits_summary_history_tab_in" = "Incoming";
"lng_credits_summary_history_tab_out" = "Outgoing"; "lng_credits_summary_history_tab_out" = "Outgoing";

View file

@ -10,7 +10,7 @@
<Identity Name="TelegramMessengerLLP.TelegramDesktop" <Identity Name="TelegramMessengerLLP.TelegramDesktop"
ProcessorArchitecture="ARCHITECTURE" ProcessorArchitecture="ARCHITECTURE"
Publisher="CN=536BC709-8EE1-4478-AF22-F0F0F26FF64A" Publisher="CN=536BC709-8EE1-4478-AF22-F0F0F26FF64A"
Version="5.1.2.0" /> Version="5.1.7.0" />
<Properties> <Properties>
<DisplayName>Telegram Desktop</DisplayName> <DisplayName>Telegram Desktop</DisplayName>
<PublisherDisplayName>Telegram Messenger LLP</PublisherDisplayName> <PublisherDisplayName>Telegram Messenger LLP</PublisherDisplayName>

View file

@ -44,8 +44,8 @@ IDI_ICON1 ICON "..\\art\\icon256.ico"
// //
VS_VERSION_INFO VERSIONINFO VS_VERSION_INFO VERSIONINFO
FILEVERSION 5,1,2,0 FILEVERSION 5,1,7,0
PRODUCTVERSION 5,1,2,0 PRODUCTVERSION 5,1,7,0
FILEFLAGSMASK 0x3fL FILEFLAGSMASK 0x3fL
#ifdef _DEBUG #ifdef _DEBUG
FILEFLAGS 0x1L FILEFLAGS 0x1L
@ -62,10 +62,10 @@ BEGIN
BEGIN BEGIN
VALUE "CompanyName", "Radolyn Labs" VALUE "CompanyName", "Radolyn Labs"
VALUE "FileDescription", "AyuGram Desktop" VALUE "FileDescription", "AyuGram Desktop"
VALUE "FileVersion", "5.1.2.0" VALUE "FileVersion", "5.1.7.0"
VALUE "LegalCopyright", "Copyright (C) 2014-2024" VALUE "LegalCopyright", "Copyright (C) 2014-2024"
VALUE "ProductName", "AyuGram Desktop" VALUE "ProductName", "AyuGram Desktop"
VALUE "ProductVersion", "5.1.2.0" VALUE "ProductVersion", "5.1.7.0"
END END
END END
BLOCK "VarFileInfo" BLOCK "VarFileInfo"

View file

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

View file

@ -199,24 +199,4 @@ rpl::producer<not_null<PeerData*>> PremiumPeerBot(
}; };
} }
void CreditsRefund(
not_null<PeerData*> peer,
const QString &entryId,
Fn<void()> done,
Fn<void(QString)> fail) {
const auto user = peer->asUser();
if (!user) {
return;
}
peer->session().api().request(MTPpayments_RefundStarsCharge(
user->inputUser,
MTP_string(entryId)
)).done([=](const MTPUpdates &result) {
peer->session().api().updates().applyUpdates(result);
done();
}).fail([=](const MTP::Error &error) {
fail(error.type());
}).send();
}
} // namespace Api } // namespace Api

View file

@ -68,12 +68,6 @@ private:
}; };
void CreditsRefund(
not_null<PeerData*> peer,
const QString &entryId,
Fn<void()> done,
Fn<void(QString)> fail);
[[nodiscard]] rpl::producer<not_null<PeerData*>> PremiumPeerBot( [[nodiscard]] rpl::producer<not_null<PeerData*>> PremiumPeerBot(
not_null<Main::Session*> session); not_null<Main::Session*> session);

View file

@ -285,7 +285,7 @@ pod prf prg ps1 ps2 ps1xml ps2xml psc1 psc2 psd1 psm1 pssc pst py py3 pyc \
pyd pyi pyo pyw pyzw pyz rb reg rgs scf scr sct search-ms settingcontent-ms \ pyd pyi pyo pyw pyzw pyz rb reg rgs scf scr sct search-ms settingcontent-ms \
sh shb shs slk sys swf t tmp u3p url vb vbe vbp vbs vbscript vdx vsmacros \ sh shb shs slk sys swf t tmp u3p url vb vbe vbp vbs vbscript vdx vsmacros \
vsd vsdm vsdx vss vssm vssx vst vstm vstx vsw vsx vtx website wlua ws wsc \ vsd vsdm vsdx vss vssm vssx vst vstm vstx vsw vsx vtx website wlua ws wsc \
wsf wsh xbap xll xlsm xnk xs"_q wsf wsh xbap xll xlsb xlsm xnk xs"_q
#elif defined Q_OS_MAC // Q_OS_MAC #elif defined Q_OS_MAC // Q_OS_MAC
u"\ u"\
applescript action app bin command csh osx workflow terminal url caction \ applescript action app bin command csh osx workflow terminal url caction \

View file

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

View file

@ -1219,6 +1219,92 @@ std::unique_ptr<HistoryView::Media> MediaFile::createView(
_document); _document);
} }
SharedContact::VcardItems SharedContact::ParseVcard(const QString &data) {
const auto decode = [&](const QByteArray &input) -> QString {
auto output = QByteArray();
for (auto i = 0; i < input.size(); ++i) {
if ((input.at(i) == '=') && ((i + 2) < input.size())) {
const auto value = input.mid((++i)++, 2);
auto converted = false;
const auto character = char(value.toUInt(&converted, 16));
if (converted) {
output.append(character);
} else {
output.append('=');
output.append(value);
}
} else {
output.append(input.at(i));
}
}
return QString::fromUtf8(output);
};
using Type = SharedContact::VcardItemType;
auto items = SharedContact::VcardItems();
for (const auto &item : data.split('\n')) {
const auto parts = item.split(':');
if (parts.size() == 2) {
const auto &type = parts.front();
const auto attributes = type.split(';', Qt::SkipEmptyParts);
const auto c = Qt::CaseInsensitive;
auto isQuotedPrintable = false;
for (const auto &attribute : attributes) {
const auto parts = attribute.split('=', Qt::SkipEmptyParts);
if (parts.size() == 2) {
if (parts.front().startsWith("ENCODING", c)) {
isQuotedPrintable = parts[1].startsWith(
"QUOTED-PRINTABLE",
c);
break;
}
}
}
const auto &value = isQuotedPrintable
? decode(parts[1].toUtf8())
: parts[1];
if (type.startsWith("TEL")) {
const auto telType = type.contains("PREF")
? Type::PhoneMain
: type.contains("HOME")
? Type::PhoneHome
: type.contains("WORK")
? Type::PhoneWork
: (type.contains("CELL")
|| type.contains("MOBILE"))
? Type::PhoneMobile
: type.contains("OTHER")
? Type::PhoneOther
: Type::Phone;
items[telType] = value;
} else if (type.startsWith("EMAIL")) {
items[Type::Email] = value;
} else if (type.startsWith("URL")) {
items[Type::Url] = value;
} else if (type.startsWith("NOTE")) {
items[Type::Note] = value;
} else if (type.startsWith("ORG")) {
items[Type::Organization] = base::duplicate(value)
.replace(';', ' ')
.trimmed();
} else if (type.startsWith("ADR")) {
items[Type::Address] = value;
} else if (type.startsWith("BDAY")) {
items[Type::Birthday] = value;
} else if (type.startsWith("N")) {
items[Type::Name] = base::duplicate(value)
.replace(';', ' ')
.trimmed();
}
}
}
return items;
}
MediaContact::MediaContact( MediaContact::MediaContact(
not_null<HistoryItem*> parent, not_null<HistoryItem*> parent,
UserId userId, UserId userId,

View file

@ -70,6 +70,8 @@ struct SharedContact final {
}; };
using VcardItems = base::flat_map<VcardItemType, QString>; using VcardItems = base::flat_map<VcardItemType, QString>;
static VcardItems ParseVcard(const QString &);
VcardItems vcardItems; VcardItems vcardItems;
}; };

View file

@ -447,7 +447,7 @@ bool UserData::someRequirePremiumToWrite() const {
} }
bool UserData::meRequiresPremiumToWrite() const { bool UserData::meRequiresPremiumToWrite() const {
return (flags() & UserDataFlag::MeRequiresPremiumToWrite); return !isSelf() && (flags() & UserDataFlag::MeRequiresPremiumToWrite);
} }
bool UserData::requirePremiumToWriteKnown() const { bool UserData::requirePremiumToWriteKnown() const {

View file

@ -7,6 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/ */
using "ui/basic.style"; using "ui/basic.style";
using "ui/layers/layers.style"; // boxRoundShadow
using "ui/widgets/widgets.style"; using "ui/widgets/widgets.style";
DialogRow { DialogRow {
@ -446,10 +447,25 @@ dialogsLoadMoreLoading: InfiniteRadialAnimation(defaultInfiniteRadialAnimation)
size: size(12px, 12px); size: size(12px, 12px);
} }
dialogsSearchInHeight: 52px; dialogsSearchInHeight: 38px;
dialogsSearchInPhotoSize: 36px; dialogsSearchInPhotoSize: 26px;
dialogsSearchInPhotoPadding: 10px; dialogsSearchInPhotoPadding: 12px;
dialogsSearchInSkip: 7px; dialogsSearchInSkip: 10px;
dialogsSearchInNameTop: 9px;
dialogsSearchInDownTop: 15px;
dialogsSearchInDown: icon {{ "intro_country_dropdown", windowBoldFg }};
dialogsSearchInDownSkip: 4px;
dialogsSearchInMenu: PopupMenu(defaultPopupMenu) {
shadow: boxRoundShadow;
animation: PanelAnimation(defaultPanelAnimation) {
shadow: boxRoundShadow;
}
scrollPadding: margins(0px, 0px, 0px, 0px);
radius: 8px;
menu: menuWithIcons;
}
dialogsSearchInCheck: icon {{ "player/player_check", mediaPlayerActiveFg }};
dialogsSearchInCheckSkip: 8px;
dialogsSearchFromStyle: defaultTextStyle; dialogsSearchFromStyle: defaultTextStyle;
dialogsSearchFromPalette: TextPalette(defaultTextPalette) { dialogsSearchFromPalette: TextPalette(defaultTextPalette) {
linkFg: dialogsNameFg; linkFg: dialogsNameFg;
@ -694,17 +710,17 @@ dialogsStoriesTooltipHide: IconButton(defaultIconButton) {
ripple: emptyRippleAnimation; ripple: emptyRippleAnimation;
} }
searchedBarHeight: 32px; searchedBarHeight: 28px;
searchedBarFont: normalFont; searchedBarFont: normalFont;
searchedBarPosition: point(17px, 7px); searchedBarPosition: point(14px, 5px);
searchedBarLabel: FlatLabel(defaultFlatLabel) { searchedBarLabel: FlatLabel(defaultFlatLabel) {
textFg: searchedBarFg; textFg: searchedBarFg;
margin: margins(17px, 7px, 17px, 7px); margin: margins(14px, 5px, 14px, 5px);
} }
searchedBarLink: LinkButton(defaultLinkButton) { searchedBarLink: LinkButton(defaultLinkButton) {
color: searchedBarFg; color: searchedBarFg;
overColor: searchedBarFg; overColor: searchedBarFg;
padding: margins(17px, 7px, 17px, 7px); padding: margins(14px, 5px, 14px, 5px);
} }
dialogsSearchTagSkip: point(8px, 4px); dialogsSearchTagSkip: point(8px, 4px);

View file

@ -9,7 +9,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "dialogs/dialogs_three_state_icon.h" #include "dialogs/dialogs_three_state_icon.h"
#include "dialogs/ui/chat_search_empty.h" #include "dialogs/ui/chat_search_empty.h"
#include "dialogs/ui/chat_search_tabs.h" #include "dialogs/ui/chat_search_in.h"
#include "dialogs/ui/dialogs_layout.h" #include "dialogs/ui/dialogs_layout.h"
#include "dialogs/ui/dialogs_stories_content.h" #include "dialogs/ui/dialogs_stories_content.h"
#include "dialogs/ui/dialogs_video_userpic.h" #include "dialogs/ui/dialogs_video_userpic.h"
@ -29,6 +29,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/widgets/scroll_area.h" #include "ui/widgets/scroll_area.h"
#include "ui/text/text_utilities.h" #include "ui/text/text_utilities.h"
#include "ui/text/text_options.h" #include "ui/text/text_options.h"
#include "ui/dynamic_thumbnails.h"
#include "ui/painter.h" #include "ui/painter.h"
#include "ui/ui_utility.h" #include "ui/ui_utility.h"
#include "data/data_drafts.h" #include "data/data_drafts.h"
@ -137,9 +138,7 @@ constexpr auto kStartReorderThreshold = 30;
if (hashtag) { if (hashtag) {
text.append(tr::lng_search_tab_by_hashtag(tr::now)); text.append(tr::lng_search_tab_by_hashtag(tr::now));
} else { } else {
text.append( text.append(tr::lng_dlg_search_for_messages(tr::now));
tr::lng_dlg_search_for_messages(tr::now)
).append('\n').append(Ui::Text::Link(tr::lng_cancel(tr::now)));
} }
} else { } else {
text.append(tr::lng_search_tab_no_results( text.append(tr::lng_search_tab_no_results(
@ -214,12 +213,9 @@ InnerWidget::InnerWidget(
, _narrowWidth(st::defaultDialogRow.padding.left() , _narrowWidth(st::defaultDialogRow.padding.left()
+ st::defaultDialogRow.photoSize + st::defaultDialogRow.photoSize
+ st::defaultDialogRow.padding.left()) + st::defaultDialogRow.padding.left())
, _cancelSearchFromUser(this, st::dialogsCancelSearchInPeer)
, _childListShown(std::move(childListShown)) { , _childListShown(std::move(childListShown)) {
setAttribute(Qt::WA_OpaquePaintEvent, true); setAttribute(Qt::WA_OpaquePaintEvent, true);
_cancelSearchFromUser->hide();
style::PaletteChanged( style::PaletteChanged(
) | rpl::start_with_next([=] { ) | rpl::start_with_next([=] {
_topicJumpCache = nullptr; _topicJumpCache = nullptr;
@ -516,8 +512,12 @@ int InnerWidget::pinnedOffset() const {
return dialogsOffset() + shownHeight(fixedOnTopCount()); return dialogsOffset() + shownHeight(fixedOnTopCount());
} }
int InnerWidget::hashtagsOffset() const {
return searchInChatOffset() + searchInChatSkip();
}
int InnerWidget::filteredOffset() const { int InnerWidget::filteredOffset() const {
return _hashtagResults.size() * st::mentionHeight; return hashtagsOffset() + (_hashtagResults.size() * st::mentionHeight);
} }
int InnerWidget::filteredIndex(int y) const { int InnerWidget::filteredIndex(int y) const {
@ -543,33 +543,19 @@ int InnerWidget::peerSearchOffset() const {
+ st::searchedBarHeight; + st::searchedBarHeight;
} }
int InnerWidget::searchTagsOffset() const {
auto result = peerSearchOffset() - st::searchedBarHeight;
if (!_peerSearchResults.empty()) {
result += (_peerSearchResults.size() * st::dialogsRowHeight)
+ st::searchedBarHeight;
}
return result;
}
int InnerWidget::searchInChatOffset() const { int InnerWidget::searchInChatOffset() const {
auto result = searchTagsOffset(); return (_searchTags ? _searchTags->height() : 0);
if (_searchTags) {
result += _searchTags->height();
}
return result;
}
int InnerWidget::searchedOffset() const {
return searchInChatOffset()
+ searchInChatSkip()
+ st::searchedBarHeight;
} }
int InnerWidget::searchInChatSkip() const { int InnerWidget::searchInChatSkip() const {
auto result = 0; return _searchIn ? _searchIn->height() : 0;
if (_searchFromShown) { }
result += st::dialogsSearchInHeight;
int InnerWidget::searchedOffset() const {
auto result = peerSearchOffset();
if (!_peerSearchResults.empty()) {
result += (_peerSearchResults.size() * st::dialogsRowHeight)
+ st::searchedBarHeight;
} }
return result; return result;
} }
@ -808,10 +794,26 @@ void InnerWidget::paintEvent(QPaintEvent *e) {
p.fillRect(dialogsClip, currentBg()); p.fillRect(dialogsClip, currentBg());
} }
} else if (_state == WidgetState::Filtered) { } else if (_state == WidgetState::Filtered) {
[[maybe_unused]] auto top = 0; if (_searchTags) {
paintSearchTags(p, {
.st = &st::forumTopicRow,
.currentBg = currentBg(),
.now = ms,
.width = fullWidth,
.paused = videoPaused,
});
p.translate(0, _searchTags->height());
}
if (_searchIn) {
p.translate(0, searchInChatSkip());
if (_searchResults.empty()) {
p.fillRect(0, 0, fullWidth, st::lineWidth, st::shadowFg);
}
}
if (!_hashtagResults.empty()) { if (!_hashtagResults.empty()) {
auto from = floorclamp(r.y(), st::mentionHeight, 0, _hashtagResults.size()); const auto skip = hashtagsOffset();
auto to = ceilclamp(r.y() + r.height(), st::mentionHeight, 0, _hashtagResults.size()); auto from = floorclamp(r.y() - skip, st::mentionHeight, 0, _hashtagResults.size());
auto to = ceilclamp(r.y() + r.height() - skip, st::mentionHeight, 0, _hashtagResults.size());
p.translate(0, from * st::mentionHeight); p.translate(0, from * st::mentionHeight);
if (from < _hashtagResults.size()) { if (from < _hashtagResults.size()) {
const auto htagleft = st::defaultDialogRow.padding.left(); const auto htagleft = st::defaultDialogRow.padding.left();
@ -853,7 +855,9 @@ void InnerWidget::paintEvent(QPaintEvent *e) {
p.drawText(htagleft + firstwidth, st::mentionTop + st::mentionFont->ascent, second); p.drawText(htagleft + firstwidth, st::mentionTop + st::mentionFont->ascent, second);
} }
p.translate(0, st::mentionHeight); p.translate(0, st::mentionHeight);
top += st::mentionHeight; }
if (to < _hashtagResults.size()) {
p.translate(0, (_hashtagResults.size() - to) * st::mentionHeight);
} }
} }
} }
@ -865,7 +869,6 @@ void InnerWidget::paintEvent(QPaintEvent *e) {
int(_filterResults.size())); int(_filterResults.size()));
const auto height = filteredHeight(from); const auto height = filteredHeight(from);
p.translate(0, height); p.translate(0, height);
top += height;
for (; from < to; ++from) { for (; from < to; ++from) {
const auto selected = isPressed() const auto selected = isPressed()
? (from == _filteredPressed) ? (from == _filteredPressed)
@ -873,7 +876,6 @@ void InnerWidget::paintEvent(QPaintEvent *e) {
const auto row = _filterResults[from].row; const auto row = _filterResults[from].row;
paintRow(row, selected, !activeEntry.fullId); paintRow(row, selected, !activeEntry.fullId);
p.translate(0, row->height()); p.translate(0, row->height());
top += row->height();
} }
} }
@ -883,13 +885,11 @@ void InnerWidget::paintEvent(QPaintEvent *e) {
p.setPen(st::searchedBarFg); p.setPen(st::searchedBarFg);
p.drawTextLeft(st::searchedBarPosition.x(), st::searchedBarPosition.y(), width(), tr::lng_search_global_results(tr::now)); p.drawTextLeft(st::searchedBarPosition.x(), st::searchedBarPosition.y(), width(), tr::lng_search_global_results(tr::now));
p.translate(0, st::searchedBarHeight); p.translate(0, st::searchedBarHeight);
top += st::searchedBarHeight;
auto skip = peerSearchOffset(); auto skip = peerSearchOffset();
auto from = floorclamp(r.y() - skip, st::dialogsRowHeight, 0, _peerSearchResults.size()); auto from = floorclamp(r.y() - skip, st::dialogsRowHeight, 0, _peerSearchResults.size());
auto to = ceilclamp(r.y() + r.height() - skip, st::dialogsRowHeight, 0, _peerSearchResults.size()); auto to = ceilclamp(r.y() + r.height() - skip, st::dialogsRowHeight, 0, _peerSearchResults.size());
p.translate(0, from * st::dialogsRowHeight); p.translate(0, from * st::dialogsRowHeight);
top += from * st::dialogsRowHeight;
if (from < _peerSearchResults.size()) { if (from < _peerSearchResults.size()) {
const auto activePeer = activeEntry.key.peer(); const auto activePeer = activeEntry.key.peer();
for (; from < to; ++from) { for (; from < to; ++from) {
@ -912,34 +912,10 @@ void InnerWidget::paintEvent(QPaintEvent *e) {
.paused = videoPaused, .paused = videoPaused,
}); });
p.translate(0, st::dialogsRowHeight); p.translate(0, st::dialogsRowHeight);
top += st::dialogsRowHeight;
} }
} if (to < _peerSearchResults.size()) {
} p.translate(0, (_peerSearchResults.size() - to) * st::dialogsRowHeight);
}
if (_searchTags) {
paintSearchTags(p, {
.st = &st::forumTopicRow,
.currentBg = currentBg(),
.now = ms,
.width = fullWidth,
.paused = videoPaused,
});
p.translate(0, _searchTags->height());
top += _searchTags->height();
}
if (_searchFromShown) {
paintSearchInChat(p, {
.st = &st::forumTopicRow,
.currentBg = currentBg(),
.now = ms,
.width = fullWidth,
.paused = videoPaused,
});
p.translate(0, searchInChatSkip());
top += searchInChatSkip();
if (_searchResults.empty()) {
p.fillRect(0, 0, fullWidth, st::lineWidth, st::shadowFg);
} }
} }
@ -952,7 +928,6 @@ void InnerWidget::paintEvent(QPaintEvent *e) {
p.setPen(st::searchedBarFg); p.setPen(st::searchedBarFg);
p.drawTextLeft(st::searchedBarPosition.x(), st::searchedBarPosition.y(), width(), text); p.drawTextLeft(st::searchedBarPosition.x(), st::searchedBarPosition.y(), width(), text);
p.translate(0, st::searchedBarHeight); p.translate(0, st::searchedBarHeight);
top += st::searchedBarHeight;
} }
} else { } else {
const auto text = showUnreadInSearchResults const auto text = showUnreadInSearchResults
@ -966,13 +941,11 @@ void InnerWidget::paintEvent(QPaintEvent *e) {
p.setPen(st::searchedBarFg); p.setPen(st::searchedBarFg);
p.drawTextLeft(st::searchedBarPosition.x(), st::searchedBarPosition.y(), width(), text); p.drawTextLeft(st::searchedBarPosition.x(), st::searchedBarPosition.y(), width(), text);
p.translate(0, st::searchedBarHeight); p.translate(0, st::searchedBarHeight);
top += st::searchedBarHeight;
auto skip = searchedOffset(); auto skip = searchedOffset();
auto from = floorclamp(r.y() - skip, _st->height, 0, _searchResults.size()); auto from = floorclamp(r.y() - skip, _st->height, 0, _searchResults.size());
auto to = ceilclamp(r.y() + r.height() - skip, _st->height, 0, _searchResults.size()); auto to = ceilclamp(r.y() + r.height() - skip, _st->height, 0, _searchResults.size());
p.translate(0, from * _st->height); p.translate(0, from * _st->height);
top += from * _st->height;
if (from < _searchResults.size()) { if (from < _searchResults.size()) {
for (; from < to; ++from) { for (; from < to; ++from) {
const auto &result = _searchResults[from]; const auto &result = _searchResults[from];
@ -1000,7 +973,6 @@ void InnerWidget::paintEvent(QPaintEvent *e) {
.displayUnreadInfo = showUnreadInSearchResults, .displayUnreadInfo = showUnreadInSearchResults,
}); });
p.translate(0, _st->height); p.translate(0, _st->height);
top += _st->height;
} }
} }
} }
@ -1211,111 +1183,112 @@ void InnerWidget::paintSearchTags(
const auto position = QPoint(_searchTagsLeft, top); const auto position = QPoint(_searchTagsLeft, top);
_searchTags->paint(p, position, context.now, context.paused); _searchTags->paint(p, position, context.now, context.paused);
} }
//
void InnerWidget::paintSearchInChat( //void InnerWidget::paintSearchInChat(
Painter &p, // Painter &p,
const Ui::PaintContext &context) const { // const Ui::PaintContext &context) const {
auto height = searchInChatSkip(); // auto height = searchInChatSkip();
//
auto top = 0; // auto top = 0;
p.setFont(st::searchedBarFont); // p.setFont(st::searchedBarFont);
auto fullRect = QRect(0, top, width(), height - top); // auto fullRect = QRect(0, top, width(), height - top);
p.fillRect(fullRect, currentBg()); // p.fillRect(fullRect, currentBg());
if (_searchFromShown) { // if (_searchFromShown) {
p.setPen(st::dialogsTextFg); // p.setPen(st::dialogsTextFg);
p.setTextPalette(st::dialogsSearchFromPalette); // p.setTextPalette(st::dialogsSearchFromPalette);
paintSearchInPeer(p, _searchFromShown, _searchFromUserUserpic, top, _searchFromUserText); // paintSearchInPeer(p, _searchFromShown, _searchFromUserUserpic, top, _searchFromUserText);
p.restoreTextPalette(); // p.restoreTextPalette();
} // }
} //}
template <typename PaintUserpic> //
void InnerWidget::paintSearchInFilter( //template <typename PaintUserpic>
Painter &p, //void InnerWidget::paintSearchInFilter(
PaintUserpic paintUserpic, // Painter &p,
int top, // PaintUserpic paintUserpic,
const style::icon *icon, // int top,
const Ui::Text::String &text) const { // const style::icon *icon,
const auto savedPen = p.pen(); // const Ui::Text::String &text) const {
const auto userpicLeft = st::defaultDialogRow.padding.left(); // const auto savedPen = p.pen();
const auto userpicTop = top // const auto userpicLeft = st::defaultDialogRow.padding.left();
+ (st::dialogsSearchInHeight - st::dialogsSearchInPhotoSize) / 2; // const auto userpicTop = top
paintUserpic(p, userpicLeft, userpicTop, st::dialogsSearchInPhotoSize); // + (st::dialogsSearchInHeight - st::dialogsSearchInPhotoSize) / 2;
// paintUserpic(p, userpicLeft, userpicTop, st::dialogsSearchInPhotoSize);
const auto nameleft = st::defaultDialogRow.padding.left() //
+ st::dialogsSearchInPhotoSize // const auto nameleft = st::defaultDialogRow.padding.left()
+ st::dialogsSearchInPhotoPadding; // + st::dialogsSearchInPhotoSize
const auto namewidth = width() // + st::dialogsSearchInPhotoPadding;
- nameleft // const auto namewidth = width()
- st::defaultDialogRow.padding.left() // - nameleft
- st::defaultDialogRow.padding.right() // - st::defaultDialogRow.padding.left()
- st::dialogsCancelSearch.width; // - st::defaultDialogRow.padding.right()
auto rectForName = QRect( // - st::dialogsCancelSearch.width;
nameleft, // auto rectForName = QRect(
top + (st::dialogsSearchInHeight - st::semiboldFont->height) / 2, // nameleft,
namewidth, // top + (st::dialogsSearchInHeight - st::semiboldFont->height) / 2,
st::semiboldFont->height); // namewidth,
if (icon) { // st::semiboldFont->height);
icon->paint(p, rectForName.topLeft(), width()); // if (icon) {
rectForName.setLeft(rectForName.left() // icon->paint(p, rectForName.topLeft(), width());
+ icon->width() // rectForName.setLeft(rectForName.left()
+ st::dialogsChatTypeSkip); // + icon->width()
} // + st::dialogsChatTypeSkip);
p.setPen(savedPen); // }
text.drawLeftElided( // p.setPen(savedPen);
p, // text.drawLeftElided(
rectForName.left(), // p,
rectForName.top(), // rectForName.left(),
rectForName.width(), // rectForName.top(),
width()); // rectForName.width(),
} // width());
//}
void InnerWidget::paintSearchInPeer( //
Painter &p, //void InnerWidget::paintSearchInPeer(
not_null<PeerData*> peer, // Painter &p,
Ui::PeerUserpicView &userpic, // not_null<PeerData*> peer,
int top, // Ui::PeerUserpicView &userpic,
const Ui::Text::String &text) const { // int top,
const auto paintUserpic = [&](Painter &p, int x, int y, int size) { // const Ui::Text::String &text) const {
peer->paintUserpicLeft(p, userpic, x, y, width(), size); // const auto paintUserpic = [&](Painter &p, int x, int y, int size) {
}; // peer->paintUserpicLeft(p, userpic, x, y, width(), size);
const auto icon = Ui::ChatTypeIcon(peer); // };
paintSearchInFilter(p, paintUserpic, top, icon, text); // const auto icon = Ui::ChatTypeIcon(peer);
} // paintSearchInFilter(p, paintUserpic, top, icon, text);
//}
void InnerWidget::paintSearchInSaved( //
Painter &p, //void InnerWidget::paintSearchInSaved(
int top, // Painter &p,
const Ui::Text::String &text) const { // int top,
const auto paintUserpic = [&](Painter &p, int x, int y, int size) { // const Ui::Text::String &text) const {
Ui::EmptyUserpic::PaintSavedMessages(p, x, y, width(), size); // const auto paintUserpic = [&](Painter &p, int x, int y, int size) {
}; // Ui::EmptyUserpic::PaintSavedMessages(p, x, y, width(), size);
paintSearchInFilter(p, paintUserpic, top, nullptr, text); // };
} // paintSearchInFilter(p, paintUserpic, top, nullptr, text);
//}
void InnerWidget::paintSearchInReplies( //
Painter &p, //void InnerWidget::paintSearchInReplies(
int top, // Painter &p,
const Ui::Text::String &text) const { // int top,
const auto paintUserpic = [&](Painter &p, int x, int y, int size) { // const Ui::Text::String &text) const {
Ui::EmptyUserpic::PaintRepliesMessages(p, x, y, width(), size); // const auto paintUserpic = [&](Painter &p, int x, int y, int size) {
}; // Ui::EmptyUserpic::PaintRepliesMessages(p, x, y, width(), size);
paintSearchInFilter(p, paintUserpic, top, nullptr, text); // };
} // paintSearchInFilter(p, paintUserpic, top, nullptr, text);
//}
void InnerWidget::paintSearchInTopic( //
Painter &p, //void InnerWidget::paintSearchInTopic(
const Ui::PaintContext &context, // Painter &p,
not_null<Data::ForumTopic*> topic, // const Ui::PaintContext &context,
Ui::PeerUserpicView &userpic, // not_null<Data::ForumTopic*> topic,
int top, // Ui::PeerUserpicView &userpic,
const Ui::Text::String &text) const { // int top,
const auto paintUserpic = [&](Painter &p, int x, int y, int size) { // const Ui::Text::String &text) const {
p.translate(x, y); // const auto paintUserpic = [&](Painter &p, int x, int y, int size) {
topic->paintUserpic(p, userpic, context); // p.translate(x, y);
p.translate(-x, -y); // topic->paintUserpic(p, userpic, context);
}; // p.translate(-x, -y);
paintSearchInFilter(p, paintUserpic, top, nullptr, text); // };
} // paintSearchInFilter(p, paintUserpic, top, nullptr, text);
//}
void InnerWidget::mouseMoveEvent(QMouseEvent *e) { void InnerWidget::mouseMoveEvent(QMouseEvent *e) {
if (_chatPreviewTouchGlobal || _touchDragStartGlobal) { if (_chatPreviewTouchGlobal || _touchDragStartGlobal) {
@ -1374,7 +1347,7 @@ void InnerWidget::selectByMouse(QPoint globalPosition) {
const auto tagBase = QPoint( const auto tagBase = QPoint(
_searchTagsLeft, _searchTagsLeft,
searchTagsOffset() + st::dialogsSearchTagBottom / 2); st::dialogsSearchTagBottom / 2);
const auto tagPoint = local - tagBase; const auto tagPoint = local - tagBase;
const auto inTags = _searchTags const auto inTags = _searchTags
&& QRect( && QRect(
@ -1425,7 +1398,7 @@ void InnerWidget::selectByMouse(QPoint globalPosition) {
_hashtagSelected = -1; _hashtagSelected = -1;
_hashtagDeleteSelected = false; _hashtagDeleteSelected = false;
} else { } else {
auto skip = 0; auto skip = hashtagsOffset();
auto hashtagSelected = (mouseY >= skip) ? ((mouseY - skip) / st::mentionHeight) : -1; auto hashtagSelected = (mouseY >= skip) ? ((mouseY - skip) / st::mentionHeight) : -1;
if (hashtagSelected < 0 || hashtagSelected >= _hashtagResults.size()) { if (hashtagSelected < 0 || hashtagSelected >= _hashtagResults.size()) {
hashtagSelected = -1; hashtagSelected = -1;
@ -1558,8 +1531,9 @@ void InnerWidget::mousePressEvent(QMouseEvent *e) {
_dragStart = e->pos(); _dragStart = e->pos();
} else if (base::in_range(_hashtagPressed, 0, _hashtagResults.size()) && !_hashtagDeletePressed) { } else if (base::in_range(_hashtagPressed, 0, _hashtagResults.size()) && !_hashtagDeletePressed) {
auto row = &_hashtagResults[_hashtagPressed]->row; auto row = &_hashtagResults[_hashtagPressed]->row;
row->addRipple(e->pos(), QSize(width(), st::mentionHeight), [this, index = _hashtagPressed] { const auto origin = e->pos() - QPoint(0, hashtagsOffset() + _hashtagPressed * st::mentionHeight);
update(0, index * st::mentionHeight, width(), st::mentionHeight); row->addRipple(origin, QSize(width(), st::mentionHeight), [this, index = _hashtagPressed] {
update(0, hashtagsOffset() + index * st::mentionHeight, width(), st::mentionHeight);
}); });
} else if (base::in_range(_filteredPressed, 0, _filterResults.size())) { } else if (base::in_range(_filteredPressed, 0, _filterResults.size())) {
const auto &result = _filterResults[_filteredPressed]; const auto &result = _filterResults[_filteredPressed];
@ -1989,17 +1963,18 @@ void InnerWidget::resizeEvent(QResizeEvent *e) {
_searchTags->resizeToWidth(width() - 2 * _searchTagsLeft); _searchTags->resizeToWidth(width() - 2 * _searchTagsLeft);
} }
resizeEmpty(); resizeEmpty();
moveCancelSearchButtons(); moveSearchIn();
} }
void InnerWidget::moveCancelSearchButtons() { void InnerWidget::moveSearchIn() {
const auto widthForCancelButton = qMax( if (!_searchIn) {
return;
}
const auto searchInWidth = std::max(
width(), width(),
st::columnMinimalWidthLeft - _narrowWidth); st::columnMinimalWidthLeft - _narrowWidth);
const auto left = widthForCancelButton - st::dialogsSearchInSkip - _cancelSearchFromUser->width(); _searchIn->resizeToWidth(searchInWidth);
const auto top = (st::dialogsSearchInHeight - st::dialogsCancelSearchInPeer.height) / 2; _searchIn->moveToLeft(0, searchInChatOffset());
const auto skip = (_searchTags ? _searchTags->height() : 0);
_cancelSearchFromUser->moveToLeft(left, skip + top);
} }
void InnerWidget::dialogRowReplaced( void InnerWidget::dialogRowReplaced(
@ -2326,7 +2301,7 @@ void InnerWidget::updateSelectedRow(Key key) {
} }
} }
} else if (_hashtagSelected >= 0) { } else if (_hashtagSelected >= 0) {
update(0, _hashtagSelected * st::mentionHeight, width(), st::mentionHeight); update(0, hashtagsOffset() + _hashtagSelected * st::mentionHeight, width(), st::mentionHeight);
} else if (_filteredSelected >= 0) { } else if (_filteredSelected >= 0) {
if (_filteredSelected < _filterResults.size()) { if (_filteredSelected < _filterResults.size()) {
const auto &result = _filterResults[_filteredSelected]; const auto &result = _filterResults[_filteredSelected];
@ -2639,7 +2614,7 @@ void InnerWidget::applySearchState(SearchState state) {
_searchTags->repaintRequests() | rpl::start_with_next([=] { _searchTags->repaintRequests() | rpl::start_with_next([=] {
const auto height = _searchTags->height(); const auto height = _searchTags->height();
update(0, searchTagsOffset(), width(), height); update(0, 0, width(), height);
}, _searchTags->lifetime()); }, _searchTags->lifetime());
_searchTags->menuRequests( _searchTags->menuRequests(
@ -2656,7 +2631,7 @@ void InnerWidget::applySearchState(SearchState state) {
1 1
) | rpl::start_with_next([=] { ) | rpl::start_with_next([=] {
refresh(); refresh();
moveCancelSearchButtons(); moveSearchIn();
}, _searchTags->lifetime()); }, _searchTags->lifetime());
} else { } else {
_searchTags = nullptr; _searchTags = nullptr;
@ -2670,17 +2645,12 @@ void InnerWidget::applySearchState(SearchState state) {
if (state.inChat) { if (state.inChat) {
onHashtagFilterUpdate(QStringView()); onHashtagFilterUpdate(QStringView());
} }
if (_searchFromShown) {
_cancelSearchFromUser->show();
_searchFromUserUserpic = _searchFromShown->createUserpicView();
} else {
_cancelSearchFromUser->hide();
_searchFromUserUserpic = {};
}
refreshSearchInChatLabel();
moveCancelSearchButtons();
_searchState = std::move(state); _searchState = std::move(state);
_searchingHashtag = IsHashtagSearchQuery(_searchState.query);
updateSearchIn();
moveSearchIn();
auto newFilter = _searchState.query; auto newFilter = _searchState.query;
const auto mentionsSearch = (newFilter == u"@"_q); const auto mentionsSearch = (newFilter == u"@"_q);
const auto words = mentionsSearch const auto words = mentionsSearch
@ -2924,12 +2894,20 @@ rpl::producer<> InnerWidget::listBottomReached() const {
return _listBottomReached.events(); return _listBottomReached.events();
} }
rpl::producer<> InnerWidget::cancelSearchFromUserRequests() const { rpl::producer<ChatSearchTab> InnerWidget::changeSearchTabRequests() const {
return _cancelSearchFromUser->clicks() | rpl::to_empty; return _changeSearchTabRequests.events();
} }
rpl::producer<> InnerWidget::cancelSearchRequests() const { rpl::producer<> InnerWidget::cancelSearchRequests() const {
return _cancelSearch.events(); return _cancelSearchRequests.events();
}
rpl::producer<> InnerWidget::cancelSearchFromRequests() const {
return _cancelSearchFromRequests.events();
}
rpl::producer<> InnerWidget::changeSearchFromRequests() const {
return _changeSearchFromRequests.events();
} }
rpl::producer<Ui::ScrollToRequest> InnerWidget::mustScrollTo() const { rpl::producer<Ui::ScrollToRequest> InnerWidget::mustScrollTo() const {
@ -3200,9 +3178,6 @@ void InnerWidget::refreshEmpty() {
} else if (_searchEmptyState != _searchState) { } else if (_searchEmptyState != _searchState) {
_searchEmptyState = _searchState; _searchEmptyState = _searchState;
_searchEmpty = MakeSearchEmpty(this, _searchState); _searchEmpty = MakeSearchEmpty(this, _searchState);
_searchEmpty->linkClicks() | rpl::start_with_next([=] {
_cancelSearch.fire({});
}, _searchEmpty->lifetime());
if (_controller->session().data().chatsListLoaded()) { if (_controller->session().data().chatsListLoaded()) {
_searchEmpty->animate(); _searchEmpty->animate();
} }
@ -3342,19 +3317,76 @@ auto InnerWidget::searchTagsChanges() const
: rpl::never<std::vector<Data::ReactionId>>(); : rpl::never<std::vector<Data::ReactionId>>();
} }
void InnerWidget::refreshSearchInChatLabel() { void InnerWidget::updateSearchIn() {
const auto from = _searchFromShown ? _searchFromShown->name() : u""_q; if (!_searchState.inChat && !_searchingHashtag) {
if (!from.isEmpty()) { _searchIn = nullptr;
const auto fromUserText = tr::lng_dlg_search_from( return;
tr::now, } else if (!_searchIn) {
lt_user, _searchIn = std::make_unique<ChatSearchIn>(this);
Ui::Text::Semibold(from), _searchIn->show();
Ui::Text::WithEntities); _searchIn->changeFromRequests() | rpl::start_to_stream(
_searchFromUserText.setMarkedText( _changeSearchFromRequests,
st::dialogsSearchFromStyle, _searchIn->lifetime());
fromUserText, _searchIn->cancelFromRequests() | rpl::start_to_stream(
Ui::DialogTextOptions()); _cancelSearchFromRequests,
_searchIn->lifetime());
_searchIn->cancelInRequests() | rpl::start_to_stream(
_cancelSearchRequests,
_searchIn->lifetime());
_searchIn->tabChanges() | rpl::start_to_stream(
_changeSearchTabRequests,
_searchIn->lifetime());
} }
const auto sublist = _searchState.inChat.sublist();
const auto topic = _searchState.inChat.topic();
const auto peer = _searchState.inChat.owningHistory()
? _searchState.inChat.owningHistory()->peer.get()
: _openedForum
? _openedForum->channel().get()
: nullptr;
const auto topicIcon = !topic
? nullptr
: topic->iconId()
? Ui::MakeEmojiThumbnail(
&topic->owner(),
Data::SerializeCustomEmojiId(topic->iconId()))
: Ui::MakeEmojiThumbnail(
&topic->owner(),
Data::TopicIconEmojiEntity({
.title = (topic->isGeneral()
? Data::ForumGeneralIconTitle()
: topic->title()),
.colorId = (topic->isGeneral()
? Data::ForumGeneralIconColor(st::windowSubTextFg->c)
: topic->colorId()),
}));
const auto peerIcon = peer
? Ui::MakeUserpicThumbnail(peer)
: sublist
? Ui::MakeUserpicThumbnail(sublist->peer())
: nullptr;
const auto myIcon = Ui::MakeIconThumbnail(st::menuIconChats);
const auto publicIcon = _searchingHashtag
? Ui::MakeIconThumbnail(st::menuIconChannel)
: nullptr;
const auto peerTabType = (peer && peer->isBroadcast())
? ChatSearchPeerTabType::Channel
: (peer && (peer->isChat() || peer->isMegagroup()))
? ChatSearchPeerTabType::Group
: ChatSearchPeerTabType::Chat;
const auto fromImage = _searchFromShown
? Ui::MakeUserpicThumbnail(_searchFromShown)
: nullptr;
const auto fromName = _searchFromShown
? _searchFromShown->shortName()
: QString();
_searchIn->apply({
{ ChatSearchTab::ThisTopic, topicIcon },
{ ChatSearchTab::ThisPeer, peerIcon },
{ ChatSearchTab::MyMessages, myIcon },
{ ChatSearchTab::PublicPosts, publicIcon },
}, _searchState.tab, peerTabType, fromImage, fromName);
} }
void InnerWidget::repaintSearchResult(int index) { void InnerWidget::repaintSearchResult(int index) {

View file

@ -61,6 +61,7 @@ class FakeRow;
class IndexedList; class IndexedList;
class SearchTags; class SearchTags;
class SearchEmpty; class SearchEmpty;
class ChatSearchIn;
struct ChosenRow { struct ChosenRow {
Key key; Key key;
@ -156,8 +157,11 @@ public:
void setLoadMoreCallback(Fn<void()> callback); void setLoadMoreCallback(Fn<void()> callback);
void setLoadMoreFilteredCallback(Fn<void()> callback); void setLoadMoreFilteredCallback(Fn<void()> callback);
[[nodiscard]] rpl::producer<> listBottomReached() const; [[nodiscard]] rpl::producer<> listBottomReached() const;
[[nodiscard]] rpl::producer<> cancelSearchFromUserRequests() const; [[nodiscard]] auto changeSearchTabRequests() const
-> rpl::producer<ChatSearchTab>;
[[nodiscard]] rpl::producer<> cancelSearchRequests() const; [[nodiscard]] rpl::producer<> cancelSearchRequests() const;
[[nodiscard]] rpl::producer<> cancelSearchFromRequests() const;
[[nodiscard]] rpl::producer<> changeSearchFromRequests() const;
[[nodiscard]] rpl::producer<ChosenRow> chosenRow() const; [[nodiscard]] rpl::producer<ChosenRow> chosenRow() const;
[[nodiscard]] rpl::producer<> updated() const; [[nodiscard]] rpl::producer<> updated() const;
@ -339,10 +343,10 @@ private:
[[nodiscard]] int filteredIndex(int y) const; [[nodiscard]] int filteredIndex(int y) const;
[[nodiscard]] int filteredHeight(int till = -1) const; [[nodiscard]] int filteredHeight(int till = -1) const;
[[nodiscard]] int peerSearchOffset() const; [[nodiscard]] int peerSearchOffset() const;
[[nodiscard]] int searchTagsOffset() const;
[[nodiscard]] int searchInChatOffset() const; [[nodiscard]] int searchInChatOffset() const;
[[nodiscard]] int searchedOffset() const; [[nodiscard]] int searchedOffset() const;
[[nodiscard]] int searchInChatSkip() const; [[nodiscard]] int searchInChatSkip() const;
[[nodiscard]] int hashtagsOffset() const;
void paintCollapsedRows( void paintCollapsedRows(
Painter &p, Painter &p,
@ -358,38 +362,38 @@ private:
void paintSearchTags( void paintSearchTags(
Painter &p, Painter &p,
const Ui::PaintContext &context) const; const Ui::PaintContext &context) const;
void paintSearchInChat( //void paintSearchInChat(
Painter &p, // Painter &p,
const Ui::PaintContext &context) const; // const Ui::PaintContext &context) const;
void paintSearchInPeer( //void paintSearchInPeer(
Painter &p, // Painter &p,
not_null<PeerData*> peer, // not_null<PeerData*> peer,
Ui::PeerUserpicView &userpic, // Ui::PeerUserpicView &userpic,
int top, // int top,
const Ui::Text::String &text) const; // const Ui::Text::String &text) const;
void paintSearchInSaved( //void paintSearchInSaved(
Painter &p, // Painter &p,
int top, // int top,
const Ui::Text::String &text) const; // const Ui::Text::String &text) const;
void paintSearchInReplies( //void paintSearchInReplies(
Painter &p, // Painter &p,
int top, // int top,
const Ui::Text::String &text) const; // const Ui::Text::String &text) const;
void paintSearchInTopic( //void paintSearchInTopic(
Painter &p, // Painter &p,
const Ui::PaintContext &context, // const Ui::PaintContext &context,
not_null<Data::ForumTopic*> topic, // not_null<Data::ForumTopic*> topic,
Ui::PeerUserpicView &userpic, // Ui::PeerUserpicView &userpic,
int top, // int top,
const Ui::Text::String &text) const; // const Ui::Text::String &text) const;
template <typename PaintUserpic> //template <typename PaintUserpic>
void paintSearchInFilter( //void paintSearchInFilter(
Painter &p, // Painter &p,
PaintUserpic paintUserpic, // PaintUserpic paintUserpic,
int top, // int top,
const style::icon *icon, // const style::icon *icon,
const Ui::Text::String &text) const; // const Ui::Text::String &text) const;
void refreshSearchInChatLabel(); void updateSearchIn();
void repaintSearchResult(int index); void repaintSearchResult(int index);
Ui::VideoUserpic *validateVideoUserpic(not_null<Row*> row); Ui::VideoUserpic *validateVideoUserpic(not_null<Row*> row);
@ -415,7 +419,7 @@ private:
void savePinnedOrder(); void savePinnedOrder();
bool pinnedShiftAnimationCallback(crl::time now); bool pinnedShiftAnimationCallback(crl::time now);
void handleChatListEntryRefreshes(); void handleChatListEntryRefreshes();
void moveCancelSearchButtons(); void moveSearchIn();
void dragPinnedFromTouch(); void dragPinnedFromTouch();
void saveChatsFilterScrollState(FilterId filterId); void saveChatsFilterScrollState(FilterId filterId);
@ -490,19 +494,22 @@ private:
WidgetState _state = WidgetState::Default; WidgetState _state = WidgetState::Default;
std::unique_ptr<ChatSearchIn> _searchIn;
rpl::event_stream<ChatSearchTab> _changeSearchTabRequests;
rpl::event_stream<> _cancelSearchRequests;
rpl::event_stream<> _cancelSearchFromRequests;
rpl::event_stream<> _changeSearchFromRequests;
object_ptr<Ui::RpWidget> _loadingAnimation = { nullptr }; object_ptr<Ui::RpWidget> _loadingAnimation = { nullptr };
object_ptr<SearchEmpty> _searchEmpty = { nullptr }; object_ptr<SearchEmpty> _searchEmpty = { nullptr };
SearchState _searchEmptyState; SearchState _searchEmptyState;
object_ptr<Ui::FlatLabel> _empty = { nullptr }; object_ptr<Ui::FlatLabel> _empty = { nullptr };
object_ptr<Ui::IconButton> _cancelSearchFromUser;
rpl::event_stream<> _cancelSearch;
Ui::DraggingScrollManager _draggingScroll; Ui::DraggingScrollManager _draggingScroll;
SearchState _searchState; SearchState _searchState;
bool _searchingHashtag = false;
History *_searchInMigrated = nullptr; History *_searchInMigrated = nullptr;
PeerData *_searchFromShown = nullptr; PeerData *_searchFromShown = nullptr;
mutable Ui::PeerUserpicView _searchFromUserUserpic;
Ui::Text::String _searchFromUserText; Ui::Text::String _searchFromUserText;
std::unique_ptr<SearchTags> _searchTags; std::unique_ptr<SearchTags> _searchTags;
int _searchTagsLeft = 0; int _searchTagsLeft = 0;

View file

@ -10,7 +10,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_folder.h" #include "data/data_folder.h"
#include "data/data_forum_topic.h" #include "data/data_forum_topic.h"
#include "data/data_saved_sublist.h" #include "data/data_saved_sublist.h"
#include "dialogs/ui/chat_search_tabs.h" #include "dialogs/ui/chat_search_in.h"
#include "history/history.h" #include "history/history.h"
namespace Dialogs { namespace Dialogs {

View file

@ -9,7 +9,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "base/qt/qt_key_modifiers.h" #include "base/qt/qt_key_modifiers.h"
#include "base/options.h" #include "base/options.h"
#include "dialogs/ui/chat_search_tabs.h" #include "dialogs/ui/chat_search_in.h"
#include "dialogs/ui/dialogs_stories_content.h" #include "dialogs/ui/dialogs_stories_content.h"
#include "dialogs/ui/dialogs_stories_list.h" #include "dialogs/ui/dialogs_stories_list.h"
#include "dialogs/ui/dialogs_suggestions.h" #include "dialogs/ui/dialogs_suggestions.h"
@ -340,7 +340,23 @@ Widget::Widget(
) | rpl::start_with_next([=] { ) | rpl::start_with_next([=] {
searchCursorMoved(); searchCursorMoved();
}, lifetime()); }, lifetime());
_inner->cancelSearchFromUserRequests( _inner->changeSearchTabRequests(
) | rpl::filter([=](ChatSearchTab tab) {
return _searchState.tab != tab;
}) | rpl::start_with_next([=](ChatSearchTab tab) {
auto copy = _searchState;
copy.tab = tab;
applySearchState(std::move(copy));
}, lifetime());
_inner->cancelSearchRequests(
) | rpl::start_with_next([=] {
cancelSearch({
.forceFullCancel = true,
.jumpBackToSearchedChat = true,
});
controller->widget()->setInnerFocus();
}, lifetime());
_inner->cancelSearchFromRequests(
) | rpl::start_with_next([=] { ) | rpl::start_with_next([=] {
auto copy = _searchState; auto copy = _searchState;
copy.fromPeer = nullptr; copy.fromPeer = nullptr;
@ -349,10 +365,9 @@ Widget::Widget(
} }
applySearchState(std::move(copy)); applySearchState(std::move(copy));
}, lifetime()); }, lifetime());
_inner->cancelSearchRequests( _inner->changeSearchFromRequests(
) | rpl::start_with_next([=] { ) | rpl::start_with_next([=] {
setInnerFocus(true); showSearchFrom();
applySearchState({});
}, lifetime()); }, lifetime());
_inner->chosenRow( _inner->chosenRow(
) | rpl::start_with_next([=](const ChosenRow &row) { ) | rpl::start_with_next([=](const ChosenRow &row) {
@ -410,7 +425,9 @@ Widget::Widget(
}, lifetime()); }, lifetime());
} }
_cancelSearch->setClickedCallback([this] { cancelSearch(); }); _cancelSearch->setClickedCallback([this] {
cancelSearch({ .jumpBackToSearchedChat = true });
});
_jumpToDate->entity()->setClickedCallback([this] { showCalendar(); }); _jumpToDate->entity()->setClickedCallback([this] { showCalendar(); });
_chooseFromUser->entity()->setClickedCallback([this] { showSearchFrom(); }); _chooseFromUser->entity()->setClickedCallback([this] { showSearchFrom(); });
rpl::single(rpl::empty) | rpl::then( rpl::single(rpl::empty) | rpl::then(
@ -680,7 +697,7 @@ void Widget::setupMoreChatsBar() {
controller()->activeChatsFilter( controller()->activeChatsFilter(
) | rpl::start_with_next([=](FilterId id) { ) | rpl::start_with_next([=](FilterId id) {
storiesToggleExplicitExpand(false); storiesToggleExplicitExpand(false);
const auto cancelled = cancelSearch(true); const auto cancelled = cancelSearch({ .forceFullCancel = true });
const auto guard = gsl::finally([&] { const auto guard = gsl::finally([&] {
if (cancelled) { if (cancelled) {
controller()->content()->dialogsCancelled(); controller()->content()->dialogsCancelled();
@ -1113,9 +1130,6 @@ void Widget::updateControlsVisibility(bool fast) {
updateJumpToDateVisibility(fast); updateJumpToDateVisibility(fast);
updateSearchFromVisibility(fast); updateSearchFromVisibility(fast);
} }
if (_searchTabs) {
_searchTabs->show();
}
if (_connecting) { if (_connecting) {
_connecting->setForceHidden(false); _connecting->setForceHidden(false);
} }
@ -1170,7 +1184,7 @@ bool Widget::cancelSearchByMouseBack() {
return _searchHasFocus return _searchHasFocus
&& !_searchSuggestionsLocked && !_searchSuggestionsLocked
&& !_searchState.inChat && !_searchState.inChat
&& cancelSearch(); && cancelSearch({ .jumpBackToSearchedChat = true });
} }
void Widget::processSearchFocusChange() { void Widget::processSearchFocusChange() {
@ -1259,102 +1273,6 @@ void Widget::updateSuggestions(anim::type animated) {
} }
} }
void Widget::updateSearchTabs() {
const auto has = _searchState.inChat || _searchingHashtag;
if (!has) {
if (_searchTabs) {
_searchTabs = nullptr;
updateControlsGeometry();
}
return;
} else if (!_searchTabs) {
const auto savedSession = &session();
const auto markedTextContext = [=](Fn<void()> repaint) {
return Core::MarkedTextContext{
.session = savedSession,
.customEmojiRepaint = std::move(repaint),
};
};
_searchTabs = std::make_unique<ChatSearchTabs>(
this,
_searchState.tab,
std::move(markedTextContext));
_searchTabs->setVisible(!_showAnimation);
_searchTabs->tabChanges(
) | rpl::filter([=](ChatSearchTab tab) {
return (_searchState.tab != tab);
}) | rpl::start_with_next([=](ChatSearchTab tab) {
auto copy = _searchState;
copy.tab = tab;
applySearchState(std::move(copy));
}, _searchTabs->lifetime());
}
const auto sublist = _searchState.inChat.sublist();
const auto topic = _searchState.inChat.topic();
const auto peer = _searchState.inChat.owningHistory()
? _searchState.inChat.owningHistory()->peer.get()
: _openedForum
? _openedForum->channel().get()
: nullptr;
const auto topicShortLabel = !topic
? TextWithEntities()
: topic->iconId()
? Ui::Text::SingleCustomEmoji(
Data::SerializeCustomEmojiId(topic->iconId()))
: Ui::Text::SingleCustomEmoji(Data::TopicIconEmojiEntity({
.title = (topic->isGeneral()
? Data::ForumGeneralIconTitle()
: topic->title()),
.colorId = (topic->isGeneral()
? Data::ForumGeneralIconColor(st::windowSubTextFg->c)
: topic->colorId()),
}));
const auto peerShortLabel = peer
? Ui::Text::SingleCustomEmoji(
session().data().customEmojiManager().peerUserpicEmojiData(
peer,
{},
true))
: sublist
? Ui::Text::SingleCustomEmoji(
session().data().customEmojiManager().peerUserpicEmojiData(
sublist->peer(),
{},
true))
: TextWithEntities();
const auto myShortLabel = DefaultShortLabel(ChatSearchTab::MyMessages);
const auto publicShortLabel = _searchingHashtag
? DefaultShortLabel(ChatSearchTab::PublicPosts)
: TextWithEntities();
if ((_searchState.tab == ChatSearchTab::ThisTopic
&& !_searchState.inChat.topic())
|| (_searchState.tab == ChatSearchTab::ThisPeer
&& !_searchState.inChat
&& !_openedForum)
|| (_searchState.tab == ChatSearchTab::PublicPosts
&& !_searchingHashtag)) {
_searchState.tab = _searchState.inChat.topic()
? ChatSearchTab::ThisTopic
: (_searchState.inChat.owningHistory()
|| _searchState.inChat.sublist())
? ChatSearchTab::ThisPeer
: ChatSearchTab::MyMessages;
}
const auto peerTabType = (peer && peer->isBroadcast())
? ChatSearchPeerTabType::Channel
: (peer && (peer->isChat() || peer->isMegagroup()))
? ChatSearchPeerTabType::Group
: ChatSearchPeerTabType::Chat;
_searchTabs->setTabShortLabels({
{ ChatSearchTab::ThisTopic, topicShortLabel },
{ ChatSearchTab::ThisPeer, peerShortLabel },
{ ChatSearchTab::MyMessages, myShortLabel },
{ ChatSearchTab::PublicPosts, publicShortLabel },
}, _searchState.tab, peerTabType);
updateControlsGeometry();
}
void Widget::changeOpenedSubsection( void Widget::changeOpenedSubsection(
FnMut<void()> change, FnMut<void()> change,
bool fromRight, bool fromRight,
@ -1405,7 +1323,7 @@ void Widget::changeOpenedFolder(Data::Folder *folder, anim::type animated) {
return; return;
} }
changeOpenedSubsection([&] { changeOpenedSubsection([&] {
cancelSearch(true); cancelSearch({ .forceFullCancel = true });
closeChildList(anim::type::instant); closeChildList(anim::type::instant);
controller()->closeForum(); controller()->closeForum();
_openedFolder = folder; _openedFolder = folder;
@ -1459,7 +1377,7 @@ void Widget::changeOpenedForum(Data::Forum *forum, anim::type animated) {
return; return;
} }
changeOpenedSubsection([&] { changeOpenedSubsection([&] {
cancelSearch(true); cancelSearch({ .forceFullCancel = true });
closeChildList(anim::type::instant); closeChildList(anim::type::instant);
_openedForum = forum; _openedForum = forum;
_searchState.tab = forum _searchState.tab = forum
@ -1949,7 +1867,7 @@ void Widget::slideFinished() {
} }
void Widget::escape() { void Widget::escape() {
if (!cancelSearch()) { if (!cancelSearch({ .jumpBackToSearchedChat = true })) {
if (controller()->shownForum().current()) { if (controller()->shownForum().current()) {
controller()->closeForum(); controller()->closeForum();
} else if (controller()->openedFolder().current()) { } else if (controller()->openedFolder().current()) {
@ -2756,9 +2674,8 @@ QString Widget::validateSearchQuery() {
setSearchQuery(fixed.text, fixed.cursorPosition); setSearchQuery(fixed.text, fixed.cursorPosition);
} }
return fixed.text; return fixed.text;
} else if (_searchingHashtag != IsHashtagSearchQuery(query)) { } else {
_searchingHashtag = !_searchingHashtag; _searchingHashtag = IsHashtagSearchQuery(query);
updateSearchTabs();
} }
return query; return query;
} }
@ -2804,7 +2721,7 @@ void Widget::showForum(
changeOpenedForum(forum, params.animated); changeOpenedForum(forum, params.animated);
return; return;
} }
cancelSearch(true); cancelSearch({ .forceFullCancel = true });
openChildList(forum, params); openChildList(forum, params);
} }
@ -2935,6 +2852,9 @@ bool Widget::applySearchState(SearchState state) {
} }
hideChildList(); hideChildList();
} }
if (state.inChat && _layout == Layout::Main) {
controller()->closeFolder();
}
// Adjust state to be consistent. // Adjust state to be consistent.
if (const auto peer = state.inChat.peer()) { if (const auto peer = state.inChat.peer()) {
@ -2956,7 +2876,7 @@ bool Widget::applySearchState(SearchState state) {
state.tab = (_openedForum && !state.inChat) state.tab = (_openedForum && !state.inChat)
? ChatSearchTab::ThisPeer ? ChatSearchTab::ThisPeer
: ChatSearchTab::MyMessages; : ChatSearchTab::MyMessages;
} else if (!state.inChat && !_searchTabs) { } else if (!state.inChat && !_searchingHashtag) {
state.tab = (forum || _openedForum) state.tab = (forum || _openedForum)
? ChatSearchTab::ThisPeer ? ChatSearchTab::ThisPeer
: ChatSearchTab::MyMessages; : ChatSearchTab::MyMessages;
@ -2990,6 +2910,20 @@ bool Widget::applySearchState(SearchState state) {
return false; return false;
} }
if ((state.tab == ChatSearchTab::ThisTopic
&& !state.inChat.topic())
|| (state.tab == ChatSearchTab::ThisPeer
&& !state.inChat
&& !_openedForum)
|| (state.tab == ChatSearchTab::PublicPosts
&& !_searchingHashtag)) {
state.tab = state.inChat.topic()
? ChatSearchTab::ThisTopic
: (state.inChat.owningHistory() || state.inChat.sublist())
? ChatSearchTab::ThisPeer
: ChatSearchTab::MyMessages;
}
const auto migrateFrom = (peer && !topic) const auto migrateFrom = (peer && !topic)
? peer->migrateFrom() ? peer->migrateFrom()
: nullptr; : nullptr;
@ -3003,7 +2937,6 @@ bool Widget::applySearchState(SearchState state) {
} }
if (inChatChanged) { if (inChatChanged) {
controller()->setSearchInChat(_searchState.inChat); controller()->setSearchInChat(_searchState.inChat);
updateSearchTabs();
} }
if (queryChanged || inChatChanged) { if (queryChanged || inChatChanged) {
updateCancelSearch(); updateCancelSearch();
@ -3028,10 +2961,6 @@ bool Widget::applySearchState(SearchState state) {
_peerSearchQuery = QString(); _peerSearchQuery = QString();
} }
if (_searchState.inChat && _layout == Layout::Main) {
controller()->closeFolder();
}
if (_searchState.query != currentSearchQuery()) { if (_searchState.query != currentSearchQuery()) {
setSearchQuery(_searchState.query); setSearchQuery(_searchState.query);
} }
@ -3358,9 +3287,6 @@ void Widget::updateControlsGeometry() {
if (_forumRequestsBar) { if (_forumRequestsBar) {
_forumRequestsBar->resizeToWidth(barw); _forumRequestsBar->resizeToWidth(barw);
} }
if (_searchTabs) {
_searchTabs->resizeToWidth(barw);
}
_updateScrollGeometryCached = [=] { _updateScrollGeometryCached = [=] {
const auto moreChatsBarTop = expandedStoriesTop const auto moreChatsBarTop = expandedStoriesTop
+ ((!_stories || _stories->isHidden()) ? 0 : _aboveScrollAdded); + ((!_stories || _stories->isHidden()) ? 0 : _aboveScrollAdded);
@ -3382,13 +3308,8 @@ void Widget::updateControlsGeometry() {
if (_forumReportBar) { if (_forumReportBar) {
_forumReportBar->bar().move(0, forumReportTop); _forumReportBar->bar().move(0, forumReportTop);
} }
const auto searchTabsTop = forumReportTop const auto scrollTop = forumReportTop
+ (_forumReportBar ? _forumReportBar->bar().height() : 0); + (_forumReportBar ? _forumReportBar->bar().height() : 0);
if (_searchTabs) {
_searchTabs->move(0, searchTabsTop);
}
const auto scrollTop = searchTabsTop
+ (_searchTabs ? _searchTabs->height() : 0);
const auto scrollHeight = height() - scrollTop - bottomSkip; const auto scrollHeight = height() - scrollTop - bottomSkip;
const auto wasScrollHeight = _scroll->height(); const auto wasScrollHeight = _scroll->height();
_scroll->setGeometry(0, scrollTop, scrollWidth, scrollHeight); _scroll->setGeometry(0, scrollTop, scrollWidth, scrollHeight);
@ -3680,10 +3601,11 @@ void Widget::setSearchQuery(const QString &query, int cursorPosition) {
} }
} }
bool Widget::cancelSearch(bool forceFullCancel) { bool Widget::cancelSearch(CancelSearchOptions options) {
cancelSearchRequest(); cancelSearchRequest();
auto updatedState = _searchState; auto updatedState = _searchState;
const auto clearingQuery = !updatedState.query.isEmpty(); const auto clearingQuery = !updatedState.query.isEmpty();
const auto forceFullCancel = options.forceFullCancel;
auto clearingInChat = (forceFullCancel || !clearingQuery) auto clearingInChat = (forceFullCancel || !clearingQuery)
&& (updatedState.inChat && (updatedState.inChat
|| updatedState.fromPeer || updatedState.fromPeer
@ -3692,7 +3614,9 @@ bool Widget::cancelSearch(bool forceFullCancel) {
updatedState.query = QString(); updatedState.query = QString();
} }
if (clearingInChat) { if (clearingInChat) {
if (updatedState.inChat && controller()->adaptive().isOneColumn()) { if (options.jumpBackToSearchedChat
&& updatedState.inChat
&& controller()->adaptive().isOneColumn()) {
if (const auto thread = updatedState.inChat.thread()) { if (const auto thread = updatedState.inChat.thread()) {
controller()->showThread(thread); controller()->showThread(thread);
} else { } else {

View file

@ -76,7 +76,7 @@ struct ChosenRow;
class InnerWidget; class InnerWidget;
enum class SearchRequestType; enum class SearchRequestType;
class Suggestions; class Suggestions;
class ChatSearchTabs; class ChatSearchIn;
enum class ChatSearchTab : uchar; enum class ChatSearchTab : uchar;
class Widget final : public Window::AbstractSectionWidget { class Widget final : public Window::AbstractSectionWidget {
@ -131,7 +131,6 @@ public:
bool floatPlayerHandleWheelEvent(QEvent *e) override; bool floatPlayerHandleWheelEvent(QEvent *e) override;
QRect floatPlayerAvailableRect() override; QRect floatPlayerAvailableRect() override;
bool cancelSearch(bool forceFullCancel = false);
bool cancelSearchByMouseBack(); bool cancelSearchByMouseBack();
QVariant inputMethodQuery(Qt::InputMethodQuery query) const override; QVariant inputMethodQuery(Qt::InputMethodQuery query) const override;
@ -255,13 +254,18 @@ private:
void updateScrollUpPosition(); void updateScrollUpPosition();
void updateLockUnlockPosition(); void updateLockUnlockPosition();
void updateSuggestions(anim::type animated); void updateSuggestions(anim::type animated);
void updateSearchTabs();
void processSearchFocusChange(); void processSearchFocusChange();
[[nodiscard]] bool redirectToSearchPossible() const; [[nodiscard]] bool redirectToSearchPossible() const;
[[nodiscard]] bool redirectKeyToSearch(QKeyEvent *e) const; [[nodiscard]] bool redirectKeyToSearch(QKeyEvent *e) const;
[[nodiscard]] bool redirectImeToSearch() const; [[nodiscard]] bool redirectImeToSearch() const;
struct CancelSearchOptions {
bool forceFullCancel = false;
bool jumpBackToSearchedChat = false;
};
bool cancelSearch(CancelSearchOptions options);
MTP::Sender _api; MTP::Sender _api;
bool _dragInScroll = false; bool _dragInScroll = false;
@ -294,7 +298,6 @@ private:
QPointer<InnerWidget> _inner; QPointer<InnerWidget> _inner;
std::unique_ptr<Suggestions> _suggestions; std::unique_ptr<Suggestions> _suggestions;
std::vector<std::unique_ptr<Suggestions>> _hidingSuggestions; std::vector<std::unique_ptr<Suggestions>> _hidingSuggestions;
std::unique_ptr<ChatSearchTabs> _searchTabs;
class BottomButton; class BottomButton;
object_ptr<BottomButton> _updateTelegram = { nullptr }; object_ptr<BottomButton> _updateTelegram = { nullptr };
object_ptr<BottomButton> _loadMoreChats = { nullptr }; object_ptr<BottomButton> _loadMoreChats = { nullptr };

View file

@ -33,12 +33,6 @@ void SearchEmpty::setup(Icon icon, rpl::producer<TextWithEntities> text) {
this, this,
std::move(text), std::move(text),
st::defaultPeerListAbout); st::defaultPeerListAbout);
label->setClickHandlerFilter([=](const auto &, Qt::MouseButton button) {
if (button == Qt::LeftButton) {
_linkClicks.fire({});
}
return false;
});
const auto size = st::recentPeersEmptySize; const auto size = st::recentPeersEmptySize;
const auto animation = [&] { const auto animation = [&] {
switch (icon) { switch (icon) {

View file

@ -26,9 +26,6 @@ public:
rpl::producer<TextWithEntities> text); rpl::producer<TextWithEntities> text);
void setMinimalHeight(int minimalHeight); void setMinimalHeight(int minimalHeight);
[[nodiscard]] rpl::producer<> linkClicks() const {
return _linkClicks.events();
}
void animate(); void animate();
@ -36,7 +33,6 @@ private:
void setup(Icon icon, rpl::producer<TextWithEntities> text); void setup(Icon icon, rpl::producer<TextWithEntities> text);
Fn<void()> _animate; Fn<void()> _animate;
rpl::event_stream<> _linkClicks;
}; };

View file

@ -0,0 +1,459 @@
/*
This file is part of Telegram Desktop,
the official desktop application for the Telegram messaging service.
For license and copyright information please follow this link:
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#include "dialogs/ui/chat_search_in.h"
#include "lang/lang_keys.h"
#include "ui/effects/ripple_animation.h"
#include "ui/text/text_utilities.h"
#include "ui/widgets/buttons.h"
#include "ui/widgets/popup_menu.h"
#include "ui/widgets/shadow.h"
#include "ui/widgets/menu/menu_item_base.h"
#include "ui/dynamic_image.h"
#include "ui/painter.h"
#include "styles/style_dialogs.h"
#include "styles/style_window.h"
namespace Dialogs {
namespace {
class Action final : public Ui::Menu::ItemBase {
public:
Action(
not_null<Ui::PopupMenu*> parentMenu,
std::shared_ptr<Ui::DynamicImage> icon,
const QString &label,
bool chosen);
bool isEnabled() const override;
not_null<QAction*> action() const override;
void handleKeyPress(not_null<QKeyEvent*> e) override;
protected:
QPoint prepareRippleStartPosition() const override;
QImage prepareRippleMask() const override;
int contentHeight() const override;
private:
void paint(Painter &p);
void resolveMinWidth();
void refreshDimensions();
const not_null<Ui::PopupMenu*> _parentMenu;
const not_null<QAction*> _dummyAction;
const style::Menu &_st;
const int _height = 0;
std::shared_ptr<Ui::DynamicImage> _icon;
Ui::Text::String _text;
bool _checked = false;
};
[[nodiscard]] QString TabLabel(
ChatSearchTab tab,
ChatSearchPeerTabType type = {}) {
switch (tab) {
case ChatSearchTab::MyMessages:
return tr::lng_search_tab_my_messages(tr::now);
case ChatSearchTab::ThisTopic:
return tr::lng_search_tab_this_topic(tr::now);
case ChatSearchTab::ThisPeer:
switch (type) {
case ChatSearchPeerTabType::Chat:
return tr::lng_search_tab_this_chat(tr::now);
case ChatSearchPeerTabType::Channel:
return tr::lng_search_tab_this_channel(tr::now);
case ChatSearchPeerTabType::Group:
return tr::lng_search_tab_this_group(tr::now);
}
Unexpected("Type in Dialogs::TabLabel.");
case ChatSearchTab::PublicPosts:
return tr::lng_search_tab_public_posts(tr::now);
}
Unexpected("Tab in Dialogs::TabLabel.");
}
Action::Action(
not_null<Ui::PopupMenu*> parentMenu,
std::shared_ptr<Ui::DynamicImage> icon,
const QString &label,
bool chosen)
: ItemBase(parentMenu->menu(), parentMenu->menu()->st())
, _parentMenu(parentMenu)
, _dummyAction(CreateChild<QAction>(parentMenu->menu().get()))
, _st(parentMenu->menu()->st())
, _height(st::dialogsSearchInHeight)
, _icon(std::move(icon))
, _checked(chosen) {
const auto parent = parentMenu->menu();
_text.setText(st::semiboldTextStyle, label);
_icon->subscribeToUpdates([=] { update(); });
initResizeHook(parent->sizeValue());
resolveMinWidth();
paintRequest(
) | rpl::start_with_next([=] {
Painter p(this);
paint(p);
}, lifetime());
enableMouseSelecting();
}
void Action::resolveMinWidth() {
const auto maxWidth = st::dialogsSearchInPhotoPadding
+ st::dialogsSearchInPhotoSize
+ st::dialogsSearchInSkip
+ _text.maxWidth()
+ st::dialogsSearchInCheckSkip
+ st::dialogsSearchInCheck.width()
+ st::dialogsSearchInCheckSkip;
setMinWidth(maxWidth);
}
void Action::paint(Painter &p) {
const auto enabled = isEnabled();
const auto selected = isSelected();
if (selected && _st.itemBgOver->c.alpha() < 255) {
p.fillRect(0, 0, width(), _height, _st.itemBg);
}
const auto &bg = selected ? _st.itemBgOver : _st.itemBg;
p.fillRect(0, 0, width(), _height, bg);
if (enabled) {
paintRipple(p, 0, 0);
}
auto x = st::dialogsSearchInPhotoPadding;
const auto photos = st::dialogsSearchInPhotoSize;
const auto photoy = (height() - photos) / 2;
p.drawImage(QRect{ x, photoy, photos, photos }, _icon->image(photos));
x += photos + st::dialogsSearchInSkip;
const auto available = width()
- x
- st::dialogsSearchInCheckSkip
- st::dialogsSearchInCheck.width()
- st::dialogsSearchInCheckSkip;
p.setPen(!enabled
? _st.itemFgDisabled
: selected
? _st.itemFgOver
: _st.itemFg);
_text.drawLeftElided(
p,
x,
st::dialogsSearchInNameTop,
available,
width());
x += available;
if (_checked) {
x += st::dialogsSearchInCheckSkip;
const auto &icon = st::dialogsSearchInCheck;
const auto icony = (height() - icon.height()) / 2;
icon.paint(p, x, icony, width());
}
}
bool Action::isEnabled() const {
return true;
}
not_null<QAction*> Action::action() const {
return _dummyAction;
}
QPoint Action::prepareRippleStartPosition() const {
return mapFromGlobal(QCursor::pos());
}
QImage Action::prepareRippleMask() const {
return Ui::RippleAnimation::RectMask(size());
}
int Action::contentHeight() const {
return _height;
}
void Action::handleKeyPress(not_null<QKeyEvent*> e) {
if (!isSelected()) {
return;
}
const auto key = e->key();
if (key == Qt::Key_Enter || key == Qt::Key_Return) {
setClicked(Ui::Menu::TriggeredSource::Keyboard);
}
}
} // namespace
FixedHashtagSearchQuery FixHashtagSearchQuery(
const QString &query,
int cursorPosition) {
const auto trimmed = query.trimmed();
const auto hash = int(trimmed.isEmpty()
? query.size()
: query.indexOf(trimmed));
const auto start = std::min(cursorPosition, hash);
auto result = query.mid(0, start);
for (const auto &ch : query.mid(start)) {
if (ch.isSpace()) {
if (cursorPosition > result.size()) {
--cursorPosition;
}
continue;
} else if (result.size() == start) {
result += '#';
if (ch != '#') {
++cursorPosition;
}
}
if (ch != '#') {
result += ch;
}
}
if (result.size() == start) {
result += '#';
++cursorPosition;
}
return { result, cursorPosition };
}
bool IsHashtagSearchQuery(const QString &query) {
const auto trimmed = query.trimmed();
if (trimmed.isEmpty() || trimmed[0] != '#') {
return false;
}
for (const auto &ch : trimmed) {
if (ch.isSpace()) {
return false;
}
}
return true;
}
void ChatSearchIn::Section::update() {
outer->update();
}
ChatSearchIn::ChatSearchIn(QWidget *parent)
: RpWidget(parent) {
_in.clicks.events() | rpl::start_with_next([=] {
showMenu();
}, lifetime());
}
ChatSearchIn::~ChatSearchIn() = default;
void ChatSearchIn::apply(
std::vector<PossibleTab> tabs,
ChatSearchTab active,
ChatSearchPeerTabType peerTabType,
std::shared_ptr<Ui::DynamicImage> fromUserpic,
QString fromName) {
_tabs = std::move(tabs);
_peerTabType = peerTabType;
_active = active;
const auto i = ranges::find(_tabs, active, &PossibleTab::tab);
Assert(i != end(_tabs));
Assert(i->icon != nullptr);
updateSection(
&_in,
i->icon->clone(),
Ui::Text::Semibold(TabLabel(active, peerTabType)));
auto text = tr::lng_dlg_search_from(
tr::now,
lt_user,
Ui::Text::Semibold(fromName),
Ui::Text::WithEntities);
updateSection(&_from, std::move(fromUserpic), std::move(text));
resizeToWidth(width());
}
rpl::producer<> ChatSearchIn::cancelInRequests() const {
return _in.cancelRequests.events();
}
rpl::producer<> ChatSearchIn::cancelFromRequests() const {
return _from.cancelRequests.events();
}
rpl::producer<> ChatSearchIn::changeFromRequests() const {
return _from.clicks.events();
}
rpl::producer<ChatSearchTab> ChatSearchIn::tabChanges() const {
return _active.changes();
}
void ChatSearchIn::showMenu() {
_menu = base::make_unique_q<Ui::PopupMenu>(
this,
st::dialogsSearchInMenu);
const auto active = _active.current();
auto activeIndex = 0;
for (const auto &tab : _tabs) {
if (!tab.icon) {
continue;
}
const auto value = tab.tab;
if (value == active) {
activeIndex = _menu->actions().size();
}
auto action = base::make_unique_q<Action>(
_menu.get(),
tab.icon,
TabLabel(value, _peerTabType),
(value == active));
action->setClickedCallback([=] {
_active = value;
});
_menu->addAction(std::move(action));
}
const auto count = int(_menu->actions().size());
const auto bottomLeft = (activeIndex * 2 >= count);
const auto single = st::dialogsSearchInHeight;
const auto in = mapToGlobal(_in.outer->pos()
+ QPoint(0, bottomLeft ? count * single : 0));
_menu->setForcedOrigin(bottomLeft
? Ui::PanelAnimation::Origin::BottomLeft
: Ui::PanelAnimation::Origin::TopLeft);
if (_menu->prepareGeometryFor(in)) {
_menu->move(_menu->pos() - QPoint(_menu->inner().x(), activeIndex * single));
_menu->popupPrepared();
}
}
void ChatSearchIn::paintEvent(QPaintEvent *e) {
auto p = Painter(this);
const auto top = QRect(0, 0, width(), st::searchedBarHeight);
p.fillRect(top, st::searchedBarBg);
p.fillRect(rect().translated(0, st::searchedBarHeight), st::dialogsBg);
p.setFont(st::searchedBarFont);
p.setPen(st::searchedBarFg);
p.drawTextLeft(
st::searchedBarPosition.x(),
st::searchedBarPosition.y(),
width(),
tr::lng_dlg_search_in(tr::now));
}
int ChatSearchIn::resizeGetHeight(int newWidth) {
auto result = st::searchedBarHeight;
if (const auto raw = _in.outer.get()) {
raw->resizeToWidth(newWidth);
raw->move(0, result);
result += raw->height();
_in.shadow->setGeometry(0, result, newWidth, st::lineWidth);
result += st::lineWidth;
}
if (const auto raw = _from.outer.get()) {
raw->resizeToWidth(newWidth);
raw->move(0, result);
result += raw->height();
_from.shadow->setGeometry(0, result, newWidth, st::lineWidth);
result += st::lineWidth;
}
return result;
}
void ChatSearchIn::updateSection(
not_null<Section*> section,
std::shared_ptr<Ui::DynamicImage> image,
TextWithEntities text) {
if (section->subscribed) {
section->image->subscribeToUpdates(nullptr);
section->subscribed = false;
}
if (!image) {
if (section->outer) {
section->cancel = nullptr;
section->shadow = nullptr;
section->outer = nullptr;
section->subscribed = false;
}
return;
} else if (!section->outer) {
auto button = std::make_unique<Ui::AbstractButton>(this);
const auto raw = button.get();
section->outer = std::move(button);
raw->resize(
st::columnMinimalWidthLeft,
st::dialogsSearchInHeight);
raw->paintRequest() | rpl::start_with_next([=] {
auto p = QPainter(raw);
if (!section->subscribed) {
section->subscribed = true;
section->image->subscribeToUpdates([=] {
raw->update();
});
}
const auto outer = raw->width();
const auto size = st::dialogsSearchInPhotoSize;
const auto left = st::dialogsSearchInPhotoPadding;
const auto top = (st::dialogsSearchInHeight - size) / 2;
p.drawImage(
QRect{ left, top, size, size },
section->image->image(size));
const auto x = left + size + st::dialogsSearchInSkip;
const auto available = outer
- st::dialogsSearchInSkip
- section->cancel->width()
- 2 * st::dialogsSearchInDownSkip
- st::dialogsSearchInDown.width()
- x;
const auto use = std::min(section->text.maxWidth(), available);
const auto iconx = x + use + st::dialogsSearchInDownSkip;
const auto icony = st::dialogsSearchInDownTop;
st::dialogsSearchInDown.paint(p, iconx, icony, outer);
p.setPen(st::windowBoldFg);
section->text.draw(p, {
.position = QPoint(x, st::dialogsSearchInNameTop),
.outerWidth = outer,
.availableWidth = available,
.elisionLines = 1,
});
}, raw->lifetime());
section->shadow = std::make_unique<Ui::PlainShadow>(this);
section->shadow->show();
const auto st = &st::dialogsCancelSearchInPeer;
section->cancel = std::make_unique<Ui::IconButton>(raw, *st);
section->cancel->show();
raw->sizeValue() | rpl::start_with_next([=](QSize size) {
const auto left = size.width() - section->cancel->width();
const auto top = (size.height() - st->height) / 2;
section->cancel->moveToLeft(left, top);
}, section->cancel->lifetime());
section->cancel->clicks() | rpl::to_empty | rpl::start_to_stream(
section->cancelRequests,
section->cancel->lifetime());
raw->clicks() | rpl::to_empty | rpl::start_to_stream(
section->clicks,
raw->lifetime());
raw->show();
}
section->image = std::move(image);
section->text.setMarkedText(st::dialogsSearchFromStyle, std::move(text));
}
} // namespace Dialogs

View file

@ -0,0 +1,100 @@
/*
This file is part of Telegram Desktop,
the official desktop application for the Telegram messaging service.
For license and copyright information please follow this link:
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#pragma once
#include "base/unique_qptr.h"
#include "ui/rp_widget.h"
namespace Ui {
class PlainShadow;
class DynamicImage;
class IconButton;
class PopupMenu;
} // namespace Ui
namespace Dialogs {
enum class ChatSearchTab : uchar {
MyMessages,
ThisTopic,
ThisPeer,
PublicPosts,
};
enum class ChatSearchPeerTabType : uchar {
Chat,
Channel,
Group,
};
class ChatSearchIn final : public Ui::RpWidget {
public:
explicit ChatSearchIn(QWidget *parent);
~ChatSearchIn();
struct PossibleTab {
ChatSearchTab tab = {};
std::shared_ptr<Ui::DynamicImage> icon;
};
void apply(
std::vector<PossibleTab> tabs,
ChatSearchTab active,
ChatSearchPeerTabType peerTabType,
std::shared_ptr<Ui::DynamicImage> fromUserpic,
QString fromName);
[[nodiscard]] rpl::producer<> cancelInRequests() const;
[[nodiscard]] rpl::producer<> cancelFromRequests() const;
[[nodiscard]] rpl::producer<> changeFromRequests() const;
[[nodiscard]] rpl::producer<ChatSearchTab> tabChanges() const;
private:
struct Section {
std::unique_ptr<Ui::RpWidget> outer;
std::unique_ptr<Ui::IconButton> cancel;
std::unique_ptr<Ui::PlainShadow> shadow;
std::shared_ptr<Ui::DynamicImage> image;
Ui::Text::String text;
rpl::event_stream<> clicks;
rpl::event_stream<> cancelRequests;
bool subscribed = false;
void update();
};
int resizeGetHeight(int newWidth) override;
void paintEvent(QPaintEvent *e) override;
void showMenu();
void updateSection(
not_null<Section*> section,
std::shared_ptr<Ui::DynamicImage> image,
TextWithEntities text);
Section _in;
Section _from;
rpl::variable<ChatSearchTab> _active;
base::unique_qptr<Ui::PopupMenu> _menu;
std::vector<PossibleTab> _tabs;
ChatSearchPeerTabType _peerTabType = ChatSearchPeerTabType::Chat;
};
struct FixedHashtagSearchQuery {
QString text;
int cursorPosition = 0;
};
[[nodiscard]] FixedHashtagSearchQuery FixHashtagSearchQuery(
const QString &query,
int cursorPosition);
[[nodiscard]] bool IsHashtagSearchQuery(const QString &query);
} // namespace Dialogs

View file

@ -1,201 +0,0 @@
/*
This file is part of Telegram Desktop,
the official desktop application for the Telegram messaging service.
For license and copyright information please follow this link:
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#include "dialogs/ui/chat_search_tabs.h"
#include "lang/lang_keys.h"
#include "ui/widgets/discrete_sliders.h"
#include "ui/widgets/shadow.h"
#include "styles/style_dialogs.h"
namespace Dialogs {
namespace {
[[nodiscard]] QString TabLabel(
ChatSearchTab tab,
ChatSearchPeerTabType type = {}) {
switch (tab) {
case ChatSearchTab::MyMessages:
return tr::lng_search_tab_my_messages(tr::now);
case ChatSearchTab::ThisTopic:
return tr::lng_search_tab_this_topic(tr::now);
case ChatSearchTab::ThisPeer:
switch (type) {
case ChatSearchPeerTabType::Chat:
return tr::lng_search_tab_this_chat(tr::now);
case ChatSearchPeerTabType::Channel:
return tr::lng_search_tab_this_channel(tr::now);
case ChatSearchPeerTabType::Group:
return tr::lng_search_tab_this_group(tr::now);
}
Unexpected("Type in Dialogs::TabLabel.");
case ChatSearchTab::PublicPosts:
return tr::lng_search_tab_public_posts(tr::now);
}
Unexpected("Tab in Dialogs::TabLabel.");
}
} // namespace
TextWithEntities DefaultShortLabel(ChatSearchTab tab) {
// Return them in QString::fromUtf8 format.
switch (tab) {
case ChatSearchTab::MyMessages:
return { QString::fromUtf8("\xf0\x9f\x93\xa8") };
case ChatSearchTab::PublicPosts:
return { QString::fromUtf8("\xf0\x9f\x8c\x8e") };
}
Unexpected("Tab in Dialogs::DefaultShortLabel.");
}
FixedHashtagSearchQuery FixHashtagSearchQuery(
const QString &query,
int cursorPosition) {
const auto trimmed = query.trimmed();
const auto hash = int(trimmed.isEmpty()
? query.size()
: query.indexOf(trimmed));
const auto start = std::min(cursorPosition, hash);
auto result = query.mid(0, start);
for (const auto &ch : query.mid(start)) {
if (ch.isSpace()) {
if (cursorPosition > result.size()) {
--cursorPosition;
}
continue;
} else if (result.size() == start) {
result += '#';
if (ch != '#') {
++cursorPosition;
}
}
if (ch != '#') {
result += ch;
}
}
if (result.size() == start) {
result += '#';
++cursorPosition;
}
return { result, cursorPosition };
}
bool IsHashtagSearchQuery(const QString &query) {
const auto trimmed = query.trimmed();
if (trimmed.isEmpty() || trimmed[0] != '#') {
return false;
}
for (const auto &ch : trimmed) {
if (ch.isSpace()) {
return false;
}
}
return true;
}
ChatSearchTabs::ChatSearchTabs(
QWidget *parent,
ChatSearchTab active,
Fn<std::any(Fn<void()>)> markedTextContext)
: RpWidget(parent)
, _tabs(std::make_unique<Ui::SettingsSlider>(this, st::dialogsSearchTabs))
, _shadow(std::make_unique<Ui::PlainShadow>(this))
, _markedTextContext(std::move(markedTextContext))
, _active(active) {
_tabs->move(st::dialogsSearchTabsPadding, 0);
_tabs->sectionActivated(
) | rpl::start_with_next([=](int index) {
_active = _list[index].value;
}, lifetime());
}
ChatSearchTabs::~ChatSearchTabs() = default;
void ChatSearchTabs::setTabShortLabels(
std::vector<ShortLabel> labels,
ChatSearchTab active,
ChatSearchPeerTabType peerTabType) {
const auto &st = st::dialogsSearchTabs;
const auto &font = st.labelStyle.font;
_list.clear();
_list.reserve(labels.size());
auto widthTotal = 0;
for (const auto tab : {
ChatSearchTab::ThisTopic,
ChatSearchTab::ThisPeer,
ChatSearchTab::MyMessages,
ChatSearchTab::PublicPosts,
}) {
const auto i = ranges::find(labels, tab, &ShortLabel::tab);
if (i != end(labels) && !i->label.empty()) {
const auto label = TabLabel(tab, peerTabType);
const auto widthFull = font->width(label) + st.strictSkip;
_list.push_back({
.value = tab,
.label = label,
.shortLabel = i->label,
.widthFull = widthFull,
});
widthTotal += widthFull;
}
}
const auto widthSingleEmoji = st::emojiSize + st.strictSkip;
for (const auto tab : {
ChatSearchTab::PublicPosts,
ChatSearchTab::ThisTopic,
ChatSearchTab::ThisPeer,
ChatSearchTab::MyMessages,
}) {
const auto i = ranges::find(_list, tab, &Tab::value);
if (i != end(_list)) {
i->widthThresholdForShort = widthTotal;
widthTotal -= i->widthFull;
widthTotal += widthSingleEmoji;
}
}
refillTabs(active, width());
}
rpl::producer<ChatSearchTab> ChatSearchTabs::tabChanges() const {
return _active.changes();
}
void ChatSearchTabs::refillTabs(
ChatSearchTab active,
int newWidth) {
auto labels = std::vector<TextWithEntities>();
const auto available = newWidth - 2 * st::dialogsSearchTabsPadding;
for (const auto &tab : _list) {
auto label = (available < tab.widthThresholdForShort)
? tab.shortLabel
: TextWithEntities{ tab.label };
labels.push_back(std::move(label));
}
_tabs->setSections(labels, _markedTextContext([=] { update(); }));
const auto i = ranges::find(_list, active, &Tab::value);
Assert(i != end(_list));
_tabs->setActiveSectionFast(i - begin(_list));
_tabs->resizeToWidth(newWidth);
}
int ChatSearchTabs::resizeGetHeight(int newWidth) {
refillTabs(_active.current(), newWidth);
_shadow->setGeometry(
0,
_tabs->y() + _tabs->height() - st::lineWidth,
newWidth,
st::lineWidth);
return _tabs->height();
}
void ChatSearchTabs::paintEvent(QPaintEvent *e) {
QPainter(this).fillRect(e->rect(), st::dialogsBg);
}
} // namespace Dialogs

View file

@ -1,89 +0,0 @@
/*
This file is part of Telegram Desktop,
the official desktop application for the Telegram messaging service.
For license and copyright information please follow this link:
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#pragma once
#include "ui/rp_widget.h"
namespace Ui {
class SettingsSlider;
class PlainShadow;
} // namespace Ui
namespace Dialogs {
enum class ChatSearchTab : uchar {
MyMessages,
ThisTopic,
ThisPeer,
PublicPosts,
};
enum class ChatSearchPeerTabType : uchar {
Chat,
Channel,
Group,
};
// Available for MyMessages and PublicPosts.
[[nodiscard]] TextWithEntities DefaultShortLabel(ChatSearchTab tab);
class ChatSearchTabs final : public Ui::RpWidget {
public:
ChatSearchTabs(
QWidget *parent,
ChatSearchTab active,
Fn<std::any(Fn<void()>)> markedTextContext);
~ChatSearchTabs();
// A [custom] emoji to use when there is not enough space for text.
// Only tabs with available short labels are shown.
struct ShortLabel {
ChatSearchTab tab = {};
TextWithEntities label;
};
void setTabShortLabels(
std::vector<ShortLabel> labels,
ChatSearchTab active,
ChatSearchPeerTabType peerTabType);
[[nodiscard]] rpl::producer<ChatSearchTab> tabChanges() const;
private:
struct Tab {
ChatSearchTab value = {};
QString label;
TextWithEntities shortLabel;
int widthFull = 0;
int widthThresholdForShort = 0;
};
void refreshTabs(ChatSearchTab active);
void refillTabs(ChatSearchTab active, int newWidth);
int resizeGetHeight(int newWidth) override;
void paintEvent(QPaintEvent *e) override;
const std::unique_ptr<Ui::SettingsSlider> _tabs;
const std::unique_ptr<Ui::PlainShadow> _shadow;
const Fn<std::any(Fn<void()>)> _markedTextContext;
std::vector<Tab> _list;
rpl::variable<ChatSearchTab> _active;
};
struct FixedHashtagSearchQuery {
QString text;
int cursorPosition = 0;
};
[[nodiscard]] FixedHashtagSearchQuery FixHashtagSearchQuery(
const QString &query,
int cursorPosition);
[[nodiscard]] bool IsHashtagSearchQuery(const QString &query);
} // namespace Dialogs

View file

@ -683,6 +683,10 @@ QString InnerWidget::elementAuthorRank(not_null<const Element*> view) {
return {}; return {};
} }
bool InnerWidget::elementHideTopicButton(not_null<const Element*> view) {
return false;
}
void InnerWidget::saveState(not_null<SectionMemento*> memento) { void InnerWidget::saveState(not_null<SectionMemento*> memento) {
memento->setFilter(std::move(_filter)); memento->setFilter(std::move(_filter));
memento->setAdmins(std::move(_admins)); memento->setAdmins(std::move(_admins));

View file

@ -141,6 +141,8 @@ public:
HistoryView::Element *replacing) override; HistoryView::Element *replacing) override;
QString elementAuthorRank( QString elementAuthorRank(
not_null<const HistoryView::Element*> view) override; not_null<const HistoryView::Element*> view) override;
bool elementHideTopicButton(
not_null<const HistoryView::Element*> view) override;
~InnerWidget(); ~InnerWidget();

View file

@ -329,6 +329,10 @@ public:
return {}; return {};
} }
bool elementHideTopicButton(not_null<const Element*> view) override {
return false;
}
not_null<HistoryView::ElementDelegate*> delegate() override { not_null<HistoryView::ElementDelegate*> delegate() override {
return this; return this;
} }
@ -4366,10 +4370,16 @@ void HistoryInner::deleteAsGroup(FullMsgId itemId) {
const auto group = session().data().groups().find(item); const auto group = session().data().groups().find(item);
if (!group) { if (!group) {
return deleteItem(item); return deleteItem(item);
} else if (CanCreateModerateMessagesBox(group->items)) {
_controller->show(Box(
CreateModerateMessagesBox,
group->items,
nullptr));
} else {
_controller->show(Box<DeleteMessagesBox>(
&session(),
session().data().itemsToIds(group->items)));
} }
_controller->show(Box<DeleteMessagesBox>(
&session(),
session().data().itemsToIds(group->items)));
} }
} }

View file

@ -218,59 +218,13 @@ std::unique_ptr<Data::Media> HistoryItem::CreateMedia(
const MTPMessageMedia &media) { const MTPMessageMedia &media) {
using Result = std::unique_ptr<Data::Media>; using Result = std::unique_ptr<Data::Media>;
return media.match([&](const MTPDmessageMediaContact &media) -> Result { return media.match([&](const MTPDmessageMediaContact &media) -> Result {
const auto vcardItems = [&] {
using Type = Data::SharedContact::VcardItemType;
auto items = Data::SharedContact::VcardItems();
for (const auto &item : qs(media.vvcard()).split('\n')) {
const auto parts = item.split(':');
if (parts.size() == 2) {
const auto &type = parts.front();
const auto &value = parts[1];
if (type.startsWith("TEL")) {
const auto telType = type.contains("PREF")
? Type::PhoneMain
: type.contains("HOME")
? Type::PhoneHome
: type.contains("WORK")
? Type::PhoneWork
: (type.contains("CELL")
|| type.contains("MOBILE"))
? Type::PhoneMobile
: type.contains("OTHER")
? Type::PhoneOther
: Type::Phone;
items[telType] = value;
} else if (type.startsWith("EMAIL")) {
items[Type::Email] = value;
} else if (type.startsWith("URL")) {
items[Type::Url] = value;
} else if (type.startsWith("NOTE")) {
items[Type::Note] = value;
} else if (type.startsWith("ORG")) {
items[Type::Organization] = value;
items[Type::Organization].replace(';', ' ');
} else if (type.startsWith("ADR")) {
items[Type::Address] = value;
} else if (type.startsWith("BDAY")) {
items[Type::Birthday] = value;
} else if (type.startsWith("N")) {
items[Type::Birthday] = value;
items[Type::Birthday].replace(';', ' ');
}
}
}
return items;
}();
return std::make_unique<Data::MediaContact>( return std::make_unique<Data::MediaContact>(
item, item,
media.vuser_id().v, media.vuser_id().v,
qs(media.vfirst_name()), qs(media.vfirst_name()),
qs(media.vlast_name()), qs(media.vlast_name()),
qs(media.vphone_number()), qs(media.vphone_number()),
vcardItems); Data::SharedContact::ParseVcard(qs(media.vvcard())));
}, [&](const MTPDmessageMediaGeo &media) -> Result { }, [&](const MTPDmessageMediaGeo &media) -> Result {
return media.vgeo().match([&](const MTPDgeoPoint &point) -> Result { return media.vgeo().match([&](const MTPDgeoPoint &point) -> Result {
return std::make_unique<Data::MediaLocation>( return std::make_unique<Data::MediaLocation>(

View file

@ -14,6 +14,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/effects/spoiler_mess.h" #include "ui/effects/spoiler_mess.h"
#include "ui/image/image.h" #include "ui/image/image.h"
#include "ui/toast/toast.h" #include "ui/toast/toast.h"
#include "ui/text/format_values.h"
#include "ui/text/text_options.h" #include "ui/text/text_options.h"
#include "ui/text/text_utilities.h" #include "ui/text/text_utilities.h"
#include "ui/chat/chat_style.h" #include "ui/chat/chat_style.h"
@ -48,9 +49,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "main/main_session.h" #include "main/main_session.h"
#include "window/window_session_controller.h" #include "window/window_session_controller.h"
#include "api/api_bot.h" #include "api/api_bot.h"
#include "styles/style_widgets.h" #include "styles/style_boxes.h"
#include "styles/style_chat.h" #include "styles/style_chat.h"
#include "styles/style_dialogs.h" // dialogsMiniReplyStory. #include "styles/style_dialogs.h" // dialogsMiniReplyStory.
#include "styles/style_settings.h"
#include "styles/style_widgets.h"
#include <QtGui/QGuiApplication> #include <QtGui/QGuiApplication>
@ -701,6 +704,11 @@ ReplyKeyboard::ReplyKeyboard(
const auto context = _item->fullId(); const auto context = _item->fullId();
const auto rowCount = int(markup->data.rows.size()); const auto rowCount = int(markup->data.rows.size());
_rows.reserve(rowCount); _rows.reserve(rowCount);
const auto buttonEmoji = Ui::Text::SingleCustomEmoji(
owner->customEmojiManager().registerInternalEmoji(
st::settingsPremiumIconStar,
QMargins(0, -st::moderateBoxExpandInnerSkip, 0, 0),
true));
for (auto i = 0; i != rowCount; ++i) { for (auto i = 0; i != rowCount; ++i) {
const auto &row = markup->data.rows[i]; const auto &row = markup->data.rows[i];
const auto rowSize = int(row.size()); const auto rowSize = int(row.size());
@ -708,17 +716,54 @@ ReplyKeyboard::ReplyKeyboard(
newRow.reserve(rowSize); newRow.reserve(rowSize);
for (auto j = 0; j != rowSize; ++j) { for (auto j = 0; j != rowSize; ++j) {
auto button = Button(); auto button = Button();
const auto text = row[j].text; using Type = HistoryMessageMarkupButton::Type;
const auto isBuy = (row[j].type == Type::Buy);
static const auto RegExp = QRegularExpression("\\b"
+ Ui::kCreditsCurrency
+ "\\b");
const auto text = isBuy
? base::duplicate(row[j].text).replace(
RegExp,
QChar(0x2B50))
: row[j].text;
const auto textWithEntities = [&] {
if (!isBuy) {
return TextWithEntities();
}
auto result = TextWithEntities();
auto firstPart = true;
for (const auto &part : text.split(QChar(0x2B50))) {
if (!firstPart) {
result.append(buttonEmoji);
}
result.append(part);
firstPart = false;
}
return result.entities.empty()
? TextWithEntities()
: result;
}();
button.type = row.at(j).type; button.type = row.at(j).type;
button.link = std::make_shared<ReplyMarkupClickHandler>( button.link = std::make_shared<ReplyMarkupClickHandler>(
owner, owner,
i, i,
j, j,
context); context);
button.text.setText( if (!textWithEntities.text.isEmpty()) {
_st->textStyle(), button.text.setMarkedText(
TextUtilities::SingleLine(text), _st->textStyle(),
kPlainTextOptions); TextUtilities::SingleLine(textWithEntities),
kMarkupTextOptions,
Core::MarkedTextContext{
.session = &item->history()->owner().session(),
.customEmojiRepaint = [=] { _st->repaint(item); },
});
} else {
button.text.setText(
_st->textStyle(),
TextUtilities::SingleLine(text),
kPlainTextOptions);
}
button.characters = text.isEmpty() ? 1 : text.size(); button.characters = text.isEmpty() ? 1 : text.size();
newRow.push_back(std::move(button)); newRow.push_back(std::move(button));
} }

View file

@ -126,6 +126,7 @@ private:
Painter &p, Painter &p,
const Ui::ChatPaintContext &context) override; const Ui::ChatPaintContext &context) override;
QString listElementAuthorRank(not_null<const Element*> view) override; QString listElementAuthorRank(not_null<const Element*> view) override;
bool listElementHideTopicButton(not_null<const Element*> view) override;
History *listTranslateHistory() override; History *listTranslateHistory() override;
void listAddTranslatedItems( void listAddTranslatedItems(
not_null<TranslateTracker*> tracker) override; not_null<TranslateTracker*> tracker) override;
@ -279,6 +280,8 @@ void Item::setupTop() {
const auto topic = _thread->asTopic(); const auto topic = _thread->asTopic();
auto nameValue = (topic auto nameValue = (topic
? Info::Profile::TitleValue(topic) ? Info::Profile::TitleValue(topic)
: _thread->peer()->isSelf()
? tr::lng_saved_messages()
: Info::Profile::NameValue(_thread->peer()) : Info::Profile::NameValue(_thread->peer())
) | rpl::start_spawning(_top->lifetime()); ) | rpl::start_spawning(_top->lifetime());
const auto name = Ui::CreateChild<Ui::FlatLabel>( const auto name = Ui::CreateChild<Ui::FlatLabel>(
@ -294,18 +297,24 @@ void Item::setupTop() {
) | rpl::map([](StatusFields &&fields) { ) | rpl::map([](StatusFields &&fields) {
return fields.text; return fields.text;
}); });
const auto status = Ui::CreateChild<Ui::FlatLabel>( const auto status = _thread->peer()->isSelf()
_top.get(), ? nullptr
(topic : Ui::CreateChild<Ui::FlatLabel>(
? Info::Profile::NameValue(topic->channel()) _top.get(),
: std::move(statusText)), (topic
st::previewStatus); ? Info::Profile::NameValue(topic->channel())
std::move(statusFields) | rpl::start_with_next([=](const StatusFields &fields) { : std::move(statusText)),
status->setTextColorOverride(fields.active st::previewStatus);
? st::windowActiveTextFg->c if (status) {
: std::optional<QColor>()); std::move(
}, status->lifetime()); statusFields
status->setAttribute(Qt::WA_TransparentForMouseEvents); ) | rpl::start_with_next([=](const StatusFields &fields) {
status->setTextColorOverride(fields.active
? st::windowActiveTextFg->c
: std::optional<QColor>());
}, status->lifetime());
status->setAttribute(Qt::WA_TransparentForMouseEvents);
}
const auto userpic = topic const auto userpic = topic
? nullptr ? nullptr
: Ui::CreateChild<Ui::UserpicButton>( : Ui::CreateChild<Ui::UserpicButton>(
@ -313,6 +322,7 @@ void Item::setupTop() {
_thread->peer(), _thread->peer(),
st::previewUserpic); st::previewUserpic);
if (userpic) { if (userpic) {
userpic->showSavedMessagesOnSelf(true);
userpic->setAttribute(Qt::WA_TransparentForMouseEvents); userpic->setAttribute(Qt::WA_TransparentForMouseEvents);
} }
const auto icon = topic const auto icon = topic
@ -334,15 +344,23 @@ void Item::setupTop() {
name->resizeToNaturalWidth(width name->resizeToNaturalWidth(width
- st.namePosition.x() - st.namePosition.x()
- st.photoPosition.x()); - st.photoPosition.x());
name->move(st::previewTop.namePosition); if (status) {
name->move(st::previewTop.namePosition);
} else {
name->move(
st::previewTop.namePosition.x(),
(st::previewTop.height - name->height()) / 2);
}
}, name->lifetime()); }, name->lifetime());
_top->geometryValue() | rpl::start_with_next([=](QRect geometry) { _top->geometryValue() | rpl::start_with_next([=](QRect geometry) {
const auto &st = st::previewTop; const auto &st = st::previewTop;
status->resizeToWidth(geometry.width() if (status) {
- st.statusPosition.x() status->resizeToWidth(geometry.width()
- st.photoPosition.x()); - st.statusPosition.x()
status->move(st.statusPosition); - st.photoPosition.x());
status->move(st.statusPosition);
}
shadow->setGeometry( shadow->setGeometry(
geometry.x(), geometry.x(),
geometry.y() + geometry.height(), geometry.y() + geometry.height(),
@ -559,8 +577,12 @@ MessagesBarData Item::listMessagesBar(
? _replies->computeInboxReadTillFull() ? _replies->computeInboxReadTillFull()
: MsgId(); : MsgId();
const auto migrated = _replies ? nullptr : _history->migrateFrom(); const auto migrated = _replies ? nullptr : _history->migrateFrom();
const auto migratedTill = migrated ? migrated->inboxReadTillId() : 0; const auto migratedTill = (migrated && migrated->unreadCount() > 0)
const auto historyTill = _replies ? 0 : _history->inboxReadTillId(); ? migrated->inboxReadTillId()
: 0;
const auto historyTill = (_replies || !_history->unreadCount())
? 0
: _history->inboxReadTillId();
if (!_replies && !migratedTill && !historyTill) { if (!_replies && !migratedTill && !historyTill) {
return {}; return {};
} }
@ -673,6 +695,10 @@ QString Item::listElementAuthorRank(not_null<const Element*> view) {
return {}; return {};
} }
bool Item::listElementHideTopicButton(not_null<const Element*> view) {
return _thread->asTopic() != nullptr;
}
History *Item::listTranslateHistory() { History *Item::listTranslateHistory() {
return nullptr; return nullptr;
} }

View file

@ -204,10 +204,16 @@ void DefaultElementDelegate::elementStartEffect(
} }
QString DefaultElementDelegate::elementAuthorRank( QString DefaultElementDelegate::elementAuthorRank(
not_null<const Element*> view) { not_null<const Element*> view) {
return {}; return {};
} }
bool DefaultElementDelegate::elementHideTopicButton(
not_null<const Element*> view) {
return true;
}
SimpleElementDelegate::SimpleElementDelegate( SimpleElementDelegate::SimpleElementDelegate(
not_null<Window::SessionController*> controller, not_null<Window::SessionController*> controller,
Fn<void()> update) Fn<void()> update)

View file

@ -117,6 +117,7 @@ public:
not_null<const Element*> view, not_null<const Element*> view,
Element *replacing) = 0; Element *replacing) = 0;
virtual QString elementAuthorRank(not_null<const Element*> view) = 0; virtual QString elementAuthorRank(not_null<const Element*> view) = 0;
virtual bool elementHideTopicButton(not_null<const Element*> view) = 0;
virtual ~ElementDelegate() { virtual ~ElementDelegate() {
} }
@ -170,6 +171,7 @@ public:
not_null<const Element*> view, not_null<const Element*> view,
Element *replacing) override; Element *replacing) override;
QString elementAuthorRank(not_null<const Element*> view) override; QString elementAuthorRank(not_null<const Element*> view) override;
bool elementHideTopicButton(not_null<const Element*> view) override;
}; };

View file

@ -1875,6 +1875,11 @@ QString ListWidget::elementAuthorRank(not_null<const Element*> view) {
return _delegate->listElementAuthorRank(view); return _delegate->listElementAuthorRank(view);
} }
bool ListWidget::elementHideTopicButton(not_null<const Element*> view) {
return _delegate->listElementHideTopicButton(view);
}
void ListWidget::saveState(not_null<ListMemento*> memento) { void ListWidget::saveState(not_null<ListMemento*> memento) {
memento->setAroundPosition(_aroundPosition); memento->setAroundPosition(_aroundPosition);
const auto state = countScrollState(); const auto state = countScrollState();

View file

@ -155,6 +155,7 @@ public:
Painter &p, Painter &p,
const Ui::ChatPaintContext &context) = 0; const Ui::ChatPaintContext &context) = 0;
virtual QString listElementAuthorRank(not_null<const Element*> view) = 0; virtual QString listElementAuthorRank(not_null<const Element*> view) = 0;
virtual bool listElementHideTopicButton(not_null<const Element*> view) = 0;
virtual History *listTranslateHistory() = 0; virtual History *listTranslateHistory() = 0;
virtual void listAddTranslatedItems( virtual void listAddTranslatedItems(
not_null<TranslateTracker*> tracker) = 0; not_null<TranslateTracker*> tracker) = 0;
@ -368,7 +369,7 @@ public:
[[nodiscard]] auto replyToMessageRequested() const [[nodiscard]] auto replyToMessageRequested() const
-> rpl::producer<ReplyToMessageRequest>; -> rpl::producer<ReplyToMessageRequest>;
void replyToMessageRequestNotify( void replyToMessageRequestNotify(
FullReplyTo to, FullReplyTo to,
bool forceAnotherChat = false); bool forceAnotherChat = false);
[[nodiscard]] rpl::producer<FullMsgId> readMessageRequested() const; [[nodiscard]] rpl::producer<FullMsgId> readMessageRequested() const;
[[nodiscard]] rpl::producer<FullMsgId> showMessageRequested() const; [[nodiscard]] rpl::producer<FullMsgId> showMessageRequested() const;
@ -425,6 +426,7 @@ public:
not_null<const Element*> view, not_null<const Element*> view,
Element *replacing) override; Element *replacing) override;
QString elementAuthorRank(not_null<const Element*> view) override; QString elementAuthorRank(not_null<const Element*> view) override;
bool elementHideTopicButton(not_null<const Element*> view) override;
void setEmptyInfoWidget(base::unique_qptr<Ui::RpWidget> &&w); void setEmptyInfoWidget(base::unique_qptr<Ui::RpWidget> &&w);
void overrideIsChatWide(bool isWide); void overrideIsChatWide(bool isWide);

View file

@ -1065,8 +1065,7 @@ QSize Message::performCountOptimalSize() {
void Message::refreshTopicButton() { void Message::refreshTopicButton() {
const auto item = data(); const auto item = data();
if (isAttachedToPrevious() if (isAttachedToPrevious()
|| (context() != Context::History || delegate()->elementHideTopicButton(this)) {
&& context() != Context::ChatPreview)) {
_topicButton = nullptr; _topicButton = nullptr;
} else if (const auto topic = item->topic()) { } else if (const auto topic = item->topic()) {
if (!_topicButton) { if (!_topicButton) {

View file

@ -687,6 +687,11 @@ QString PinnedWidget::listElementAuthorRank(not_null<const Element*> view) {
return {}; return {};
} }
bool PinnedWidget::listElementHideTopicButton(
not_null<const Element*> view) {
return true;
}
History *PinnedWidget::listTranslateHistory() { History *PinnedWidget::listTranslateHistory() {
return _history; return _history;
} }

View file

@ -132,6 +132,7 @@ public:
Painter &p, Painter &p,
const Ui::ChatPaintContext &context) override; const Ui::ChatPaintContext &context) override;
QString listElementAuthorRank(not_null<const Element*> view) override; QString listElementAuthorRank(not_null<const Element*> view) override;
bool listElementHideTopicButton(not_null<const Element*> view) override;
History *listTranslateHistory() override; History *listTranslateHistory() override;
void listAddTranslatedItems( void listAddTranslatedItems(
not_null<TranslateTracker*> tracker) override; not_null<TranslateTracker*> tracker) override;

View file

@ -228,11 +228,7 @@ RepliesWidget::RepliesWidget(
listShowPremiumToast(emoji); listShowPremiumToast(emoji);
}, },
.mode = ComposeControls::Mode::Normal, .mode = ComposeControls::Mode::Normal,
.sendMenuDetails = [=] { .sendMenuDetails = [=] { return sendMenuDetails(); },
using Type = SendMenu::Type;
const auto type = _topic ? Type::Scheduled : Type::SilentOnly;
return SendMenu::Details{ .type = type };
},
.regularWindow = controller, .regularWindow = controller,
.stickerOrEmojiChosen = controller->stickerOrEmojiChosen(), .stickerOrEmojiChosen = controller->stickerOrEmojiChosen(),
.scheduledToggleValue = _topic .scheduledToggleValue = _topic
@ -963,7 +959,7 @@ bool RepliesWidget::confirmSendingFiles(
_composeControls->getTextWithAppliedMarkdown(), _composeControls->getTextWithAppliedMarkdown(),
_history->peer, _history->peer,
Api::SendType::Normal, Api::SendType::Normal,
SendMenu::Details{ SendMenu::Type::SilentOnly }); // #TODO replies schedule sendMenuDetails());
box->setConfirmedCallback(crl::guard(this, [=]( box->setConfirmedCallback(crl::guard(this, [=](
Ui::PreparedList &&list, Ui::PreparedList &&list,
@ -1096,7 +1092,6 @@ void RepliesWidget::checkReplyReturns() {
void RepliesWidget::uploadFile( void RepliesWidget::uploadFile(
const QByteArray &fileContent, const QByteArray &fileContent,
SendMediaType type) { SendMediaType type) {
// #TODO replies schedule
session().api().sendFile(fileContent, type, prepareSendAction({})); session().api().sendFile(fileContent, type, prepareSendAction({}));
} }
@ -1146,7 +1141,7 @@ bool RepliesWidget::showSendingFilesError(
} }
Api::SendAction RepliesWidget::prepareSendAction( Api::SendAction RepliesWidget::prepareSendAction(
Api::SendOptions options) const { Api::SendOptions options) const {
auto result = Api::SendAction(_history, options); auto result = Api::SendAction(_history, options);
result.replyTo = replyTo(); result.replyTo = replyTo();
result.options.sendAs = _composeControls->sendAsPeer(); result.options.sendAs = _composeControls->sendAsPeer();
@ -1158,11 +1153,6 @@ void RepliesWidget::send() {
return; return;
} }
send({}); send({});
// #TODO replies schedule
//const auto callback = [=](Api::SendOptions options) { send(options); };
//Ui::show(
// PrepareScheduleBox(this, sendMenuType(), callback),
// Ui::LayerOption::KeepOther);
} }
void RepliesWidget::sendVoice(ComposeControls::VoiceToSend &&data) { void RepliesWidget::sendVoice(ComposeControls::VoiceToSend &&data) {
@ -1346,13 +1336,6 @@ void RepliesWidget::refreshJoinGroupButton() {
void RepliesWidget::sendExistingDocument( void RepliesWidget::sendExistingDocument(
not_null<DocumentData*> document) { not_null<DocumentData*> document) {
sendExistingDocument(document, {}, std::nullopt); sendExistingDocument(document, {}, std::nullopt);
// #TODO replies schedule
//const auto callback = [=](Api::SendOptions options) {
// sendExistingDocument(document, options);
//};
//Ui::show(
// PrepareScheduleBox(this, sendMenuType(), callback),
// Ui::LayerOption::KeepOther);
} }
bool RepliesWidget::sendExistingDocument( bool RepliesWidget::sendExistingDocument(
@ -1382,13 +1365,6 @@ bool RepliesWidget::sendExistingDocument(
void RepliesWidget::sendExistingPhoto(not_null<PhotoData*> photo) { void RepliesWidget::sendExistingPhoto(not_null<PhotoData*> photo) {
sendExistingPhoto(photo, {}); sendExistingPhoto(photo, {});
// #TODO replies schedule
//const auto callback = [=](Api::SendOptions options) {
// sendExistingPhoto(photo, options);
//};
//Ui::show(
// PrepareScheduleBox(this, sendMenuType(), callback),
// Ui::LayerOption::KeepOther);
} }
bool RepliesWidget::sendExistingPhoto( bool RepliesWidget::sendExistingPhoto(
@ -1459,13 +1435,9 @@ void RepliesWidget::sendInlineResult(
} }
SendMenu::Details RepliesWidget::sendMenuDetails() const { SendMenu::Details RepliesWidget::sendMenuDetails() const {
// #TODO replies schedule using Type = SendMenu::Type;
const auto type = _history->peer->isSelf() const auto type = _topic ? Type::Scheduled : Type::SilentOnly;
? SendMenu::Type::Reminder return SendMenu::Details{ .type = type };
: HistoryView::CanScheduleUntilOnline(_history->peer)
? SendMenu::Type::ScheduledToUser
: SendMenu::Type::Scheduled;
return { .type = type, .effectAllowed = _history->peer->isUser() };
} }
FullReplyTo RepliesWidget::replyTo() const { FullReplyTo RepliesWidget::replyTo() const {
@ -2650,6 +2622,11 @@ QString RepliesWidget::listElementAuthorRank(not_null<const Element*> view) {
: QString(); : QString();
} }
bool RepliesWidget::listElementHideTopicButton(
not_null<const Element*> view) {
return true;
}
History *RepliesWidget::listTranslateHistory() { History *RepliesWidget::listTranslateHistory() {
return _history; return _history;
} }

View file

@ -175,6 +175,7 @@ public:
Painter &p, Painter &p,
const Ui::ChatPaintContext &context) override; const Ui::ChatPaintContext &context) override;
QString listElementAuthorRank(not_null<const Element*> view) override; QString listElementAuthorRank(not_null<const Element*> view) override;
bool listElementHideTopicButton(not_null<const Element*> view) override;
History *listTranslateHistory() override; History *listTranslateHistory() override;
void listAddTranslatedItems( void listAddTranslatedItems(
not_null<TranslateTracker*> tracker) override; not_null<TranslateTracker*> tracker) override;

View file

@ -1458,6 +1458,11 @@ QString ScheduledWidget::listElementAuthorRank(
return {}; return {};
} }
bool ScheduledWidget::listElementHideTopicButton(
not_null<const Element*> view) {
return true;
}
History *ScheduledWidget::listTranslateHistory() { History *ScheduledWidget::listTranslateHistory() {
return nullptr; return nullptr;
} }

View file

@ -159,6 +159,7 @@ public:
Painter &p, Painter &p,
const Ui::ChatPaintContext &context) override; const Ui::ChatPaintContext &context) override;
QString listElementAuthorRank(not_null<const Element*> view) override; QString listElementAuthorRank(not_null<const Element*> view) override;
bool listElementHideTopicButton(not_null<const Element*> view) override;
History *listTranslateHistory() override; History *listTranslateHistory() override;
void listAddTranslatedItems( void listAddTranslatedItems(
not_null<TranslateTracker*> tracker) override; not_null<TranslateTracker*> tracker) override;

View file

@ -744,6 +744,11 @@ QString SublistWidget::listElementAuthorRank(not_null<const Element*> view) {
return {}; return {};
} }
bool SublistWidget::listElementHideTopicButton(
not_null<const Element*> view) {
return true;
}
History *SublistWidget::listTranslateHistory() { History *SublistWidget::listTranslateHistory() {
return _history; return _history;
} }

View file

@ -136,6 +136,7 @@ public:
Painter &p, Painter &p,
const Ui::ChatPaintContext &context) override; const Ui::ChatPaintContext &context) override;
QString listElementAuthorRank(not_null<const Element*> view) override; QString listElementAuthorRank(not_null<const Element*> view) override;
bool listElementHideTopicButton(not_null<const Element*> view) override;
History *listTranslateHistory() override; History *listTranslateHistory() override;
void listAddTranslatedItems( void listAddTranslatedItems(
not_null<TranslateTracker*> tracker) override; not_null<TranslateTracker*> tracker) override;

View file

@ -189,7 +189,15 @@ ClickHandlerPtr AddContactClickHandler(not_null<HistoryItem*> item) {
}); });
} }
} }
box->addButton(tr::lng_cancel(), [=] { box->closeBox(); }); {
const auto inner = box->verticalLayout();
if (inner->count() > 2) {
delete inner->widgetAt(inner->count() - 1);
delete inner->widgetAt(inner->count() - 1);
}
}
box->addButton(tr::lng_close(), [=] { box->closeBox(); });
}; };
} }
@ -259,6 +267,7 @@ QSize Contact::countOptimalSize() {
full); full);
} }
const auto vcardBoxFactory = _vcardBoxFactory;
_buttons.clear(); _buttons.clear();
if (_contact) { if (_contact) {
const auto message = tr::lng_contact_send_message(tr::now).toUpper(); const auto message = tr::lng_contact_send_message(tr::now).toUpper();
@ -276,20 +285,20 @@ QSize Contact::countOptimalSize() {
}); });
} }
_mainButton.link = _buttons.front().link; _mainButton.link = _buttons.front().link;
} else if (const auto vcardBoxFactory = _vcardBoxFactory) { } else if (vcardBoxFactory) {
const auto view = tr::lng_contact_details_button(tr::now).toUpper(); const auto view = tr::lng_contact_details_button(tr::now).toUpper();
_buttons.push_back({ _buttons.push_back({
view, view,
st::semiboldFont->width(view), st::semiboldFont->width(view),
AddContactClickHandler(_parent->data()), AddContactClickHandler(_parent->data()),
}); });
}
if (vcardBoxFactory) {
_mainButton.link = std::make_shared<LambdaClickHandler>([=]( _mainButton.link = std::make_shared<LambdaClickHandler>([=](
const ClickContext &context) { const ClickContext &context) {
const auto my = context.other.value<ClickHandlerContext>(); const auto my = context.other.value<ClickHandlerContext>();
if (const auto controller = my.sessionWindow.get()) { if (const auto controller = my.sessionWindow.get()) {
controller->uiShow()->show(Box([=](not_null<Ui::GenericBox*> box) { controller->uiShow()->show(Box(vcardBoxFactory));
vcardBoxFactory(box);
}));
} }
}); });
} }

View file

@ -113,6 +113,10 @@ void ExtendedPreview::unloadHeavyPart() {
_spoiler.animation = nullptr; _spoiler.animation = nullptr;
} }
bool ExtendedPreview::enforceBubbleWidth() const {
return true;
}
QSize ExtendedPreview::countOptimalSize() { QSize ExtendedPreview::countOptimalSize() {
const auto &preview = _invoice->extendedPreview; const auto &preview = _invoice->extendedPreview;
const auto dimensions = preview.dimensions; const auto dimensions = preview.dimensions;

View file

@ -54,6 +54,7 @@ public:
bool hasHeavyPart() const override; bool hasHeavyPart() const override;
void unloadHeavyPart() override; void unloadHeavyPart() override;
bool enforceBubbleWidth() const override;
private: private:
int minWidthForButton() const; int minWidthForButton() const;

View file

@ -383,6 +383,10 @@ bool Gif::autoplayEnabled() const {
_data); _data);
} }
bool Gif::hideMessageText() const {
return _data->isVideoMessage();
}
void Gif::draw(Painter &p, const PaintContext &context) const { void Gif::draw(Painter &p, const PaintContext &context) const {
if (width() < st::msgPadding.left() + st::msgPadding.right() + 1) return; if (width() < st::msgPadding.left() + st::msgPadding.right() + 1) return;

View file

@ -54,9 +54,7 @@ public:
bool spoiler); bool spoiler);
~Gif(); ~Gif();
bool hideMessageText() const override { bool hideMessageText() const override;
return false;
}
void draw(Painter &p, const PaintContext &context) const override; void draw(Painter &p, const PaintContext &context) const override;
TextState textState(QPoint point, StateRequest request) const override; TextState textState(QPoint point, StateRequest request) const override;

View file

@ -166,6 +166,10 @@ void Photo::unloadHeavyPart() {
togglePollingStory(false); togglePollingStory(false);
} }
bool Photo::enforceBubbleWidth() const {
return true;
}
void Photo::togglePollingStory(bool enabled) const { void Photo::togglePollingStory(bool enabled) const {
const auto pollingStory = (enabled ? 1 : 0); const auto pollingStory = (enabled ? 1 : 0);
if (!_storyId || _pollingStory == pollingStory) { if (!_storyId || _pollingStory == pollingStory) {
@ -197,6 +201,9 @@ QSize Photo::countOptimalSize() {
auto maxWidth = qMax(maxActualWidth, scaled.height()); auto maxWidth = qMax(maxActualWidth, scaled.height());
auto minHeight = qMax(scaled.height(), st::minPhotoSize); auto minHeight = qMax(scaled.height(), st::minPhotoSize);
if (_parent->hasBubble()) { if (_parent->hasBubble()) {
const auto captionMaxWidth = _parent->textualMaxWidth();
const auto maxWithCaption = qMin(st::msgMaxWidth, captionMaxWidth);
maxWidth = qMin(qMax(maxWidth, maxWithCaption), st::msgMaxWidth);
minHeight = adjustHeightForLessCrop( minHeight = adjustHeightForLessCrop(
dimensions, dimensions,
{ maxWidth, minHeight }); { maxWidth, minHeight });

View file

@ -88,6 +88,7 @@ public:
bool hasHeavyPart() const override; bool hasHeavyPart() const override;
void unloadHeavyPart() override; void unloadHeavyPart() override;
bool enforceBubbleWidth() const override;
protected: protected:
float64 dataProgress() const override; float64 dataProgress() const override;

View file

@ -1113,7 +1113,13 @@ void Selector::createList() {
_list->searchQueries( _list->searchQueries(
) | rpl::start_with_next([=](std::vector<QString> &&query) { ) | rpl::start_with_next([=](std::vector<QString> &&query) {
_stickers->applySearchQuery(std::move(query)); _stickers->applySearchQuery(std::move(query));
updateVisibleTopBottom(); }, _stickers->lifetime());
rpl::combine(
_list->heightValue(),
_stickers->heightValue()
) | rpl::start_with_next([=] {
InvokeQueued(lists, updateVisibleTopBottom);
}, _stickers->lifetime()); }, _stickers->lifetime());
rpl::combine( rpl::combine(

View file

@ -1479,7 +1479,11 @@ object_ptr<Ui::RpWidget> DetailsFiller::setupPersonalChannel(
auto &lifetime = preview->lifetime(); auto &lifetime = preview->lifetime();
using namespace Dialogs::Ui; using namespace Dialogs::Ui;
const auto previewView = lifetime.make_state<MessageView>(); const auto previewView = lifetime.make_state<MessageView>();
const auto previewUpdate = [=] { preview->update(); };
preview->resize(0, st::infoLabeled.style.font->height); preview->resize(0, st::infoLabeled.style.font->height);
if (!previewView->dependsOn(item)) {
previewView->prepare(item, nullptr, previewUpdate, {});
}
preview->paintRequest( preview->paintRequest(
) | rpl::start_with_next([=, fullId = item->fullId()]( ) | rpl::start_with_next([=, fullId = item->fullId()](
const QRect &rect) { const QRect &rect) {
@ -1508,11 +1512,7 @@ object_ptr<Ui::RpWidget> DetailsFiller::setupPersonalChannel(
preview->rect(), preview->rect(),
tr::lng_contacts_loading(tr::now), tr::lng_contacts_loading(tr::now),
style::al_left); style::al_left);
previewView->prepare( previewView->prepare(item, nullptr, previewUpdate, {});
item,
nullptr,
[=] { preview->update(); },
{});
preview->update(); preview->update();
} }
}, preview->lifetime()); }, preview->lifetime());

View file

@ -85,7 +85,10 @@ std::unique_ptr<PeerListRow> ListController::createRow(
if (const auto channel = peer->asChannel()) { if (const auto channel = peer->asChannel()) {
if (const auto count = channel->membersCount(); count > 1) { if (const auto count = channel->membersCount(); count > 1) {
result->setCustomStatus( result->setCustomStatus(
tr::lng_chat_status_subscribers(tr::now, lt_count, count)); tr::lng_chat_status_subscribers(
tr::now,
lt_count_decimal,
count));
} }
} }
return result; return result;

View file

@ -861,10 +861,6 @@ public:
void rowClicked(not_null<PeerListRow*> row) override; void rowClicked(not_null<PeerListRow*> row) override;
void loadMoreRows() override; void loadMoreRows() override;
base::unique_qptr<Ui::PopupMenu> rowContextMenu(
QWidget *parent,
not_null<PeerListRow*> row) override;
[[nodiscard]] bool skipRequest() const; [[nodiscard]] bool skipRequest() const;
void requestNext(); void requestNext();
@ -957,29 +953,6 @@ void CreditsController::rowClicked(not_null<PeerListRow*> row) {
} }
} }
base::unique_qptr<Ui::PopupMenu> CreditsController::rowContextMenu(
QWidget *parent,
not_null<PeerListRow*> row) {
const auto entry = static_cast<const CreditsRow*>(row.get())->entry();
if (!entry.bareId) {
return nullptr;
}
auto menu = base::make_unique_q<Ui::PopupMenu>(
parent,
st::defaultPopupMenu);
const auto peer = row->peer();
const auto callback = crl::guard(parent, [=, id = entry.id] {
const auto show = delegate()->peerListUiShow();
Api::CreditsRefund(
peer,
id,
[=] { show->showToast(tr::lng_report_spam_done(tr::now)); },
[=](const QString &error) { show->showToast(error); });
});
menu->addAction(tr::lng_channel_earn_history_return(tr::now), callback);
return menu;
}
rpl::producer<bool> CreditsController::allLoadedValue() const { rpl::producer<bool> CreditsController::allLoadedValue() const {
return _allLoaded.value(); return _allLoaded.value();
} }

View file

@ -1895,9 +1895,18 @@ void OverlayWidget::contentSizeChanged() {
} }
void OverlayWidget::recountSkipTop() { void OverlayWidget::recountSkipTop() {
const auto bottom = (!_streamed || !_streamed->controls) const auto controllerBottomNoFullScreenVideo = _groupThumbs
? height() ? _groupThumbsTop
: (_streamed->controls->y() - st::mediaviewCaptionPadding.bottom()); : height();
// We need the bottom in case of non-full-screen-video mode
// to count correct _availableHeight in non-full-screen-video mode.
//
// Originally this is controls->y() - padding.bottom().
const auto bottom = (_streamed && _streamed->controls)
? (controllerBottomNoFullScreenVideo
- _streamed->controls->height()
- 2 * st::mediaviewCaptionPadding.bottom())
: height();
const auto skipHeightBottom = (height() - bottom); const auto skipHeightBottom = (height() - bottom);
_skipTop = _minUsedTop + std::min( _skipTop = _minUsedTop + std::min(
std::max( std::max(
@ -1963,7 +1972,7 @@ void OverlayWidget::resizeContentByScreenSize() {
_h = _height; _h = _height;
} }
_x = (width() - _w) / 2; _x = (width() - _w) / 2;
_y = _skipTop + (_availableHeight - _h) / 2; _y = _skipTop + (useh - _h) / 2;
_geometryAnimation.stop(); _geometryAnimation.stop();
} }
@ -4031,7 +4040,7 @@ void OverlayWidget::refreshClipControllerGeometry() {
st::mediaviewControllerSize.height()); st::mediaviewControllerSize.height());
_streamed->controls->move( _streamed->controls->move(
(width() - controllerWidth) / 2, (width() - controllerWidth) / 2,
(controllerBottom (controllerBottom // Duplicated in recountSkipTop().
- _streamed->controls->height() - _streamed->controls->height()
- st::mediaviewCaptionPadding.bottom())); - st::mediaviewCaptionPadding.bottom()));
Ui::SendPendingMoveResizeEvents(_streamed->controls.get()); Ui::SendPendingMoveResizeEvents(_streamed->controls.get());

View file

@ -161,6 +161,7 @@ private:
Painter &p, Painter &p,
const Ui::ChatPaintContext &context) override; const Ui::ChatPaintContext &context) override;
QString listElementAuthorRank(not_null<const Element*> view) override; QString listElementAuthorRank(not_null<const Element*> view) override;
bool listElementHideTopicButton(not_null<const Element*> view) override;
History *listTranslateHistory() override; History *listTranslateHistory() override;
void listAddTranslatedItems( void listAddTranslatedItems(
not_null<TranslateTracker*> tracker) override; not_null<TranslateTracker*> tracker) override;
@ -1046,6 +1047,11 @@ QString ShortcutMessages::listElementAuthorRank(
return {}; return {};
} }
bool ShortcutMessages::listElementHideTopicButton(
not_null<const Element*> view) {
return true;
}
History *ShortcutMessages::listTranslateHistory() { History *ShortcutMessages::listTranslateHistory() {
return nullptr; return nullptr;
} }

View file

@ -165,7 +165,7 @@ QImage GenerateStars(int height, int count) {
void FillCreditOptions( void FillCreditOptions(
not_null<Window::SessionController*> controller, not_null<Window::SessionController*> controller,
not_null<Ui::VerticalLayout*> container, not_null<Ui::VerticalLayout*> container,
int minCredits, int minimumCredits,
Fn<void()> paid) { Fn<void()> paid) {
const auto options = container->add( const auto options = container->add(
object_ptr<Ui::SlideWrap<Ui::VerticalLayout>>( object_ptr<Ui::SlideWrap<Ui::VerticalLayout>>(
@ -191,6 +191,10 @@ void FillCreditOptions(
- st.iconLeft - st.iconLeft
- singleStarWidth; - singleStarWidth;
const auto buttonHeight = st.height + rect::m::sum::v(st.padding); const auto buttonHeight = st.height + rect::m::sum::v(st.padding);
const auto minCredits = (!options.empty()
&& (minimumCredits > options.back().credits))
? 0
: minimumCredits;
for (auto i = 0; i < options.size(); i++) { for (auto i = 0; i < options.size(); i++) {
const auto &option = options[i]; const auto &option = options[i];
if (option.credits < minCredits) { if (option.credits < minCredits) {
@ -269,10 +273,11 @@ void FillCreditOptions(
{ {
auto text = tr::lng_credits_summary_options_about( auto text = tr::lng_credits_summary_options_about(
lt_link, lt_link,
tr::lng_credits_summary_options_about_link( rpl::combine(
) | rpl::map([](const QString &t) { tr::lng_credits_summary_options_about_link(),
using namespace Ui::Text; tr::lng_credits_summary_options_about_url()
return Link(t, u"https://telegram.org/tos"_q); ) | rpl::map([](const QString &text, const QString &url) {
return Ui::Text::Link(text, url);
}), }),
Ui::Text::RichLangValue); Ui::Text::RichLangValue);
Ui::AddSkip(content); Ui::AddSkip(content);

View file

@ -1111,6 +1111,7 @@ previewMarkRead: FlatButton(historyComposeButton) {
previewName: FlatLabel(defaultFlatLabel) { previewName: FlatLabel(defaultFlatLabel) {
style: semiboldTextStyle; style: semiboldTextStyle;
textFg: windowFg; textFg: windowFg;
maxHeight: 30px;
} }
previewStatus: FlatLabel(defaultFlatLabel) { previewStatus: FlatLabel(defaultFlatLabel) {
textFg: windowSubTextFg; textFg: windowSubTextFg;

View file

@ -14,6 +14,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_peer.h" #include "data/data_peer.h"
#include "data/data_photo.h" #include "data/data_photo.h"
#include "data/data_photo_media.h" #include "data/data_photo_media.h"
#include "data/data_session.h"
#include "data/stickers/data_custom_emoji.h"
#include "data/data_story.h" #include "data/data_story.h"
#include "main/main_session.h" #include "main/main_session.h"
#include "ui/empty_userpic.h" #include "ui/empty_userpic.h"
@ -28,6 +30,8 @@ class PeerUserpic final : public DynamicImage {
public: public:
PeerUserpic(not_null<PeerData*> peer, bool forceRound); PeerUserpic(not_null<PeerData*> peer, bool forceRound);
std::shared_ptr<DynamicImage> clone() override;
QImage image(int size) override; QImage image(int size) override;
void subscribeToUpdates(Fn<void()> callback) override; void subscribeToUpdates(Fn<void()> callback) override;
@ -58,7 +62,6 @@ private:
class StoryThumbnail : public DynamicImage { class StoryThumbnail : public DynamicImage {
public: public:
explicit StoryThumbnail(FullStoryId id); explicit StoryThumbnail(FullStoryId id);
virtual ~StoryThumbnail() = default;
QImage image(int size) override; QImage image(int size) override;
void subscribeToUpdates(Fn<void()> callback) override; void subscribeToUpdates(Fn<void()> callback) override;
@ -68,6 +71,9 @@ protected:
Image *image = nullptr; Image *image = nullptr;
bool blurred = false; bool blurred = false;
}; };
[[nodiscard]] FullStoryId id() const;
[[nodiscard]] virtual Main::Session &session() = 0; [[nodiscard]] virtual Main::Session &session() = 0;
[[nodiscard]] virtual Thumb loaded(FullStoryId id) = 0; [[nodiscard]] virtual Thumb loaded(FullStoryId id) = 0;
virtual void clear() = 0; virtual void clear() = 0;
@ -85,6 +91,8 @@ class PhotoThumbnail final : public StoryThumbnail {
public: public:
PhotoThumbnail(not_null<PhotoData*> photo, FullStoryId id); PhotoThumbnail(not_null<PhotoData*> photo, FullStoryId id);
std::shared_ptr<DynamicImage> clone() override;
private: private:
Main::Session &session() override; Main::Session &session() override;
Thumb loaded(FullStoryId id) override; Thumb loaded(FullStoryId id) override;
@ -99,6 +107,8 @@ class VideoThumbnail final : public StoryThumbnail {
public: public:
VideoThumbnail(not_null<DocumentData*> video, FullStoryId id); VideoThumbnail(not_null<DocumentData*> video, FullStoryId id);
std::shared_ptr<DynamicImage> clone() override;
private: private:
Main::Session &session() override; Main::Session &session() override;
Thumb loaded(FullStoryId id) override; Thumb loaded(FullStoryId id) override;
@ -111,6 +121,8 @@ private:
class EmptyThumbnail final : public DynamicImage { class EmptyThumbnail final : public DynamicImage {
public: public:
std::shared_ptr<DynamicImage> clone() override;
QImage image(int size) override; QImage image(int size) override;
void subscribeToUpdates(Fn<void()> callback) override; void subscribeToUpdates(Fn<void()> callback) override;
@ -121,6 +133,8 @@ private:
class SavedMessagesUserpic final : public DynamicImage { class SavedMessagesUserpic final : public DynamicImage {
public: public:
std::shared_ptr<DynamicImage> clone() override;
QImage image(int size) override; QImage image(int size) override;
void subscribeToUpdates(Fn<void()> callback) override; void subscribeToUpdates(Fn<void()> callback) override;
@ -132,6 +146,8 @@ private:
class RepliesUserpic final : public DynamicImage { class RepliesUserpic final : public DynamicImage {
public: public:
std::shared_ptr<DynamicImage> clone() override;
QImage image(int size) override; QImage image(int size) override;
void subscribeToUpdates(Fn<void()> callback) override; void subscribeToUpdates(Fn<void()> callback) override;
@ -141,11 +157,48 @@ private:
}; };
class IconThumbnail final : public DynamicImage {
public:
explicit IconThumbnail(const style::icon &icon);
std::shared_ptr<DynamicImage> clone() override;
QImage image(int size) override;
void subscribeToUpdates(Fn<void()> callback) override;
private:
const style::icon &_icon;
int _paletteVersion = 0;
QImage _frame;
};
class EmojiThumbnail final : public DynamicImage {
public:
EmojiThumbnail(not_null<Data::Session*> owner, const QString &data);
std::shared_ptr<DynamicImage> clone() override;
QImage image(int size) override;
void subscribeToUpdates(Fn<void()> callback) override;
private:
const not_null<Data::Session*> _owner;
const QString _data;
std::unique_ptr<Ui::Text::CustomEmoji> _emoji;
QImage _frame;
};
PeerUserpic::PeerUserpic(not_null<PeerData*> peer, bool forceRound) PeerUserpic::PeerUserpic(not_null<PeerData*> peer, bool forceRound)
: _peer(peer) : _peer(peer)
, _forceRound(forceRound) { , _forceRound(forceRound) {
} }
std::shared_ptr<DynamicImage> PeerUserpic::clone() {
return std::make_shared<PeerUserpic>(_peer, _forceRound);
}
QImage PeerUserpic::image(int size) { QImage PeerUserpic::image(int size) {
Expects(_subscribed != nullptr); Expects(_subscribed != nullptr);
@ -280,11 +333,19 @@ void StoryThumbnail::subscribeToUpdates(Fn<void()> callback) {
} }
} }
FullStoryId StoryThumbnail::id() const {
return _id;
}
PhotoThumbnail::PhotoThumbnail(not_null<PhotoData*> photo, FullStoryId id) PhotoThumbnail::PhotoThumbnail(not_null<PhotoData*> photo, FullStoryId id)
: StoryThumbnail(id) : StoryThumbnail(id)
, _photo(photo) { , _photo(photo) {
} }
std::shared_ptr<DynamicImage> PhotoThumbnail::clone() {
return std::make_shared<PhotoThumbnail>(_photo, id());
}
Main::Session &PhotoThumbnail::session() { Main::Session &PhotoThumbnail::session() {
return _photo->session(); return _photo->session();
} }
@ -311,6 +372,10 @@ VideoThumbnail::VideoThumbnail(
, _video(video) { , _video(video) {
} }
std::shared_ptr<DynamicImage> VideoThumbnail::clone() {
return std::make_shared<VideoThumbnail>(_video, id());
}
Main::Session &VideoThumbnail::session() { Main::Session &VideoThumbnail::session() {
return _video->session(); return _video->session();
} }
@ -330,6 +395,10 @@ void VideoThumbnail::clear() {
_media = nullptr; _media = nullptr;
} }
std::shared_ptr<DynamicImage> EmptyThumbnail::clone() {
return std::make_shared<EmptyThumbnail>();
}
QImage EmptyThumbnail::image(int size) { QImage EmptyThumbnail::image(int size) {
const auto ratio = style::DevicePixelRatio(); const auto ratio = style::DevicePixelRatio();
if (_cached.width() != size * ratio) { if (_cached.width() != size * ratio) {
@ -345,6 +414,10 @@ QImage EmptyThumbnail::image(int size) {
void EmptyThumbnail::subscribeToUpdates(Fn<void()> callback) { void EmptyThumbnail::subscribeToUpdates(Fn<void()> callback) {
} }
std::shared_ptr<DynamicImage> SavedMessagesUserpic::clone() {
return std::make_shared<SavedMessagesUserpic>();
}
QImage SavedMessagesUserpic::image(int size) { QImage SavedMessagesUserpic::image(int size) {
const auto good = (_frame.width() == size * _frame.devicePixelRatio()); const auto good = (_frame.width() == size * _frame.devicePixelRatio());
const auto paletteVersion = style::PaletteVersion(); const auto paletteVersion = style::PaletteVersion();
@ -367,6 +440,13 @@ QImage SavedMessagesUserpic::image(int size) {
} }
void SavedMessagesUserpic::subscribeToUpdates(Fn<void()> callback) { void SavedMessagesUserpic::subscribeToUpdates(Fn<void()> callback) {
if (!callback) {
_frame = {};
}
}
std::shared_ptr<DynamicImage> RepliesUserpic::clone() {
return std::make_shared<RepliesUserpic>();
} }
QImage RepliesUserpic::image(int size) { QImage RepliesUserpic::image(int size) {
@ -391,6 +471,90 @@ QImage RepliesUserpic::image(int size) {
} }
void RepliesUserpic::subscribeToUpdates(Fn<void()> callback) { void RepliesUserpic::subscribeToUpdates(Fn<void()> callback) {
if (!callback) {
_frame = {};
}
}
IconThumbnail::IconThumbnail(const style::icon &icon) : _icon(icon) {
}
std::shared_ptr<DynamicImage> IconThumbnail::clone() {
return std::make_shared<IconThumbnail>(_icon);
}
QImage IconThumbnail::image(int size) {
const auto good = (_frame.width() == size * _frame.devicePixelRatio());
const auto paletteVersion = style::PaletteVersion();
if (!good || _paletteVersion != paletteVersion) {
_paletteVersion = paletteVersion;
const auto ratio = style::DevicePixelRatio();
if (!good) {
_frame = QImage(
QSize(size, size) * ratio,
QImage::Format_ARGB32_Premultiplied);
_frame.setDevicePixelRatio(ratio);
}
_frame.fill(Qt::transparent);
auto p = Painter(&_frame);
_icon.paintInCenter(p, QRect(0, 0, size, size));
}
return _frame;
}
void IconThumbnail::subscribeToUpdates(Fn<void()> callback) {
if (!callback) {
_frame = {};
}
}
EmojiThumbnail::EmojiThumbnail(
not_null<Data::Session*> owner,
const QString &data)
: _owner(owner)
, _data(data) {
}
void EmojiThumbnail::subscribeToUpdates(Fn<void()> callback) {
if (!callback) {
_emoji = nullptr;
return;
}
_emoji = _owner->customEmojiManager().create(
_data,
std::move(callback),
Data::CustomEmojiSizeTag::Large);
}
std::shared_ptr<DynamicImage> EmojiThumbnail::clone() {
return std::make_shared<EmojiThumbnail>(_owner, _data);
}
QImage EmojiThumbnail::image(int size) {
Expects(_emoji != nullptr);
const auto ratio = style::DevicePixelRatio();
const auto good = (_frame.width() == size * _frame.devicePixelRatio());
if (!good) {
_frame = QImage(
QSize(size, size) * ratio,
QImage::Format_ARGB32_Premultiplied);
_frame.setDevicePixelRatio(ratio);
}
_frame.fill(Qt::transparent);
auto p = Painter(&_frame);
_emoji->paint(p, {
.textColor = st::windowBoldFg->c,
.now = crl::now(),
.position = QPoint(0, 0),
.paused = false,
});
p.end();
return _frame;
} }
} // namespace } // namespace
@ -422,4 +586,14 @@ std::shared_ptr<DynamicImage> MakeStoryThumbnail(
}); });
} }
std::shared_ptr<DynamicImage> MakeIconThumbnail(const style::icon &icon) {
return std::make_shared<IconThumbnail>(icon);
}
std::shared_ptr<DynamicImage> MakeEmojiThumbnail(
not_null<Data::Session*> owner,
const QString &data) {
return std::make_shared<EmojiThumbnail>(owner, data);
}
} // namespace Ui } // namespace Ui

View file

@ -11,6 +11,7 @@ class PeerData;
namespace Data { namespace Data {
class Story; class Story;
class Session;
} // namespace Data } // namespace Data
namespace Ui { namespace Ui {
@ -24,5 +25,10 @@ class DynamicImage;
[[nodiscard]] std::shared_ptr<DynamicImage> MakeRepliesThumbnail(); [[nodiscard]] std::shared_ptr<DynamicImage> MakeRepliesThumbnail();
[[nodiscard]] std::shared_ptr<DynamicImage> MakeStoryThumbnail( [[nodiscard]] std::shared_ptr<DynamicImage> MakeStoryThumbnail(
not_null<Data::Story*> story); not_null<Data::Story*> story);
[[nodiscard]] std::shared_ptr<DynamicImage> MakeIconThumbnail(
const style::icon &icon);
[[nodiscard]] std::shared_ptr<DynamicImage> MakeEmojiThumbnail(
not_null<Data::Session*> owner,
const QString &data);
} // namespace Ui } // namespace Ui

View file

@ -136,6 +136,7 @@ menuIconGroupCreate: icon {{ "menu/groups_create", menuIconColor }};
menuIconSigned: icon {{ "menu/signed", menuIconColor }}; menuIconSigned: icon {{ "menu/signed", menuIconColor }};
menuIconAntispam: icon {{ "menu/antispam", menuIconColor }}; menuIconAntispam: icon {{ "menu/antispam", menuIconColor }};
menuIconChatDiscuss: icon {{ "menu/chat_discuss", menuIconColor }}; menuIconChatDiscuss: icon {{ "menu/chat_discuss", menuIconColor }};
menuIconChats: icon {{ "menu/chats", menuIconColor }};
menuIconBotCommands: icon {{ "menu/bot_commands", menuIconColor }}; menuIconBotCommands: icon {{ "menu/bot_commands", menuIconColor }};
menuIconPremium: icon {{ "menu/premium", menuIconColor }}; menuIconPremium: icon {{ "menu/premium", menuIconColor }};
menuIconShop: icon {{ "menu/shop", menuIconColor }}; menuIconShop: icon {{ "menu/shop", menuIconColor }};

View file

@ -172,7 +172,7 @@ FROM builder AS libvpx
RUN git init libvpx \ RUN git init libvpx \
&& cd libvpx \ && cd libvpx \
&& git remote add origin {{ GIT }}/webmproject/libvpx.git \ && git remote add origin {{ GIT }}/webmproject/libvpx.git \
&& git fetch --depth=1 origin 51057f4ba894e13f9bba278905bacf6aaaecd992 \ && git fetch --depth=1 origin 12f3a2ac603e8f10742105519e0cd03c3b8f71dd \
&& git reset --hard FETCH_HEAD \ && git reset --hard FETCH_HEAD \
&& CFLAGS="$CFLAGS -fno-lto" CXXFLAGS="$CXXFLAGS -fno-lto" ./configure \ && CFLAGS="$CFLAGS -fno-lto" CXXFLAGS="$CXXFLAGS -fno-lto" ./configure \
--disable-examples \ --disable-examples \

View file

@ -107,7 +107,7 @@ elif (win64):
elif (mac): elif (mac):
environment.update({ environment.update({
'SPECIAL_TARGET': 'mac', 'SPECIAL_TARGET': 'mac',
'MAKE_THREADS_CNT': '-j8', 'MAKE_THREADS_CNT': '-j' + str(os.cpu_count()),
'MACOSX_DEPLOYMENT_TARGET': '10.13', 'MACOSX_DEPLOYMENT_TARGET': '10.13',
'UNGUARDED': '-Werror=unguarded-availability-new', 'UNGUARDED': '-Werror=unguarded-availability-new',
'MIN_VER': '-mmacosx-version-min=10.13', 'MIN_VER': '-mmacosx-version-min=10.13',
@ -435,7 +435,7 @@ if customRunCommand:
stage('patches', """ stage('patches', """
git clone https://github.com/desktop-app/patches.git git clone https://github.com/desktop-app/patches.git
cd patches cd patches
git checkout 25f76cf4d5 git checkout c237d12bcd
""") """)
stage('msys64', """ stage('msys64', """
@ -511,9 +511,9 @@ stage('lzma', """
win: win:
git clone https://github.com/desktop-app/lzma.git git clone https://github.com/desktop-app/lzma.git
cd lzma\\C\\Util\\LzmaLib cd lzma\\C\\Util\\LzmaLib
msbuild LzmaLib.sln /property:Configuration=Debug /property:Platform="$X8664" msbuild -m LzmaLib.sln /property:Configuration=Debug /property:Platform="$X8664"
release: release:
msbuild LzmaLib.sln /property:Configuration=Release /property:Platform="$X8664" msbuild -m LzmaLib.sln /property:Configuration=Release /property:Platform="$X8664"
""") """)
stage('xz', """ stage('xz', """
@ -540,9 +540,9 @@ win:
-DCMAKE_C_FLAGS_DEBUG="/MTd /Zi /Ob0 /Od /RTC1" ^ -DCMAKE_C_FLAGS_DEBUG="/MTd /Zi /Ob0 /Od /RTC1" ^
-DCMAKE_C_FLAGS_RELEASE="/MT /O2 /Ob2 /DNDEBUG" ^ -DCMAKE_C_FLAGS_RELEASE="/MT /O2 /Ob2 /DNDEBUG" ^
-DCMAKE_C_FLAGS="/DZLIB_WINAPI" -DCMAKE_C_FLAGS="/DZLIB_WINAPI"
cmake --build . --config Debug cmake --build . --config Debug --parallel
release: release:
cmake --build . --config Release cmake --build . --config Release --parallel
mac: mac:
CFLAGS="$MIN_VER $UNGUARDED" LDFLAGS="$MIN_VER" ./configure \\ CFLAGS="$MIN_VER $UNGUARDED" LDFLAGS="$MIN_VER" ./configure \\
--static \\ --static \\
@ -560,9 +560,9 @@ win:
-A %WIN32X64% ^ -A %WIN32X64% ^
-DWITH_JPEG8=ON ^ -DWITH_JPEG8=ON ^
-DPNG_SUPPORTED=OFF -DPNG_SUPPORTED=OFF
cmake --build . --config Debug cmake --build . --config Debug --parallel
release: release:
cmake --build . --config Release cmake --build . --config Release --parallel
mac: mac:
CFLAGS="-arch arm64" cmake -B build.arm64 . \\ CFLAGS="-arch arm64" cmake -B build.arm64 . \\
-D CMAKE_SYSTEM_NAME=Darwin \\ -D CMAKE_SYSTEM_NAME=Darwin \\
@ -643,8 +643,8 @@ win:
-DCMAKE_MSVC_RUNTIME_LIBRARY="MultiThreaded$<$<CONFIG:Debug>:Debug>" ^ -DCMAKE_MSVC_RUNTIME_LIBRARY="MultiThreaded$<$<CONFIG:Debug>:Debug>" ^
-DCMAKE_C_FLAGS_DEBUG="/MTd /Zi /Ob0 /Od /RTC1" ^ -DCMAKE_C_FLAGS_DEBUG="/MTd /Zi /Ob0 /Od /RTC1" ^
-DCMAKE_C_FLAGS_RELEASE="/MT /O2 /Ob2 /DNDEBUG" -DCMAKE_C_FLAGS_RELEASE="/MT /O2 /Ob2 /DNDEBUG"
cmake --build out --config Debug cmake --build out --config Debug --parallel
cmake --build out --config Release cmake --build out --config Release --parallel
cmake --install out --config Release cmake --install out --config Release
mac: mac:
CFLAGS="$UNGUARDED" CPPFLAGS="$UNGUARDED" cmake -B build . \\ CFLAGS="$UNGUARDED" CPPFLAGS="$UNGUARDED" cmake -B build . \\
@ -663,9 +663,9 @@ stage('rnnoise', """
cd out cd out
win: win:
cmake -A %WIN32X64% .. cmake -A %WIN32X64% ..
cmake --build . --config Debug cmake --build . --config Debug --parallel
release: release:
cmake --build . --config Release cmake --build . --config Release --parallel
!win: !win:
mkdir Debug mkdir Debug
cd Debug cd Debug
@ -780,10 +780,10 @@ win:
-DBUILD_SHARED_LIBS=OFF ^ -DBUILD_SHARED_LIBS=OFF ^
-DAVIF_ENABLE_WERROR=OFF ^ -DAVIF_ENABLE_WERROR=OFF ^
-DAVIF_CODEC_DAV1D=ON -DAVIF_CODEC_DAV1D=ON
cmake --build . --config Debug cmake --build . --config Debug --parallel
cmake --install . --config Debug cmake --install . --config Debug
release: release:
cmake --build . --config Release cmake --build . --config Release --parallel
cmake --install . --config Release cmake --install . --config Release
mac: mac:
cmake . \\ cmake . \\
@ -816,10 +816,10 @@ win:
-DBUILD_SHARED_LIBS=OFF ^ -DBUILD_SHARED_LIBS=OFF ^
-DENABLE_DECODER=OFF ^ -DENABLE_DECODER=OFF ^
-DENABLE_ENCODER=OFF -DENABLE_ENCODER=OFF
cmake --build . --config Debug cmake --build . --config Debug --parallel
cmake --install . --config Debug cmake --install . --config Debug
release: release:
cmake --build . --config Release cmake --build . --config Release --parallel
cmake --install . --config Release cmake --install . --config Release
mac: mac:
cmake . \\ cmake . \\
@ -898,10 +898,10 @@ win:
-DWITH_RAV1E=OFF ^ -DWITH_RAV1E=OFF ^
-DWITH_RAV1E_PLUGIN=OFF ^ -DWITH_RAV1E_PLUGIN=OFF ^
-DWITH_EXAMPLES=OFF -DWITH_EXAMPLES=OFF
cmake --build . --config Debug cmake --build . --config Debug --parallel
cmake --install . --config Debug cmake --install . --config Debug
release: release:
cmake --build . --config Release cmake --build . --config Release --parallel
cmake --install . --config Release cmake --install . --config Release
mac: mac:
cmake . \\ cmake . \\
@ -964,10 +964,10 @@ win:
-DCMAKE_C_FLAGS_RELEASE="/MT /O2 /Ob2 /DNDEBUG" ^ -DCMAKE_C_FLAGS_RELEASE="/MT /O2 /Ob2 /DNDEBUG" ^
-DCMAKE_CXX_FLAGS_RELEASE="/MT /O2 /Ob2 /DNDEBUG" ^ -DCMAKE_CXX_FLAGS_RELEASE="/MT /O2 /Ob2 /DNDEBUG" ^
%cmake_defines% %cmake_defines%
cmake --build . --config Debug cmake --build . --config Debug --parallel
cmake --install . --config Debug cmake --install . --config Debug
release: release:
cmake --build . --config Release cmake --build . --config Release --parallel
cmake --install . --config Release cmake --install . --config Release
mac: mac:
cmake . \\ cmake . \\
@ -983,7 +983,7 @@ stage('libvpx', """
git clone https://github.com/webmproject/libvpx.git git clone https://github.com/webmproject/libvpx.git
depends:patches/libvpx/*.patch depends:patches/libvpx/*.patch
cd libvpx cd libvpx
git checkout 51057f4ba8 git checkout v1.14.1
win: win:
for /r %%i in (..\\patches\\libvpx\\*) do git apply %%i for /r %%i in (..\\patches\\libvpx\\*) do git apply %%i
@ -1322,7 +1322,7 @@ release:
ninja -C out/Release%FolderPostfix% common crash_generation_client exception_handler ninja -C out/Release%FolderPostfix% common crash_generation_client exception_handler
cd tools\\windows\\dump_syms cd tools\\windows\\dump_syms
gyp dump_syms.gyp --format=msvs gyp dump_syms.gyp --format=msvs
msbuild dump_syms.vcxproj /property:Configuration=Release /property:Platform="x64" msbuild -m dump_syms.vcxproj /property:Configuration=Release /property:Platform="x64"
win: win:
deactivate deactivate
mac: mac:

View file

@ -1,7 +1,7 @@
AppVersion 5001002 AppVersion 5001007
AppVersionStrMajor 5.1 AppVersionStrMajor 5.1
AppVersionStrSmall 5.1.2 AppVersionStrSmall 5.1.7
AppVersionStr 5.1.2 AppVersionStr 5.1.7
BetaChannel 0 BetaChannel 0
AlphaVersion 0 AlphaVersion 0
AppVersionOriginal 5.1.2 AppVersionOriginal 5.1.7

View file

@ -92,8 +92,8 @@ PRIVATE
dialogs/dialogs_three_state_icon.h dialogs/dialogs_three_state_icon.h
dialogs/ui/chat_search_empty.cpp dialogs/ui/chat_search_empty.cpp
dialogs/ui/chat_search_empty.h dialogs/ui/chat_search_empty.h
dialogs/ui/chat_search_tabs.cpp dialogs/ui/chat_search_in.cpp
dialogs/ui/chat_search_tabs.h dialogs/ui/chat_search_in.h
dialogs/ui/dialogs_stories_list.cpp dialogs/ui/dialogs_stories_list.cpp
dialogs/ui/dialogs_stories_list.h dialogs/ui/dialogs_stories_list.h
dialogs/ui/top_peers_strip.cpp dialogs/ui/top_peers_strip.cpp

@ -1 +1 @@
Subproject commit 115530c7aa8694f461d04f95d6a3561a18562e7e Subproject commit 659b9181240aae16c05ef8ab7e6c4dd527afcf8a

View file

@ -1,3 +1,31 @@
5.1.7 (14.06.24)
- Fix recently searched hashtags in chats search.
- Fix formatting shortcuts on macOS.
- Fix non-Telegram-Stars-invoice bot buttons with star emoji.
5.1.6 (13.06.24)
- Fix search in archived chats in single-column layout.
- Improve chat previews for topics, Saved Messages and groups.
- Fix formatting shortcuts on Linux.
- Fix options for Telegram Stars buying in case of large amounts.
5.1.5 (07.06.24)
- Return WebView on Windows.
5.1.4 (06.06.24)
- Improve design of search in chat.
- Show vCard information for shared contacts.
- Allow scheduling media in topic groups.
- Several minor bugfixes.
5.1.3 (04.06.24)
- Rebuild version for macOS to fix the phrases.
5.1.2 (03.06.24) 5.1.2 (03.06.24)
- Several bugs fixed including a couple of crashes. - Several bugs fixed including a couple of crashes.