Ask for boosts to unlock group restrictions.

This commit is contained in:
John Preston 2025-01-02 23:52:49 +04:00
parent a7ae7a8cda
commit 5f10c1875c
38 changed files with 587 additions and 389 deletions

View file

@ -3267,13 +3267,13 @@ void ApiWrap::finishForwarding(const SendAction &action) {
const auto topicRootId = action.replyTo.topicRootId; const auto topicRootId = action.replyTo.topicRootId;
auto toForward = history->resolveForwardDraft(topicRootId); auto toForward = history->resolveForwardDraft(topicRootId);
if (!toForward.items.empty()) { if (!toForward.items.empty()) {
const auto error = GetErrorTextForSending( const auto error = GetErrorForSending(
history->peer, history->peer,
{ {
.topicRootId = topicRootId, .topicRootId = topicRootId,
.forward = &toForward.items, .forward = &toForward.items,
}); });
if (!error.isEmpty()) { if (error) {
return; return;
} }

View file

@ -26,7 +26,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_user.h" #include "data/data_user.h"
#include "data/stickers/data_custom_emoji.h" #include "data/stickers/data_custom_emoji.h"
#include "history/history.h" #include "history/history.h"
#include "history/history_item_helpers.h" // GetErrorTextForSending. #include "history/history_item_helpers.h" // GetErrorForSending.
#include "history/view/history_view_group_call_bar.h" // GenerateUserpics... #include "history/view/history_view_group_call_bar.h" // GenerateUserpics...
#include "lang/lang_keys.h" #include "lang/lang_keys.h"
#include "main/main_session.h" #include "main/main_session.h"
@ -1492,27 +1492,14 @@ object_ptr<Ui::BoxContent> ShareInviteLinkBox(
return; return;
} }
const auto error = [&] { const auto errorWithThread = GetErrorForSending(
for (const auto thread : result) { result,
const auto error = GetErrorTextForSending( { .text = &comment });
thread, if (errorWithThread.error) {
{ .text = &comment });
if (!error.isEmpty()) {
return std::make_pair(error, thread);
}
}
return std::make_pair(QString(), result.front());
}();
if (!error.first.isEmpty()) {
auto text = TextWithEntities();
if (result.size() > 1) {
text.append(
Ui::Text::Bold(error.second->chatListName())
).append("\n\n");
}
text.append(error.first);
if (*box) { if (*box) {
(*box)->uiShow()->showBox(Ui::MakeInformBox(text)); (*box)->uiShow()->showBox(MakeSendErrorBox(
errorWithThread,
result.size() > 1));
} }
return; return;
} }

View file

@ -220,7 +220,7 @@ SendFilesCheck DefaultCheckForPeer(
} }
SendFilesCheck DefaultCheckForPeer( SendFilesCheck DefaultCheckForPeer(
std::shared_ptr<Ui::Show> show, std::shared_ptr<ChatHelpers::Show> show,
not_null<PeerData*> peer) { not_null<PeerData*> peer) {
return [=]( return [=](
const Ui::PreparedFile &file, const Ui::PreparedFile &file,
@ -228,7 +228,7 @@ SendFilesCheck DefaultCheckForPeer(
bool silent) { bool silent) {
const auto error = Data::FileRestrictionError(peer, file, compress); const auto error = Data::FileRestrictionError(peer, file, compress);
if (error && !silent) { if (error && !silent) {
show->showToast(*error); Data::ShowSendErrorToast(show, peer, error);
} }
return !error.has_value(); return !error.has_value();
}; };

View file

@ -80,7 +80,7 @@ using SendFilesCheck = Fn<bool(
not_null<Window::SessionController*> controller, not_null<Window::SessionController*> controller,
not_null<PeerData*> peer); not_null<PeerData*> peer);
[[nodiscard]] SendFilesCheck DefaultCheckForPeer( [[nodiscard]] SendFilesCheck DefaultCheckForPeer(
std::shared_ptr<Ui::Show> show, std::shared_ptr<ChatHelpers::Show> show,
not_null<PeerData*> peer); not_null<PeerData*> peer);
using SendFilesConfirmed = Fn<void( using SendFilesConfirmed = Fn<void(

View file

@ -1508,26 +1508,11 @@ ShareBox::SubmitCallback ShareBox::DefaultForwardCallback(
return; return;
} }
const auto error = [&] { const auto error = GetErrorForSending(
for (const auto thread : result) { result,
const auto error = GetErrorTextForSending( { .forward = &items, .text = &comment });
thread, if (error.error) {
{ .forward = &items, .text = &comment }); show->showBox(MakeSendErrorBox(error, result.size() > 1));
if (!error.isEmpty()) {
return std::make_pair(error, thread);
}
}
return std::make_pair(QString(), result.front());
}();
if (!error.first.isEmpty()) {
auto text = TextWithEntities();
if (result.size() > 1) {
text.append(
Ui::Text::Bold(error.second->chatListName())
).append("\n\n");
}
text.append(error.first);
show->showBox(Ui::MakeInformBox(text));
return; return;
} }
@ -1737,30 +1722,13 @@ void FastShareLink(
return; return;
} }
const auto error = [&] { const auto error = GetErrorForSending(
for (const auto thread : result) { result,
const auto error = GetErrorTextForSending( { .text = &comment });
thread, if (error.error) {
{ .text = &comment });
if (!error.isEmpty()) {
return std::make_pair(error, thread);
}
}
return std::make_pair(QString(), result.front());
}();
if (!error.first.isEmpty()) {
auto text = TextWithEntities();
if (result.size() > 1) {
text.append(
Ui::Text::Bold(error.second->chatListName())
).append("\n\n");
}
text.append(error.first);
if (const auto weak = *box) { if (const auto weak = *box) {
weak->getDelegate()->show(Ui::MakeConfirmBox({ weak->getDelegate()->show(
.text = text, MakeSendErrorBox(error, result.size() > 1));
.inform = true,
}));
} }
return; return;
} }

View file

@ -23,7 +23,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "lang/lang_keys.h" #include "lang/lang_keys.h"
#include "boxes/share_box.h" #include "boxes/share_box.h"
#include "history/view/history_view_schedule_box.h" #include "history/view/history_view_schedule_box.h"
#include "history/history_item_helpers.h" // GetErrorTextForSending. #include "history/history_item_helpers.h" // GetErrorForSending.
#include "history/history.h" #include "history/history.h"
#include "data/data_histories.h" #include "data/data_histories.h"
#include "data/data_session.h" #include "data/data_session.h"
@ -139,30 +139,13 @@ object_ptr<ShareBox> ShareInviteLinkBox(
return; return;
} }
const auto error = [&] { const auto error = GetErrorForSending(
for (const auto thread : result) { result,
const auto error = GetErrorTextForSending( { .text = &comment });
thread, if (error.error) {
{ .text = &comment });
if (!error.isEmpty()) {
return std::make_pair(error, thread);
}
}
return std::make_pair(QString(), result.front());
}();
if (!error.first.isEmpty()) {
auto text = TextWithEntities();
if (result.size() > 1) {
text.append(
Ui::Text::Bold(error.second->chatListName())
).append("\n\n");
}
text.append(error.first);
if (const auto weak = *box) { if (const auto weak = *box) {
weak->getDelegate()->show(ConfirmBox({ weak->getDelegate()->show(
.text = text, MakeSendErrorBox(error, result.size() > 1));
.inform = true,
}));
} }
return; return;
} }

View file

@ -12,6 +12,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "chat_helpers/gifs_list_widget.h" #include "chat_helpers/gifs_list_widget.h"
#include "menu/menu_send.h" #include "menu/menu_send.h"
#include "ui/controls/tabbed_search.h" #include "ui/controls/tabbed_search.h"
#include "ui/text/text_utilities.h"
#include "ui/widgets/buttons.h" #include "ui/widgets/buttons.h"
#include "ui/widgets/labels.h" #include "ui/widgets/labels.h"
#include "ui/widgets/shadow.h" #include "ui/widgets/shadow.h"
@ -1042,23 +1043,39 @@ void TabbedSelector::checkRestrictedPeer() {
? Data::RestrictionError( ? Data::RestrictionError(
_currentPeer, _currentPeer,
ChatRestriction::SendOther) ChatRestriction::SendOther)
: std::nullopt) : Data::SendError())
: std::nullopt; : Data::SendError();
if (error) { const auto changed = (_restrictedLabelKey != error.text);
if (!_restrictedLabel) { if (!changed) {
_restrictedLabel.create(
this,
*error,
st::stickersRestrictedLabel);
_restrictedLabel->show();
updateRestrictedLabelGeometry();
currentTab()->footer()->hide();
_scroll->hide();
_bottomShadow->hide();
update();
}
return; return;
} }
_restrictedLabelKey = error.text;
if (error) {
const auto show = _show;
const auto peer = _currentPeer;
_restrictedLabel.create(
this,
rpl::single(error.boostsToLift
? Ui::Text::Link(error.text)
: TextWithEntities{ error.text }),
st::stickersRestrictedLabel);
const auto lifting = error.boostsToLift;
_restrictedLabel->setClickHandlerFilter([=](auto...) {
const auto window = show->resolveWindow(
ChatHelpers::WindowUsage::PremiumPromo);
window->resolveBoostState(peer->asChannel(), lifting);
return false;
});
_restrictedLabel->show();
updateRestrictedLabelGeometry();
currentTab()->footer()->hide();
_scroll->hide();
_bottomShadow->hide();
update();
return;
}
} else {
_restrictedLabelKey = QString();
} }
if (_restrictedLabel) { if (_restrictedLabel) {
_restrictedLabel.destroy(); _restrictedLabel.destroy();

View file

@ -309,6 +309,7 @@ private:
object_ptr<Ui::PlainShadow> _bottomShadow; object_ptr<Ui::PlainShadow> _bottomShadow;
object_ptr<Ui::ScrollArea> _scroll; object_ptr<Ui::ScrollArea> _scroll;
object_ptr<Ui::FlatLabel> _restrictedLabel = { nullptr }; object_ptr<Ui::FlatLabel> _restrictedLabel = { nullptr };
QString _restrictedLabelKey;
std::vector<Tab> _tabs; std::vector<Tab> _tabs;
SelectorTab _currentTabType = SelectorTab::Emoji; SelectorTab _currentTabType = SelectorTab::Emoji;

View file

@ -9,6 +9,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "base/unixtime.h" #include "base/unixtime.h"
#include "boxes/peers/edit_peer_permissions_box.h" #include "boxes/peers/edit_peer_permissions_box.h"
#include "chat_helpers/compose/compose_show.h"
#include "data/data_chat.h" #include "data/data_chat.h"
#include "data/data_channel.h" #include "data/data_channel.h"
#include "data/data_forum_topic.h" #include "data/data_forum_topic.h"
@ -17,6 +18,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "lang/lang_keys.h" #include "lang/lang_keys.h"
#include "main/main_session.h" #include "main/main_session.h"
#include "ui/chat/attach/attach_prepare.h" #include "ui/chat/attach/attach_prepare.h"
#include "ui/text/text_utilities.h"
#include "ui/toast/toast.h"
#include "window/window_session_controller.h"
namespace { namespace {
@ -167,7 +171,7 @@ bool CanSendAnyOf(
Unexpected("Peer type in CanSendAnyOf."); Unexpected("Peer type in CanSendAnyOf.");
} }
std::optional<QString> RestrictionError( SendError RestrictionError(
not_null<PeerData*> peer, not_null<PeerData*> peer,
ChatRestriction restriction) { ChatRestriction restriction) {
using Flag = ChatRestriction; using Flag = ChatRestriction;
@ -175,10 +179,13 @@ std::optional<QString> RestrictionError(
if (const auto user = peer->asUser()) { if (const auto user = peer->asUser()) {
if (user->meRequiresPremiumToWrite() if (user->meRequiresPremiumToWrite()
&& !user->session().premium()) { && !user->session().premium()) {
return tr::lng_restricted_send_non_premium( return SendError({
tr::now, .text = tr::lng_restricted_send_non_premium(
lt_user, tr::now,
user->shortName()); lt_user,
user->shortName()),
.premiumToLift = true,
});
} }
const auto result = (restriction == Flag::SendVoiceMessages) const auto result = (restriction == Flag::SendVoiceMessages)
? tr::lng_restricted_send_voice_messages( ? tr::lng_restricted_send_voice_messages(
@ -194,7 +201,7 @@ std::optional<QString> RestrictionError(
? u"can't send polls :("_q ? u"can't send polls :("_q
: (restriction == Flag::PinMessages) : (restriction == Flag::PinMessages)
? u"can't pin :("_q ? u"can't pin :("_q
: std::optional<QString>(); : SendError();
Ensures(result.has_value()); Ensures(result.has_value());
return result; return result;
@ -253,6 +260,15 @@ std::optional<QString> RestrictionError(
Unexpected("Restriction in Data::RestrictionErrorKey."); Unexpected("Restriction in Data::RestrictionErrorKey.");
} }
} }
if (all
&& channel->boostsUnrestrict()
&& !channel->unrestrictedByBoosts()) {
return SendError({
.text = tr::lng_restricted_boost_group(tr::now),
.boostsToLift = (channel->boostsUnrestrict()
- channel->boostsApplied()),
});
}
switch (restriction) { switch (restriction) {
case Flag::SendPolls: case Flag::SendPolls:
return all return all
@ -302,10 +318,10 @@ std::optional<QString> RestrictionError(
} }
Unexpected("Restriction in Data::RestrictionErrorKey."); Unexpected("Restriction in Data::RestrictionErrorKey.");
} }
return std::nullopt; return SendError();
} }
std::optional<QString> AnyFileRestrictionError(not_null<PeerData*> peer) { SendError AnyFileRestrictionError(not_null<PeerData*> peer) {
using Restriction = ChatRestriction; using Restriction = ChatRestriction;
for (const auto right : FilesSendRestrictionsList()) { for (const auto right : FilesSendRestrictionsList()) {
if (!RestrictionError(peer, right)) { if (!RestrictionError(peer, right)) {
@ -315,7 +331,7 @@ std::optional<QString> AnyFileRestrictionError(not_null<PeerData*> peer) {
return RestrictionError(peer, Restriction::SendFiles); return RestrictionError(peer, Restriction::SendFiles);
} }
std::optional<QString> FileRestrictionError( SendError FileRestrictionError(
not_null<PeerData*> peer, not_null<PeerData*> peer,
const Ui::PreparedList &list, const Ui::PreparedList &list,
std::optional<bool> compress) { std::optional<bool> compress) {
@ -339,7 +355,7 @@ std::optional<QString> FileRestrictionError(
return {}; return {};
} }
std::optional<QString> FileRestrictionError( SendError FileRestrictionError(
not_null<PeerData*> peer, not_null<PeerData*> peer,
const Ui::PreparedFile &file, const Ui::PreparedFile &file,
std::optional<bool> compress) { std::optional<bool> compress) {
@ -383,4 +399,32 @@ std::optional<QString> FileRestrictionError(
return {}; return {};
} }
void ShowSendErrorToast(
not_null<Window::SessionNavigation*> navigation,
not_null<PeerData*> peer,
Data::SendError error) {
return ShowSendErrorToast(navigation->uiShow(), peer, error);
}
void ShowSendErrorToast(
std::shared_ptr<ChatHelpers::Show> show,
not_null<PeerData*> peer,
Data::SendError error) {
Expects(peer->isChannel());
if (!error.boostsToLift) {
show->showToast(*error);
return;
}
const auto boost = [=] {
const auto window = show->resolveWindow(
ChatHelpers::WindowUsage::PremiumPromo);
window->resolveBoostState(peer->asChannel(), error.boostsToLift);
};
show->showToast({
.text = Ui::Text::Link(*error),
.filter = [=](const auto &...) { boost(); return false; },
});
}
} // namespace Data } // namespace Data

View file

@ -7,11 +7,19 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/ */
#pragma once #pragma once
namespace ChatHelpers {
class Show;
} // namespace ChatHelpers
namespace Ui { namespace Ui {
struct PreparedList; struct PreparedList;
struct PreparedFile; struct PreparedFile;
} // namespace Ui } // namespace Ui
namespace Window {
class SessionNavigation;
} // namespace Window
enum class ChatAdminRight { enum class ChatAdminRight {
ChangeInfo = (1 << 0), ChangeInfo = (1 << 0),
PostMessages = (1 << 1), PostMessages = (1 << 1),
@ -175,18 +183,65 @@ struct RestrictionsSetOptions {
return CanSendAnyOf(peer, AllSendRestrictions(), forbidInForums); return CanSendAnyOf(peer, AllSendRestrictions(), forbidInForums);
} }
[[nodiscard]] std::optional<QString> RestrictionError( struct SendError {
SendError(QString text = QString()) : text(std::move(text)) {
}
struct Args {
QString text;
int boostsToLift = 0;
bool premiumToLift = false;
};
SendError(Args &&args)
: text(std::move(args.text))
, boostsToLift(args.boostsToLift)
, premiumToLift(args.premiumToLift) {
}
QString text;
int boostsToLift = 0;
bool premiumToLift = false;
[[nodiscard]] SendError value_or(SendError other) const {
return *this ? *this : other;
}
explicit operator bool() const {
return !text.isEmpty();
}
[[nodiscard]] bool has_value() const {
return !text.isEmpty();
}
[[nodiscard]] const QString &operator*() const {
return text;
}
};
struct SendErrorWithThread {
SendError error;
Thread *thread = nullptr;
};
[[nodiscard]] SendError RestrictionError(
not_null<PeerData*> peer, not_null<PeerData*> peer,
ChatRestriction restriction); ChatRestriction restriction);
[[nodiscard]] std::optional<QString> AnyFileRestrictionError( [[nodiscard]] SendError AnyFileRestrictionError(not_null<PeerData*> peer);
not_null<PeerData*> peer); [[nodiscard]] SendError FileRestrictionError(
[[nodiscard]] std::optional<QString> FileRestrictionError(
not_null<PeerData*> peer, not_null<PeerData*> peer,
const Ui::PreparedList &list, const Ui::PreparedList &list,
std::optional<bool> compress); std::optional<bool> compress);
[[nodiscard]] std::optional<QString> FileRestrictionError( [[nodiscard]] SendError FileRestrictionError(
not_null<PeerData*> peer, not_null<PeerData*> peer,
const Ui::PreparedFile &file, const Ui::PreparedFile &file,
std::optional<bool> compress); std::optional<bool> compress);
void ShowSendErrorToast(
not_null<Window::SessionNavigation*> navigation,
not_null<PeerData*> peer,
SendError error);
void ShowSendErrorToast(
std::shared_ptr<ChatHelpers::Show> show,
not_null<PeerData*> peer,
SendError error);
} // namespace Data } // namespace Data

View file

@ -423,7 +423,7 @@ bool Story::hasDirectLink() const {
return !_peer->username().isEmpty(); return !_peer->username().isEmpty();
} }
std::optional<QString> Story::errorTextForForward( Data::SendError Story::errorTextForForward(
not_null<Thread*> to) const { not_null<Thread*> to) const {
const auto peer = to->peer(); const auto peer = to->peer();
const auto holdsPhoto = v::is<not_null<PhotoData*>>(_media.data); const auto holdsPhoto = v::is<not_null<PhotoData*>>(_media.data);
@ -433,10 +433,10 @@ std::optional<QString> Story::errorTextForForward(
const auto second = holdsPhoto const auto second = holdsPhoto
? ChatRestriction::SendVideos ? ChatRestriction::SendVideos
: ChatRestriction::SendPhotos; : ChatRestriction::SendPhotos;
if (const auto error = Data::RestrictionError(peer, first)) { if (const auto one = Data::RestrictionError(peer, first)) {
return *error; return one;
} else if (const auto error = Data::RestrictionError(peer, second)) { } else if (const auto two = Data::RestrictionError(peer, second)) {
return *error; return two;
} else if (!Data::CanSend(to, first, false) } else if (!Data::CanSend(to, first, false)
|| !Data::CanSend(to, second, false)) { || !Data::CanSend(to, second, false)) {
return tr::lng_forward_cant(tr::now); return tr::lng_forward_cant(tr::now);

View file

@ -24,6 +24,7 @@ namespace Data {
class Session; class Session;
class Thread; class Thread;
class MediaPreload; class MediaPreload;
struct SendError;
enum class StoryPrivacy : uchar { enum class StoryPrivacy : uchar {
Public, Public,
@ -191,7 +192,7 @@ public:
[[nodiscard]] bool canReport() const; [[nodiscard]] bool canReport() const;
[[nodiscard]] bool hasDirectLink() const; [[nodiscard]] bool hasDirectLink() const;
[[nodiscard]] std::optional<QString> errorTextForForward( [[nodiscard]] Data::SendError errorTextForForward(
not_null<Thread*> to) const; not_null<Thread*> to) const;
void setCaption(TextWithEntities &&caption); void setCaption(TextWithEntities &&caption);

View file

@ -2479,17 +2479,17 @@ bool HistoryItem::requiresSendInlineRight() const {
return Has<HistoryMessageVia>(); return Has<HistoryMessageVia>();
} }
std::optional<QString> HistoryItem::errorTextForForward( Data::SendError HistoryItem::errorTextForForward(
not_null<Data::Thread*> to) const { not_null<Data::Thread*> to) const {
const auto requiredRight = requiredSendRight(); const auto requiredRight = requiredSendRight();
const auto requiresInline = requiresSendInlineRight(); const auto requiresInline = requiresSendInlineRight();
const auto peer = to->peer(); const auto peer = to->peer();
constexpr auto kInline = ChatRestriction::SendInline; constexpr auto kInline = ChatRestriction::SendInline;
if (const auto error = Data::RestrictionError(peer, requiredRight)) { if (const auto error = Data::RestrictionError(peer, requiredRight)) {
return *error; return error;
} else if (requiresInline && !Data::CanSend(to, kInline)) { } else if (requiresInline && !Data::CanSend(to, kInline)) {
return Data::RestrictionError(peer, kInline).value_or( const auto forInline = Data::RestrictionError(peer, kInline);
tr::lng_forward_cant(tr::now)); return forInline ? forInline : tr::lng_forward_cant(tr::now);
} else if (_media } else if (_media
&& _media->poll() && _media->poll()
&& _media->poll()->publicVotes() && _media->poll()->publicVotes()

View file

@ -68,6 +68,7 @@ struct SponsoredFrom;
class Story; class Story;
class SavedSublist; class SavedSublist;
struct PaidReactionSend; struct PaidReactionSend;
struct SendError;
} // namespace Data } // namespace Data
namespace Main { namespace Main {
@ -442,7 +443,7 @@ public:
[[nodiscard]] bool suggestDeleteAllReport() const; [[nodiscard]] bool suggestDeleteAllReport() const;
[[nodiscard]] ChatRestriction requiredSendRight() const; [[nodiscard]] ChatRestriction requiredSendRight() const;
[[nodiscard]] bool requiresSendInlineRight() const; [[nodiscard]] bool requiresSendInlineRight() const;
[[nodiscard]] std::optional<QString> errorTextForForward( [[nodiscard]] Data::SendError errorTextForForward(
not_null<Data::Thread*> to) const; not_null<Data::Thread*> to) const;
[[nodiscard]] const HistoryMessageTranslation *translation() const; [[nodiscard]] const HistoryMessageTranslation *translation() const;
[[nodiscard]] bool translationShowRequiresCheck(LanguageId to) const; [[nodiscard]] bool translationShowRequiresCheck(LanguageId to) const;

View file

@ -38,6 +38,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "base/unixtime.h" #include "base/unixtime.h"
#include "core/application.h" #include "core/application.h"
#include "core/click_handler_types.h" // ClickHandlerContext. #include "core/click_handler_types.h" // ClickHandlerContext.
#include "ui/boxes/confirm_box.h"
#include "ui/text/format_values.h" #include "ui/text/format_values.h"
#include "ui/text/text_utilities.h" #include "ui/text/text_utilities.h"
#include "ui/toast/toast.h" #include "ui/toast/toast.h"
@ -59,7 +60,7 @@ bool PeerCallKnown(not_null<PeerData*> peer) {
} // namespace } // namespace
QString GetErrorTextForSending( Data::SendError GetErrorForSending(
not_null<PeerData*> peer, not_null<PeerData*> peer,
SendingErrorRequest request) { SendingErrorRequest request) {
const auto forum = request.topicRootId ? peer->forum() : nullptr; const auto forum = request.topicRootId ? peer->forum() : nullptr;
@ -71,13 +72,13 @@ QString GetErrorTextForSending(
: peer->owner().history(peer); : peer->owner().history(peer);
if (request.story) { if (request.story) {
if (const auto error = request.story->errorTextForForward(thread)) { if (const auto error = request.story->errorTextForForward(thread)) {
return *error; return error;
} }
} }
if (request.forward) { if (request.forward) {
for (const auto &item : *request.forward) { for (const auto &item : *request.forward) {
if (const auto error = item->errorTextForForward(thread)) { if (const auto error = item->errorTextForForward(thread)) {
return *error; return error;
} }
} }
} }
@ -87,7 +88,7 @@ QString GetErrorTextForSending(
peer, peer,
ChatRestriction::SendOther); ChatRestriction::SendOther);
if (error) { if (error) {
return *error; return error;
} else if (!Data::CanSendTexts(thread)) { } else if (!Data::CanSendTexts(thread)) {
return tr::lng_forward_cant(tr::now); return tr::lng_forward_cant(tr::now);
} }
@ -134,14 +135,58 @@ QString GetErrorTextForSending(
} }
} }
return QString(); return {};
} }
QString GetErrorTextForSending( Data::SendError GetErrorForSending(
not_null<Data::Thread*> thread, not_null<Data::Thread*> thread,
SendingErrorRequest request) { SendingErrorRequest request) {
request.topicRootId = thread->topicRootId(); request.topicRootId = thread->topicRootId();
return GetErrorTextForSending(thread->peer(), std::move(request)); return GetErrorForSending(thread->peer(), std::move(request));
}
Data::SendErrorWithThread GetErrorForSending(
const std::vector<not_null<Data::Thread*>> &threads,
SendingErrorRequest request) {
for (const auto thread : threads) {
const auto error = GetErrorForSending(thread, request);
if (error) {
return Data::SendErrorWithThread{ error, thread };
}
}
return {};
}
object_ptr<Ui::BoxContent> MakeSendErrorBox(
const Data::SendErrorWithThread &error,
bool withTitle) {
Expects(error.error.has_value() && error.thread != nullptr);
auto text = TextWithEntities();
if (withTitle) {
text.append(
Ui::Text::Bold(error.thread->chatListName())
).append("\n\n");
}
if (error.error.boostsToLift) {
text.append(Ui::Text::Link(error.error.text));
} else {
text.append(error.error.text);
}
const auto peer = error.thread->peer();
const auto lifting = error.error.boostsToLift;
const auto filter = [=](const auto &...) {
Expects(peer->isChannel());
const auto window = ChatHelpers::ResolveWindowDefault()(
&peer->session(),
ChatHelpers::WindowUsage::PremiumPromo);
window->resolveBoostState(peer->asChannel(), lifting);
return false;
};
return Ui::MakeInformBox({
.text = text,
.labelFilter = filter,
});
} }
void RequestDependentMessageItem( void RequestDependentMessageItem(

View file

@ -7,6 +7,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/ */
#pragma once #pragma once
#include "base/object_ptr.h"
class History; class History;
namespace Api { namespace Api {
@ -17,12 +19,18 @@ struct SendAction;
namespace Data { namespace Data {
class Story; class Story;
class Thread; class Thread;
struct SendError;
struct SendErrorWithThread;
} // namespace Data } // namespace Data
namespace Main { namespace Main {
class Session; class Session;
} // namespace Main } // namespace Main
namespace Ui {
class BoxContent;
} // namespace Ui
struct PreparedServiceText { struct PreparedServiceText {
TextWithEntities text; TextWithEntities text;
std::vector<ClickHandlerPtr> links; std::vector<ClickHandlerPtr> links;
@ -108,13 +116,20 @@ struct SendingErrorRequest {
const TextWithTags *text = nullptr; const TextWithTags *text = nullptr;
bool ignoreSlowmodeCountdown = false; bool ignoreSlowmodeCountdown = false;
}; };
[[nodiscard]] QString GetErrorTextForSending( [[nodiscard]] Data::SendError GetErrorForSending(
not_null<PeerData*> peer, not_null<PeerData*> peer,
SendingErrorRequest request); SendingErrorRequest request);
[[nodiscard]] QString GetErrorTextForSending( [[nodiscard]] Data::SendError GetErrorForSending(
not_null<Data::Thread*> thread, not_null<Data::Thread*> thread,
SendingErrorRequest request); SendingErrorRequest request);
[[nodiscard]] Data::SendErrorWithThread GetErrorForSending(
const std::vector<not_null<Data::Thread*>> &threads,
SendingErrorRequest request);
[[nodiscard]] object_ptr<Ui::BoxContent> MakeSendErrorBox(
const Data::SendErrorWithThread &error,
bool withTitle);
[[nodiscard]] TextWithEntities DropDisallowedCustomEmoji( [[nodiscard]] TextWithEntities DropDisallowedCustomEmoji(
not_null<PeerData*> to, not_null<PeerData*> to,
TextWithEntities text); TextWithEntities text);

View file

@ -85,7 +85,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/stickers/data_custom_emoji.h" #include "data/stickers/data_custom_emoji.h"
#include "history/history.h" #include "history/history.h"
#include "history/history_item.h" #include "history/history_item.h"
#include "history/history_item_helpers.h" // GetErrorTextForSending. #include "history/history_item_helpers.h" // GetErrorForSending.
#include "history/history_drag_area.h" #include "history/history_drag_area.h"
#include "history/history_inner_widget.h" #include "history/history_inner_widget.h"
#include "history/history_item_components.h" #include "history/history_item_components.h"
@ -1028,7 +1028,7 @@ void HistoryWidget::refreshTabbedPanel() {
void HistoryWidget::initVoiceRecordBar() { void HistoryWidget::initVoiceRecordBar() {
_voiceRecordBar->setStartRecordingFilter([=] { _voiceRecordBar->setStartRecordingFilter([=] {
const auto error = [&]() -> std::optional<QString> { const auto error = [&]() -> Data::SendError {
if (_peer) { if (_peer) {
if (const auto error = Data::RestrictionError( if (const auto error = Data::RestrictionError(
_peer, _peer,
@ -1036,10 +1036,10 @@ void HistoryWidget::initVoiceRecordBar() {
return error; return error;
} }
} }
return std::nullopt; return {};
}(); }();
if (error) { if (error) {
controller()->showToast(*error); Data::ShowSendErrorToast(controller(), _peer, error);
return true; return true;
} else if (showSlowmodeError()) { } else if (showSlowmodeError()) {
return true; return true;
@ -4672,7 +4672,7 @@ void HistoryWidget::chooseAttach(
if (!_peer || !_canSendMessages) { if (!_peer || !_canSendMessages) {
return; return;
} else if (const auto error = Data::AnyFileRestrictionError(_peer)) { } else if (const auto error = Data::AnyFileRestrictionError(_peer)) {
controller()->showToast(*error); Data::ShowSendErrorToast(controller(), _peer, error);
return; return;
} else if (showSlowmodeError()) { } else if (showSlowmodeError()) {
return; return;
@ -5702,12 +5702,12 @@ bool HistoryWidget::showSendingFilesError(
bool HistoryWidget::showSendingFilesError( bool HistoryWidget::showSendingFilesError(
const Ui::PreparedList &list, const Ui::PreparedList &list,
std::optional<bool> compress) const { std::optional<bool> compress) const {
const auto text = [&] { const auto error = [&]() -> Data::SendError {
const auto error = _peer const auto error = _peer
? Data::FileRestrictionError(_peer, list, compress) ? Data::FileRestrictionError(_peer, list, compress)
: std::nullopt; : Data::SendError();
if (error) { if (!_peer || error) {
return *error; return error;
} else if (const auto left = _peer->slowmodeSecondsLeft()) { } else if (const auto left = _peer->slowmodeSecondsLeft()) {
return tr::lng_slowmode_enabled( return tr::lng_slowmode_enabled(
tr::now, tr::now,
@ -5727,15 +5727,15 @@ bool HistoryWidget::showSendingFilesError(
} }
return tr::lng_forward_send_files_cant(tr::now); return tr::lng_forward_send_files_cant(tr::now);
}(); }();
if (text.isEmpty()) { if (!error) {
return false; return false;
} else if (text == u"(toolarge)"_q) { } else if (error.text == u"(toolarge)"_q) {
const auto fileSize = list.files.back().size; const auto fileSize = list.files.back().size;
controller()->show( controller()->show(
Box(FileSizeLimitBox, &session(), fileSize, nullptr)); Box(FileSizeLimitBox, &session(), fileSize, nullptr));
return true; return true;
} }
controller()->showToast(text); Data::ShowSendErrorToast(controller(), _peer, error);
return true; return true;
} }
@ -5775,7 +5775,7 @@ bool HistoryWidget::showSendMessageError(
return false; return false;
} }
const auto topicRootId = resolveReplyToTopicRootId(); const auto topicRootId = resolveReplyToTopicRootId();
const auto error = GetErrorTextForSending( const auto error = GetErrorForSending(
_peer, _peer,
{ {
.topicRootId = topicRootId, .topicRootId = topicRootId,
@ -5783,10 +5783,10 @@ bool HistoryWidget::showSendMessageError(
.text = &textWithTags, .text = &textWithTags,
.ignoreSlowmodeCountdown = ignoreSlowmodeCountdown, .ignoreSlowmodeCountdown = ignoreSlowmodeCountdown,
}); });
if (error.isEmpty()) { if (!error) {
return false; return false;
} }
controller()->showToast(error); Data::ShowSendErrorToast(controller(), _peer, error);
return true; return true;
} }
@ -6286,39 +6286,39 @@ int HistoryWidget::countAutomaticScrollTop() {
return ScrollMax; return ScrollMax;
} }
QString HistoryWidget::computeSendRestriction() const { Data::SendError HistoryWidget::computeSendRestriction() const {
if (const auto user = _peer ? _peer->asUser() : nullptr) {
if (user->meRequiresPremiumToWrite()
&& !user->session().premium()) {
return u"premium_required"_q;
}
}
const auto allWithoutPolls = Data::AllSendRestrictions() const auto allWithoutPolls = Data::AllSendRestrictions()
& ~ChatRestriction::SendPolls; & ~ChatRestriction::SendPolls;
const auto error = (_peer && !Data::CanSendAnyOf(_peer, allWithoutPolls)) return (_peer && !Data::CanSendAnyOf(_peer, allWithoutPolls))
? Data::RestrictionError(_peer, ChatRestriction::SendOther) ? Data::RestrictionError(_peer, ChatRestriction::SendOther)
: std::nullopt; : Data::SendError();
return error ? (u"restriction:"_q + *error) : QString();
} }
void HistoryWidget::updateSendRestriction() { void HistoryWidget::updateSendRestriction() {
const auto restriction = computeSendRestriction(); const auto restriction = computeSendRestriction();
if (_sendRestrictionKey == restriction) { if (_sendRestrictionKey == restriction.text) {
return; return;
} }
_sendRestrictionKey = restriction; _sendRestrictionKey = restriction.text;
if (restriction.isEmpty()) { if (!restriction) {
_sendRestriction = nullptr; _sendRestriction = nullptr;
} else if (restriction == u"premium_required"_q) { } else if (restriction.premiumToLift) {
_sendRestriction = PremiumRequiredSendRestriction( _sendRestriction = PremiumRequiredSendRestriction(
this, this,
_peer->asUser(), _peer->asUser(),
controller()); controller());
} else if (restriction.startsWith(u"restriction:"_q)) { } else if (const auto lifting = restriction.boostsToLift) {
const auto error = restriction.mid(12); auto button = base::make_unique_q<Ui::FlatButton>(
_sendRestriction = TextErrorSendRestriction(this, error); this,
restriction.text,
st::historyComposeButton);
const auto channel = _peer->asChannel();
button->setClickedCallback([=] {
controller()->resolveBoostState(channel, lifting);
});
_sendRestriction = std::move(button);
} else { } else {
Unexpected("Restriction type."); _sendRestriction = TextErrorSendRestriction(this, restriction.text);
} }
if (_sendRestriction) { if (_sendRestriction) {
_sendRestriction->show(); _sendRestriction->show();
@ -7130,9 +7130,8 @@ void HistoryWidget::sendInlineResult(InlineBots::ResultSelected result) {
return; return;
} }
auto errorText = result.result->getErrorOnSend(_history); if (const auto error = result.result->getErrorOnSend(_history)) {
if (!errorText.isEmpty()) { Data::ShowSendErrorToast(controller(), _peer, error);
controller()->showToast(errorText);
return; return;
} }
@ -7774,9 +7773,9 @@ bool HistoryWidget::sendExistingDocument(
std::optional<MsgId> localId) { std::optional<MsgId> localId) {
const auto error = _peer const auto error = _peer
? Data::RestrictionError(_peer, ChatRestriction::SendStickers) ? Data::RestrictionError(_peer, ChatRestriction::SendStickers)
: std::nullopt; : Data::SendError();
if (error) { if (error) {
controller()->showToast(*error); Data::ShowSendErrorToast(controller(), _peer, error);
return false; return false;
} else if (!_peer } else if (!_peer
|| !_canSendMessages || !_canSendMessages
@ -7811,9 +7810,9 @@ bool HistoryWidget::sendExistingPhoto(
Api::SendOptions options) { Api::SendOptions options) {
const auto error = _peer const auto error = _peer
? Data::RestrictionError(_peer, ChatRestriction::SendPhotos) ? Data::RestrictionError(_peer, ChatRestriction::SendPhotos)
: std::nullopt; : Data::SendError();
if (error) { if (error) {
controller()->showToast(*error); Data::ShowSendErrorToast(controller(), _peer, error);
return false; return false;
} else if (!_peer || !_canSendMessages) { } else if (!_peer || !_canSendMessages) {
return false; return false;

View file

@ -29,6 +29,7 @@ class Error;
namespace Data { namespace Data {
class PhotoMedia; class PhotoMedia;
struct SendError;
} // namespace Data } // namespace Data
namespace SendMenu { namespace SendMenu {
@ -579,7 +580,7 @@ private:
void addMessagesToBack(not_null<PeerData*> peer, const QVector<MTPMessage> &messages); void addMessagesToBack(not_null<PeerData*> peer, const QVector<MTPMessage> &messages);
void updateSendRestriction(); void updateSendRestriction();
[[nodiscard]] QString computeSendRestriction() const; [[nodiscard]] Data::SendError computeSendRestriction() const;
void updateHistoryGeometry(bool initial = false, bool loadedDown = false, const ScrollChange &change = { ScrollChangeNone, 0 }); void updateHistoryGeometry(bool initial = false, bool loadedDown = false, const ScrollChange &change = { ScrollChangeNone, 0 });
void updateListSize(); void updateListSize();
void startItemRevealAnimations(); void startItemRevealAnimations();

View file

@ -48,6 +48,7 @@ struct WriteRestriction {
QString text; QString text;
QString button; QString button;
Type type = Type::None; Type type = Type::None;
int boostsToLift = false;
[[nodiscard]] bool empty() const { [[nodiscard]] bool empty() const {
return (type == Type::None); return (type == Type::None);

View file

@ -2222,7 +2222,7 @@ void SetupRestrictionView(
not_null<Ui::RpWidget*> widget, not_null<Ui::RpWidget*> widget,
not_null<const style::ComposeControls*> st, not_null<const style::ComposeControls*> st,
std::shared_ptr<ChatHelpers::Show> show, std::shared_ptr<ChatHelpers::Show> show,
const QString &name, not_null<PeerData*> peer,
rpl::producer<Controls::WriteRestriction> restriction, rpl::producer<Controls::WriteRestriction> restriction,
Fn<void(QPainter &p, QRect clip)> paintBackground) { Fn<void(QPainter &p, QRect clip)> paintBackground) {
struct State { struct State {
@ -2234,7 +2234,9 @@ void SetupRestrictionView(
}; };
const auto state = widget->lifetime().make_state<State>(); const auto state = widget->lifetime().make_state<State>();
state->updateGeometries = [=] { state->updateGeometries = [=] {
if (!state->label) { if (!state->label && state->button) {
state->button->setGeometry(widget->rect());
} else if (!state->label) {
return; return;
} else if (state->button) { } else if (state->button) {
const auto available = widget->width() const auto available = widget->width()
@ -2307,14 +2309,24 @@ void SetupRestrictionView(
) | rpl::distinct_until_changed( ) | rpl::distinct_until_changed(
) | rpl::start_with_next([=](Controls::WriteRestriction value) { ) | rpl::start_with_next([=](Controls::WriteRestriction value) {
using Type = Controls::WriteRestriction::Type; using Type = Controls::WriteRestriction::Type;
if (value.type == Type::Rights) { if (const auto lifting = value.boostsToLift) {
state->button = std::make_unique<Ui::FlatButton>(
widget,
tr::lng_restricted_boost_group(tr::now),
st::historyComposeButton);
state->button->setClickedCallback([=] {
const auto window = show->resolveWindow(
ChatHelpers::WindowUsage::PremiumPromo);
window->resolveBoostState(peer->asChannel(), lifting);
});
} else if (value.type == Type::Rights) {
state->icon = nullptr; state->icon = nullptr;
state->unlock = nullptr; state->unlock = nullptr;
state->button = nullptr; state->button = nullptr;
state->label = makeLabel(value.text, st->restrictionLabel); state->label = makeLabel(value.text, st->restrictionLabel);
} else if (value.type == Type::PremiumRequired) { } else if (value.type == Type::PremiumRequired) {
state->icon = makeIcon(); state->icon = makeIcon();
state->unlock = makeUnlock(value.button, name); state->unlock = makeUnlock(value.button, peer->shortName());
state->button = std::make_unique<Ui::AbstractButton>(widget); state->button = std::make_unique<Ui::AbstractButton>(widget);
state->button->setClickedCallback([=] { state->button->setClickedCallback([=] {
::Settings::ShowPremiumPromoToast( ::Settings::ShowPremiumPromoToast(
@ -2322,7 +2334,7 @@ void SetupRestrictionView(
tr::lng_send_non_premium_message_toast( tr::lng_send_non_premium_message_toast(
tr::now, tr::now,
lt_user, lt_user,
TextWithEntities{ name }, TextWithEntities{ peer->shortName() },
lt_link, lt_link,
Ui::Text::Link( Ui::Text::Link(
Ui::Text::Bold( Ui::Text::Bold(
@ -2373,7 +2385,7 @@ void ComposeControls::initWriteRestriction() {
_writeRestricted.get(), _writeRestricted.get(),
&_st, &_st,
_show, _show,
_history->peer->shortName(), _history->peer,
_writeRestriction.value(), _writeRestriction.value(),
background); background);
@ -2405,7 +2417,7 @@ void ComposeControls::initVoiceRecordBar() {
}, _wrap->lifetime()); }, _wrap->lifetime());
_voiceRecordBar->setStartRecordingFilter([=] { _voiceRecordBar->setStartRecordingFilter([=] {
const auto error = [&]() -> std::optional<QString> { const auto error = [&]() -> Data::SendError {
const auto peer = _history ? _history->peer.get() : nullptr; const auto peer = _history ? _history->peer.get() : nullptr;
if (peer) { if (peer) {
if (const auto error = Data::RestrictionError( if (const auto error = Data::RestrictionError(
@ -2414,10 +2426,10 @@ void ComposeControls::initVoiceRecordBar() {
return error; return error;
} }
} }
return std::nullopt; return {};
}(); }();
if (error) { if (error) {
_show->showToast(*error); Data::ShowSendErrorToast(_show, _history->peer, error);
return true; return true;
} else if (_showSlowmodeError && _showSlowmodeError()) { } else if (_showSlowmodeError && _showSlowmodeError()) {
return true; return true;

View file

@ -23,7 +23,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "history/history.h" #include "history/history.h"
#include "history/history_drag_area.h" #include "history/history_drag_area.h"
#include "history/history_item_components.h" #include "history/history_item_components.h"
#include "history/history_item_helpers.h" // GetErrorTextForSending. #include "history/history_item_helpers.h" // GetErrorForSending.
#include "history/history_view_swipe.h" #include "history/history_view_swipe.h"
#include "ui/chat/pinned_bar.h" #include "ui/chat/pinned_bar.h"
#include "ui/chat/chat_style.h" #include "ui/chat/chat_style.h"
@ -661,7 +661,7 @@ void RepliesWidget::setupComposeControls() {
? _topic ? _topic
: _history->peer->forumTopicFor(_rootId); : _history->peer->forumTopicFor(_rootId);
return (!topic || topic->canToggleClosed() || !topic->closed()) return (!topic || topic->canToggleClosed() || !topic->closed())
? std::optional<QString>() ? Data::SendError()
: tr::lng_forum_topic_closed(tr::now); : tr::lng_forum_topic_closed(tr::now);
}); });
auto writeRestriction = rpl::combine( auto writeRestriction = rpl::combine(
@ -670,7 +670,7 @@ void RepliesWidget::setupComposeControls() {
Data::PeerUpdate::Flag::Rights), Data::PeerUpdate::Flag::Rights),
Data::CanSendAnythingValue(_history->peer), Data::CanSendAnythingValue(_history->peer),
std::move(topicWriteRestrictions) std::move(topicWriteRestrictions)
) | rpl::map([=](auto, auto, std::optional<QString> topicRestriction) { ) | rpl::map([=](auto, auto, Data::SendError topicRestriction) {
const auto allWithoutPolls = Data::AllSendRestrictions() const auto allWithoutPolls = Data::AllSendRestrictions()
& ~ChatRestriction::SendPolls; & ~ChatRestriction::SendPolls;
const auto canSendAnything = _topic const auto canSendAnything = _topic
@ -687,10 +687,11 @@ void RepliesWidget::setupComposeControls() {
: tr::lng_group_not_accessible(tr::now)) : tr::lng_group_not_accessible(tr::now))
: topicRestriction : topicRestriction
? std::move(topicRestriction) ? std::move(topicRestriction)
: std::optional<QString>(); : Data::SendError();
return text ? Controls::WriteRestriction{ return text ? Controls::WriteRestriction{
.text = std::move(*text), .text = std::move(*text),
.type = Controls::WriteRestrictionType::Rights, .type = Controls::WriteRestrictionType::Rights,
.boostsToLift = text.boostsToLift,
} : Controls::WriteRestriction(); } : Controls::WriteRestriction();
}); });
@ -936,7 +937,7 @@ void RepliesWidget::chooseAttach(
std::optional<bool> overrideSendImagesAsPhotos) { std::optional<bool> overrideSendImagesAsPhotos) {
_choosingAttach = false; _choosingAttach = false;
if (const auto error = Data::AnyFileRestrictionError(_history->peer)) { if (const auto error = Data::AnyFileRestrictionError(_history->peer)) {
controller()->showToast(*error); Data::ShowSendErrorToast(controller(), _history->peer, error);
return; return;
} else if (showSlowmodeError()) { } else if (showSlowmodeError()) {
return; return;
@ -1168,11 +1169,11 @@ bool RepliesWidget::showSendingFilesError(
bool RepliesWidget::showSendingFilesError( bool RepliesWidget::showSendingFilesError(
const Ui::PreparedList &list, const Ui::PreparedList &list,
std::optional<bool> compress) const { std::optional<bool> compress) const {
const auto text = [&] { const auto error = [&]() -> Data::SendError {
const auto peer = _history->peer; const auto peer = _history->peer;
const auto error = Data::FileRestrictionError(peer, list, compress); const auto error = Data::FileRestrictionError(peer, list, compress);
if (error) { if (error) {
return *error; return error;
} else if (const auto left = _history->peer->slowmodeSecondsLeft()) { } else if (const auto left = _history->peer->slowmodeSecondsLeft()) {
return tr::lng_slowmode_enabled( return tr::lng_slowmode_enabled(
tr::now, tr::now,
@ -1192,16 +1193,16 @@ bool RepliesWidget::showSendingFilesError(
} }
return tr::lng_forward_send_files_cant(tr::now); return tr::lng_forward_send_files_cant(tr::now);
}(); }();
if (text.isEmpty()) { if (!error) {
return false; return false;
} else if (text == u"(toolarge)"_q) { } else if (error.text == u"(toolarge)"_q) {
const auto fileSize = list.files.back().size; const auto fileSize = list.files.back().size;
controller()->show( controller()->show(
Box(FileSizeLimitBox, &session(), fileSize, nullptr)); Box(FileSizeLimitBox, &session(), fileSize, nullptr));
return true; return true;
} }
controller()->showToast(text); Data::ShowSendErrorToast(controller(), _history->peer, error);
return true; return true;
} }
@ -1247,7 +1248,7 @@ void RepliesWidget::send(Api::SendOptions options) {
message.textWithTags = _composeControls->getTextWithAppliedMarkdown(); message.textWithTags = _composeControls->getTextWithAppliedMarkdown();
message.webPage = _composeControls->webPageDraft(); message.webPage = _composeControls->webPageDraft();
const auto error = GetErrorTextForSending( const auto error = GetErrorForSending(
_history->peer, _history->peer,
{ {
.topicRootId = _topic ? _topic->rootId() : MsgId(0), .topicRootId = _topic ? _topic->rootId() : MsgId(0),
@ -1255,8 +1256,8 @@ void RepliesWidget::send(Api::SendOptions options) {
.text = &message.textWithTags, .text = &message.textWithTags,
.ignoreSlowmodeCountdown = (options.scheduled != 0), .ignoreSlowmodeCountdown = (options.scheduled != 0),
}); });
if (!error.isEmpty()) { if (error) {
controller()->showToast(error); Data::ShowSendErrorToast(controller(), _history->peer, error);
return; return;
} }
@ -1407,7 +1408,7 @@ bool RepliesWidget::sendExistingDocument(
_history->peer, _history->peer,
ChatRestriction::SendStickers); ChatRestriction::SendStickers);
if (error) { if (error) {
controller()->showToast(*error); Data::ShowSendErrorToast(controller(), _history->peer, error);
return false; return false;
} else if (showSlowmodeError() } else if (showSlowmodeError()
|| ShowSendPremiumError(controller(), document)) { || ShowSendPremiumError(controller(), document)) {
@ -1435,7 +1436,7 @@ bool RepliesWidget::sendExistingPhoto(
_history->peer, _history->peer,
ChatRestriction::SendPhotos); ChatRestriction::SendPhotos);
if (error) { if (error) {
controller()->showToast(*error); Data::ShowSendErrorToast(controller(), _history->peer, error);
return false; return false;
} else if (showSlowmodeError()) { } else if (showSlowmodeError()) {
return false; return false;
@ -1453,9 +1454,8 @@ bool RepliesWidget::sendExistingPhoto(
void RepliesWidget::sendInlineResult( void RepliesWidget::sendInlineResult(
not_null<InlineBots::Result*> result, not_null<InlineBots::Result*> result,
not_null<UserData*> bot) { not_null<UserData*> bot) {
const auto errorText = result->getErrorOnSend(_history); if (const auto error = result->getErrorOnSend(_history)) {
if (!errorText.isEmpty()) { Data::ShowSendErrorToast(controller(), _history->peer, error);
controller()->showToast(errorText);
return; return;
} }
sendInlineResult(result, bot, {}, std::nullopt); sendInlineResult(result, bot, {}, std::nullopt);

View file

@ -14,7 +14,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "history/view/history_view_sticker_toast.h" #include "history/view/history_view_sticker_toast.h"
#include "history/history.h" #include "history/history.h"
#include "history/history_drag_area.h" #include "history/history_drag_area.h"
#include "history/history_item_helpers.h" // GetErrorTextForSending. #include "history/history_item_helpers.h" // GetErrorForSending.
#include "menu/menu_send.h" // SendMenu::Type. #include "menu/menu_send.h" // SendMenu::Type.
#include "ui/widgets/buttons.h" #include "ui/widgets/buttons.h"
#include "ui/widgets/tooltip.h" #include "ui/widgets/tooltip.h"
@ -252,83 +252,85 @@ ScheduledWidget::~ScheduledWidget() = default;
void ScheduledWidget::setupComposeControls() { void ScheduledWidget::setupComposeControls() {
auto writeRestriction = _forumTopic auto writeRestriction = _forumTopic
? [&] { ? [&] {
auto topicWriteRestrictions = rpl::single( auto topicWriteRestrictions = rpl::single(
) | rpl::then(session().changes().topicUpdates( ) | rpl::then(session().changes().topicUpdates(
Data::TopicUpdate::Flag::Closed Data::TopicUpdate::Flag::Closed
) | rpl::filter([=](const Data::TopicUpdate &update) { ) | rpl::filter([=](const Data::TopicUpdate &update) {
return (update.topic->history() == _history) return (update.topic->history() == _history)
&& (update.topic->rootId() == _forumTopic->rootId()); && (update.topic->rootId() == _forumTopic->rootId());
}) | rpl::to_empty) | rpl::map([=] { }) | rpl::to_empty) | rpl::map([=] {
return (!_forumTopic return (!_forumTopic
|| _forumTopic->canToggleClosed() || _forumTopic->canToggleClosed()
|| !_forumTopic->closed()) || !_forumTopic->closed())
? std::optional<QString>() ? Data::SendError()
: tr::lng_forum_topic_closed(tr::now); : tr::lng_forum_topic_closed(tr::now);
}); });
return rpl::combine( return rpl::combine(
session().changes().peerFlagsValue( session().changes().peerFlagsValue(
_history->peer, _history->peer,
Data::PeerUpdate::Flag::Rights), Data::PeerUpdate::Flag::Rights),
Data::CanSendAnythingValue(_history->peer), Data::CanSendAnythingValue(_history->peer),
std::move(topicWriteRestrictions) std::move(topicWriteRestrictions)
) | rpl::map([=]( ) | rpl::map([=](
auto, auto,
auto, auto,
std::optional<QString> topicRestriction) { Data::SendError topicRestriction) {
const auto allWithoutPolls = Data::AllSendRestrictions() const auto allWithoutPolls = Data::AllSendRestrictions()
& ~ChatRestriction::SendPolls; & ~ChatRestriction::SendPolls;
const auto canSendAnything = Data::CanSendAnyOf( const auto canSendAnything = Data::CanSendAnyOf(
_forumTopic, _forumTopic,
allWithoutPolls); allWithoutPolls);
const auto restriction = Data::RestrictionError( const auto restriction = Data::RestrictionError(
_history->peer, _history->peer,
ChatRestriction::SendOther); ChatRestriction::SendOther);
auto text = !canSendAnything auto text = !canSendAnything
? (restriction ? (restriction
? restriction ? restriction
: topicRestriction
? std::move(topicRestriction)
: tr::lng_group_not_accessible(tr::now))
: topicRestriction : topicRestriction
? std::move(topicRestriction) ? std::move(topicRestriction)
: tr::lng_group_not_accessible(tr::now)) : Data::SendError();
: topicRestriction return text ? Controls::WriteRestriction{
? std::move(topicRestriction) .text = std::move(*text),
: std::optional<QString>(); .type = Controls::WriteRestrictionType::Rights,
return text ? Controls::WriteRestriction{ .boostsToLift = text.boostsToLift,
.text = std::move(*text), } : Controls::WriteRestriction();
.type = Controls::WriteRestrictionType::Rights, }) | rpl::type_erased();
} : Controls::WriteRestriction(); }()
}) | rpl::type_erased();
}()
: [&] { : [&] {
return rpl::combine( return rpl::combine(
session().changes().peerFlagsValue( session().changes().peerFlagsValue(
_history->peer, _history->peer,
Data::PeerUpdate::Flag::Rights), Data::PeerUpdate::Flag::Rights),
Data::CanSendAnythingValue(_history->peer) Data::CanSendAnythingValue(_history->peer)
) | rpl::map([=] { ) | rpl::map([=] {
const auto allWithoutPolls = Data::AllSendRestrictions() const auto allWithoutPolls = Data::AllSendRestrictions()
& ~ChatRestriction::SendPolls; & ~ChatRestriction::SendPolls;
const auto canSendAnything = Data::CanSendAnyOf( const auto canSendAnything = Data::CanSendAnyOf(
_history->peer, _history->peer,
allWithoutPolls, allWithoutPolls,
false); false);
const auto restriction = Data::RestrictionError( const auto restriction = Data::RestrictionError(
_history->peer, _history->peer,
ChatRestriction::SendOther); ChatRestriction::SendOther);
auto text = !canSendAnything auto text = !canSendAnything
? (restriction ? (restriction
? restriction ? restriction
: tr::lng_group_not_accessible(tr::now)) : tr::lng_group_not_accessible(tr::now))
: std::optional<QString>(); : Data::SendError();
return text ? Controls::WriteRestriction{ return text ? Controls::WriteRestriction{
.text = std::move(*text), .text = std::move(*text),
.type = Controls::WriteRestrictionType::Rights, .type = Controls::WriteRestrictionType::Rights,
} : Controls::WriteRestriction(); .boostsToLift = text.boostsToLift,
}) | rpl::type_erased(); } : Controls::WriteRestriction();
}(); }) | rpl::type_erased();
}();
_composeControls->setHistory({ _composeControls->setHistory({
.history = _history.get(), .history = _history.get(),
.writeRestriction = std::move(writeRestriction), .writeRestriction = std::move(writeRestriction),
}); });
_composeControls->height( _composeControls->height(
) | rpl::start_with_next([=] { ) | rpl::start_with_next([=] {
@ -463,7 +465,7 @@ void ScheduledWidget::setupComposeControls() {
void ScheduledWidget::chooseAttach() { void ScheduledWidget::chooseAttach() {
if (const auto error = Data::AnyFileRestrictionError(_history->peer)) { if (const auto error = Data::AnyFileRestrictionError(_history->peer)) {
controller()->showToast(*error); Data::ShowSendErrorToast(controller(), _history->peer, error);
return; return;
} }
@ -667,12 +669,12 @@ bool ScheduledWidget::showSendingFilesError(
bool ScheduledWidget::showSendingFilesError( bool ScheduledWidget::showSendingFilesError(
const Ui::PreparedList &list, const Ui::PreparedList &list,
std::optional<bool> compress) const { std::optional<bool> compress) const {
const auto text = [&] { const auto error = [&]() -> Data::SendError {
using Error = Ui::PreparedList::Error; using Error = Ui::PreparedList::Error;
const auto peer = _history->peer; const auto peer = _history->peer;
const auto error = Data::FileRestrictionError(peer, list, compress); const auto error = Data::FileRestrictionError(peer, list, compress);
if (error) { if (error) {
return *error; return error;
} else switch (list.error) { } else switch (list.error) {
case Error::None: return QString(); case Error::None: return QString();
case Error::EmptyFile: case Error::EmptyFile:
@ -685,16 +687,16 @@ bool ScheduledWidget::showSendingFilesError(
} }
return tr::lng_forward_send_files_cant(tr::now); return tr::lng_forward_send_files_cant(tr::now);
}(); }();
if (text.isEmpty()) { if (!error) {
return false; return false;
} else if (text == u"(toolarge)"_q) { } else if (error.text == u"(toolarge)"_q) {
const auto fileSize = list.files.back().size; const auto fileSize = list.files.back().size;
controller()->show( controller()->show(
Box(FileSizeLimitBox, &session(), fileSize, nullptr)); Box(FileSizeLimitBox, &session(), fileSize, nullptr));
return true; return true;
} }
controller()->showToast(text); Data::ShowSendErrorToast(controller(), _history->peer, error);
return true; return true;
} }
@ -717,7 +719,7 @@ void ScheduledWidget::send() {
return; return;
} }
const auto error = GetErrorTextForSending( const auto error = GetErrorForSending(
_history->peer, _history->peer,
{ {
.topicRootId = _forumTopic .topicRootId = _forumTopic
@ -729,8 +731,8 @@ void ScheduledWidget::send() {
.text = &textWithTags, .text = &textWithTags,
.ignoreSlowmodeCountdown = true, .ignoreSlowmodeCountdown = true,
}); });
if (!error.isEmpty()) { if (error) {
controller()->showToast(error); Data::ShowSendErrorToast(controller(), _history->peer, error);
return; return;
} }
const auto callback = [=](Api::SendOptions options) { send(options); }; const auto callback = [=](Api::SendOptions options) { send(options); };
@ -865,7 +867,7 @@ bool ScheduledWidget::sendExistingDocument(
_history->peer, _history->peer,
ChatRestriction::SendStickers); ChatRestriction::SendStickers);
if (error) { if (error) {
controller()->showToast(*error); Data::ShowSendErrorToast(controller(), _history->peer, error);
return false; return false;
} else if (ShowSendPremiumError(controller(), document)) { } else if (ShowSendPremiumError(controller(), document)) {
return false; return false;
@ -893,7 +895,7 @@ bool ScheduledWidget::sendExistingPhoto(
_history->peer, _history->peer,
ChatRestriction::SendPhotos); ChatRestriction::SendPhotos);
if (error) { if (error) {
controller()->showToast(*error); Data::ShowSendErrorToast(controller(), _history->peer, error);
return false; return false;
} }
@ -909,9 +911,8 @@ bool ScheduledWidget::sendExistingPhoto(
void ScheduledWidget::sendInlineResult( void ScheduledWidget::sendInlineResult(
not_null<InlineBots::Result*> result, not_null<InlineBots::Result*> result,
not_null<UserData*> bot) { not_null<UserData*> bot) {
const auto errorText = result->getErrorOnSend(_history); if (const auto error = result->getErrorOnSend(_history)) {
if (!errorText.isEmpty()) { Data::ShowSendErrorToast(controller(), _history->peer, error);
controller()->showToast(errorText);
return; return;
} }
const auto callback = [=](Api::SendOptions options) { const auto callback = [=](Api::SendOptions options) {

View file

@ -394,13 +394,9 @@ not_null<HistoryItem*> Result::makeMessage(
return sendData->makeMessage(this, history, std::move(fields)); return sendData->makeMessage(this, history, std::move(fields));
} }
QString Result::getErrorOnSend(not_null<History*> history) const { Data::SendError Result::getErrorOnSend(not_null<History*> history) const {
const auto specific = sendData->getErrorOnSend(this, history); return sendData->getErrorOnSend(this, history).value_or(
return !specific.isEmpty() Data::RestrictionError(history->peer, ChatRestriction::SendInline));
? specific
: Data::RestrictionError(
history->peer,
ChatRestriction::SendInline).value_or(QString());
} }
std::optional<Data::LocationPoint> Result::getLocationPoint() const { std::optional<Data::LocationPoint> Result::getLocationPoint() const {

View file

@ -20,6 +20,7 @@ struct HistoryItemCommonFields;
namespace Data { namespace Data {
class LocationPoint; class LocationPoint;
struct SendError;
} // namespace Data } // namespace Data
namespace InlineBots { namespace InlineBots {
@ -69,7 +70,8 @@ public:
[[nodiscard]] not_null<HistoryItem*> makeMessage( [[nodiscard]] not_null<HistoryItem*> makeMessage(
not_null<History*> history, not_null<History*> history,
HistoryItemCommonFields &&fields) const; HistoryItemCommonFields &&fields) const;
QString getErrorOnSend(not_null<History*> history) const; [[nodiscard]] Data::SendError getErrorOnSend(
not_null<History*> history) const;
// interface for Layout:: usage // interface for Layout:: usage
std::optional<Data::LocationPoint> getLocationPoint() const; std::optional<Data::LocationPoint> getLocationPoint() const;

View file

@ -42,11 +42,11 @@ not_null<HistoryItem*> SendDataCommon::makeMessage(
std::move(distinct.media)); std::move(distinct.media));
} }
QString SendDataCommon::getErrorOnSend( Data::SendError SendDataCommon::getErrorOnSend(
const Result *owner, const Result *owner,
not_null<History*> history) const { not_null<History*> history) const {
const auto type = ChatRestriction::SendOther; const auto type = ChatRestriction::SendOther;
return Data::RestrictionError(history->peer, type).value_or(QString()); return Data::RestrictionError(history->peer, type);
} }
SendDataCommon::SentMessageFields SendText::getSentMessageFields() const { SendDataCommon::SentMessageFields SendText::getSentMessageFields() const {
@ -106,11 +106,11 @@ not_null<HistoryItem*> SendPhoto::makeMessage(
TextWithEntities{ _message, _entities }); TextWithEntities{ _message, _entities });
} }
QString SendPhoto::getErrorOnSend( Data::SendError SendPhoto::getErrorOnSend(
const Result *owner, const Result *owner,
not_null<History*> history) const { not_null<History*> history) const {
const auto type = ChatRestriction::SendPhotos; const auto type = ChatRestriction::SendPhotos;
return Data::RestrictionError(history->peer, type).value_or(QString()); return Data::RestrictionError(history->peer, type);
} }
not_null<HistoryItem*> SendFile::makeMessage( not_null<HistoryItem*> SendFile::makeMessage(
@ -123,11 +123,11 @@ not_null<HistoryItem*> SendFile::makeMessage(
TextWithEntities{ _message, _entities }); TextWithEntities{ _message, _entities });
} }
QString SendFile::getErrorOnSend( Data::SendError SendFile::getErrorOnSend(
const Result *owner, const Result *owner,
not_null<History*> history) const { not_null<History*> history) const {
const auto type = _document->requiredSendRight(); const auto type = _document->requiredSendRight();
return Data::RestrictionError(history->peer, type).value_or(QString()); return Data::RestrictionError(history->peer, type);
} }
not_null<HistoryItem*> SendGame::makeMessage( not_null<HistoryItem*> SendGame::makeMessage(
@ -137,11 +137,11 @@ not_null<HistoryItem*> SendGame::makeMessage(
return history->makeMessage(std::move(fields), _game); return history->makeMessage(std::move(fields), _game);
} }
QString SendGame::getErrorOnSend( Data::SendError SendGame::getErrorOnSend(
const Result *owner, const Result *owner,
not_null<History*> history) const { not_null<History*> history) const {
const auto type = ChatRestriction::SendGames; const auto type = ChatRestriction::SendGames;
return Data::RestrictionError(history->peer, type).value_or(QString()); return Data::RestrictionError(history->peer, type);
} }
SendDataCommon::SentMessageFields SendInvoice::getSentMessageFields() const { SendDataCommon::SentMessageFields SendInvoice::getSentMessageFields() const {

View file

@ -11,6 +11,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
struct HistoryItemCommonFields; struct HistoryItemCommonFields;
namespace Data {
struct SendError;
} // namespace Data
namespace Main { namespace Main {
class Session; class Session;
} // namespace Main } // namespace Main
@ -44,7 +48,7 @@ public:
const Result *owner, const Result *owner,
not_null<History*> history, not_null<History*> history,
HistoryItemCommonFields &&fields) const = 0; HistoryItemCommonFields &&fields) const = 0;
virtual QString getErrorOnSend( virtual Data::SendError getErrorOnSend(
const Result *owner, const Result *owner,
not_null<History*> history) const = 0; not_null<History*> history) const = 0;
@ -80,7 +84,7 @@ public:
not_null<History*> history, not_null<History*> history,
HistoryItemCommonFields &&fields) const override; HistoryItemCommonFields &&fields) const override;
QString getErrorOnSend( Data::SendError getErrorOnSend(
const Result *owner, const Result *owner,
not_null<History*> history) const override; not_null<History*> history) const override;
@ -241,7 +245,7 @@ public:
not_null<History*> history, not_null<History*> history,
HistoryItemCommonFields &&fields) const override; HistoryItemCommonFields &&fields) const override;
QString getErrorOnSend( Data::SendError getErrorOnSend(
const Result *owner, const Result *owner,
not_null<History*> history) const override; not_null<History*> history) const override;
@ -275,7 +279,7 @@ public:
not_null<History*> history, not_null<History*> history,
HistoryItemCommonFields &&fields) const override; HistoryItemCommonFields &&fields) const override;
QString getErrorOnSend( Data::SendError getErrorOnSend(
const Result *owner, const Result *owner,
not_null<History*> history) const override; not_null<History*> history) const override;
@ -303,7 +307,7 @@ public:
not_null<History*> history, not_null<History*> history,
HistoryItemCommonFields &&fields) const override; HistoryItemCommonFields &&fields) const override;
QString getErrorOnSend( Data::SendError getErrorOnSend(
const Result *owner, const Result *owner,
not_null<History*> history) const override; not_null<History*> history) const override;

View file

@ -25,6 +25,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "mainwindow.h" #include "mainwindow.h"
#include "main/main_session.h" #include "main/main_session.h"
#include "window/window_session_controller.h" #include "window/window_session_controller.h"
#include "ui/text/text_utilities.h"
#include "ui/widgets/popup_menu.h" #include "ui/widgets/popup_menu.h"
#include "ui/widgets/buttons.h" #include "ui/widgets/buttons.h"
#include "ui/widgets/labels.h" #include "ui/widgets/labels.h"
@ -113,19 +114,35 @@ void Inner::checkRestrictedPeer() {
const auto error = Data::RestrictionError( const auto error = Data::RestrictionError(
_inlineQueryPeer, _inlineQueryPeer,
ChatRestriction::SendInline); ChatRestriction::SendInline);
if (error) { const auto changed = (_restrictedLabelKey != error.text);
if (!_restrictedLabel) { if (!changed) {
_restrictedLabel.create(this, *error, st::stickersRestrictedLabel);
_restrictedLabel->show();
_restrictedLabel->move(st::inlineResultsLeft - st::roundRadiusSmall, st::stickerPanPadding);
_restrictedLabel->resizeToNaturalWidth(width() - (st::inlineResultsLeft - st::roundRadiusSmall) * 2);
if (_switchPmButton) {
_switchPmButton->hide();
}
repaintItems();
}
return; return;
} }
_restrictedLabelKey = error.text;
if (error) {
const auto window = _controller;
const auto peer = _inlineQueryPeer;
_restrictedLabel.create(
this,
rpl::single(error.boostsToLift
? Ui::Text::Link(error.text)
: TextWithEntities{ error.text }),
st::stickersRestrictedLabel);
const auto lifting = error.boostsToLift;
_restrictedLabel->setClickHandlerFilter([=](auto...) {
window->resolveBoostState(peer->asChannel(), lifting);
return false;
});
_restrictedLabel->show();
updateRestrictedLabelGeometry();
if (_switchPmButton) {
_switchPmButton->hide();
}
repaintItems();
return;
}
} else {
_restrictedLabelKey = QString();
} }
if (_restrictedLabel) { if (_restrictedLabel) {
_restrictedLabel.destroy(); _restrictedLabel.destroy();
@ -136,6 +153,18 @@ void Inner::checkRestrictedPeer() {
} }
} }
void Inner::updateRestrictedLabelGeometry() {
if (!_restrictedLabel) {
return;
}
auto labelWidth = width() - st::stickerPanPadding * 2;
_restrictedLabel->resizeToWidth(labelWidth);
_restrictedLabel->moveToLeft(
(width() - _restrictedLabel->width()) / 2,
st::stickerPanPadding);
}
bool Inner::isRestrictedView() { bool Inner::isRestrictedView() {
checkRestrictedPeer(); checkRestrictedPeer();
return (_restrictedLabel != nullptr); return (_restrictedLabel != nullptr);
@ -178,6 +207,10 @@ rpl::producer<> Inner::inlineRowsCleared() const {
Inner::~Inner() = default; Inner::~Inner() = default;
void Inner::resizeEvent(QResizeEvent *e) {
updateRestrictedLabelGeometry();
}
void Inner::paintEvent(QPaintEvent *e) { void Inner::paintEvent(QPaintEvent *e) {
Painter p(this); Painter p(this);
QRect r = e ? e->rect() : rect(); QRect r = e ? e->rect() : rect();

View file

@ -108,6 +108,7 @@ protected:
void mousePressEvent(QMouseEvent *e) override; void mousePressEvent(QMouseEvent *e) override;
void mouseReleaseEvent(QMouseEvent *e) override; void mouseReleaseEvent(QMouseEvent *e) override;
void mouseMoveEvent(QMouseEvent *e) override; void mouseMoveEvent(QMouseEvent *e) override;
void resizeEvent(QResizeEvent *e) override;
void paintEvent(QPaintEvent *e) override; void paintEvent(QPaintEvent *e) override;
void leaveEventHook(QEvent *e) override; void leaveEventHook(QEvent *e) override;
void leaveToChildEvent(QEvent *e, QWidget *child) override; void leaveToChildEvent(QEvent *e, QWidget *child) override;
@ -136,6 +137,7 @@ private:
void clearInlineRows(bool resultsDeleted); void clearInlineRows(bool resultsDeleted);
ItemBase *layoutPrepareInlineResult(Result *result); ItemBase *layoutPrepareInlineResult(Result *result);
void updateRestrictedLabelGeometry();
void deleteUnusedInlineLayouts(); void deleteUnusedInlineLayouts();
int validateExistingInlineRows(const Results &results); int validateExistingInlineRows(const Results &results);
@ -162,6 +164,7 @@ private:
QByteArray _switchPmUrl; QByteArray _switchPmUrl;
object_ptr<Ui::FlatLabel> _restrictedLabel = { nullptr }; object_ptr<Ui::FlatLabel> _restrictedLabel = { nullptr };
QString _restrictedLabelKey;
base::unique_qptr<Ui::PopupMenu> _menu; base::unique_qptr<Ui::PopupMenu> _menu;

View file

@ -52,7 +52,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "apiwrap.h" #include "apiwrap.h"
#include "dialogs/dialogs_widget.h" #include "dialogs/dialogs_widget.h"
#include "history/history_widget.h" #include "history/history_widget.h"
#include "history/history_item_helpers.h" // GetErrorTextForSending. #include "history/history_item_helpers.h" // GetErrorForSending.
#include "history/view/media/history_view_media.h" #include "history/view/media/history_view_media.h"
#include "history/view/history_view_service_message.h" #include "history/view/history_view_service_message.h"
#include "history/view/history_view_sublist_section.h" #include "history/view/history_view_sublist_section.h"
@ -556,15 +556,15 @@ bool MainWidget::setForwardDraft(
const auto history = thread->owningHistory(); const auto history = thread->owningHistory();
const auto items = session().data().idsToItems(draft.ids); const auto items = session().data().idsToItems(draft.ids);
const auto topicRootId = thread->topicRootId(); const auto topicRootId = thread->topicRootId();
const auto error = GetErrorTextForSending( const auto error = GetErrorForSending(
history->peer, history->peer,
{ {
.topicRootId = topicRootId, .topicRootId = topicRootId,
.forward = &items, .forward = &items,
.ignoreSlowmodeCountdown = true, .ignoreSlowmodeCountdown = true,
}); });
if (!error.isEmpty()) { if (error) {
_controller->show(Ui::MakeInformBox(error)); Data::ShowSendErrorToast(_controller, history->peer, error);
return false; return false;
} }
@ -611,12 +611,12 @@ bool MainWidget::sendPaths(
not_null<Data::Thread*> thread, not_null<Data::Thread*> thread,
const QStringList &paths) { const QStringList &paths) {
if (!Data::CanSendAnyOf(thread, Data::FilesSendRestrictions())) { if (!Data::CanSendAnyOf(thread, Data::FilesSendRestrictions())) {
_controller->show(Ui::MakeInformBox( _controller->showToast(
tr::lng_forward_send_files_cant())); tr::lng_forward_send_files_cant(tr::now));
return false; return false;
} else if (const auto error = Data::AnyFileRestrictionError( } else if (const auto error = Data::AnyFileRestrictionError(
thread->peer())) { thread->peer())) {
_controller->show(Ui::MakeInformBox(*error)); Data::ShowSendErrorToast(controller(), thread->peer(), error);
return false; return false;
} else { } else {
_controller->showThread( _controller->showThread(
@ -659,12 +659,12 @@ bool MainWidget::filesOrForwardDrop(
} }
return false; return false;
} else if (!Data::CanSendAnyOf(thread, Data::FilesSendRestrictions())) { } else if (!Data::CanSendAnyOf(thread, Data::FilesSendRestrictions())) {
_controller->show(Ui::MakeInformBox( _controller->showToast(
tr::lng_forward_send_files_cant())); tr::lng_forward_send_files_cant(tr::now));
return false; return false;
} else if (const auto error = Data::AnyFileRestrictionError( } else if (const auto error = Data::AnyFileRestrictionError(
thread->peer())) { thread->peer())) {
_controller->show(Ui::MakeInformBox(*error)); Data::ShowSendErrorToast(_controller, thread->peer(), error);
return false; return false;
} else { } else {
_controller->showThread( _controller->showThread(

View file

@ -20,6 +20,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "core/file_utilities.h" #include "core/file_utilities.h"
#include "core/mime_type.h" #include "core/mime_type.h"
#include "data/stickers/data_custom_emoji.h" #include "data/stickers/data_custom_emoji.h"
#include "data/data_chat_participant_status.h"
#include "data/data_document.h" #include "data/data_document.h"
#include "data/data_message_reaction_id.h" #include "data/data_message_reaction_id.h"
#include "data/data_peer_values.h" #include "data/data_peer_values.h"
@ -219,15 +220,15 @@ bool ReplyArea::send(
return false; return false;
} }
const auto error = GetErrorTextForSending( const auto error = GetErrorForSending(
_data.peer, _data.peer,
{ {
.topicRootId = MsgId(0), .topicRootId = MsgId(0),
.text = &message.textWithTags, .text = &message.textWithTags,
.ignoreSlowmodeCountdown = (options.scheduled != 0), .ignoreSlowmodeCountdown = (options.scheduled != 0),
}); });
if (!error.isEmpty()) { if (error) {
_controller->uiShow()->showToast(error); Data::ShowSendErrorToast(_controller->uiShow(), _data.peer, error);
return false; return false;
} }
@ -262,7 +263,7 @@ bool ReplyArea::sendExistingDocument(
_data.peer, _data.peer,
ChatRestriction::SendStickers); ChatRestriction::SendStickers);
if (error) { if (error) {
show->showToast(*error); Data::ShowSendErrorToast(show, _data.peer, error);
return false; return false;
} else if (showSlowmodeError() } else if (showSlowmodeError()
|| Window::ShowSendPremiumError(show, document)) { || Window::ShowSendPremiumError(show, document)) {
@ -290,7 +291,7 @@ bool ReplyArea::sendExistingPhoto(
_data.peer, _data.peer,
ChatRestriction::SendPhotos); ChatRestriction::SendPhotos);
if (error) { if (error) {
show->showToast(*error); Data::ShowSendErrorToast(show, _data.peer, error);
return false; return false;
} else if (showSlowmodeError()) { } else if (showSlowmodeError()) {
return false; return false;
@ -308,9 +309,9 @@ bool ReplyArea::sendExistingPhoto(
void ReplyArea::sendInlineResult( void ReplyArea::sendInlineResult(
not_null<InlineBots::Result*> result, not_null<InlineBots::Result*> result,
not_null<UserData*> bot) { not_null<UserData*> bot) {
const auto errorText = result->getErrorOnSend(history()); if (const auto error = result->getErrorOnSend(history())) {
if (!errorText.isEmpty()) { const auto show = _controller->uiShow();
_controller->uiShow()->showToast(errorText); Data::ShowSendErrorToast(show, history()->peer, error);
return; return;
} }
sendInlineResult(result, bot, {}, std::nullopt); sendInlineResult(result, bot, {}, std::nullopt);
@ -363,11 +364,11 @@ bool ReplyArea::showSendingFilesError(
bool ReplyArea::showSendingFilesError( bool ReplyArea::showSendingFilesError(
const Ui::PreparedList &list, const Ui::PreparedList &list,
std::optional<bool> compress) const { std::optional<bool> compress) const {
const auto text = [&] { const auto error = [&]() -> Data::SendError {
const auto peer = _data.peer; const auto peer = _data.peer;
const auto error = Data::FileRestrictionError(peer, list, compress); const auto error = Data::FileRestrictionError(peer, list, compress);
if (error) { if (error) {
return *error; return error;
} }
using Error = Ui::PreparedList::Error; using Error = Ui::PreparedList::Error;
switch (list.error) { switch (list.error) {
@ -382,9 +383,9 @@ bool ReplyArea::showSendingFilesError(
} }
return tr::lng_forward_send_files_cant(tr::now); return tr::lng_forward_send_files_cant(tr::now);
}(); }();
if (text.isEmpty()) { if (!error) {
return false; return false;
} else if (text == u"(toolarge)"_q) { } else if (error.text == u"(toolarge)"_q) {
const auto fileSize = list.files.back().size; const auto fileSize = list.files.back().size;
_controller->uiShow()->showBox(Box( _controller->uiShow()->showBox(Box(
FileSizeLimitBox, FileSizeLimitBox,
@ -394,7 +395,7 @@ bool ReplyArea::showSendingFilesError(
return true; return true;
} }
_controller->uiShow()->showToast(text); Data::ShowSendErrorToast(_controller->uiShow(), _data.peer, error);
return true; return true;
} }
@ -422,7 +423,7 @@ void ReplyArea::chooseAttach(
} }
const auto peer = not_null(_data.peer); const auto peer = not_null(_data.peer);
if (const auto error = Data::AnyFileRestrictionError(peer)) { if (const auto error = Data::AnyFileRestrictionError(peer)) {
_controller->uiShow()->showToast(*error); Data::ShowSendErrorToast(_controller->uiShow(), peer, error);
return; return;
} else if (showSlowmodeError()) { } else if (showSlowmodeError()) {
return; return;

View file

@ -22,7 +22,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_thread.h" #include "data/data_thread.h"
#include "data/data_user.h" #include "data/data_user.h"
#include "history/history.h" #include "history/history.h"
#include "history/history_item_helpers.h" // GetErrorTextForSending. #include "history/history_item_helpers.h" // GetErrorForSending.
#include "history/view/history_view_context_menu.h" // CopyStoryLink. #include "history/view/history_view_context_menu.h" // CopyStoryLink.
#include "lang/lang_keys.h" #include "lang/lang_keys.h"
#include "main/main_session.h" #include "main/main_session.h"
@ -87,26 +87,11 @@ namespace Media::Stories {
return; return;
} }
const auto peer = story->peer(); const auto peer = story->peer();
const auto error = [&] { const auto error = GetErrorForSending(
for (const auto thread : result) { result,
const auto error = GetErrorTextForSending( { .story = story, .text = &comment });
thread, if (error.error) {
{ .story = story, .text = &comment }); show->showBox(MakeSendErrorBox(error, result.size() > 1));
if (!error.isEmpty()) {
return std::make_pair(error, thread);
}
}
return std::make_pair(QString(), result.front());
}();
if (!error.first.isEmpty()) {
auto text = TextWithEntities();
if (result.size() > 1) {
text.append(
Ui::Text::Bold(error.second->chatListName())
).append("\n\n");
}
text.append(error.first);
show->showBox(Ui::MakeInformBox(text));
return; return;
} }

View file

@ -185,7 +185,9 @@ std::optional<QString> RestrictionToSend(
not_null<Window::Controller*> controller, not_null<Window::Controller*> controller,
ChatRestriction right) { ChatRestriction right) {
if (const auto peer = ActiveChat(controller).peer()) { if (const auto peer = ActiveChat(controller).peer()) {
return Data::RestrictionError(peer, right); if (const auto error = Data::RestrictionError(peer, right)) {
return *error;
}
} }
return std::nullopt; return std::nullopt;
} }

View file

@ -1538,10 +1538,8 @@ void ShortcutMessages::sendInlineResult(
not_null<UserData*> bot) { not_null<UserData*> bot) {
if (showPremiumRequired()) { if (showPremiumRequired()) {
return; return;
} } else if (const auto error = result->getErrorOnSend(_history)) {
const auto errorText = result->getErrorOnSend(_history); Data::ShowSendErrorToast(_controller, _history->peer, error);
if (!errorText.isEmpty()) {
_controller->showToast(errorText);
return; return;
} }
sendInlineResult(result, bot, {}, std::nullopt); sendInlineResult(result, bot, {}, std::nullopt);

View file

@ -325,8 +325,14 @@ void BoostBox(
return (counters.mine > 1) ? u"x%1"_q.arg(counters.mine) : u""_q; return (counters.mine > 1) ? u"x%1"_q.arg(counters.mine) : u""_q;
}); });
const auto wasMine = state->data.current().mine;
const auto wasLifting = data.lifting;
auto text = state->data.value( auto text = state->data.value(
) | rpl::map([=](BoostCounters counters) { ) | rpl::map([=](BoostCounters counters) {
const auto lifting = wasLifting
? (wasLifting
- std::clamp(counters.mine - wasMine, 0, wasLifting - 1))
: 0;
const auto bold = Ui::Text::Bold(name); const auto bold = Ui::Text::Bold(name);
const auto now = counters.boosts; const auto now = counters.boosts;
const auto full = !counters.nextLevelBoosts; const auto full = !counters.nextLevelBoosts;
@ -337,7 +343,14 @@ void BoostBox(
lt_count, lt_count,
rpl::single(float64(counters.level + (left ? 1 : 0))), rpl::single(float64(counters.level + (left ? 1 : 0))),
Ui::Text::RichLangValue); Ui::Text::RichLangValue);
return (counters.mine || full) return (lifting > 1)
? tr::lng_boost_group_lift_restrictions_many(
lt_count,
rpl::single(float64(lifting)),
Ui::Text::RichLangValue)
: lifting
? tr::lng_boost_group_lift_restrictions(Ui::Text::RichLangValue)
: (counters.mine || full)
? (left ? (left
? tr::lng_boost_channel_needs_unlock( ? tr::lng_boost_channel_needs_unlock(
lt_count, lt_count,
@ -365,6 +378,14 @@ void BoostBox(
rpl::single(bold), rpl::single(bold),
Ui::Text::RichLangValue); Ui::Text::RichLangValue);
}) | rpl::flatten_latest(); }) | rpl::flatten_latest();
if (wasLifting) {
state->data.value(
) | rpl::start_with_next([=](BoostCounters counters) {
if (counters.mine - wasMine >= wasLifting) {
box->closeBox();
}
}, box->lifetime());
}
auto faded = object_ptr<Ui::FadeWrap<>>( auto faded = object_ptr<Ui::FadeWrap<>>(
close->parentWidget(), close->parentWidget(),

View file

@ -48,6 +48,7 @@ struct BoostBoxData {
QString name; QString name;
BoostCounters boost; BoostCounters boost;
BoostFeatures features; BoostFeatures features;
int lifting = 0;
bool allowMulti = false; bool allowMulti = false;
bool group = false; bool group = false;
}; };

View file

@ -61,7 +61,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "api/api_updates.h" #include "api/api_updates.h"
#include "mtproto/mtproto_config.h" #include "mtproto/mtproto_config.h"
#include "history/history.h" #include "history/history.h"
#include "history/history_item_helpers.h" // GetErrorTextForSending. #include "history/history_item_helpers.h" // GetErrorForSending.
#include "history/view/history_view_context_menu.h" #include "history/view/history_view_context_menu.h"
#include "window/window_separate_id.h" #include "window/window_separate_id.h"
#include "window/window_session_controller.h" #include "window/window_session_controller.h"
@ -2595,11 +2595,11 @@ QPointer<Ui::BoxContent> ShowSendNowMessagesBox(
: tr::lng_scheduled_send_now(tr::now); : tr::lng_scheduled_send_now(tr::now);
const auto list = session->data().idsToItems(items); const auto list = session->data().idsToItems(items);
const auto error = GetErrorTextForSending( const auto error = GetErrorForSending(
history->peer, history->peer,
{ .forward = &list }); { .forward = &list });
if (!error.isEmpty()) { if (error) {
navigation->showToast(error); Data::ShowSendErrorToast(navigation, history->peer, error);
return { nullptr }; return { nullptr };
} }
auto done = [ auto done = [

View file

@ -764,7 +764,10 @@ void SessionNavigation::showPeerByLinkResolved(
} }
} }
void SessionNavigation::resolveBoostState(not_null<ChannelData*> channel) { void SessionNavigation::resolveBoostState(
not_null<ChannelData*> channel,
int boostsToLift) {
_boostsToLift = boostsToLift;
if (_boostStateResolving == channel) { if (_boostStateResolving == channel) {
return; return;
} }
@ -772,18 +775,33 @@ void SessionNavigation::resolveBoostState(not_null<ChannelData*> channel) {
_api.request(MTPpremium_GetBoostsStatus( _api.request(MTPpremium_GetBoostsStatus(
channel->input channel->input
)).done([=](const MTPpremium_BoostsStatus &result) { )).done([=](const MTPpremium_BoostsStatus &result) {
_boostStateResolving = nullptr; if (base::take(_boostStateResolving) != channel) {
return;
}
const auto boosted = std::make_shared<bool>();
channel->updateLevelHint(result.data().vlevel().v); channel->updateLevelHint(result.data().vlevel().v);
const auto submit = [=](Fn<void(Ui::BoostCounters)> done) { const auto submit = [=](Fn<void(Ui::BoostCounters)> done) {
applyBoost(channel, done); applyBoost(channel, [=](Ui::BoostCounters counters) {
*boosted = true;
done(counters);
});
}; };
uiShow()->show(Box(Ui::BoostBox, Ui::BoostBoxData{ const auto lifting = base::take(_boostsToLift);
const auto box = uiShow()->show(Box(Ui::BoostBox, Ui::BoostBoxData{
.name = channel->name(), .name = channel->name(),
.boost = ParseBoostCounters(result), .boost = ParseBoostCounters(result),
.features = LookupBoostFeatures(channel), .features = LookupBoostFeatures(channel),
.lifting = lifting,
.allowMulti = (BoostsForGift(_session) > 0), .allowMulti = (BoostsForGift(_session) > 0),
.group = channel->isMegagroup(), .group = channel->isMegagroup(),
}, submit)); }, submit));
if (lifting) {
box->boxClosing() | rpl::start_with_next([=] {
if (*boosted) {
channel->updateFullForced();
}
}, box->lifetime());
}
}).fail([=](const MTP::Error &error) { }).fail([=](const MTP::Error &error) {
_boostStateResolving = nullptr; _boostStateResolving = nullptr;
showToast(u"Error: "_q + error.type()); showToast(u"Error: "_q + error.type());

View file

@ -253,7 +253,9 @@ public:
Dialogs::Key inChat, Dialogs::Key inChat,
PeerData *searchFrom = nullptr); PeerData *searchFrom = nullptr);
void resolveBoostState(not_null<ChannelData*> channel); void resolveBoostState(
not_null<ChannelData*> channel,
int boostsToLift = 0);
void resolveCollectible( void resolveCollectible(
PeerId ownerId, PeerId ownerId,
@ -319,6 +321,7 @@ private:
mtpRequestId _showingRepliesRequestId = 0; mtpRequestId _showingRepliesRequestId = 0;
ChannelData *_boostStateResolving = nullptr; ChannelData *_boostStateResolving = nullptr;
int _boostsToLift = 0;
QString _collectibleEntity; QString _collectibleEntity;
mtpRequestId _collectibleRequestId = 0; mtpRequestId _collectibleRequestId = 0;