Added initial implementation of right button for bots in dialogs list.

This commit is contained in:
23rd 2024-11-24 16:53:28 +03:00
parent 14cc7789d9
commit 8a3aa660cb
9 changed files with 259 additions and 39 deletions

View file

@ -1475,6 +1475,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_profile_enable_notifications" = "Notifications";
"lng_profile_send_message" = "Send Message";
"lng_profile_open_app" = "Open App";
"lng_profile_open_app_short" = "Open";
"lng_profile_open_app_about" = "By launching this mini app, you agree to the {terms}.";
"lng_profile_open_app_terms" = "Terms of Service for Mini Apps";
"lng_profile_bot_permissions_title" = "Allow access to";

View file

@ -1826,8 +1826,8 @@ void StickersBox::Inner::setPressed(SelectedRow pressed) {
if (_megagroupSet && pressedIndex >= 0 && pressedIndex < _rows.size()) {
update(0, _itemsTop + pressedIndex * _rowHeight, width(), _rowHeight);
auto &set = _rows[pressedIndex];
auto rippleMask = Ui::RippleAnimation::RectMask(QSize(width(), _rowHeight));
if (!set->ripple) {
auto rippleMask = Ui::RippleAnimation::RectMask(QSize(width(), _rowHeight));
set->ripple = std::make_unique<Ui::RippleAnimation>(st::defaultRippleAnimation, std::move(rippleMask), [this, pressedIndex] {
update(0, _itemsTop + pressedIndex * _rowHeight, width(), _rowHeight);
});

View file

@ -108,6 +108,10 @@ taggedForumDialogRow: DialogRow(forumDialogRow) {
}
dialogRowFilterTagSkip : 4px;
dialogRowFilterTagFont : font(10px);
dialogRowOpenBotTextStyle: semiboldTextStyle;
dialogRowOpenBotHeight: 20px;
dialogRowOpenBotRight: 10px;
dialogRowOpenBotTop: 32px;
forumDialogJumpArrow: icon{{ "dialogs/dialogs_topic_arrow", dialogsTextFg }};
forumDialogJumpArrowOver: icon{{ "dialogs/dialogs_topic_arrow", dialogsTextFgOver }};

View file

@ -29,6 +29,7 @@ class SavedSublist;
} // namespace Data
namespace Ui {
class RippleAnimation;
struct PeerUserpicView;
} // namespace Ui
@ -43,6 +44,14 @@ class Row;
class IndexedList;
class MainList;
struct RightButton final {
QImage bg;
QImage selectedBg;
QImage activeBg;
Ui::Text::String text;
std::unique_ptr<Ui::RippleAnimation> ripple;
};
struct RowsByLetter {
not_null<Row*> main;
base::flat_map<QChar, not_null<Row*>> letters;

View file

@ -29,6 +29,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/text/text_options.h"
#include "ui/dynamic_thumbnails.h"
#include "ui/painter.h"
#include "ui/rect.h"
#include "ui/ui_utility.h"
#include "data/data_drafts.h"
#include "data/data_folder.h"
@ -62,6 +63,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "window/window_controller.h"
#include "window/window_session_controller.h"
#include "window/window_peer_menu.h"
#include "ui/effects/ripple_animation.h"
#include "ui/effects/loading_element.h"
#include "ui/widgets/multi_select.h"
#include "ui/widgets/menu/menu_add_action_callback_factory.h"
@ -122,6 +124,19 @@ constexpr auto kPreviewPostsLimit = 3;
return result;
}
[[nodiscard]] UserData *MaybeBotWithApp(Row *row) {
if (row) {
if (const auto history = row->key().history()) {
if (const auto user = history->peer->asUser()) {
if (user->botInfo && user->botInfo->hasMainApp) {
return user;
}
}
}
}
return nullptr;
}
[[nodiscard]] object_ptr<SearchEmpty> MakeSearchEmpty(
QWidget *parent,
SearchState state) {
@ -773,7 +788,7 @@ void InnerWidget::paintEvent(QPaintEvent *e) {
not_null<Row*> row,
bool selected,
bool mayBeActive) {
const auto key = row->key();
const auto &key = row->key();
const auto active = mayBeActive && isRowActive(row, activeEntry);
const auto forum = key.history() && key.history()->isForum();
if (forum && !_topicJumpCache) {
@ -781,6 +796,7 @@ void InnerWidget::paintEvent(QPaintEvent *e) {
}
const auto expanding = forum
&& (key.history()->peer->id == childListShown.peerId);
context.rightButton = maybeCacheRightButton(row);
context.st = (forum ? &st::forumDialogRow : _st.get());
@ -1195,6 +1211,47 @@ void InnerWidget::paintEvent(QPaintEvent *e) {
}
}
[[nodiscard]] RightButton *InnerWidget::maybeCacheRightButton(Row *row) {
if (const auto user = MaybeBotWithApp(row)) {
const auto it = _rightButtons.find(user->id);
if (it == _rightButtons.end()) {
auto rightButton = RightButton();
const auto text
= tr::lng_profile_open_app_short(tr::now).toUpper();
rightButton.text.setText(st::dialogRowOpenBotTextStyle, text);
const auto size = QSize(
rightButton.text.maxWidth()
+ rightButton.text.minHeight(),
st::dialogRowOpenBotHeight);
const auto generateBg = [&](const style::color &c) {
auto bg = QImage(
style::DevicePixelRatio() * size,
QImage::Format_ARGB32_Premultiplied);
bg.setDevicePixelRatio(style::DevicePixelRatio());
bg.fill(Qt::transparent);
{
auto p = QPainter(&bg);
auto hq = PainterHighQualityEnabler(p);
p.setPen(Qt::NoPen);
p.setBrush(c);
const auto r = size.height() / 2;
p.drawRoundedRect(Rect(size), r, r);
}
return bg;
};
rightButton.bg = generateBg(st::activeButtonBg);
rightButton.selectedBg = generateBg(st::activeButtonBgOver);
rightButton.activeBg = generateBg(st::activeButtonFg);
return &(_rightButtons.emplace(
user->id,
std::move(rightButton)).first->second);
} else {
return &(it->second);
}
}
return nullptr;
}
Ui::VideoUserpic *InnerWidget::validateVideoUserpic(not_null<Row*> row) {
const auto history = row->history();
return history ? validateVideoUserpic(history) : nullptr;
@ -1554,6 +1611,26 @@ void InnerWidget::clearIrrelevantState() {
}
}
bool InnerWidget::lookupIsInBotAppButton(
Row *row,
QPoint localPosition) {
if (const auto user = MaybeBotWithApp(row)) {
const auto it = _rightButtons.find(user->id);
if (it != _rightButtons.end()) {
const auto s = it->second.bg.size() / style::DevicePixelRatio();
const auto r = QRect(
width() - s.width() - st::dialogRowOpenBotRight,
st::dialogRowOpenBotTop,
s.width(),
s.height());
if (r.contains(localPosition)) {
return true;
}
}
}
return false;
}
void InnerWidget::selectByMouse(QPoint globalPosition) {
const auto local = mapFromGlobal(globalPosition);
if (updateReorderPinned(local)) {
@ -1594,16 +1671,19 @@ void InnerWidget::selectByMouse(QPoint globalPosition) {
: (mouseY >= offset)
? _shownList->rowAtY(mouseY - offset)
: nullptr;
const auto mappedY = selected ? mouseY - offset - selected->top() : 0;
const auto selectedTopicJump = selected
&& selected->lookupIsInTopicJump(
local.x(),
mouseY - offset - selected->top());
&& selected->lookupIsInTopicJump(local.x(), mappedY);
const auto selectedBotApp = selected
&& lookupIsInBotAppButton(selected, QPoint(local.x(), mappedY));
if (_collapsedSelected != collapsedSelected
|| _selected != selected
|| _selectedTopicJump != selectedTopicJump) {
|| _selectedTopicJump != selectedTopicJump
|| _selectedBotApp != selectedBotApp) {
updateSelectedRow();
_selected = selected;
_selectedTopicJump = selectedTopicJump;
_selectedBotApp = selectedBotApp;
_collapsedSelected = collapsedSelected;
updateSelectedRow();
setCursor((_selected || _collapsedSelected >= 0)
@ -1729,7 +1809,7 @@ void InnerWidget::mousePressEvent(QMouseEvent *e) {
selectByMouse(e->globalPos());
_pressButton = e->button();
setPressed(_selected, _selectedTopicJump);
setPressed(_selected, _selectedTopicJump, _selectedBotApp);
setCollapsedPressed(_collapsedSelected);
setHashtagPressed(_hashtagSelected);
_hashtagDeletePressed = _hashtagDeleteSelected;
@ -1762,7 +1842,22 @@ void InnerWidget::mousePressEvent(QMouseEvent *e) {
};
const auto origin = e->pos()
- QPoint(0, dialogsOffset() + _pressed->top());
if (_pressedTopicJump) {
if (_pressedBotApp && _pressedBotAppData) {
const auto size = _pressedBotAppData->bg.size()
/ style::DevicePixelRatio();
if (!_pressedBotAppData->ripple) {
const auto r = size.height() / 2;
_pressedBotAppData->ripple
= std::make_unique<Ui::RippleAnimation>(
st::defaultRippleAnimation,
Ui::RippleAnimation::RoundRectMask(size, r),
updateCallback);
}
const auto shift = QPoint(
width() - size.width() - st::dialogRowOpenBotRight,
st::dialogRowOpenBotTop);
_pressedBotAppData->ripple->add(origin - shift);
} else if (_pressedTopicJump) {
row->addTopicJumpRipple(
origin,
_topicJumpCache.get(),
@ -2094,6 +2189,7 @@ void InnerWidget::mousePressReleased(
setCollapsedPressed(-1);
const auto pressedTopicRootId = _pressedTopicJumpRootId;
const auto pressedTopicJump = _pressedTopicJump;
const auto pressedBotApp = _pressedBotApp;
auto pressed = _pressed;
clearPressed();
auto hashtagPressed = _hashtagPressed;
@ -2113,12 +2209,16 @@ void InnerWidget::mousePressReleased(
if (wasDragging) {
selectByMouse(globalPosition);
}
if (_pressedBotAppData && _pressedBotAppData->ripple) {
_pressedBotAppData->ripple->lastStop();
}
updateSelectedRow();
if (!wasDragging && button == Qt::LeftButton) {
if ((collapsedPressed >= 0 && collapsedPressed == _collapsedSelected)
|| (pressed
&& pressed == _selected
&& pressedTopicJump == _selectedTopicJump)
&& pressedTopicJump == _selectedTopicJump
&& pressedBotApp == _selectedBotApp)
|| (hashtagPressed >= 0
&& hashtagPressed == _hashtagSelected
&& hashtagDeletePressed == _hashtagDeleteSelected)
@ -2131,7 +2231,13 @@ void InnerWidget::mousePressReleased(
&& searchedPressed == _searchedSelected)
|| (pressedMorePosts
&& pressedMorePosts == _selectedMorePosts)) {
chooseRow(modifiers, pressedTopicRootId);
if (pressedBotApp) {
if (const auto user = MaybeBotWithApp(pressed)) {
_openBotMainAppRequests.fire(peerToUser(user->id));
}
} else {
chooseRow(modifiers, pressedTopicRootId);
}
}
}
if (auto activated = ClickHandler::unpressed()) {
@ -2152,14 +2258,31 @@ void InnerWidget::setCollapsedPressed(int pressed) {
}
}
void InnerWidget::setPressed(Row *pressed, bool pressedTopicJump) {
if (_pressed != pressed || (pressed && _pressedTopicJump != pressedTopicJump)) {
void InnerWidget::setPressed(
Row *pressed,
bool pressedTopicJump,
bool pressedBotApp) {
if ((_pressed != pressed)
|| (pressed && _pressedTopicJump != pressedTopicJump)
|| (pressed && _pressedBotApp != pressedBotApp)) {
if (_pressed) {
_pressed->stopLastRipple();
}
if (_pressedBotAppData && _pressedBotAppData->ripple) {
_pressedBotAppData->ripple->lastStop();
}
_pressed = pressed;
if (pressed || !pressedTopicJump) {
if (pressed || !pressedTopicJump || !pressedBotApp) {
_pressedTopicJump = pressedTopicJump;
_pressedBotApp = pressedBotApp;
if (pressedBotApp) {
if (const auto user = MaybeBotWithApp(pressed)) {
const auto it = _rightButtons.find(user->id);
if (it != _rightButtons.end()) {
_pressedBotAppData = &(it->second);
}
}
}
const auto history = pressedTopicJump
? pressed->history()
: nullptr;
@ -2170,7 +2293,7 @@ void InnerWidget::setPressed(Row *pressed, bool pressedTopicJump) {
}
void InnerWidget::clearPressed() {
setPressed(nullptr, false);
setPressed(nullptr, false, false);
}
void InnerWidget::setHashtagPressed(int pressed) {
@ -2265,7 +2388,7 @@ void InnerWidget::dialogRowReplaced(
_selected = newRow;
}
if (_pressed == oldRow) {
setPressed(newRow, _pressedTopicJump);
setPressed(newRow, _pressedTopicJump, _pressedBotApp);
}
if (_dragging == oldRow) {
if (newRow) {
@ -4822,4 +4945,8 @@ bool InnerWidget::jumpToDialogRow(RowDescriptor to) {
return _controller->jumpToChatListEntry(to);
}
rpl::producer<UserId> InnerWidget::openBotMainAppRequests() const {
return _openBotMainAppRequests.events();
}
} // namespace Dialogs

View file

@ -64,6 +64,7 @@ class SearchTags;
class SearchEmpty;
class ChatSearchIn;
enum class HashOrCashtag : uchar;
struct RightButton;
struct ChosenRow {
Key key;
@ -200,6 +201,8 @@ public:
return _touchCancelRequests.events();
}
[[nodiscard]] rpl::producer<UserId> openBotMainAppRequests() const;
protected:
void visibleTopBottomUpdated(
int visibleTop,
@ -286,7 +289,7 @@ private:
void scrollToItem(int top, int height);
void scrollToDefaultSelected();
void setCollapsedPressed(int pressed);
void setPressed(Row *pressed, bool pressedTopicJump);
void setPressed(Row *pressed, bool pressedTopicJump, bool pressedBotApp);
void clearPressed();
void setHashtagPressed(int pressed);
void setFilteredPressed(int pressed, bool pressedTopicJump);
@ -451,6 +454,11 @@ private:
void saveChatsFilterScrollState(FilterId filterId);
void restoreChatsFilterScrollState(FilterId filterId);
[[nodiscard]] bool lookupIsInBotAppButton(
Row *row,
QPoint localPosition);
[[nodiscard]] RightButton *maybeCacheRightButton(Row *row);
[[nodiscard]] QImage *cacheChatsFilterTag(
const Data::ChatFilter &filter,
uint8 more,
@ -483,6 +491,10 @@ private:
bool _selectedTopicJump = false;
bool _pressedTopicJump = false;
RightButton *_pressedBotAppData = nullptr;
bool _selectedBotApp = false;
bool _pressedBotApp = false;
Row *_dragging = nullptr;
int _draggingIndex = -1;
int _aboveIndex = -1;
@ -566,6 +578,8 @@ private:
bool _waitingAllChatListEntryRefreshesForTags = false;
rpl::lifetime _handleChatListEntryTagRefreshesLifetime;
std::unordered_map<PeerId, RightButton> _rightButtons;
Fn<void()> _loadMoreCallback;
Fn<void()> _loadMoreFilteredCallback;
rpl::event_stream<> _listBottomReached;
@ -577,6 +591,7 @@ private:
rpl::event_stream<SearchRequestDelay> _searchRequests;
rpl::event_stream<QString> _completeHashtagRequests;
rpl::event_stream<> _refreshHashtagsRequests;
rpl::event_stream<UserId> _openBotMainAppRequests;
RowDescriptor _chatPreviewRow;
bool _chatPreviewScheduled = false;

View file

@ -478,6 +478,12 @@ Widget::Widget(
) | rpl::start_with_next([=](const ChosenRow &row) {
chosenRow(row);
}, lifetime());
_inner->openBotMainAppRequests(
) | rpl::start_with_next([=](UserId userId) {
if (const auto user = session().data().user(userId)) {
openBotMainApp(user);
}
}, lifetime());
_scroll->geometryChanged(
) | rpl::start_with_next(crl::guard(_inner, [=] {

View file

@ -7,41 +7,42 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#include "dialogs/ui/dialogs_layout.h"
#include "base/unixtime.h"
#include "core/ui_integration.h"
#include "data/data_channel.h"
#include "data/data_drafts.h"
#include "data/data_folder.h"
#include "data/data_forum_topic.h"
#include "data/data_peer_values.h"
#include "data/data_saved_sublist.h"
#include "data/data_session.h"
#include "data/data_user.h"
#include "data/stickers/data_custom_emoji.h"
#include "dialogs/dialogs_list.h"
#include "dialogs/dialogs_three_state_icon.h"
#include "dialogs/ui/dialogs_video_userpic.h"
#include "styles/style_dialogs.h"
#include "styles/style_window.h"
#include "history/history.h"
#include "history/history_item.h"
#include "history/history_item_components.h"
#include "history/history_item_helpers.h"
#include "history/history_unread_things.h"
#include "history/view/history_view_item_preview.h"
#include "history/view/history_view_send_action.h"
#include "lang/lang_keys.h"
#include "main/main_session.h"
#include "storage/localstorage.h"
#include "support/support_helper.h"
#include "ui/empty_userpic.h"
#include "ui/painter.h"
#include "ui/power_saving.h"
#include "ui/text/format_values.h"
#include "ui/text/text_options.h"
#include "ui/text/text_utilities.h"
#include "ui/unread_badge.h"
#include "ui/unread_badge_paint.h"
#include "ui/painter.h"
#include "ui/power_saving.h"
#include "core/ui_integration.h"
#include "lang/lang_keys.h"
#include "support/support_helper.h"
#include "main/main_session.h"
#include "history/view/history_view_send_action.h"
#include "history/view/history_view_item_preview.h"
#include "history/history_unread_things.h"
#include "history/history_item.h"
#include "history/history_item_components.h"
#include "history/history_item_helpers.h"
#include "history/history.h"
#include "base/unixtime.h"
#include "data/data_channel.h"
#include "data/data_user.h"
#include "data/data_folder.h"
#include "data/data_peer_values.h"
#include "styles/style_dialogs.h"
#include "styles/style_widgets.h"
#include "styles/style_window.h"
namespace Dialogs::Ui {
namespace {
@ -84,6 +85,55 @@ void PaintRowTopRight(
text);
}
int PaintRightButton(QPainter &p, const PaintContext &context) {
if (context.width < st::columnMinimalWidthLeft) {
return 0;
}
if (const auto rightButton = context.rightButton) {
const auto size = rightButton->bg.size() / style::DevicePixelRatio();
const auto left = context.width
- size.width()
- st::dialogRowOpenBotRight;
const auto top = st::dialogRowOpenBotTop;
p.drawImage(
left,
top,
context.active
? rightButton->activeBg
: context.selected
? rightButton->selectedBg
: rightButton->bg);
if (rightButton->ripple) {
rightButton->ripple->paint(
p,
left,
top,
size.width() - size.height() / 2,
context.active
? &st::universalRippleAnimation.color->c
: &st::activeButtonBgRipple->c);
if (rightButton->ripple->empty()) {
rightButton->ripple.reset();
}
}
p.setPen(context.active
? st::activeButtonBg
: context.selected
? st::activeButtonFgOver
: st::activeButtonFg);
rightButton->text.draw(p, {
.position = QPoint(
left + size.height() / 2,
top + (st::dialogRowOpenBotHeight - rightButton->text.minHeight()) / 2),
.availableWidth = size.width() - size.height() / 2,
.outerWidth = size.width() - size.height() / 2,
.elisionLines = 1,
});
return size.width() + st::dialogsUnreadPadding;
}
return 0;
}
int PaintBadges(
QPainter &p,
const PaintContext &context,
@ -93,7 +143,9 @@ int PaintBadges(
bool displayPinnedIcon = false,
int pinnedIconTop = 0) {
auto initial = right;
if (badgesState.unread
if (const auto used = PaintRightButton(p, context)) {
return used - st::dialogsUnreadPadding;
} else if (badgesState.unread
&& !badgesState.unreadCounter
&& context.st->unreadMarkDiameter > 0) {
const auto d = context.st->unreadMarkDiameter;
@ -430,7 +482,9 @@ void PaintRow(
}
auto availableWidth = namewidth;
if (entry->isPinnedDialog(context.filter)
if (const auto used = PaintRightButton(p, context)) {
availableWidth -= used;
} else if (entry->isPinnedDialog(context.filter)
&& (context.filter || !entry->fixedOnTopIndex())) {
auto &icon = ThreeStateIcon(
st::dialogsPinnedIcon,
@ -528,7 +582,9 @@ void PaintRow(
}
} else if (!item) {
auto availableWidth = namewidth;
if (entry->isPinnedDialog(context.filter)
if (const auto used = PaintRightButton(p, context)) {
availableWidth -= used;
} else if (entry->isPinnedDialog(context.filter)
&& (context.filter || !entry->fixedOnTopIndex())) {
auto &icon = ThreeStateIcon(
st::dialogsPinnedIcon,

View file

@ -29,6 +29,7 @@ namespace Dialogs {
class Row;
class FakeRow;
class BasicRow;
struct RightButton;
} // namespace Dialogs
namespace Dialogs::Ui {
@ -53,6 +54,7 @@ struct TopicJumpCache {
};
struct PaintContext {
RightButton *rightButton = nullptr;
std::vector<QImage*> *chatsFilterTags = nullptr;
not_null<const style::DialogRow*> st;
TopicJumpCache *topicJumpCache = nullptr;