diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index 29f186057..2cac04b85 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -2611,6 +2611,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_broadcast_silent_ph" = "Silent broadcast..."; "lng_send_anonymous_ph" = "Send anonymously..."; "lng_story_reply_ph" = "Reply privately..."; +"lng_story_comment_ph" = "Comment story..."; "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}"; diff --git a/Telegram/SourceFiles/dialogs/dialogs_row.cpp b/Telegram/SourceFiles/dialogs/dialogs_row.cpp index 29f849956..9ecddf88f 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_row.cpp +++ b/Telegram/SourceFiles/dialogs/dialogs_row.cpp @@ -448,7 +448,7 @@ void Row::paintUserpic( ? _cornerBadgeShown : !_cornerBadgeUserpic->layersManager.isDisplayedNone(); const auto storiesPeer = peer - ? ((peer->isUser() || peer->isBroadcast()) ? peer : nullptr) + ? ((peer->isUser() || peer->isChannel()) ? peer : nullptr) : nullptr; const auto storiesFolder = peer ? nullptr : _id.folder(); const auto storiesHas = storiesPeer diff --git a/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.cpp b/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.cpp index cc960fe9b..c7340489b 100644 --- a/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.cpp +++ b/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.cpp @@ -11,6 +11,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "base/event_filter.h" #include "base/platform/base_platform_info.h" #include "base/qt_signal_producer.h" +#include "base/timer_rpl.h" #include "base/unixtime.h" #include "boxes/edit_caption_box.h" #include "chat_helpers/compose/compose_show.h" @@ -95,6 +96,7 @@ constexpr auto kMouseEvents = { QEvent::MouseButtonPress, QEvent::MouseButtonRelease }; +constexpr auto kRefreshSlowmodeLabelTimeout = crl::time(200); constexpr auto kCommonModifiers = 0 | Qt::ShiftModifier @@ -3343,4 +3345,46 @@ void ComposeControls::checkCharsLimitation() { } } +rpl::producer SlowmodeSecondsLeft(not_null peer) { + return peer->session().changes().peerFlagsValue( + peer, + Data::PeerUpdate::Flag::Slowmode + ) | rpl::map([=] { + return peer->slowmodeSecondsLeft(); + }) | rpl::map([=](int delay) -> rpl::producer { + auto start = rpl::single(delay); + if (!delay) { + return start; + } + return std::move( + start + ) | rpl::then(base::timer_each( + kRefreshSlowmodeLabelTimeout + ) | rpl::map([=] { + return peer->slowmodeSecondsLeft(); + }) | rpl::take_while([=](int delay) { + return delay > 0; + })) | rpl::then(rpl::single(0)); + }) | rpl::flatten_latest(); +} + +rpl::producer SendDisabledBySlowmode(not_null peer) { + const auto history = peer->owner().history(peer); + auto hasSendingMessage = peer->session().changes().historyFlagsValue( + history, + Data::HistoryUpdate::Flag::ClientSideMessages + ) | rpl::map([=] { + return history->latestSendingMessage() != nullptr; + }) | rpl::distinct_until_changed(); + + using namespace rpl::mappers; + const auto channel = peer->asChannel(); + return (!channel || channel->amCreator()) + ? (rpl::single(false) | rpl::type_erased()) + : rpl::combine( + channel->slowmodeAppliedValue(), + std::move(hasSendingMessage), + _1 && _2); +} + } // namespace HistoryView diff --git a/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.h b/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.h index 4911bfde9..b1bc012ca 100644 --- a/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.h +++ b/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.h @@ -439,4 +439,9 @@ private: }; +[[nodiscard]] rpl::producer SlowmodeSecondsLeft( + not_null peer); +[[nodiscard]] rpl::producer SendDisabledBySlowmode( + not_null peer); + } // namespace HistoryView diff --git a/Telegram/SourceFiles/history/view/history_view_replies_section.cpp b/Telegram/SourceFiles/history/view/history_view_replies_section.cpp index 1043cd596..a599e638e 100644 --- a/Telegram/SourceFiles/history/view/history_view_replies_section.cpp +++ b/Telegram/SourceFiles/history/view/history_view_replies_section.cpp @@ -99,8 +99,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL namespace HistoryView { namespace { -constexpr auto kRefreshSlowmodeLabelTimeout = crl::time(200); - rpl::producer RootViewContent( not_null history, MsgId rootId, @@ -631,45 +629,6 @@ bool RepliesWidget::computeAreComments() const { } void RepliesWidget::setupComposeControls() { - auto slowmodeSecondsLeft = session().changes().peerFlagsValue( - _history->peer, - Data::PeerUpdate::Flag::Slowmode - ) | rpl::map([=] { - return _history->peer->slowmodeSecondsLeft(); - }) | rpl::map([=](int delay) -> rpl::producer { - auto start = rpl::single(delay); - if (!delay) { - return start; - } - return std::move( - start - ) | rpl::then(base::timer_each( - kRefreshSlowmodeLabelTimeout - ) | rpl::map([=] { - return _history->peer->slowmodeSecondsLeft(); - }) | rpl::take_while([=](int delay) { - return delay > 0; - })) | rpl::then(rpl::single(0)); - }) | rpl::flatten_latest(); - - const auto channel = _history->peer->asChannel(); - Assert(channel != nullptr); - - auto hasSendingMessage = session().changes().historyFlagsValue( - _history, - Data::HistoryUpdate::Flag::ClientSideMessages - ) | rpl::map([=] { - return _history->latestSendingMessage() != nullptr; - }) | rpl::distinct_until_changed(); - - using namespace rpl::mappers; - auto sendDisabledBySlowmode = (!channel || channel->amCreator()) - ? (rpl::single(false) | rpl::type_erased()) - : rpl::combine( - channel->slowmodeAppliedValue(), - std::move(hasSendingMessage), - _1 && _2); - auto topicWriteRestrictions = rpl::single( ) | rpl::then(session().changes().topicUpdates( Data::TopicUpdate::Flag::Closed @@ -719,8 +678,8 @@ void RepliesWidget::setupComposeControls() { .topicRootId = _topic ? _topic->rootId() : MsgId(0), .showSlowmodeError = [=] { return showSlowmodeError(); }, .sendActionFactory = [=] { return prepareSendAction({}); }, - .slowmodeSecondsLeft = std::move(slowmodeSecondsLeft), - .sendDisabledBySlowmode = std::move(sendDisabledBySlowmode), + .slowmodeSecondsLeft = SlowmodeSecondsLeft(_history->peer), + .sendDisabledBySlowmode = SendDisabledBySlowmode(_history->peer), .writeRestriction = std::move(writeRestriction), }); diff --git a/Telegram/SourceFiles/media/stories/media_stories_controller.cpp b/Telegram/SourceFiles/media/stories/media_stories_controller.cpp index 76e880664..7814b71d2 100644 --- a/Telegram/SourceFiles/media/stories/media_stories_controller.cpp +++ b/Telegram/SourceFiles/media/stories/media_stories_controller.cpp @@ -299,7 +299,9 @@ Controller::Controller(not_null delegate) _reactions->chosen( ) | rpl::start_with_next([=](Reactions::Chosen chosen) { - reactionChosen(chosen.mode, chosen.reaction); + if (reactionChosen(chosen.mode, chosen.reaction)) { + _reactions->animateAndProcess(std::move(chosen)); + } }, _lifetime); _delegate->storiesLayerShown( @@ -628,13 +630,15 @@ void Controller::toggleLiked() { _reactions->toggleLiked(); } -void Controller::reactionChosen(ReactionsMode mode, ChosenReaction chosen) { +bool Controller::reactionChosen(ReactionsMode mode, ChosenReaction chosen) { + auto result = true; if (mode == ReactionsMode::Message) { - _replyArea->sendReaction(chosen.id); + result = _replyArea->sendReaction(chosen.id); } else if (const auto peer = shownPeer()) { peer->owner().stories().sendReaction(_shown, chosen.id); } unfocusReply(); + return result; } void Controller::showFullCaption() { @@ -907,7 +911,7 @@ void Controller::show( .views = story->views(), .total = story->interactions(), .type = RecentViewsTypeFor(peer), - .canViewReactions = CanViewReactionsFor(peer), + .canViewReactions = CanViewReactionsFor(peer) && !peer->isMegagroup(), }, _reactions->likedValue()); if (const auto nowLikeButton = _recentViews->likeButton()) { if (wasLikeButton != nowLikeButton) { @@ -1007,7 +1011,7 @@ void Controller::subscribeToSession() { .views = update.story->views(), .total = update.story->interactions(), .type = RecentViewsTypeFor(peer), - .canViewReactions = CanViewReactionsFor(peer), + .canViewReactions = CanViewReactionsFor(peer) && !peer->isMegagroup(), }); updateAreas(update.story); } diff --git a/Telegram/SourceFiles/media/stories/media_stories_controller.h b/Telegram/SourceFiles/media/stories/media_stories_controller.h index 0a6c4a8fe..b8ad1e20b 100644 --- a/Telegram/SourceFiles/media/stories/media_stories_controller.h +++ b/Telegram/SourceFiles/media/stories/media_stories_controller.h @@ -260,7 +260,7 @@ private: [[nodiscard]] int repostSkipTop() const; void updateAreas(Data::Story *story); - void reactionChosen(ReactionsMode mode, ChosenReaction chosen); + bool reactionChosen(ReactionsMode mode, ChosenReaction chosen); const not_null _delegate; diff --git a/Telegram/SourceFiles/media/stories/media_stories_reactions.cpp b/Telegram/SourceFiles/media/stories/media_stories_reactions.cpp index bea75eb90..f401f6b37 100644 --- a/Telegram/SourceFiles/media/stories/media_stories_reactions.cpp +++ b/Telegram/SourceFiles/media/stories/media_stories_reactions.cpp @@ -793,7 +793,7 @@ Reactions::Reactions(not_null controller) : _controller(controller) , _panel(std::make_unique(_controller)) { _panel->chosen() | rpl::start_with_next([=](Chosen &&chosen) { - animateAndProcess(std::move(chosen)); + _chosen.fire(std::move(chosen)); }, _lifetime); } @@ -887,7 +887,7 @@ auto Reactions::attachToMenu( selector->chosen() | rpl::start_with_next([=](ChosenReaction reaction) { menu->hideMenu(); - animateAndProcess({ reaction, ReactionsMode::Reaction }); + _chosen.fire({ reaction, ReactionsMode::Reaction }); }, selector->lifetime()); return AttachSelectorResult::Attached; @@ -933,7 +933,7 @@ void Reactions::toggleLiked() { void Reactions::applyLike(Data::ReactionId id) { if (_liked.current() != id) { - animateAndProcess({ { .id = id }, ReactionsMode::Reaction }); + _chosen.fire({ { .id = id }, ReactionsMode::Reaction }); } } @@ -971,8 +971,6 @@ void Reactions::animateAndProcess(Chosen &&chosen) { .scaleOutTarget = scaleOutTarget, }, target, std::move(done)); } - - _chosen.fire(std::move(chosen)); } void Reactions::assignLikedId(Data::ReactionId id) { diff --git a/Telegram/SourceFiles/media/stories/media_stories_reactions.h b/Telegram/SourceFiles/media/stories/media_stories_reactions.h index d27761f46..de9f0e0ce 100644 --- a/Telegram/SourceFiles/media/stories/media_stories_reactions.h +++ b/Telegram/SourceFiles/media/stories/media_stories_reactions.h @@ -87,6 +87,8 @@ public: void attachToReactionButton(not_null button); void setReactionIconWidget(Ui::RpWidget *widget); + void animateAndProcess(Chosen &&chosen); + using AttachStripResult = HistoryView::Reactions::AttachSelectorResult; [[nodiscard]] AttachStripResult attachToMenu( not_null menu, @@ -95,8 +97,6 @@ public: private: class Panel; - void animateAndProcess(Chosen &&chosen); - void assignLikedId(Data::ReactionId id); [[nodiscard]] Fn setLikedIdIconInit( not_null owner, diff --git a/Telegram/SourceFiles/media/stories/media_stories_recent_views.cpp b/Telegram/SourceFiles/media/stories/media_stories_recent_views.cpp index 442199300..e6b53d277 100644 --- a/Telegram/SourceFiles/media/stories/media_stories_recent_views.cpp +++ b/Telegram/SourceFiles/media/stories/media_stories_recent_views.cpp @@ -139,7 +139,7 @@ constexpr auto kLoadViewsPages = 2; RecentViewsType RecentViewsTypeFor(not_null peer) { return peer->isSelf() ? RecentViewsType::Self - : peer->isChannel() + : peer->isBroadcast() ? RecentViewsType::Channel : peer->isServiceUser() ? RecentViewsType::Changelog diff --git a/Telegram/SourceFiles/media/stories/media_stories_reply.cpp b/Telegram/SourceFiles/media/stories/media_stories_reply.cpp index dba4cb02b..d6959d1a6 100644 --- a/Telegram/SourceFiles/media/stories/media_stories_reply.cpp +++ b/Telegram/SourceFiles/media/stories/media_stories_reply.cpp @@ -39,6 +39,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "storage/storage_account.h" #include "storage/storage_media_prepare.h" #include "ui/chat/attach/attach_prepare.h" +#include "ui/text/format_values.h" #include "ui/round_rect.h" #include "window/section_widget.h" #include "styles/style_boxes.h" // sendMediaPreviewSize. @@ -49,11 +50,15 @@ namespace Media::Stories { namespace { [[nodiscard]] rpl::producer PlaceholderText( - const std::shared_ptr &show) { - return show->session().data().stories().stealthModeValue( - ) | rpl::map([](Data::StealthMode value) { - return value.enabledTill; - }) | rpl::distinct_until_changed() | rpl::map([](TimeId till) { + const std::shared_ptr &show, + rpl::producer isComment) { + return rpl::combine( + show->session().data().stories().stealthModeValue(), + std::move(isComment) + ) | rpl::map([](Data::StealthMode value, bool isComment) { + return std::tuple(value.enabledTill, isComment); + }) | rpl::distinct_until_changed( + ) | rpl::map([](TimeId till, bool isComment) { return rpl::single( rpl::empty ) | rpl::then( @@ -64,11 +69,13 @@ namespace { return left > 0; }) | rpl::then( rpl::single(0) - ) | rpl::map([](TimeId left) { + ) | rpl::map([=](TimeId left) { return left ? tr::lng_stealth_mode_countdown( lt_left, rpl::single(TimeLeftText(left))) + : isComment + ? tr::lng_story_comment_ph() : tr::lng_story_reply_ph(); }) | rpl::flatten_latest(); }) | rpl::flatten_latest(); @@ -118,7 +125,9 @@ ReplyArea::ReplyArea(not_null controller) .mode = HistoryView::ComposeControlsMode::Normal, .sendMenuType = SendMenu::Type::SilentOnly, .stickerOrEmojiChosen = _controller->stickerOrEmojiChosen(), - .customPlaceholder = PlaceholderText(_controller->uiShow()), + .customPlaceholder = PlaceholderText( + _controller->uiShow(), + rpl::deferred([=] { return _isComment.value(); })), .voiceCustomCancelText = tr::lng_record_cancel_stories(tr::now), .voiceLockFromBottom = true, .features = { @@ -171,7 +180,7 @@ void ReplyArea::initGeometry() { }, _lifetime); } -void ReplyArea::sendReaction(const Data::ReactionId &id) { +bool ReplyArea::sendReaction(const Data::ReactionId &id) { Expects(_data.peer != nullptr); auto message = Api::MessageToSend(prepareSendAction({})); @@ -188,9 +197,8 @@ void ReplyArea::sendReaction(const Data::ReactionId &id) { }; } } - if (!message.textWithTags.empty()) { - send(std::move(message), {}, true); - } + return !message.textWithTags.empty() + && send(std::move(message), {}, true); } void ReplyArea::send(Api::SendOptions options) { @@ -203,10 +211,14 @@ void ReplyArea::send(Api::SendOptions options) { send(std::move(message), options); } -void ReplyArea::send( +bool ReplyArea::send( Api::MessageToSend message, Api::SendOptions options, bool skipToast) { + if (!options.scheduled && showSlowmodeError()) { + return false; + } + const auto error = GetErrorTextForSending( _data.peer, { @@ -216,12 +228,14 @@ void ReplyArea::send( }); if (!error.isEmpty()) { _controller->uiShow()->showToast(error); + return false; } session().api().sendMessage(std::move(message)); finishSending(skipToast); _controls->clear(); + return true; } void ReplyArea::sendVoice(VoiceToSend &&data) { @@ -249,7 +263,8 @@ bool ReplyArea::sendExistingDocument( if (error) { show->showToast(*error); return false; - } else if (Window::ShowSendPremiumError(show, document)) { + } else if (showSlowmodeError() + || Window::ShowSendPremiumError(show, document)) { return false; } @@ -279,6 +294,8 @@ bool ReplyArea::sendExistingPhoto( if (error) { show->showToast(*error); return false; + } else if (showSlowmodeError()) { + return false; } Api::SendExistingPhoto( @@ -409,6 +426,8 @@ void ReplyArea::chooseAttach( if (const auto error = Data::AnyFileRestrictionError(peer)) { _controller->uiShow()->showToast(*error); return; + } else if (showSlowmodeError()) { + return; } const auto filter = (overrideSendImagesAsPhotos == true) @@ -666,6 +685,7 @@ void ReplyArea::show( const auto peer = data.peer; const auto history = peer ? peer->owner().history(peer).get() : nullptr; const auto user = peer->asUser(); + _isComment = peer->isMegagroup(); auto writeRestriction = Data::CanSendAnythingValue( peer ) | rpl::map([=](bool can) { @@ -681,8 +701,13 @@ void ReplyArea::show( .type = WriteRestrictionType::PremiumRequired, }; }); + using namespace HistoryView; _controls->setHistory({ .history = history, + .showSlowmodeError = [=] { return showSlowmodeError(); }, + .sendActionFactory = [=] { return prepareSendAction({}); }, + .slowmodeSecondsLeft = SlowmodeSecondsLeft(history->peer), + .sendDisabledBySlowmode = SendDisabledBySlowmode(history->peer), .liked = std::move( likedValue ) | rpl::map([](const Data::ReactionId &id) { @@ -692,7 +717,7 @@ void ReplyArea::show( }); _controls->clear(); const auto hidden = peer - && (!peer->isUser() || peer->isSelf() || peer->isServiceUser()); + && (peer->isBroadcast() || peer->isSelf() || peer->isServiceUser()); const auto cant = !peer; if (!hidden && !cant) { _controls->show(); @@ -714,6 +739,33 @@ void ReplyArea::show( } } +bool ReplyArea::showSlowmodeError() { + const auto text = [&] { + const auto story = _controller->story(); + if (!story) { + return QString(); + } + const auto peer = story->peer(); + if (const auto left = peer->slowmodeSecondsLeft()) { + return tr::lng_slowmode_enabled( + tr::now, + lt_left, + Ui::FormatDurationWordsSlowmode(left)); + } else if (peer->slowmodeApplied()) { + const auto history = peer->owner().history(peer); + if (const auto item = history->latestSendingMessage()) { + return tr::lng_slowmode_no_many(tr::now); + } + } + return QString(); + }(); + if (text.isEmpty()) { + return false; + } + _controller->uiShow()->showToast(text); + return true; +} + Main::Session &ReplyArea::session() const { Expects(_data.peer != nullptr); diff --git a/Telegram/SourceFiles/media/stories/media_stories_reply.h b/Telegram/SourceFiles/media/stories/media_stories_reply.h index bb5a58e70..90214b421 100644 --- a/Telegram/SourceFiles/media/stories/media_stories_reply.h +++ b/Telegram/SourceFiles/media/stories/media_stories_reply.h @@ -64,7 +64,7 @@ public: void show( ReplyAreaData data, rpl::producer likedValue); - void sendReaction(const Data::ReactionId &id); + bool sendReaction(const Data::ReactionId &id); [[nodiscard]] bool focused() const; [[nodiscard]] rpl::producer focusedValue() const; @@ -84,7 +84,7 @@ private: [[nodiscard]] Main::Session &session() const; [[nodiscard]] not_null history() const; - void send( + bool send( Api::MessageToSend message, Api::SendOptions options, bool skipToast = false); @@ -142,8 +142,11 @@ private: void chooseAttach(std::optional overrideSendImagesAsPhotos); void showPremiumToast(not_null emoji); + [[nodiscard]] bool showSlowmodeError(); const not_null _controller; + rpl::variable _isComment; + const std::unique_ptr _controls; std::unique_ptr _cant;