mirror of
https://github.com/AyuGram/AyuGramDesktop.git
synced 2025-04-15 21:57:10 +02:00
Support nice empty topic view.
This commit is contained in:
parent
99564d3d44
commit
c8ed8e0e5f
18 changed files with 418 additions and 237 deletions
|
@ -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.";
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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 };
|
||||
|
|
Loading…
Add table
Reference in a new issue