Support nice empty topic view.

This commit is contained in:
John Preston 2022-10-25 20:40:26 +04:00
parent 99564d3d44
commit c8ed8e0e5f
18 changed files with 418 additions and 237 deletions

View file

@ -1509,7 +1509,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_action_gift_received" = "{user} sent you a gift for {cost}";
"lng_action_gift_received_me" = "You sent to {user} a gift for {cost}";
"lng_action_topic_created_inside" = "Topic created";
"lng_action_topic_closed_inside" = "Topics closed";
"lng_action_topic_closed_inside" = "Topic closed";
"lng_action_topic_reopened_inside" = "Topic reopened";
"lng_action_topic_created" = "{topic} — was created";
"lng_action_topic_closed" = "{topic} — was closed";
@ -3536,9 +3536,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_forum_topic_delete" = "Delete";
"lng_forum_topic_delete_sure" = "Are you sure you want to delete this topic?";
"lng_forum_topic_created_title_my" = "Almost done!";
"lng_forum_topic_created_body_my" = "Send the first message to start this topic.";
"lng_forum_topic_created_body_my" = "Send the first message\nto start this topic.";
"lng_forum_topic_created_title" = "Topic started!";
"lng_forum_topic_created_body" = "Send a message to open the discussion";
"lng_forum_topic_created_body" = "Send a message to open\nthe discussion.";
"lng_forum_topics_switch" = "Topics";
"lng_forum_topics_not_enough#one" = "Only groups with more than **{count} member** can have topics enabled.";
"lng_forum_topics_not_enough#other" = "Only groups with more than **{count} members** can have topics enabled.";

View file

@ -2016,6 +2016,10 @@ void ApiWrap::saveCurrentDraftToCloud() {
for (const auto &controller : _session->windows()) {
controller->materializeLocalDrafts();
if (const auto thread = controller->activeChatCurrent().thread()) {
const auto topic = thread->asTopic();
if (topic && topic->creating()) {
continue;
}
const auto history = thread->owningHistory();
_session->local().writeDrafts(history);

View file

@ -468,7 +468,6 @@ void EditForumTopicBox(
state->iconId = (iconId != kDefaultIconId) ? iconId : 0;
}, box->lifetime());
const auto requestId = std::make_shared<mtpRequestId>();
const auto create = [=] {
const auto channel = forum->peer->asChannel();
if (!channel || !channel->isForum()) {
@ -505,9 +504,11 @@ void EditForumTopicBox(
topic->applyTitle(title->getLastText().trimmed());
topic->applyColorId(state->defaultIcon.current().colorId);
topic->applyIconId(state->iconId.current());
box->closeBox();
} else {
using Flag = MTPchannels_EditForumTopic::Flag;
const auto api = &forum->session().api();
const auto weak = Ui::MakeWeak(box.get());
state->requestId = api->request(MTPchannels_EditForumTopic(
MTP_flags(Flag::f_title | Flag::f_icon_emoji_id),
topic->channel()->inputChannel,
@ -517,12 +518,16 @@ void EditForumTopicBox(
MTPBool() // closed
)).done([=](const MTPUpdates &result) {
api->applyUpdates(result);
box->closeBox();
if (const auto strong = weak.data()) {
strong->closeBox();
}
}).fail([=](const MTP::Error &error) {
if (error.type() == u"TOPIC_NOT_MODIFIED") {
box->closeBox();
} else {
state->requestId = -1;
if (const auto strong = weak.data()) {
if (error.type() == u"TOPIC_NOT_MODIFIED") {
strong->closeBox();
} else {
state->requestId = -1;
}
}
}).send();
}

View file

@ -589,7 +589,7 @@ bool AddReplyToMessageAction(
const auto peer = item ? item->history()->peer.get() : nullptr;
if (!item
|| !item->isRegular()
|| (topic ? topic->canWrite() : !peer->canWrite())
|| (topic ? !topic->canWrite() : !peer->canWrite())
|| (context != Context::History && context != Context::Replies)) {
return false;
}

View file

@ -702,7 +702,7 @@ auto Element::contextDependentServiceText() -> TextWithLinks {
const QString &title,
std::optional<DocumentId> iconId) {
auto result = TextWithEntities{ title };
auto full = iconId
auto full = (iconId && *iconId)
? wrapIcon(*iconId).append(' ').append(std::move(result))
: result;
return Ui::Text::Link(std::move(full), topicUrl);

View file

@ -1908,167 +1908,170 @@ void ListWidget::paintEvent(QPaintEvent *e) {
return this->itemTop(elem) < bottom;
});
if (from != end(_items)) {
_reactionsManager->startEffectsCollection();
auto context = preparePaintContext(clip);
if (from == end(_items)) {
_delegate->listPaintEmpty(p, context);
return;
}
_reactionsManager->startEffectsCollection();
const auto session = &controller()->session();
auto top = itemTop(from->get());
auto context = preparePaintContext(clip).translated(0, -top);
p.translate(0, top);
const auto &sendingAnimation = _controller->sendingAnimation();
for (auto i = from; i != to; ++i) {
const auto view = *i;
const auto item = view->data();
const auto height = view->height();
if (!sendingAnimation.hasAnimatedMessage(item)) {
context.reactionInfo
= _reactionsManager->currentReactionPaintInfo();
context.outbg = view->hasOutLayout();
context.selection = itemRenderSelection(view);
view->draw(p, context);
}
const auto isSponsored = item->isSponsored();
const auto isUnread = _delegate->listElementShownUnread(view)
&& item->isRegular();
const auto withReaction = item->hasUnreadReaction();
const auto yShown = [&](int y) {
return (_visibleBottom >= y && _visibleTop <= y);
};
const auto markShown = isSponsored
? view->markSponsoredViewed(_visibleBottom - top)
: withReaction
? yShown(top + context.reactionInfo->position.y())
: isUnread
? yShown(top + height)
: yShown(top + height / 2);
if (markShown) {
if (isSponsored) {
session->data().sponsoredMessages().view(
item->fullId());
} else if (isUnread) {
readTill = item;
}
if (item->hasViews()) {
session->api().views().scheduleIncrement(item);
}
if (withReaction) {
readContents.insert(item);
} else if (item->isUnreadMention()
&& !item->isUnreadMedia()) {
readContents.insert(item);
_highlighter.enqueue(view);
}
}
session->data().reactions().poll(item, context.now);
if (item->hasExtendedMediaPreview()) {
session->api().views().pollExtendedMedia(item);
}
_reactionsManager->recordCurrentReactionEffect(
item->fullId(),
QPoint(0, top));
top += height;
context.translate(0, -height);
p.translate(0, height);
const auto session = &controller()->session();
auto top = itemTop(from->get());
context = context.translated(0, -top);
p.translate(0, top);
const auto &sendingAnimation = _controller->sendingAnimation();
for (auto i = from; i != to; ++i) {
const auto view = *i;
const auto item = view->data();
const auto height = view->height();
if (!sendingAnimation.hasAnimatedMessage(item)) {
context.reactionInfo
= _reactionsManager->currentReactionPaintInfo();
context.outbg = view->hasOutLayout();
context.selection = itemRenderSelection(view);
view->draw(p, context);
}
context.translate(0, top);
p.translate(0, -top);
enumerateUserpics([&](not_null<Element*> view, int userpicTop) {
// stop the enumeration if the userpic is below the painted rect
if (userpicTop >= clip.top() + clip.height()) {
return false;
const auto isSponsored = item->isSponsored();
const auto isUnread = _delegate->listElementShownUnread(view)
&& item->isRegular();
const auto withReaction = item->hasUnreadReaction();
const auto yShown = [&](int y) {
return (_visibleBottom >= y && _visibleTop <= y);
};
const auto markShown = isSponsored
? view->markSponsoredViewed(_visibleBottom - top)
: withReaction
? yShown(top + context.reactionInfo->position.y())
: isUnread
? yShown(top + height)
: yShown(top + height / 2);
if (markShown) {
if (isSponsored) {
session->data().sponsoredMessages().view(
item->fullId());
} else if (isUnread) {
readTill = item;
}
if (item->hasViews()) {
session->api().views().scheduleIncrement(item);
}
if (withReaction) {
readContents.insert(item);
} else if (item->isUnreadMention()
&& !item->isUnreadMedia()) {
readContents.insert(item);
_highlighter.enqueue(view);
}
}
session->data().reactions().poll(item, context.now);
if (item->hasExtendedMediaPreview()) {
session->api().views().pollExtendedMedia(item);
}
_reactionsManager->recordCurrentReactionEffect(
item->fullId(),
QPoint(0, top));
top += height;
context.translate(0, -height);
p.translate(0, height);
}
context.translate(0, top);
p.translate(0, -top);
// paint the userpic if it intersects the painted rect
if (userpicTop + st::msgPhotoSize > clip.top()) {
if (const auto from = view->data()->displayFrom()) {
from->paintUserpicLeft(
enumerateUserpics([&](not_null<Element*> view, int userpicTop) {
// stop the enumeration if the userpic is below the painted rect
if (userpicTop >= clip.top() + clip.height()) {
return false;
}
// paint the userpic if it intersects the painted rect
if (userpicTop + st::msgPhotoSize > clip.top()) {
if (const auto from = view->data()->displayFrom()) {
from->paintUserpicLeft(
p,
_userpics[from],
st::historyPhotoLeft,
userpicTop,
view->width(),
st::msgPhotoSize);
} else if (const auto info = view->data()->hiddenSenderInfo()) {
if (info->customUserpic.empty()) {
info->emptyUserpic.paint(
p,
_userpics[from],
st::historyPhotoLeft,
userpicTop,
view->width(),
st::msgPhotoSize);
} else if (const auto info = view->data()->hiddenSenderInfo()) {
if (info->customUserpic.empty()) {
info->emptyUserpic.paint(
p,
st::historyPhotoLeft,
userpicTop,
view->width(),
st::msgPhotoSize);
} else {
const auto painted = info->paintCustomUserpic(
p,
st::historyPhotoLeft,
userpicTop,
view->width(),
st::msgPhotoSize);
if (!painted) {
const auto itemId = view->data()->fullId();
auto &v = _sponsoredUserpics[itemId.msg];
if (!info->customUserpic.isCurrentView(v)) {
v = info->customUserpic.createView();
info->customUserpic.load(session, itemId);
}
} else {
const auto painted = info->paintCustomUserpic(
p,
st::historyPhotoLeft,
userpicTop,
view->width(),
st::msgPhotoSize);
if (!painted) {
const auto itemId = view->data()->fullId();
auto &v = _sponsoredUserpics[itemId.msg];
if (!info->customUserpic.isCurrentView(v)) {
v = info->customUserpic.createView();
info->customUserpic.load(session, itemId);
}
}
}
} else {
Unexpected("Corrupt forwarded information in message.");
}
}
return true;
});
auto dateHeight = st::msgServicePadding.bottom() + st::msgServiceFont->height + st::msgServicePadding.top();
auto scrollDateOpacity = _scrollDateOpacity.value(_scrollDateShown ? 1. : 0.);
enumerateDates([&](not_null<Element*> view, int itemtop, int dateTop) {
// stop the enumeration if the date is above the painted rect
if (dateTop + dateHeight <= clip.top()) {
return false;
}
const auto displayDate = view->displayDate();
auto dateInPlace = displayDate;
if (dateInPlace) {
const auto correctDateTop = itemtop + st::msgServiceMargin.top();
dateInPlace = (dateTop < correctDateTop + dateHeight);
}
//bool noFloatingDate = (item->date.date() == lastDate && displayDate);
//if (noFloatingDate) {
// if (itemtop < showFloatingBefore) {
// noFloatingDate = false;
// }
//}
// paint the date if it intersects the painted rect
if (dateTop < clip.top() + clip.height()) {
auto opacity = (dateInPlace/* || noFloatingDate*/) ? 1. : scrollDateOpacity;
if (opacity > 0.) {
p.setOpacity(opacity);
int dateY = /*noFloatingDate ? itemtop :*/ (dateTop - st::msgServiceMargin.top());
int width = view->width();
if (const auto date = view->Get<HistoryView::DateBadge>()) {
date->paint(p, context.st, dateY, width, _isChatWide);
} else {
Unexpected("Corrupt forwarded information in message.");
ServiceMessagePainter::PaintDate(
p,
context.st,
ItemDateText(
view->data(),
IsItemScheduledUntilOnline(view->data())),
dateY,
width,
_isChatWide);
}
}
return true;
});
}
return true;
});
auto dateHeight = st::msgServicePadding.bottom() + st::msgServiceFont->height + st::msgServicePadding.top();
auto scrollDateOpacity = _scrollDateOpacity.value(_scrollDateShown ? 1. : 0.);
enumerateDates([&](not_null<Element*> view, int itemtop, int dateTop) {
// stop the enumeration if the date is above the painted rect
if (dateTop + dateHeight <= clip.top()) {
return false;
}
const auto displayDate = view->displayDate();
auto dateInPlace = displayDate;
if (dateInPlace) {
const auto correctDateTop = itemtop + st::msgServiceMargin.top();
dateInPlace = (dateTop < correctDateTop + dateHeight);
}
//bool noFloatingDate = (item->date.date() == lastDate && displayDate);
//if (noFloatingDate) {
// if (itemtop < showFloatingBefore) {
// noFloatingDate = false;
// }
//}
// paint the date if it intersects the painted rect
if (dateTop < clip.top() + clip.height()) {
auto opacity = (dateInPlace/* || noFloatingDate*/) ? 1. : scrollDateOpacity;
if (opacity > 0.) {
p.setOpacity(opacity);
int dateY = /*noFloatingDate ? itemtop :*/ (dateTop - st::msgServiceMargin.top());
int width = view->width();
if (const auto date = view->Get<HistoryView::DateBadge>()) {
date->paint(p, context.st, dateY, width, _isChatWide);
} else {
ServiceMessagePainter::PaintDate(
p,
context.st,
ItemDateText(
view->data(),
IsItemScheduledUntilOnline(view->data())),
dateY,
width,
_isChatWide);
}
}
}
return true;
});
_reactionsManager->paint(p, context);
_emojiInteractions->paint(p);
}
_reactionsManager->paint(p, context);
_emojiInteractions->paint(p);
}
void ListWidget::maybeMarkReactionsRead(not_null<HistoryItem*> item) {

View file

@ -132,6 +132,9 @@ public:
not_null<DocumentData*> document,
FullMsgId context,
bool showInMediaView) = 0;
virtual void listPaintEmpty(
Painter &p,
const Ui::ChatPaintContext &context) = 0;
};
struct SelectionData {

View file

@ -603,6 +603,11 @@ void PinnedWidget::listOpenDocument(
controller()->openDocument(document, context, MsgId(), showInMediaView);
}
void PinnedWidget::listPaintEmpty(
Painter &p,
const Ui::ChatPaintContext &context) {
}
void PinnedWidget::confirmDeleteSelected() {
ConfirmDeleteSelectedItems(_inner);
}

View file

@ -119,6 +119,9 @@ public:
not_null<DocumentData*> document,
FullMsgId context,
bool showInMediaView) override;
void listPaintEmpty(
Painter &p,
const Ui::ChatPaintContext &context) override;
// CornerButtonsDelegate delegate.
void cornerButtonsShowAtPosition(

View file

@ -15,6 +15,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "history/view/history_view_sticker_toast.h"
#include "history/view/history_view_cursor_state.h"
#include "history/view/history_view_contact_status.h"
#include "history/view/history_view_service_message.h"
#include "history/history.h"
#include "history/history_drag_area.h"
#include "history/history_item_components.h"
@ -355,16 +356,17 @@ RepliesWidget::RepliesWidget(
}
RepliesWidget::~RepliesWidget() {
base::take(_sendAction);
session().api().saveCurrentDraftToCloud();
controller()->sendingAnimation().clear();
if (_topic && _topic->creating()) {
_emptyPainter = nullptr;
_topic->discard();
_topic = nullptr;
}
base::take(_sendAction);
_history->owner().sendActionManager().repliesPainterRemoved(
_history,
_rootId);
session().api().saveCurrentDraftToCloud();
controller()->sendingAnimation().clear();
}
void RepliesWidget::orderWidgets() {
@ -518,6 +520,11 @@ void RepliesWidget::setTopic(Data::ForumTopic *topic) {
}
subscribeToTopic();
}
if (_topic && emptyShown()) {
setupEmptyPainter();
} else {
_emptyPainter = nullptr;
}
}
HistoryItem *RepliesWidget::lookupRoot() const {
@ -1772,6 +1779,12 @@ void RepliesWidget::paintEvent(QPaintEvent *e) {
SectionWidget::PaintBackground(controller(), _theme.get(), this, bg);
}
bool RepliesWidget::emptyShown() const {
return _topic
&& (_inner->isEmpty()
|| (_topic->lastKnownServerMessageId() == _rootId));
}
void RepliesWidget::onScroll() {
if (_skipScrollEvent) {
return;
@ -2065,6 +2078,32 @@ void RepliesWidget::listOpenDocument(
controller()->openDocument(document, context, _rootId, showInMediaView);
}
void RepliesWidget::listPaintEmpty(
Painter &p,
const Ui::ChatPaintContext &context) {
if (!emptyShown()) {
return;
} else if (!_emptyPainter) {
setupEmptyPainter();
}
_emptyPainter->paint(p, context.st, width(), _scroll->height());
}
void RepliesWidget::setupEmptyPainter() {
Expects(_topic != nullptr);
_emptyPainter = std::make_unique<EmptyPainter>(_topic, [=] {
return controller()->isGifPausedAtLeastFor(
Window::GifPauseReason::Any);
}, [=] {
if (emptyShown()) {
update();
} else {
_emptyPainter = nullptr;
}
});
}
void RepliesWidget::confirmDeleteSelected() {
ConfirmDeleteSelectedItems(_inner);
}

View file

@ -66,6 +66,7 @@ class ComposeControls;
class SendActionPainter;
class StickerToast;
class TopicReopenBar;
class EmptyPainter;
class RepliesWidget final
: public Window::SectionWidget
@ -158,6 +159,9 @@ public:
not_null<DocumentData*> document,
FullMsgId context,
bool showInMediaView) override;
void listPaintEmpty(
Painter &p,
const Ui::ChatPaintContext &context) override;
// CornerButtonsDelegate delegate.
void cornerButtonsShowAtPosition(
@ -275,7 +279,9 @@ private:
Api::SendOptions options,
std::optional<MsgId> localMessageId);
void setupEmptyPainter();
void refreshJoinGroupButton();
[[nodiscard]] bool emptyShown() const;
[[nodiscard]] bool showSlowmodeError();
[[nodiscard]] std::optional<QString> writeRestriction() const;
@ -296,6 +302,7 @@ private:
std::unique_ptr<ComposeControls> _composeControls;
std::unique_ptr<Ui::FlatButton> _joinGroup;
std::unique_ptr<TopicReopenBar> _topicReopenBar;
std::unique_ptr<EmptyPainter> _emptyPainter;
bool _skipScrollEvent = false;
std::unique_ptr<Ui::PinnedBar> _rootView;

View file

@ -1282,6 +1282,11 @@ void ScheduledWidget::listOpenDocument(
controller()->openDocument(document, context, MsgId(), showInMediaView);
}
void ScheduledWidget::listPaintEmpty(
Painter &p,
const Ui::ChatPaintContext &context) {
}
void ScheduledWidget::confirmSendNowSelected() {
ConfirmSendNowSelectedItems(_inner);
}

View file

@ -142,6 +142,9 @@ public:
not_null<DocumentData*> document,
FullMsgId context,
bool showInMediaView) override;
void listPaintEmpty(
Painter &p,
const Ui::ChatPaintContext &context) override;
// CornerButtonsDelegate delegate.
void cornerButtonsShowAtPosition(

View file

@ -15,18 +15,28 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_abstract_structure.h"
#include "data/data_chat.h"
#include "data/data_channel.h"
#include "info/profile/info_profile_cover.h"
#include "ui/chat/chat_style.h"
#include "ui/text/text_options.h"
#include "ui/painter.h"
#include "ui/ui_utility.h"
#include "mainwidget.h"
#include "menu/menu_ttl_validator.h"
#include "data/data_forum_topic.h"
#include "lang/lang_keys.h"
#include "styles/style_chat.h"
#include "styles/style_info.h"
namespace HistoryView {
namespace {
TextParseOptions EmptyLineOptions = {
TextParseMultiline, // flags
4096, // maxw
1, // maxh
Qt::LayoutDirectionAuto, // lang-dependent
};
enum CircleMask {
NormalMask = 0x00,
InvertedMask = 0x01,
@ -181,7 +191,11 @@ bool NeedAboutGroup(not_null<History*> history) {
return false;
}
} // namepsace
void SetText(Ui::Text::String &text, const QString &content) {
text.setText(st::serviceTextStyle, content, EmptyLineOptions);
}
} // namespace
int WideChatWidth() {
return st::msgMaxWidth + 2 * st::msgPhotoSkip + 2 * st::msgMargin.left();
@ -657,6 +671,21 @@ EmptyPainter::EmptyPainter(not_null<History*> history)
}
}
EmptyPainter::EmptyPainter(
not_null<Data::ForumTopic*> topic,
Fn<bool()> paused,
Fn<void()> update)
: _history(topic->history())
, _topic(topic)
, _icon(
std::make_unique<Info::Profile::TopicIconView>(topic, paused, update))
, _header(st::msgMinWidth)
, _text(st::msgMinWidth) {
fillAboutTopic();
}
EmptyPainter::~EmptyPainter() = default;
void EmptyPainter::fillAboutGroup() {
const auto phrases = {
tr::lng_group_about1(tr::now),
@ -664,34 +693,38 @@ void EmptyPainter::fillAboutGroup() {
tr::lng_group_about3(tr::now),
tr::lng_group_about4(tr::now),
};
const auto setText = [](Ui::Text::String &text, const QString &content) {
text.setText(
st::serviceTextStyle,
content,
Ui::NameTextOptions());
};
setText(_header, tr::lng_group_about_header(tr::now));
setText(_text, tr::lng_group_about_text(tr::now));
SetText(_header, tr::lng_group_about_header(tr::now));
SetText(_text, tr::lng_group_about_text(tr::now));
for (const auto &text : phrases) {
_phrases.emplace_back(st::msgMinWidth);
setText(_phrases.back(), text);
SetText(_phrases.back(), text);
}
}
void EmptyPainter::fillAboutTopic() {
SetText(_header, _topic->my()
? tr::lng_forum_topic_created_title_my(tr::now)
: tr::lng_forum_topic_created_title(tr::now));
SetText(_text, _topic->my()
? tr::lng_forum_topic_created_body_my(tr::now)
: tr::lng_forum_topic_created_body(tr::now));
}
void EmptyPainter::paint(
Painter &p,
not_null<const Ui::ChatStyle*> st,
int width,
int height) {
if (_phrases.empty()) {
if (_phrases.empty() && _text.isEmpty()) {
return;
}
constexpr auto kMaxTextLines = 3;
const auto maxPhraseWidth = ranges::max_element(
_phrases,
ranges::less(),
&Ui::Text::String::maxWidth
)->maxWidth();
const auto maxPhraseWidth = _phrases.empty()
? 0
: ranges::max_element(
_phrases,
ranges::less(),
&Ui::Text::String::maxWidth)->maxWidth();
const auto &font = st::serviceTextStyle.font;
const auto maxBubbleWidth = width - 2 * st::historyGroupAboutMargin;
@ -708,13 +741,17 @@ void EmptyPainter::paint(
text.countHeight(innerWidth),
kMaxTextLines * font->height);
};
const auto iconHeight = _icon
? st::infoTopicCover.photo.size.height()
: 0;
const auto bubbleHeight = padding.top()
+ (_icon ? (iconHeight + st::historyGroupAboutHeaderSkip) : 0)
+ textHeight(_header)
+ st::historyGroupAboutHeaderSkip
+ textHeight(_text)
+ st::historyGroupAboutTextSkip
+ ranges::accumulate(_phrases, 0, ranges::plus(), textHeight)
+ st::historyGroupAboutSkip * int(_phrases.size() - 1)
+ st::historyGroupAboutSkip * std::max(int(_phrases.size()) - 1, 0)
+ padding.bottom();
const auto bubbleLeft = (width - bubbleWidth) / 2;
const auto bubbleTop = (height - bubbleHeight) / 2;
@ -731,6 +768,13 @@ void EmptyPainter::paint(
const auto left = bubbleLeft + padding.left();
auto top = bubbleTop + padding.top();
if (_icon) {
_icon->paintInRect(
p,
QRect(bubbleLeft, top, bubbleWidth, iconHeight));
top += iconHeight + st::historyGroupAboutHeaderSkip;
}
_header.drawElided(
p,
left,
@ -745,7 +789,8 @@ void EmptyPainter::paint(
left,
top,
innerWidth,
kMaxTextLines);
kMaxTextLines,
_topic ? style::al_top : style::al_topleft);
top += textHeight(_text) + st::historyGroupAboutTextSkip;
for (const auto &text : _phrases) {

View file

@ -16,6 +16,14 @@ class ChatStyle;
struct CornersPixmaps;
} // namespace Ui
namespace Data {
class ForumTopic;
} // namespace Data
namespace Info::Profile {
class TopicIconView;
} // namespace Info::Profile
namespace HistoryView {
class Service final : public Element {
@ -116,6 +124,11 @@ private:
class EmptyPainter {
public:
explicit EmptyPainter(not_null<History*> history);
EmptyPainter(
not_null<Data::ForumTopic*> topic,
Fn<bool()> paused,
Fn<void()> update);
~EmptyPainter();
void paint(
Painter &p,
@ -125,12 +138,17 @@ public:
private:
void fillAboutGroup();
void fillAboutTopic();
not_null<History*> _history;
Data::ForumTopic *_topic = nullptr;
std::unique_ptr<Info::Profile::TopicIconView> _icon;
Ui::Text::String _header;
Ui::Text::String _text;
std::vector<Ui::Text::String> _phrases;
rpl::lifetime _lifetime;
};
} // namespace HistoryView

View file

@ -123,7 +123,6 @@ StaticStickerPlayer::StaticStickerPlayer(
: _frame(Images::Read({
.path = location.name(),
.content = data,
.forceOpaque = true,
}).image) {
if (!_frame.isNull()) {
size = _frame.size().scaled(size, Qt::KeepAspectRatio);

View file

@ -118,54 +118,59 @@ Cover::Cover(
}
TopicIconView::TopicIconView(
QWidget *parent,
not_null<Window::SessionController*> controller,
not_null<Data::ForumTopic*> topic)
: AbstractButton(parent) {
setup(topic, [=] {
return controller->isGifPausedAtLeastFor(
Window::GifPauseReason::Layer);
});
not_null<Data::ForumTopic*> topic,
Fn<bool()> paused,
Fn<void()> update)
: _topic(topic)
, _paused(std::move(paused))
, _update(std::move(update)) {
setup(topic);
}
void TopicIconView::setup(
not_null<Data::ForumTopic*> topic,
Fn<bool()> paused) {
void TopicIconView::paintInRect(QPainter &p, QRect rect) {
const auto paint = [&](const QImage &image) {
const auto size = image.size() / image.devicePixelRatio();
p.drawImage(
rect.x() + (rect.width() - size.width()) / 2,
rect.y() + (rect.height() - size.height()) / 2,
image);
};
if (_player && _player->ready()) {
paint(_player->frame(
st::infoTopicCover.photo.size,
QColor(0, 0, 0, 0),
false,
crl::now(),
_paused()).image);
_player->markFrameShown();
} else if (!_topic->iconId() && !_image.isNull()) {
paint(_image);
}
}
void TopicIconView::setup(not_null<Data::ForumTopic*> topic) {
setupPlayer(topic);
setupImage(topic);
resize(st::infoTopicCover.photo.size);
paintRequest(
) | rpl::start_with_next([=] {
auto p = QPainter(this);
const auto paint = [&](const QImage &image) {
const auto size = image.size() / image.devicePixelRatio();
p.drawImage(
(st::infoTopicCover.photo.size.width() - size.width()) / 2,
(st::infoTopicCover.photo.size.height() - size.height()) / 2,
image);
};
if (_player && _player->ready()) {
paint(_player->frame(
st::infoTopicCover.photo.size,
QColor(0, 0, 0, 0),
false,
crl::now(),
paused()).image);
_player->markFrameShown();
} else if (!topic->iconId() && !_image.isNull()) {
paint(_image);
}
}, lifetime());
}
void TopicIconView::setupPlayer(not_null<Data::ForumTopic*> topic) {
IconIdValue(
topic
) | rpl::map([=](DocumentId id) {
return topic->owner().customEmojiManager().resolve(id);
) | rpl::map([=](DocumentId id) -> rpl::producer<DocumentData*> {
if (!id) {
return rpl::single((DocumentData*)nullptr);
}
return topic->owner().customEmojiManager().resolve(
id
) | rpl::map([=](not_null<DocumentData*> document) {
return document.get();
});
}) | rpl::flatten_latest(
) | rpl::map([=](not_null<DocumentData*> document) {
) | rpl::map([=](DocumentData *document)
-> rpl::producer<std::shared_ptr<StickerPlayer>> {
if (!document) {
return rpl::single(std::shared_ptr<StickerPlayer>());
}
const auto media = document->createMediaView();
media->checkStickerLarge();
media->goodThumbnailWanted();
@ -195,13 +200,16 @@ void TopicIconView::setupPlayer(not_null<Data::ForumTopic*> topic) {
media->bytes(),
st::infoTopicCover.photo.size);
}
result->setRepaintCallback([=] { update(); });
result->setRepaintCallback(_update);
return result;
});
}) | rpl::flatten_latest(
) | rpl::start_with_next([=](std::shared_ptr<StickerPlayer> player) {
_player = std::move(player);
}, lifetime());
if (!_player) {
_update();
}
}, _lifetime);
}
void TopicIconView::setupImage(not_null<Data::ForumTopic*> topic) {
@ -213,7 +221,25 @@ void TopicIconView::setupImage(not_null<Data::ForumTopic*> topic) {
return ForumTopicIconFrame(colorId, title, st::infoForumTopicIcon);
}) | rpl::start_with_next([=](QImage &&image) {
_image = std::move(image);
update();
_update();
}, _lifetime);
}
TopicIconButton::TopicIconButton(
QWidget *parent,
not_null<Window::SessionController*> controller,
not_null<Data::ForumTopic*> topic)
: AbstractButton(parent)
, _view(
topic,
[=] { return controller->isGifPausedAtLeastFor(
Window::GifPauseReason::Layer); },
[=] { update(); }) {
resize(st::infoTopicCover.photo.size);
paintRequest(
) | rpl::start_with_next([=] {
auto p = QPainter(this);
_view.paintInRect(p, rect());
}, lifetime());
}
@ -248,8 +274,8 @@ Cover::Cover(
_peer,
Ui::UserpicButton::Role::OpenPhoto,
_st.photo))
, _iconView(topic
? object_ptr<TopicIconView>(this, controller, topic)
, _iconButton(topic
? object_ptr<TopicIconButton>(this, controller, topic)
: nullptr)
, _name(this, _st.name)
, _status(this, _st.status)
@ -285,7 +311,7 @@ Cover::Cover(
_userpic->takeResultImage());
}, _userpic->lifetime());
} else if (topic->canEdit()) {
_iconView->setClickedCallback([=] {
_iconButton->setClickedCallback([=] {
_controller->show(Box(
EditForumTopicBox,
_controller,
@ -293,7 +319,7 @@ Cover::Cover(
topic->rootId()));
});
} else {
_iconView->setAttribute(Qt::WA_TransparentForMouseEvents);
_iconButton->setAttribute(Qt::WA_TransparentForMouseEvents);
}
}
@ -303,7 +329,7 @@ void Cover::setupChildGeometry() {
if (_userpic) {
_userpic->moveToLeft(_st.photoLeft, _st.photoTop, newWidth);
} else {
_iconView->moveToLeft(_st.photoLeft, _st.photoTop, newWidth);
_iconButton->moveToLeft(_st.photoLeft, _st.photoTop, newWidth);
}
refreshNameGeometry(newWidth);
refreshStatusGeometry(newWidth);

View file

@ -44,24 +44,40 @@ namespace Info::Profile {
class EmojiStatusPanel;
class Badge;
class TopicIconView final : public Ui::AbstractButton {
class TopicIconView final {
public:
TopicIconView(
not_null<Data::ForumTopic*> topic,
Fn<bool()> paused,
Fn<void()> update);
void paintInRect(QPainter &p, QRect rect);
private:
using StickerPlayer = HistoryView::StickerPlayer;
void setup(not_null<Data::ForumTopic*> topic);
void setupPlayer(not_null<Data::ForumTopic*> topic);
void setupImage(not_null<Data::ForumTopic*> topic);
const not_null<Data::ForumTopic*> _topic;
Fn<bool()> _paused;
Fn<void()> _update;
std::shared_ptr<StickerPlayer> _player;
QImage _image;
rpl::lifetime _lifetime;
};
class TopicIconButton final : public Ui::AbstractButton {
public:
TopicIconButton(
QWidget *parent,
not_null<Window::SessionController*> controller,
not_null<Data::ForumTopic*> topic);
private:
using StickerPlayer = HistoryView::StickerPlayer;
void setup(
not_null<Data::ForumTopic*> topic,
Fn<bool()> paused);
void setupPlayer(not_null<Data::ForumTopic*> topic);
void setupImage(not_null<Data::ForumTopic*> topic);
std::shared_ptr<StickerPlayer> _player;
QImage _image;
TopicIconView _view;
};
@ -112,7 +128,7 @@ private:
int _onlineCount = 0;
object_ptr<Ui::UserpicButton> _userpic;
object_ptr<TopicIconView> _iconView;
object_ptr<TopicIconButton> _iconButton;
object_ptr<Ui::FlatLabel> _name = { nullptr };
object_ptr<Ui::FlatLabel> _status = { nullptr };
//object_ptr<CoverDropArea> _dropArea = { nullptr };