Add monoforum sender bar divider.

This commit is contained in:
John Preston 2025-05-12 12:41:00 +04:00
parent c6d43a802c
commit 43b4499125
27 changed files with 388 additions and 90 deletions

View file

@ -6091,6 +6091,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_forum_messages#other" = "{count} messages"; "lng_forum_messages#other" = "{count} messages";
"lng_forum_show_topics_list" = "Show Topics List"; "lng_forum_show_topics_list" = "Show Topics List";
"lng_monoforum_choose_to_reply" = "Choose a message to reply.";
"lng_request_peer_requirements" = "Requirements"; "lng_request_peer_requirements" = "Requirements";
"lng_request_peer_rights" = "You must have these admin rights: {rights}."; "lng_request_peer_rights" = "You must have these admin rights: {rights}.";
"lng_request_peer_rights_and" = "{rights} and {last}"; "lng_request_peer_rights_and" = "{rights} and {last}";

View file

@ -2936,7 +2936,9 @@ bool EditPeerInfoBox::Available(not_null<PeerData*> peer) {
// canViewAdmins() is removed, because in supergroups it is // canViewAdmins() is removed, because in supergroups it is
// always true and in channels it is equal to canViewBanned(). // always true and in channels it is equal to canViewBanned().
if (channel->isMonoforum()) {
return false;
}
return false return false
//|| channel->canViewMembers() //|| channel->canViewMembers()
//|| channel->canViewAdmins() //|| channel->canViewAdmins()

View file

@ -341,12 +341,6 @@ ChannelData *ChannelData::monoforumLink() const {
return _monoforumLink; return _monoforumLink;
} }
bool ChannelData::requiresMonoforumPeer() const {
return isMonoforum()
&& _monoforumLink
&& (_monoforumLink->amCreator() || _monoforumLink->hasAdminRights());
}
void ChannelData::setMembersCount(int newMembersCount) { void ChannelData::setMembersCount(int newMembersCount) {
if (_membersCount != newMembersCount) { if (_membersCount != newMembersCount) {
if (isMegagroup() if (isMegagroup()

View file

@ -429,7 +429,6 @@ public:
void setMonoforumLink(ChannelData *link); void setMonoforumLink(ChannelData *link);
[[nodiscard]] ChannelData *monoforumLink() const; [[nodiscard]] ChannelData *monoforumLink() const;
[[nodiscard]] bool requiresMonoforumPeer() const;
void ptsInit(int32 pts) { void ptsInit(int32 pts) {
_ptsWaiter.init(pts); _ptsWaiter.init(pts);

View file

@ -190,18 +190,21 @@ struct SendError {
struct Args { struct Args {
QString text; QString text;
int boostsToLift = 0; int boostsToLift = 0;
bool monoforumAdmin = false;
bool premiumToLift = false; bool premiumToLift = false;
bool frozen = false; bool frozen = false;
}; };
SendError(Args &&args) SendError(Args &&args)
: text(std::move(args.text)) : text(std::move(args.text))
, boostsToLift(args.boostsToLift) , boostsToLift(args.boostsToLift)
, monoforumAdmin(args.monoforumAdmin)
, premiumToLift(args.premiumToLift) , premiumToLift(args.premiumToLift)
, frozen(args.frozen) { , frozen(args.frozen) {
} }
QString text; QString text;
int boostsToLift = 0; int boostsToLift = 0;
bool monoforumAdmin = false;
bool premiumToLift = false; bool premiumToLift = false;
bool frozen = false; bool frozen = false;
@ -210,7 +213,7 @@ struct SendError {
} }
explicit operator bool() const { explicit operator bool() const {
return !text.isEmpty(); return monoforumAdmin || !text.isEmpty();
} }
[[nodiscard]] bool has_value() const { [[nodiscard]] bool has_value() const {
return !text.isEmpty(); return !text.isEmpty();

View file

@ -65,8 +65,7 @@ MTPInputReplyTo ReplyToForMTP(
: replyTo.monoforumPeerId : replyTo.monoforumPeerId
? history->owner().peer(replyTo.monoforumPeerId).get() ? history->owner().peer(replyTo.monoforumPeerId).get()
: history->session().user().get(); : history->session().user().get();
const auto replyToMonoforumPeer = (history->peer->isChannel() const auto replyToMonoforumPeer = history->peer->amMonoforumAdmin()
&& history->peer->asChannel()->requiresMonoforumPeer())
? possibleMonoforumPeer ? possibleMonoforumPeer
: nullptr; : nullptr;
const auto external = replyTo.messageId const auto external = replyTo.messageId
@ -98,8 +97,7 @@ MTPInputReplyTo ReplyToForMTP(
(replyToMonoforumPeer (replyToMonoforumPeer
? replyToMonoforumPeer->input ? replyToMonoforumPeer->input
: MTPInputPeer())); : MTPInputPeer()));
} else if (history->peer->isChannel() } else if (history->peer->amMonoforumAdmin()
&& history->peer->asChannel()->requiresMonoforumPeer()
&& replyTo.monoforumPeerId) { && replyTo.monoforumPeerId) {
const auto replyToMonoforumPeer = replyTo.monoforumPeerId const auto replyToMonoforumPeer = replyTo.monoforumPeerId
? history->owner().peer(replyTo.monoforumPeerId) ? history->owner().peer(replyTo.monoforumPeerId)

View file

@ -427,10 +427,12 @@ QImage *PeerData::userpicCloudImage(Ui::PeerUserpicView &view) const {
void PeerData::paintUserpic( void PeerData::paintUserpic(
Painter &p, Painter &p,
Ui::PeerUserpicView &view, Ui::PeerUserpicView &view,
int x, const PaintUserpicContext &context) const {
int y, if (const auto broadcast = monoforumBroadcast()) {
int size, broadcast->paintUserpic(p, view, context);
bool forceCircle) const { return;
}
const auto size = context.size;
const auto cloud = userpicCloudImage(view); const auto cloud = userpicCloudImage(view);
const auto ratio = style::DevicePixelRatio(); const auto ratio = style::DevicePixelRatio();
Ui::ValidateUserpicCache( Ui::ValidateUserpicCache(
@ -438,8 +440,8 @@ void PeerData::paintUserpic(
cloud, cloud,
cloud ? nullptr : ensureEmptyUserpic().get(), cloud ? nullptr : ensureEmptyUserpic().get(),
size * ratio, size * ratio,
!forceCircle && (isForum() || isMonoforum())); context.forumLayout);
p.drawImage(QRect(x, y, size, size), view.cached); p.drawImage(QRect(context.position, QSize(size, size)), view.cached);
} }
void PeerData::loadUserpic() { void PeerData::loadUserpic() {
@ -1118,6 +1120,16 @@ const ChannelData *PeerData::asChannelOrMigrated() const {
return migrateTo(); return migrateTo();
} }
ChannelData *PeerData::asMonoforum() {
const auto channel = asMegagroup();
return (channel && channel->isMonoforum()) ? channel : nullptr;
}
const ChannelData *PeerData::asMonoforum() const {
const auto channel = asMegagroup();
return (channel && channel->isMonoforum()) ? channel : nullptr;
}
ChatData *PeerData::migrateFrom() const { ChatData *PeerData::migrateFrom() const {
if (const auto megagroup = asMegagroup()) { if (const auto megagroup = asMegagroup()) {
return megagroup->amIn() return megagroup->amIn()
@ -1150,6 +1162,16 @@ not_null<const PeerData*> PeerData::migrateToOrMe() const {
return this; return this;
} }
ChannelData *PeerData::monoforumBroadcast() const {
const auto monoforum = asMonoforum();
return monoforum ? monoforum->monoforumLink() : nullptr;
}
ChannelData *PeerData::broadcastMonoforum() const {
const auto broadcast = asBroadcast();
return broadcast ? broadcast->monoforumLink() : nullptr;
}
const QString &PeerData::topBarNameText() const { const QString &PeerData::topBarNameText() const {
if (const auto to = migrateTo()) { if (const auto to = migrateTo()) {
return to->topBarNameText(); return to->topBarNameText();
@ -1572,12 +1594,21 @@ bool PeerData::canManageGroupCall() const {
return chat->amCreator() return chat->amCreator()
|| (chat->adminRights() & ChatAdminRight::ManageCall); || (chat->adminRights() & ChatAdminRight::ManageCall);
} else if (const auto group = asChannel()) { } else if (const auto group = asChannel()) {
if (group->isMonoforum()) {
return false;
}
return group->amCreator() return group->amCreator()
|| (group->adminRights() & ChatAdminRight::ManageCall); || (group->adminRights() & ChatAdminRight::ManageCall);
} }
return false; return false;
} }
bool PeerData::amMonoforumAdmin() const {
const auto broadcast = monoforumBroadcast();
return broadcast
&& (broadcast->amCreator() || broadcast->hasAdminRights());
}
int PeerData::starsPerMessage() const { int PeerData::starsPerMessage() const {
if (const auto user = asUser()) { if (const auto user = asUser()) {
return user->starsPerMessage(); return user->starsPerMessage();

View file

@ -277,6 +277,7 @@ public:
[[nodiscard]] rpl::producer<bool> slowmodeAppliedValue() const; [[nodiscard]] rpl::producer<bool> slowmodeAppliedValue() const;
[[nodiscard]] int slowmodeSecondsLeft() const; [[nodiscard]] int slowmodeSecondsLeft() const;
[[nodiscard]] bool canManageGroupCall() const; [[nodiscard]] bool canManageGroupCall() const;
[[nodiscard]] bool amMonoforumAdmin() const;
[[nodiscard]] int starsPerMessage() const; [[nodiscard]] int starsPerMessage() const;
[[nodiscard]] int starsPerMessageChecked() const; [[nodiscard]] int starsPerMessageChecked() const;
@ -297,12 +298,20 @@ public:
[[nodiscard]] const ChatData *asChatNotMigrated() const; [[nodiscard]] const ChatData *asChatNotMigrated() const;
[[nodiscard]] ChannelData *asChannelOrMigrated(); [[nodiscard]] ChannelData *asChannelOrMigrated();
[[nodiscard]] const ChannelData *asChannelOrMigrated() const; [[nodiscard]] const ChannelData *asChannelOrMigrated() const;
[[nodiscard]] ChannelData *asMonoforum();
[[nodiscard]] const ChannelData *asMonoforum() const;
[[nodiscard]] ChatData *migrateFrom() const; [[nodiscard]] ChatData *migrateFrom() const;
[[nodiscard]] ChannelData *migrateTo() const; [[nodiscard]] ChannelData *migrateTo() const;
[[nodiscard]] not_null<PeerData*> migrateToOrMe(); [[nodiscard]] not_null<PeerData*> migrateToOrMe();
[[nodiscard]] not_null<const PeerData*> migrateToOrMe() const; [[nodiscard]] not_null<const PeerData*> migrateToOrMe() const;
// isMonoforum() ? monoforumLink() : nullptr
[[nodiscard]] ChannelData *monoforumBroadcast() const;
// isMonoforum() ? nullptr : monoforumLink()
[[nodiscard]] ChannelData *broadcastMonoforum() const;
void updateFull(); void updateFull();
void updateFullForced(); void updateFullForced();
void fullUpdated(); void fullUpdated();
@ -332,13 +341,29 @@ public:
const ImageLocation &location, const ImageLocation &location,
bool hasVideo); bool hasVideo);
void setUserpicPhoto(const MTPPhoto &data); void setUserpicPhoto(const MTPPhoto &data);
struct PaintUserpicContext {
QPoint position;
int size = 0;
bool forumLayout = false;
};
void paintUserpic(
Painter &p,
Ui::PeerUserpicView &view,
const PaintUserpicContext &context) const;
void paintUserpic( void paintUserpic(
Painter &p, Painter &p,
Ui::PeerUserpicView &view, Ui::PeerUserpicView &view,
int x, int x,
int y, int y,
int size, int size,
bool forceCircle = false) const; bool forceCircle = false) const {
paintUserpic(p, view, {
.position = { x, y },
.size = size,
.forumLayout = !forceCircle && (isForum() || isMonoforum()),
});
}
void paintUserpicLeft( void paintUserpicLeft(
Painter &p, Painter &p,
Ui::PeerUserpicView &view, Ui::PeerUserpicView &view,

View file

@ -4657,6 +4657,10 @@ void Session::refreshChatListEntry(Dialogs::Key key) {
if (const auto forum = history->peer->forum()) { if (const auto forum = history->peer->forum()) {
forum->preloadTopics(); forum->preloadTopics();
} }
if (history->peer->isMonoforum()
&& !history->peer->monoforumBroadcast()) {
history->peer->updateFull();
}
} }
} }

View file

@ -941,7 +941,7 @@ void Widget::chosenRow(const ChosenRow &row) {
} }
return; return;
} else if (history } else if (history
&& history->isMonoforum() && history->peer->amMonoforumAdmin()
&& !row.message.fullId && !row.message.fullId
&& !controller()->adaptive().isOneColumn()) { && !controller()->adaptive().isOneColumn()) {
const auto monoforum = history->peer->monoforum(); const auto monoforum = history->peer->monoforum();

View file

@ -2789,7 +2789,10 @@ bool History::shouldBeInChatList() const {
} else if (isPinnedDialog(FilterId())) { } else if (isPinnedDialog(FilterId())) {
return true; return true;
} else if (const auto channel = peer->asChannel()) { } else if (const auto channel = peer->asChannel()) {
if (!channel->amIn()) { if (channel->isMonoforum()) {
return !lastMessageKnown()
|| (lastMessage() != nullptr);
} else if (!channel->amIn()) {
return isTopPromoted(); return isTopPromoted();
} }
} else if (const auto chat = peer->asChat()) { } else if (const auto chat = peer->asChat()) {

View file

@ -3770,8 +3770,7 @@ void HistoryItem::createComponents(CreateConfig &&config) {
} else if (config.inlineMarkup) { } else if (config.inlineMarkup) {
mask |= HistoryMessageReplyMarkup::Bit(); mask |= HistoryMessageReplyMarkup::Bit();
} }
const auto requiresMonoforumPeer = _history->peer->isChannel() const auto requiresMonoforumPeer = _history->peer->amMonoforumAdmin();
&& _history->peer->asChannel()->requiresMonoforumPeer();
if (_history->peer->isSelf() if (_history->peer->isSelf()
|| config.savedSublistPeer || config.savedSublistPeer
|| requiresMonoforumPeer) { || requiresMonoforumPeer) {

View file

@ -57,7 +57,7 @@ struct BotKeyboardButton;
extern const char kOptionFastButtonsMode[]; extern const char kOptionFastButtonsMode[];
[[nodiscard]] bool FastButtonsMode(); [[nodiscard]] bool FastButtonsMode();
struct HistoryMessageVia : public RuntimeComponent<HistoryMessageVia, HistoryItem> { struct HistoryMessageVia : RuntimeComponent<HistoryMessageVia, HistoryItem> {
void create(not_null<Data::Session*> owner, UserId userId); void create(not_null<Data::Session*> owner, UserId userId);
void resize(int32 availw) const; void resize(int32 availw) const;
@ -68,7 +68,8 @@ struct HistoryMessageVia : public RuntimeComponent<HistoryMessageVia, HistoryIte
ClickHandlerPtr link; ClickHandlerPtr link;
}; };
struct HistoryMessageViews : public RuntimeComponent<HistoryMessageViews, HistoryItem> { struct HistoryMessageViews
: RuntimeComponent<HistoryMessageViews, HistoryItem> {
static constexpr auto kMaxRecentRepliers = 3; static constexpr auto kMaxRecentRepliers = 3;
struct Part { struct Part {
@ -87,13 +88,15 @@ struct HistoryMessageViews : public RuntimeComponent<HistoryMessageViews, Histor
int forwardsCount = 0; int forwardsCount = 0;
}; };
struct HistoryMessageSigned : public RuntimeComponent<HistoryMessageSigned, HistoryItem> { struct HistoryMessageSigned
: RuntimeComponent<HistoryMessageSigned, HistoryItem> {
QString author; QString author;
UserData *viaBusinessBot = nullptr; UserData *viaBusinessBot = nullptr;
bool isAnonymousRank = false; bool isAnonymousRank = false;
}; };
struct HistoryMessageEdited : public RuntimeComponent<HistoryMessageEdited, HistoryItem> { struct HistoryMessageEdited
: RuntimeComponent<HistoryMessageEdited, HistoryItem> {
TimeId date = 0; TimeId date = 0;
}; };
@ -134,7 +137,8 @@ private:
}; };
struct HistoryMessageForwarded : public RuntimeComponent<HistoryMessageForwarded, HistoryItem> { struct HistoryMessageForwarded
: RuntimeComponent<HistoryMessageForwarded, HistoryItem> {
void create( void create(
const HistoryMessageVia *via, const HistoryMessageVia *via,
not_null<const HistoryItem*> item) const; not_null<const HistoryItem*> item) const;
@ -162,12 +166,14 @@ struct HistoryMessageForwarded : public RuntimeComponent<HistoryMessageForwarded
bool story = false; bool story = false;
}; };
struct HistoryMessageSavedMediaData : public RuntimeComponent<HistoryMessageSavedMediaData, HistoryItem> { struct HistoryMessageSavedMediaData
: RuntimeComponent<HistoryMessageSavedMediaData, HistoryItem> {
TextWithEntities text; TextWithEntities text;
std::unique_ptr<Data::Media> media; std::unique_ptr<Data::Media> media;
}; };
struct HistoryMessageSaved : public RuntimeComponent<HistoryMessageSaved, HistoryItem> { struct HistoryMessageSaved
: RuntimeComponent<HistoryMessageSaved, HistoryItem> {
Data::SavedSublist *sublist = nullptr; Data::SavedSublist *sublist = nullptr;
}; };
@ -274,7 +280,7 @@ struct ReplyFields {
const MTPInputReplyTo &reply); const MTPInputReplyTo &reply);
struct HistoryMessageReply struct HistoryMessageReply
: public RuntimeComponent<HistoryMessageReply, HistoryItem> { : RuntimeComponent<HistoryMessageReply, HistoryItem> {
HistoryMessageReply(); HistoryMessageReply();
HistoryMessageReply(const HistoryMessageReply &other) = delete; HistoryMessageReply(const HistoryMessageReply &other) = delete;
HistoryMessageReply(HistoryMessageReply &&other) = delete; HistoryMessageReply(HistoryMessageReply &&other) = delete;
@ -358,7 +364,7 @@ private:
}; };
struct HistoryMessageTranslation struct HistoryMessageTranslation
: public RuntimeComponent<HistoryMessageTranslation, HistoryItem> { : RuntimeComponent<HistoryMessageTranslation, HistoryItem> {
TextWithEntities text; TextWithEntities text;
LanguageId to; LanguageId to;
bool requested = false; bool requested = false;
@ -367,7 +373,7 @@ struct HistoryMessageTranslation
}; };
struct HistoryMessageReplyMarkup struct HistoryMessageReplyMarkup
: public RuntimeComponent<HistoryMessageReplyMarkup, HistoryItem> { : RuntimeComponent<HistoryMessageReplyMarkup, HistoryItem> {
using Button = HistoryMessageMarkupButton; using Button = HistoryMessageMarkupButton;
void createForwarded(const HistoryMessageReplyMarkup &original); void createForwarded(const HistoryMessageReplyMarkup &original);
@ -565,7 +571,7 @@ private:
// Special type of Component for the channel actions log. // Special type of Component for the channel actions log.
struct HistoryMessageLogEntryOriginal struct HistoryMessageLogEntryOriginal
: public RuntimeComponent<HistoryMessageLogEntryOriginal, HistoryItem> { : RuntimeComponent<HistoryMessageLogEntryOriginal, HistoryItem> {
HistoryMessageLogEntryOriginal(); HistoryMessageLogEntryOriginal();
HistoryMessageLogEntryOriginal(HistoryMessageLogEntryOriginal &&other); HistoryMessageLogEntryOriginal(HistoryMessageLogEntryOriginal &&other);
HistoryMessageLogEntryOriginal &operator=(HistoryMessageLogEntryOriginal &&other); HistoryMessageLogEntryOriginal &operator=(HistoryMessageLogEntryOriginal &&other);
@ -597,19 +603,19 @@ struct MessageFactcheck {
const tl::conditional<MTPFactCheck> &factcheck); const tl::conditional<MTPFactCheck> &factcheck);
struct HistoryMessageFactcheck struct HistoryMessageFactcheck
: public RuntimeComponent<HistoryMessageFactcheck, HistoryItem> { : RuntimeComponent<HistoryMessageFactcheck, HistoryItem> {
MessageFactcheck data; MessageFactcheck data;
WebPageData *page = nullptr; WebPageData *page = nullptr;
bool requested = false; bool requested = false;
}; };
struct HistoryMessageRestrictions struct HistoryMessageRestrictions
: public RuntimeComponent<HistoryMessageRestrictions, HistoryItem> { : RuntimeComponent<HistoryMessageRestrictions, HistoryItem> {
std::vector<Data::UnavailableReason> reasons; std::vector<Data::UnavailableReason> reasons;
}; };
struct HistoryServiceData struct HistoryServiceData
: public RuntimeComponent<HistoryServiceData, HistoryItem> { : RuntimeComponent<HistoryServiceData, HistoryItem> {
std::vector<ClickHandlerPtr> textLinks; std::vector<ClickHandlerPtr> textLinks;
}; };
@ -625,13 +631,13 @@ struct HistoryServiceDependentData {
}; };
struct HistoryServicePinned struct HistoryServicePinned
: public RuntimeComponent<HistoryServicePinned, HistoryItem> : RuntimeComponent<HistoryServicePinned, HistoryItem>
, public HistoryServiceDependentData { , HistoryServiceDependentData {
}; };
struct HistoryServiceTopicInfo struct HistoryServiceTopicInfo
: public RuntimeComponent<HistoryServiceTopicInfo, HistoryItem> : RuntimeComponent<HistoryServiceTopicInfo, HistoryItem>
, public HistoryServiceDependentData { , HistoryServiceDependentData {
QString title; QString title;
DocumentId iconId = 0; DocumentId iconId = 0;
bool closed = false; bool closed = false;
@ -652,14 +658,14 @@ struct HistoryServiceTopicInfo
}; };
struct HistoryServiceGameScore struct HistoryServiceGameScore
: public RuntimeComponent<HistoryServiceGameScore, HistoryItem> : RuntimeComponent<HistoryServiceGameScore, HistoryItem>
, public HistoryServiceDependentData { , HistoryServiceDependentData {
int score = 0; int score = 0;
}; };
struct HistoryServicePayment struct HistoryServicePayment
: public RuntimeComponent<HistoryServicePayment, HistoryItem> : RuntimeComponent<HistoryServicePayment, HistoryItem>
, public HistoryServiceDependentData { , HistoryServiceDependentData {
QString slug; QString slug;
TextWithEntities amount; TextWithEntities amount;
ClickHandlerPtr invoiceLink; ClickHandlerPtr invoiceLink;
@ -669,22 +675,22 @@ struct HistoryServicePayment
}; };
struct HistoryServiceSameBackground struct HistoryServiceSameBackground
: public RuntimeComponent<HistoryServiceSameBackground, HistoryItem> : RuntimeComponent<HistoryServiceSameBackground, HistoryItem>
, public HistoryServiceDependentData { , HistoryServiceDependentData {
}; };
struct HistoryServiceGiveawayResults struct HistoryServiceGiveawayResults
: public RuntimeComponent<HistoryServiceGiveawayResults, HistoryItem> : RuntimeComponent<HistoryServiceGiveawayResults, HistoryItem>
, public HistoryServiceDependentData { , HistoryServiceDependentData {
}; };
struct HistoryServiceCustomLink struct HistoryServiceCustomLink
: public RuntimeComponent<HistoryServiceCustomLink, HistoryItem> { : RuntimeComponent<HistoryServiceCustomLink, HistoryItem> {
ClickHandlerPtr link; ClickHandlerPtr link;
}; };
struct HistoryServicePaymentRefund struct HistoryServicePaymentRefund
: public RuntimeComponent<HistoryServicePaymentRefund, HistoryItem> { : RuntimeComponent<HistoryServicePaymentRefund, HistoryItem> {
ClickHandlerPtr link; ClickHandlerPtr link;
PeerData *peer = nullptr; PeerData *peer = nullptr;
QString transactionId; QString transactionId;
@ -707,7 +713,7 @@ struct TimeToLiveSingleView {
}; };
struct HistoryServiceSelfDestruct struct HistoryServiceSelfDestruct
: public RuntimeComponent<HistoryServiceSelfDestruct, HistoryItem> { : RuntimeComponent<HistoryServiceSelfDestruct, HistoryItem> {
using Type = HistorySelfDestructType; using Type = HistorySelfDestructType;
Type type = Type::Photo; Type type = Type::Photo;
@ -716,24 +722,25 @@ struct HistoryServiceSelfDestruct
}; };
struct HistoryServiceOngoingCall struct HistoryServiceOngoingCall
: public RuntimeComponent<HistoryServiceOngoingCall, HistoryItem> { : RuntimeComponent<HistoryServiceOngoingCall, HistoryItem> {
CallId id = 0; CallId id = 0;
ClickHandlerPtr link; ClickHandlerPtr link;
rpl::lifetime lifetime; rpl::lifetime lifetime;
}; };
struct HistoryServiceChatThemeChange struct HistoryServiceChatThemeChange
: public RuntimeComponent<HistoryServiceChatThemeChange, HistoryItem> { : RuntimeComponent<HistoryServiceChatThemeChange, HistoryItem> {
ClickHandlerPtr link; ClickHandlerPtr link;
}; };
struct HistoryServiceTTLChange struct HistoryServiceTTLChange
: public RuntimeComponent<HistoryServiceTTLChange, HistoryItem> { : RuntimeComponent<HistoryServiceTTLChange, HistoryItem> {
ClickHandlerPtr link; ClickHandlerPtr link;
}; };
class FileClickHandler; class FileClickHandler;
struct HistoryDocumentThumbed : public RuntimeComponent<HistoryDocumentThumbed, HistoryView::Document> { struct HistoryDocumentThumbed
: RuntimeComponent<HistoryDocumentThumbed, HistoryView::Document> {
std::shared_ptr<FileClickHandler> linksavel; std::shared_ptr<FileClickHandler> linksavel;
std::shared_ptr<FileClickHandler> linkopenwithl; std::shared_ptr<FileClickHandler> linkopenwithl;
std::shared_ptr<FileClickHandler> linkcancell; std::shared_ptr<FileClickHandler> linkcancell;
@ -745,13 +752,15 @@ struct HistoryDocumentThumbed : public RuntimeComponent<HistoryDocumentThumbed,
mutable bool blurred : 1 = false; mutable bool blurred : 1 = false;
}; };
struct HistoryDocumentCaptioned : public RuntimeComponent<HistoryDocumentCaptioned, HistoryView::Document> { struct HistoryDocumentCaptioned
: RuntimeComponent<HistoryDocumentCaptioned, HistoryView::Document> {
HistoryDocumentCaptioned(); HistoryDocumentCaptioned();
Ui::Text::String caption; Ui::Text::String caption;
}; };
struct HistoryDocumentNamed : public RuntimeComponent<HistoryDocumentNamed, HistoryView::Document> { struct HistoryDocumentNamed
: RuntimeComponent<HistoryDocumentNamed, HistoryView::Document> {
Ui::Text::String name; Ui::Text::String name;
}; };
@ -763,7 +772,8 @@ struct HistoryDocumentVoicePlayback {
Ui::Animations::Basic progressAnimation; Ui::Animations::Basic progressAnimation;
}; };
class HistoryDocumentVoice : public RuntimeComponent<HistoryDocumentVoice, HistoryView::Document> { class HistoryDocumentVoice
: public RuntimeComponent<HistoryDocumentVoice, HistoryView::Document> {
// We don't use float64 because components should align to pointer even on 32bit systems. // We don't use float64 because components should align to pointer even on 32bit systems.
static constexpr float64 kFloatToIntMultiplier = 65536.; static constexpr float64 kFloatToIntMultiplier = 65536.;

View file

@ -6633,6 +6633,12 @@ int HistoryWidget::countAutomaticScrollTop() {
} }
Data::SendError HistoryWidget::computeSendRestriction() const { Data::SendError HistoryWidget::computeSendRestriction() const {
if (!_canSendMessages && _peer->amMonoforumAdmin()) {
return Data::SendError({
.text = tr::lng_monoforum_choose_to_reply(tr::now),
.monoforumAdmin = true,
});
}
const auto allWithoutPolls = Data::AllSendRestrictions() const auto allWithoutPolls = Data::AllSendRestrictions()
& ~ChatRestriction::SendPolls; & ~ChatRestriction::SendPolls;
return (_peer && !Data::CanSendAnyOf(_peer, allWithoutPolls)) return (_peer && !Data::CanSendAnyOf(_peer, allWithoutPolls))
@ -8753,10 +8759,17 @@ bool HistoryWidget::updateCanSendMessage() {
const auto topic = resolveReplyToTopic(); const auto topic = resolveReplyToTopic();
const auto allWithoutPolls = Data::AllSendRestrictions() const auto allWithoutPolls = Data::AllSendRestrictions()
& ~ChatRestriction::SendPolls; & ~ChatRestriction::SendPolls;
const auto newCanSendMessages = topic const auto onlyReplies = _peer->amMonoforumAdmin();
const auto restrictedOnlyReplies = onlyReplies
&& (!_replyTo.messageId || _replyTo.messageId.peer != _peer->id);
const auto newCanSendMessages = restrictedOnlyReplies
? false
: topic
? Data::CanSendAnyOf(topic, allWithoutPolls) ? Data::CanSendAnyOf(topic, allWithoutPolls)
: Data::CanSendAnyOf(_peer, allWithoutPolls); : Data::CanSendAnyOf(_peer, allWithoutPolls);
const auto newCanSendTexts = topic const auto newCanSendTexts = restrictedOnlyReplies
? false
: topic
? Data::CanSend(topic, ChatRestriction::SendOther) ? Data::CanSend(topic, ChatRestriction::SendOther)
: Data::CanSend(_peer, ChatRestriction::SendOther); : Data::CanSend(_peer, ChatRestriction::SendOther);
if (_canSendMessages == newCanSendMessages if (_canSendMessages == newCanSendMessages

View file

@ -2702,7 +2702,11 @@ QRect ChatWidget::floatPlayerAvailableRect() {
} }
Context ChatWidget::listContext() { Context ChatWidget::listContext() {
return _sublist ? Context::SavedSublist : Context::Replies; return !_sublist
? Context::Replies
: _sublist->parentChat()
? Context::Monoforum
: Context::SavedSublist;
} }
bool ChatWidget::listScrollTo(int top, bool syntetic) { bool ChatWidget::listScrollTo(int top, bool syntetic) {
@ -2883,10 +2887,8 @@ void ChatWidget::listMarkReadTill(not_null<HistoryItem*> item) {
void ChatWidget::listMarkContentsRead( void ChatWidget::listMarkContentsRead(
const base::flat_set<not_null<HistoryItem*>> &items) { const base::flat_set<not_null<HistoryItem*>> &items) {
if (!_sublist) {
session().api().markContentsRead(items); session().api().markContentsRead(items);
} }
}
MessagesBarData ChatWidget::listMessagesBar( MessagesBarData ChatWidget::listMessagesBar(
const std::vector<not_null<Element*>> &elements) { const std::vector<not_null<Element*>> &elements) {

View file

@ -41,6 +41,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/painter.h" #include "ui/painter.h"
#include "ui/rect.h" #include "ui/rect.h"
#include "data/components/sponsored_messages.h" #include "data/components/sponsored_messages.h"
#include "data/data_channel.h"
#include "data/data_saved_sublist.h"
#include "data/data_session.h" #include "data/data_session.h"
#include "data/data_forum.h" #include "data/data_forum.h"
#include "data/data_forum_topic.h" #include "data/data_forum_topic.h"
@ -475,6 +477,83 @@ void DateBadge::paint(
ServiceMessagePainter::PaintDate(p, st, text, width, y, w, chatWide); ServiceMessagePainter::PaintDate(p, st, text, width, y, w, chatWide);
} }
void MonoforumSenderBar::init(
not_null<PeerData*> parentChat,
not_null<PeerData*> peer) {
author = peer;
text.setText(st::semiboldTextStyle, peer->name());
const auto skip = st::monoforumBarUserpicSkip;
const auto userpic = st::msgServicePadding.top()
+ st::msgServiceFont->height
+ st::msgServicePadding.bottom()
- 2 * skip;
width = skip + userpic + skip * 2 + text.maxWidth() + st::msgServicePadding.right();
}
int MonoforumSenderBar::height() const {
return st::msgServiceMargin.top()
+ st::msgServicePadding.top()
+ st::msgServiceFont->height
+ st::msgServicePadding.bottom()
+ st::msgServiceMargin.bottom();
}
void MonoforumSenderBar::paint(
Painter &p,
not_null<const Ui::ChatStyle*> st,
int y,
int w,
bool chatWide) const {
Expects(author != nullptr);
int left = st::msgServiceMargin.left();
const auto maxwidth = chatWide
? std::min(w, WideChatWidth())
: w;
w = maxwidth - st::msgServiceMargin.left() - st::msgServiceMargin.left();
const auto use = std::min(w, width);
left += (w - use) / 2;
int h = st::msgServicePadding.top() + st::msgServiceFont->height + st::msgServicePadding.bottom();
ServiceMessagePainter::PaintBubble(
p,
st->msgServiceBg(),
st->serviceBgCornersNormal(),
QRect(left, y + st::msgServiceMargin.top(), use, h));
const auto skip = st::monoforumBarUserpicSkip;
{
auto pen = st->msgServiceBg()->p;
pen.setWidthF(skip);
pen.setCapStyle(Qt::RoundCap);
pen.setDashPattern({ 2., 2. });
p.setPen(pen);
const auto top = y + st::msgServiceMargin.top() + (h / 2);
p.drawLine(0, top, left, top);
p.drawLine(left + use, top, 2 * w, top);
}
const auto userpic = st::msgServicePadding.top()
+ st::msgServiceFont->height
+ st::msgServicePadding.bottom()
- 2 * skip;
const auto available = use - (skip + userpic + skip * 2 + st::msgServicePadding.right());
author->paintUserpic(p, view, left + skip, y + st::msgServiceMargin.top() + skip, userpic);
p.setFont(st::msgServiceFont);
p.setPen(st->msgServiceFg());
text.draw(p, {
.position = {
left + skip + userpic + skip * 2,
y + st::msgServiceMargin.top() + st::msgServicePadding.top(),
},
.availableWidth = available,
.elisionLines = 1,
});
}
void ServicePreMessage::init(PreparedServiceText string) { void ServicePreMessage::init(PreparedServiceText string) {
text = Ui::Text::String( text = Ui::Text::String(
st::serviceTextStyle, st::serviceTextStyle,
@ -1220,6 +1299,7 @@ void Element::validateTextSkipBlock(bool has, int width, int height) {
} }
void Element::previousInBlocksChanged() { void Element::previousInBlocksChanged() {
recountMonoforumSenderBarInBlocks();
recountDisplayDateInBlocks(); recountDisplayDateInBlocks();
recountAttachToPreviousInBlocks(); recountAttachToPreviousInBlocks();
} }
@ -1255,7 +1335,8 @@ bool Element::computeIsAttachToPrevious(not_null<Element*> previous) {
const auto item = data(); const auto item = data();
if (!Has<DateBadge>() if (!Has<DateBadge>()
&& !Has<UnreadBar>() && !Has<UnreadBar>()
&& !Has<ServicePreMessage>()) { && !Has<ServicePreMessage>()
&& !Has<MonoforumSenderBar>()) {
const auto prev = previous->data(); const auto prev = previous->data();
const auto previousMarkup = prev->inlineReplyMarkup(); const auto previousMarkup = prev->inlineReplyMarkup();
const auto possible = (std::abs(prev->date() - item->date()) const auto possible = (std::abs(prev->date() - item->date())
@ -1385,6 +1466,37 @@ void Element::recountAttachToPreviousInBlocks() {
setAttachToPrevious(attachToPrevious, previous); setAttachToPrevious(attachToPrevious, previous);
} }
void Element::recountMonoforumSenderBarInBlocks() {
const auto item = data();
const auto sublist = item->savedSublist();
const auto parentChat = sublist ? sublist->parentChat() : nullptr;
const auto barPeer = [&]() -> PeerData* {
if (!parentChat
|| isHidden()
|| item->isEmpty()
|| item->isSponsored()) {
return nullptr;
}
const auto peer = sublist->peer();
if (const auto previous = previousDisplayedInBlocks()) {
const auto prev = previous->data();
if (const auto prevSublist = prev->savedSublist()) {
Assert(prevSublist->parentChat() == parentChat);
if (prevSublist->peer() == peer) {
return nullptr;
}
}
}
return peer;
}();
if (barPeer && !Has<MonoforumSenderBar>()) {
AddComponents(MonoforumSenderBar::Bit());
Get<MonoforumSenderBar>()->init(parentChat, barPeer);
} else if (!barPeer && Has<MonoforumSenderBar>()) {
RemoveComponents(MonoforumSenderBar::Bit());
}
}
void Element::recountDisplayDateInBlocks() { void Element::recountDisplayDateInBlocks() {
setDisplayDate([&] { setDisplayDate([&] {
const auto item = data(); const auto item = data();

View file

@ -11,6 +11,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "base/runtime_composer.h" #include "base/runtime_composer.h"
#include "base/flags.h" #include "base/flags.h"
#include "base/weak_ptr.h" #include "base/weak_ptr.h"
#include "ui/userpic_view.h"
class History; class History;
class HistoryBlock; class HistoryBlock;
@ -58,6 +59,7 @@ enum class Context : char {
Pinned, Pinned,
AdminLog, AdminLog,
ContactPreview, ContactPreview,
Monoforum,
SavedSublist, SavedSublist,
TTLViewer, TTLViewer,
ShortcutMessages, ShortcutMessages,
@ -220,7 +222,7 @@ QString DateTooltipText(not_null<Element*> view);
// Any HistoryView::Element can have this Component for // Any HistoryView::Element can have this Component for
// displaying the unread messages bar above the message. // displaying the unread messages bar above the message.
struct UnreadBar : public RuntimeComponent<UnreadBar, Element> { struct UnreadBar : RuntimeComponent<UnreadBar, Element> {
void init(const QString &string); void init(const QString &string);
static int height(); static int height();
@ -241,7 +243,7 @@ struct UnreadBar : public RuntimeComponent<UnreadBar, Element> {
// Any HistoryView::Element can have this Component for // Any HistoryView::Element can have this Component for
// displaying the day mark above the message. // displaying the day mark above the message.
struct DateBadge : public RuntimeComponent<DateBadge, Element> { struct DateBadge : RuntimeComponent<DateBadge, Element> {
void init(const QString &date); void init(const QString &date);
int height() const; int height() const;
@ -257,10 +259,27 @@ struct DateBadge : public RuntimeComponent<DateBadge, Element> {
}; };
struct MonoforumSenderBar : RuntimeComponent<MonoforumSenderBar, Element> {
void init(not_null<PeerData*> parentChat, not_null<PeerData*> peer);
int height() const;
void paint(
Painter &p,
not_null<const Ui::ChatStyle*> st,
int y,
int w,
bool chatWide) const;
PeerData *author = nullptr;
Ui::Text::String text;
ClickHandlerPtr link;
mutable Ui::PeerUserpicView view;
int width = 0;
};
// Any HistoryView::Element can have this Component for // Any HistoryView::Element can have this Component for
// displaying some text in layout of a service message above the message. // displaying some text in layout of a service message above the message.
struct ServicePreMessage struct ServicePreMessage : RuntimeComponent<ServicePreMessage, Element> {
: public RuntimeComponent<ServicePreMessage, Element> {
void init(PreparedServiceText string); void init(PreparedServiceText string);
int resizeToWidth(int newWidth, bool chatWide); int resizeToWidth(int newWidth, bool chatWide);
@ -281,7 +300,7 @@ struct ServicePreMessage
}; };
struct FakeBotAboutTop : public RuntimeComponent<FakeBotAboutTop, Element> { struct FakeBotAboutTop : RuntimeComponent<FakeBotAboutTop, Element> {
void init(); void init();
Ui::Text::String text; Ui::Text::String text;
@ -289,7 +308,7 @@ struct FakeBotAboutTop : public RuntimeComponent<FakeBotAboutTop, Element> {
int height = 0; int height = 0;
}; };
struct PurchasedTag : public RuntimeComponent<PurchasedTag, Element> { struct PurchasedTag : RuntimeComponent<PurchasedTag, Element> {
Ui::Text::String text; Ui::Text::String text;
}; };
@ -629,14 +648,17 @@ protected:
std::unique_ptr<Reactions::InlineList> _reactions; std::unique_ptr<Reactions::InlineList> _reactions;
private: private:
void recountMonoforumSenderBarInBlocks();
// This should be called only from previousInBlocksChanged() // This should be called only from previousInBlocksChanged()
// to add required bits to the Composer mask // to add required bits to the Composer mask
// after that always use Has<DateBadge>(). // after that always use Has<DateBadge>().
void recountDisplayDateInBlocks(); void recountDisplayDateInBlocks();
// This should be called only from previousInBlocksChanged() or when // This should be called only from previousInBlocksChanged() or when
// DateBadge or UnreadBar bit is changed in the Composer mask // DateBadge or UnreadBar or MonoforumSenderBar bit
// then the result should be cached in a client side flag // is changed in the Composer mask then the result
// should be cached in a client side flag
// HistoryView::Element::Flag::AttachedToPrevious. // HistoryView::Element::Flag::AttachedToPrevious.
void recountAttachToPreviousInBlocks(); void recountAttachToPreviousInBlocks();

View file

@ -1088,6 +1088,9 @@ int Message::marginTop() const {
if (const auto bar = Get<UnreadBar>()) { if (const auto bar = Get<UnreadBar>()) {
result += bar->height(); result += bar->height();
} }
if (const auto monoforumBar = Get<MonoforumSenderBar>()) {
result += monoforumBar->height();
}
if (const auto service = Get<ServicePreMessage>()) { if (const auto service = Get<ServicePreMessage>()) {
result += service->height; result += service->height;
} }
@ -1146,6 +1149,27 @@ void Message::draw(Painter &p, const PaintContext &context) const {
} }
} }
if (const auto monoforumBar = Get<MonoforumSenderBar>()) {
auto barh = monoforumBar->height();
auto skip = 0;
if (const auto date = Get<DateBadge>()) {
skip += date->height();
}
if (const auto bar = Get<UnreadBar>()) {
skip += bar->height();
}
if (context.clip.intersects(QRect(0, skip, width(), barh))) {
p.translate(0, skip);
monoforumBar->paint(
p,
context.st,
0,
width(),
delegate()->elementIsChatWide());
p.translate(0, -skip);
}
}
if (const auto service = Get<ServicePreMessage>()) { if (const auto service = Get<ServicePreMessage>()) {
service->paint(p, context, g, delegate()->elementIsChatWide()); service->paint(p, context, g, delegate()->elementIsChatWide());
} }
@ -2458,6 +2482,8 @@ bool Message::hasFromPhoto() const {
switch (context()) { switch (context()) {
case Context::AdminLog: case Context::AdminLog:
return true; return true;
case Context::Monoforum:
return delegate()->elementIsChatWide();
case Context::History: case Context::History:
case Context::ChatPreview: case Context::ChatPreview:
case Context::TTLViewer: case Context::TTLViewer:
@ -3685,6 +3711,8 @@ bool Message::hasFromName() const {
switch (context()) { switch (context()) {
case Context::AdminLog: case Context::AdminLog:
return true; return true;
case Context::Monoforum:
return false;
case Context::History: case Context::History:
case Context::ChatPreview: case Context::ChatPreview:
case Context::TTLViewer: case Context::TTLViewer:
@ -3953,6 +3981,8 @@ bool Message::displayFastShare() const {
bool Message::displayGoToOriginal() const { bool Message::displayGoToOriginal() const {
if (isPinnedContext()) { if (isPinnedContext()) {
return !hasOutLayout(); return !hasOutLayout();
} else if (context() == Context::Monoforum) {
return false;
} }
const auto item = data(); const auto item = data();
if (const auto forwarded = item->Get<HistoryMessageForwarded>()) { if (const auto forwarded = item->Get<HistoryMessageForwarded>()) {

View file

@ -35,8 +35,7 @@ class InlineList;
} // namespace Reactions } // namespace Reactions
// Special type of Component for the channel actions log. // Special type of Component for the channel actions log.
struct LogEntryOriginal struct LogEntryOriginal : RuntimeComponent<LogEntryOriginal, Element> {
: public RuntimeComponent<LogEntryOriginal, Element> {
LogEntryOriginal(); LogEntryOriginal();
LogEntryOriginal(LogEntryOriginal &&other); LogEntryOriginal(LogEntryOriginal &&other);
LogEntryOriginal &operator=(LogEntryOriginal &&other); LogEntryOriginal &operator=(LogEntryOriginal &&other);
@ -45,13 +44,12 @@ struct LogEntryOriginal
std::unique_ptr<WebPage> page; std::unique_ptr<WebPage> page;
}; };
struct Factcheck struct Factcheck : RuntimeComponent<Factcheck, Element> {
: public RuntimeComponent<Factcheck, Element> {
std::unique_ptr<WebPage> page; std::unique_ptr<WebPage> page;
bool expanded = false; bool expanded = false;
}; };
struct PsaTooltipState : public RuntimeComponent<PsaTooltipState, Element> { struct PsaTooltipState : RuntimeComponent<PsaTooltipState, Element> {
QString type; QString type;
mutable ClickHandlerPtr link; mutable ClickHandlerPtr link;
mutable Ui::Animations::Simple buttonVisibleAnimation; mutable Ui::Animations::Simple buttonVisibleAnimation;

View file

@ -452,6 +452,9 @@ QSize Service::performCountCurrentSize(int newWidth) {
if (const auto bar = Get<UnreadBar>()) { if (const auto bar = Get<UnreadBar>()) {
newHeight += bar->height(); newHeight += bar->height();
} }
if (const auto monoforumBar = Get<MonoforumSenderBar>()) {
newHeight += monoforumBar->height();
}
data()->resolveDependent(); data()->resolveDependent();
@ -525,6 +528,9 @@ int Service::marginTop() const {
if (const auto bar = Get<UnreadBar>()) { if (const auto bar = Get<UnreadBar>()) {
result += bar->height(); result += bar->height();
} }
if (const auto monoforumBar = Get<MonoforumSenderBar>()) {
result += monoforumBar->height();
}
return result; return result;
} }
@ -557,6 +563,27 @@ void Service::draw(Painter &p, const PaintContext &context) const {
} }
} }
if (const auto monoforumBar = Get<MonoforumSenderBar>()) {
auto barh = monoforumBar->height();
auto skip = 0;
if (const auto date = Get<DateBadge>()) {
skip += date->height();
}
if (const auto bar = Get<UnreadBar>()) {
skip += bar->height();
}
if (context.clip.intersects(QRect(0, skip, width(), barh))) {
p.translate(0, skip);
monoforumBar->paint(
p,
context.st,
0,
width(),
delegate()->elementIsChatWide());
p.translate(0, -skip);
}
}
if (isHidden()) { if (isHidden()) {
return; return;
} }

View file

@ -492,6 +492,9 @@ void TopBarWidget::paintTopBar(Painter &p) {
? history->peer.get() ? history->peer.get()
: sublist ? sublist->peer().get() : sublist ? sublist->peer().get()
: nullptr; : nullptr;
const auto broadcastForMonoforum = history
? history->peer->monoforumBroadcast()
: nullptr;
if (topic && _activeChat.section == Section::Replies) { if (topic && _activeChat.section == Section::Replies) {
p.setPen(st::dialogsNameFg); p.setPen(st::dialogsNameFg);
topic->chatListNameText().drawElided( topic->chatListNameText().drawElided(
@ -515,9 +518,12 @@ void TopBarWidget::paintTopBar(Painter &p) {
} }
} else if (folder } else if (folder
|| (peer && (peer->sharedMediaInfo() || peer->isVerifyCodes())) || (peer && (peer->sharedMediaInfo() || peer->isVerifyCodes()))
|| broadcastForMonoforum
|| (_activeChat.section == Section::Scheduled) || (_activeChat.section == Section::Scheduled)
|| (_activeChat.section == Section::Pinned)) { || (_activeChat.section == Section::Pinned)) {
auto text = (_activeChat.section == Section::Scheduled) auto text = broadcastForMonoforum
? broadcastForMonoforum->name() + u" Messages"_q AssertIsDebug()
: (_activeChat.section == Section::Scheduled)
? ((peer && peer->isSelf()) ? ((peer && peer->isSelf())
? tr::lng_reminder_messages(tr::now) ? tr::lng_reminder_messages(tr::now)
: tr::lng_scheduled_messages(tr::now)) : tr::lng_scheduled_messages(tr::now))

View file

@ -628,10 +628,13 @@ Cover::Cover(
: object_ptr<Ui::UserpicButton>( : object_ptr<Ui::UserpicButton>(
this, this,
controller, controller,
_peer, (_peer->monoforumBroadcast()
? _peer->monoforumBroadcast()
: _peer),
Ui::UserpicButton::Role::OpenPhoto, Ui::UserpicButton::Role::OpenPhoto,
Ui::UserpicButton::Source::PeerPhoto, Ui::UserpicButton::Source::PeerPhoto,
_st.photo)) _st.photo,
_peer->monoforumBroadcast() != nullptr))
, _changePersonal((role == Role::Info , _changePersonal((role == Role::Info
|| topic || topic
|| !_peer->isUser() || !_peer->isUser()
@ -647,6 +650,9 @@ Cover::Cover(
, _showLastSeen(this, tr::lng_status_lastseen_when(), _st.showLastSeen) , _showLastSeen(this, tr::lng_status_lastseen_when(), _st.showLastSeen)
, _refreshStatusTimer([this] { refreshStatusText(); }) { , _refreshStatusTimer([this] { refreshStatusText(); }) {
_peer->updateFull(); _peer->updateFull();
if (const auto broadcast = _peer->monoforumBroadcast()) {
broadcast->updateFull();
}
_name->setSelectable(true); _name->setSelectable(true);
_name->setContextCopyText(tr::lng_profile_copy_fullname(tr::now)); _name->setContextCopyText(tr::lng_profile_copy_fullname(tr::now));
@ -979,6 +985,12 @@ void Cover::refreshStatusText() {
chat->count, chat->count,
int(chat->participants.size())); int(chat->participants.size()));
return { .text = ChatStatusText(fullCount, onlineCount, true) }; return { .text = ChatStatusText(fullCount, onlineCount, true) };
} else if (auto broadcast = _peer->monoforumBroadcast()) {
auto result = ChatStatusText(
qMax(broadcast->membersCount(), 1),
0,
false);
return TextWithEntities{ .text = result };
} else if (auto channel = _peer->asChannel()) { } else if (auto channel = _peer->asChannel()) {
const auto onlineCount = _onlineCount.current(); const auto onlineCount = _onlineCount.current();
const auto fullCount = qMax(channel->membersCount(), 1); const auto fullCount = qMax(channel->membersCount(), 1);

View file

@ -99,8 +99,10 @@ object_ptr<Ui::RpWidget> InnerWidget::setupContent(
result->add(std::move(actions)); result->add(std::move(actions));
} }
if (_peer->isChat() || _peer->isMegagroup()) { if (_peer->isChat() || _peer->isMegagroup()) {
if (!_peer->isMonoforum()) {
setupMembers(result.data()); setupMembers(result.data());
} }
}
return result; return result;
} }

View file

@ -181,7 +181,7 @@ private:
}; };
struct Info : public RuntimeComponent<Info, LayoutItemBase> { struct Info : RuntimeComponent<Info, LayoutItemBase> {
int top = 0; int top = 0;
}; };

View file

@ -49,6 +49,7 @@ msgReplyBarSize: size(2px, 36px);
msgReplyBarSkip: 10px; msgReplyBarSkip: 10px;
msgServicePadding: margins(12px, 3px, 12px, 4px); msgServicePadding: margins(12px, 3px, 12px, 4px);
msgServiceMargin: margins(10px, 10px, 10px, 2px); msgServiceMargin: margins(10px, 10px, 10px, 2px);
monoforumBarUserpicSkip: 2px;
msgDateSpace: 12px; msgDateSpace: 12px;
msgDateDelta: point(2px, 5px); msgDateDelta: point(2px, 5px);

View file

@ -180,12 +180,14 @@ UserpicButton::UserpicButton(
not_null<PeerData*> peer, not_null<PeerData*> peer,
Role role, Role role,
Source source, Source source,
const style::UserpicButton &st) const style::UserpicButton &st,
bool forceForumShape)
: RippleButton(parent, st.changeButton.ripple) : RippleButton(parent, st.changeButton.ripple)
, _st(st) , _st(st)
, _controller(controller) , _controller(controller)
, _window(&controller->window()) , _window(&controller->window())
, _peer(peer) , _peer(peer)
, _forceForumShape(forceForumShape)
, _role(role) , _role(role)
, _source(source) { , _source(source) {
if (_source == Source::Custom) { if (_source == Source::Custom) {

View file

@ -69,7 +69,8 @@ public:
not_null<PeerData*> peer, not_null<PeerData*> peer,
Role role, Role role,
Source source, Source source,
const style::UserpicButton &st); const style::UserpicButton &st,
bool forceForumShape = false);
UserpicButton( UserpicButton(
QWidget *parent, QWidget *parent,
not_null<PeerData*> peer, // Role::Custom, Source::PeerPhoto not_null<PeerData*> peer, // Role::Custom, Source::PeerPhoto