Show nice empty quick reply placeholder.

This commit is contained in:
John Preston 2024-03-01 10:25:37 +04:00
parent d5e920e45a
commit 7f7d544943
17 changed files with 181 additions and 40 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.6 KiB

View file

@ -47,7 +47,11 @@ Widget::Widget(
auto inner = _type->create( auto inner = _type->create(
this, this,
controller->parentController(), controller->parentController(),
scroll()); scroll(),
controller->wrapValue(
) | rpl::map([](Wrap wrap) { return (wrap == Wrap::Layer)
? ::Settings::Container::Layer
: ::Settings::Container::Section; }));
if (inner->hasFlexibleTopBar()) { if (inner->hasFlexibleTopBar()) {
auto filler = setInnerWidget(object_ptr<Ui::RpWidget>(this)); auto filler = setInnerWidget(object_ptr<Ui::RpWidget>(this));
filler->resize(1, 1); filler->resize(1, 1);

View file

@ -96,9 +96,8 @@ void QuickReplies::setupContent(
Box(EditShortcutNameBox, QString(), crl::guard(this, submit))); Box(EditShortcutNameBox, QString(), crl::guard(this, submit)));
}); });
Ui::AddSkip(content); const auto dividerWrap = content->add(
Ui::AddDivider(content); object_ptr<Ui::VerticalLayout>(content));
Ui::AddSkip(content);
const auto inner = content->add( const auto inner = content->add(
object_ptr<Ui::VerticalLayout>(content)); object_ptr<Ui::VerticalLayout>(content));
@ -133,6 +132,18 @@ void QuickReplies::setupContent(
while (old--) { while (old--) {
delete inner->widgetAt(0); delete inner->widgetAt(0);
} }
if (!inner->count()) {
while (dividerWrap->count()) {
delete dividerWrap->widgetAt(0);
}
} else if (!dividerWrap->count()) {
AddSkip(dividerWrap);
AddDivider(dividerWrap);
AddSkip(dividerWrap);
}
if (const auto width = content->width()) {
content->resizeToWidth(width);
}
}, content->lifetime()); }, content->lifetime());
Ui::ResizeFitChild(this, content); Ui::ResizeFitChild(this, content);
@ -170,6 +181,7 @@ void EditShortcutNameBox(
box->setFocusCallback([=] { box->setFocusCallback([=] {
field->setFocusFast(); field->setFocusFast();
}); });
field->selectAll();
const auto callback = [=] { const auto callback = [=] {
const auto name = field->getLastText().trimmed(); const auto name = field->getLastText().trimmed();

View file

@ -27,6 +27,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "history/view/history_view_corner_buttons.h" #include "history/view/history_view_corner_buttons.h"
#include "history/view/history_view_empty_list_bubble.h" #include "history/view/history_view_empty_list_bubble.h"
#include "history/view/history_view_list_widget.h" #include "history/view/history_view_list_widget.h"
#include "history/view/history_view_service_message.h"
#include "history/view/history_view_sticker_toast.h" #include "history/view/history_view_sticker_toast.h"
#include "history/history.h" #include "history/history.h"
#include "history/history_item.h" #include "history/history_item.h"
@ -51,6 +52,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/text/text_utilities.h" #include "ui/text/text_utilities.h"
#include "ui/widgets/menu/menu_add_action_callback.h" #include "ui/widgets/menu/menu_add_action_callback.h"
#include "ui/widgets/scroll_area.h" #include "ui/widgets/scroll_area.h"
#include "ui/painter.h"
#include "window/themes/window_theme.h" #include "window/themes/window_theme.h"
#include "window/section_widget.h" #include "window/section_widget.h"
#include "window/window_session_controller.h" #include "window/window_session_controller.h"
@ -74,6 +76,7 @@ public:
QWidget *parent, QWidget *parent,
not_null<Window::SessionController*> controller, not_null<Window::SessionController*> controller,
not_null<Ui::ScrollArea*> scroll, not_null<Ui::ScrollArea*> scroll,
rpl::producer<Container> containerValue,
BusinessShortcutId shortcutId); BusinessShortcutId shortcutId);
~ShortcutMessages(); ~ShortcutMessages();
@ -97,7 +100,7 @@ public:
QRect clip) override; QRect clip) override;
private: private:
void outerResized(QSize outer); void outerResized();
void updateComposeControlsPosition(); void updateComposeControlsPosition();
// ListDelegate interface. // ListDelegate interface.
@ -247,6 +250,7 @@ private:
const Window::SectionShow &params); const Window::SectionShow &params);
void showAtEnd(); void showAtEnd();
void finishSending(); void finishSending();
void refreshEmptyText();
const not_null<Window::SessionController*> _controller; const not_null<Window::SessionController*> _controller;
const not_null<Main::Session*> _session; const not_null<Main::Session*> _session;
@ -254,6 +258,7 @@ private:
const not_null<History*> _history; const not_null<History*> _history;
rpl::variable<BusinessShortcutId> _shortcutId; rpl::variable<BusinessShortcutId> _shortcutId;
rpl::variable<QString> _shortcut; rpl::variable<QString> _shortcut;
rpl::variable<Container> _container;
std::shared_ptr<Ui::ChatStyle> _style; std::shared_ptr<Ui::ChatStyle> _style;
std::shared_ptr<Ui::ChatTheme> _theme; std::shared_ptr<Ui::ChatTheme> _theme;
QPointer<ListWidget> _inner; QPointer<ListWidget> _inner;
@ -262,6 +267,14 @@ private:
rpl::event_stream<> _showBackRequests; rpl::event_stream<> _showBackRequests;
bool _skipScrollEvent = false; bool _skipScrollEvent = false;
QSize _inOuterResize;
QSize _pendingOuterResize;
const style::icon *_emptyIcon = nullptr;
Ui::Text::String _emptyText;
int _emptyTextWidth = 0;
int _emptyTextHeight = 0;
rpl::variable<Info::SelectedItems> _selectedItems rpl::variable<Info::SelectedItems> _selectedItems
= Info::SelectedItems(Storage::SharedMediaType::kCount); = Info::SelectedItems(Storage::SharedMediaType::kCount);
@ -283,22 +296,33 @@ struct Factory final : AbstractSectionFactory {
object_ptr<AbstractSection> create( object_ptr<AbstractSection> create(
not_null<QWidget*> parent, not_null<QWidget*> parent,
not_null<Window::SessionController*> controller, not_null<Window::SessionController*> controller,
not_null<Ui::ScrollArea*> scroll not_null<Ui::ScrollArea*> scroll,
rpl::producer<Container> containerValue
) const final override { ) const final override {
return object_ptr<ShortcutMessages>( return object_ptr<ShortcutMessages>(
parent, parent,
controller, controller,
scroll, scroll,
std::move(containerValue),
shortcutId); shortcutId);
} }
const BusinessShortcutId shortcutId = {}; const BusinessShortcutId shortcutId = {};
}; };
[[nodiscard]] bool IsAway(const QString &shortcut) {
return (shortcut == u"away"_q);
}
[[nodiscard]] bool IsGreeting(const QString &shortcut) {
return (shortcut == u"hello"_q);
}
ShortcutMessages::ShortcutMessages( ShortcutMessages::ShortcutMessages(
QWidget *parent, QWidget *parent,
not_null<Window::SessionController*> controller, not_null<Window::SessionController*> controller,
not_null<Ui::ScrollArea*> scroll, not_null<Ui::ScrollArea*> scroll,
rpl::producer<Container> containerValue,
BusinessShortcutId shortcutId) BusinessShortcutId shortcutId)
: AbstractSection(parent) : AbstractSection(parent)
, _controller(controller) , _controller(controller)
@ -308,6 +332,7 @@ ShortcutMessages::ShortcutMessages(
, _shortcutId(shortcutId) , _shortcutId(shortcutId)
, _shortcut( , _shortcut(
_session->data().shortcutMessages().lookupShortcut(shortcutId).name) _session->data().shortcutMessages().lookupShortcut(shortcutId).name)
, _container(std::move(containerValue))
, _cornerButtons( , _cornerButtons(
_scroll, _scroll,
controller->chatStyle(), controller->chatStyle(),
@ -345,8 +370,8 @@ ShortcutMessages::ShortcutMessages(
_scroll->sizeValue() | rpl::filter([](QSize size) { _scroll->sizeValue() | rpl::filter([](QSize size) {
return !size.isEmpty(); return !size.isEmpty();
}) | rpl::start_with_next([=](QSize size) { }) | rpl::start_with_next([=] {
outerResized(size); outerResized();
}, lifetime()); }, lifetime());
_scroll->scrolls( _scroll->scrolls(
@ -354,6 +379,11 @@ ShortcutMessages::ShortcutMessages(
processScroll(); processScroll();
}, lifetime()); }, lifetime());
_shortcut.value() | rpl::start_with_next([=] {
refreshEmptyText();
_inner->update();
}, lifetime());
_inner->editMessageRequested( _inner->editMessageRequested(
) | rpl::start_with_next([=](auto fullId) { ) | rpl::start_with_next([=](auto fullId) {
if (const auto item = _session->data().message(fullId)) { if (const auto item = _session->data().message(fullId)) {
@ -364,17 +394,6 @@ ShortcutMessages::ShortcutMessages(
} }
}, _inner->lifetime()); }, _inner->lifetime());
{
auto emptyInfo = base::make_unique_q<EmptyListBubbleWidget>(
_inner,
_style.get(),
st::msgServicePadding);
const auto emptyText = Ui::Text::Semibold(
u"give me your money.."_q);
emptyInfo->setText(emptyText);
_inner->setEmptyInfoWidget(std::move(emptyInfo));
}
_inner->heightValue() | rpl::start_with_next([=](int height) { _inner->heightValue() | rpl::start_with_next([=](int height) {
resize(width(), height); resize(width(), height);
}, lifetime()); }, lifetime());
@ -382,15 +401,62 @@ ShortcutMessages::ShortcutMessages(
ShortcutMessages::~ShortcutMessages() = default; ShortcutMessages::~ShortcutMessages() = default;
void ShortcutMessages::refreshEmptyText() {
const auto &shortcut = _shortcut.current();
const auto away = IsAway(shortcut);
const auto greeting = !away && IsGreeting(shortcut);
auto text = away
? tr::lng_away_empty_title(
tr::now,
Ui::Text::Bold
).append("\n\n").append(tr::lng_away_empty_about(tr::now))
: greeting
? tr::lng_greeting_empty_title(
tr::now,
Ui::Text::Bold
).append("\n\n").append(tr::lng_greeting_empty_about(tr::now))
: tr::lng_replies_empty_title(
tr::now,
Ui::Text::Bold
).append("\n\n").append(tr::lng_replies_empty_about(
tr::now,
lt_shortcut,
Ui::Text::Bold('/' + shortcut),
Ui::Text::WithEntities));
_emptyIcon = away
? &st::awayEmptyIcon
: greeting
? &st::greetingEmptyIcon
: &st::repliesEmptyIcon;
const auto padding = st::repliesEmptyPadding;
const auto minWidth = st::repliesEmptyWidth / 4;
const auto maxWidth = std::max(
minWidth + 1,
st::repliesEmptyWidth - padding.left() - padding.right());
_emptyText = Ui::Text::String(
st::messageTextStyle,
text,
kMarkupTextOptions,
minWidth);
const auto countHeight = [&](int width) {
return _emptyText.countHeight(width);
};
_emptyTextWidth = Ui::FindNiceTooltipWidth(
minWidth,
maxWidth,
countHeight);
_emptyTextHeight = countHeight(_emptyTextWidth);
}
Type ShortcutMessages::Id(BusinessShortcutId shortcutId) { Type ShortcutMessages::Id(BusinessShortcutId shortcutId) {
return std::make_shared<Factory>(shortcutId); return std::make_shared<Factory>(shortcutId);
} }
rpl::producer<QString> ShortcutMessages::title() { rpl::producer<QString> ShortcutMessages::title() {
return _shortcut.value() | rpl::map([=](const QString &shortcut) { return _shortcut.value() | rpl::map([=](const QString &shortcut) {
return (shortcut == u"away"_q) return IsAway(shortcut)
? tr::lng_away_title() ? tr::lng_away_title()
: (shortcut == u"hello"_q) : IsGreeting(shortcut)
? tr::lng_greeting_title() ? tr::lng_greeting_title()
: rpl::single('/' + shortcut); : rpl::single('/' + shortcut);
}) | rpl::flatten_latest(); }) | rpl::flatten_latest();
@ -497,22 +563,36 @@ bool ShortcutMessages::paintOuter(
return true; return true;
} }
void ShortcutMessages::outerResized(QSize outer) { void ShortcutMessages::outerResized() {
const auto contentWidth = outer.width(); const auto outer = _scroll->size();
if (!_inOuterResize.isEmpty()) {
_pendingOuterResize = (_inOuterResize != outer)
? outer
: QSize();
return;
}
_inOuterResize = outer;
const auto newScrollTop = _scroll->isHidden() do {
? std::nullopt const auto newScrollTop = _scroll->isHidden()
: _scroll->scrollTop() ? std::nullopt
? base::make_optional(_scroll->scrollTop()) : _scroll->scrollTop()
: 0; ? base::make_optional(_scroll->scrollTop())
_skipScrollEvent = true; : 0;
_inner->resizeToWidth(contentWidth, st::boxWidth); _skipScrollEvent = true;
_skipScrollEvent = false; const auto minHeight = (_container.current() == Container::Layer)
? st::boxWidth
: _inOuterResize.height();
_inner->resizeToWidth(_inOuterResize.width(), minHeight);
_skipScrollEvent = false;
if (!_scroll->isHidden()) { if (!_scroll->isHidden() && newScrollTop) {
if (newScrollTop) {
_scroll->scrollToY(*newScrollTop); _scroll->scrollToY(*newScrollTop);
} }
_inOuterResize = base::take(_pendingOuterResize);
} while (!_inOuterResize.isEmpty());
if (!_scroll->isHidden()) {
updateInnerVisibleArea(); updateInnerVisibleArea();
} }
updateComposeControlsPosition(); updateComposeControlsPosition();
@ -896,8 +976,36 @@ void ShortcutMessages::listOpenDocument(
} }
void ShortcutMessages::listPaintEmpty( void ShortcutMessages::listPaintEmpty(
Painter &p, Painter &p,
const Ui::ChatPaintContext &context) { const Ui::ChatPaintContext &context) {
Expects(_emptyIcon != nullptr);
const auto width = st::repliesEmptyWidth;
const auto padding = st::repliesEmptyPadding;
const auto height = padding.top()
+ _emptyIcon->height()
+ st::repliesEmptySkip
+ _emptyTextHeight
+ padding.bottom();
const auto r = QRect(
(this->width() - width) / 2,
(this->height() - height) / 3,
width,
height);
HistoryView::ServiceMessagePainter::PaintBubble(p, context.st, r);
_emptyIcon->paint(
p,
r.x() + (r.width() - _emptyIcon->width()) / 2,
r.y() + padding.top(),
this->width());
p.setPen(st::msgServiceFg);
_emptyText.draw(
p,
r.x() + (r.width() - _emptyTextWidth) / 2,
r.y() + padding.top() + _emptyIcon->height() + st::repliesEmptySkip,
_emptyTextWidth,
style::al_top);
} }
QString ShortcutMessages::listElementAuthorRank( QString ShortcutMessages::listElementAuthorRank(

View file

@ -562,7 +562,8 @@ struct SectionFactory<Business> : AbstractSectionFactory {
object_ptr<AbstractSection> create( object_ptr<AbstractSection> create(
not_null<QWidget*> parent, not_null<QWidget*> parent,
not_null<Window::SessionController*> controller, not_null<Window::SessionController*> controller,
not_null<Ui::ScrollArea*> scroll not_null<Ui::ScrollArea*> scroll,
rpl::producer<Container> containerValue
) const final override { ) const final override {
return object_ptr<Business>(parent, controller); return object_ptr<Business>(parent, controller);
} }

View file

@ -26,13 +26,19 @@ class SessionController;
namespace Settings { namespace Settings {
enum class Container {
Section,
Layer,
};
class AbstractSection; class AbstractSection;
struct AbstractSectionFactory { struct AbstractSectionFactory {
[[nodiscard]] virtual object_ptr<AbstractSection> create( [[nodiscard]] virtual object_ptr<AbstractSection> create(
not_null<QWidget*> parent, not_null<QWidget*> parent,
not_null<Window::SessionController*> controller, not_null<Window::SessionController*> controller,
not_null<Ui::ScrollArea*> scroll) const = 0; not_null<Ui::ScrollArea*> scroll,
rpl::producer<Container> containerValue) const = 0;
[[nodiscard]] virtual bool hasCustomTopBar() const { [[nodiscard]] virtual bool hasCustomTopBar() const {
return false; return false;
} }
@ -45,7 +51,8 @@ struct SectionFactory : AbstractSectionFactory {
object_ptr<AbstractSection> create( object_ptr<AbstractSection> create(
not_null<QWidget*> parent, not_null<QWidget*> parent,
not_null<Window::SessionController*> controller, not_null<Window::SessionController*> controller,
not_null<Ui::ScrollArea*> scroll not_null<Ui::ScrollArea*> scroll,
rpl::producer<Container> containerValue
) const final override { ) const final override {
return object_ptr<SectionType>(parent, controller); return object_ptr<SectionType>(parent, controller);
} }

View file

@ -44,7 +44,8 @@ struct Factory : AbstractSectionFactory {
object_ptr<AbstractSection> create( object_ptr<AbstractSection> create(
not_null<QWidget*> parent, not_null<QWidget*> parent,
not_null<Window::SessionController*> controller, not_null<Window::SessionController*> controller,
not_null<Ui::ScrollArea*> scroll not_null<Ui::ScrollArea*> scroll,
rpl::producer<Container> containerValue
) const final override { ) const final override {
return object_ptr<NotificationsType>(parent, controller, type); return object_ptr<NotificationsType>(parent, controller, type);
} }

View file

@ -1268,7 +1268,8 @@ struct SectionFactory<Premium> : AbstractSectionFactory {
object_ptr<AbstractSection> create( object_ptr<AbstractSection> create(
not_null<QWidget*> parent, not_null<QWidget*> parent,
not_null<Window::SessionController*> controller, not_null<Window::SessionController*> controller,
not_null<Ui::ScrollArea*> scroll not_null<Ui::ScrollArea*> scroll,
rpl::producer<Container> containerValue
) const final override { ) const final override {
return object_ptr<Premium>(parent, controller); return object_ptr<Premium>(parent, controller);
} }

View file

@ -1044,6 +1044,13 @@ premiumRequiredWidth: 186px;
premiumRequiredIcon: icon{{ "chat/large_lockedchat", msgServiceFg }}; premiumRequiredIcon: icon{{ "chat/large_lockedchat", msgServiceFg }};
premiumRequiredCircle: 60px; premiumRequiredCircle: 60px;
repliesEmptyIcon: icon{{ "chat/large_quickreply", msgServiceFg }};
greetingEmptyIcon: icon{{ "chat/large_greeting", msgServiceFg }};
awayEmptyIcon: icon{{ "chat/large_away", msgServiceFg }};
repliesEmptyWidth: 264px;
repliesEmptySkip: 16px;
repliesEmptyPadding: margins(10px, 20px, 10px, 16px);
boostMessageIcon: icon {{ "stories/boost_mini", windowFg }}; boostMessageIcon: icon {{ "stories/boost_mini", windowFg }};
boostMessageIconPadding: margins(0px, 2px, 0px, 0px); boostMessageIconPadding: margins(0px, 2px, 0px, 0px);
boostsMessageIcon: icon {{ "stories/boosts_mini", windowFg }}; boostsMessageIcon: icon {{ "stories/boosts_mini", windowFg }};