Send comments to group stories.

This commit is contained in:
John Preston 2024-02-12 13:15:08 +04:00
parent 11f0847295
commit f674ace805
12 changed files with 140 additions and 74 deletions

View file

@ -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}";

View file

@ -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

View file

@ -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<int> SlowmodeSecondsLeft(not_null<PeerData*> peer) {
return peer->session().changes().peerFlagsValue(
peer,
Data::PeerUpdate::Flag::Slowmode
) | rpl::map([=] {
return peer->slowmodeSecondsLeft();
}) | rpl::map([=](int delay) -> rpl::producer<int> {
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<bool> SendDisabledBySlowmode(not_null<PeerData*> 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

View file

@ -439,4 +439,9 @@ private:
};
[[nodiscard]] rpl::producer<int> SlowmodeSecondsLeft(
not_null<PeerData*> peer);
[[nodiscard]] rpl::producer<bool> SendDisabledBySlowmode(
not_null<PeerData*> peer);
} // namespace HistoryView

View file

@ -99,8 +99,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
namespace HistoryView {
namespace {
constexpr auto kRefreshSlowmodeLabelTimeout = crl::time(200);
rpl::producer<Ui::MessageBarContent> RootViewContent(
not_null<History*> 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<int> {
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),
});

View file

@ -299,7 +299,9 @@ Controller::Controller(not_null<Delegate*> 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);
}

View file

@ -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*> _delegate;

View file

@ -793,7 +793,7 @@ Reactions::Reactions(not_null<Controller*> controller)
: _controller(controller)
, _panel(std::make_unique<Panel>(_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) {

View file

@ -87,6 +87,8 @@ public:
void attachToReactionButton(not_null<Ui::RpWidget*> button);
void setReactionIconWidget(Ui::RpWidget *widget);
void animateAndProcess(Chosen &&chosen);
using AttachStripResult = HistoryView::Reactions::AttachSelectorResult;
[[nodiscard]] AttachStripResult attachToMenu(
not_null<Ui::PopupMenu*> menu,
@ -95,8 +97,6 @@ public:
private:
class Panel;
void animateAndProcess(Chosen &&chosen);
void assignLikedId(Data::ReactionId id);
[[nodiscard]] Fn<void(Ui::ReactionFlyCenter)> setLikedIdIconInit(
not_null<Data::Session*> owner,

View file

@ -139,7 +139,7 @@ constexpr auto kLoadViewsPages = 2;
RecentViewsType RecentViewsTypeFor(not_null<PeerData*> peer) {
return peer->isSelf()
? RecentViewsType::Self
: peer->isChannel()
: peer->isBroadcast()
? RecentViewsType::Channel
: peer->isServiceUser()
? RecentViewsType::Changelog

View file

@ -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<QString> PlaceholderText(
const std::shared_ptr<ChatHelpers::Show> &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<ChatHelpers::Show> &show,
rpl::producer<bool> 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*> 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);

View file

@ -64,7 +64,7 @@ public:
void show(
ReplyAreaData data,
rpl::producer<Data::ReactionId> likedValue);
void sendReaction(const Data::ReactionId &id);
bool sendReaction(const Data::ReactionId &id);
[[nodiscard]] bool focused() const;
[[nodiscard]] rpl::producer<bool> focusedValue() const;
@ -84,7 +84,7 @@ private:
[[nodiscard]] Main::Session &session() const;
[[nodiscard]] not_null<History*> history() const;
void send(
bool send(
Api::MessageToSend message,
Api::SendOptions options,
bool skipToast = false);
@ -142,8 +142,11 @@ private:
void chooseAttach(std::optional<bool> overrideSendImagesAsPhotos);
void showPremiumToast(not_null<DocumentData*> emoji);
[[nodiscard]] bool showSlowmodeError();
const not_null<Controller*> _controller;
rpl::variable<bool> _isComment;
const std::unique_ptr<HistoryView::ComposeControls> _controls;
std::unique_ptr<Cant> _cant;