Allow changing the recipients.

This commit is contained in:
John Preston 2024-12-02 20:20:34 +04:00
parent 82cec83d87
commit b8bf3f6520
14 changed files with 386 additions and 56 deletions

View file

@ -1404,10 +1404,6 @@ void Session::forgetPassportCredentials() {
_passportCredentials = nullptr;
}
QString Session::nameSortKey(const QString &name) const {
return TextUtilities::RemoveAccents(name).toLower();
}
void Session::setupMigrationViewer() {
session().changes().peerUpdates(
PeerUpdate::Flag::Migration

View file

@ -115,8 +115,6 @@ public:
return *_session;
}
[[nodiscard]] QString nameSortKey(const QString &name) const;
[[nodiscard]] Groups &groups() {
return _groups;
}

View file

@ -98,7 +98,7 @@ History::History(not_null<Data::Session*> owner, PeerId peerId)
: Thread(owner, Type::History)
, peer(owner->peer(peerId))
, _delegateMixin(HistoryInner::DelegateMixin())
, _chatListNameSortKey(owner->nameSortKey(peer->name()))
, _chatListNameSortKey(TextUtilities::NameSortKey(peer->name()))
, _sendActionPainter(this) {
Thread::setMuted(owner->notifySettings().isMuted(peer));
@ -2314,7 +2314,7 @@ const QString &History::chatListNameSortKey() const {
}
void History::refreshChatListNameSortKey() {
_chatListNameSortKey = owner().nameSortKey(peer->name());
_chatListNameSortKey = TextUtilities::NameSortKey(peer->name());
}
const base::flat_set<QString> &History::chatListNameWords() const {

View file

@ -12,6 +12,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "boxes/send_credits_box.h" // Ui::CreditsEmoji.
#include "chat_helpers/stickers_lottie.h"
#include "core/ui_integration.h"
#include "data/stickers/data_custom_emoji.h"
#include "data/data_channel.h"
#include "data/data_document.h"
#include "data/data_session.h"
#include "history/view/media/history_view_sticker.h"
@ -21,9 +23,13 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "settings/settings_common.h"
#include "ui/boxes/confirm_box.h"
#include "ui/controls/userpic_button.h"
#include "ui/controls/who_reacted_context_action.h"
#include "ui/dynamic_image.h"
#include "ui/dynamic_thumbnails.h"
#include "ui/layers/generic_box.h"
#include "ui/widgets/buttons.h"
#include "ui/widgets/labels.h"
#include "ui/widgets/popup_menu.h"
#include "ui/wrap/padding_wrap.h"
#include "ui/wrap/table_layout.h"
#include "ui/wrap/vertical_layout.h"
@ -31,6 +37,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/painter.h"
#include "ui/vertical_list.h"
#include "styles/style_chat.h"
#include "styles/style_chat_helpers.h"
#include "styles/style_dialogs.h"
#include "styles/style_giveaway.h"
#include "styles/style_layers.h"
@ -156,6 +163,58 @@ void ConnectStarRef(
return result;
}
void ChooseRecipient(
not_null<Ui::RpWidget*> button,
const std::vector<not_null<PeerData*>> &list,
not_null<PeerData*> now,
Fn<void(not_null<PeerData*>)> done) {
const auto menu = Ui::CreateChild<Ui::PopupMenu>(
button,
st::starrefPopupMenu);
struct Entry {
not_null<Ui::WhoReactedEntryAction*> action;
std::shared_ptr<Ui::DynamicImage> userpic;
};
auto actions = std::make_shared<std::vector<Entry>>();
actions->reserve(list.size());
for (const auto &peer : list) {
auto view = peer->createUserpicView();
auto action = base::make_unique_q<Ui::WhoReactedEntryAction>(
menu->menu(),
Data::ReactedMenuFactory(&list.front()->session()),
menu->menu()->st(),
Ui::WhoReactedEntryData());
const auto index = int(actions->size());
actions->push_back({ action.get(), Ui::MakeUserpicThumbnail(peer) });
const auto updateUserpic = [=] {
const auto size = st::defaultWhoRead.photoSize;
actions->at(index).action->setData({
.text = peer->name(),
.date = (peer->isSelf()
? tr::lng_group_call_join_as_personal(tr::now)
: peer->isUser()
? tr::lng_status_bot(tr::now)
: peer->isBroadcast()
? tr::lng_channel_status(tr::now)
: tr::lng_group_status(tr::now)),
.type = (peer == now
? Ui::WhoReactedType::RefRecipientNow
: Ui::WhoReactedType::RefRecipient),
.userpic = actions->at(index).userpic->image(size),
.callback = [=] { done(peer); },
});
};
actions->back().userpic->subscribeToUpdates(updateUserpic);
menu->addAction(std::move(action));
updateUserpic();
}
menu->popup(button->mapToGlobal(QPoint(button->width() / 2, 0)));
}
} // namespace
QString FormatCommission(ushort commission) {
@ -453,9 +512,10 @@ object_ptr<Ui::BoxContent> StarRefLinkBox(
});
}
[[nodiscard]] object_ptr<Ui::BoxContent> JoinStarRefBox(
object_ptr<Ui::BoxContent> JoinStarRefBox(
ConnectedBot row,
not_null<PeerData*> peer,
not_null<PeerData*> initialRecipient,
std::vector<not_null<PeerData*>> recipients,
Fn<void(ConnectedBotState)> done) {
Expects(row.bot->isUser());
@ -464,6 +524,13 @@ object_ptr<Ui::BoxContent> StarRefLinkBox(
const auto bot = row.bot;
const auto program = row.state.program;
auto list = recipients;
if (!list.empty()) {
list.erase(ranges::remove(list, bot), end(list));
if (!ranges::contains(list, initialRecipient)) {
list.insert(begin(list), initialRecipient);
}
}
box->setStyle(st::starrefFooterBox);
box->setNoContentMargin(true);
@ -471,13 +538,34 @@ object_ptr<Ui::BoxContent> StarRefLinkBox(
box->closeBox();
});
box->addRow(
CreateUserpicsTransfer(
box,
rpl::single(std::vector{ not_null<PeerData*>(bot) }),
peer,
UserpicsTransferType::StarRefJoin),
st::boxRowPadding + st::starrefJoinUserpicsPadding);
struct State {
rpl::variable<not_null<PeerData*>> recipient;
QPointer<Ui::GenericBox> weak;
bool sent = false;
};
const auto state = std::make_shared<State>(State{
.recipient = initialRecipient,
.weak = box.get(),
});
const auto userpicsWrap = box->addRow(
object_ptr<Ui::VerticalLayout>(box),
QMargins());
state->recipient.value(
) | rpl::start_with_next([=](not_null<PeerData*> recipient) {
while (userpicsWrap->count()) {
delete userpicsWrap->widgetAt(0);
}
userpicsWrap->add(
CreateUserpicsTransfer(
box,
rpl::single(std::vector{ not_null<PeerData*>(bot) }),
recipient,
UserpicsTransferType::StarRefJoin),
st::boxRowPadding + st::starrefJoinUserpicsPadding);
userpicsWrap->resizeToWidth(box->width());
}, box->lifetime());
box->addRow(
object_ptr<Ui::CenterWrap<Ui::FlatLabel>>(
box,
@ -504,7 +592,7 @@ object_ptr<Ui::BoxContent> StarRefLinkBox(
Ui::AddSkip(box->verticalLayout(), st::defaultVerticalListSkip * 3);
if (const auto average = program.revenuePerUser) {
const auto layout = box->verticalLayout();
const auto session = &peer->session();
const auto session = &initialRecipient->session();
const auto makeContext = [session](Fn<void()> update) {
return Core::MarkedTextContext{
.session = session,
@ -512,13 +600,14 @@ object_ptr<Ui::BoxContent> StarRefLinkBox(
};
};
auto text = Ui::Text::Colorized(Ui::CreditsEmoji(session));
text.append(Lang::FormatStarsAmountDecimal(average));
text.append(Lang::FormatStarsAmountRounded(average));
layout->add(
object_ptr<Ui::FlatLabel>(
box,
tr::lng_star_ref_one_daily_revenue(
lt_amount,
rpl::single(Ui::Text::Wrapped(text, EntityType::Bold)),
rpl::single(
Ui::Text::Wrapped(text, EntityType::Bold)),
Ui::Text::WithEntities),
st::starrefRevenueText,
st::defaultPopupMenu,
@ -526,35 +615,89 @@ object_ptr<Ui::BoxContent> StarRefLinkBox(
st::boxRowPadding);
Ui::AddSkip(layout, st::defaultVerticalListSkip);
}
#if 0
box->addRow(
object_ptr<Ui::FlatLabel>(
box,
tr::lng_star_ref_link_recipient(),
st::starrefCenteredText));
Ui::AddSkip(box->verticalLayout());
box->addRow(object_ptr<Ui::AbstractButton>::fromRaw(
MakePeerBubbleButton(box, peer).release()
))->setAttribute(Qt::WA_TransparentForMouseEvents);
#endif
struct State {
QPointer<Ui::GenericBox> weak;
bool sent = false;
};
const auto state = std::make_shared<State>();
state->weak = box;
if (!list.empty()) {
struct Name {
not_null<PeerData*> peer;
QString name;
};
auto names = ranges::views::transform(list, [](auto peer) {
const auto name = TextUtilities::NameSortKey(peer->name());
return Name{ peer, name };
}) | ranges::to_vector;
ranges::sort(names, ranges::less(), &Name::name);
list = ranges::views::transform(names, &Name::peer)
| ranges::to_vector;
box->addRow(
object_ptr<Ui::FlatLabel>(
box,
tr::lng_star_ref_link_recipient(),
st::starrefCenteredText));
Ui::AddSkip(box->verticalLayout());
const auto recipientWrap = box->addRow(
object_ptr<Ui::VerticalLayout>(box),
QMargins());
state->recipient.value(
) | rpl::start_with_next([=](not_null<PeerData*> recipient) {
while (recipientWrap->count()) {
delete recipientWrap->widgetAt(0);
}
const auto selectable = (list.size() > 1);
const auto bgOverride = selectable
? &st::lightButtonBgOver
: nullptr;
const auto right = selectable
? Ui::CreateChild<Ui::RpWidget>(recipientWrap)
: nullptr;
if (right) {
const auto skip = st::chatGiveawayPeerPadding.right();
const auto icon = &st::starrefRecipientArrow;
right->resize(skip + icon->width(), icon->height());
right->paintRequest() | rpl::start_with_next([=] {
auto p = QPainter(right);
icon->paint(p, skip, 0, right->width());
}, right->lifetime());
}
const auto button = recipientWrap->add(
object_ptr<Ui::AbstractButton>::fromRaw(
MakePeerBubbleButton(
box,
recipient,
right,
bgOverride).release()),
st::boxRowPadding);
recipientWrap->resizeToWidth(box->width());
if (!selectable) {
button->setAttribute(Qt::WA_TransparentForMouseEvents);
return;
}
button->setClickedCallback([=] {
const auto callback = [=](not_null<PeerData*> peer) {
state->recipient = peer;
};
ChooseRecipient(
button,
list,
state->recipient.current(),
crl::guard(button, callback));
});
}, box->lifetime());
}
const auto send = [=] {
if (state->sent) {
return;
}
state->sent = true;
ConnectStarRef(bot->asUser(), peer, [=](ConnectedBot info) {
const auto recipient = state->recipient.current();
ConnectStarRef(bot->asUser(), recipient, [=](ConnectedBot info) {
if (const auto onstack = done) {
onstack(info.state);
}
show->show(StarRefLinkBox(info, peer));
show->show(StarRefLinkBox(info, recipient));
if (const auto strong = state->weak.data()) {
strong->closeBox();
}
@ -624,18 +767,103 @@ object_ptr<Ui::BoxContent> ConfirmEndBox(Fn<void()> finish) {
});
}
void ResolveRecipients(
not_null<Main::Session*> session,
Fn<void(std::vector<not_null<PeerData*>>)> done) {
struct State {
not_null<Main::Session*> session;
std::vector<not_null<PeerData*>> list;
Fn<void(std::vector<not_null<PeerData*>>)> done;
};
const auto state = std::make_shared<State>(State{
.session = session,
.done = std::move(done),
});
const auto finish1 = [state](const MTPmessages_Chats &result) {
const auto already = int(state->list.size());
const auto session = state->session;
result.match([&](const auto &data) {
const auto &list = data.vchats().v;
state->list.reserve(list.size() + (already ? already : 1));
if (!already) {
state->list.push_back(session->user());
}
for (const auto &chat : list) {
const auto peer = session->data().processChat(chat);
if (const auto channel = peer->asBroadcast()) {
if (channel->canPostMessages()) {
state->list.push_back(channel);
}
}
}
if (already) {
base::take(state->done)(base::take(state->list));
}
});
};
const auto finish2 = [state](const MTPVector<MTPUser> &result) {
const auto already = int(state->list.size());
const auto session = state->session;
const auto &list = result.v;
state->list.reserve(list.size() + (already ? already : 1));
if (!already) {
state->list.push_back(session->user());
}
for (const auto &user : list) {
state->list.push_back(session->data().processUser(user));
}
if (already) {
base::take(state->done)(base::take(state->list));
}
};
session->api().request(MTPchannels_GetAdminedPublicChannels(
MTP_flags(0)
)).done(finish1).fail([=] {
finish1(MTP_messages_chats(MTP_vector<MTPChat>(0)));
}).send();
state->session->api().request(MTPbots_GetAdminedBots(
)).done(finish2).fail([=] {
finish2(MTP_vector<MTPUser>(0));
}).send();
}
std::unique_ptr<Ui::AbstractButton> MakePeerBubbleButton(
not_null<QWidget*> parent,
not_null<PeerData*> peer,
Ui::RpWidget *right) {
auto result = std::make_unique<Ui::AbstractButton>(parent);
Ui::RpWidget *right,
const style::color *bgOverride) {
class Button final : public Ui::AbstractButton {
public:
Button(QWidget *parent, not_null<int*> innerWidth)
: AbstractButton(parent)
, _innerWidth(innerWidth) {
}
void mouseMoveEvent(QMouseEvent *e) override {
const auto inner = *_innerWidth;
const auto skip = (width() - inner) / 2;
const auto p = e->pos();
const auto over = QRect(skip, 0, inner, height()).contains(p);
setOver(over, StateChangeSource::ByHover);
}
private:
const not_null<int*> _innerWidth;
};
auto ownedWidth = std::make_unique<int>();
const auto width = ownedWidth.get();
auto result = std::make_unique<Button>(parent, width);
result->lifetime().add([moved = std::move(ownedWidth)] {});
const auto size = st::chatGiveawayPeerSize;
const auto padding = st::chatGiveawayPeerPadding;
const auto raw = result.get();
const auto width = raw->lifetime().make_state<int>();
const auto name = raw->lifetime().make_state<Ui::FlatLabel>(
raw,
rpl::single(peer->name()),
@ -691,7 +919,7 @@ std::unique_ptr<Ui::AbstractButton> MakePeerBubbleButton(
p.setClipRect(left + skip, 0, *width - skip, size);
auto hq = PainterHighQualityEnabler(p);
p.setPen(Qt::NoPen);
p.setBrush(st::windowBgOver);
p.setBrush(bgOverride ? *bgOverride : st::windowBgOver);
p.drawRoundedRect(left, 0, *width, size, skip, skip);
}, raw->lifetime());

View file

@ -23,6 +23,10 @@ namespace style {
struct RoundButton;
} // namespace style
namespace Main {
class Session;
} // namespace Main
namespace Info::BotStarRef {
struct ConnectedBotState {
@ -65,14 +69,20 @@ void AddFullWidthButtonFooter(
not_null<PeerData*> peer);
[[nodiscard]] object_ptr<Ui::BoxContent> JoinStarRefBox(
ConnectedBot row,
not_null<PeerData*> peer,
not_null<PeerData*> initialRecipient,
std::vector<not_null<PeerData*>> recipients,
Fn<void(ConnectedBotState)> done = nullptr);
[[nodiscard]] object_ptr<Ui::BoxContent> ConfirmEndBox(Fn<void()> finish);
void ResolveRecipients(
not_null<Main::Session*> session,
Fn<void(std::vector<not_null<PeerData*>>)> done);
std::unique_ptr<Ui::AbstractButton> MakePeerBubbleButton(
not_null<QWidget*> parent,
not_null<PeerData*> peer,
Ui::RpWidget *right = nullptr);
Ui::RpWidget *right = nullptr,
const style::color *bgOverride = nullptr);
void ConfirmUpdate(
std::shared_ptr<Ui::Show> show,

View file

@ -87,6 +87,7 @@ public:
private:
[[nodiscard]] std::unique_ptr<PeerListRow> createRow(ConnectedBot bot);
void open(not_null<UserData*> bot, ConnectedBotState state);
void requestRecipients();
void setupAddForBot();
const not_null<Window::SessionController*> _controller;
@ -97,6 +98,8 @@ private:
base::flat_set<not_null<PeerData*>> _resolving;
UserData *_openOnResolve = nullptr;
Fn<void()> _recipientsReady;
std::vector<not_null<PeerData*>> _recipients;
rpl::event_stream<ConnectedBot> _connected;
rpl::event_stream<> _addForBot;
@ -104,6 +107,7 @@ private:
TimeId _offsetDate = 0;
QString _offsetThing;
bool _allLoaded = false;
bool _recipientsRequested = false;
rpl::variable<int> _rowCount = 0;
@ -328,17 +332,50 @@ void ListController::rowClicked(not_null<PeerListRow*> row) {
}
void ListController::open(not_null<UserData*> bot, ConnectedBotState state) {
const auto show = _controller->uiShow();
if (_type == JoinType::Joined || !state.link.isEmpty()) {
_controller->show(StarRefLinkBox({ bot, state }, _peer));
_recipientsReady = nullptr;
show->show(StarRefLinkBox({ bot, state }, _peer));
} else {
const auto requireOthers = (_type == JoinType::Existing)
|| _peer->isSelf();
const auto requestOthers = requireOthers && _recipients.empty();
if (requestOthers) {
_recipientsReady = [=] {
Expects(!_recipients.empty());
open(bot, state);
};
requestRecipients();
return;
}
const auto connected = crl::guard(this, [=](ConnectedBotState now) {
_states[bot] = now;
_connected.fire({ bot, now });
});
_controller->show(JoinStarRefBox({ bot, state }, _peer, connected));
show->show(JoinStarRefBox(
{ bot, state },
_peer,
requireOthers ? _recipients : std::vector<not_null<PeerData*>>(),
connected));
}
}
void ListController::requestRecipients() {
if (_recipientsRequested) {
return;
}
_recipientsRequested = true;
const auto session = &this->session();
ResolveRecipients(session, crl::guard(this, [=](
std::vector<not_null<PeerData*>> list) {
_recipients = std::move(list);
if (const auto callback = base::take(_recipientsReady)) {
callback();
}
}));
}
void RevokeLink(
not_null<Window::SessionController*> controller,
not_null<PeerData*> peer,

View file

@ -2142,6 +2142,29 @@ void ActionsFiller::addAffiliateProgram(not_null<UserData*> user) {
});
const auto show = _controller->uiShow();
struct StarRefRecipients {
std::vector<not_null<PeerData*>> list;
bool requested = false;
Fn<void()> open;
};
const auto recipients = std::make_shared<StarRefRecipients>();
recipients->open = [=] {
if (!recipients->list.empty()) {
const auto program = user->botInfo->starRefProgram;
show->show(Info::BotStarRef::JoinStarRefBox(
{ user, { program } },
user->session().user(),
recipients->list));
} else if (!recipients->requested) {
recipients->requested = true;
const auto done = [=](std::vector<not_null<PeerData*>> list) {
recipients->list = std::move(list);
recipients->open();
};
Info::BotStarRef::ResolveRecipients(&user->session(), done);
}
};
Ui::AddSkip(inner);
::Settings::AddButtonWithLabel(
inner,
@ -2149,12 +2172,7 @@ void ActionsFiller::addAffiliateProgram(not_null<UserData*> user) {
rpl::duplicate(commission),
st::infoSharedMediaButton,
{ &st::menuIconSharing }
)->setClickedCallback([=] {
const auto program = user->botInfo->starRefProgram;
show->show(Info::BotStarRef::JoinStarRefBox(
{ user, { program } },
user->session().user()));
});
)->setClickedCallback(recipients->open);
Ui::AddSkip(inner);
Ui::AddDividerText(
inner,

View file

@ -963,6 +963,11 @@ QString FormatStarsAmountDecimal(StarsAmount amount) {
return FormatExactCountDecimal(amount.value());
}
QString FormatStarsAmountRounded(StarsAmount amount) {
const auto value = amount.value();
return FormatExactCountDecimal(base::SafeRound(value * 100.) / 100.);
}
PluralResult Plural(
ushort keyBase,
float64 value,

View file

@ -31,6 +31,7 @@ struct ShortenedCount {
[[nodiscard]] QString FormatExactCountDecimal(float64 number);
[[nodiscard]] ShortenedCount FormatStarsAmountToShort(StarsAmount amount);
[[nodiscard]] QString FormatStarsAmountDecimal(StarsAmount amount);
[[nodiscard]] QString FormatStarsAmountRounded(StarsAmount amount);
struct PluralResult {
int keyShift = 0;

View file

@ -1177,6 +1177,8 @@ botEmojiStatusUserpic: UserpicButton(defaultUserpicButton) {
photoSize: chatGiveawayPeerSize;
}
botEmojiStatusName: FlatLabel(defaultFlatLabel) {
minWidth: 32px;
maxHeight: 20px;
}
botEmojiStatusEmoji: FlatLabel(botEmojiStatusName) {
textFg: profileVerifiedCheckBg;

View file

@ -796,7 +796,8 @@ void WhoReactedEntryAction::paint(Painter &&p) {
if (selected && _st.itemBgOver->c.alpha() < 255) {
p.fillRect(0, 0, width(), _height, _st.itemBg);
}
p.fillRect(0, 0, width(), _height, selected ? _st.itemBgOver : _st.itemBg);
const auto bg = selected ? _st.itemBgOver : _st.itemBg;
p.fillRect(0, 0, width(), _height, bg);
if (enabled) {
paintRipple(p, 0, 0);
}
@ -817,6 +818,18 @@ void WhoReactedEntryAction::paint(Painter &&p) {
p.drawEllipse(photoLeft, photoTop, photoSize, photoSize);
} else if (!_userpic.isNull()) {
p.drawImage(photoLeft, photoTop, _userpic);
if (_type == WhoReactedType::RefRecipientNow) {
auto hq = PainterHighQualityEnabler(p);
p.setBrush(Qt::NoBrush);
auto bgPen = bg->p;
bgPen.setWidthF(st::lineWidth * 6.);
p.setPen(bgPen);
p.drawEllipse(photoLeft, photoTop, photoSize, photoSize);
auto fgPen = st::windowBgActive->p;
fgPen.setWidthF(st::lineWidth * 2.);
p.setPen(fgPen);
p.drawEllipse(photoLeft, photoTop, photoSize, photoSize);
}
} else if (!_custom) {
st::menuIconReactions.paintInCenter(
p,
@ -852,7 +865,16 @@ void WhoReactedEntryAction::paint(Painter &&p) {
_textWidth,
width());
}
if (withDate) {
if (_type == WhoReactedType::RefRecipient
|| _type == WhoReactedType::RefRecipientNow) {
p.setPen(selected ? _st.itemFgShortcutOver : _st.itemFgShortcut);
_date.drawLeftElided(
p,
st::defaultWhoRead.nameLeft,
st::whoReadDateTop,
_textWidth,
width());
} else if (withDate) {
const auto iconPosition = QPoint(
st::defaultWhoRead.nameLeft,
st::whoReadDateTop) + st::whoReadDateChecksPosition;

View file

@ -74,6 +74,8 @@ enum class WhoReactedType : uchar {
Reposted,
Forwarded,
Preloader,
RefRecipient,
RefRecipientNow,
};
struct WhoReactedEntryData {

View file

@ -475,6 +475,17 @@ starrefLinkCountIcon: icon{{ "chat/mini_subscribers", historyPeerUserpicFg }};
starrefLinkCountIconPosition: point(0px, 1px);
starrefLinkCountFont: font(10px bold);
starrefLinkCountPadding: margins(2px, 0px, 3px, 1px);
starrefRecipientBg: lightButtonBgOver;
starrefRecipientBgDisabled: windowBgOver;
starrefRecipientArrow: icon{{ "calendar_down", lightButtonFg }};
starrefAddForBotIcon: icon {{ "menu/bot_add", lightButtonFg }};
starrefAddForBotIconPosition: point(23px, 2px);
starrefPopupMenu: PopupMenu(defaultPopupMenu) {
maxHeight: 320px;
menu: Menu(defaultMenu) {
widthMin: 156px;
widthMax: 200px;
}
}

@ -1 +1 @@
Subproject commit 3254aebf55133066e0a8775cabdfcef568b79625
Subproject commit 93c1299a39258199d0336b33dc49cab029371785