diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index 2aeb46f50..013244b04 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -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}"; diff --git a/Telegram/SourceFiles/api/api_sensitive_content.cpp b/Telegram/SourceFiles/api/api_sensitive_content.cpp index 97388d638..31f83f5eb 100644 --- a/Telegram/SourceFiles/api/api_sensitive_content.cpp +++ b/Telegram/SourceFiles/api/api_sensitive_content.cpp @@ -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 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 diff --git a/Telegram/SourceFiles/api/api_sensitive_content.h b/Telegram/SourceFiles/api/api_sensitive_content.h index 0a61f9da7..576bf275f 100644 --- a/Telegram/SourceFiles/api/api_sensitive_content.h +++ b/Telegram/SourceFiles/api/api_sensitive_content.h @@ -22,7 +22,8 @@ class SensitiveContent final { public: explicit SensitiveContent(not_null 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 _session; MTP::Sender _api; - mtpRequestId _requestId = 0; + mtpRequestId _loadRequestId = 0; + mtpRequestId _saveRequestId = 0; rpl::variable _enabled = false; rpl::variable _canChange = false; base::Timer _appConfigReloadTimer; + bool _appConfigReloadForce = false; + bool _loadPending = false; + bool _loaded = false; }; diff --git a/Telegram/SourceFiles/boxes/peers/edit_peer_info_box.cpp b/Telegram/SourceFiles/boxes/peers/edit_peer_info_box.cpp index 4af6d5fbd..fcdfa8299 100644 --- a/Telegram/SourceFiles/boxes/peers/edit_peer_info_box.cpp +++ b/Telegram/SourceFiles/boxes/peers/edit_peer_info_box.cpp @@ -1074,7 +1074,7 @@ void Controller::fillSignaturesButton() { rpl::single(QString()), [] {}, st::manageGroupTopButtonWithText, - { &st::menuIconSigned }))); + { &st::menuIconProfile }))); profiles->toggleOn(signs->toggledValue()); profiles->finishAnimating(); diff --git a/Telegram/SourceFiles/chat_helpers/ttl_media_layer_widget.cpp b/Telegram/SourceFiles/chat_helpers/ttl_media_layer_widget.cpp index d2e2e945e..74ef95961 100644 --- a/Telegram/SourceFiles/chat_helpers/ttl_media_layer_widget.cpp +++ b/Telegram/SourceFiles/chat_helpers/ttl_media_layer_widget.cpp @@ -166,7 +166,7 @@ PreviewWrap::PreviewWrap( } }, lifetime()); session->data().itemViewRefreshRequest( - ) | rpl::start_with_next([=](not_null item) { + ) | rpl::start_with_next([=](not_null item) { if (item == _item) { if (goodItem()) { createView(); diff --git a/Telegram/SourceFiles/data/data_channel.cpp b/Telegram/SourceFiles/data/data_channel.cpp index 39e41a753..3a52d4c5f 100644 --- a/Telegram/SourceFiles/data/data_channel.cpp +++ b/Telegram/SourceFiles/data/data_channel.cpp @@ -544,12 +544,9 @@ auto ChannelData::unavailableReasons() const return _unavailableReasons; } -void ChannelData::setUnavailableReasons( +void ChannelData::setUnavailableReasonsList( std::vector &&reasons) { - if (_unavailableReasons != reasons) { - _unavailableReasons = std::move(reasons); - session().changes().peerUpdated(this, UpdateFlag::UnavailableReason); - } + _unavailableReasons = std::move(reasons); } void ChannelData::setAvailableMinId(MsgId availableMinId) { diff --git a/Telegram/SourceFiles/data/data_channel.h b/Telegram/SourceFiles/data/data_channel.h index 178890107..90f9e2b8d 100644 --- a/Telegram/SourceFiles/data/data_channel.h +++ b/Telegram/SourceFiles/data/data_channel.h @@ -433,9 +433,6 @@ public: return _ptsWaiter.waitingForShortPoll(); } - void setUnavailableReasons( - std::vector &&reason); - [[nodiscard]] MsgId availableMinId() const { return _availableMinId; } @@ -515,6 +512,9 @@ private: -> const std::vector & override; bool canEditLastAdmin(not_null user) const; + void setUnavailableReasonsList( + std::vector &&reasons) override; + Flags _flags = ChannelDataFlags(Flag::Forbidden); PtsWaiter _ptsWaiter; diff --git a/Telegram/SourceFiles/data/data_download_manager.cpp b/Telegram/SourceFiles/data/data_download_manager.cpp index 29a1899e0..cbd60ff05 100644 --- a/Telegram/SourceFiles/data/data_download_manager.cpp +++ b/Telegram/SourceFiles/data/data_download_manager.cpp @@ -143,7 +143,7 @@ void DownloadManager::trackSession(not_null session) { }, data.lifetime); session->data().itemViewRefreshRequest( - ) | rpl::start_with_next([=](not_null item) { + ) | rpl::start_with_next([=](not_null item) { changed(item); }, data.lifetime); diff --git a/Telegram/SourceFiles/data/data_peer.cpp b/Telegram/SourceFiles/data/data_peer.cpp index 08aaca699..097cfdfa8 100644 --- a/Telegram/SourceFiles/data/data_peer.cpp +++ b/Telegram/SourceFiles/data/data_peer.cpp @@ -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 &IgnoredReasons( + not_null session) { + return session->appConfig().ignoredRestrictionReasons(); +} + } // namespace namespace Data { @@ -85,10 +91,7 @@ UnavailableReason UnavailableReason::Sensitive() { QString UnavailableReason::Compute( not_null session, const std::vector &list) { - const auto &config = session->appConfig(); - const auto skip = config.get>( - "ignore_restriction_reasons", - std::vector()); + 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 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::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 &&reasons) { + Unexpected("PeerData::setUnavailableReasonsList."); +} + +void PeerData::setUnavailableReasons( + std::vector &&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(). diff --git a/Telegram/SourceFiles/data/data_peer.h b/Telegram/SourceFiles/data/data_peer.h index 7c393718b..f88d801ce 100644 --- a/Telegram/SourceFiles/data/data_peer.h +++ b/Telegram/SourceFiles/data/data_peer.h @@ -99,6 +99,8 @@ struct UnavailableReason { [[nodiscard]] static QString Compute( not_null session, const std::vector &list); + [[nodiscard]] static bool IgnoreSensitiveMark( + not_null session); [[nodiscard]] static std::vector Extract( const MTPvector *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 &&reason); [[nodiscard]] ClickHandlerPtr createOpenLink(); [[nodiscard]] const ClickHandlerPtr &openLink() { @@ -489,6 +493,10 @@ private: const ImageLocation &location, bool hasVideo); + virtual void setUnavailableReasonsList( + std::vector &&reasons); + void setHasSensitiveContent(bool has); + const not_null _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; diff --git a/Telegram/SourceFiles/data/data_photo.cpp b/Telegram/SourceFiles/data/data_photo.cpp index 4556c2195..0a2d37f8f 100644 --- a/Telegram/SourceFiles/data/data_photo.cpp +++ b/Telegram/SourceFiles/data/data_photo.cpp @@ -252,8 +252,7 @@ Image *PhotoData::getReplyPreview( Image *PhotoData::getReplyPreview(not_null 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); } diff --git a/Telegram/SourceFiles/data/data_session.cpp b/Telegram/SourceFiles/data/data_session.cpp index 4c2ad3422..8016381b2 100644 --- a/Telegram/SourceFiles/data/data_session.cpp +++ b/Telegram/SourceFiles/data/data_session.cpp @@ -311,6 +311,22 @@ Session::Session(not_null session) _stories->loadMore(Data::StorySourcesList::NotHidden); }); + + session->appConfig().ignoredRestrictionReasonsChanges( + ) | rpl::start_with_next([=](std::vector &&changed) { + auto refresh = std::vector>(); + 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> Session::viewResizeRequest() const { return _viewResizeRequest.events(); } -void Session::requestItemViewRefresh(not_null item) { +void Session::requestItemViewRefresh(not_null item) { if (const auto view = item->mainView()) { notifyHistoryChangeDelayed(item->history()); view->refreshInBlock(); @@ -1781,7 +1797,7 @@ void Session::requestItemViewRefresh(not_null item) { _itemViewRefreshRequest.fire_copy(item); } -rpl::producer> Session::itemViewRefreshRequest() const { +rpl::producer> Session::itemViewRefreshRequest() const { return _itemViewRefreshRequest.events(); } @@ -1807,6 +1823,31 @@ void Session::requestItemTextRefresh(not_null item) { } } +void Session::registerRestricted( + not_null item, + const QString &reason) { + Expects(item->hasPossibleRestrictions()); + + _possiblyRestricted[item].emplace(reason); +} + +void Session::registerRestricted( + not_null item, + const std::vector &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 item) { @@ -2510,6 +2551,9 @@ void Session::unregisterMessage(not_null 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); diff --git a/Telegram/SourceFiles/data/data_session.h b/Telegram/SourceFiles/data/data_session.h index 5abb66c98..424a92649 100644 --- a/Telegram/SourceFiles/data/data_session.h +++ b/Telegram/SourceFiles/data/data_session.h @@ -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> itemResizeRequest() const; void requestViewResize(not_null view); [[nodiscard]] rpl::producer> viewResizeRequest() const; - void requestItemViewRefresh(not_null item); - [[nodiscard]] rpl::producer> itemViewRefreshRequest() const; + void requestItemViewRefresh(not_null item); + [[nodiscard]] rpl::producer> itemViewRefreshRequest() const; void requestItemTextRefresh(not_null item); void requestUnreadReactionsAnimation(not_null item); void notifyHistoryUnloaded(not_null history); @@ -313,6 +314,13 @@ public: void notifyPinnedDialogsOrderUpdated(); [[nodiscard]] rpl::producer<> pinnedDialogsOrderUpdated() const; + void registerRestricted( + not_null item, + const QString &reason); + void registerRestricted( + not_null item, + const std::vector &reasons); + void registerHighlightProcess( uint64 processId, not_null item); @@ -920,7 +928,7 @@ private: rpl::event_stream> _viewRepaintRequest; rpl::event_stream> _itemResizeRequest; rpl::event_stream> _viewResizeRequest; - rpl::event_stream> _itemViewRefreshRequest; + rpl::event_stream> _itemViewRefreshRequest; rpl::event_stream> _itemTextRefreshRequest; rpl::event_stream> _itemDataChanges; rpl::event_stream> _itemRemoved; @@ -1021,6 +1029,10 @@ private: base::flat_multi_map> _pollsClosings; base::Timer _pollsClosingTimer; + base::flat_map< + not_null, + base::flat_set> _possiblyRestricted; + base::flat_map> _folders; std::unordered_map< diff --git a/Telegram/SourceFiles/data/data_types.h b/Telegram/SourceFiles/data/data_types.h index 932844085..fbcdf3e3f 100644 --- a/Telegram/SourceFiles/data/data_types.h +++ b/Telegram/SourceFiles/data/data_types.h @@ -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; diff --git a/Telegram/SourceFiles/data/data_user.cpp b/Telegram/SourceFiles/data/data_user.cpp index cf369694f..729f27dce 100644 --- a/Telegram/SourceFiles/data/data_user.cpp +++ b/Telegram/SourceFiles/data/data_user.cpp @@ -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 &&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); + } } } diff --git a/Telegram/SourceFiles/data/data_user.h b/Telegram/SourceFiles/data/data_user.h index 8b737c349..d891d4937 100644 --- a/Telegram/SourceFiles/data/data_user.h +++ b/Telegram/SourceFiles/data/data_user.h @@ -185,9 +185,6 @@ public: void setBirthday(Data::Birthday value); void setBirthday(const tl::conditional &value); - void setUnavailableReasons( - std::vector &&reasons); - int commonChatsCount() const; void setCommonChatsCount(int count); @@ -218,6 +215,9 @@ private: auto unavailableReasons() const -> const std::vector & override; + void setUnavailableReasonsList( + std::vector &&reasons) override; + Flags _flags; Data::LastseenStatus _lastseen; Data::Birthday _birthday; diff --git a/Telegram/SourceFiles/history/history_item.cpp b/Telegram/SourceFiles/history/history_item.cpp index 18e803aeb..cebb425ca 100644 --- a/Telegram/SourceFiles/history/history_item.cpp +++ b/Telegram/SourceFiles/history/history_item.cpp @@ -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()) { + _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(); diff --git a/Telegram/SourceFiles/history/history_item.h b/Telegram/SourceFiles/history/history_item.h index 6b10b6534..682075e60 100644 --- a/Telegram/SourceFiles/history/history_item.h +++ b/Telegram/SourceFiles/history/history_item.h @@ -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(this)->inlineReplyMarkup(); @@ -660,6 +660,7 @@ private: [[nodiscard]] PreparedServiceText prepareCallScheduledText( TimeId scheduleDate); + void flagSensitiveContent(); [[nodiscard]] PeerData *computeDisplayFrom() const; const not_null _history; diff --git a/Telegram/SourceFiles/history/view/history_view_element.cpp b/Telegram/SourceFiles/history/view/history_view_element.cpp index cb27621ba..39d5850aa 100644 --- a/Telegram/SourceFiles/history/view/history_view_element.cpp +++ b/Telegram/SourceFiles/history/view/history_view_element.cpp @@ -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()); + } } } diff --git a/Telegram/SourceFiles/history/view/media/history_view_gif.cpp b/Telegram/SourceFiles/history/view/media/history_view_gif.cpp index ffe0d7757..cfd472f45 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_gif.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_gif.cpp @@ -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() : 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 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 { diff --git a/Telegram/SourceFiles/history/view/media/history_view_gif.h b/Telegram/SourceFiles/history/view/media/history_view_gif.h index f86421f92..45edc9ce3 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_gif.h +++ b/Telegram/SourceFiles/history/view/media/history_view_gif.h @@ -90,6 +90,14 @@ public: void stopAnimation() override; void checkAnimation() override; + void drawSpoilerTag( + Painter &p, + QRect rthumb, + const PaintContext &context, + Fn 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; const std::unique_ptr _spoiler; + mutable std::unique_ptr _spoilerTag; mutable std::unique_ptr _transcribe; mutable std::shared_ptr _dataMedia; mutable std::unique_ptr _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; }; diff --git a/Telegram/SourceFiles/history/view/media/history_view_media.cpp b/Telegram/SourceFiles/history/view/media/history_view_media.cpp index ddd76f945..2fc977402 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_media.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_media.cpp @@ -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 spoiler, + std::unique_ptr &tag, + QRect rthumb, + const PaintContext &context, + Fn 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 &tag) const { + const auto item = parent()->data(); + if (item->isMediaSensitive()) { + tag = std::make_unique(); + 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(); + tag->price = price; + } +} + +ClickHandlerPtr Media::spoilerTagLink( + not_null spoiler, + std::unique_ptr &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 spoiler) { const auto weak = base::make_weak(this); spoiler->link = std::make_shared([weak, spoiler]( diff --git a/Telegram/SourceFiles/history/view/media/history_view_media.h b/Telegram/SourceFiles/history/view/media/history_view_media.h index 94ad6520b..93c877b66 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_media.h +++ b/Telegram/SourceFiles/history/view/media/history_view_media.h @@ -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 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 spoiler, QRect rect, const PaintContext &context) const; + void drawSpoilerTag( + Painter &p, + not_null spoiler, + std::unique_ptr &tag, + QRect rthumb, + const PaintContext &context, + Fn generateBackground) const; + void setupSpoilerTag(std::unique_ptr &tag) const; + [[nodiscard]] ClickHandlerPtr spoilerTagLink( + not_null spoiler, + std::unique_ptr &tag) const; void createSpoilerLink(not_null spoiler); void repaint() const; diff --git a/Telegram/SourceFiles/history/view/media/history_view_media_common.cpp b/Telegram/SourceFiles/history/view/media/history_view_media_common.cpp index 245f4ceb9..b899d9fc9 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_media_common.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_media_common.cpp @@ -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 item) { }); } +ClickHandlerPtr MakeSensitiveMediaLink( + ClickHandlerPtr reveal, + not_null item) { + const auto session = &item->history()->session(); + return std::make_shared([=](ClickContext context) { + const auto my = context.other.value(); + 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 box) { + struct State { + rpl::variable canChange; + Ui::Checkbox *checkbox = nullptr; + }; + const auto state = box->lifetime().make_state(); + const auto sensitive = &session->api().sensitiveContent(); + state->canChange = sensitive->canChange(); + const auto done = [=](Fn 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>( + box, + object_ptr( + 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 diff --git a/Telegram/SourceFiles/history/view/media/history_view_media_common.h b/Telegram/SourceFiles/history/view/media/history_view_media_common.h index a37d0ab62..03646372f 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_media_common.h +++ b/Telegram/SourceFiles/history/view/media/history_view_media_common.h @@ -75,6 +75,10 @@ void PaintInterpolatedIcon( int newWidth, int maxWidth); -[[nodiscard]] ClickHandlerPtr MakePaidMediaLink(not_null item); +[[nodiscard]] ClickHandlerPtr MakePaidMediaLink( + not_null item); +[[nodiscard]] ClickHandlerPtr MakeSensitiveMediaLink( + ClickHandlerPtr reveal, + not_null item); } // namespace HistoryView diff --git a/Telegram/SourceFiles/history/view/media/history_view_media_grouped.cpp b/Telegram/SourceFiles/history/view/media/history_view_media_grouped.cpp index 4b26222ee..5cf80255f 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_media_grouped.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_media_grouped.cpp @@ -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())) { diff --git a/Telegram/SourceFiles/history/view/media/history_view_media_grouped.h b/Telegram/SourceFiles/history/view/media/history_view_media_grouped.h index 5397b6817..459eb4b1c 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_media_grouped.h +++ b/Telegram/SourceFiles/history/view/media/history_view_media_grouped.h @@ -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 _captionItem; std::vector _parts; diff --git a/Telegram/SourceFiles/history/view/media/history_view_media_spoiler.h b/Telegram/SourceFiles/history/view/media/history_view_media_spoiler.h index 2b885a25a..f668a2fd9 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_media_spoiler.h +++ b/Telegram/SourceFiles/history/view/media/history_view_media_spoiler.h @@ -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 diff --git a/Telegram/SourceFiles/history/view/media/history_view_photo.cpp b/Telegram/SourceFiles/history/view/media/history_view_photo.cpp index 043883035..a8a5f6da9 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_photo.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_photo.cpp @@ -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() : nullptr) { +, _spoiler((spoiler || realParent->isMediaSensitive()) + ? std::make_unique() + : 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->price = price; -} - -void Photo::drawPriceTag( +void Photo::drawSpoilerTag( Painter &p, QRect rthumb, const PaintContext &context, Fn 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 { diff --git a/Telegram/SourceFiles/history/view/media/history_view_photo.h b/Telegram/SourceFiles/history/view/media/history_view_photo.h index 3c1aec3e4..710901f76 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_photo.h +++ b/Telegram/SourceFiles/history/view/media/history_view_photo.h @@ -75,13 +75,13 @@ public: QPoint point, StateRequest request) const override; - void drawPriceTag( + void drawSpoilerTag( Painter &p, QRect rthumb, const PaintContext &context, Fn 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 _data; const FullStoryId _storyId; - mutable std::unique_ptr _priceTag; mutable std::shared_ptr _dataMedia; mutable std::unique_ptr _streamed; const std::unique_ptr _spoiler; + mutable std::unique_ptr _spoilerTag; mutable QImage _imageCache; mutable std::optional _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; diff --git a/Telegram/SourceFiles/main/main_app_config.cpp b/Telegram/SourceFiles/main/main_app_config.cpp index 6a4c63c35..11e81958c 100644 --- a/Telegram/SourceFiles/main/main_app_config.cpp +++ b/Telegram/SourceFiles/main/main_app_config.cpp @@ -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 was) { + _ignoreRestrictionReasons = get>( + u"ignore_restriction_reasons"_q, + std::vector()); + 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(); } diff --git a/Telegram/SourceFiles/main/main_app_config.h b/Telegram/SourceFiles/main/main_app_config.h index f09d5f120..09fe65ea1 100644 --- a/Telegram/SourceFiles/main/main_app_config.h +++ b/Telegram/SourceFiles/main/main_app_config.h @@ -55,7 +55,15 @@ public: [[nodiscard]] bool newRequirePremiumFree() const; - void refresh(); + [[nodiscard]] auto ignoredRestrictionReasons() const + -> const std::vector & { + 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 &&fallback) const; + void updateIgnoredRestrictionReasons(std::vector was); + const not_null _account; std::optional _api; mtpRequestId _requestId = 0; int32 _hash = 0; + bool _pendingRefresh = false; base::flat_map _data; rpl::event_stream<> _refreshed; base::flat_set _dismissedSuggestions; + std::vector _ignoreRestrictionReasons; + rpl::event_stream> _ignoreRestrictionChanges; + rpl::lifetime _lifetime; }; diff --git a/Telegram/SourceFiles/settings/settings_chat.cpp b/Telegram/SourceFiles/settings/settings_chat.cpp index 9d6203c13..3ddc6d8f5 100644 --- a/Telegram/SourceFiles/settings/settings_chat.cpp +++ b/Telegram/SourceFiles/settings/settings_chat.cpp @@ -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 controller, not_null 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 controller) { const auto content = Ui::CreateChild(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); diff --git a/Telegram/SourceFiles/settings/settings_privacy_security.cpp b/Telegram/SourceFiles/settings/settings_privacy_security.cpp index 13dae9fb6..f71134c3c 100644 --- a/Telegram/SourceFiles/settings/settings_privacy_security.cpp +++ b/Telegram/SourceFiles/settings/settings_privacy_security.cpp @@ -577,47 +577,6 @@ void SetupTopPeers( Ui::AddDividerText(container, tr::lng_settings_top_peers_about()); } -void SetupSensitiveContent( - not_null controller, - not_null container, - rpl::producer<> updateTrigger) { - using namespace rpl::mappers; - - const auto wrap = container->add( - object_ptr>( - container, - object_ptr(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