diff --git a/Telegram/SourceFiles/boxes/star_gift_box.cpp b/Telegram/SourceFiles/boxes/star_gift_box.cpp index 092fa9c2d2..1b3397dbf2 100644 --- a/Telegram/SourceFiles/boxes/star_gift_box.cpp +++ b/Telegram/SourceFiles/boxes/star_gift_box.cpp @@ -17,6 +17,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "base/unixtime.h" #include "boxes/filters/edit_filter_chats_list.h" #include "boxes/peers/edit_peer_color_box.h" +#include "boxes/peers/prepare_short_info_box.h" #include "boxes/gift_premium_box.h" #include "boxes/peer_list_controllers.h" #include "boxes/premium_preview_box.h" @@ -70,6 +71,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/effects/path_shift_gradient.h" #include "ui/effects/premium_graphics.h" #include "ui/effects/premium_stars_colored.h" +#include "ui/effects/ripple_animation.h" #include "ui/layers/generic_box.h" #include "ui/new_badges.h" #include "ui/painter.h" @@ -92,6 +94,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "styles/style_chat.h" #include "styles/style_chat_helpers.h" #include "styles/style_credits.h" +#include "styles/style_info.h" #include "styles/style_layers.h" #include "styles/style_menu_icons.h" #include "styles/style_premium.h" @@ -118,6 +121,13 @@ constexpr auto kGiftsPreloadTimeout = 3 * crl::time(1000); using namespace HistoryView; using namespace Info::PeerGifts; +enum class PickType { + Activate, + SendMessage, + OpenProfile, +}; +using PickCallback = Fn, PickType)>; + struct PremiumGiftsDescriptor { std::vector list; std::shared_ptr api; @@ -142,6 +152,80 @@ struct GiftDetails { bool byStars = false; }; +class PeerRow final : public PeerListRow { +public: + using PeerListRow::PeerListRow; + + QSize rightActionSize() const override; + QMargins rightActionMargins() const override; + void rightActionPaint( + Painter &p, + int x, + int y, + int outerWidth, + bool selected, + bool actionSelected) override; + + void rightActionAddRipple( + QPoint point, + Fn updateCallback) override; + void rightActionStopLastRipple() override; + +private: + std::unique_ptr _actionRipple; + +}; + +QSize PeerRow::rightActionSize() const { + return QSize( + st::inviteLinkThreeDotsIcon.width(), + st::inviteLinkThreeDotsIcon.height()); +} + +QMargins PeerRow::rightActionMargins() const { + return QMargins( + 0, + (st::inviteLinkList.item.height - rightActionSize().height()) / 2, + st::inviteLinkThreeDotsSkip, + 0); +} + +void PeerRow::rightActionPaint( + Painter &p, + int x, + int y, + int outerWidth, + bool selected, + bool actionSelected) { + if (_actionRipple) { + _actionRipple->paint(p, x, y, outerWidth); + if (_actionRipple->empty()) { + _actionRipple.reset(); + } + } + (actionSelected + ? st::inviteLinkThreeDotsIconOver + : st::inviteLinkThreeDotsIcon).paint(p, x, y, outerWidth); +} + +void PeerRow::rightActionAddRipple(QPoint point, Fn updateCallback) { + if (!_actionRipple) { + auto mask = Ui::RippleAnimation::EllipseMask( + Size(st::inviteLinkThreeDotsIcon.height())); + _actionRipple = std::make_unique( + st::defaultRippleAnimation, + std::move(mask), + std::move(updateCallback)); + } + _actionRipple->add(point); +} + +void PeerRow::rightActionStopLastRipple() { + if (_actionRipple) { + _actionRipple->lastStop(); + } +} + class PreviewDelegate final : public DefaultElementDelegate { public: PreviewDelegate( @@ -2258,6 +2342,24 @@ void GiftBox( } } +[[nodiscard]] base::unique_qptr CreateRowContextMenu( + QWidget *parent, + not_null peer, + PickCallback pick) { + auto result = base::make_unique_q( + parent, + st::popupMenuWithIcons); + result->addAction( + tr::lng_context_send_message(tr::now), + [=] { pick(peer, PickType::SendMessage); }, + &st::menuIconChatBubble); + result->addAction( + tr::lng_context_view_profile(tr::now), + [=] { pick(peer, PickType::OpenProfile); }, + &st::menuIconProfile); + return result; +} + struct CustomList { object_ptr content = { nullptr }; Fn overrideKey; @@ -2267,16 +2369,19 @@ struct CustomList { class Controller final : public ContactsBoxController { public: - Controller( - not_null session, - Fn)> choose); + Controller(not_null session, PickCallback pick); void noSearchSubmit(); bool overrideKeyboardNavigation( int direction, int fromIndex, - int toIndex) override; + int toIndex) override final; + + void rowRightActionClicked(not_null row) override final; + base::unique_qptr rowContextMenu( + QWidget *parent, + not_null row) override final; private: std::unique_ptr createRow( @@ -2285,11 +2390,13 @@ private: void prepareViewHook() override; void rowClicked(not_null row) override; - const Fn)> _choose; + const PickCallback _pick; const std::vector _contactBirthdays; CustomList _selfOption; CustomList _birthdayOptions; + base::unique_qptr _menu; + bool _skipUpDirectionSelect = false; }; @@ -2297,16 +2404,16 @@ private: [[nodiscard]] CustomList MakeCustomList( not_null session, Fn)> fill, - Fn)> activate, + PickCallback pick, rpl::producer below) { - class SelfController final : public PeerListController { + class CustomController final : public PeerListController { public: - SelfController( + CustomController( not_null session, Fn)> fill, - Fn)> activate) + PickCallback pick) : _session(session) - , _activate(std::move(activate)) + , _pick(std::move(pick)) , _fill(std::move(fill)) { } @@ -2318,17 +2425,37 @@ private: void loadMoreRows() override { } void rowClicked(not_null row) override { - _activate(row->peer()); + _pick(row->peer(), PickType::Activate); } Main::Session &session() const override { return *_session; } + void rowRightActionClicked(not_null row) override { + delegate()->peerListShowRowMenu(row, true); + } + + base::unique_qptr rowContextMenu( + QWidget *parent, + not_null row) override { + auto result = CreateRowContextMenu(parent, row->peer(), _pick); + + if (result) { + base::take(_menu); + + _menu = base::unique_qptr(result.get()); + } + + return result; + } + private: const not_null _session; - Fn)> _activate; + PickCallback _pick; Fn)> _fill; + base::unique_qptr _menu; + }; auto result = object_ptr((QWidget*)nullptr); @@ -2340,10 +2467,10 @@ private: PeerListContentDelegateSimple >(); const auto controller - = container->lifetime().make_state( + = container->lifetime().make_state( session, fill, - activate); + pick); controller->setStyleOverrides(&st::peerListSingleRow); const auto content = container->add(object_ptr( @@ -2392,18 +2519,18 @@ private: .overrideKey = overrideKey, .activate = [=] { if (content->hasSelection()) { - activate(content->rowAt(content->selectedIndex())->peer()); + pick( + content->rowAt(content->selectedIndex())->peer(), + PickType::Activate); } }, .hasSelection = hasSelection, }; } -Controller::Controller( - not_null session, - Fn)> choose) +Controller::Controller(not_null session, PickCallback pick) : ContactsBoxController(session) -, _choose(std::move(choose)) +, _pick(std::move(pick)) , _contactBirthdays( session->data().knownContactBirthdays().value_or(std::vector{})) , _selfOption( @@ -2415,7 +2542,7 @@ Controller::Controller( controller->delegate()->peerListAppendRow(std::move(row)); controller->delegate()->peerListRefreshRows(); }, - [=](not_null peer) { _choose(peer); }, + _pick, _contactBirthdays.empty() ? tr::lng_contacts_header() : tr::lng_gift_subtitle_birthdays())) @@ -2458,7 +2585,7 @@ Controller::Controller( }); for (const auto user : usersWithBirthdays) { - auto row = std::make_unique(user); + auto row = std::make_unique(user); if (auto s = status(user->birthday()); !s.isEmpty()) { row->setCustomStatus(std::move(s)); } @@ -2467,13 +2594,34 @@ Controller::Controller( controller->delegate()->peerListRefreshRows(); }, - [=](not_null peer) { _choose(peer); }, + _pick, _contactBirthdays.empty() ? rpl::producer(nullptr) : tr::lng_contacts_header())) { setStyleOverrides(&st::peerListSmallSkips); } +void Controller::rowRightActionClicked(not_null row) { + delegate()->peerListShowRowMenu(row, true); +} + +base::unique_qptr Controller::rowContextMenu( + QWidget *parent, + not_null row) { + auto result = CreateRowContextMenu(parent, row->peer(), _pick); + + if (result) { + // First clear _menu value, so that we don't check row positions yet. + base::take(_menu); + + // Here unique_qptr is used like a shared pointer, where + // not the last destroyed pointer destroys the object, but the first. + _menu = base::unique_qptr(result.get()); + } + + return result; +} + void Controller::noSearchSubmit() { if (const auto onstack = _selfOption.activate) { onstack(); @@ -2543,7 +2691,7 @@ std::unique_ptr Controller::createRow( || user->isInaccessible()) { return nullptr; } - return ContactsBoxController::createRow(user); + return std::make_unique(user); } void Controller::prepareViewHook() { @@ -2554,7 +2702,7 @@ void Controller::prepareViewHook() { } void Controller::rowClicked(not_null row) { - _choose(row->peer()); + _pick(row->peer(), PickType::Activate); } } // namespace @@ -2568,8 +2716,15 @@ void ChooseStarGiftRecipient( lifetime->destroy(); auto controller = std::make_unique( session, - [=](not_null peer) { - ShowStarGiftBox(window, peer); + [=](not_null peer, PickType type) { + if (type == PickType::Activate) { + ShowStarGiftBox(window, peer); + } else if (type == PickType::SendMessage) { + using Way = Window::SectionShow::Way; + window->showPeerHistory(peer, Way::Forward); + } else if (type == PickType::OpenProfile) { + window->show(PrepareShortInfoBox(peer, window)); + } }); const auto controllerRaw = controller.get(); auto initBox = [=](not_null box) {