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.0 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(
this,
controller->parentController(),
scroll());
scroll(),
controller->wrapValue(
) | rpl::map([](Wrap wrap) { return (wrap == Wrap::Layer)
? ::Settings::Container::Layer
: ::Settings::Container::Section; }));
if (inner->hasFlexibleTopBar()) {
auto filler = setInnerWidget(object_ptr<Ui::RpWidget>(this));
filler->resize(1, 1);

View File

@ -96,9 +96,8 @@ void QuickReplies::setupContent(
Box(EditShortcutNameBox, QString(), crl::guard(this, submit)));
});
Ui::AddSkip(content);
Ui::AddDivider(content);
Ui::AddSkip(content);
const auto dividerWrap = content->add(
object_ptr<Ui::VerticalLayout>(content));
const auto inner = content->add(
object_ptr<Ui::VerticalLayout>(content));
@ -133,6 +132,18 @@ void QuickReplies::setupContent(
while (old--) {
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());
Ui::ResizeFitChild(this, content);
@ -170,6 +181,7 @@ void EditShortcutNameBox(
box->setFocusCallback([=] {
field->setFocusFast();
});
field->selectAll();
const auto callback = [=] {
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_empty_list_bubble.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/history.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/widgets/menu/menu_add_action_callback.h"
#include "ui/widgets/scroll_area.h"
#include "ui/painter.h"
#include "window/themes/window_theme.h"
#include "window/section_widget.h"
#include "window/window_session_controller.h"
@ -74,6 +76,7 @@ public:
QWidget *parent,
not_null<Window::SessionController*> controller,
not_null<Ui::ScrollArea*> scroll,
rpl::producer<Container> containerValue,
BusinessShortcutId shortcutId);
~ShortcutMessages();
@ -97,7 +100,7 @@ public:
QRect clip) override;
private:
void outerResized(QSize outer);
void outerResized();
void updateComposeControlsPosition();
// ListDelegate interface.
@ -247,6 +250,7 @@ private:
const Window::SectionShow &params);
void showAtEnd();
void finishSending();
void refreshEmptyText();
const not_null<Window::SessionController*> _controller;
const not_null<Main::Session*> _session;
@ -254,6 +258,7 @@ private:
const not_null<History*> _history;
rpl::variable<BusinessShortcutId> _shortcutId;
rpl::variable<QString> _shortcut;
rpl::variable<Container> _container;
std::shared_ptr<Ui::ChatStyle> _style;
std::shared_ptr<Ui::ChatTheme> _theme;
QPointer<ListWidget> _inner;
@ -262,6 +267,14 @@ private:
rpl::event_stream<> _showBackRequests;
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
= Info::SelectedItems(Storage::SharedMediaType::kCount);
@ -283,22 +296,33 @@ struct Factory final : AbstractSectionFactory {
object_ptr<AbstractSection> create(
not_null<QWidget*> parent,
not_null<Window::SessionController*> controller,
not_null<Ui::ScrollArea*> scroll
not_null<Ui::ScrollArea*> scroll,
rpl::producer<Container> containerValue
) const final override {
return object_ptr<ShortcutMessages>(
parent,
controller,
scroll,
std::move(containerValue),
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(
QWidget *parent,
not_null<Window::SessionController*> controller,
not_null<Ui::ScrollArea*> scroll,
rpl::producer<Container> containerValue,
BusinessShortcutId shortcutId)
: AbstractSection(parent)
, _controller(controller)
@ -308,6 +332,7 @@ ShortcutMessages::ShortcutMessages(
, _shortcutId(shortcutId)
, _shortcut(
_session->data().shortcutMessages().lookupShortcut(shortcutId).name)
, _container(std::move(containerValue))
, _cornerButtons(
_scroll,
controller->chatStyle(),
@ -345,8 +370,8 @@ ShortcutMessages::ShortcutMessages(
_scroll->sizeValue() | rpl::filter([](QSize size) {
return !size.isEmpty();
}) | rpl::start_with_next([=](QSize size) {
outerResized(size);
}) | rpl::start_with_next([=] {
outerResized();
}, lifetime());
_scroll->scrolls(
@ -354,6 +379,11 @@ ShortcutMessages::ShortcutMessages(
processScroll();
}, lifetime());
_shortcut.value() | rpl::start_with_next([=] {
refreshEmptyText();
_inner->update();
}, lifetime());
_inner->editMessageRequested(
) | rpl::start_with_next([=](auto fullId) {
if (const auto item = _session->data().message(fullId)) {
@ -364,17 +394,6 @@ ShortcutMessages::ShortcutMessages(
}
}, _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) {
resize(width(), height);
}, lifetime());
@ -382,15 +401,62 @@ ShortcutMessages::ShortcutMessages(
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) {
return std::make_shared<Factory>(shortcutId);
}
rpl::producer<QString> ShortcutMessages::title() {
return _shortcut.value() | rpl::map([=](const QString &shortcut) {
return (shortcut == u"away"_q)
return IsAway(shortcut)
? tr::lng_away_title()
: (shortcut == u"hello"_q)
: IsGreeting(shortcut)
? tr::lng_greeting_title()
: rpl::single('/' + shortcut);
}) | rpl::flatten_latest();
@ -497,22 +563,36 @@ bool ShortcutMessages::paintOuter(
return true;
}
void ShortcutMessages::outerResized(QSize outer) {
const auto contentWidth = outer.width();
void ShortcutMessages::outerResized() {
const auto outer = _scroll->size();
if (!_inOuterResize.isEmpty()) {
_pendingOuterResize = (_inOuterResize != outer)
? outer
: QSize();
return;
}
_inOuterResize = outer;
const auto newScrollTop = _scroll->isHidden()
? std::nullopt
: _scroll->scrollTop()
? base::make_optional(_scroll->scrollTop())
: 0;
_skipScrollEvent = true;
_inner->resizeToWidth(contentWidth, st::boxWidth);
_skipScrollEvent = false;
do {
const auto newScrollTop = _scroll->isHidden()
? std::nullopt
: _scroll->scrollTop()
? base::make_optional(_scroll->scrollTop())
: 0;
_skipScrollEvent = true;
const auto minHeight = (_container.current() == Container::Layer)
? st::boxWidth
: _inOuterResize.height();
_inner->resizeToWidth(_inOuterResize.width(), minHeight);
_skipScrollEvent = false;
if (!_scroll->isHidden()) {
if (newScrollTop) {
if (!_scroll->isHidden() && newScrollTop) {
_scroll->scrollToY(*newScrollTop);
}
_inOuterResize = base::take(_pendingOuterResize);
} while (!_inOuterResize.isEmpty());
if (!_scroll->isHidden()) {
updateInnerVisibleArea();
}
updateComposeControlsPosition();
@ -896,8 +976,36 @@ void ShortcutMessages::listOpenDocument(
}
void ShortcutMessages::listPaintEmpty(
Painter &p,
const Ui::ChatPaintContext &context) {
Painter &p,
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(

View File

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

View File

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

View File

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

View File

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

View File

@ -1044,6 +1044,13 @@ premiumRequiredWidth: 186px;
premiumRequiredIcon: icon{{ "chat/large_lockedchat", msgServiceFg }};
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 }};
boostMessageIconPadding: margins(0px, 2px, 0px, 0px);
boostsMessageIcon: icon {{ "stories/boosts_mini", windowFg }};