diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index 2eed3e508..865e14498 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -1944,6 +1944,18 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_broadcast_ph" = "Broadcast a message..."; "lng_broadcast_silent_ph" = "Silent broadcast..."; "lng_send_anonymous_ph" = "Send anonymously..."; +"lng_send_text_no" = "Text not allowed."; +"lng_send_text_no_about" = "The admins of this group only allow sending {types}."; +"lng_send_text_type_and_last" = "{types} and {last}"; +"lng_send_text_type_photos" = "Photos"; +"lng_send_text_type_videos" = "Video files"; +"lng_send_text_type_video_messages" = "Video Messages"; +"lng_send_text_type_music" = "Music"; +"lng_send_text_type_voice_messages" = "Voice Messages"; +"lng_send_text_type_files" = "Files"; +"lng_send_text_type_stickers" = "Stickers & GIFs"; +"lng_send_text_type_polls" = "Polls"; + "lng_send_as_title" = "Send message as..."; "lng_send_as_anonymous_admin" = "Anonymous admin"; "lng_send_as_premium_required" = "Subscribe to {link} to be able to comment on behalf of your channels in group chats."; diff --git a/Telegram/SourceFiles/chat_helpers/message_field.cpp b/Telegram/SourceFiles/chat_helpers/message_field.cpp index 471a0434d..019fd5517 100644 --- a/Telegram/SourceFiles/chat_helpers/message_field.cpp +++ b/Telegram/SourceFiles/chat_helpers/message_field.cpp @@ -18,7 +18,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "core/shortcuts.h" #include "core/application.h" #include "core/core_settings.h" +#include "ui/toast/toast.h" #include "ui/wrap/vertical_layout.h" +#include "ui/widgets/buttons.h" #include "ui/widgets/popup_menu.h" #include "ui/ui_utility.h" #include "data/data_session.h" @@ -50,6 +52,7 @@ using EditLinkAction = Ui::InputField::EditLinkAction; using EditLinkSelection = Ui::InputField::EditLinkSelection; constexpr auto kParseLinksTimeout = crl::time(1000); +constexpr auto kTypesDuration = 4 * crl::time(1000); // For mention / custom emoji tags save and validate selfId, // ignore tags for different users. @@ -773,3 +776,86 @@ void MessageLinksParser::apply( } _list = std::move(parsed); } + +base::unique_qptr CreateDisabledFieldView( + QWidget *parent, + not_null peer) { + auto result = base::make_unique_q(parent); + const auto raw = result.get(); + const auto label = CreateChild( + result.get(), + tr::lng_send_text_no(), + st::historySendDisabled); + label->setAttribute(Qt::WA_TransparentForMouseEvents); + raw->setPointerCursor(false); + raw->widthValue( + ) | rpl::start_with_next([=](int width) { + const auto &st = st::historyComposeField; + const auto margins = (st.textMargins + st.placeholderMargins); + const auto available = width - margins.left() - margins.right(); + const auto skip = st::historySendDisabledIconSkip; + label->resizeToWidth(available - skip); + label->moveToLeft(margins.left() + skip, margins.top(), width); + }, label->lifetime()); + raw->paintRequest( + ) | rpl::start_with_next([=] { + auto p = QPainter(raw); + const auto &st = st::historyComposeField; + const auto margins = (st.textMargins + st.placeholderMargins); + const auto &icon = st::historySendDisabledIcon; + icon.paint( + p, + margins.left() + st::historySendDisabledPosition.x(), + margins.top() + st::historySendDisabledPosition.y(), + raw->width()); + }, raw->lifetime()); + using WeakToast = base::weak_ptr; + const auto toast = raw->lifetime().make_state(); + raw->setClickedCallback([=] { + if (toast->get()) { + return; + } + using Flag = ChatRestriction; + const auto map = base::flat_map>{ + { Flag::SendPhotos, tr::lng_send_text_type_photos }, + { Flag::SendVideos, tr::lng_send_text_type_videos }, + { + Flag::SendVideoMessages, + tr::lng_send_text_type_video_messages, + }, + { Flag::SendMusic, tr::lng_send_text_type_music }, + { + Flag::SendVoiceMessages, + tr::lng_send_text_type_voice_messages, + }, + { Flag::SendFiles, tr::lng_send_text_type_files }, + { Flag::SendStickers, tr::lng_send_text_type_stickers }, + { Flag::SendPolls, tr::lng_send_text_type_polls }, + }; + auto list = QStringList(); + for (const auto &[flag, phrase] : map) { + if (Data::CanSend(peer, flag, false)) { + list.append(phrase(tr::now)); + } + } + if (list.empty()) { + return; + } + const auto types = (list.size() > 1) + ? tr::lng_send_text_type_and_last( + tr::now, + lt_types, + list.mid(0, list.size() - 1).join(", "), + lt_last, + list.back()) + : list.back(); + *toast = Ui::Toast::Show(parent, { + .text = { tr::lng_send_text_no_about(tr::now, lt_types, types) }, + .st = &st::defaultMultilineToast, + .durationMs = kTypesDuration, + .multiline = true, + .slideSide = RectPart::Bottom, + }); + }); + return result; +} diff --git a/Telegram/SourceFiles/chat_helpers/message_field.h b/Telegram/SourceFiles/chat_helpers/message_field.h index e91257a85..8efa27c2c 100644 --- a/Telegram/SourceFiles/chat_helpers/message_field.h +++ b/Telegram/SourceFiles/chat_helpers/message_field.h @@ -122,3 +122,7 @@ private: base::qt_connection _connection; }; + +[[nodiscard]] base::unique_qptr CreateDisabledFieldView( + QWidget *parent, + not_null peer); diff --git a/Telegram/SourceFiles/chat_helpers/tabbed_selector.cpp b/Telegram/SourceFiles/chat_helpers/tabbed_selector.cpp index e1f22258f..95fa113fe 100644 --- a/Telegram/SourceFiles/chat_helpers/tabbed_selector.cpp +++ b/Telegram/SourceFiles/chat_helpers/tabbed_selector.cpp @@ -918,9 +918,9 @@ void TabbedSelector::checkRestrictedPeer() { _currentPeer, ChatRestriction::SendGifs) : (_currentTabType == SelectorTab::Emoji && _mode == Mode::Full) - ? (Data::RestrictionError( - _currentPeer, - ChatRestriction::SendInline) + ? ((true || Data::RestrictionError( + _currentPeer, // We don't allow input if texts are forbidden. + ChatRestriction::SendInline)) ? Data::RestrictionError( _currentPeer, ChatRestriction::SendOther) diff --git a/Telegram/SourceFiles/history/history_widget.cpp b/Telegram/SourceFiles/history/history_widget.cpp index ed8b0dbbb..1b72dd6ab 100644 --- a/Telegram/SourceFiles/history/history_widget.cpp +++ b/Telegram/SourceFiles/history/history_widget.cpp @@ -1737,7 +1737,7 @@ void HistoryWidget::setInnerFocus() { || isRecording() || isBotStart() || isBlocked() - || !_canSendMessages) { + || !_canSendTexts) { _list->setFocus(); } else { _field->setFocus(); @@ -1870,7 +1870,7 @@ void HistoryWidget::fastShowAtEnd(not_null history) { void HistoryWidget::applyDraft(FieldHistoryAction fieldHistoryAction) { InvokeQueued(this, [=] { updateStickersByEmoji(); }); - if (_voiceRecordBar->isActive()) { + if (_voiceRecordBar->isActive() || !_canSendTexts) { return; } @@ -1884,7 +1884,7 @@ void HistoryWidget::applyDraft(FieldHistoryAction fieldHistoryAction) { if (!draft || (!_history->localEditDraft({}) && !fieldAvailable)) { auto fieldWillBeHiddenAfterEdit = (!fieldAvailable && _editMsgId != 0); clearFieldText(0, fieldHistoryAction); - _field->setFocus(); + setInnerFocus(); _processingReplyItem = _replyEditMsg = nullptr; _processingReplyId = _replyToId = 0; setEditMsgId(0); @@ -1898,7 +1898,7 @@ void HistoryWidget::applyDraft(FieldHistoryAction fieldHistoryAction) { _textUpdateEvents = 0; setFieldText(draft->textWithTags, 0, fieldHistoryAction); - _field->setFocus(); + setInnerFocus(); draft->cursor.applyTo(_field); _textUpdateEvents = TextUpdateEvent::SaveDraft | TextUpdateEvent::SendTyping; @@ -2110,6 +2110,8 @@ void HistoryWidget::showHistory( _list = nullptr; _peer = nullptr; _canSendMessages = false; + _canSendTexts = false; + _fieldDisabled = nullptr; _silent.destroy(); updateBotKeyboard(); } else { @@ -2136,7 +2138,6 @@ void HistoryWidget::showHistory( if (peerId) { _peer = session().data().peer(peerId); - _canSendMessages = Data::CanSendAnything(_peer); _contactStatus = std::make_unique( controller(), this, @@ -2628,6 +2629,13 @@ std::optional HistoryWidget::writeRestriction() const { } void HistoryWidget::updateControlsVisibility() { + auto fieldDisabledRemoved = (_fieldDisabled != nullptr); + const auto guard = gsl::finally([&] { + if (fieldDisabledRemoved) { + _fieldDisabled = nullptr; + } + }); + if (!_showAnimation) { _topShadow->setVisible(_peer != nullptr); _topBar->setVisible(_peer != nullptr); @@ -2745,7 +2753,19 @@ void HistoryWidget::updateControlsVisibility() { _send->show(); updateSendButtonType(); - _field->show(); + if (_canSendTexts) { + _field->show(); + } else { + fieldDisabledRemoved = false; + if (!_fieldDisabled) { + _fieldDisabled = CreateDisabledFieldView(this, _peer); + orderWidgets(); + updateControlsGeometry(); + update(); + } + _fieldDisabled->show(); + hideFieldIfVisible(); + } if (_kbShown) { _kbScroll->show(); _tabbedSelectorToggle->hide(); @@ -3696,7 +3716,7 @@ void HistoryWidget::saveEditMsg() { cancelEdit(); } else if (error == u"MESSAGE_EMPTY"_q) { _field->selectAll(); - _field->setFocus(); + setInnerFocus(); } else { controller()->showToast({ tr::lng_edit_error(tr::now) }); } @@ -3819,7 +3839,7 @@ void HistoryWidget::send(Api::SendOptions options) { hideSelectorControlsAnimated(); if (_previewData && _previewData->pendingTill) previewCancel(); - _field->setFocus(); + setInnerFocus(); if (!_keyboard->hasMarkup() && _keyboard->forceReply() && !_kbReplyTo) { toggleKeyboard(); @@ -4260,7 +4280,7 @@ void HistoryWidget::sendBotCommand(const Bot::SendCommandRequest &request) { } } - _field->setFocus(); + setInnerFocus(); } void HistoryWidget::hideSingleUseKeyboard(PeerData *peer, MsgId replyTo) { @@ -4281,7 +4301,7 @@ void HistoryWidget::hideSingleUseKeyboard(PeerData *peer, MsgId replyTo) { } bool HistoryWidget::insertBotCommand(const QString &cmd) { - if (!canWriteMessage()) { + if (!_canSendTexts) { return false; } @@ -4327,7 +4347,7 @@ bool HistoryWidget::insertBotCommand(const QString &cmd) { { toInsert, TextWithTags::Tags() }, TextUpdateEvent::SaveDraft, Ui::InputField::HistoryAction::NewEntry); - _field->setFocus(); + setInnerFocus(); return true; } return false; @@ -4808,10 +4828,20 @@ void HistoryWidget::recountChatWidth() { controller()->adaptive().setChatLayout(layout); } +int HistoryWidget::fieldHeight() const { + return _canSendTexts + ? _field->height() + : (st::historySendSize.height() - 2 * st::historySendPadding); +} + +bool HistoryWidget::fieldOrDisabledShown() const { + return !_field->isHidden() || _fieldDisabled; +} + void HistoryWidget::moveFieldControls() { auto keyboardHeight = 0; auto bottom = height(); - auto maxKeyboardHeight = computeMaxFieldHeight() - _field->height(); + auto maxKeyboardHeight = computeMaxFieldHeight() - fieldHeight(); _keyboard->resizeToWidth(width(), maxKeyboardHeight); if (_kbShown) { keyboardHeight = qMin(_keyboard->height(), maxKeyboardHeight); @@ -4834,6 +4864,11 @@ void HistoryWidget::moveFieldControls() { _sendAs->moveToLeft(left, buttonsBottom); left += _sendAs->width(); } _field->moveToLeft(left, bottom - _field->height() - st::historySendPadding); + if (_fieldDisabled) { + _fieldDisabled->moveToLeft( + left, + bottom - fieldHeight() - st::historySendPadding); + } auto right = st::historySendRight; _send->moveToRight(right, buttonsBottom); right += _send->width(); _voiceRecordBar->moveToLeft(0, bottom - _voiceRecordBar->height()); @@ -4896,6 +4931,9 @@ void HistoryWidget::updateFieldSize() { if (_scheduled) fieldWidth -= _scheduled->width(); if (_ttlInfo) fieldWidth -= _ttlInfo->width(); + if (_fieldDisabled) { + _fieldDisabled->resize(fieldWidth, fieldHeight()); + } if (_field->width() != fieldWidth) { _field->resize(fieldWidth, _field->height()); } else { @@ -5552,7 +5590,7 @@ void HistoryWidget::updateHistoryGeometry( newScrollHeight -= _unblock->height(); } else { if (editingMessage() || _canSendMessages) { - newScrollHeight -= (_field->height() + 2 * st::historySendPadding); + newScrollHeight -= (fieldHeight() + 2 * st::historySendPadding); } else if (writeRestriction().has_value()) { newScrollHeight -= _unblock->height(); } @@ -6197,7 +6235,7 @@ void HistoryWidget::sendInlineResult(InlineBots::ResultSelected result) { hideSelectorControlsAnimated(); - _field->setFocus(); + setInnerFocus(); } void HistoryWidget::updatePinnedViewer() { @@ -6690,7 +6728,7 @@ bool HistoryWidget::sendExistingDocument( hideSelectorControlsAnimated(); - _field->setFocus(); + setInnerFocus(); return true; } @@ -6715,7 +6753,7 @@ bool HistoryWidget::sendExistingPhoto( hideSelectorControlsAnimated(); - _field->setFocus(); + setInnerFocus(); return true; } @@ -6889,9 +6927,7 @@ void HistoryWidget::setReplyFieldsFromProcessing() { _saveDraftStart = crl::now(); saveDraft(); - if (!_field->isHidden()) { - _field->setFocus(); - } + setInnerFocus(); } void HistoryWidget::editMessage(FullMsgId itemId) { @@ -6961,7 +6997,9 @@ void HistoryWidget::editMessage(not_null item) { updateBotKeyboard(); - if (!_field->isHidden()) _fieldBarCancel->show(); + if (fieldOrDisabledShown()) { + _fieldBarCancel->show(); + } updateFieldPlaceholder(); updateMouseTracking(); updateReplyToName(); @@ -6972,7 +7010,7 @@ void HistoryWidget::editMessage(not_null item) { _saveDraftStart = crl::now(); saveDraft(); - _field->setFocus(); + setInnerFocus(); } void HistoryWidget::hidePinnedMessage() { @@ -7328,10 +7366,15 @@ bool HistoryWidget::updateCanSendMessage() { const auto newCanSendMessages = topic ? Data::CanSendAnyOf(topic, allWithoutPolls) : Data::CanSendAnyOf(_peer, allWithoutPolls); - if (_canSendMessages == newCanSendMessages) { + const auto newCanSendTexts = topic + ? Data::CanSend(topic, ChatRestriction::SendOther) + : Data::CanSend(_peer, ChatRestriction::SendOther); + if (_canSendMessages == newCanSendMessages + && _canSendTexts == newCanSendTexts) { return false; } _canSendMessages = newCanSendMessages; + _canSendTexts = newCanSendTexts; if (!_canSendMessages) { cancelReply(); } @@ -7474,7 +7517,7 @@ void HistoryWidget::updateTopBarSelection() { || isRecording() || isBotStart() || isBlocked() - || !_canSendMessages) { + || !_canSendTexts) { _list->setFocus(); } else { _field->setFocus(); @@ -7504,7 +7547,7 @@ void HistoryWidget::updateReplyEditText(not_null item) { item->inReplyText(), Ui::DialogTextOptions(), context); - if (!_field->isHidden() || isRecording()) { + if (fieldOrDisabledShown() || isRecording()) { _fieldBarCancel->show(); updateMouseTracking(); } @@ -7581,7 +7624,7 @@ void HistoryWidget::drawField(Painter &p, const QRect &rect) { _repaintFieldScheduled = false; auto backy = _field->y() - st::historySendPadding; - auto backh = _field->height() + 2 * st::historySendPadding; + auto backh = fieldHeight() + 2 * st::historySendPadding; auto hasForward = readyToForward(); auto drawMsgText = (_editMsgId || _replyToId) ? _replyEditMsg : _kbReplyTo; if (_editMsgId || _replyToId || (!hasForward && _kbReplyTo)) { @@ -7801,7 +7844,8 @@ void HistoryWidget::paintEvent(QPaintEvent *e) { Painter p(this); const auto clip = e->rect(); if (_list) { - const auto restrictionHidden = !_field->isHidden() || isRecording(); + const auto restrictionHidden = fieldOrDisabledShown() + || isRecording(); if (restrictionHidden || replyToId() || readyToForward() @@ -7824,7 +7868,7 @@ void HistoryWidget::paintEvent(QPaintEvent *e) { const auto tr = QRect( (width() - w) / 2, st::msgServiceMargin.top() + (height() - - _field->height() + - fieldHeight() - 2 * st::historySendPadding - h - st::msgServiceMargin.top() diff --git a/Telegram/SourceFiles/history/history_widget.h b/Telegram/SourceFiles/history/history_widget.h index aa0c827aa..5533ca0cc 100644 --- a/Telegram/SourceFiles/history/history_widget.h +++ b/Telegram/SourceFiles/history/history_widget.h @@ -569,6 +569,8 @@ private: void clearFieldText( TextUpdateEvents events = 0, FieldHistoryAction fieldHistoryAction = FieldHistoryAction::Clear); + [[nodiscard]] int fieldHeight() const; + [[nodiscard]] bool fieldOrDisabledShown() const; void unregisterDraftSources(); void registerDraftSource(); @@ -589,8 +591,8 @@ private: void checkReplyReturns(); void scrollToAnimationCallback(FullMsgId attachToId, int relativeTo); - bool readyToForward() const; - bool hasSilentToggle() const; + [[nodiscard]] bool readyToForward() const; + [[nodiscard]] bool hasSilentToggle() const; void checkSupportPreload(bool force = false); void handleSupportSwitch(not_null updated); @@ -676,6 +678,7 @@ private: PeerData *_peer = nullptr; bool _canSendMessages = false; + bool _canSendTexts = false; MsgId _showAtMsgId = ShowAtUnreadMsgId; int _firstLoadRequest = 0; // Not real mtpRequestId. @@ -742,6 +745,7 @@ private: std::unique_ptr _composeSearch; bool _cmdStartShown = false; object_ptr _field; + base::unique_qptr _fieldDisabled; bool _inReplyEditForward = false; bool _inClickable = false; diff --git a/Telegram/SourceFiles/ui/chat/chat.style b/Telegram/SourceFiles/ui/chat/chat.style index 3cc7b7a50..000f42735 100644 --- a/Telegram/SourceFiles/ui/chat/chat.style +++ b/Telegram/SourceFiles/ui/chat/chat.style @@ -1247,3 +1247,12 @@ historyTranslateSettings: IconButton(defaultIconButton) { ripple: defaultRippleAnimation; } historyTranslateMenuPosition: point(-6px, 40px); + +historySendDisabled: FlatLabel(defaultFlatLabel) { + minWidth: 10px; + maxHeight: 20px; + textFg: placeholderFg; +} +historySendDisabledIcon: icon {{ "emoji/premium_lock", placeholderFgActive }}; +historySendDisabledIconSkip: 20px; +historySendDisabledPosition: point(0px, 0px);