Support group-like channels.

This commit is contained in:
John Preston 2024-08-13 13:51:11 +02:00
parent 24a7e48b75
commit 9ea495f59d
17 changed files with 152 additions and 46 deletions

View file

@ -3584,6 +3584,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_edit_channel_title" = "Edit channel"; "lng_edit_channel_title" = "Edit channel";
"lng_edit_bot_title" = "Edit bot"; "lng_edit_bot_title" = "Edit bot";
"lng_edit_sign_messages" = "Sign messages"; "lng_edit_sign_messages" = "Sign messages";
"lng_edit_sign_messages_about" = "Add names of admins to the messages they post.";
"lng_edit_sign_profiles" = "Show authors' profiles";
"lng_edit_sign_profiles_about" = "Add names and photos of admins to the messages they post, linking to their profiles.";
"lng_edit_group" = "Edit group"; "lng_edit_group" = "Edit group";
"lng_edit_channel_color" = "Change name color"; "lng_edit_channel_color" = "Change name color";
"lng_edit_channel_level_min" = "Level 1+"; "lng_edit_channel_level_min" = "Level 1+";

View file

@ -41,14 +41,14 @@ void InnerFillMessagePostFlags(
const SendOptions &options, const SendOptions &options,
not_null<PeerData*> peer, not_null<PeerData*> peer,
MessageFlags &flags) { MessageFlags &flags) {
const auto anonymousPost = peer->amAnonymous();
if (ShouldSendSilent(peer, options)) { if (ShouldSendSilent(peer, options)) {
flags |= MessageFlag::Silent; flags |= MessageFlag::Silent;
} }
if (!anonymousPost || options.sendAs) { if (!peer->amAnonymous()) {
flags |= MessageFlag::HasFromId; flags |= MessageFlag::HasFromId;
return; }
} else if (peer->asMegagroup()) { const auto channel = peer->asBroadcast();
if (!channel) {
return; return;
} }
flags |= MessageFlag::Post; flags |= MessageFlag::Post;
@ -57,7 +57,7 @@ void InnerFillMessagePostFlags(
return; return;
} }
flags |= MessageFlag::HasViews; flags |= MessageFlag::HasViews;
if (peer->asChannel()->addsSignature()) { if (channel->addsSignature()) {
flags |= MessageFlag::HasPostAuthor; flags |= MessageFlag::HasPostAuthor;
} }
} }

View file

@ -3282,9 +3282,9 @@ void ApiWrap::forwardMessages(
if (!action.options.scheduled && !action.options.shortcutId) { if (!action.options.scheduled && !action.options.shortcutId) {
histories.readInbox(history); histories.readInbox(history);
} }
const auto sendAs = action.options.sendAs;
const auto anonymousPost = peer->amAnonymous(); const auto anonymousPost = peer->amAnonymous();
const auto silentPost = ShouldSendSilent(peer, action.options); const auto silentPost = ShouldSendSilent(peer, action.options);
const auto sendAs = action.options.sendAs;
using SendFlag = MTPmessages_ForwardMessages::Flag; using SendFlag = MTPmessages_ForwardMessages::Flag;
auto flags = MessageFlags(); auto flags = MessageFlags();

View file

@ -1057,19 +1057,50 @@ void Controller::fillSignaturesButton() {
return; return;
} }
AddButtonWithText( const auto signs = AddButtonWithText(
_controls.buttonsLayout, _controls.buttonsLayout,
tr::lng_edit_sign_messages(), tr::lng_edit_sign_messages(),
rpl::single(QString()), rpl::single(QString()),
[] {}, [] {},
{ &st::menuIconSigned } { &st::menuIconSigned }
)->toggleOn(rpl::single(channel->addsSignature()) )->toggleOn(rpl::single(channel->addsSignature()));
)->toggledValue(
const auto profiles = _controls.buttonsLayout->add(
object_ptr<Ui::SlideWrap<Ui::SettingsButton>>(
_controls.buttonsLayout,
EditPeerInfoBox::CreateButton(
_controls.buttonsLayout,
tr::lng_edit_sign_profiles(),
rpl::single(QString()),
[] {},
st::manageGroupTopButtonWithText,
{ &st::menuIconSigned })));
profiles->toggleOn(signs->toggledValue());
profiles->finishAnimating();
profiles->entity()->toggleOn(rpl::single(
channel->addsSignature() && channel->signatureProfiles()
))->toggledValue(
) | rpl::start_with_next([=](bool toggled) {
_signatureProfilesSavedValue = toggled;
}, profiles->entity()->lifetime());
signs->toggledValue(
) | rpl::start_with_next([=](bool toggled) { ) | rpl::start_with_next([=](bool toggled) {
_signaturesSavedValue = toggled; _signaturesSavedValue = toggled;
if (!toggled) {
_signatureProfilesSavedValue = false;
}
}, _controls.buttonsLayout->lifetime()); }, _controls.buttonsLayout->lifetime());
_signatureProfilesSavedValue = channel->signatureProfiles(); Ui::AddSkip(_controls.buttonsLayout);
Ui::AddDividerText(
_controls.buttonsLayout,
rpl::conditional(
signs->toggledValue(),
tr::lng_edit_sign_profiles_about(Ui::Text::WithEntities),
tr::lng_edit_sign_messages_about(Ui::Text::WithEntities)));
Ui::AddSkip(_controls.buttonsLayout);
} }
void Controller::fillHistoryVisibilityButton() { void Controller::fillHistoryVisibilityButton() {
@ -1227,11 +1258,9 @@ void Controller::fillManageSection() {
} }
if (canEditSignatures) { if (canEditSignatures) {
fillSignaturesButton(); fillSignaturesButton();
} } else if (canEditPreHistoryHidden
if (canEditPreHistoryHidden
|| canEditForum || canEditForum
|| canEditColorIndex || canEditColorIndex
|| canEditSignatures
//|| canEditInviteLinks //|| canEditInviteLinks
|| canViewOrEditLinkedChat || canViewOrEditLinkedChat
|| canEditType) { || canEditType) {

View file

@ -184,7 +184,11 @@ void ChannelData::setFlags(ChannelDataFlags which) {
}); });
} }
} }
if (diff & (Flag::Forum | Flag::CallNotEmpty | Flag::SimilarExpanded)) { if (diff & (Flag::Forum
| Flag::CallNotEmpty
| Flag::SimilarExpanded
| Flag::Signatures
| Flag::SignatureProfiles)) {
if (const auto history = this->owner().historyLoaded(this)) { if (const auto history = this->owner().historyLoaded(this)) {
if (diff & Flag::CallNotEmpty) { if (diff & Flag::CallNotEmpty) {
history->updateChatListEntry(); history->updateChatListEntry();
@ -203,6 +207,12 @@ void ChannelData::setFlags(ChannelDataFlags which) {
history->owner().requestItemResize(item); history->owner().requestItemResize(item);
} }
} }
if (diff & Flag::SignatureProfiles) {
history->forceFullResize();
}
if (diff & (Flag::Signatures | Flag::SignatureProfiles)) {
session().changes().peerUpdated(this, UpdateFlag::Rights);
}
} }
} }
if (const auto raw = taken.get()) { if (const auto raw = taken.get()) {

View file

@ -1268,9 +1268,12 @@ Data::RestrictionCheckResult PeerData::amRestricted(
} }
bool PeerData::amAnonymous() const { bool PeerData::amAnonymous() const {
return isBroadcast() if (const auto channel = asChannel()) {
|| (isChannel() return channel->isBroadcast()
&& (asChannel()->adminRights() & ChatAdminRight::Anonymous)); ? !channel->signatureProfiles()
: (channel->adminRights() & ChatAdminRight::Anonymous);
}
return false;
} }
bool PeerData::canRevokeFullHistory() const { bool PeerData::canRevokeFullHistory() const {

View file

@ -313,19 +313,20 @@ enum class MessageFlag : uint64 {
// If not set then we need to refresh _displayFrom value. // If not set then we need to refresh _displayFrom value.
DisplayFromChecked = (1ULL << 40), DisplayFromChecked = (1ULL << 40),
DisplayFromProfiles = (1ULL << 41),
ShowSimilarChannels = (1ULL << 41), ShowSimilarChannels = (1ULL << 42),
Sponsored = (1ULL << 42), Sponsored = (1ULL << 43),
ReactionsAreTags = (1ULL << 43), ReactionsAreTags = (1ULL << 44),
ShortcutMessage = (1ULL << 44), ShortcutMessage = (1ULL << 45),
EffectWatched = (1ULL << 45), EffectWatched = (1ULL << 46),
SensitiveContent = (1ULL << 46), SensitiveContent = (1ULL << 47),
AllowSensitive = (1ULL << 47), AllowSensitive = (1ULL << 48),
}; };
inline constexpr bool is_flag_type(MessageFlag) { return true; } inline constexpr bool is_flag_type(MessageFlag) { return true; }
using MessageFlags = base::flags<MessageFlag>; using MessageFlags = base::flags<MessageFlag>;

View file

@ -730,8 +730,8 @@ bool HistoryInner::canHaveFromUserpics() const {
&& !_peer->isRepliesChat() && !_peer->isRepliesChat()
&& !_isChatWide) { && !_isChatWide) {
return false; return false;
} else if (_peer->isChannel() && !_peer->isMegagroup()) { } else if (const auto channel = _peer->asBroadcast()) {
return false; return channel->signatureProfiles();
} }
return true; return true;
} }

View file

@ -1232,10 +1232,19 @@ PeerData *HistoryItem::computeDisplayFrom() const {
} }
PeerData *HistoryItem::displayFrom() const { PeerData *HistoryItem::displayFrom() const {
if (!(_flags & MessageFlag::DisplayFromChecked)) { if (_flags & MessageFlag::DisplayFromChecked) {
_flags |= MessageFlag::DisplayFromChecked; const auto showing = isPostShowingAuthor();
_displayFrom = computeDisplayFrom(); const auto flag = (_flags & MessageFlag::DisplayFromProfiles);
if (showing && !flag) {
_flags |= MessageFlag::DisplayFromProfiles;
} else if (!showing && flag) {
_flags &= ~MessageFlag::DisplayFromProfiles;
} else {
return _displayFrom;
}
} }
_flags |= MessageFlag::DisplayFromChecked;
_displayFrom = computeDisplayFrom();
return _displayFrom; return _displayFrom;
} }
@ -2148,7 +2157,7 @@ QString HistoryItem::notificationHeader() const {
return QString(); return QString();
} else if (out() && isFromScheduled() && !_history->peer->isSelf()) { } else if (out() && isFromScheduled() && !_history->peer->isSelf()) {
return tr::lng_from_you(tr::now); return tr::lng_from_you(tr::now);
} else if (!_history->peer->isUser() && !isPost()) { } else if (!_history->peer->isUser() && !isPostHidingAuthor()) {
return from()->name(); return from()->name();
} }
return QString(); return QString();
@ -2746,7 +2755,9 @@ bool HistoryItem::inThread(MsgId rootId) const {
} }
not_null<PeerData*> HistoryItem::author() const { not_null<PeerData*> HistoryItem::author() const {
return (isPost() && !isSponsored()) ? _history->peer : from(); return (isPostHidingAuthor() && !isSponsored())
? _history->peer
: from();
} }
TimeId HistoryItem::originalDate() const { TimeId HistoryItem::originalDate() const {
@ -3092,6 +3103,23 @@ bool HistoryItem::isUploading() const {
return _media && _media->uploading(); return _media && _media->uploading();
} }
bool HistoryItem::hasRealFromId() const {
return !isPost() || (_flags & MessageFlag::HasFromId);
}
bool HistoryItem::isPostHidingAuthor() const {
if (!isPost()) {
return false;
} else if (const auto channel = _history->peer->asBroadcast()) {
return !channel->signatureProfiles();
}
return false; // Should not happen, I guess.
}
bool HistoryItem::isPostShowingAuthor() const {
return isPost() && !isPostHidingAuthor();
}
bool HistoryItem::isRegular() const { bool HistoryItem::isRegular() const {
return isHistoryEntry() && !isLocal(); return isHistoryEntry() && !isLocal();
} }
@ -3464,7 +3492,7 @@ ItemPreview HistoryItem::toPreview(ToPreviewOptions options) const {
return {}; return {};
}; };
const auto sender = [&]() -> std::optional<QString> { const auto sender = [&]() -> std::optional<QString> {
if (options.hideSender || isPost() || isEmpty()) { if (options.hideSender || isPostHidingAuthor() || isEmpty()) {
return {}; return {};
} else if (!_history->peer->isUser()) { } else if (!_history->peer->isUser()) {
if (const auto from = displayFrom()) { if (const auto from = displayFrom()) {

View file

@ -327,6 +327,9 @@ public:
[[nodiscard]] bool showSimilarChannels() const { [[nodiscard]] bool showSimilarChannels() const {
return _flags & MessageFlag::ShowSimilarChannels; return _flags & MessageFlag::ShowSimilarChannels;
} }
[[nodiscard]] bool hasRealFromId() const;
[[nodiscard]] bool isPostHidingAuthor() const;
[[nodiscard]] bool isPostShowingAuthor() const;
[[nodiscard]] bool isRegular() const; [[nodiscard]] bool isRegular() const;
[[nodiscard]] bool isUploading() const; [[nodiscard]] bool isUploading() const;
void sendFailed(); void sendFailed();

View file

@ -822,6 +822,14 @@ HistoryWidget::HistoryWidget(
updateStickersByEmoji(); updateStickersByEmoji();
updateFieldPlaceholder(); updateFieldPlaceholder();
_preview->checkNow(false); _preview->checkNow(false);
const auto was = (_sendAs != nullptr);
refreshSendAsToggle();
if (was != (_sendAs != nullptr)) {
updateControlsVisibility();
updateControlsGeometry();
orderWidgets();
}
} }
if (flags & PeerUpdateFlag::Migration) { if (flags & PeerUpdateFlag::Migration) {
handlePeerMigration(); handlePeerMigration();

View file

@ -23,6 +23,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "core/click_handler_types.h" #include "core/click_handler_types.h"
#include "main/main_session.h" #include "main/main_session.h"
#include "lottie/lottie_icon.h" #include "lottie/lottie_icon.h"
#include "data/data_channel.h"
#include "data/data_session.h" #include "data/data_session.h"
#include "data/data_message_reactions.h" #include "data/data_message_reactions.h"
#include "window/window_session_controller.h" #include "window/window_session_controller.h"
@ -570,10 +571,14 @@ BottomInfo::Data BottomInfoDataFromMessage(not_null<Message*> message) {
if (message->context() == Context::ShortcutMessages) { if (message->context() == Context::ShortcutMessages) {
result.flags |= Flag::Shortcut; result.flags |= Flag::Shortcut;
} }
if (const auto msgsigned = item->Get<HistoryMessageSigned>()) { if (!item->isPost()
if (!msgsigned->isAnonymousRank) { || !item->hasRealFromId()
result.author = msgsigned->author; || !item->history()->peer->asChannel()->signatureProfiles()) {
} if (const auto msgsigned = item->Get<HistoryMessageSigned>()) {
if (!msgsigned->isAnonymousRank) {
result.author = msgsigned->author;
}
}
} }
if (message->displayedEditDate()) { if (message->displayedEditDate()) {
result.flags |= Flag::Edited; result.flags |= Flag::Edited;

View file

@ -1087,7 +1087,7 @@ bool Element::computeIsAttachToPrevious(not_null<Element*> previous) {
const auto item = view->data(); const auto item = view->data();
return !item->isService() return !item->isService()
&& !item->isEmpty() && !item->isEmpty()
&& !item->isPost() && !item->isPostHidingAuthor()
&& (!item->history()->peer->isMegagroup() && (!item->history()->peer->isMegagroup()
|| !view->hasOutLayout() || !view->hasOutLayout()
|| !item->from()->isChannel()); || !item->from()->isChannel());

View file

@ -408,6 +408,7 @@ Message::Message(
Element *replacing) Element *replacing)
: Element(delegate, data, replacing, Flag(0)) : Element(delegate, data, replacing, Flag(0))
, _hideReply(delegate->elementHideReply(this)) , _hideReply(delegate->elementHideReply(this))
, _postShowingAuthor(data->isPostShowingAuthor() ? 1 : 0)
, _bottomInfo( , _bottomInfo(
&data->history()->owner().reactions(), &data->history()->owner().reactions(),
BottomInfoDataFromMessage(this)) { BottomInfoDataFromMessage(this)) {
@ -2272,10 +2273,11 @@ bool Message::hasFromPhoto() const {
case Context::SavedSublist: case Context::SavedSublist:
case Context::ScheduledTopic: { case Context::ScheduledTopic: {
const auto item = data(); const auto item = data();
if (item->isPost()) { if (item->isPostHidingAuthor()) {
return false; return false;
} } else if (item->isPost()) {
if (item->isEmpty() return true;
} else if (item->isEmpty()
|| (context() == Context::Replies && item->isDiscussionPost())) { || (context() == Context::Replies && item->isDiscussionPost())) {
return false; return false;
} else if (delegate()->elementIsChatWide()) { } else if (delegate()->elementIsChatWide()) {
@ -4244,6 +4246,14 @@ int Message::resizeContentGetHeight(int newWidth) {
const auto mediaDisplayed = media ? media->isDisplayed() : false; const auto mediaDisplayed = media ? media->isDisplayed() : false;
const auto bubble = drawBubble(); const auto bubble = drawBubble();
const auto postShowingAuthor = item->isPostShowingAuthor() ? 1 : 0;
if (_postShowingAuthor != postShowingAuthor) {
_postShowingAuthor = postShowingAuthor;
_bottomInfo.update(BottomInfoDataFromMessage(this), newWidth);
_fromNameVersion = -1;
previousInBlocksChanged();
}
item->resolveDependent(); item->resolveDependent();
// This code duplicates countGeometry() but also resizes media. // This code duplicates countGeometry() but also resizes media.

View file

@ -315,10 +315,11 @@ private:
mutable std::unique_ptr<FromNameStatus> _fromNameStatus; mutable std::unique_ptr<FromNameStatus> _fromNameStatus;
Ui::Text::String _rightBadge; Ui::Text::String _rightBadge;
mutable int _fromNameVersion = 0; mutable int _fromNameVersion = 0;
uint32 _bubbleWidthLimit : 29 = 0; uint32 _bubbleWidthLimit : 28 = 0;
uint32 _invertMedia : 1 = 0; uint32 _invertMedia : 1 = 0;
uint32 _hideReply : 1 = 0; uint32 _hideReply : 1 = 0;
uint32 _rightBadgeHasBoosts : 1 = 0; uint32 _rightBadgeHasBoosts : 1 = 0;
uint32 _postShowingAuthor : 1 = 0;
BottomInfo _bottomInfo; BottomInfo _bottomInfo;

View file

@ -43,11 +43,16 @@ SendAsPeers::SendAsPeers(not_null<Session*> session)
bool SendAsPeers::shouldChoose(not_null<PeerData*> peer) { bool SendAsPeers::shouldChoose(not_null<PeerData*> peer) {
refresh(peer); refresh(peer);
return Data::CanSendAnything(peer, false) && (list(peer).size() > 1); const auto channel = peer->asBroadcast();
return Data::CanSendAnything(peer, false)
&& (list(peer).size() > 1)
&& (!channel
|| channel->addsSignature()
|| channel->signatureProfiles());
} }
void SendAsPeers::refresh(not_null<PeerData*> peer, bool force) { void SendAsPeers::refresh(not_null<PeerData*> peer, bool force) {
if (!peer->isMegagroup()) { if (!peer->isChannel()) {
return; return;
} }
const auto now = crl::now(); const auto now = crl::now();
@ -117,7 +122,7 @@ not_null<PeerData*> SendAsPeers::ResolveChosen(
? i->peer ? i->peer
: !list.empty() : !list.empty()
? list.front().peer ? list.front().peer
: (peer->isMegagroup() && peer->amAnonymous()) : peer->amAnonymous()
? peer ? peer
: peer->session().user(); : peer->session().user();
} }

View file

@ -263,9 +263,9 @@ void SetupSendAsButton(
auto userpic = current->value( auto userpic = current->value(
) | rpl::filter([=](PeerData *peer) { ) | rpl::filter([=](PeerData *peer) {
return peer && peer->isMegagroup(); return peer && peer->isChannel();
}) | rpl::map([=](not_null<PeerData*> peer) { }) | rpl::map([=](not_null<PeerData*> peer) {
const auto channel = peer->asMegagroup(); const auto channel = peer->asChannel();
auto updates = rpl::single( auto updates = rpl::single(
rpl::empty rpl::empty