diff --git a/Telegram/Resources/icons/menu/chats.png b/Telegram/Resources/icons/menu/chats.png
new file mode 100644
index 000000000..e97616ee0
Binary files /dev/null and b/Telegram/Resources/icons/menu/chats.png differ
diff --git a/Telegram/Resources/icons/menu/chats@2x.png b/Telegram/Resources/icons/menu/chats@2x.png
new file mode 100644
index 000000000..6682dbcf6
Binary files /dev/null and b/Telegram/Resources/icons/menu/chats@2x.png differ
diff --git a/Telegram/Resources/icons/menu/chats@3x.png b/Telegram/Resources/icons/menu/chats@3x.png
new file mode 100644
index 000000000..7ac3ab7d0
Binary files /dev/null and b/Telegram/Resources/icons/menu/chats@3x.png differ
diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings
index bd153b6e7..c34c6406e 100644
--- a/Telegram/Resources/langs/lang.strings
+++ b/Telegram/Resources/langs/lang.strings
@@ -2316,6 +2316,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"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_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_in" = "Incoming";
"lng_credits_summary_history_tab_out" = "Outgoing";
diff --git a/Telegram/Resources/uwp/AppX/AppxManifest.xml b/Telegram/Resources/uwp/AppX/AppxManifest.xml
index 8068a8279..fed726359 100644
--- a/Telegram/Resources/uwp/AppX/AppxManifest.xml
+++ b/Telegram/Resources/uwp/AppX/AppxManifest.xml
@@ -10,7 +10,7 @@
+ Version="5.1.7.0" />
Telegram Desktop
Telegram Messenger LLP
diff --git a/Telegram/Resources/winrc/Telegram.rc b/Telegram/Resources/winrc/Telegram.rc
index e37585ba0..7935257fe 100644
--- a/Telegram/Resources/winrc/Telegram.rc
+++ b/Telegram/Resources/winrc/Telegram.rc
@@ -44,8 +44,8 @@ IDI_ICON1 ICON "..\\art\\icon256.ico"
//
VS_VERSION_INFO VERSIONINFO
- FILEVERSION 5,1,2,0
- PRODUCTVERSION 5,1,2,0
+ FILEVERSION 5,1,7,0
+ PRODUCTVERSION 5,1,7,0
FILEFLAGSMASK 0x3fL
#ifdef _DEBUG
FILEFLAGS 0x1L
@@ -62,10 +62,10 @@ BEGIN
BEGIN
VALUE "CompanyName", "Radolyn Labs"
VALUE "FileDescription", "AyuGram Desktop"
- VALUE "FileVersion", "5.1.2.0"
+ VALUE "FileVersion", "5.1.7.0"
VALUE "LegalCopyright", "Copyright (C) 2014-2024"
VALUE "ProductName", "AyuGram Desktop"
- VALUE "ProductVersion", "5.1.2.0"
+ VALUE "ProductVersion", "5.1.7.0"
END
END
BLOCK "VarFileInfo"
diff --git a/Telegram/Resources/winrc/Updater.rc b/Telegram/Resources/winrc/Updater.rc
index dccfed972..abc321c45 100644
--- a/Telegram/Resources/winrc/Updater.rc
+++ b/Telegram/Resources/winrc/Updater.rc
@@ -35,8 +35,8 @@ LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
//
VS_VERSION_INFO VERSIONINFO
- FILEVERSION 5,1,2,0
- PRODUCTVERSION 5,1,2,0
+ FILEVERSION 5,1,7,0
+ PRODUCTVERSION 5,1,7,0
FILEFLAGSMASK 0x3fL
#ifdef _DEBUG
FILEFLAGS 0x1L
@@ -53,10 +53,10 @@ BEGIN
BEGIN
VALUE "CompanyName", "Radolyn Labs"
VALUE "FileDescription", "AyuGram Desktop Updater"
- VALUE "FileVersion", "5.1.2.0"
+ VALUE "FileVersion", "5.1.7.0"
VALUE "LegalCopyright", "Copyright (C) 2014-2024"
VALUE "ProductName", "AyuGram Desktop"
- VALUE "ProductVersion", "5.1.2.0"
+ VALUE "ProductVersion", "5.1.7.0"
END
END
BLOCK "VarFileInfo"
diff --git a/Telegram/SourceFiles/api/api_credits.cpp b/Telegram/SourceFiles/api/api_credits.cpp
index 00f9e789f..44d274e58 100644
--- a/Telegram/SourceFiles/api/api_credits.cpp
+++ b/Telegram/SourceFiles/api/api_credits.cpp
@@ -199,24 +199,4 @@ rpl::producer> PremiumPeerBot(
};
}
-void CreditsRefund(
- not_null peer,
- const QString &entryId,
- Fn done,
- Fn 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
diff --git a/Telegram/SourceFiles/api/api_credits.h b/Telegram/SourceFiles/api/api_credits.h
index bd064a65b..265e7b387 100644
--- a/Telegram/SourceFiles/api/api_credits.h
+++ b/Telegram/SourceFiles/api/api_credits.h
@@ -68,12 +68,6 @@ private:
};
-void CreditsRefund(
- not_null peer,
- const QString &entryId,
- Fn done,
- Fn fail);
-
[[nodiscard]] rpl::producer> PremiumPeerBot(
not_null session);
diff --git a/Telegram/SourceFiles/core/mime_type.cpp b/Telegram/SourceFiles/core/mime_type.cpp
index cc1c14c84..5e15fd4e3 100644
--- a/Telegram/SourceFiles/core/mime_type.cpp
+++ b/Telegram/SourceFiles/core/mime_type.cpp
@@ -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 \
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 \
-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
u"\
applescript action app bin command csh osx workflow terminal url caction \
diff --git a/Telegram/SourceFiles/core/version.h b/Telegram/SourceFiles/core/version.h
index 9f185d8b4..d7cfbb940 100644
--- a/Telegram/SourceFiles/core/version.h
+++ b/Telegram/SourceFiles/core/version.h
@@ -22,7 +22,7 @@ constexpr auto AppId = "{53F49750-6209-4FBF-9CA8-7A333C87D666}"_cs;
constexpr auto AppNameOld = "AyuGram for Windows"_cs;
constexpr auto AppName = "AyuGram Desktop"_cs;
constexpr auto AppFile = "AyuGram"_cs;
-constexpr auto AppVersion = 5001002;
-constexpr auto AppVersionStr = "5.1.2";
+constexpr auto AppVersion = 5001007;
+constexpr auto AppVersionStr = "5.1.7";
constexpr auto AppBetaVersion = false;
constexpr auto AppAlphaVersion = TDESKTOP_ALPHA_VERSION;
diff --git a/Telegram/SourceFiles/data/data_media_types.cpp b/Telegram/SourceFiles/data/data_media_types.cpp
index feb8ad16d..4594b7fa2 100644
--- a/Telegram/SourceFiles/data/data_media_types.cpp
+++ b/Telegram/SourceFiles/data/data_media_types.cpp
@@ -1219,6 +1219,92 @@ std::unique_ptr MediaFile::createView(
_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(
not_null parent,
UserId userId,
diff --git a/Telegram/SourceFiles/data/data_media_types.h b/Telegram/SourceFiles/data/data_media_types.h
index d9f0773c0..08d1320b9 100644
--- a/Telegram/SourceFiles/data/data_media_types.h
+++ b/Telegram/SourceFiles/data/data_media_types.h
@@ -70,6 +70,8 @@ struct SharedContact final {
};
using VcardItems = base::flat_map;
+ static VcardItems ParseVcard(const QString &);
+
VcardItems vcardItems;
};
diff --git a/Telegram/SourceFiles/data/data_user.cpp b/Telegram/SourceFiles/data/data_user.cpp
index c4e1bbf68..d5247c08e 100644
--- a/Telegram/SourceFiles/data/data_user.cpp
+++ b/Telegram/SourceFiles/data/data_user.cpp
@@ -447,7 +447,7 @@ bool UserData::someRequirePremiumToWrite() const {
}
bool UserData::meRequiresPremiumToWrite() const {
- return (flags() & UserDataFlag::MeRequiresPremiumToWrite);
+ return !isSelf() && (flags() & UserDataFlag::MeRequiresPremiumToWrite);
}
bool UserData::requirePremiumToWriteKnown() const {
diff --git a/Telegram/SourceFiles/dialogs/dialogs.style b/Telegram/SourceFiles/dialogs/dialogs.style
index 5f2ae6217..026136a76 100644
--- a/Telegram/SourceFiles/dialogs/dialogs.style
+++ b/Telegram/SourceFiles/dialogs/dialogs.style
@@ -7,6 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
using "ui/basic.style";
+using "ui/layers/layers.style"; // boxRoundShadow
using "ui/widgets/widgets.style";
DialogRow {
@@ -446,10 +447,25 @@ dialogsLoadMoreLoading: InfiniteRadialAnimation(defaultInfiniteRadialAnimation)
size: size(12px, 12px);
}
-dialogsSearchInHeight: 52px;
-dialogsSearchInPhotoSize: 36px;
-dialogsSearchInPhotoPadding: 10px;
-dialogsSearchInSkip: 7px;
+dialogsSearchInHeight: 38px;
+dialogsSearchInPhotoSize: 26px;
+dialogsSearchInPhotoPadding: 12px;
+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;
dialogsSearchFromPalette: TextPalette(defaultTextPalette) {
linkFg: dialogsNameFg;
@@ -694,17 +710,17 @@ dialogsStoriesTooltipHide: IconButton(defaultIconButton) {
ripple: emptyRippleAnimation;
}
-searchedBarHeight: 32px;
+searchedBarHeight: 28px;
searchedBarFont: normalFont;
-searchedBarPosition: point(17px, 7px);
+searchedBarPosition: point(14px, 5px);
searchedBarLabel: FlatLabel(defaultFlatLabel) {
textFg: searchedBarFg;
- margin: margins(17px, 7px, 17px, 7px);
+ margin: margins(14px, 5px, 14px, 5px);
}
searchedBarLink: LinkButton(defaultLinkButton) {
color: searchedBarFg;
overColor: searchedBarFg;
- padding: margins(17px, 7px, 17px, 7px);
+ padding: margins(14px, 5px, 14px, 5px);
}
dialogsSearchTagSkip: point(8px, 4px);
diff --git a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp
index 3216e512c..b18471d0e 100644
--- a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp
+++ b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp
@@ -9,7 +9,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "dialogs/dialogs_three_state_icon.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_stories_content.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/text/text_utilities.h"
#include "ui/text/text_options.h"
+#include "ui/dynamic_thumbnails.h"
#include "ui/painter.h"
#include "ui/ui_utility.h"
#include "data/data_drafts.h"
@@ -137,9 +138,7 @@ constexpr auto kStartReorderThreshold = 30;
if (hashtag) {
text.append(tr::lng_search_tab_by_hashtag(tr::now));
} else {
- text.append(
- tr::lng_dlg_search_for_messages(tr::now)
- ).append('\n').append(Ui::Text::Link(tr::lng_cancel(tr::now)));
+ text.append(tr::lng_dlg_search_for_messages(tr::now));
}
} else {
text.append(tr::lng_search_tab_no_results(
@@ -214,12 +213,9 @@ InnerWidget::InnerWidget(
, _narrowWidth(st::defaultDialogRow.padding.left()
+ st::defaultDialogRow.photoSize
+ st::defaultDialogRow.padding.left())
-, _cancelSearchFromUser(this, st::dialogsCancelSearchInPeer)
, _childListShown(std::move(childListShown)) {
setAttribute(Qt::WA_OpaquePaintEvent, true);
- _cancelSearchFromUser->hide();
-
style::PaletteChanged(
) | rpl::start_with_next([=] {
_topicJumpCache = nullptr;
@@ -516,8 +512,12 @@ int InnerWidget::pinnedOffset() const {
return dialogsOffset() + shownHeight(fixedOnTopCount());
}
+int InnerWidget::hashtagsOffset() const {
+ return searchInChatOffset() + searchInChatSkip();
+}
+
int InnerWidget::filteredOffset() const {
- return _hashtagResults.size() * st::mentionHeight;
+ return hashtagsOffset() + (_hashtagResults.size() * st::mentionHeight);
}
int InnerWidget::filteredIndex(int y) const {
@@ -543,33 +543,19 @@ int InnerWidget::peerSearchOffset() const {
+ 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 {
- auto result = searchTagsOffset();
- if (_searchTags) {
- result += _searchTags->height();
- }
- return result;
-}
-
-int InnerWidget::searchedOffset() const {
- return searchInChatOffset()
- + searchInChatSkip()
- + st::searchedBarHeight;
+ return (_searchTags ? _searchTags->height() : 0);
}
int InnerWidget::searchInChatSkip() const {
- auto result = 0;
- if (_searchFromShown) {
- result += st::dialogsSearchInHeight;
+ return _searchIn ? _searchIn->height() : 0;
+}
+
+int InnerWidget::searchedOffset() const {
+ auto result = peerSearchOffset();
+ if (!_peerSearchResults.empty()) {
+ result += (_peerSearchResults.size() * st::dialogsRowHeight)
+ + st::searchedBarHeight;
}
return result;
}
@@ -808,10 +794,26 @@ void InnerWidget::paintEvent(QPaintEvent *e) {
p.fillRect(dialogsClip, currentBg());
}
} 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()) {
- auto from = floorclamp(r.y(), st::mentionHeight, 0, _hashtagResults.size());
- auto to = ceilclamp(r.y() + r.height(), st::mentionHeight, 0, _hashtagResults.size());
+ const auto skip = hashtagsOffset();
+ 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);
if (from < _hashtagResults.size()) {
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.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()));
const auto height = filteredHeight(from);
p.translate(0, height);
- top += height;
for (; from < to; ++from) {
const auto selected = isPressed()
? (from == _filteredPressed)
@@ -873,7 +876,6 @@ void InnerWidget::paintEvent(QPaintEvent *e) {
const auto row = _filterResults[from].row;
paintRow(row, selected, !activeEntry.fullId);
p.translate(0, row->height());
- top += row->height();
}
}
@@ -883,13 +885,11 @@ void InnerWidget::paintEvent(QPaintEvent *e) {
p.setPen(st::searchedBarFg);
p.drawTextLeft(st::searchedBarPosition.x(), st::searchedBarPosition.y(), width(), tr::lng_search_global_results(tr::now));
p.translate(0, st::searchedBarHeight);
- top += st::searchedBarHeight;
auto skip = peerSearchOffset();
auto from = floorclamp(r.y() - 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);
- top += from * st::dialogsRowHeight;
if (from < _peerSearchResults.size()) {
const auto activePeer = activeEntry.key.peer();
for (; from < to; ++from) {
@@ -912,34 +912,10 @@ void InnerWidget::paintEvent(QPaintEvent *e) {
.paused = videoPaused,
});
p.translate(0, st::dialogsRowHeight);
- top += 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);
+ if (to < _peerSearchResults.size()) {
+ p.translate(0, (_peerSearchResults.size() - to) * st::dialogsRowHeight);
+ }
}
}
@@ -952,7 +928,6 @@ void InnerWidget::paintEvent(QPaintEvent *e) {
p.setPen(st::searchedBarFg);
p.drawTextLeft(st::searchedBarPosition.x(), st::searchedBarPosition.y(), width(), text);
p.translate(0, st::searchedBarHeight);
- top += st::searchedBarHeight;
}
} else {
const auto text = showUnreadInSearchResults
@@ -966,13 +941,11 @@ void InnerWidget::paintEvent(QPaintEvent *e) {
p.setPen(st::searchedBarFg);
p.drawTextLeft(st::searchedBarPosition.x(), st::searchedBarPosition.y(), width(), text);
p.translate(0, st::searchedBarHeight);
- top += st::searchedBarHeight;
auto skip = searchedOffset();
auto from = floorclamp(r.y() - 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);
- top += from * _st->height;
if (from < _searchResults.size()) {
for (; from < to; ++from) {
const auto &result = _searchResults[from];
@@ -1000,7 +973,6 @@ void InnerWidget::paintEvent(QPaintEvent *e) {
.displayUnreadInfo = showUnreadInSearchResults,
});
p.translate(0, _st->height);
- top += _st->height;
}
}
}
@@ -1211,111 +1183,112 @@ void InnerWidget::paintSearchTags(
const auto position = QPoint(_searchTagsLeft, top);
_searchTags->paint(p, position, context.now, context.paused);
}
-
-void InnerWidget::paintSearchInChat(
- Painter &p,
- const Ui::PaintContext &context) const {
- auto height = searchInChatSkip();
-
- auto top = 0;
- p.setFont(st::searchedBarFont);
- auto fullRect = QRect(0, top, width(), height - top);
- p.fillRect(fullRect, currentBg());
- if (_searchFromShown) {
- p.setPen(st::dialogsTextFg);
- p.setTextPalette(st::dialogsSearchFromPalette);
- paintSearchInPeer(p, _searchFromShown, _searchFromUserUserpic, top, _searchFromUserText);
- p.restoreTextPalette();
- }
-}
-template
-void InnerWidget::paintSearchInFilter(
- Painter &p,
- PaintUserpic paintUserpic,
- int top,
- const style::icon *icon,
- const Ui::Text::String &text) const {
- const auto savedPen = p.pen();
- const auto userpicLeft = st::defaultDialogRow.padding.left();
- const auto userpicTop = top
- + (st::dialogsSearchInHeight - st::dialogsSearchInPhotoSize) / 2;
- paintUserpic(p, userpicLeft, userpicTop, st::dialogsSearchInPhotoSize);
-
- const auto nameleft = st::defaultDialogRow.padding.left()
- + st::dialogsSearchInPhotoSize
- + st::dialogsSearchInPhotoPadding;
- const auto namewidth = width()
- - nameleft
- - st::defaultDialogRow.padding.left()
- - st::defaultDialogRow.padding.right()
- - st::dialogsCancelSearch.width;
- auto rectForName = QRect(
- nameleft,
- top + (st::dialogsSearchInHeight - st::semiboldFont->height) / 2,
- namewidth,
- st::semiboldFont->height);
- if (icon) {
- icon->paint(p, rectForName.topLeft(), width());
- rectForName.setLeft(rectForName.left()
- + icon->width()
- + st::dialogsChatTypeSkip);
- }
- p.setPen(savedPen);
- text.drawLeftElided(
- p,
- rectForName.left(),
- rectForName.top(),
- rectForName.width(),
- width());
-}
-
-void InnerWidget::paintSearchInPeer(
- Painter &p,
- not_null peer,
- Ui::PeerUserpicView &userpic,
- int top,
- const Ui::Text::String &text) const {
- 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);
-}
-
-void InnerWidget::paintSearchInSaved(
- Painter &p,
- int top,
- const Ui::Text::String &text) const {
- 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);
-}
-
-void InnerWidget::paintSearchInReplies(
- Painter &p,
- int top,
- const Ui::Text::String &text) const {
- 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);
-}
-
-void InnerWidget::paintSearchInTopic(
- Painter &p,
- const Ui::PaintContext &context,
- not_null topic,
- Ui::PeerUserpicView &userpic,
- int top,
- const Ui::Text::String &text) const {
- const auto paintUserpic = [&](Painter &p, int x, int y, int size) {
- p.translate(x, y);
- topic->paintUserpic(p, userpic, context);
- p.translate(-x, -y);
- };
- paintSearchInFilter(p, paintUserpic, top, nullptr, text);
-}
+//
+//void InnerWidget::paintSearchInChat(
+// Painter &p,
+// const Ui::PaintContext &context) const {
+// auto height = searchInChatSkip();
+//
+// auto top = 0;
+// p.setFont(st::searchedBarFont);
+// auto fullRect = QRect(0, top, width(), height - top);
+// p.fillRect(fullRect, currentBg());
+// if (_searchFromShown) {
+// p.setPen(st::dialogsTextFg);
+// p.setTextPalette(st::dialogsSearchFromPalette);
+// paintSearchInPeer(p, _searchFromShown, _searchFromUserUserpic, top, _searchFromUserText);
+// p.restoreTextPalette();
+// }
+//}
+//
+//template
+//void InnerWidget::paintSearchInFilter(
+// Painter &p,
+// PaintUserpic paintUserpic,
+// int top,
+// const style::icon *icon,
+// const Ui::Text::String &text) const {
+// const auto savedPen = p.pen();
+// const auto userpicLeft = st::defaultDialogRow.padding.left();
+// const auto userpicTop = top
+// + (st::dialogsSearchInHeight - st::dialogsSearchInPhotoSize) / 2;
+// paintUserpic(p, userpicLeft, userpicTop, st::dialogsSearchInPhotoSize);
+//
+// const auto nameleft = st::defaultDialogRow.padding.left()
+// + st::dialogsSearchInPhotoSize
+// + st::dialogsSearchInPhotoPadding;
+// const auto namewidth = width()
+// - nameleft
+// - st::defaultDialogRow.padding.left()
+// - st::defaultDialogRow.padding.right()
+// - st::dialogsCancelSearch.width;
+// auto rectForName = QRect(
+// nameleft,
+// top + (st::dialogsSearchInHeight - st::semiboldFont->height) / 2,
+// namewidth,
+// st::semiboldFont->height);
+// if (icon) {
+// icon->paint(p, rectForName.topLeft(), width());
+// rectForName.setLeft(rectForName.left()
+// + icon->width()
+// + st::dialogsChatTypeSkip);
+// }
+// p.setPen(savedPen);
+// text.drawLeftElided(
+// p,
+// rectForName.left(),
+// rectForName.top(),
+// rectForName.width(),
+// width());
+//}
+//
+//void InnerWidget::paintSearchInPeer(
+// Painter &p,
+// not_null peer,
+// Ui::PeerUserpicView &userpic,
+// int top,
+// const Ui::Text::String &text) const {
+// 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);
+//}
+//
+//void InnerWidget::paintSearchInSaved(
+// Painter &p,
+// int top,
+// const Ui::Text::String &text) const {
+// 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);
+//}
+//
+//void InnerWidget::paintSearchInReplies(
+// Painter &p,
+// int top,
+// const Ui::Text::String &text) const {
+// 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);
+//}
+//
+//void InnerWidget::paintSearchInTopic(
+// Painter &p,
+// const Ui::PaintContext &context,
+// not_null topic,
+// Ui::PeerUserpicView &userpic,
+// int top,
+// const Ui::Text::String &text) const {
+// const auto paintUserpic = [&](Painter &p, int x, int y, int size) {
+// p.translate(x, y);
+// topic->paintUserpic(p, userpic, context);
+// p.translate(-x, -y);
+// };
+// paintSearchInFilter(p, paintUserpic, top, nullptr, text);
+//}
void InnerWidget::mouseMoveEvent(QMouseEvent *e) {
if (_chatPreviewTouchGlobal || _touchDragStartGlobal) {
@@ -1374,7 +1347,7 @@ void InnerWidget::selectByMouse(QPoint globalPosition) {
const auto tagBase = QPoint(
_searchTagsLeft,
- searchTagsOffset() + st::dialogsSearchTagBottom / 2);
+ st::dialogsSearchTagBottom / 2);
const auto tagPoint = local - tagBase;
const auto inTags = _searchTags
&& QRect(
@@ -1425,7 +1398,7 @@ void InnerWidget::selectByMouse(QPoint globalPosition) {
_hashtagSelected = -1;
_hashtagDeleteSelected = false;
} else {
- auto skip = 0;
+ auto skip = hashtagsOffset();
auto hashtagSelected = (mouseY >= skip) ? ((mouseY - skip) / st::mentionHeight) : -1;
if (hashtagSelected < 0 || hashtagSelected >= _hashtagResults.size()) {
hashtagSelected = -1;
@@ -1558,8 +1531,9 @@ void InnerWidget::mousePressEvent(QMouseEvent *e) {
_dragStart = e->pos();
} else if (base::in_range(_hashtagPressed, 0, _hashtagResults.size()) && !_hashtagDeletePressed) {
auto row = &_hashtagResults[_hashtagPressed]->row;
- row->addRipple(e->pos(), QSize(width(), st::mentionHeight), [this, index = _hashtagPressed] {
- update(0, index * st::mentionHeight, width(), st::mentionHeight);
+ const auto origin = e->pos() - QPoint(0, hashtagsOffset() + _hashtagPressed * 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())) {
const auto &result = _filterResults[_filteredPressed];
@@ -1989,17 +1963,18 @@ void InnerWidget::resizeEvent(QResizeEvent *e) {
_searchTags->resizeToWidth(width() - 2 * _searchTagsLeft);
}
resizeEmpty();
- moveCancelSearchButtons();
+ moveSearchIn();
}
-void InnerWidget::moveCancelSearchButtons() {
- const auto widthForCancelButton = qMax(
+void InnerWidget::moveSearchIn() {
+ if (!_searchIn) {
+ return;
+ }
+ const auto searchInWidth = std::max(
width(),
st::columnMinimalWidthLeft - _narrowWidth);
- const auto left = widthForCancelButton - st::dialogsSearchInSkip - _cancelSearchFromUser->width();
- const auto top = (st::dialogsSearchInHeight - st::dialogsCancelSearchInPeer.height) / 2;
- const auto skip = (_searchTags ? _searchTags->height() : 0);
- _cancelSearchFromUser->moveToLeft(left, skip + top);
+ _searchIn->resizeToWidth(searchInWidth);
+ _searchIn->moveToLeft(0, searchInChatOffset());
}
void InnerWidget::dialogRowReplaced(
@@ -2326,7 +2301,7 @@ void InnerWidget::updateSelectedRow(Key key) {
}
}
} 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) {
if (_filteredSelected < _filterResults.size()) {
const auto &result = _filterResults[_filteredSelected];
@@ -2639,7 +2614,7 @@ void InnerWidget::applySearchState(SearchState state) {
_searchTags->repaintRequests() | rpl::start_with_next([=] {
const auto height = _searchTags->height();
- update(0, searchTagsOffset(), width(), height);
+ update(0, 0, width(), height);
}, _searchTags->lifetime());
_searchTags->menuRequests(
@@ -2656,7 +2631,7 @@ void InnerWidget::applySearchState(SearchState state) {
1
) | rpl::start_with_next([=] {
refresh();
- moveCancelSearchButtons();
+ moveSearchIn();
}, _searchTags->lifetime());
} else {
_searchTags = nullptr;
@@ -2670,17 +2645,12 @@ void InnerWidget::applySearchState(SearchState state) {
if (state.inChat) {
onHashtagFilterUpdate(QStringView());
}
- if (_searchFromShown) {
- _cancelSearchFromUser->show();
- _searchFromUserUserpic = _searchFromShown->createUserpicView();
- } else {
- _cancelSearchFromUser->hide();
- _searchFromUserUserpic = {};
- }
- refreshSearchInChatLabel();
- moveCancelSearchButtons();
-
_searchState = std::move(state);
+ _searchingHashtag = IsHashtagSearchQuery(_searchState.query);
+
+ updateSearchIn();
+ moveSearchIn();
+
auto newFilter = _searchState.query;
const auto mentionsSearch = (newFilter == u"@"_q);
const auto words = mentionsSearch
@@ -2924,12 +2894,20 @@ rpl::producer<> InnerWidget::listBottomReached() const {
return _listBottomReached.events();
}
-rpl::producer<> InnerWidget::cancelSearchFromUserRequests() const {
- return _cancelSearchFromUser->clicks() | rpl::to_empty;
+rpl::producer InnerWidget::changeSearchTabRequests() const {
+ return _changeSearchTabRequests.events();
}
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 InnerWidget::mustScrollTo() const {
@@ -3200,9 +3178,6 @@ void InnerWidget::refreshEmpty() {
} else if (_searchEmptyState != _searchState) {
_searchEmptyState = _searchState;
_searchEmpty = MakeSearchEmpty(this, _searchState);
- _searchEmpty->linkClicks() | rpl::start_with_next([=] {
- _cancelSearch.fire({});
- }, _searchEmpty->lifetime());
if (_controller->session().data().chatsListLoaded()) {
_searchEmpty->animate();
}
@@ -3342,19 +3317,76 @@ auto InnerWidget::searchTagsChanges() const
: rpl::never>();
}
-void InnerWidget::refreshSearchInChatLabel() {
- const auto from = _searchFromShown ? _searchFromShown->name() : u""_q;
- if (!from.isEmpty()) {
- const auto fromUserText = tr::lng_dlg_search_from(
- tr::now,
- lt_user,
- Ui::Text::Semibold(from),
- Ui::Text::WithEntities);
- _searchFromUserText.setMarkedText(
- st::dialogsSearchFromStyle,
- fromUserText,
- Ui::DialogTextOptions());
+void InnerWidget::updateSearchIn() {
+ if (!_searchState.inChat && !_searchingHashtag) {
+ _searchIn = nullptr;
+ return;
+ } else if (!_searchIn) {
+ _searchIn = std::make_unique(this);
+ _searchIn->show();
+ _searchIn->changeFromRequests() | rpl::start_to_stream(
+ _changeSearchFromRequests,
+ _searchIn->lifetime());
+ _searchIn->cancelFromRequests() | rpl::start_to_stream(
+ _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) {
diff --git a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.h b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.h
index 9f069c243..ebb22aa82 100644
--- a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.h
+++ b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.h
@@ -61,6 +61,7 @@ class FakeRow;
class IndexedList;
class SearchTags;
class SearchEmpty;
+class ChatSearchIn;
struct ChosenRow {
Key key;
@@ -156,8 +157,11 @@ public:
void setLoadMoreCallback(Fn callback);
void setLoadMoreFilteredCallback(Fn callback);
[[nodiscard]] rpl::producer<> listBottomReached() const;
- [[nodiscard]] rpl::producer<> cancelSearchFromUserRequests() const;
+ [[nodiscard]] auto changeSearchTabRequests() const
+ -> rpl::producer;
[[nodiscard]] rpl::producer<> cancelSearchRequests() const;
+ [[nodiscard]] rpl::producer<> cancelSearchFromRequests() const;
+ [[nodiscard]] rpl::producer<> changeSearchFromRequests() const;
[[nodiscard]] rpl::producer chosenRow() const;
[[nodiscard]] rpl::producer<> updated() const;
@@ -339,10 +343,10 @@ private:
[[nodiscard]] int filteredIndex(int y) const;
[[nodiscard]] int filteredHeight(int till = -1) const;
[[nodiscard]] int peerSearchOffset() const;
- [[nodiscard]] int searchTagsOffset() const;
[[nodiscard]] int searchInChatOffset() const;
[[nodiscard]] int searchedOffset() const;
[[nodiscard]] int searchInChatSkip() const;
+ [[nodiscard]] int hashtagsOffset() const;
void paintCollapsedRows(
Painter &p,
@@ -358,38 +362,38 @@ private:
void paintSearchTags(
Painter &p,
const Ui::PaintContext &context) const;
- void paintSearchInChat(
- Painter &p,
- const Ui::PaintContext &context) const;
- void paintSearchInPeer(
- Painter &p,
- not_null peer,
- Ui::PeerUserpicView &userpic,
- int top,
- const Ui::Text::String &text) const;
- void paintSearchInSaved(
- Painter &p,
- int top,
- const Ui::Text::String &text) const;
- void paintSearchInReplies(
- Painter &p,
- int top,
- const Ui::Text::String &text) const;
- void paintSearchInTopic(
- Painter &p,
- const Ui::PaintContext &context,
- not_null topic,
- Ui::PeerUserpicView &userpic,
- int top,
- const Ui::Text::String &text) const;
- template
- void paintSearchInFilter(
- Painter &p,
- PaintUserpic paintUserpic,
- int top,
- const style::icon *icon,
- const Ui::Text::String &text) const;
- void refreshSearchInChatLabel();
+ //void paintSearchInChat(
+ // Painter &p,
+ // const Ui::PaintContext &context) const;
+ //void paintSearchInPeer(
+ // Painter &p,
+ // not_null peer,
+ // Ui::PeerUserpicView &userpic,
+ // int top,
+ // const Ui::Text::String &text) const;
+ //void paintSearchInSaved(
+ // Painter &p,
+ // int top,
+ // const Ui::Text::String &text) const;
+ //void paintSearchInReplies(
+ // Painter &p,
+ // int top,
+ // const Ui::Text::String &text) const;
+ //void paintSearchInTopic(
+ // Painter &p,
+ // const Ui::PaintContext &context,
+ // not_null topic,
+ // Ui::PeerUserpicView &userpic,
+ // int top,
+ // const Ui::Text::String &text) const;
+ //template
+ //void paintSearchInFilter(
+ // Painter &p,
+ // PaintUserpic paintUserpic,
+ // int top,
+ // const style::icon *icon,
+ // const Ui::Text::String &text) const;
+ void updateSearchIn();
void repaintSearchResult(int index);
Ui::VideoUserpic *validateVideoUserpic(not_null row);
@@ -415,7 +419,7 @@ private:
void savePinnedOrder();
bool pinnedShiftAnimationCallback(crl::time now);
void handleChatListEntryRefreshes();
- void moveCancelSearchButtons();
+ void moveSearchIn();
void dragPinnedFromTouch();
void saveChatsFilterScrollState(FilterId filterId);
@@ -490,19 +494,22 @@ private:
WidgetState _state = WidgetState::Default;
+ std::unique_ptr _searchIn;
+ rpl::event_stream _changeSearchTabRequests;
+ rpl::event_stream<> _cancelSearchRequests;
+ rpl::event_stream<> _cancelSearchFromRequests;
+ rpl::event_stream<> _changeSearchFromRequests;
object_ptr _loadingAnimation = { nullptr };
object_ptr _searchEmpty = { nullptr };
SearchState _searchEmptyState;
object_ptr _empty = { nullptr };
- object_ptr _cancelSearchFromUser;
- rpl::event_stream<> _cancelSearch;
Ui::DraggingScrollManager _draggingScroll;
SearchState _searchState;
+ bool _searchingHashtag = false;
History *_searchInMigrated = nullptr;
PeerData *_searchFromShown = nullptr;
- mutable Ui::PeerUserpicView _searchFromUserUserpic;
Ui::Text::String _searchFromUserText;
std::unique_ptr _searchTags;
int _searchTagsLeft = 0;
diff --git a/Telegram/SourceFiles/dialogs/dialogs_key.cpp b/Telegram/SourceFiles/dialogs/dialogs_key.cpp
index 7c1be62aa..544c48cca 100644
--- a/Telegram/SourceFiles/dialogs/dialogs_key.cpp
+++ b/Telegram/SourceFiles/dialogs/dialogs_key.cpp
@@ -10,7 +10,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_folder.h"
#include "data/data_forum_topic.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"
namespace Dialogs {
diff --git a/Telegram/SourceFiles/dialogs/dialogs_widget.cpp b/Telegram/SourceFiles/dialogs/dialogs_widget.cpp
index ba82d4d84..704ac14c2 100644
--- a/Telegram/SourceFiles/dialogs/dialogs_widget.cpp
+++ b/Telegram/SourceFiles/dialogs/dialogs_widget.cpp
@@ -9,7 +9,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "base/qt/qt_key_modifiers.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_list.h"
#include "dialogs/ui/dialogs_suggestions.h"
@@ -340,7 +340,23 @@ Widget::Widget(
) | rpl::start_with_next([=] {
searchCursorMoved();
}, 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([=] {
auto copy = _searchState;
copy.fromPeer = nullptr;
@@ -349,10 +365,9 @@ Widget::Widget(
}
applySearchState(std::move(copy));
}, lifetime());
- _inner->cancelSearchRequests(
+ _inner->changeSearchFromRequests(
) | rpl::start_with_next([=] {
- setInnerFocus(true);
- applySearchState({});
+ showSearchFrom();
}, lifetime());
_inner->chosenRow(
) | rpl::start_with_next([=](const ChosenRow &row) {
@@ -410,7 +425,9 @@ Widget::Widget(
}, lifetime());
}
- _cancelSearch->setClickedCallback([this] { cancelSearch(); });
+ _cancelSearch->setClickedCallback([this] {
+ cancelSearch({ .jumpBackToSearchedChat = true });
+ });
_jumpToDate->entity()->setClickedCallback([this] { showCalendar(); });
_chooseFromUser->entity()->setClickedCallback([this] { showSearchFrom(); });
rpl::single(rpl::empty) | rpl::then(
@@ -680,7 +697,7 @@ void Widget::setupMoreChatsBar() {
controller()->activeChatsFilter(
) | rpl::start_with_next([=](FilterId id) {
storiesToggleExplicitExpand(false);
- const auto cancelled = cancelSearch(true);
+ const auto cancelled = cancelSearch({ .forceFullCancel = true });
const auto guard = gsl::finally([&] {
if (cancelled) {
controller()->content()->dialogsCancelled();
@@ -1113,9 +1130,6 @@ void Widget::updateControlsVisibility(bool fast) {
updateJumpToDateVisibility(fast);
updateSearchFromVisibility(fast);
}
- if (_searchTabs) {
- _searchTabs->show();
- }
if (_connecting) {
_connecting->setForceHidden(false);
}
@@ -1170,7 +1184,7 @@ bool Widget::cancelSearchByMouseBack() {
return _searchHasFocus
&& !_searchSuggestionsLocked
&& !_searchState.inChat
- && cancelSearch();
+ && cancelSearch({ .jumpBackToSearchedChat = true });
}
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 repaint) {
- return Core::MarkedTextContext{
- .session = savedSession,
- .customEmojiRepaint = std::move(repaint),
- };
- };
- _searchTabs = std::make_unique(
- 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(
FnMut change,
bool fromRight,
@@ -1405,7 +1323,7 @@ void Widget::changeOpenedFolder(Data::Folder *folder, anim::type animated) {
return;
}
changeOpenedSubsection([&] {
- cancelSearch(true);
+ cancelSearch({ .forceFullCancel = true });
closeChildList(anim::type::instant);
controller()->closeForum();
_openedFolder = folder;
@@ -1459,7 +1377,7 @@ void Widget::changeOpenedForum(Data::Forum *forum, anim::type animated) {
return;
}
changeOpenedSubsection([&] {
- cancelSearch(true);
+ cancelSearch({ .forceFullCancel = true });
closeChildList(anim::type::instant);
_openedForum = forum;
_searchState.tab = forum
@@ -1949,7 +1867,7 @@ void Widget::slideFinished() {
}
void Widget::escape() {
- if (!cancelSearch()) {
+ if (!cancelSearch({ .jumpBackToSearchedChat = true })) {
if (controller()->shownForum().current()) {
controller()->closeForum();
} else if (controller()->openedFolder().current()) {
@@ -2756,9 +2674,8 @@ QString Widget::validateSearchQuery() {
setSearchQuery(fixed.text, fixed.cursorPosition);
}
return fixed.text;
- } else if (_searchingHashtag != IsHashtagSearchQuery(query)) {
- _searchingHashtag = !_searchingHashtag;
- updateSearchTabs();
+ } else {
+ _searchingHashtag = IsHashtagSearchQuery(query);
}
return query;
}
@@ -2804,7 +2721,7 @@ void Widget::showForum(
changeOpenedForum(forum, params.animated);
return;
}
- cancelSearch(true);
+ cancelSearch({ .forceFullCancel = true });
openChildList(forum, params);
}
@@ -2935,6 +2852,9 @@ bool Widget::applySearchState(SearchState state) {
}
hideChildList();
}
+ if (state.inChat && _layout == Layout::Main) {
+ controller()->closeFolder();
+ }
// Adjust state to be consistent.
if (const auto peer = state.inChat.peer()) {
@@ -2956,7 +2876,7 @@ bool Widget::applySearchState(SearchState state) {
state.tab = (_openedForum && !state.inChat)
? ChatSearchTab::ThisPeer
: ChatSearchTab::MyMessages;
- } else if (!state.inChat && !_searchTabs) {
+ } else if (!state.inChat && !_searchingHashtag) {
state.tab = (forum || _openedForum)
? ChatSearchTab::ThisPeer
: ChatSearchTab::MyMessages;
@@ -2990,6 +2910,20 @@ bool Widget::applySearchState(SearchState state) {
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)
? peer->migrateFrom()
: nullptr;
@@ -3003,7 +2937,6 @@ bool Widget::applySearchState(SearchState state) {
}
if (inChatChanged) {
controller()->setSearchInChat(_searchState.inChat);
- updateSearchTabs();
}
if (queryChanged || inChatChanged) {
updateCancelSearch();
@@ -3028,10 +2961,6 @@ bool Widget::applySearchState(SearchState state) {
_peerSearchQuery = QString();
}
- if (_searchState.inChat && _layout == Layout::Main) {
- controller()->closeFolder();
- }
-
if (_searchState.query != currentSearchQuery()) {
setSearchQuery(_searchState.query);
}
@@ -3358,9 +3287,6 @@ void Widget::updateControlsGeometry() {
if (_forumRequestsBar) {
_forumRequestsBar->resizeToWidth(barw);
}
- if (_searchTabs) {
- _searchTabs->resizeToWidth(barw);
- }
_updateScrollGeometryCached = [=] {
const auto moreChatsBarTop = expandedStoriesTop
+ ((!_stories || _stories->isHidden()) ? 0 : _aboveScrollAdded);
@@ -3382,13 +3308,8 @@ void Widget::updateControlsGeometry() {
if (_forumReportBar) {
_forumReportBar->bar().move(0, forumReportTop);
}
- const auto searchTabsTop = forumReportTop
+ const auto scrollTop = forumReportTop
+ (_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 wasScrollHeight = _scroll->height();
_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();
auto updatedState = _searchState;
const auto clearingQuery = !updatedState.query.isEmpty();
+ const auto forceFullCancel = options.forceFullCancel;
auto clearingInChat = (forceFullCancel || !clearingQuery)
&& (updatedState.inChat
|| updatedState.fromPeer
@@ -3692,7 +3614,9 @@ bool Widget::cancelSearch(bool forceFullCancel) {
updatedState.query = QString();
}
if (clearingInChat) {
- if (updatedState.inChat && controller()->adaptive().isOneColumn()) {
+ if (options.jumpBackToSearchedChat
+ && updatedState.inChat
+ && controller()->adaptive().isOneColumn()) {
if (const auto thread = updatedState.inChat.thread()) {
controller()->showThread(thread);
} else {
diff --git a/Telegram/SourceFiles/dialogs/dialogs_widget.h b/Telegram/SourceFiles/dialogs/dialogs_widget.h
index 33d239a48..581bfdc00 100644
--- a/Telegram/SourceFiles/dialogs/dialogs_widget.h
+++ b/Telegram/SourceFiles/dialogs/dialogs_widget.h
@@ -76,7 +76,7 @@ struct ChosenRow;
class InnerWidget;
enum class SearchRequestType;
class Suggestions;
-class ChatSearchTabs;
+class ChatSearchIn;
enum class ChatSearchTab : uchar;
class Widget final : public Window::AbstractSectionWidget {
@@ -131,7 +131,6 @@ public:
bool floatPlayerHandleWheelEvent(QEvent *e) override;
QRect floatPlayerAvailableRect() override;
- bool cancelSearch(bool forceFullCancel = false);
bool cancelSearchByMouseBack();
QVariant inputMethodQuery(Qt::InputMethodQuery query) const override;
@@ -255,13 +254,18 @@ private:
void updateScrollUpPosition();
void updateLockUnlockPosition();
void updateSuggestions(anim::type animated);
- void updateSearchTabs();
void processSearchFocusChange();
[[nodiscard]] bool redirectToSearchPossible() const;
[[nodiscard]] bool redirectKeyToSearch(QKeyEvent *e) const;
[[nodiscard]] bool redirectImeToSearch() const;
+ struct CancelSearchOptions {
+ bool forceFullCancel = false;
+ bool jumpBackToSearchedChat = false;
+ };
+ bool cancelSearch(CancelSearchOptions options);
+
MTP::Sender _api;
bool _dragInScroll = false;
@@ -294,7 +298,6 @@ private:
QPointer _inner;
std::unique_ptr _suggestions;
std::vector> _hidingSuggestions;
- std::unique_ptr _searchTabs;
class BottomButton;
object_ptr _updateTelegram = { nullptr };
object_ptr _loadMoreChats = { nullptr };
diff --git a/Telegram/SourceFiles/dialogs/ui/chat_search_empty.cpp b/Telegram/SourceFiles/dialogs/ui/chat_search_empty.cpp
index 855112f85..7c3c7f393 100644
--- a/Telegram/SourceFiles/dialogs/ui/chat_search_empty.cpp
+++ b/Telegram/SourceFiles/dialogs/ui/chat_search_empty.cpp
@@ -33,12 +33,6 @@ void SearchEmpty::setup(Icon icon, rpl::producer text) {
this,
std::move(text),
st::defaultPeerListAbout);
- label->setClickHandlerFilter([=](const auto &, Qt::MouseButton button) {
- if (button == Qt::LeftButton) {
- _linkClicks.fire({});
- }
- return false;
- });
const auto size = st::recentPeersEmptySize;
const auto animation = [&] {
switch (icon) {
diff --git a/Telegram/SourceFiles/dialogs/ui/chat_search_empty.h b/Telegram/SourceFiles/dialogs/ui/chat_search_empty.h
index 6ddf52676..0ad901ad9 100644
--- a/Telegram/SourceFiles/dialogs/ui/chat_search_empty.h
+++ b/Telegram/SourceFiles/dialogs/ui/chat_search_empty.h
@@ -26,9 +26,6 @@ public:
rpl::producer text);
void setMinimalHeight(int minimalHeight);
- [[nodiscard]] rpl::producer<> linkClicks() const {
- return _linkClicks.events();
- }
void animate();
@@ -36,7 +33,6 @@ private:
void setup(Icon icon, rpl::producer text);
Fn _animate;
- rpl::event_stream<> _linkClicks;
};
diff --git a/Telegram/SourceFiles/dialogs/ui/chat_search_in.cpp b/Telegram/SourceFiles/dialogs/ui/chat_search_in.cpp
new file mode 100644
index 000000000..0524b3a63
--- /dev/null
+++ b/Telegram/SourceFiles/dialogs/ui/chat_search_in.cpp
@@ -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 parentMenu,
+ std::shared_ptr icon,
+ const QString &label,
+ bool chosen);
+
+ bool isEnabled() const override;
+ not_null action() const override;
+
+ void handleKeyPress(not_null 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 _parentMenu;
+ const not_null _dummyAction;
+ const style::Menu &_st;
+ const int _height = 0;
+
+ std::shared_ptr _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 parentMenu,
+ std::shared_ptr icon,
+ const QString &label,
+ bool chosen)
+: ItemBase(parentMenu->menu(), parentMenu->menu()->st())
+, _parentMenu(parentMenu)
+, _dummyAction(CreateChild(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 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 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 tabs,
+ ChatSearchTab active,
+ ChatSearchPeerTabType peerTabType,
+ std::shared_ptr 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 ChatSearchIn::tabChanges() const {
+ return _active.changes();
+}
+
+void ChatSearchIn::showMenu() {
+ _menu = base::make_unique_q(
+ 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(
+ _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,
+ std::shared_ptr 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(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(this);
+ section->shadow->show();
+
+ const auto st = &st::dialogsCancelSearchInPeer;
+ section->cancel = std::make_unique(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
diff --git a/Telegram/SourceFiles/dialogs/ui/chat_search_in.h b/Telegram/SourceFiles/dialogs/ui/chat_search_in.h
new file mode 100644
index 000000000..5e99f2d05
--- /dev/null
+++ b/Telegram/SourceFiles/dialogs/ui/chat_search_in.h
@@ -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 icon;
+ };
+ void apply(
+ std::vector tabs,
+ ChatSearchTab active,
+ ChatSearchPeerTabType peerTabType,
+ std::shared_ptr fromUserpic,
+ QString fromName);
+
+ [[nodiscard]] rpl::producer<> cancelInRequests() const;
+ [[nodiscard]] rpl::producer<> cancelFromRequests() const;
+ [[nodiscard]] rpl::producer<> changeFromRequests() const;
+ [[nodiscard]] rpl::producer tabChanges() const;
+
+private:
+ struct Section {
+ std::unique_ptr outer;
+ std::unique_ptr cancel;
+ std::unique_ptr shadow;
+ std::shared_ptr 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,
+ std::shared_ptr image,
+ TextWithEntities text);
+
+ Section _in;
+ Section _from;
+ rpl::variable _active;
+
+ base::unique_qptr _menu;
+
+ std::vector _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
diff --git a/Telegram/SourceFiles/dialogs/ui/chat_search_tabs.cpp b/Telegram/SourceFiles/dialogs/ui/chat_search_tabs.cpp
deleted file mode 100644
index 24fc21507..000000000
--- a/Telegram/SourceFiles/dialogs/ui/chat_search_tabs.cpp
+++ /dev/null
@@ -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)> markedTextContext)
-: RpWidget(parent)
-, _tabs(std::make_unique(this, st::dialogsSearchTabs))
-, _shadow(std::make_unique(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 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 ChatSearchTabs::tabChanges() const {
- return _active.changes();
-}
-
-void ChatSearchTabs::refillTabs(
- ChatSearchTab active,
- int newWidth) {
- auto labels = std::vector();
- 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
diff --git a/Telegram/SourceFiles/dialogs/ui/chat_search_tabs.h b/Telegram/SourceFiles/dialogs/ui/chat_search_tabs.h
deleted file mode 100644
index 720bf5734..000000000
--- a/Telegram/SourceFiles/dialogs/ui/chat_search_tabs.h
+++ /dev/null
@@ -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)> 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 labels,
- ChatSearchTab active,
- ChatSearchPeerTabType peerTabType);
-
- [[nodiscard]] rpl::producer 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 _tabs;
- const std::unique_ptr _shadow;
- const Fn)> _markedTextContext;
-
- std::vector _list;
- rpl::variable _active;
-
-};
-
-struct FixedHashtagSearchQuery {
- QString text;
- int cursorPosition = 0;
-};
-[[nodiscard]] FixedHashtagSearchQuery FixHashtagSearchQuery(
- const QString &query,
- int cursorPosition);
-
-[[nodiscard]] bool IsHashtagSearchQuery(const QString &query);
-
-} // namespace Dialogs
diff --git a/Telegram/SourceFiles/history/admin_log/history_admin_log_inner.cpp b/Telegram/SourceFiles/history/admin_log/history_admin_log_inner.cpp
index ea7b637c0..2e341a4df 100644
--- a/Telegram/SourceFiles/history/admin_log/history_admin_log_inner.cpp
+++ b/Telegram/SourceFiles/history/admin_log/history_admin_log_inner.cpp
@@ -683,6 +683,10 @@ QString InnerWidget::elementAuthorRank(not_null view) {
return {};
}
+bool InnerWidget::elementHideTopicButton(not_null view) {
+ return false;
+}
+
void InnerWidget::saveState(not_null memento) {
memento->setFilter(std::move(_filter));
memento->setAdmins(std::move(_admins));
diff --git a/Telegram/SourceFiles/history/admin_log/history_admin_log_inner.h b/Telegram/SourceFiles/history/admin_log/history_admin_log_inner.h
index bbe3c0381..5f55e5d40 100644
--- a/Telegram/SourceFiles/history/admin_log/history_admin_log_inner.h
+++ b/Telegram/SourceFiles/history/admin_log/history_admin_log_inner.h
@@ -141,6 +141,8 @@ public:
HistoryView::Element *replacing) override;
QString elementAuthorRank(
not_null view) override;
+ bool elementHideTopicButton(
+ not_null view) override;
~InnerWidget();
diff --git a/Telegram/SourceFiles/history/history_inner_widget.cpp b/Telegram/SourceFiles/history/history_inner_widget.cpp
index 51f1b6deb..b855c2482 100644
--- a/Telegram/SourceFiles/history/history_inner_widget.cpp
+++ b/Telegram/SourceFiles/history/history_inner_widget.cpp
@@ -329,6 +329,10 @@ public:
return {};
}
+ bool elementHideTopicButton(not_null view) override {
+ return false;
+ }
+
not_null delegate() override {
return this;
}
@@ -4366,10 +4370,16 @@ void HistoryInner::deleteAsGroup(FullMsgId itemId) {
const auto group = session().data().groups().find(item);
if (!group) {
return deleteItem(item);
+ } else if (CanCreateModerateMessagesBox(group->items)) {
+ _controller->show(Box(
+ CreateModerateMessagesBox,
+ group->items,
+ nullptr));
+ } else {
+ _controller->show(Box(
+ &session(),
+ session().data().itemsToIds(group->items)));
}
- _controller->show(Box(
- &session(),
- session().data().itemsToIds(group->items)));
}
}
diff --git a/Telegram/SourceFiles/history/history_item.cpp b/Telegram/SourceFiles/history/history_item.cpp
index 36108ce0d..3eb40825f 100644
--- a/Telegram/SourceFiles/history/history_item.cpp
+++ b/Telegram/SourceFiles/history/history_item.cpp
@@ -218,59 +218,13 @@ std::unique_ptr HistoryItem::CreateMedia(
const MTPMessageMedia &media) {
using Result = std::unique_ptr;
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(
item,
media.vuser_id().v,
qs(media.vfirst_name()),
qs(media.vlast_name()),
qs(media.vphone_number()),
- vcardItems);
+ Data::SharedContact::ParseVcard(qs(media.vvcard())));
}, [&](const MTPDmessageMediaGeo &media) -> Result {
return media.vgeo().match([&](const MTPDgeoPoint &point) -> Result {
return std::make_unique(
diff --git a/Telegram/SourceFiles/history/history_item_components.cpp b/Telegram/SourceFiles/history/history_item_components.cpp
index 9abd00f82..52f4c0112 100644
--- a/Telegram/SourceFiles/history/history_item_components.cpp
+++ b/Telegram/SourceFiles/history/history_item_components.cpp
@@ -14,6 +14,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/effects/spoiler_mess.h"
#include "ui/image/image.h"
#include "ui/toast/toast.h"
+#include "ui/text/format_values.h"
#include "ui/text/text_options.h"
#include "ui/text/text_utilities.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 "window/window_session_controller.h"
#include "api/api_bot.h"
-#include "styles/style_widgets.h"
+#include "styles/style_boxes.h"
#include "styles/style_chat.h"
#include "styles/style_dialogs.h" // dialogsMiniReplyStory.
+#include "styles/style_settings.h"
+#include "styles/style_widgets.h"
#include
@@ -701,6 +704,11 @@ ReplyKeyboard::ReplyKeyboard(
const auto context = _item->fullId();
const auto rowCount = int(markup->data.rows.size());
_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) {
const auto &row = markup->data.rows[i];
const auto rowSize = int(row.size());
@@ -708,17 +716,54 @@ ReplyKeyboard::ReplyKeyboard(
newRow.reserve(rowSize);
for (auto j = 0; j != rowSize; ++j) {
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.link = std::make_shared(
owner,
i,
j,
context);
- button.text.setText(
- _st->textStyle(),
- TextUtilities::SingleLine(text),
- kPlainTextOptions);
+ if (!textWithEntities.text.isEmpty()) {
+ button.text.setMarkedText(
+ _st->textStyle(),
+ 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();
newRow.push_back(std::move(button));
}
diff --git a/Telegram/SourceFiles/history/view/history_view_chat_preview.cpp b/Telegram/SourceFiles/history/view/history_view_chat_preview.cpp
index 91540a729..9bf9739f3 100644
--- a/Telegram/SourceFiles/history/view/history_view_chat_preview.cpp
+++ b/Telegram/SourceFiles/history/view/history_view_chat_preview.cpp
@@ -126,6 +126,7 @@ private:
Painter &p,
const Ui::ChatPaintContext &context) override;
QString listElementAuthorRank(not_null view) override;
+ bool listElementHideTopicButton(not_null view) override;
History *listTranslateHistory() override;
void listAddTranslatedItems(
not_null tracker) override;
@@ -279,6 +280,8 @@ void Item::setupTop() {
const auto topic = _thread->asTopic();
auto nameValue = (topic
? Info::Profile::TitleValue(topic)
+ : _thread->peer()->isSelf()
+ ? tr::lng_saved_messages()
: Info::Profile::NameValue(_thread->peer())
) | rpl::start_spawning(_top->lifetime());
const auto name = Ui::CreateChild(
@@ -294,18 +297,24 @@ void Item::setupTop() {
) | rpl::map([](StatusFields &&fields) {
return fields.text;
});
- const auto status = Ui::CreateChild(
- _top.get(),
- (topic
- ? Info::Profile::NameValue(topic->channel())
- : std::move(statusText)),
- st::previewStatus);
- std::move(statusFields) | rpl::start_with_next([=](const StatusFields &fields) {
- status->setTextColorOverride(fields.active
- ? st::windowActiveTextFg->c
- : std::optional());
- }, status->lifetime());
- status->setAttribute(Qt::WA_TransparentForMouseEvents);
+ const auto status = _thread->peer()->isSelf()
+ ? nullptr
+ : Ui::CreateChild(
+ _top.get(),
+ (topic
+ ? Info::Profile::NameValue(topic->channel())
+ : std::move(statusText)),
+ st::previewStatus);
+ if (status) {
+ std::move(
+ statusFields
+ ) | rpl::start_with_next([=](const StatusFields &fields) {
+ status->setTextColorOverride(fields.active
+ ? st::windowActiveTextFg->c
+ : std::optional());
+ }, status->lifetime());
+ status->setAttribute(Qt::WA_TransparentForMouseEvents);
+ }
const auto userpic = topic
? nullptr
: Ui::CreateChild(
@@ -313,6 +322,7 @@ void Item::setupTop() {
_thread->peer(),
st::previewUserpic);
if (userpic) {
+ userpic->showSavedMessagesOnSelf(true);
userpic->setAttribute(Qt::WA_TransparentForMouseEvents);
}
const auto icon = topic
@@ -334,15 +344,23 @@ void Item::setupTop() {
name->resizeToNaturalWidth(width
- st.namePosition.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());
_top->geometryValue() | rpl::start_with_next([=](QRect geometry) {
const auto &st = st::previewTop;
- status->resizeToWidth(geometry.width()
- - st.statusPosition.x()
- - st.photoPosition.x());
- status->move(st.statusPosition);
+ if (status) {
+ status->resizeToWidth(geometry.width()
+ - st.statusPosition.x()
+ - st.photoPosition.x());
+ status->move(st.statusPosition);
+ }
shadow->setGeometry(
geometry.x(),
geometry.y() + geometry.height(),
@@ -559,8 +577,12 @@ MessagesBarData Item::listMessagesBar(
? _replies->computeInboxReadTillFull()
: MsgId();
const auto migrated = _replies ? nullptr : _history->migrateFrom();
- const auto migratedTill = migrated ? migrated->inboxReadTillId() : 0;
- const auto historyTill = _replies ? 0 : _history->inboxReadTillId();
+ const auto migratedTill = (migrated && migrated->unreadCount() > 0)
+ ? migrated->inboxReadTillId()
+ : 0;
+ const auto historyTill = (_replies || !_history->unreadCount())
+ ? 0
+ : _history->inboxReadTillId();
if (!_replies && !migratedTill && !historyTill) {
return {};
}
@@ -673,6 +695,10 @@ QString Item::listElementAuthorRank(not_null view) {
return {};
}
+bool Item::listElementHideTopicButton(not_null view) {
+ return _thread->asTopic() != nullptr;
+}
+
History *Item::listTranslateHistory() {
return nullptr;
}
diff --git a/Telegram/SourceFiles/history/view/history_view_element.cpp b/Telegram/SourceFiles/history/view/history_view_element.cpp
index f58b631f3..e54a572b3 100644
--- a/Telegram/SourceFiles/history/view/history_view_element.cpp
+++ b/Telegram/SourceFiles/history/view/history_view_element.cpp
@@ -204,10 +204,16 @@ void DefaultElementDelegate::elementStartEffect(
}
QString DefaultElementDelegate::elementAuthorRank(
- not_null view) {
+ not_null view) {
return {};
}
+bool DefaultElementDelegate::elementHideTopicButton(
+ not_null view) {
+ return true;
+}
+
+
SimpleElementDelegate::SimpleElementDelegate(
not_null controller,
Fn update)
diff --git a/Telegram/SourceFiles/history/view/history_view_element.h b/Telegram/SourceFiles/history/view/history_view_element.h
index 0c01e9767..d4139e8ec 100644
--- a/Telegram/SourceFiles/history/view/history_view_element.h
+++ b/Telegram/SourceFiles/history/view/history_view_element.h
@@ -117,6 +117,7 @@ public:
not_null view,
Element *replacing) = 0;
virtual QString elementAuthorRank(not_null view) = 0;
+ virtual bool elementHideTopicButton(not_null view) = 0;
virtual ~ElementDelegate() {
}
@@ -170,6 +171,7 @@ public:
not_null view,
Element *replacing) override;
QString elementAuthorRank(not_null view) override;
+ bool elementHideTopicButton(not_null view) override;
};
diff --git a/Telegram/SourceFiles/history/view/history_view_list_widget.cpp b/Telegram/SourceFiles/history/view/history_view_list_widget.cpp
index e5f8746f3..8408aaccd 100644
--- a/Telegram/SourceFiles/history/view/history_view_list_widget.cpp
+++ b/Telegram/SourceFiles/history/view/history_view_list_widget.cpp
@@ -1875,6 +1875,11 @@ QString ListWidget::elementAuthorRank(not_null view) {
return _delegate->listElementAuthorRank(view);
}
+bool ListWidget::elementHideTopicButton(not_null view) {
+ return _delegate->listElementHideTopicButton(view);
+}
+
+
void ListWidget::saveState(not_null memento) {
memento->setAroundPosition(_aroundPosition);
const auto state = countScrollState();
diff --git a/Telegram/SourceFiles/history/view/history_view_list_widget.h b/Telegram/SourceFiles/history/view/history_view_list_widget.h
index 5cfc57c08..d0507474f 100644
--- a/Telegram/SourceFiles/history/view/history_view_list_widget.h
+++ b/Telegram/SourceFiles/history/view/history_view_list_widget.h
@@ -155,6 +155,7 @@ public:
Painter &p,
const Ui::ChatPaintContext &context) = 0;
virtual QString listElementAuthorRank(not_null view) = 0;
+ virtual bool listElementHideTopicButton(not_null view) = 0;
virtual History *listTranslateHistory() = 0;
virtual void listAddTranslatedItems(
not_null tracker) = 0;
@@ -368,7 +369,7 @@ public:
[[nodiscard]] auto replyToMessageRequested() const
-> rpl::producer;
void replyToMessageRequestNotify(
- FullReplyTo to,
+ FullReplyTo to,
bool forceAnotherChat = false);
[[nodiscard]] rpl::producer readMessageRequested() const;
[[nodiscard]] rpl::producer showMessageRequested() const;
@@ -425,6 +426,7 @@ public:
not_null view,
Element *replacing) override;
QString elementAuthorRank(not_null view) override;
+ bool elementHideTopicButton(not_null view) override;
void setEmptyInfoWidget(base::unique_qptr &&w);
void overrideIsChatWide(bool isWide);
diff --git a/Telegram/SourceFiles/history/view/history_view_message.cpp b/Telegram/SourceFiles/history/view/history_view_message.cpp
index facc0a193..dd50ca58b 100644
--- a/Telegram/SourceFiles/history/view/history_view_message.cpp
+++ b/Telegram/SourceFiles/history/view/history_view_message.cpp
@@ -1065,8 +1065,7 @@ QSize Message::performCountOptimalSize() {
void Message::refreshTopicButton() {
const auto item = data();
if (isAttachedToPrevious()
- || (context() != Context::History
- && context() != Context::ChatPreview)) {
+ || delegate()->elementHideTopicButton(this)) {
_topicButton = nullptr;
} else if (const auto topic = item->topic()) {
if (!_topicButton) {
diff --git a/Telegram/SourceFiles/history/view/history_view_pinned_section.cpp b/Telegram/SourceFiles/history/view/history_view_pinned_section.cpp
index 38b71c3ec..7cd506b10 100644
--- a/Telegram/SourceFiles/history/view/history_view_pinned_section.cpp
+++ b/Telegram/SourceFiles/history/view/history_view_pinned_section.cpp
@@ -687,6 +687,11 @@ QString PinnedWidget::listElementAuthorRank(not_null view) {
return {};
}
+bool PinnedWidget::listElementHideTopicButton(
+ not_null view) {
+ return true;
+}
+
History *PinnedWidget::listTranslateHistory() {
return _history;
}
diff --git a/Telegram/SourceFiles/history/view/history_view_pinned_section.h b/Telegram/SourceFiles/history/view/history_view_pinned_section.h
index 2c54e0684..75956e403 100644
--- a/Telegram/SourceFiles/history/view/history_view_pinned_section.h
+++ b/Telegram/SourceFiles/history/view/history_view_pinned_section.h
@@ -132,6 +132,7 @@ public:
Painter &p,
const Ui::ChatPaintContext &context) override;
QString listElementAuthorRank(not_null view) override;
+ bool listElementHideTopicButton(not_null view) override;
History *listTranslateHistory() override;
void listAddTranslatedItems(
not_null tracker) override;
diff --git a/Telegram/SourceFiles/history/view/history_view_replies_section.cpp b/Telegram/SourceFiles/history/view/history_view_replies_section.cpp
index 4f208d51d..02a588ac9 100644
--- a/Telegram/SourceFiles/history/view/history_view_replies_section.cpp
+++ b/Telegram/SourceFiles/history/view/history_view_replies_section.cpp
@@ -228,11 +228,7 @@ RepliesWidget::RepliesWidget(
listShowPremiumToast(emoji);
},
.mode = ComposeControls::Mode::Normal,
- .sendMenuDetails = [=] {
- using Type = SendMenu::Type;
- const auto type = _topic ? Type::Scheduled : Type::SilentOnly;
- return SendMenu::Details{ .type = type };
- },
+ .sendMenuDetails = [=] { return sendMenuDetails(); },
.regularWindow = controller,
.stickerOrEmojiChosen = controller->stickerOrEmojiChosen(),
.scheduledToggleValue = _topic
@@ -963,7 +959,7 @@ bool RepliesWidget::confirmSendingFiles(
_composeControls->getTextWithAppliedMarkdown(),
_history->peer,
Api::SendType::Normal,
- SendMenu::Details{ SendMenu::Type::SilentOnly }); // #TODO replies schedule
+ sendMenuDetails());
box->setConfirmedCallback(crl::guard(this, [=](
Ui::PreparedList &&list,
@@ -1096,7 +1092,6 @@ void RepliesWidget::checkReplyReturns() {
void RepliesWidget::uploadFile(
const QByteArray &fileContent,
SendMediaType type) {
- // #TODO replies schedule
session().api().sendFile(fileContent, type, prepareSendAction({}));
}
@@ -1146,7 +1141,7 @@ bool RepliesWidget::showSendingFilesError(
}
Api::SendAction RepliesWidget::prepareSendAction(
- Api::SendOptions options) const {
+ Api::SendOptions options) const {
auto result = Api::SendAction(_history, options);
result.replyTo = replyTo();
result.options.sendAs = _composeControls->sendAsPeer();
@@ -1158,11 +1153,6 @@ void RepliesWidget::send() {
return;
}
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) {
@@ -1346,13 +1336,6 @@ void RepliesWidget::refreshJoinGroupButton() {
void RepliesWidget::sendExistingDocument(
not_null document) {
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(
@@ -1382,13 +1365,6 @@ bool RepliesWidget::sendExistingDocument(
void RepliesWidget::sendExistingPhoto(not_null 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(
@@ -1459,13 +1435,9 @@ void RepliesWidget::sendInlineResult(
}
SendMenu::Details RepliesWidget::sendMenuDetails() const {
- // #TODO replies schedule
- const auto type = _history->peer->isSelf()
- ? SendMenu::Type::Reminder
- : HistoryView::CanScheduleUntilOnline(_history->peer)
- ? SendMenu::Type::ScheduledToUser
- : SendMenu::Type::Scheduled;
- return { .type = type, .effectAllowed = _history->peer->isUser() };
+ using Type = SendMenu::Type;
+ const auto type = _topic ? Type::Scheduled : Type::SilentOnly;
+ return SendMenu::Details{ .type = type };
}
FullReplyTo RepliesWidget::replyTo() const {
@@ -2650,6 +2622,11 @@ QString RepliesWidget::listElementAuthorRank(not_null view) {
: QString();
}
+bool RepliesWidget::listElementHideTopicButton(
+ not_null view) {
+ return true;
+}
+
History *RepliesWidget::listTranslateHistory() {
return _history;
}
diff --git a/Telegram/SourceFiles/history/view/history_view_replies_section.h b/Telegram/SourceFiles/history/view/history_view_replies_section.h
index 37c8540fd..c8bacc2c7 100644
--- a/Telegram/SourceFiles/history/view/history_view_replies_section.h
+++ b/Telegram/SourceFiles/history/view/history_view_replies_section.h
@@ -175,6 +175,7 @@ public:
Painter &p,
const Ui::ChatPaintContext &context) override;
QString listElementAuthorRank(not_null view) override;
+ bool listElementHideTopicButton(not_null view) override;
History *listTranslateHistory() override;
void listAddTranslatedItems(
not_null tracker) override;
diff --git a/Telegram/SourceFiles/history/view/history_view_scheduled_section.cpp b/Telegram/SourceFiles/history/view/history_view_scheduled_section.cpp
index 124db7535..1633ced75 100644
--- a/Telegram/SourceFiles/history/view/history_view_scheduled_section.cpp
+++ b/Telegram/SourceFiles/history/view/history_view_scheduled_section.cpp
@@ -1458,6 +1458,11 @@ QString ScheduledWidget::listElementAuthorRank(
return {};
}
+bool ScheduledWidget::listElementHideTopicButton(
+ not_null view) {
+ return true;
+}
+
History *ScheduledWidget::listTranslateHistory() {
return nullptr;
}
diff --git a/Telegram/SourceFiles/history/view/history_view_scheduled_section.h b/Telegram/SourceFiles/history/view/history_view_scheduled_section.h
index b2b0efa41..ba4813713 100644
--- a/Telegram/SourceFiles/history/view/history_view_scheduled_section.h
+++ b/Telegram/SourceFiles/history/view/history_view_scheduled_section.h
@@ -159,6 +159,7 @@ public:
Painter &p,
const Ui::ChatPaintContext &context) override;
QString listElementAuthorRank(not_null view) override;
+ bool listElementHideTopicButton(not_null view) override;
History *listTranslateHistory() override;
void listAddTranslatedItems(
not_null tracker) override;
diff --git a/Telegram/SourceFiles/history/view/history_view_sublist_section.cpp b/Telegram/SourceFiles/history/view/history_view_sublist_section.cpp
index 470643bdc..be275bb9a 100644
--- a/Telegram/SourceFiles/history/view/history_view_sublist_section.cpp
+++ b/Telegram/SourceFiles/history/view/history_view_sublist_section.cpp
@@ -744,6 +744,11 @@ QString SublistWidget::listElementAuthorRank(not_null view) {
return {};
}
+bool SublistWidget::listElementHideTopicButton(
+ not_null view) {
+ return true;
+}
+
History *SublistWidget::listTranslateHistory() {
return _history;
}
diff --git a/Telegram/SourceFiles/history/view/history_view_sublist_section.h b/Telegram/SourceFiles/history/view/history_view_sublist_section.h
index eff71d50c..6379c9845 100644
--- a/Telegram/SourceFiles/history/view/history_view_sublist_section.h
+++ b/Telegram/SourceFiles/history/view/history_view_sublist_section.h
@@ -136,6 +136,7 @@ public:
Painter &p,
const Ui::ChatPaintContext &context) override;
QString listElementAuthorRank(not_null view) override;
+ bool listElementHideTopicButton(not_null view) override;
History *listTranslateHistory() override;
void listAddTranslatedItems(
not_null tracker) override;
diff --git a/Telegram/SourceFiles/history/view/media/history_view_contact.cpp b/Telegram/SourceFiles/history/view/media/history_view_contact.cpp
index d0fbfe730..dc8995903 100644
--- a/Telegram/SourceFiles/history/view/media/history_view_contact.cpp
+++ b/Telegram/SourceFiles/history/view/media/history_view_contact.cpp
@@ -189,7 +189,15 @@ ClickHandlerPtr AddContactClickHandler(not_null 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);
}
+ const auto vcardBoxFactory = _vcardBoxFactory;
_buttons.clear();
if (_contact) {
const auto message = tr::lng_contact_send_message(tr::now).toUpper();
@@ -276,20 +285,20 @@ QSize Contact::countOptimalSize() {
});
}
_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();
_buttons.push_back({
view,
st::semiboldFont->width(view),
AddContactClickHandler(_parent->data()),
});
+ }
+ if (vcardBoxFactory) {
_mainButton.link = std::make_shared([=](
const ClickContext &context) {
const auto my = context.other.value();
if (const auto controller = my.sessionWindow.get()) {
- controller->uiShow()->show(Box([=](not_null box) {
- vcardBoxFactory(box);
- }));
+ controller->uiShow()->show(Box(vcardBoxFactory));
}
});
}
diff --git a/Telegram/SourceFiles/history/view/media/history_view_extended_preview.cpp b/Telegram/SourceFiles/history/view/media/history_view_extended_preview.cpp
index 582e95265..9c1a73a64 100644
--- a/Telegram/SourceFiles/history/view/media/history_view_extended_preview.cpp
+++ b/Telegram/SourceFiles/history/view/media/history_view_extended_preview.cpp
@@ -113,6 +113,10 @@ void ExtendedPreview::unloadHeavyPart() {
_spoiler.animation = nullptr;
}
+bool ExtendedPreview::enforceBubbleWidth() const {
+ return true;
+}
+
QSize ExtendedPreview::countOptimalSize() {
const auto &preview = _invoice->extendedPreview;
const auto dimensions = preview.dimensions;
diff --git a/Telegram/SourceFiles/history/view/media/history_view_extended_preview.h b/Telegram/SourceFiles/history/view/media/history_view_extended_preview.h
index 0e645456e..da2cd5d14 100644
--- a/Telegram/SourceFiles/history/view/media/history_view_extended_preview.h
+++ b/Telegram/SourceFiles/history/view/media/history_view_extended_preview.h
@@ -54,6 +54,7 @@ public:
bool hasHeavyPart() const override;
void unloadHeavyPart() override;
+ bool enforceBubbleWidth() const override;
private:
int minWidthForButton() const;
diff --git a/Telegram/SourceFiles/history/view/media/history_view_gif.cpp b/Telegram/SourceFiles/history/view/media/history_view_gif.cpp
index 422f36656..b7ec25fae 100644
--- a/Telegram/SourceFiles/history/view/media/history_view_gif.cpp
+++ b/Telegram/SourceFiles/history/view/media/history_view_gif.cpp
@@ -383,6 +383,10 @@ bool Gif::autoplayEnabled() const {
_data);
}
+bool Gif::hideMessageText() const {
+ return _data->isVideoMessage();
+}
+
void Gif::draw(Painter &p, const PaintContext &context) const {
if (width() < st::msgPadding.left() + st::msgPadding.right() + 1) return;
diff --git a/Telegram/SourceFiles/history/view/media/history_view_gif.h b/Telegram/SourceFiles/history/view/media/history_view_gif.h
index 840b12587..f0460bf0d 100644
--- a/Telegram/SourceFiles/history/view/media/history_view_gif.h
+++ b/Telegram/SourceFiles/history/view/media/history_view_gif.h
@@ -54,9 +54,7 @@ public:
bool spoiler);
~Gif();
- bool hideMessageText() const override {
- return false;
- }
+ bool hideMessageText() const override;
void draw(Painter &p, const PaintContext &context) const override;
TextState textState(QPoint point, StateRequest request) const override;
diff --git a/Telegram/SourceFiles/history/view/media/history_view_photo.cpp b/Telegram/SourceFiles/history/view/media/history_view_photo.cpp
index 686004e73..64d87bb64 100644
--- a/Telegram/SourceFiles/history/view/media/history_view_photo.cpp
+++ b/Telegram/SourceFiles/history/view/media/history_view_photo.cpp
@@ -166,6 +166,10 @@ void Photo::unloadHeavyPart() {
togglePollingStory(false);
}
+bool Photo::enforceBubbleWidth() const {
+ return true;
+}
+
void Photo::togglePollingStory(bool enabled) const {
const auto pollingStory = (enabled ? 1 : 0);
if (!_storyId || _pollingStory == pollingStory) {
@@ -197,6 +201,9 @@ QSize Photo::countOptimalSize() {
auto maxWidth = qMax(maxActualWidth, scaled.height());
auto minHeight = qMax(scaled.height(), st::minPhotoSize);
if (_parent->hasBubble()) {
+ const auto captionMaxWidth = _parent->textualMaxWidth();
+ const auto maxWithCaption = qMin(st::msgMaxWidth, captionMaxWidth);
+ maxWidth = qMin(qMax(maxWidth, maxWithCaption), st::msgMaxWidth);
minHeight = adjustHeightForLessCrop(
dimensions,
{ maxWidth, minHeight });
diff --git a/Telegram/SourceFiles/history/view/media/history_view_photo.h b/Telegram/SourceFiles/history/view/media/history_view_photo.h
index 8b50f52ac..44fbed00c 100644
--- a/Telegram/SourceFiles/history/view/media/history_view_photo.h
+++ b/Telegram/SourceFiles/history/view/media/history_view_photo.h
@@ -88,6 +88,7 @@ public:
bool hasHeavyPart() const override;
void unloadHeavyPart() override;
+ bool enforceBubbleWidth() const override;
protected:
float64 dataProgress() const override;
diff --git a/Telegram/SourceFiles/history/view/reactions/history_view_reactions_selector.cpp b/Telegram/SourceFiles/history/view/reactions/history_view_reactions_selector.cpp
index fe01819aa..24f206096 100644
--- a/Telegram/SourceFiles/history/view/reactions/history_view_reactions_selector.cpp
+++ b/Telegram/SourceFiles/history/view/reactions/history_view_reactions_selector.cpp
@@ -1113,7 +1113,13 @@ void Selector::createList() {
_list->searchQueries(
) | rpl::start_with_next([=](std::vector &&query) {
_stickers->applySearchQuery(std::move(query));
- updateVisibleTopBottom();
+ }, _stickers->lifetime());
+
+ rpl::combine(
+ _list->heightValue(),
+ _stickers->heightValue()
+ ) | rpl::start_with_next([=] {
+ InvokeQueued(lists, updateVisibleTopBottom);
}, _stickers->lifetime());
rpl::combine(
diff --git a/Telegram/SourceFiles/info/profile/info_profile_actions.cpp b/Telegram/SourceFiles/info/profile/info_profile_actions.cpp
index 3ceaeca49..4c9f3b529 100644
--- a/Telegram/SourceFiles/info/profile/info_profile_actions.cpp
+++ b/Telegram/SourceFiles/info/profile/info_profile_actions.cpp
@@ -1479,7 +1479,11 @@ object_ptr DetailsFiller::setupPersonalChannel(
auto &lifetime = preview->lifetime();
using namespace Dialogs::Ui;
const auto previewView = lifetime.make_state();
+ const auto previewUpdate = [=] { preview->update(); };
preview->resize(0, st::infoLabeled.style.font->height);
+ if (!previewView->dependsOn(item)) {
+ previewView->prepare(item, nullptr, previewUpdate, {});
+ }
preview->paintRequest(
) | rpl::start_with_next([=, fullId = item->fullId()](
const QRect &rect) {
@@ -1508,11 +1512,7 @@ object_ptr DetailsFiller::setupPersonalChannel(
preview->rect(),
tr::lng_contacts_loading(tr::now),
style::al_left);
- previewView->prepare(
- item,
- nullptr,
- [=] { preview->update(); },
- {});
+ previewView->prepare(item, nullptr, previewUpdate, {});
preview->update();
}
}, preview->lifetime());
diff --git a/Telegram/SourceFiles/info/similar_channels/info_similar_channels_widget.cpp b/Telegram/SourceFiles/info/similar_channels/info_similar_channels_widget.cpp
index cfc911aaf..ae77ec2e2 100644
--- a/Telegram/SourceFiles/info/similar_channels/info_similar_channels_widget.cpp
+++ b/Telegram/SourceFiles/info/similar_channels/info_similar_channels_widget.cpp
@@ -85,7 +85,10 @@ std::unique_ptr ListController::createRow(
if (const auto channel = peer->asChannel()) {
if (const auto count = channel->membersCount(); count > 1) {
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;
diff --git a/Telegram/SourceFiles/info/statistics/info_statistics_list_controllers.cpp b/Telegram/SourceFiles/info/statistics/info_statistics_list_controllers.cpp
index 2dd25c571..f319f873b 100644
--- a/Telegram/SourceFiles/info/statistics/info_statistics_list_controllers.cpp
+++ b/Telegram/SourceFiles/info/statistics/info_statistics_list_controllers.cpp
@@ -861,10 +861,6 @@ public:
void rowClicked(not_null row) override;
void loadMoreRows() override;
- base::unique_qptr rowContextMenu(
- QWidget *parent,
- not_null row) override;
-
[[nodiscard]] bool skipRequest() const;
void requestNext();
@@ -957,29 +953,6 @@ void CreditsController::rowClicked(not_null row) {
}
}
-base::unique_qptr CreditsController::rowContextMenu(
- QWidget *parent,
- not_null row) {
- const auto entry = static_cast(row.get())->entry();
- if (!entry.bareId) {
- return nullptr;
- }
- auto menu = base::make_unique_q(
- 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 CreditsController::allLoadedValue() const {
return _allLoaded.value();
}
diff --git a/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp b/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp
index 6ee4937c6..cde54caf0 100644
--- a/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp
+++ b/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp
@@ -1895,9 +1895,18 @@ void OverlayWidget::contentSizeChanged() {
}
void OverlayWidget::recountSkipTop() {
- const auto bottom = (!_streamed || !_streamed->controls)
- ? height()
- : (_streamed->controls->y() - st::mediaviewCaptionPadding.bottom());
+ const auto controllerBottomNoFullScreenVideo = _groupThumbs
+ ? _groupThumbsTop
+ : 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);
_skipTop = _minUsedTop + std::min(
std::max(
@@ -1963,7 +1972,7 @@ void OverlayWidget::resizeContentByScreenSize() {
_h = _height;
}
_x = (width() - _w) / 2;
- _y = _skipTop + (_availableHeight - _h) / 2;
+ _y = _skipTop + (useh - _h) / 2;
_geometryAnimation.stop();
}
@@ -4031,7 +4040,7 @@ void OverlayWidget::refreshClipControllerGeometry() {
st::mediaviewControllerSize.height());
_streamed->controls->move(
(width() - controllerWidth) / 2,
- (controllerBottom
+ (controllerBottom // Duplicated in recountSkipTop().
- _streamed->controls->height()
- st::mediaviewCaptionPadding.bottom()));
Ui::SendPendingMoveResizeEvents(_streamed->controls.get());
diff --git a/Telegram/SourceFiles/settings/business/settings_shortcut_messages.cpp b/Telegram/SourceFiles/settings/business/settings_shortcut_messages.cpp
index 96c6cb841..9724bdec2 100644
--- a/Telegram/SourceFiles/settings/business/settings_shortcut_messages.cpp
+++ b/Telegram/SourceFiles/settings/business/settings_shortcut_messages.cpp
@@ -161,6 +161,7 @@ private:
Painter &p,
const Ui::ChatPaintContext &context) override;
QString listElementAuthorRank(not_null view) override;
+ bool listElementHideTopicButton(not_null view) override;
History *listTranslateHistory() override;
void listAddTranslatedItems(
not_null tracker) override;
@@ -1046,6 +1047,11 @@ QString ShortcutMessages::listElementAuthorRank(
return {};
}
+bool ShortcutMessages::listElementHideTopicButton(
+ not_null view) {
+ return true;
+}
+
History *ShortcutMessages::listTranslateHistory() {
return nullptr;
}
diff --git a/Telegram/SourceFiles/settings/settings_credits_graphics.cpp b/Telegram/SourceFiles/settings/settings_credits_graphics.cpp
index b855121a4..91a8c1625 100644
--- a/Telegram/SourceFiles/settings/settings_credits_graphics.cpp
+++ b/Telegram/SourceFiles/settings/settings_credits_graphics.cpp
@@ -165,7 +165,7 @@ QImage GenerateStars(int height, int count) {
void FillCreditOptions(
not_null controller,
not_null container,
- int minCredits,
+ int minimumCredits,
Fn paid) {
const auto options = container->add(
object_ptr>(
@@ -191,6 +191,10 @@ void FillCreditOptions(
- st.iconLeft
- singleStarWidth;
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++) {
const auto &option = options[i];
if (option.credits < minCredits) {
@@ -269,10 +273,11 @@ void FillCreditOptions(
{
auto text = tr::lng_credits_summary_options_about(
lt_link,
- tr::lng_credits_summary_options_about_link(
- ) | rpl::map([](const QString &t) {
- using namespace Ui::Text;
- return Link(t, u"https://telegram.org/tos"_q);
+ rpl::combine(
+ tr::lng_credits_summary_options_about_link(),
+ tr::lng_credits_summary_options_about_url()
+ ) | rpl::map([](const QString &text, const QString &url) {
+ return Ui::Text::Link(text, url);
}),
Ui::Text::RichLangValue);
Ui::AddSkip(content);
diff --git a/Telegram/SourceFiles/ui/chat/chat.style b/Telegram/SourceFiles/ui/chat/chat.style
index c8be6153b..68670a742 100644
--- a/Telegram/SourceFiles/ui/chat/chat.style
+++ b/Telegram/SourceFiles/ui/chat/chat.style
@@ -1111,6 +1111,7 @@ previewMarkRead: FlatButton(historyComposeButton) {
previewName: FlatLabel(defaultFlatLabel) {
style: semiboldTextStyle;
textFg: windowFg;
+ maxHeight: 30px;
}
previewStatus: FlatLabel(defaultFlatLabel) {
textFg: windowSubTextFg;
diff --git a/Telegram/SourceFiles/ui/dynamic_thumbnails.cpp b/Telegram/SourceFiles/ui/dynamic_thumbnails.cpp
index b9ea1ad0e..fdbd4b69f 100644
--- a/Telegram/SourceFiles/ui/dynamic_thumbnails.cpp
+++ b/Telegram/SourceFiles/ui/dynamic_thumbnails.cpp
@@ -14,6 +14,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_peer.h"
#include "data/data_photo.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 "main/main_session.h"
#include "ui/empty_userpic.h"
@@ -28,6 +30,8 @@ class PeerUserpic final : public DynamicImage {
public:
PeerUserpic(not_null peer, bool forceRound);
+ std::shared_ptr clone() override;
+
QImage image(int size) override;
void subscribeToUpdates(Fn callback) override;
@@ -58,7 +62,6 @@ private:
class StoryThumbnail : public DynamicImage {
public:
explicit StoryThumbnail(FullStoryId id);
- virtual ~StoryThumbnail() = default;
QImage image(int size) override;
void subscribeToUpdates(Fn callback) override;
@@ -68,6 +71,9 @@ protected:
Image *image = nullptr;
bool blurred = false;
};
+
+ [[nodiscard]] FullStoryId id() const;
+
[[nodiscard]] virtual Main::Session &session() = 0;
[[nodiscard]] virtual Thumb loaded(FullStoryId id) = 0;
virtual void clear() = 0;
@@ -85,6 +91,8 @@ class PhotoThumbnail final : public StoryThumbnail {
public:
PhotoThumbnail(not_null photo, FullStoryId id);
+ std::shared_ptr clone() override;
+
private:
Main::Session &session() override;
Thumb loaded(FullStoryId id) override;
@@ -99,6 +107,8 @@ class VideoThumbnail final : public StoryThumbnail {
public:
VideoThumbnail(not_null video, FullStoryId id);
+ std::shared_ptr clone() override;
+
private:
Main::Session &session() override;
Thumb loaded(FullStoryId id) override;
@@ -111,6 +121,8 @@ private:
class EmptyThumbnail final : public DynamicImage {
public:
+ std::shared_ptr clone() override;
+
QImage image(int size) override;
void subscribeToUpdates(Fn callback) override;
@@ -121,6 +133,8 @@ private:
class SavedMessagesUserpic final : public DynamicImage {
public:
+ std::shared_ptr clone() override;
+
QImage image(int size) override;
void subscribeToUpdates(Fn callback) override;
@@ -132,6 +146,8 @@ private:
class RepliesUserpic final : public DynamicImage {
public:
+ std::shared_ptr clone() override;
+
QImage image(int size) override;
void subscribeToUpdates(Fn callback) override;
@@ -141,11 +157,48 @@ private:
};
+class IconThumbnail final : public DynamicImage {
+public:
+ explicit IconThumbnail(const style::icon &icon);
+
+ std::shared_ptr clone() override;
+
+ QImage image(int size) override;
+ void subscribeToUpdates(Fn callback) override;
+
+private:
+ const style::icon &_icon;
+ int _paletteVersion = 0;
+ QImage _frame;
+
+};
+
+class EmojiThumbnail final : public DynamicImage {
+public:
+ EmojiThumbnail(not_null owner, const QString &data);
+
+ std::shared_ptr clone() override;
+
+ QImage image(int size) override;
+ void subscribeToUpdates(Fn callback) override;
+
+private:
+ const not_null _owner;
+ const QString _data;
+ std::unique_ptr _emoji;
+ QImage _frame;
+
+};
+
PeerUserpic::PeerUserpic(not_null peer, bool forceRound)
: _peer(peer)
, _forceRound(forceRound) {
}
+std::shared_ptr PeerUserpic::clone() {
+ return std::make_shared(_peer, _forceRound);
+}
+
QImage PeerUserpic::image(int size) {
Expects(_subscribed != nullptr);
@@ -280,11 +333,19 @@ void StoryThumbnail::subscribeToUpdates(Fn callback) {
}
}
+FullStoryId StoryThumbnail::id() const {
+ return _id;
+}
+
PhotoThumbnail::PhotoThumbnail(not_null photo, FullStoryId id)
: StoryThumbnail(id)
, _photo(photo) {
}
+std::shared_ptr PhotoThumbnail::clone() {
+ return std::make_shared(_photo, id());
+}
+
Main::Session &PhotoThumbnail::session() {
return _photo->session();
}
@@ -311,6 +372,10 @@ VideoThumbnail::VideoThumbnail(
, _video(video) {
}
+std::shared_ptr VideoThumbnail::clone() {
+ return std::make_shared(_video, id());
+}
+
Main::Session &VideoThumbnail::session() {
return _video->session();
}
@@ -330,6 +395,10 @@ void VideoThumbnail::clear() {
_media = nullptr;
}
+std::shared_ptr EmptyThumbnail::clone() {
+ return std::make_shared();
+}
+
QImage EmptyThumbnail::image(int size) {
const auto ratio = style::DevicePixelRatio();
if (_cached.width() != size * ratio) {
@@ -345,6 +414,10 @@ QImage EmptyThumbnail::image(int size) {
void EmptyThumbnail::subscribeToUpdates(Fn callback) {
}
+std::shared_ptr SavedMessagesUserpic::clone() {
+ return std::make_shared();
+}
+
QImage SavedMessagesUserpic::image(int size) {
const auto good = (_frame.width() == size * _frame.devicePixelRatio());
const auto paletteVersion = style::PaletteVersion();
@@ -367,6 +440,13 @@ QImage SavedMessagesUserpic::image(int size) {
}
void SavedMessagesUserpic::subscribeToUpdates(Fn callback) {
+ if (!callback) {
+ _frame = {};
+ }
+}
+
+std::shared_ptr RepliesUserpic::clone() {
+ return std::make_shared();
}
QImage RepliesUserpic::image(int size) {
@@ -391,6 +471,90 @@ QImage RepliesUserpic::image(int size) {
}
void RepliesUserpic::subscribeToUpdates(Fn callback) {
+ if (!callback) {
+ _frame = {};
+ }
+}
+
+IconThumbnail::IconThumbnail(const style::icon &icon) : _icon(icon) {
+}
+
+std::shared_ptr IconThumbnail::clone() {
+ return std::make_shared(_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 callback) {
+ if (!callback) {
+ _frame = {};
+ }
+}
+
+EmojiThumbnail::EmojiThumbnail(
+ not_null owner,
+ const QString &data)
+: _owner(owner)
+, _data(data) {
+}
+
+void EmojiThumbnail::subscribeToUpdates(Fn callback) {
+ if (!callback) {
+ _emoji = nullptr;
+ return;
+ }
+ _emoji = _owner->customEmojiManager().create(
+ _data,
+ std::move(callback),
+ Data::CustomEmojiSizeTag::Large);
+}
+
+std::shared_ptr EmojiThumbnail::clone() {
+ return std::make_shared(_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
@@ -422,4 +586,14 @@ std::shared_ptr MakeStoryThumbnail(
});
}
+std::shared_ptr MakeIconThumbnail(const style::icon &icon) {
+ return std::make_shared(icon);
+}
+
+std::shared_ptr MakeEmojiThumbnail(
+ not_null owner,
+ const QString &data) {
+ return std::make_shared(owner, data);
+}
+
} // namespace Ui
diff --git a/Telegram/SourceFiles/ui/dynamic_thumbnails.h b/Telegram/SourceFiles/ui/dynamic_thumbnails.h
index 4b4e0e553..df36ae984 100644
--- a/Telegram/SourceFiles/ui/dynamic_thumbnails.h
+++ b/Telegram/SourceFiles/ui/dynamic_thumbnails.h
@@ -11,6 +11,7 @@ class PeerData;
namespace Data {
class Story;
+class Session;
} // namespace Data
namespace Ui {
@@ -24,5 +25,10 @@ class DynamicImage;
[[nodiscard]] std::shared_ptr MakeRepliesThumbnail();
[[nodiscard]] std::shared_ptr MakeStoryThumbnail(
not_null