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 story); +[[nodiscard]] std::shared_ptr MakeIconThumbnail( + const style::icon &icon); +[[nodiscard]] std::shared_ptr MakeEmojiThumbnail( + not_null owner, + const QString &data); } // namespace Ui diff --git a/Telegram/SourceFiles/ui/menu_icons.style b/Telegram/SourceFiles/ui/menu_icons.style index 8f2c0f861..8d08ff7de 100644 --- a/Telegram/SourceFiles/ui/menu_icons.style +++ b/Telegram/SourceFiles/ui/menu_icons.style @@ -136,6 +136,7 @@ menuIconGroupCreate: icon {{ "menu/groups_create", menuIconColor }}; menuIconSigned: icon {{ "menu/signed", menuIconColor }}; menuIconAntispam: icon {{ "menu/antispam", menuIconColor }}; menuIconChatDiscuss: icon {{ "menu/chat_discuss", menuIconColor }}; +menuIconChats: icon {{ "menu/chats", menuIconColor }}; menuIconBotCommands: icon {{ "menu/bot_commands", menuIconColor }}; menuIconPremium: icon {{ "menu/premium", menuIconColor }}; menuIconShop: icon {{ "menu/shop", menuIconColor }}; diff --git a/Telegram/build/docker/centos_env/Dockerfile b/Telegram/build/docker/centos_env/Dockerfile index f41aa85a8..02c3f9f3e 100644 --- a/Telegram/build/docker/centos_env/Dockerfile +++ b/Telegram/build/docker/centos_env/Dockerfile @@ -172,7 +172,7 @@ FROM builder AS libvpx RUN git init libvpx \ && cd libvpx \ && git remote add origin {{ GIT }}/webmproject/libvpx.git \ - && git fetch --depth=1 origin 51057f4ba894e13f9bba278905bacf6aaaecd992 \ + && git fetch --depth=1 origin 12f3a2ac603e8f10742105519e0cd03c3b8f71dd \ && git reset --hard FETCH_HEAD \ && CFLAGS="$CFLAGS -fno-lto" CXXFLAGS="$CXXFLAGS -fno-lto" ./configure \ --disable-examples \ diff --git a/Telegram/build/prepare/prepare.py b/Telegram/build/prepare/prepare.py index 8384492e6..a705fa843 100644 --- a/Telegram/build/prepare/prepare.py +++ b/Telegram/build/prepare/prepare.py @@ -107,7 +107,7 @@ elif (win64): elif (mac): environment.update({ 'SPECIAL_TARGET': 'mac', - 'MAKE_THREADS_CNT': '-j8', + 'MAKE_THREADS_CNT': '-j' + str(os.cpu_count()), 'MACOSX_DEPLOYMENT_TARGET': '10.13', 'UNGUARDED': '-Werror=unguarded-availability-new', 'MIN_VER': '-mmacosx-version-min=10.13', @@ -435,7 +435,7 @@ if customRunCommand: stage('patches', """ git clone https://github.com/desktop-app/patches.git cd patches - git checkout 25f76cf4d5 + git checkout c237d12bcd """) stage('msys64', """ @@ -511,9 +511,9 @@ stage('lzma', """ win: git clone https://github.com/desktop-app/lzma.git cd lzma\\C\\Util\\LzmaLib - msbuild LzmaLib.sln /property:Configuration=Debug /property:Platform="$X8664" + msbuild -m LzmaLib.sln /property:Configuration=Debug /property:Platform="$X8664" release: - msbuild LzmaLib.sln /property:Configuration=Release /property:Platform="$X8664" + msbuild -m LzmaLib.sln /property:Configuration=Release /property:Platform="$X8664" """) stage('xz', """ @@ -540,9 +540,9 @@ win: -DCMAKE_C_FLAGS_DEBUG="/MTd /Zi /Ob0 /Od /RTC1" ^ -DCMAKE_C_FLAGS_RELEASE="/MT /O2 /Ob2 /DNDEBUG" ^ -DCMAKE_C_FLAGS="/DZLIB_WINAPI" - cmake --build . --config Debug + cmake --build . --config Debug --parallel release: - cmake --build . --config Release + cmake --build . --config Release --parallel mac: CFLAGS="$MIN_VER $UNGUARDED" LDFLAGS="$MIN_VER" ./configure \\ --static \\ @@ -560,9 +560,9 @@ win: -A %WIN32X64% ^ -DWITH_JPEG8=ON ^ -DPNG_SUPPORTED=OFF - cmake --build . --config Debug + cmake --build . --config Debug --parallel release: - cmake --build . --config Release + cmake --build . --config Release --parallel mac: CFLAGS="-arch arm64" cmake -B build.arm64 . \\ -D CMAKE_SYSTEM_NAME=Darwin \\ @@ -643,8 +643,8 @@ win: -DCMAKE_MSVC_RUNTIME_LIBRARY="MultiThreaded$<$:Debug>" ^ -DCMAKE_C_FLAGS_DEBUG="/MTd /Zi /Ob0 /Od /RTC1" ^ -DCMAKE_C_FLAGS_RELEASE="/MT /O2 /Ob2 /DNDEBUG" - cmake --build out --config Debug - cmake --build out --config Release + cmake --build out --config Debug --parallel + cmake --build out --config Release --parallel cmake --install out --config Release mac: CFLAGS="$UNGUARDED" CPPFLAGS="$UNGUARDED" cmake -B build . \\ @@ -663,9 +663,9 @@ stage('rnnoise', """ cd out win: cmake -A %WIN32X64% .. - cmake --build . --config Debug + cmake --build . --config Debug --parallel release: - cmake --build . --config Release + cmake --build . --config Release --parallel !win: mkdir Debug cd Debug @@ -780,10 +780,10 @@ win: -DBUILD_SHARED_LIBS=OFF ^ -DAVIF_ENABLE_WERROR=OFF ^ -DAVIF_CODEC_DAV1D=ON - cmake --build . --config Debug + cmake --build . --config Debug --parallel cmake --install . --config Debug release: - cmake --build . --config Release + cmake --build . --config Release --parallel cmake --install . --config Release mac: cmake . \\ @@ -816,10 +816,10 @@ win: -DBUILD_SHARED_LIBS=OFF ^ -DENABLE_DECODER=OFF ^ -DENABLE_ENCODER=OFF - cmake --build . --config Debug + cmake --build . --config Debug --parallel cmake --install . --config Debug release: - cmake --build . --config Release + cmake --build . --config Release --parallel cmake --install . --config Release mac: cmake . \\ @@ -898,10 +898,10 @@ win: -DWITH_RAV1E=OFF ^ -DWITH_RAV1E_PLUGIN=OFF ^ -DWITH_EXAMPLES=OFF - cmake --build . --config Debug + cmake --build . --config Debug --parallel cmake --install . --config Debug release: - cmake --build . --config Release + cmake --build . --config Release --parallel cmake --install . --config Release mac: cmake . \\ @@ -964,10 +964,10 @@ win: -DCMAKE_C_FLAGS_RELEASE="/MT /O2 /Ob2 /DNDEBUG" ^ -DCMAKE_CXX_FLAGS_RELEASE="/MT /O2 /Ob2 /DNDEBUG" ^ %cmake_defines% - cmake --build . --config Debug + cmake --build . --config Debug --parallel cmake --install . --config Debug release: - cmake --build . --config Release + cmake --build . --config Release --parallel cmake --install . --config Release mac: cmake . \\ @@ -983,7 +983,7 @@ stage('libvpx', """ git clone https://github.com/webmproject/libvpx.git depends:patches/libvpx/*.patch cd libvpx - git checkout 51057f4ba8 + git checkout v1.14.1 win: for /r %%i in (..\\patches\\libvpx\\*) do git apply %%i @@ -1322,7 +1322,7 @@ release: ninja -C out/Release%FolderPostfix% common crash_generation_client exception_handler cd tools\\windows\\dump_syms gyp dump_syms.gyp --format=msvs - msbuild dump_syms.vcxproj /property:Configuration=Release /property:Platform="x64" + msbuild -m dump_syms.vcxproj /property:Configuration=Release /property:Platform="x64" win: deactivate mac: diff --git a/Telegram/build/version b/Telegram/build/version index 326855a80..6f0f009fb 100644 --- a/Telegram/build/version +++ b/Telegram/build/version @@ -1,7 +1,7 @@ -AppVersion 5001002 +AppVersion 5001007 AppVersionStrMajor 5.1 -AppVersionStrSmall 5.1.2 -AppVersionStr 5.1.2 +AppVersionStrSmall 5.1.7 +AppVersionStr 5.1.7 BetaChannel 0 AlphaVersion 0 -AppVersionOriginal 5.1.2 +AppVersionOriginal 5.1.7 diff --git a/Telegram/cmake/td_ui.cmake b/Telegram/cmake/td_ui.cmake index b3c6d1ed0..d08ee51ed 100644 --- a/Telegram/cmake/td_ui.cmake +++ b/Telegram/cmake/td_ui.cmake @@ -92,8 +92,8 @@ PRIVATE dialogs/dialogs_three_state_icon.h dialogs/ui/chat_search_empty.cpp dialogs/ui/chat_search_empty.h - dialogs/ui/chat_search_tabs.cpp - dialogs/ui/chat_search_tabs.h + dialogs/ui/chat_search_in.cpp + dialogs/ui/chat_search_in.h dialogs/ui/dialogs_stories_list.cpp dialogs/ui/dialogs_stories_list.h dialogs/ui/top_peers_strip.cpp diff --git a/Telegram/lib_webview b/Telegram/lib_webview index 115530c7a..659b91812 160000 --- a/Telegram/lib_webview +++ b/Telegram/lib_webview @@ -1 +1 @@ -Subproject commit 115530c7aa8694f461d04f95d6a3561a18562e7e +Subproject commit 659b9181240aae16c05ef8ab7e6c4dd527afcf8a diff --git a/changelog.txt b/changelog.txt index bb3526416..5eb0f2e7e 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,3 +1,31 @@ +5.1.7 (14.06.24) + +- Fix recently searched hashtags in chats search. +- Fix formatting shortcuts on macOS. +- Fix non-Telegram-Stars-invoice bot buttons with star emoji. + +5.1.6 (13.06.24) + +- Fix search in archived chats in single-column layout. +- Improve chat previews for topics, Saved Messages and groups. +- Fix formatting shortcuts on Linux. +- Fix options for Telegram Stars buying in case of large amounts. + +5.1.5 (07.06.24) + +- Return WebView on Windows. + +5.1.4 (06.06.24) + +- Improve design of search in chat. +- Show vCard information for shared contacts. +- Allow scheduling media in topic groups. +- Several minor bugfixes. + +5.1.3 (04.06.24) + +- Rebuild version for macOS to fix the phrases. + 5.1.2 (03.06.24) - Several bugs fixed including a couple of crashes.