Support 18+/restrictions for messages.

This commit is contained in:
John Preston 2024-08-14 15:17:59 +02:00
parent 074dbf41e0
commit b9de12fedb
35 changed files with 711 additions and 299 deletions

View file

@ -3485,6 +3485,13 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_paid_react_show_in_top" = "Show me in Top Senders";
"lng_paid_react_anonymous" = "Anonymous";
"lng_sensitive_tag" = "18+";
"lng_sensitive_title" = "18+";
"lng_sensitive_text" = "This media may contain sensitive content suitable only for adults. Do you still want to view it?";
"lng_sensitive_always" = "Always show 18+ media";
"lng_sensitive_view" = "View Anyway";
"lng_sensitive_toast" = "You can update the visibility of sensitive media in **Settings > Chat Settings > Sensitive content**";
"lng_translate_show_original" = "Show Original";
"lng_translate_bar_to" = "Translate to {name}";
"lng_translate_bar_to_other" = "Translate to {name}";

View file

@ -14,7 +14,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
namespace Api {
namespace {
constexpr auto kRefreshAppConfigTimeout = 3 * crl::time(1000);
constexpr auto kRefreshAppConfigTimeout = crl::time(1);
} // namespace
@ -24,19 +24,40 @@ SensitiveContent::SensitiveContent(not_null<ApiWrap*> api)
, _appConfigReloadTimer([=] { _session->appConfig().refresh(); }) {
}
void SensitiveContent::reload() {
if (_requestId) {
void SensitiveContent::preload() {
if (!_loaded) {
reload();
}
}
void SensitiveContent::reload(bool force) {
if (_loadRequestId) {
if (force) {
_loadPending = true;
}
return;
}
_requestId = _api.request(MTPaccount_GetContentSettings(
_loaded = true;
_loadRequestId = _api.request(MTPaccount_GetContentSettings(
)).done([=](const MTPaccount_ContentSettings &result) {
_requestId = 0;
result.match([&](const MTPDaccount_contentSettings &data) {
_enabled = data.is_sensitive_enabled();
_canChange = data.is_sensitive_can_change();
});
_loadRequestId = 0;
const auto &data = result.data();
const auto enabled = data.is_sensitive_enabled();
const auto canChange = data.is_sensitive_can_change();
const auto changed = (_enabled.current() != enabled)
|| (_canChange.current() != canChange);
if (changed) {
_enabled = enabled;
_canChange = canChange;
}
if (base::take(_appConfigReloadForce) || changed) {
_appConfigReloadTimer.callOnce(kRefreshAppConfigTimeout);
}
if (base::take(_loadPending)) {
reload();
}
}).fail([=] {
_requestId = 0;
_loadRequestId = 0;
}).send();
}
@ -57,17 +78,24 @@ void SensitiveContent::update(bool enabled) {
return;
}
using Flag = MTPaccount_SetContentSettings::Flag;
_api.request(_requestId).cancel();
_requestId = _api.request(MTPaccount_SetContentSettings(
_api.request(_saveRequestId).cancel();
if (const auto load = base::take(_loadRequestId)) {
_api.request(load).cancel();
_loadPending = true;
}
const auto finish = [=] {
_saveRequestId = 0;
if (base::take(_loadPending)) {
_appConfigReloadForce = true;
reload(true);
} else {
_appConfigReloadTimer.callOnce(kRefreshAppConfigTimeout);
}
};
_saveRequestId = _api.request(MTPaccount_SetContentSettings(
MTP_flags(enabled ? Flag::f_sensitive_enabled : Flag(0))
)).done([=] {
_requestId = 0;
}).fail([=] {
_requestId = 0;
}).send();
)).done(finish).fail(finish).send();
_enabled = enabled;
_appConfigReloadTimer.callOnce(kRefreshAppConfigTimeout);
}
} // namespace Api

View file

@ -22,7 +22,8 @@ class SensitiveContent final {
public:
explicit SensitiveContent(not_null<ApiWrap*> api);
void reload();
void preload();
void reload(bool force = false);
void update(bool enabled);
[[nodiscard]] bool enabledCurrent() const;
@ -32,10 +33,14 @@ public:
private:
const not_null<Main::Session*> _session;
MTP::Sender _api;
mtpRequestId _requestId = 0;
mtpRequestId _loadRequestId = 0;
mtpRequestId _saveRequestId = 0;
rpl::variable<bool> _enabled = false;
rpl::variable<bool> _canChange = false;
base::Timer _appConfigReloadTimer;
bool _appConfigReloadForce = false;
bool _loadPending = false;
bool _loaded = false;
};

View file

@ -1074,7 +1074,7 @@ void Controller::fillSignaturesButton() {
rpl::single(QString()),
[] {},
st::manageGroupTopButtonWithText,
{ &st::menuIconSigned })));
{ &st::menuIconProfile })));
profiles->toggleOn(signs->toggledValue());
profiles->finishAnimating();

View file

@ -166,7 +166,7 @@ PreviewWrap::PreviewWrap(
}
}, lifetime());
session->data().itemViewRefreshRequest(
) | rpl::start_with_next([=](not_null<HistoryItem*> item) {
) | rpl::start_with_next([=](not_null<const HistoryItem*> item) {
if (item == _item) {
if (goodItem()) {
createView();

View file

@ -544,12 +544,9 @@ auto ChannelData::unavailableReasons() const
return _unavailableReasons;
}
void ChannelData::setUnavailableReasons(
void ChannelData::setUnavailableReasonsList(
std::vector<Data::UnavailableReason> &&reasons) {
if (_unavailableReasons != reasons) {
_unavailableReasons = std::move(reasons);
session().changes().peerUpdated(this, UpdateFlag::UnavailableReason);
}
_unavailableReasons = std::move(reasons);
}
void ChannelData::setAvailableMinId(MsgId availableMinId) {

View file

@ -433,9 +433,6 @@ public:
return _ptsWaiter.waitingForShortPoll();
}
void setUnavailableReasons(
std::vector<Data::UnavailableReason> &&reason);
[[nodiscard]] MsgId availableMinId() const {
return _availableMinId;
}
@ -515,6 +512,9 @@ private:
-> const std::vector<Data::UnavailableReason> & override;
bool canEditLastAdmin(not_null<UserData*> user) const;
void setUnavailableReasonsList(
std::vector<Data::UnavailableReason> &&reasons) override;
Flags _flags = ChannelDataFlags(Flag::Forbidden);
PtsWaiter _ptsWaiter;

View file

@ -143,7 +143,7 @@ void DownloadManager::trackSession(not_null<Main::Session*> session) {
}, data.lifetime);
session->data().itemViewRefreshRequest(
) | rpl::start_with_next([=](not_null<HistoryItem*> item) {
) | rpl::start_with_next([=](not_null<const HistoryItem*> item) {
changed(item);
}, data.lifetime);

View file

@ -7,6 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#include "data/data_peer.h"
#include "api/api_sensitive_content.h"
#include "data/data_user.h"
#include "data/data_chat.h"
#include "data/data_chat_participant_status.h"
@ -58,6 +59,11 @@ constexpr auto kUserpicSize = 160;
using UpdateFlag = Data::PeerUpdate::Flag;
[[nodiscard]] const std::vector<QString> &IgnoredReasons(
not_null<Main::Session*> session) {
return session->appConfig().ignoredRestrictionReasons();
}
} // namespace
namespace Data {
@ -85,10 +91,7 @@ UnavailableReason UnavailableReason::Sensitive() {
QString UnavailableReason::Compute(
not_null<Main::Session*> session,
const std::vector<UnavailableReason> &list) {
const auto &config = session->appConfig();
const auto skip = config.get<std::vector<QString>>(
"ignore_restriction_reasons",
std::vector<QString>());
const auto &skip = IgnoredReasons(session);
auto &&filtered = ranges::views::all(
list
) | ranges::views::filter([&](const Data::UnavailableReason &reason) {
@ -99,6 +102,13 @@ QString UnavailableReason::Compute(
return (first != filtered.end()) ? first->text : QString();
}
bool UnavailableReason::IgnoreSensitiveMark(
not_null<Main::Session*> session) {
return ranges::contains(
IgnoredReasons(session),
UnavailableReason::Sensitive().reason);
}
// We should get a full restriction in "{full}: {reason}" format and we
// need to find an "-all" tag in {full}, otherwise ignore this restriction.
std::vector<UnavailableReason> UnavailableReason::Extract(
@ -554,11 +564,45 @@ QString PeerData::computeUnavailableReason() const {
unavailableReasons());
}
bool PeerData::isUnavailableSensitive() const {
return ranges::contains(
unavailableReasons(),
bool PeerData::hasSensitiveContent() const {
return _sensitiveContent == 1;
}
void PeerData::setUnavailableReasonsList(
std::vector<Data::UnavailableReason> &&reasons) {
Unexpected("PeerData::setUnavailableReasonsList.");
}
void PeerData::setUnavailableReasons(
std::vector<Data::UnavailableReason> &&reasons) {
const auto i = ranges::find(
reasons,
true,
&Data::UnavailableReason::sensitive);
const auto sensitive = (i != end(reasons));
if (sensitive) {
reasons.erase(i);
}
auto changed = (sensitive != hasSensitiveContent());
if (changed) {
setHasSensitiveContent(sensitive);
}
if (reasons != unavailableReasons()) {
setUnavailableReasonsList(std::move(reasons));
changed = true;
}
if (changed) {
session().changes().peerUpdated(
this,
UpdateFlag::UnavailableReason);
}
}
void PeerData::setHasSensitiveContent(bool has) {
_sensitiveContent = has ? 1 : 0;
if (has) {
session().api().sensitiveContent().preload();
}
}
// This is duplicated in CanPinMessagesValue().

View file

@ -99,6 +99,8 @@ struct UnavailableReason {
[[nodiscard]] static QString Compute(
not_null<Main::Session*> session,
const std::vector<UnavailableReason> &list);
[[nodiscard]] static bool IgnoreSensitiveMark(
not_null<Main::Session*> session);
[[nodiscard]] static std::vector<UnavailableReason> Extract(
const MTPvector<MTPRestrictionReason> *list);
@ -347,7 +349,9 @@ public:
// If this string is not empty we must not allow to open the
// conversation and we must show this string instead.
[[nodiscard]] QString computeUnavailableReason() const;
[[nodiscard]] bool isUnavailableSensitive() const;
[[nodiscard]] bool hasSensitiveContent() const;
void setUnavailableReasons(
std::vector<Data::UnavailableReason> &&reason);
[[nodiscard]] ClickHandlerPtr createOpenLink();
[[nodiscard]] const ClickHandlerPtr &openLink() {
@ -489,6 +493,10 @@ private:
const ImageLocation &location,
bool hasVideo);
virtual void setUnavailableReasonsList(
std::vector<Data::UnavailableReason> &&reasons);
void setHasSensitiveContent(bool has);
const not_null<Data::Session*> _owner;
mutable Data::CloudImage _userpic;
@ -507,7 +515,8 @@ private:
crl::time _lastFullUpdate = 0;
QString _name;
uint32 _nameVersion : 31 = 1;
uint32 _nameVersion : 30 = 1;
uint32 _sensitiveContent : 1 = 0;
uint32 _wallPaperOverriden : 1 = 0;
TimeId _ttlPeriod = 0;

View file

@ -252,8 +252,7 @@ Image *PhotoData::getReplyPreview(
Image *PhotoData::getReplyPreview(not_null<HistoryItem*> item) {
const auto media = item->media();
const auto spoiler = (media && media->hasSpoiler())
|| item->hasSensitiveSpoiler();
const auto spoiler = (media && media->hasSpoiler());
return getReplyPreview(item->fullId(), item->history()->peer, spoiler);
}

View file

@ -311,6 +311,22 @@ Session::Session(not_null<Main::Session*> session)
_stories->loadMore(Data::StorySourcesList::NotHidden);
});
session->appConfig().ignoredRestrictionReasonsChanges(
) | rpl::start_with_next([=](std::vector<QString> &&changed) {
auto refresh = std::vector<not_null<const HistoryItem*>>();
for (const auto &[item, reasons] : _possiblyRestricted) {
for (const auto &reason : changed) {
if (reasons.contains(reason)) {
refresh.push_back(item);
break;
}
}
}
for (const auto &item : refresh) {
requestItemViewRefresh(item);
}
}, _lifetime);
}
void Session::subscribeForTopicRepliesLists() {
@ -1773,7 +1789,7 @@ rpl::producer<not_null<ViewElement*>> Session::viewResizeRequest() const {
return _viewResizeRequest.events();
}
void Session::requestItemViewRefresh(not_null<HistoryItem*> item) {
void Session::requestItemViewRefresh(not_null<const HistoryItem*> item) {
if (const auto view = item->mainView()) {
notifyHistoryChangeDelayed(item->history());
view->refreshInBlock();
@ -1781,7 +1797,7 @@ void Session::requestItemViewRefresh(not_null<HistoryItem*> item) {
_itemViewRefreshRequest.fire_copy(item);
}
rpl::producer<not_null<HistoryItem*>> Session::itemViewRefreshRequest() const {
rpl::producer<not_null<const HistoryItem*>> Session::itemViewRefreshRequest() const {
return _itemViewRefreshRequest.events();
}
@ -1807,6 +1823,31 @@ void Session::requestItemTextRefresh(not_null<HistoryItem*> item) {
}
}
void Session::registerRestricted(
not_null<const HistoryItem*> item,
const QString &reason) {
Expects(item->hasPossibleRestrictions());
_possiblyRestricted[item].emplace(reason);
}
void Session::registerRestricted(
not_null<const HistoryItem*> item,
const std::vector<UnavailableReason> &reasons) {
Expects(item->hasPossibleRestrictions());
auto &list = _possiblyRestricted[item];
if (list.empty()) {
auto &&simple = reasons
| ranges::views::transform(&UnavailableReason::reason);
list = { begin(simple), end(simple) };
} else {
for (const auto &reason : reasons) {
list.emplace(reason.reason);
}
}
}
void Session::registerHighlightProcess(
uint64 processId,
not_null<HistoryItem*> item) {
@ -2510,6 +2551,9 @@ void Session::unregisterMessage(not_null<HistoryItem*> item) {
const auto peerId = item->history()->peer->id;
const auto itemId = item->id;
_itemRemoved.fire_copy(item);
if (item->hasPossibleRestrictions()) {
_possiblyRestricted.remove(item);
}
session().changes().messageUpdated(
item,
Data::MessageUpdate::Flag::Destroyed);

View file

@ -69,6 +69,7 @@ class SavedMessages;
class Chatbots;
class BusinessInfo;
struct ReactionId;
struct UnavailableReason;
struct RepliesReadTillUpdate {
FullMsgId id;
@ -288,8 +289,8 @@ public:
[[nodiscard]] rpl::producer<not_null<const HistoryItem*>> itemResizeRequest() const;
void requestViewResize(not_null<ViewElement*> view);
[[nodiscard]] rpl::producer<not_null<ViewElement*>> viewResizeRequest() const;
void requestItemViewRefresh(not_null<HistoryItem*> item);
[[nodiscard]] rpl::producer<not_null<HistoryItem*>> itemViewRefreshRequest() const;
void requestItemViewRefresh(not_null<const HistoryItem*> item);
[[nodiscard]] rpl::producer<not_null<const HistoryItem*>> itemViewRefreshRequest() const;
void requestItemTextRefresh(not_null<HistoryItem*> item);
void requestUnreadReactionsAnimation(not_null<HistoryItem*> item);
void notifyHistoryUnloaded(not_null<const History*> history);
@ -313,6 +314,13 @@ public:
void notifyPinnedDialogsOrderUpdated();
[[nodiscard]] rpl::producer<> pinnedDialogsOrderUpdated() const;
void registerRestricted(
not_null<const HistoryItem*> item,
const QString &reason);
void registerRestricted(
not_null<const HistoryItem*> item,
const std::vector<UnavailableReason> &reasons);
void registerHighlightProcess(
uint64 processId,
not_null<HistoryItem*> item);
@ -920,7 +928,7 @@ private:
rpl::event_stream<not_null<const ViewElement*>> _viewRepaintRequest;
rpl::event_stream<not_null<const HistoryItem*>> _itemResizeRequest;
rpl::event_stream<not_null<ViewElement*>> _viewResizeRequest;
rpl::event_stream<not_null<HistoryItem*>> _itemViewRefreshRequest;
rpl::event_stream<not_null<const HistoryItem*>> _itemViewRefreshRequest;
rpl::event_stream<not_null<HistoryItem*>> _itemTextRefreshRequest;
rpl::event_stream<not_null<HistoryItem*>> _itemDataChanges;
rpl::event_stream<not_null<const HistoryItem*>> _itemRemoved;
@ -1021,6 +1029,10 @@ private:
base::flat_multi_map<TimeId, not_null<PollData*>> _pollsClosings;
base::Timer _pollsClosingTimer;
base::flat_map<
not_null<const HistoryItem*>,
base::flat_set<QString>> _possiblyRestricted;
base::flat_map<FolderId, std::unique_ptr<Folder>> _folders;
std::unordered_map<

View file

@ -326,7 +326,7 @@ enum class MessageFlag : uint64 {
EffectWatched = (1ULL << 46),
SensitiveContent = (1ULL << 47),
AllowSensitive = (1ULL << 48),
HasRestrictions = (1ULL << 48),
};
inline constexpr bool is_flag_type(MessageFlag) { return true; }
using MessageFlags = base::flags<MessageFlag>;

View file

@ -7,6 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#include "data/data_user.h"
#include "api/api_sensitive_content.h"
#include "storage/localstorage.h"
#include "storage/storage_user_photos.h"
#include "main/main_session.h"
@ -117,14 +118,9 @@ auto UserData::unavailableReasons() const
return _unavailableReasons;
}
void UserData::setUnavailableReasons(
void UserData::setUnavailableReasonsList(
std::vector<Data::UnavailableReason> &&reasons) {
if (_unavailableReasons != reasons) {
_unavailableReasons = std::move(reasons);
session().changes().peerUpdated(
this,
UpdateFlag::UnavailableReason);
}
_unavailableReasons = std::move(reasons);
}
void UserData::setCommonChatsCount(int count) {
@ -516,6 +512,10 @@ void UserData::setBirthday(Data::Birthday value) {
if (_birthday != value) {
_birthday = value;
session().changes().peerUpdated(this, UpdateFlag::Birthday);
if (isSelf()) {
session().api().sensitiveContent().reload(true);
}
}
}

View file

@ -185,9 +185,6 @@ public:
void setBirthday(Data::Birthday value);
void setBirthday(const tl::conditional<MTPBirthday> &value);
void setUnavailableReasons(
std::vector<Data::UnavailableReason> &&reasons);
int commonChatsCount() const;
void setCommonChatsCount(int count);
@ -218,6 +215,9 @@ private:
auto unavailableReasons() const
-> const std::vector<Data::UnavailableReason> & override;
void setUnavailableReasonsList(
std::vector<Data::UnavailableReason> &&reasons) override;
Flags _flags;
Data::LastseenStatus _lastseen;
Data::Birthday _birthday;

View file

@ -7,6 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#include "history/history_item.h"
#include "api/api_sensitive_content.h"
#include "lang/lang_keys.h"
#include "mainwidget.h"
#include "calls/calls_instance.h" // Core::App().calls().joinGroupCall.
@ -3356,6 +3357,8 @@ EffectId HistoryItem::effectId() const {
QString HistoryItem::computeUnavailableReason() const {
if (const auto restrictions = Get<HistoryMessageRestrictions>()) {
_flags |= MessageFlag::HasRestrictions;
_history->owner().registerRestricted(this, restrictions->reasons);
return Data::UnavailableReason::Compute(
&history()->session(),
restrictions->reasons);
@ -3363,13 +3366,19 @@ QString HistoryItem::computeUnavailableReason() const {
return QString();
}
bool HistoryItem::hasSensitiveSpoiler() const {
return (_flags & MessageFlag::SensitiveContent)
&& !(_flags & MessageFlag::AllowSensitive);
bool HistoryItem::isMediaSensitive() const {
if (!(_flags & MessageFlag::SensitiveContent)
&& !_history->peer->hasSensitiveContent()) {
return false;
}
_flags |= MessageFlag::HasRestrictions;
_history->owner().registerRestricted(this, u"sensitive"_q);
return !Data::UnavailableReason::IgnoreSensitiveMark(
&_history->session());
}
void HistoryItem::allowSensitive() {
_flags |= MessageFlag::AllowSensitive;
bool HistoryItem::hasPossibleRestrictions() const {
return _flags & MessageFlag::HasRestrictions;
}
bool HistoryItem::isEmpty() const {
@ -3666,10 +3675,10 @@ void HistoryItem::createComponents(CreateConfig &&config) {
&Data::UnavailableReason::sensitive);
if (i != end(restrictions->reasons)) {
restrictions->reasons.erase(i);
_flags |= MessageFlag::SensitiveContent;
flagSensitiveContent();
}
} else if (!config.restrictions.empty()) {
_flags |= MessageFlag::SensitiveContent;
flagSensitiveContent();
}
if (out() && isSending()) {
@ -3679,6 +3688,11 @@ void HistoryItem::createComponents(CreateConfig &&config) {
}
}
void HistoryItem::flagSensitiveContent() {
_flags |= MessageFlag::SensitiveContent;
_history->session().api().sensitiveContent().preload();
}
bool HistoryItem::checkRepliesPts(
const HistoryMessageRepliesData &data) const {
const auto channel = _history->peer->asChannel();

View file

@ -522,9 +522,9 @@ public:
[[nodiscard]] bool isEmpty() const;
[[nodiscard]] MessageGroupId groupId() const;
[[nodiscard]] EffectId effectId() const;
[[nodiscard]] bool hasPossibleRestrictions() const;
[[nodiscard]] QString computeUnavailableReason() const;
[[nodiscard]] bool hasSensitiveSpoiler() const;
void allowSensitive();
[[nodiscard]] bool isMediaSensitive() const;
[[nodiscard]] const HistoryMessageReplyMarkup *inlineReplyMarkup() const {
return const_cast<HistoryItem*>(this)->inlineReplyMarkup();
@ -660,6 +660,7 @@ private:
[[nodiscard]] PreparedServiceText prepareCallScheduledText(
TimeId scheduleDate);
void flagSensitiveContent();
[[nodiscard]] PeerData *computeDisplayFrom() const;
const not_null<History*> _history;

View file

@ -736,6 +736,10 @@ void Element::refreshMedia(Element *replacing) {
_flags &= ~Flag::HiddenByGroup;
const auto item = data();
if (!item->computeUnavailableReason().isEmpty()) {
_media = nullptr;
return;
}
if (const auto media = item->media()) {
if (media->canBeGrouped()) {
if (const auto group = history()->owner().groups().find(item)) {
@ -1004,7 +1008,12 @@ void Element::validateText() {
: contextDependentText.links;
setTextWithLinks(markedText, customLinks);
} else {
setTextWithLinks(_textItem->translatedTextWithLocalEntities());
const auto unavailable = item->computeUnavailableReason();
if (!unavailable.isEmpty()) {
setTextWithLinks(Ui::Text::Italic(unavailable));
} else {
setTextWithLinks(_textItem->translatedTextWithLocalEntities());
}
}
}

View file

@ -135,10 +135,13 @@ Gif::Gif(
, _storyId(realParent->media()
? realParent->media()->storyId()
: FullStoryId())
, _spoiler((spoiler || IsHiddenRoundMessage(_parent))
, _spoiler((spoiler
|| IsHiddenRoundMessage(_parent)
|| realParent->isMediaSensitive())
? std::make_unique<MediaSpoiler>()
: nullptr)
, _downloadSize(Ui::FormatSizeText(_data->size)) {
, _downloadSize(Ui::FormatSizeText(_data->size))
, _sensitiveSpoiler(realParent->isMediaSensitive()) {
if (_data->isVideoMessage() && _parent->data()->media()->ttlSeconds()) {
if (_spoiler) {
_drawTtl = CreateTtlPaintCallback([=] { repaint(); });
@ -582,9 +585,11 @@ void Gif::draw(Painter &p, const PaintContext &context) const {
}
}
if (radial
|| (!streamingMode
&& ((!loaded && !_data->loading()) || !autoplay))) {
const auto paintInCenter = !_sensitiveSpoiler
&& (radial
|| (!streamingMode
&& ((!loaded && !_data->loading()) || !autoplay)));
if (paintInCenter) {
const auto radialRevealed = 1.;
const auto opacity = (item->isSending() || _data->uploading())
? 1.
@ -652,6 +657,10 @@ void Gif::draw(Painter &p, const PaintContext &context) const {
}
}
p.setOpacity(1.);
} else if (_sensitiveSpoiler) {
drawSpoilerTag(p, rthumb, context, [&] {
return spoilerTagBackground();
});
}
if (displayMute) {
auto muteRect = style::rtlrect(rthumb.x() + (rthumb.width() - st::historyVideoMessageMuteSize) / 2, rthumb.y() + st::msgDateImgDelta, st::historyVideoMessageMuteSize, st::historyVideoMessageMuteSize, width());
@ -835,6 +844,28 @@ void Gif::paintTranscribe(
context);
}
void Gif::drawSpoilerTag(
Painter &p,
QRect rthumb,
const PaintContext &context,
Fn<QImage()> generateBackground) const {
Media::drawSpoilerTag(
p,
_spoiler.get(),
_spoilerTag,
rthumb,
context,
std::move(generateBackground));
}
ClickHandlerPtr Gif::spoilerTagLink() const {
return Media::spoilerTagLink(_spoiler.get(), _spoilerTag);
}
QImage Gif::spoilerTagBackground() const {
return _spoiler ? _spoiler->background : QImage();
}
void Gif::validateVideoThumbnail() const {
const auto content = _dataMedia->videoThumbnailContent();
if (_videoThumbnailFrame || content.isEmpty()) {
@ -1113,10 +1144,12 @@ TextState Gif::textState(QPoint point, StateRequest request) const {
}
if (QRect(usex + paintx, painty, usew, painth).contains(point)) {
ensureDataMediaCreated();
result.link = (isRound && _parent->data()->media()->ttlSeconds())
? _openl // Overriden.
: (_spoiler && !_spoiler->revealed)
? _spoiler->link
result.link = (_spoiler && !_spoiler->revealed)
? (_sensitiveSpoiler
? spoilerTagLink()
: (isRound && _parent->data()->media()->ttlSeconds())
? _openl // Overriden.
: _spoiler->link)
: _data->uploading()
? _cancell
: _realParent->isSending()
@ -1335,9 +1368,11 @@ void Gif::drawGrouped(
p.setOpacity(1.);
}
if (radial
|| (!streamingMode
&& ((!loaded && !_data->loading()) || !autoplay))) {
const auto paintInCenter = !_sensitiveSpoiler
&& (radial
|| (!streamingMode
&& ((!loaded && !_data->loading()) || !autoplay)));
if (paintInCenter) {
const auto radialRevealed = 1.;
const auto opacity = (item->isSending() || _data->uploading())
? 1.
@ -1439,8 +1474,8 @@ TextState Gif::getStateGrouped(
}
}
ensureDataMediaCreated();
return TextState(_parent, (_spoiler && !_spoiler->revealed)
? _spoiler->link
auto link = (_spoiler && !_spoiler->revealed)
? (_sensitiveSpoiler ? spoilerTagLink() : _spoiler->link)
: _data->uploading()
? _cancell
: _realParent->isSending()
@ -1449,7 +1484,8 @@ TextState Gif::getStateGrouped(
? _openl
: _data->loading()
? _cancell
: _savel);
: _savel;
return TextState(_parent, std::move(link));
}
void Gif::ensureDataMediaCreated() const {

View file

@ -90,6 +90,14 @@ public:
void stopAnimation() override;
void checkAnimation() override;
void drawSpoilerTag(
Painter &p,
QRect rthumb,
const PaintContext &context,
Fn<QImage()> generateBackground) const override;
ClickHandlerPtr spoilerTagLink() const override;
QImage spoilerTagBackground() const override;
void hideSpoilers() override;
bool needsBubble() const override;
bool unwrapped() const override;
@ -203,6 +211,7 @@ private:
const FullStoryId _storyId;
std::unique_ptr<Streamed> _streamed;
const std::unique_ptr<MediaSpoiler> _spoiler;
mutable std::unique_ptr<MediaSpoilerTag> _spoilerTag;
mutable std::unique_ptr<TranscribeButton> _transcribe;
mutable std::shared_ptr<Data::DocumentMedia> _dataMedia;
mutable std::unique_ptr<Image> _videoThumbnailFrame;
@ -214,6 +223,7 @@ private:
mutable bool _thumbIsEllipse : 1 = false;
mutable bool _pollingStory : 1 = false;
mutable bool _purchasedPriceTag : 1 = false;
const bool _sensitiveSpoiler : 1 = false;
};

View file

@ -13,13 +13,14 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "history/view/history_view_element.h"
#include "history/view/history_view_cursor_state.h"
#include "history/view/history_view_text_helper.h"
#include "history/view/media/history_view_sticker.h"
#include "history/view/media/history_view_media_common.h"
#include "history/view/media/history_view_media_spoiler.h"
#include "history/view/media/history_view_sticker.h"
#include "storage/storage_shared_media.h"
#include "data/data_document.h"
#include "data/data_session.h"
#include "data/data_web_page.h"
#include "lang/lang_tag.h" // FormatCountDecimal.
#include "lang/lang_keys.h"
#include "ui/item_text_options.h"
#include "ui/chat/chat_style.h"
#include "ui/chat/message_bubble.h"
@ -31,6 +32,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/text/text_utilities.h"
#include "core/ui_integration.h"
#include "styles/style_chat.h"
#include "styles/style_chat_helpers.h"
#include "styles/style_menu_icons.h" // mediaMenuIconStealth.
namespace HistoryView {
namespace {
@ -351,6 +354,156 @@ void Media::fillImageSpoiler(
spoiler->cornerCache);
}
void Media::drawSpoilerTag(
Painter &p,
not_null<MediaSpoiler*> spoiler,
std::unique_ptr<MediaSpoilerTag> &tag,
QRect rthumb,
const PaintContext &context,
Fn<QImage()> generateBackground) const {
if (!tag) {
setupSpoilerTag(tag);
if (!tag) {
return;
}
}
const auto revealed = spoiler->revealAnimation.value(
spoiler->revealed ? 1. : 0.);
if (revealed == 1.) {
return;
}
p.setOpacity(1. - revealed);
const auto st = context.st;
const auto darken = st->msgDateImgBg()->c;
const auto fg = st->msgDateImgFg()->c;
const auto star = st->creditsBg1()->c;
if (tag->cache.isNull()
|| tag->darken != darken
|| tag->fg != fg
|| tag->star != star) {
const auto ratio = style::DevicePixelRatio();
auto bg = generateBackground();
if (bg.isNull()) {
bg = QImage(ratio, ratio, QImage::Format_ARGB32_Premultiplied);
bg.fill(Qt::black);
}
auto text = Ui::Text::String();
auto iconSkip = 0;
if (tag->sensitive) {
text.setText(
st::semiboldTextStyle,
tr::lng_sensitive_tag(tr::now));
iconSkip = st::mediaMenuIconStealth.width() * 1.4;
} else {
const auto session = &history()->session();
auto price = Ui::Text::Colorized(Ui::CreditsEmoji(session));
price.append(Lang::FormatCountDecimal(tag->price));
text.setMarkedText(
st::semiboldTextStyle,
tr::lng_paid_price(
tr::now,
lt_price,
price,
Ui::Text::WithEntities),
kMarkupTextOptions,
Core::MarkedTextContext{
.session = session,
.customEmojiRepaint = [] {},
});
}
const auto width = iconSkip + text.maxWidth();
const auto inner = QRect(0, 0, width, text.minHeight());
const auto outer = inner.marginsAdded(st::paidTagPadding);
const auto size = outer.size();
const auto radius = std::min(size.width(), size.height()) / 2;
auto cache = QImage(
size * ratio,
QImage::Format_ARGB32_Premultiplied);
cache.setDevicePixelRatio(ratio);
cache.fill(Qt::black);
auto p = Painter(&cache);
auto hq = PainterHighQualityEnabler(p);
p.drawImage(
QRect(
(size.width() - rthumb.width()) / 2,
(size.height() - rthumb.height()) / 2,
rthumb.width(),
rthumb.height()),
bg);
p.fillRect(QRect(QPoint(), size), darken);
p.setPen(fg);
p.setTextPalette(st->priceTagTextPalette());
if (iconSkip) {
st::mediaMenuIconStealth.paint(
p,
-outer.x(),
(size.height() - st::mediaMenuIconStealth.height()) / 2,
size.width(),
fg);
}
text.draw(p, iconSkip - outer.x(), -outer.y(), width);
p.end();
tag->darken = darken;
tag->fg = fg;
tag->cache = Images::Round(
std::move(cache),
Images::CornersMask(radius));
}
const auto &cache = tag->cache;
const auto size = cache.size() / cache.devicePixelRatio();
const auto left = rthumb.x() + (rthumb.width() - size.width()) / 2;
const auto top = rthumb.y() + (rthumb.height() - size.height()) / 2;
p.drawImage(left, top, cache);
if (context.selected()) {
auto hq = PainterHighQualityEnabler(p);
const auto radius = std::min(size.width(), size.height()) / 2;
p.setPen(Qt::NoPen);
p.setBrush(st->msgSelectOverlay());
p.drawRoundedRect(
QRect(left, top, size.width(), size.height()),
radius,
radius);
}
p.setOpacity(1.);
}
void Media::setupSpoilerTag(std::unique_ptr<MediaSpoilerTag> &tag) const {
const auto item = parent()->data();
if (item->isMediaSensitive()) {
tag = std::make_unique<MediaSpoilerTag>();
tag->sensitive = 1;
return;
}
const auto media = parent()->data()->media();
const auto invoice = media ? media->invoice() : nullptr;
if (const auto price = invoice->isPaidMedia ? invoice->amount : 0) {
tag = std::make_unique<MediaSpoilerTag>();
tag->price = price;
}
}
ClickHandlerPtr Media::spoilerTagLink(
not_null<MediaSpoiler*> spoiler,
std::unique_ptr<MediaSpoilerTag> &tag) const {
const auto item = parent()->data();
if (!item->isRegular() || spoiler->revealed) {
return nullptr;
} else if (!tag) {
setupSpoilerTag(tag);
if (!tag) {
return nullptr;
}
}
if (!tag->link) {
tag->link = tag->sensitive
? MakeSensitiveMediaLink(spoiler->link, item)
: MakePaidMediaLink(item);
}
return tag->link;
}
void Media::createSpoilerLink(not_null<MediaSpoiler*> spoiler) {
const auto weak = base::make_weak(this);
spoiler->link = std::make_shared<LambdaClickHandler>([weak, spoiler](

View file

@ -47,6 +47,7 @@ enum class InfoDisplayType : char;
struct TextState;
struct StateRequest;
struct MediaSpoiler;
struct MediaSpoilerTag;
class StickerPlayer;
class Element;
struct SelectedQuote;
@ -223,18 +224,18 @@ public:
QPoint point,
StateRequest request) const;
virtual void drawPriceTag(
virtual void drawSpoilerTag(
Painter &p,
QRect rthumb,
const PaintContext &context,
Fn<QImage()> generateBackground) const {
Unexpected("Price tag method call.");
Unexpected("Spoiler tag method call.");
}
[[nodiscard]] virtual ClickHandlerPtr priceTagLink() const {
Unexpected("Price tag method call.");
[[nodiscard]] virtual ClickHandlerPtr spoilerTagLink() const {
Unexpected("Spoiler tag method call.");
}
[[nodiscard]] virtual QImage priceTagBackground() const {
Unexpected("Price tag method call.");
[[nodiscard]] virtual QImage spoilerTagBackground() const {
Unexpected("Spoiler tag method call.");
}
[[nodiscard]] virtual bool animating() const {
@ -390,6 +391,17 @@ protected:
not_null<MediaSpoiler*> spoiler,
QRect rect,
const PaintContext &context) const;
void drawSpoilerTag(
Painter &p,
not_null<MediaSpoiler*> spoiler,
std::unique_ptr<MediaSpoilerTag> &tag,
QRect rthumb,
const PaintContext &context,
Fn<QImage()> generateBackground) const;
void setupSpoilerTag(std::unique_ptr<MediaSpoilerTag> &tag) const;
[[nodiscard]] ClickHandlerPtr spoilerTagLink(
not_null<MediaSpoiler*> spoiler,
std::unique_ptr<MediaSpoilerTag> &tag) const;
void createSpoilerLink(not_null<MediaSpoiler*> spoiler);
void repaint() const;

View file

@ -7,10 +7,16 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#include "history/view/media/history_view_media_common.h"
#include "api/api_sensitive_content.h"
#include "api/api_views.h"
#include "apiwrap.h"
#include "ui/boxes/confirm_box.h"
#include "ui/layers/generic_box.h"
#include "ui/text/format_values.h"
#include "ui/text/text_utilities.h"
#include "ui/toast/toast.h"
#include "ui/widgets/checkbox.h"
#include "ui/wrap/slide_wrap.h"
#include "ui/painter.h"
#include "core/click_handler_types.h"
#include "data/data_document.h"
@ -272,4 +278,62 @@ ClickHandlerPtr MakePaidMediaLink(not_null<HistoryItem*> item) {
});
}
ClickHandlerPtr MakeSensitiveMediaLink(
ClickHandlerPtr reveal,
not_null<HistoryItem*> item) {
const auto session = &item->history()->session();
return std::make_shared<LambdaClickHandler>([=](ClickContext context) {
const auto my = context.other.value<ClickHandlerContext>();
const auto controller = my.sessionWindow.get();
const auto show = controller ? controller->uiShow() : my.show;
if (!show) {
reveal->onClick(context);
return;
}
show->show(Box([=](not_null<Ui::GenericBox*> box) {
struct State {
rpl::variable<bool> canChange;
Ui::Checkbox *checkbox = nullptr;
};
const auto state = box->lifetime().make_state<State>();
const auto sensitive = &session->api().sensitiveContent();
state->canChange = sensitive->canChange();
const auto done = [=](Fn<void()> close) {
if (state->canChange.current()
&& state->checkbox->checked()) {
show->showToast({
.text = tr::lng_sensitive_toast(
tr::now,
Ui::Text::RichLangValue),
.adaptive = true,
.duration = 5 * crl::time(1000),
});
sensitive->update(true);
} else {
reveal->onClick(context);
}
close();
};
Ui::ConfirmBox(box, {
.text = tr::lng_sensitive_text(Ui::Text::RichLangValue),
.confirmed = done,
.confirmText = tr::lng_sensitive_view(),
.title = tr::lng_sensitive_title(),
});
const auto skip = st::defaultCheckbox.margin.bottom();
const auto wrap = box->addRow(
object_ptr<Ui::SlideWrap<Ui::Checkbox>>(
box,
object_ptr<Ui::Checkbox>(
box,
tr::lng_sensitive_always(tr::now),
false)),
st::boxRowPadding + QMargins(0, 0, 0, skip));
wrap->toggleOn(state->canChange.value());
wrap->finishAnimating();
state->checkbox = wrap->entity();
}));
});
}
} // namespace HistoryView

View file

@ -75,6 +75,10 @@ void PaintInterpolatedIcon(
int newWidth,
int maxWidth);
[[nodiscard]] ClickHandlerPtr MakePaidMediaLink(not_null<HistoryItem*> item);
[[nodiscard]] ClickHandlerPtr MakePaidMediaLink(
not_null<HistoryItem*> item);
[[nodiscard]] ClickHandlerPtr MakeSensitiveMediaLink(
ClickHandlerPtr reveal,
not_null<HistoryItem*> item);
} // namespace HistoryView

View file

@ -297,16 +297,19 @@ QMargins GroupedMedia::groupedPadding() const {
(normal.bottom() - grouped.bottom()) + addToBottom);
}
Media *GroupedMedia::lookupUnpaidMedia() const {
Media *GroupedMedia::lookupSpoilerTagMedia() const {
if (_parts.empty()) {
return nullptr;
}
const auto media = _parts.front().content.get();
if (media && _parts.front().item->isMediaSensitive()) {
return media;
}
const auto photo = media ? media->getPhoto() : nullptr;
return (photo && photo->extendedMediaPreview()) ? media : nullptr;
}
QImage GroupedMedia::generatePriceTagBackground(QRect full) const {
QImage GroupedMedia::generateSpoilerTagBackground(QRect full) const {
const auto ratio = style::DevicePixelRatio();
auto result = QImage(
full.size() * ratio,
@ -317,7 +320,7 @@ QImage GroupedMedia::generatePriceTagBackground(QRect full) const {
const auto skip1 = st::historyGroupSkip / 2;
const auto skip2 = st::historyGroupSkip - skip1;
for (const auto &part : _parts) {
auto background = part.content->priceTagBackground();
auto background = part.content->spoilerTagBackground();
const auto extended = part.geometry.translated(shift).marginsAdded(
{ skip1, skip1, skip2, skip2 });
if (background.isNull()) {
@ -394,7 +397,7 @@ void GroupedMedia::draw(Painter &p, const PaintContext &context) const {
? Ui::BubbleRounding{ kSmall, kSmall, kSmall, kSmall }
: adjustedBubbleRounding();
auto highlight = context.highlight.range;
const auto unpaid = lookupUnpaidMedia();
const auto tagged = lookupSpoilerTagMedia();
auto fullRect = QRect();
const auto subpartHighlight = IsSubGroupSelection(highlight);
for (auto i = 0, count = int(_parts.size()); i != count; ++i) {
@ -435,7 +438,7 @@ void GroupedMedia::draw(Painter &p, const PaintContext &context) const {
if (!part.cache.isNull()) {
nowCache = true;
}
if (unpaid || _purchasedPriceTag) {
if (tagged || _purchasedPriceTag) {
fullRect = fullRect.united(part.geometry);
}
}
@ -443,9 +446,9 @@ void GroupedMedia::draw(Painter &p, const PaintContext &context) const {
history()->owner().registerHeavyViewPart(_parent);
}
if (unpaid) {
unpaid->drawPriceTag(p, fullRect, context, [&] {
return generatePriceTagBackground(fullRect);
if (tagged) {
tagged->drawSpoilerTag(p, fullRect, context, [&] {
return generateSpoilerTagBackground(fullRect);
});
} else if (_purchasedPriceTag) {
drawPurchasedTag(p, fullRect, context);
@ -511,9 +514,11 @@ PointState GroupedMedia::pointState(QPoint point) const {
TextState GroupedMedia::textState(QPoint point, StateRequest request) const {
const auto groupPadding = groupedPadding();
auto result = getPartState(point - QPoint(0, groupPadding.top()), request);
if (const auto unpaid = lookupUnpaidMedia()) {
if (const auto tagged = lookupSpoilerTagMedia()) {
if (QRect(0, 0, width(), height()).contains(point)) {
result.link = unpaid->priceTagLink();
if (auto link = tagged->spoilerTagLink()) {
result.link = std::move(link);
}
}
}
if (_parent->media() == this && (!_parent->hasBubble() || isBubbleBottom())) {

View file

@ -149,8 +149,8 @@ private:
RectParts sides) const;
[[nodiscard]] QMargins groupedPadding() const;
[[nodiscard]] Media *lookupUnpaidMedia() const;
[[nodiscard]] QImage generatePriceTagBackground(QRect full) const;
[[nodiscard]] Media *lookupSpoilerTagMedia() const;
[[nodiscard]] QImage generateSpoilerTagBackground(QRect full) const;
mutable std::optional<HistoryItem*> _captionItem;
std::vector<Part> _parts;

View file

@ -26,4 +26,14 @@ struct MediaSpoiler {
bool revealed = false;
};
struct MediaSpoilerTag {
uint64 price : 63 = 0;
uint64 sensitive : 1 = 0;
QImage cache;
QColor darken;
QColor fg;
QColor star;
ClickHandlerPtr link;
};
} // namespace HistoryView

View file

@ -63,15 +63,6 @@ struct Photo::Streamed {
QImage roundingMask;
};
struct Photo::PriceTag {
uint64 price = 0;
QImage cache;
QColor darken;
QColor fg;
QColor star;
ClickHandlerPtr link;
};
Photo::Streamed::Streamed(
std::shared_ptr<::Media::Streaming::Document> shared)
: instance(std::move(shared), nullptr) {
@ -87,7 +78,10 @@ Photo::Photo(
, _storyId(realParent->media()
? realParent->media()->storyId()
: FullStoryId())
, _spoiler(spoiler ? std::make_unique<MediaSpoiler>() : nullptr) {
, _spoiler((spoiler || realParent->isMediaSensitive())
? std::make_unique<MediaSpoiler>()
: nullptr)
, _sensitiveSpoiler(realParent->isMediaSensitive() ? 1 : 0) {
create(realParent->fullId());
}
@ -342,7 +336,8 @@ void Photo::draw(Painter &p, const PaintContext &context) const {
}
const auto showEnlarge = loaded && _showEnlarge;
const auto paintInCenter = (radial || (!loaded && !_data->loading()));
const auto paintInCenter = !_sensitiveSpoiler
&& (radial || (!loaded && !_data->loading()));
if (paintInCenter || showEnlarge) {
p.setPen(Qt::NoPen);
if (context.selected()) {
@ -382,9 +377,9 @@ void Photo::draw(Painter &p, const PaintContext &context) const {
QRect rinner(inner.marginsRemoved(QMargins(st::msgFileRadialLine, st::msgFileRadialLine, st::msgFileRadialLine, st::msgFileRadialLine)));
_animation->radial.draw(p, rinner, st::msgFileRadialLine, sti->historyFileThumbRadialFg);
}
} else if (preview) {
drawPriceTag(p, rthumb, context, [&] {
return priceTagBackground();
} else if (_sensitiveSpoiler || preview) {
drawSpoilerTag(p, rthumb, context, [&] {
return spoilerTagBackground();
});
}
if (showEnlarge) {
@ -426,105 +421,18 @@ void Photo::draw(Painter &p, const PaintContext &context) const {
}
}
void Photo::setupPriceTag() const {
const auto media = parent()->data()->media();
const auto invoice = media ? media->invoice() : nullptr;
const auto price = invoice->isPaidMedia ? invoice->amount : 0;
if (!price) {
return;
}
_priceTag = std::make_unique<PriceTag>();
_priceTag->price = price;
}
void Photo::drawPriceTag(
void Photo::drawSpoilerTag(
Painter &p,
QRect rthumb,
const PaintContext &context,
Fn<QImage()> generateBackground) const {
if (!_priceTag) {
setupPriceTag();
if (!_priceTag) {
return;
}
}
const auto st = context.st;
const auto darken = st->msgDateImgBg()->c;
const auto fg = st->msgDateImgFg()->c;
const auto star = st->creditsBg1()->c;
if (_priceTag->cache.isNull()
|| _priceTag->darken != darken
|| _priceTag->fg != fg
|| _priceTag->star != star) {
const auto ratio = style::DevicePixelRatio();
auto bg = generateBackground();
if (bg.isNull()) {
bg = QImage(ratio, ratio, QImage::Format_ARGB32_Premultiplied);
bg.fill(Qt::black);
}
auto text = Ui::Text::String();
const auto session = &history()->session();
auto price = Ui::Text::Colorized(Ui::CreditsEmoji(session));
price.append(Lang::FormatCountDecimal(_priceTag->price));
text.setMarkedText(
st::semiboldTextStyle,
tr::lng_paid_price(
tr::now,
lt_price,
price,
Ui::Text::WithEntities),
kMarkupTextOptions,
Core::MarkedTextContext{
.session = session,
.customEmojiRepaint = [] {},
});
const auto width = text.maxWidth();
const auto inner = QRect(0, 0, width, text.minHeight());
const auto outer = inner.marginsAdded(st::paidTagPadding);
const auto size = outer.size();
const auto radius = std::min(size.width(), size.height()) / 2;
auto cache = QImage(
size * ratio,
QImage::Format_ARGB32_Premultiplied);
cache.setDevicePixelRatio(ratio);
cache.fill(Qt::black);
auto p = Painter(&cache);
auto hq = PainterHighQualityEnabler(p);
p.drawImage(
QRect(
(size.width() - rthumb.width()) / 2,
(size.height() - rthumb.height()) / 2,
rthumb.width(),
rthumb.height()),
bg);
p.fillRect(QRect(QPoint(), size), darken);
p.setPen(fg);
p.setTextPalette(st->priceTagTextPalette());
text.draw(p, -outer.x(), -outer.y(), width);
p.end();
_priceTag->darken = darken;
_priceTag->fg = fg;
_priceTag->cache = Images::Round(
std::move(cache),
Images::CornersMask(radius));
}
const auto &cache = _priceTag->cache;
const auto size = cache.size() / cache.devicePixelRatio();
const auto left = rthumb.x() + (rthumb.width() - size.width()) / 2;
const auto top = rthumb.y() + (rthumb.height() - size.height()) / 2;
p.drawImage(left, top, cache);
if (context.selected()) {
auto hq = PainterHighQualityEnabler(p);
const auto radius = std::min(size.width(), size.height()) / 2;
p.setPen(Qt::NoPen);
p.setBrush(st->msgSelectOverlay());
p.drawRoundedRect(
QRect(left, top, size.width(), size.height()),
radius,
radius);
}
Media::drawSpoilerTag(
p,
_spoiler.get(),
_spoilerTag,
rthumb,
context,
std::move(generateBackground));
}
void Photo::validateUserpicImageCache(QSize size, bool forum) const {
@ -734,23 +642,11 @@ QRect Photo::enlargeRect() const {
};
}
ClickHandlerPtr Photo::priceTagLink() const {
const auto item = parent()->data();
if (!item->isRegular()) {
return nullptr;
} else if (!_priceTag) {
setupPriceTag();
if (!_priceTag) {
return nullptr;
}
}
if (!_priceTag->link) {
_priceTag->link = MakePaidMediaLink(item);
}
return _priceTag->link;
ClickHandlerPtr Photo::spoilerTagLink() const {
return Media::spoilerTagLink(_spoiler.get(), _spoilerTag);
}
QImage Photo::priceTagBackground() const {
QImage Photo::spoilerTagBackground() const {
return _spoiler ? _spoiler->background : QImage();
}
@ -767,10 +663,10 @@ TextState Photo::textState(QPoint point, StateRequest request) const {
if (QRect(paintx, painty, paintw, painth).contains(point)) {
ensureDataMediaCreated();
result.link = _data->extendedMediaPreview()
? priceTagLink()
: (_spoiler && !_spoiler->revealed)
? _spoiler->link
result.link = (_spoiler && !_spoiler->revealed)
? ((_data->extendedMediaPreview() || _sensitiveSpoiler)
? spoilerTagLink()
: _spoiler->link)
: _data->uploading()
? _cancell
: _dataMedia->loaded()
@ -875,10 +771,11 @@ void Photo::drawGrouped(
p.setOpacity(1.);
}
const auto displayState = radial
|| (!loaded && !_data->loading())
|| _data->waitingForAlbum();
if (displayState) {
const auto paintInCenter = !_sensitiveSpoiler
&& (radial
|| (!loaded && !_data->loading())
|| _data->waitingForAlbum());
if (paintInCenter) {
const auto radialOpacity = radial
? _animation->radial.opacity()
: 1.;
@ -941,17 +838,18 @@ TextState Photo::getStateGrouped(
return {};
}
ensureDataMediaCreated();
return TextState(_parent, _data->extendedMediaPreview()
? priceTagLink()
: (_spoiler && !_spoiler->revealed)
? _spoiler->link
auto link = (_spoiler && !_spoiler->revealed)
? ((_data->extendedMediaPreview() || _sensitiveSpoiler)
? spoilerTagLink()
: _spoiler->link)
: _data->uploading()
? _cancell
: _dataMedia->loaded()
? _openl
: _data->loading()
? _cancell
: _savel);
: _savel;
return TextState(_parent, std::move(link));
}
float64 Photo::dataProgress() const {

View file

@ -75,13 +75,13 @@ public:
QPoint point,
StateRequest request) const override;
void drawPriceTag(
void drawSpoilerTag(
Painter &p,
QRect rthumb,
const PaintContext &context,
Fn<QImage()> generateBackground) const override;
ClickHandlerPtr priceTagLink() const override;
QImage priceTagBackground() const override;
ClickHandlerPtr spoilerTagLink() const override;
QImage spoilerTagBackground() const override;
void hideSpoilers() override;
bool needsBubble() const override;
@ -105,7 +105,6 @@ protected:
private:
struct Streamed;
struct PriceTag;
void create(FullMsgId contextId, PeerData *chat = nullptr);
@ -115,7 +114,7 @@ private:
void ensureDataMediaCreated() const;
void dataMediaCreated() const;
void setupPriceTag() const;
void setupSpoilerTag() const;
QSize countOptimalSize() override;
QSize countCurrentSize(int newWidth) override;
@ -161,14 +160,15 @@ private:
const not_null<PhotoData*> _data;
const FullStoryId _storyId;
mutable std::unique_ptr<PriceTag> _priceTag;
mutable std::shared_ptr<Data::PhotoMedia> _dataMedia;
mutable std::unique_ptr<Streamed> _streamed;
const std::unique_ptr<MediaSpoiler> _spoiler;
mutable std::unique_ptr<MediaSpoilerTag> _spoilerTag;
mutable QImage _imageCache;
mutable std::optional<Ui::BubbleRounding> _imageCacheRounding;
uint32 _serviceWidth : 27 = 0;
uint32 _serviceWidth : 26 = 0;
uint32 _purchasedPriceTag : 1 = 0;
const uint32 _sensitiveSpoiler : 1 = 0;
mutable uint32 _imageCacheForum : 1 = 0;
mutable uint32 _imageCacheBlurred : 1 = 0;
mutable uint32 _pollingStory : 1 = 0;

View file

@ -38,15 +38,18 @@ void AppConfig::start() {
}, _lifetime);
}
void AppConfig::refresh() {
void AppConfig::refresh(bool force) {
if (_requestId || !_api) {
if (force) {
_pendingRefresh = true;
}
return;
}
_pendingRefresh = false;
_requestId = _api->request(MTPhelp_GetAppConfig(
MTP_int(_hash)
)).done([=](const MTPhelp_AppConfig &result) {
_requestId = 0;
refreshDelayed();
result.match([&](const MTPDhelp_appConfig &data) {
_hash = data.vhash().v;
@ -55,15 +58,25 @@ void AppConfig::refresh() {
LOG(("API Error: Unexpected config type."));
return;
}
auto was = ignoredRestrictionReasons();
_data.clear();
for (const auto &element : config.c_jsonObject().vvalue().v) {
element.match([&](const MTPDjsonObjectValue &data) {
_data.emplace_or_assign(qs(data.vkey()), data.vvalue());
});
}
updateIgnoredRestrictionReasons(std::move(was));
DEBUG_LOG(("getAppConfig result handled."));
_refreshed.fire({});
}, [](const MTPDhelp_appConfigNotModified &) {});
if (base::take(_pendingRefresh)) {
refresh();
} else {
refreshDelayed();
}
}).fail([=] {
_requestId = 0;
refreshDelayed();
@ -76,6 +89,24 @@ void AppConfig::refreshDelayed() {
});
}
void AppConfig::updateIgnoredRestrictionReasons(std::vector<QString> was) {
_ignoreRestrictionReasons = get<std::vector<QString>>(
u"ignore_restriction_reasons"_q,
std::vector<QString>());
ranges::sort(_ignoreRestrictionReasons);
if (_ignoreRestrictionReasons != was) {
for (const auto &reason : _ignoreRestrictionReasons) {
const auto i = ranges::remove(was, reason);
if (i != end(was)) {
was.erase(i, end(was));
} else {
was.push_back(reason);
}
}
_ignoreRestrictionChanges.fire(std::move(was));
}
}
rpl::producer<> AppConfig::refreshed() const {
return _refreshed.events();
}

View file

@ -55,7 +55,15 @@ public:
[[nodiscard]] bool newRequirePremiumFree() const;
void refresh();
[[nodiscard]] auto ignoredRestrictionReasons() const
-> const std::vector<QString> & {
return _ignoreRestrictionReasons;
}
[[nodiscard]] auto ignoredRestrictionReasonsChanges() const {
return _ignoreRestrictionChanges.events();
}
void refresh(bool force = false);
private:
void refreshDelayed();
@ -84,14 +92,20 @@ private:
const QString &key,
std::vector<int> &&fallback) const;
void updateIgnoredRestrictionReasons(std::vector<QString> was);
const not_null<Account*> _account;
std::optional<MTP::Sender> _api;
mtpRequestId _requestId = 0;
int32 _hash = 0;
bool _pendingRefresh = false;
base::flat_map<QString, MTPJSONValue> _data;
rpl::event_stream<> _refreshed;
base::flat_set<QString> _dismissedSuggestions;
std::vector<QString> _ignoreRestrictionReasons;
rpl::event_stream<std::vector<QString>> _ignoreRestrictionChanges;
rpl::lifetime _lifetime;
};

View file

@ -7,7 +7,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#include "settings/settings_chat.h"
#include "base/timer_rpl.h"
#include "settings/settings_advanced.h"
#include "settings/settings_privacy_security.h"
#include "settings/settings_experimental.h"
#include "boxes/abstract_box.h"
#include "boxes/peers/edit_peer_color_box.h"
@ -1022,7 +1024,6 @@ void SetupMessages(
void SetupArchive(
not_null<Window::SessionController*> controller,
not_null<Ui::VerticalLayout*> container) {
Ui::AddDivider(container);
Ui::AddSkip(container);
PreloadArchiveSettings(&controller->session());
@ -1801,12 +1802,17 @@ void Chat::fillTopBarMenu(const Ui::Menu::MenuCallback &addAction) {
void Chat::setupContent(not_null<Window::SessionController*> controller) {
const auto content = Ui::CreateChild<Ui::VerticalLayout>(this);
auto updateOnTick = rpl::single(
) | rpl::then(base::timer_each(60 * crl::time(1000)));
SetupThemeOptions(controller, content);
SetupThemeSettings(controller, content);
SetupCloudThemes(controller, content);
SetupChatBackground(controller, content);
SetupStickersEmoji(controller, content);
SetupMessages(controller, content);
Ui::AddDivider(content);
SetupSensitiveContent(controller, content, std::move(updateOnTick));
SetupArchive(controller, content);
Ui::ResizeFitChild(this, content);

View file

@ -577,47 +577,6 @@ void SetupTopPeers(
Ui::AddDividerText(container, tr::lng_settings_top_peers_about());
}
void SetupSensitiveContent(
not_null<Window::SessionController*> controller,
not_null<Ui::VerticalLayout*> container,
rpl::producer<> updateTrigger) {
using namespace rpl::mappers;
const auto wrap = container->add(
object_ptr<Ui::SlideWrap<Ui::VerticalLayout>>(
container,
object_ptr<Ui::VerticalLayout>(container)));
const auto inner = wrap->entity();
Ui::AddSkip(inner);
Ui::AddSubsectionTitle(inner, tr::lng_settings_sensitive_title());
const auto session = &controller->session();
std::move(
updateTrigger
) | rpl::start_with_next([=] {
session->api().sensitiveContent().reload();
}, container->lifetime());
inner->add(object_ptr<Button>(
inner,
tr::lng_settings_sensitive_disable_filtering(),
st::settingsButtonNoIcon
))->toggleOn(
session->api().sensitiveContent().enabled()
)->toggledChanges(
) | rpl::filter([=](bool toggled) {
return toggled != session->api().sensitiveContent().enabledCurrent();
}) | rpl::start_with_next([=](bool toggled) {
session->api().sensitiveContent().update(toggled);
}, container->lifetime());
Ui::AddSkip(inner);
Ui::AddDividerText(inner, tr::lng_settings_sensitive_about());
wrap->toggleOn(session->api().sensitiveContent().canChange());
}
void SetupSelfDestruction(
not_null<Window::SessionController*> controller,
not_null<Ui::VerticalLayout*> container,
@ -911,6 +870,47 @@ void SetupSecurity(
} // namespace
void SetupSensitiveContent(
not_null<Window::SessionController*> controller,
not_null<Ui::VerticalLayout*> container,
rpl::producer<> updateTrigger) {
using namespace rpl::mappers;
const auto wrap = container->add(
object_ptr<Ui::SlideWrap<Ui::VerticalLayout>>(
container,
object_ptr<Ui::VerticalLayout>(container)));
const auto inner = wrap->entity();
Ui::AddSkip(inner);
Ui::AddSubsectionTitle(inner, tr::lng_settings_sensitive_title());
const auto session = &controller->session();
std::move(
updateTrigger
) | rpl::start_with_next([=] {
session->api().sensitiveContent().reload();
}, container->lifetime());
inner->add(object_ptr<Button>(
inner,
tr::lng_settings_sensitive_disable_filtering(),
st::settingsButtonNoIcon
))->toggleOn(
session->api().sensitiveContent().enabled()
)->toggledChanges(
) | rpl::filter([=](bool toggled) {
return toggled != session->api().sensitiveContent().enabledCurrent();
}) | rpl::start_with_next([=](bool toggled) {
session->api().sensitiveContent().update(toggled);
}, container->lifetime());
Ui::AddSkip(inner);
Ui::AddDividerText(inner, tr::lng_settings_sensitive_about());
wrap->toggleOn(session->api().sensitiveContent().canChange());
}
int ExceptionUsersCount(const std::vector<not_null<PeerData*>> &exceptions) {
const auto add = [](int already, not_null<PeerData*> peer) {
if (const auto chat = peer->asChat()) {
@ -1099,11 +1099,6 @@ void PrivacySecurity::setupContent(
SetupSecurity(controller, content, trigger(), showOtherMethod());
SetupPrivacy(controller, content, trigger());
SetupTopPeers(controller, content);
#if !defined OS_MAC_STORE && !defined OS_WIN_STORE
SetupSensitiveContent(controller, content, trigger());
#else // !OS_MAC_STORE && !OS_WIN_STORE
AddDivider(content);
#endif // !OS_MAC_STORE && !OS_WIN_STORE
SetupArchiveAndMute(controller, content);
SetupConfirmationExtensions(controller, content);
SetupBotsAndWebsites(controller, content);

View file

@ -18,6 +18,11 @@ class BoxContent;
namespace Settings {
void SetupSensitiveContent(
not_null<Window::SessionController*> controller,
not_null<Ui::VerticalLayout*> container,
rpl::producer<> updateTrigger);
int ExceptionUsersCount(const std::vector<not_null<PeerData*>> &exceptions);
bool CheckEditCloudPassword(not_null<::Main::Session*> session);