mirror of
https://github.com/AyuGram/AyuGramDesktop.git
synced 2025-06-05 06:33:57 +02:00
Support 18+/restrictions for messages.
This commit is contained in:
parent
074dbf41e0
commit
b9de12fedb
35 changed files with 711 additions and 299 deletions
|
@ -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_show_in_top" = "Show me in Top Senders";
|
||||||
"lng_paid_react_anonymous" = "Anonymous";
|
"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_show_original" = "Show Original";
|
||||||
"lng_translate_bar_to" = "Translate to {name}";
|
"lng_translate_bar_to" = "Translate to {name}";
|
||||||
"lng_translate_bar_to_other" = "Translate to {name}";
|
"lng_translate_bar_to_other" = "Translate to {name}";
|
||||||
|
|
|
@ -14,7 +14,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
namespace Api {
|
namespace Api {
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
constexpr auto kRefreshAppConfigTimeout = 3 * crl::time(1000);
|
constexpr auto kRefreshAppConfigTimeout = crl::time(1);
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
|
@ -24,19 +24,40 @@ SensitiveContent::SensitiveContent(not_null<ApiWrap*> api)
|
||||||
, _appConfigReloadTimer([=] { _session->appConfig().refresh(); }) {
|
, _appConfigReloadTimer([=] { _session->appConfig().refresh(); }) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void SensitiveContent::reload() {
|
void SensitiveContent::preload() {
|
||||||
if (_requestId) {
|
if (!_loaded) {
|
||||||
|
reload();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void SensitiveContent::reload(bool force) {
|
||||||
|
if (_loadRequestId) {
|
||||||
|
if (force) {
|
||||||
|
_loadPending = true;
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
_requestId = _api.request(MTPaccount_GetContentSettings(
|
_loaded = true;
|
||||||
|
_loadRequestId = _api.request(MTPaccount_GetContentSettings(
|
||||||
)).done([=](const MTPaccount_ContentSettings &result) {
|
)).done([=](const MTPaccount_ContentSettings &result) {
|
||||||
_requestId = 0;
|
_loadRequestId = 0;
|
||||||
result.match([&](const MTPDaccount_contentSettings &data) {
|
const auto &data = result.data();
|
||||||
_enabled = data.is_sensitive_enabled();
|
const auto enabled = data.is_sensitive_enabled();
|
||||||
_canChange = data.is_sensitive_can_change();
|
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([=] {
|
}).fail([=] {
|
||||||
_requestId = 0;
|
_loadRequestId = 0;
|
||||||
}).send();
|
}).send();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -57,17 +78,24 @@ void SensitiveContent::update(bool enabled) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
using Flag = MTPaccount_SetContentSettings::Flag;
|
using Flag = MTPaccount_SetContentSettings::Flag;
|
||||||
_api.request(_requestId).cancel();
|
_api.request(_saveRequestId).cancel();
|
||||||
_requestId = _api.request(MTPaccount_SetContentSettings(
|
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))
|
MTP_flags(enabled ? Flag::f_sensitive_enabled : Flag(0))
|
||||||
)).done([=] {
|
)).done(finish).fail(finish).send();
|
||||||
_requestId = 0;
|
|
||||||
}).fail([=] {
|
|
||||||
_requestId = 0;
|
|
||||||
}).send();
|
|
||||||
_enabled = enabled;
|
_enabled = enabled;
|
||||||
|
|
||||||
_appConfigReloadTimer.callOnce(kRefreshAppConfigTimeout);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace Api
|
} // namespace Api
|
||||||
|
|
|
@ -22,7 +22,8 @@ class SensitiveContent final {
|
||||||
public:
|
public:
|
||||||
explicit SensitiveContent(not_null<ApiWrap*> api);
|
explicit SensitiveContent(not_null<ApiWrap*> api);
|
||||||
|
|
||||||
void reload();
|
void preload();
|
||||||
|
void reload(bool force = false);
|
||||||
void update(bool enabled);
|
void update(bool enabled);
|
||||||
|
|
||||||
[[nodiscard]] bool enabledCurrent() const;
|
[[nodiscard]] bool enabledCurrent() const;
|
||||||
|
@ -32,10 +33,14 @@ public:
|
||||||
private:
|
private:
|
||||||
const not_null<Main::Session*> _session;
|
const not_null<Main::Session*> _session;
|
||||||
MTP::Sender _api;
|
MTP::Sender _api;
|
||||||
mtpRequestId _requestId = 0;
|
mtpRequestId _loadRequestId = 0;
|
||||||
|
mtpRequestId _saveRequestId = 0;
|
||||||
rpl::variable<bool> _enabled = false;
|
rpl::variable<bool> _enabled = false;
|
||||||
rpl::variable<bool> _canChange = false;
|
rpl::variable<bool> _canChange = false;
|
||||||
base::Timer _appConfigReloadTimer;
|
base::Timer _appConfigReloadTimer;
|
||||||
|
bool _appConfigReloadForce = false;
|
||||||
|
bool _loadPending = false;
|
||||||
|
bool _loaded = false;
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -1074,7 +1074,7 @@ void Controller::fillSignaturesButton() {
|
||||||
rpl::single(QString()),
|
rpl::single(QString()),
|
||||||
[] {},
|
[] {},
|
||||||
st::manageGroupTopButtonWithText,
|
st::manageGroupTopButtonWithText,
|
||||||
{ &st::menuIconSigned })));
|
{ &st::menuIconProfile })));
|
||||||
profiles->toggleOn(signs->toggledValue());
|
profiles->toggleOn(signs->toggledValue());
|
||||||
profiles->finishAnimating();
|
profiles->finishAnimating();
|
||||||
|
|
||||||
|
|
|
@ -166,7 +166,7 @@ PreviewWrap::PreviewWrap(
|
||||||
}
|
}
|
||||||
}, lifetime());
|
}, lifetime());
|
||||||
session->data().itemViewRefreshRequest(
|
session->data().itemViewRefreshRequest(
|
||||||
) | rpl::start_with_next([=](not_null<HistoryItem*> item) {
|
) | rpl::start_with_next([=](not_null<const HistoryItem*> item) {
|
||||||
if (item == _item) {
|
if (item == _item) {
|
||||||
if (goodItem()) {
|
if (goodItem()) {
|
||||||
createView();
|
createView();
|
||||||
|
|
|
@ -544,12 +544,9 @@ auto ChannelData::unavailableReasons() const
|
||||||
return _unavailableReasons;
|
return _unavailableReasons;
|
||||||
}
|
}
|
||||||
|
|
||||||
void ChannelData::setUnavailableReasons(
|
void ChannelData::setUnavailableReasonsList(
|
||||||
std::vector<Data::UnavailableReason> &&reasons) {
|
std::vector<Data::UnavailableReason> &&reasons) {
|
||||||
if (_unavailableReasons != reasons) {
|
_unavailableReasons = std::move(reasons);
|
||||||
_unavailableReasons = std::move(reasons);
|
|
||||||
session().changes().peerUpdated(this, UpdateFlag::UnavailableReason);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void ChannelData::setAvailableMinId(MsgId availableMinId) {
|
void ChannelData::setAvailableMinId(MsgId availableMinId) {
|
||||||
|
|
|
@ -433,9 +433,6 @@ public:
|
||||||
return _ptsWaiter.waitingForShortPoll();
|
return _ptsWaiter.waitingForShortPoll();
|
||||||
}
|
}
|
||||||
|
|
||||||
void setUnavailableReasons(
|
|
||||||
std::vector<Data::UnavailableReason> &&reason);
|
|
||||||
|
|
||||||
[[nodiscard]] MsgId availableMinId() const {
|
[[nodiscard]] MsgId availableMinId() const {
|
||||||
return _availableMinId;
|
return _availableMinId;
|
||||||
}
|
}
|
||||||
|
@ -515,6 +512,9 @@ private:
|
||||||
-> const std::vector<Data::UnavailableReason> & override;
|
-> const std::vector<Data::UnavailableReason> & override;
|
||||||
bool canEditLastAdmin(not_null<UserData*> user) const;
|
bool canEditLastAdmin(not_null<UserData*> user) const;
|
||||||
|
|
||||||
|
void setUnavailableReasonsList(
|
||||||
|
std::vector<Data::UnavailableReason> &&reasons) override;
|
||||||
|
|
||||||
Flags _flags = ChannelDataFlags(Flag::Forbidden);
|
Flags _flags = ChannelDataFlags(Flag::Forbidden);
|
||||||
|
|
||||||
PtsWaiter _ptsWaiter;
|
PtsWaiter _ptsWaiter;
|
||||||
|
|
|
@ -143,7 +143,7 @@ void DownloadManager::trackSession(not_null<Main::Session*> session) {
|
||||||
}, data.lifetime);
|
}, data.lifetime);
|
||||||
|
|
||||||
session->data().itemViewRefreshRequest(
|
session->data().itemViewRefreshRequest(
|
||||||
) | rpl::start_with_next([=](not_null<HistoryItem*> item) {
|
) | rpl::start_with_next([=](not_null<const HistoryItem*> item) {
|
||||||
changed(item);
|
changed(item);
|
||||||
}, data.lifetime);
|
}, data.lifetime);
|
||||||
|
|
||||||
|
|
|
@ -7,6 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
*/
|
*/
|
||||||
#include "data/data_peer.h"
|
#include "data/data_peer.h"
|
||||||
|
|
||||||
|
#include "api/api_sensitive_content.h"
|
||||||
#include "data/data_user.h"
|
#include "data/data_user.h"
|
||||||
#include "data/data_chat.h"
|
#include "data/data_chat.h"
|
||||||
#include "data/data_chat_participant_status.h"
|
#include "data/data_chat_participant_status.h"
|
||||||
|
@ -58,6 +59,11 @@ constexpr auto kUserpicSize = 160;
|
||||||
|
|
||||||
using UpdateFlag = Data::PeerUpdate::Flag;
|
using UpdateFlag = Data::PeerUpdate::Flag;
|
||||||
|
|
||||||
|
[[nodiscard]] const std::vector<QString> &IgnoredReasons(
|
||||||
|
not_null<Main::Session*> session) {
|
||||||
|
return session->appConfig().ignoredRestrictionReasons();
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
namespace Data {
|
namespace Data {
|
||||||
|
@ -85,10 +91,7 @@ UnavailableReason UnavailableReason::Sensitive() {
|
||||||
QString UnavailableReason::Compute(
|
QString UnavailableReason::Compute(
|
||||||
not_null<Main::Session*> session,
|
not_null<Main::Session*> session,
|
||||||
const std::vector<UnavailableReason> &list) {
|
const std::vector<UnavailableReason> &list) {
|
||||||
const auto &config = session->appConfig();
|
const auto &skip = IgnoredReasons(session);
|
||||||
const auto skip = config.get<std::vector<QString>>(
|
|
||||||
"ignore_restriction_reasons",
|
|
||||||
std::vector<QString>());
|
|
||||||
auto &&filtered = ranges::views::all(
|
auto &&filtered = ranges::views::all(
|
||||||
list
|
list
|
||||||
) | ranges::views::filter([&](const Data::UnavailableReason &reason) {
|
) | ranges::views::filter([&](const Data::UnavailableReason &reason) {
|
||||||
|
@ -99,6 +102,13 @@ QString UnavailableReason::Compute(
|
||||||
return (first != filtered.end()) ? first->text : QString();
|
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
|
// We should get a full restriction in "{full}: {reason}" format and we
|
||||||
// need to find an "-all" tag in {full}, otherwise ignore this restriction.
|
// need to find an "-all" tag in {full}, otherwise ignore this restriction.
|
||||||
std::vector<UnavailableReason> UnavailableReason::Extract(
|
std::vector<UnavailableReason> UnavailableReason::Extract(
|
||||||
|
@ -554,11 +564,45 @@ QString PeerData::computeUnavailableReason() const {
|
||||||
unavailableReasons());
|
unavailableReasons());
|
||||||
}
|
}
|
||||||
|
|
||||||
bool PeerData::isUnavailableSensitive() const {
|
bool PeerData::hasSensitiveContent() const {
|
||||||
return ranges::contains(
|
return _sensitiveContent == 1;
|
||||||
unavailableReasons(),
|
}
|
||||||
|
|
||||||
|
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,
|
true,
|
||||||
&Data::UnavailableReason::sensitive);
|
&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().
|
// This is duplicated in CanPinMessagesValue().
|
||||||
|
|
|
@ -99,6 +99,8 @@ struct UnavailableReason {
|
||||||
[[nodiscard]] static QString Compute(
|
[[nodiscard]] static QString Compute(
|
||||||
not_null<Main::Session*> session,
|
not_null<Main::Session*> session,
|
||||||
const std::vector<UnavailableReason> &list);
|
const std::vector<UnavailableReason> &list);
|
||||||
|
[[nodiscard]] static bool IgnoreSensitiveMark(
|
||||||
|
not_null<Main::Session*> session);
|
||||||
|
|
||||||
[[nodiscard]] static std::vector<UnavailableReason> Extract(
|
[[nodiscard]] static std::vector<UnavailableReason> Extract(
|
||||||
const MTPvector<MTPRestrictionReason> *list);
|
const MTPvector<MTPRestrictionReason> *list);
|
||||||
|
@ -347,7 +349,9 @@ public:
|
||||||
// If this string is not empty we must not allow to open the
|
// If this string is not empty we must not allow to open the
|
||||||
// conversation and we must show this string instead.
|
// conversation and we must show this string instead.
|
||||||
[[nodiscard]] QString computeUnavailableReason() const;
|
[[nodiscard]] QString computeUnavailableReason() const;
|
||||||
[[nodiscard]] bool isUnavailableSensitive() const;
|
[[nodiscard]] bool hasSensitiveContent() const;
|
||||||
|
void setUnavailableReasons(
|
||||||
|
std::vector<Data::UnavailableReason> &&reason);
|
||||||
|
|
||||||
[[nodiscard]] ClickHandlerPtr createOpenLink();
|
[[nodiscard]] ClickHandlerPtr createOpenLink();
|
||||||
[[nodiscard]] const ClickHandlerPtr &openLink() {
|
[[nodiscard]] const ClickHandlerPtr &openLink() {
|
||||||
|
@ -489,6 +493,10 @@ private:
|
||||||
const ImageLocation &location,
|
const ImageLocation &location,
|
||||||
bool hasVideo);
|
bool hasVideo);
|
||||||
|
|
||||||
|
virtual void setUnavailableReasonsList(
|
||||||
|
std::vector<Data::UnavailableReason> &&reasons);
|
||||||
|
void setHasSensitiveContent(bool has);
|
||||||
|
|
||||||
const not_null<Data::Session*> _owner;
|
const not_null<Data::Session*> _owner;
|
||||||
|
|
||||||
mutable Data::CloudImage _userpic;
|
mutable Data::CloudImage _userpic;
|
||||||
|
@ -507,7 +515,8 @@ private:
|
||||||
crl::time _lastFullUpdate = 0;
|
crl::time _lastFullUpdate = 0;
|
||||||
|
|
||||||
QString _name;
|
QString _name;
|
||||||
uint32 _nameVersion : 31 = 1;
|
uint32 _nameVersion : 30 = 1;
|
||||||
|
uint32 _sensitiveContent : 1 = 0;
|
||||||
uint32 _wallPaperOverriden : 1 = 0;
|
uint32 _wallPaperOverriden : 1 = 0;
|
||||||
|
|
||||||
TimeId _ttlPeriod = 0;
|
TimeId _ttlPeriod = 0;
|
||||||
|
|
|
@ -252,8 +252,7 @@ Image *PhotoData::getReplyPreview(
|
||||||
|
|
||||||
Image *PhotoData::getReplyPreview(not_null<HistoryItem*> item) {
|
Image *PhotoData::getReplyPreview(not_null<HistoryItem*> item) {
|
||||||
const auto media = item->media();
|
const auto media = item->media();
|
||||||
const auto spoiler = (media && media->hasSpoiler())
|
const auto spoiler = (media && media->hasSpoiler());
|
||||||
|| item->hasSensitiveSpoiler();
|
|
||||||
return getReplyPreview(item->fullId(), item->history()->peer, spoiler);
|
return getReplyPreview(item->fullId(), item->history()->peer, spoiler);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -311,6 +311,22 @@ Session::Session(not_null<Main::Session*> session)
|
||||||
|
|
||||||
_stories->loadMore(Data::StorySourcesList::NotHidden);
|
_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() {
|
void Session::subscribeForTopicRepliesLists() {
|
||||||
|
@ -1773,7 +1789,7 @@ rpl::producer<not_null<ViewElement*>> Session::viewResizeRequest() const {
|
||||||
return _viewResizeRequest.events();
|
return _viewResizeRequest.events();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Session::requestItemViewRefresh(not_null<HistoryItem*> item) {
|
void Session::requestItemViewRefresh(not_null<const HistoryItem*> item) {
|
||||||
if (const auto view = item->mainView()) {
|
if (const auto view = item->mainView()) {
|
||||||
notifyHistoryChangeDelayed(item->history());
|
notifyHistoryChangeDelayed(item->history());
|
||||||
view->refreshInBlock();
|
view->refreshInBlock();
|
||||||
|
@ -1781,7 +1797,7 @@ void Session::requestItemViewRefresh(not_null<HistoryItem*> item) {
|
||||||
_itemViewRefreshRequest.fire_copy(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();
|
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(
|
void Session::registerHighlightProcess(
|
||||||
uint64 processId,
|
uint64 processId,
|
||||||
not_null<HistoryItem*> item) {
|
not_null<HistoryItem*> item) {
|
||||||
|
@ -2510,6 +2551,9 @@ void Session::unregisterMessage(not_null<HistoryItem*> item) {
|
||||||
const auto peerId = item->history()->peer->id;
|
const auto peerId = item->history()->peer->id;
|
||||||
const auto itemId = item->id;
|
const auto itemId = item->id;
|
||||||
_itemRemoved.fire_copy(item);
|
_itemRemoved.fire_copy(item);
|
||||||
|
if (item->hasPossibleRestrictions()) {
|
||||||
|
_possiblyRestricted.remove(item);
|
||||||
|
}
|
||||||
session().changes().messageUpdated(
|
session().changes().messageUpdated(
|
||||||
item,
|
item,
|
||||||
Data::MessageUpdate::Flag::Destroyed);
|
Data::MessageUpdate::Flag::Destroyed);
|
||||||
|
|
|
@ -69,6 +69,7 @@ class SavedMessages;
|
||||||
class Chatbots;
|
class Chatbots;
|
||||||
class BusinessInfo;
|
class BusinessInfo;
|
||||||
struct ReactionId;
|
struct ReactionId;
|
||||||
|
struct UnavailableReason;
|
||||||
|
|
||||||
struct RepliesReadTillUpdate {
|
struct RepliesReadTillUpdate {
|
||||||
FullMsgId id;
|
FullMsgId id;
|
||||||
|
@ -288,8 +289,8 @@ public:
|
||||||
[[nodiscard]] rpl::producer<not_null<const HistoryItem*>> itemResizeRequest() const;
|
[[nodiscard]] rpl::producer<not_null<const HistoryItem*>> itemResizeRequest() const;
|
||||||
void requestViewResize(not_null<ViewElement*> view);
|
void requestViewResize(not_null<ViewElement*> view);
|
||||||
[[nodiscard]] rpl::producer<not_null<ViewElement*>> viewResizeRequest() const;
|
[[nodiscard]] rpl::producer<not_null<ViewElement*>> viewResizeRequest() const;
|
||||||
void requestItemViewRefresh(not_null<HistoryItem*> item);
|
void requestItemViewRefresh(not_null<const HistoryItem*> item);
|
||||||
[[nodiscard]] rpl::producer<not_null<HistoryItem*>> itemViewRefreshRequest() const;
|
[[nodiscard]] rpl::producer<not_null<const HistoryItem*>> itemViewRefreshRequest() const;
|
||||||
void requestItemTextRefresh(not_null<HistoryItem*> item);
|
void requestItemTextRefresh(not_null<HistoryItem*> item);
|
||||||
void requestUnreadReactionsAnimation(not_null<HistoryItem*> item);
|
void requestUnreadReactionsAnimation(not_null<HistoryItem*> item);
|
||||||
void notifyHistoryUnloaded(not_null<const History*> history);
|
void notifyHistoryUnloaded(not_null<const History*> history);
|
||||||
|
@ -313,6 +314,13 @@ public:
|
||||||
void notifyPinnedDialogsOrderUpdated();
|
void notifyPinnedDialogsOrderUpdated();
|
||||||
[[nodiscard]] rpl::producer<> pinnedDialogsOrderUpdated() const;
|
[[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(
|
void registerHighlightProcess(
|
||||||
uint64 processId,
|
uint64 processId,
|
||||||
not_null<HistoryItem*> item);
|
not_null<HistoryItem*> item);
|
||||||
|
@ -920,7 +928,7 @@ private:
|
||||||
rpl::event_stream<not_null<const ViewElement*>> _viewRepaintRequest;
|
rpl::event_stream<not_null<const ViewElement*>> _viewRepaintRequest;
|
||||||
rpl::event_stream<not_null<const HistoryItem*>> _itemResizeRequest;
|
rpl::event_stream<not_null<const HistoryItem*>> _itemResizeRequest;
|
||||||
rpl::event_stream<not_null<ViewElement*>> _viewResizeRequest;
|
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*>> _itemTextRefreshRequest;
|
||||||
rpl::event_stream<not_null<HistoryItem*>> _itemDataChanges;
|
rpl::event_stream<not_null<HistoryItem*>> _itemDataChanges;
|
||||||
rpl::event_stream<not_null<const HistoryItem*>> _itemRemoved;
|
rpl::event_stream<not_null<const HistoryItem*>> _itemRemoved;
|
||||||
|
@ -1021,6 +1029,10 @@ private:
|
||||||
base::flat_multi_map<TimeId, not_null<PollData*>> _pollsClosings;
|
base::flat_multi_map<TimeId, not_null<PollData*>> _pollsClosings;
|
||||||
base::Timer _pollsClosingTimer;
|
base::Timer _pollsClosingTimer;
|
||||||
|
|
||||||
|
base::flat_map<
|
||||||
|
not_null<const HistoryItem*>,
|
||||||
|
base::flat_set<QString>> _possiblyRestricted;
|
||||||
|
|
||||||
base::flat_map<FolderId, std::unique_ptr<Folder>> _folders;
|
base::flat_map<FolderId, std::unique_ptr<Folder>> _folders;
|
||||||
|
|
||||||
std::unordered_map<
|
std::unordered_map<
|
||||||
|
|
|
@ -326,7 +326,7 @@ enum class MessageFlag : uint64 {
|
||||||
EffectWatched = (1ULL << 46),
|
EffectWatched = (1ULL << 46),
|
||||||
|
|
||||||
SensitiveContent = (1ULL << 47),
|
SensitiveContent = (1ULL << 47),
|
||||||
AllowSensitive = (1ULL << 48),
|
HasRestrictions = (1ULL << 48),
|
||||||
};
|
};
|
||||||
inline constexpr bool is_flag_type(MessageFlag) { return true; }
|
inline constexpr bool is_flag_type(MessageFlag) { return true; }
|
||||||
using MessageFlags = base::flags<MessageFlag>;
|
using MessageFlags = base::flags<MessageFlag>;
|
||||||
|
|
|
@ -7,6 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
*/
|
*/
|
||||||
#include "data/data_user.h"
|
#include "data/data_user.h"
|
||||||
|
|
||||||
|
#include "api/api_sensitive_content.h"
|
||||||
#include "storage/localstorage.h"
|
#include "storage/localstorage.h"
|
||||||
#include "storage/storage_user_photos.h"
|
#include "storage/storage_user_photos.h"
|
||||||
#include "main/main_session.h"
|
#include "main/main_session.h"
|
||||||
|
@ -117,14 +118,9 @@ auto UserData::unavailableReasons() const
|
||||||
return _unavailableReasons;
|
return _unavailableReasons;
|
||||||
}
|
}
|
||||||
|
|
||||||
void UserData::setUnavailableReasons(
|
void UserData::setUnavailableReasonsList(
|
||||||
std::vector<Data::UnavailableReason> &&reasons) {
|
std::vector<Data::UnavailableReason> &&reasons) {
|
||||||
if (_unavailableReasons != reasons) {
|
_unavailableReasons = std::move(reasons);
|
||||||
_unavailableReasons = std::move(reasons);
|
|
||||||
session().changes().peerUpdated(
|
|
||||||
this,
|
|
||||||
UpdateFlag::UnavailableReason);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void UserData::setCommonChatsCount(int count) {
|
void UserData::setCommonChatsCount(int count) {
|
||||||
|
@ -516,6 +512,10 @@ void UserData::setBirthday(Data::Birthday value) {
|
||||||
if (_birthday != value) {
|
if (_birthday != value) {
|
||||||
_birthday = value;
|
_birthday = value;
|
||||||
session().changes().peerUpdated(this, UpdateFlag::Birthday);
|
session().changes().peerUpdated(this, UpdateFlag::Birthday);
|
||||||
|
|
||||||
|
if (isSelf()) {
|
||||||
|
session().api().sensitiveContent().reload(true);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -185,9 +185,6 @@ public:
|
||||||
void setBirthday(Data::Birthday value);
|
void setBirthday(Data::Birthday value);
|
||||||
void setBirthday(const tl::conditional<MTPBirthday> &value);
|
void setBirthday(const tl::conditional<MTPBirthday> &value);
|
||||||
|
|
||||||
void setUnavailableReasons(
|
|
||||||
std::vector<Data::UnavailableReason> &&reasons);
|
|
||||||
|
|
||||||
int commonChatsCount() const;
|
int commonChatsCount() const;
|
||||||
void setCommonChatsCount(int count);
|
void setCommonChatsCount(int count);
|
||||||
|
|
||||||
|
@ -218,6 +215,9 @@ private:
|
||||||
auto unavailableReasons() const
|
auto unavailableReasons() const
|
||||||
-> const std::vector<Data::UnavailableReason> & override;
|
-> const std::vector<Data::UnavailableReason> & override;
|
||||||
|
|
||||||
|
void setUnavailableReasonsList(
|
||||||
|
std::vector<Data::UnavailableReason> &&reasons) override;
|
||||||
|
|
||||||
Flags _flags;
|
Flags _flags;
|
||||||
Data::LastseenStatus _lastseen;
|
Data::LastseenStatus _lastseen;
|
||||||
Data::Birthday _birthday;
|
Data::Birthday _birthday;
|
||||||
|
|
|
@ -7,6 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
*/
|
*/
|
||||||
#include "history/history_item.h"
|
#include "history/history_item.h"
|
||||||
|
|
||||||
|
#include "api/api_sensitive_content.h"
|
||||||
#include "lang/lang_keys.h"
|
#include "lang/lang_keys.h"
|
||||||
#include "mainwidget.h"
|
#include "mainwidget.h"
|
||||||
#include "calls/calls_instance.h" // Core::App().calls().joinGroupCall.
|
#include "calls/calls_instance.h" // Core::App().calls().joinGroupCall.
|
||||||
|
@ -3356,6 +3357,8 @@ EffectId HistoryItem::effectId() const {
|
||||||
|
|
||||||
QString HistoryItem::computeUnavailableReason() const {
|
QString HistoryItem::computeUnavailableReason() const {
|
||||||
if (const auto restrictions = Get<HistoryMessageRestrictions>()) {
|
if (const auto restrictions = Get<HistoryMessageRestrictions>()) {
|
||||||
|
_flags |= MessageFlag::HasRestrictions;
|
||||||
|
_history->owner().registerRestricted(this, restrictions->reasons);
|
||||||
return Data::UnavailableReason::Compute(
|
return Data::UnavailableReason::Compute(
|
||||||
&history()->session(),
|
&history()->session(),
|
||||||
restrictions->reasons);
|
restrictions->reasons);
|
||||||
|
@ -3363,13 +3366,19 @@ QString HistoryItem::computeUnavailableReason() const {
|
||||||
return QString();
|
return QString();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool HistoryItem::hasSensitiveSpoiler() const {
|
bool HistoryItem::isMediaSensitive() const {
|
||||||
return (_flags & MessageFlag::SensitiveContent)
|
if (!(_flags & MessageFlag::SensitiveContent)
|
||||||
&& !(_flags & MessageFlag::AllowSensitive);
|
&& !_history->peer->hasSensitiveContent()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
_flags |= MessageFlag::HasRestrictions;
|
||||||
|
_history->owner().registerRestricted(this, u"sensitive"_q);
|
||||||
|
return !Data::UnavailableReason::IgnoreSensitiveMark(
|
||||||
|
&_history->session());
|
||||||
}
|
}
|
||||||
|
|
||||||
void HistoryItem::allowSensitive() {
|
bool HistoryItem::hasPossibleRestrictions() const {
|
||||||
_flags |= MessageFlag::AllowSensitive;
|
return _flags & MessageFlag::HasRestrictions;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool HistoryItem::isEmpty() const {
|
bool HistoryItem::isEmpty() const {
|
||||||
|
@ -3666,10 +3675,10 @@ void HistoryItem::createComponents(CreateConfig &&config) {
|
||||||
&Data::UnavailableReason::sensitive);
|
&Data::UnavailableReason::sensitive);
|
||||||
if (i != end(restrictions->reasons)) {
|
if (i != end(restrictions->reasons)) {
|
||||||
restrictions->reasons.erase(i);
|
restrictions->reasons.erase(i);
|
||||||
_flags |= MessageFlag::SensitiveContent;
|
flagSensitiveContent();
|
||||||
}
|
}
|
||||||
} else if (!config.restrictions.empty()) {
|
} else if (!config.restrictions.empty()) {
|
||||||
_flags |= MessageFlag::SensitiveContent;
|
flagSensitiveContent();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (out() && isSending()) {
|
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(
|
bool HistoryItem::checkRepliesPts(
|
||||||
const HistoryMessageRepliesData &data) const {
|
const HistoryMessageRepliesData &data) const {
|
||||||
const auto channel = _history->peer->asChannel();
|
const auto channel = _history->peer->asChannel();
|
||||||
|
|
|
@ -522,9 +522,9 @@ public:
|
||||||
[[nodiscard]] bool isEmpty() const;
|
[[nodiscard]] bool isEmpty() const;
|
||||||
[[nodiscard]] MessageGroupId groupId() const;
|
[[nodiscard]] MessageGroupId groupId() const;
|
||||||
[[nodiscard]] EffectId effectId() const;
|
[[nodiscard]] EffectId effectId() const;
|
||||||
|
[[nodiscard]] bool hasPossibleRestrictions() const;
|
||||||
[[nodiscard]] QString computeUnavailableReason() const;
|
[[nodiscard]] QString computeUnavailableReason() const;
|
||||||
[[nodiscard]] bool hasSensitiveSpoiler() const;
|
[[nodiscard]] bool isMediaSensitive() const;
|
||||||
void allowSensitive();
|
|
||||||
|
|
||||||
[[nodiscard]] const HistoryMessageReplyMarkup *inlineReplyMarkup() const {
|
[[nodiscard]] const HistoryMessageReplyMarkup *inlineReplyMarkup() const {
|
||||||
return const_cast<HistoryItem*>(this)->inlineReplyMarkup();
|
return const_cast<HistoryItem*>(this)->inlineReplyMarkup();
|
||||||
|
@ -660,6 +660,7 @@ private:
|
||||||
[[nodiscard]] PreparedServiceText prepareCallScheduledText(
|
[[nodiscard]] PreparedServiceText prepareCallScheduledText(
|
||||||
TimeId scheduleDate);
|
TimeId scheduleDate);
|
||||||
|
|
||||||
|
void flagSensitiveContent();
|
||||||
[[nodiscard]] PeerData *computeDisplayFrom() const;
|
[[nodiscard]] PeerData *computeDisplayFrom() const;
|
||||||
|
|
||||||
const not_null<History*> _history;
|
const not_null<History*> _history;
|
||||||
|
|
|
@ -736,6 +736,10 @@ void Element::refreshMedia(Element *replacing) {
|
||||||
_flags &= ~Flag::HiddenByGroup;
|
_flags &= ~Flag::HiddenByGroup;
|
||||||
|
|
||||||
const auto item = data();
|
const auto item = data();
|
||||||
|
if (!item->computeUnavailableReason().isEmpty()) {
|
||||||
|
_media = nullptr;
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (const auto media = item->media()) {
|
if (const auto media = item->media()) {
|
||||||
if (media->canBeGrouped()) {
|
if (media->canBeGrouped()) {
|
||||||
if (const auto group = history()->owner().groups().find(item)) {
|
if (const auto group = history()->owner().groups().find(item)) {
|
||||||
|
@ -1004,7 +1008,12 @@ void Element::validateText() {
|
||||||
: contextDependentText.links;
|
: contextDependentText.links;
|
||||||
setTextWithLinks(markedText, customLinks);
|
setTextWithLinks(markedText, customLinks);
|
||||||
} else {
|
} else {
|
||||||
setTextWithLinks(_textItem->translatedTextWithLocalEntities());
|
const auto unavailable = item->computeUnavailableReason();
|
||||||
|
if (!unavailable.isEmpty()) {
|
||||||
|
setTextWithLinks(Ui::Text::Italic(unavailable));
|
||||||
|
} else {
|
||||||
|
setTextWithLinks(_textItem->translatedTextWithLocalEntities());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -135,10 +135,13 @@ Gif::Gif(
|
||||||
, _storyId(realParent->media()
|
, _storyId(realParent->media()
|
||||||
? realParent->media()->storyId()
|
? realParent->media()->storyId()
|
||||||
: FullStoryId())
|
: FullStoryId())
|
||||||
, _spoiler((spoiler || IsHiddenRoundMessage(_parent))
|
, _spoiler((spoiler
|
||||||
|
|| IsHiddenRoundMessage(_parent)
|
||||||
|
|| realParent->isMediaSensitive())
|
||||||
? std::make_unique<MediaSpoiler>()
|
? std::make_unique<MediaSpoiler>()
|
||||||
: nullptr)
|
: nullptr)
|
||||||
, _downloadSize(Ui::FormatSizeText(_data->size)) {
|
, _downloadSize(Ui::FormatSizeText(_data->size))
|
||||||
|
, _sensitiveSpoiler(realParent->isMediaSensitive()) {
|
||||||
if (_data->isVideoMessage() && _parent->data()->media()->ttlSeconds()) {
|
if (_data->isVideoMessage() && _parent->data()->media()->ttlSeconds()) {
|
||||||
if (_spoiler) {
|
if (_spoiler) {
|
||||||
_drawTtl = CreateTtlPaintCallback([=] { repaint(); });
|
_drawTtl = CreateTtlPaintCallback([=] { repaint(); });
|
||||||
|
@ -582,9 +585,11 @@ void Gif::draw(Painter &p, const PaintContext &context) const {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (radial
|
const auto paintInCenter = !_sensitiveSpoiler
|
||||||
|| (!streamingMode
|
&& (radial
|
||||||
&& ((!loaded && !_data->loading()) || !autoplay))) {
|
|| (!streamingMode
|
||||||
|
&& ((!loaded && !_data->loading()) || !autoplay)));
|
||||||
|
if (paintInCenter) {
|
||||||
const auto radialRevealed = 1.;
|
const auto radialRevealed = 1.;
|
||||||
const auto opacity = (item->isSending() || _data->uploading())
|
const auto opacity = (item->isSending() || _data->uploading())
|
||||||
? 1.
|
? 1.
|
||||||
|
@ -652,6 +657,10 @@ void Gif::draw(Painter &p, const PaintContext &context) const {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
p.setOpacity(1.);
|
p.setOpacity(1.);
|
||||||
|
} else if (_sensitiveSpoiler) {
|
||||||
|
drawSpoilerTag(p, rthumb, context, [&] {
|
||||||
|
return spoilerTagBackground();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
if (displayMute) {
|
if (displayMute) {
|
||||||
auto muteRect = style::rtlrect(rthumb.x() + (rthumb.width() - st::historyVideoMessageMuteSize) / 2, rthumb.y() + st::msgDateImgDelta, st::historyVideoMessageMuteSize, st::historyVideoMessageMuteSize, width());
|
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);
|
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 {
|
void Gif::validateVideoThumbnail() const {
|
||||||
const auto content = _dataMedia->videoThumbnailContent();
|
const auto content = _dataMedia->videoThumbnailContent();
|
||||||
if (_videoThumbnailFrame || content.isEmpty()) {
|
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)) {
|
if (QRect(usex + paintx, painty, usew, painth).contains(point)) {
|
||||||
ensureDataMediaCreated();
|
ensureDataMediaCreated();
|
||||||
result.link = (isRound && _parent->data()->media()->ttlSeconds())
|
result.link = (_spoiler && !_spoiler->revealed)
|
||||||
? _openl // Overriden.
|
? (_sensitiveSpoiler
|
||||||
: (_spoiler && !_spoiler->revealed)
|
? spoilerTagLink()
|
||||||
? _spoiler->link
|
: (isRound && _parent->data()->media()->ttlSeconds())
|
||||||
|
? _openl // Overriden.
|
||||||
|
: _spoiler->link)
|
||||||
: _data->uploading()
|
: _data->uploading()
|
||||||
? _cancell
|
? _cancell
|
||||||
: _realParent->isSending()
|
: _realParent->isSending()
|
||||||
|
@ -1335,9 +1368,11 @@ void Gif::drawGrouped(
|
||||||
p.setOpacity(1.);
|
p.setOpacity(1.);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (radial
|
const auto paintInCenter = !_sensitiveSpoiler
|
||||||
|| (!streamingMode
|
&& (radial
|
||||||
&& ((!loaded && !_data->loading()) || !autoplay))) {
|
|| (!streamingMode
|
||||||
|
&& ((!loaded && !_data->loading()) || !autoplay)));
|
||||||
|
if (paintInCenter) {
|
||||||
const auto radialRevealed = 1.;
|
const auto radialRevealed = 1.;
|
||||||
const auto opacity = (item->isSending() || _data->uploading())
|
const auto opacity = (item->isSending() || _data->uploading())
|
||||||
? 1.
|
? 1.
|
||||||
|
@ -1439,8 +1474,8 @@ TextState Gif::getStateGrouped(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ensureDataMediaCreated();
|
ensureDataMediaCreated();
|
||||||
return TextState(_parent, (_spoiler && !_spoiler->revealed)
|
auto link = (_spoiler && !_spoiler->revealed)
|
||||||
? _spoiler->link
|
? (_sensitiveSpoiler ? spoilerTagLink() : _spoiler->link)
|
||||||
: _data->uploading()
|
: _data->uploading()
|
||||||
? _cancell
|
? _cancell
|
||||||
: _realParent->isSending()
|
: _realParent->isSending()
|
||||||
|
@ -1449,7 +1484,8 @@ TextState Gif::getStateGrouped(
|
||||||
? _openl
|
? _openl
|
||||||
: _data->loading()
|
: _data->loading()
|
||||||
? _cancell
|
? _cancell
|
||||||
: _savel);
|
: _savel;
|
||||||
|
return TextState(_parent, std::move(link));
|
||||||
}
|
}
|
||||||
|
|
||||||
void Gif::ensureDataMediaCreated() const {
|
void Gif::ensureDataMediaCreated() const {
|
||||||
|
|
|
@ -90,6 +90,14 @@ public:
|
||||||
void stopAnimation() override;
|
void stopAnimation() override;
|
||||||
void checkAnimation() 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;
|
void hideSpoilers() override;
|
||||||
bool needsBubble() const override;
|
bool needsBubble() const override;
|
||||||
bool unwrapped() const override;
|
bool unwrapped() const override;
|
||||||
|
@ -203,6 +211,7 @@ private:
|
||||||
const FullStoryId _storyId;
|
const FullStoryId _storyId;
|
||||||
std::unique_ptr<Streamed> _streamed;
|
std::unique_ptr<Streamed> _streamed;
|
||||||
const std::unique_ptr<MediaSpoiler> _spoiler;
|
const std::unique_ptr<MediaSpoiler> _spoiler;
|
||||||
|
mutable std::unique_ptr<MediaSpoilerTag> _spoilerTag;
|
||||||
mutable std::unique_ptr<TranscribeButton> _transcribe;
|
mutable std::unique_ptr<TranscribeButton> _transcribe;
|
||||||
mutable std::shared_ptr<Data::DocumentMedia> _dataMedia;
|
mutable std::shared_ptr<Data::DocumentMedia> _dataMedia;
|
||||||
mutable std::unique_ptr<Image> _videoThumbnailFrame;
|
mutable std::unique_ptr<Image> _videoThumbnailFrame;
|
||||||
|
@ -214,6 +223,7 @@ private:
|
||||||
mutable bool _thumbIsEllipse : 1 = false;
|
mutable bool _thumbIsEllipse : 1 = false;
|
||||||
mutable bool _pollingStory : 1 = false;
|
mutable bool _pollingStory : 1 = false;
|
||||||
mutable bool _purchasedPriceTag : 1 = false;
|
mutable bool _purchasedPriceTag : 1 = false;
|
||||||
|
const bool _sensitiveSpoiler : 1 = false;
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -13,13 +13,14 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include "history/view/history_view_element.h"
|
#include "history/view/history_view_element.h"
|
||||||
#include "history/view/history_view_cursor_state.h"
|
#include "history/view/history_view_cursor_state.h"
|
||||||
#include "history/view/history_view_text_helper.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_media_spoiler.h"
|
||||||
|
#include "history/view/media/history_view_sticker.h"
|
||||||
#include "storage/storage_shared_media.h"
|
#include "storage/storage_shared_media.h"
|
||||||
#include "data/data_document.h"
|
#include "data/data_document.h"
|
||||||
#include "data/data_session.h"
|
#include "data/data_session.h"
|
||||||
#include "data/data_web_page.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/item_text_options.h"
|
||||||
#include "ui/chat/chat_style.h"
|
#include "ui/chat/chat_style.h"
|
||||||
#include "ui/chat/message_bubble.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 "ui/text/text_utilities.h"
|
||||||
#include "core/ui_integration.h"
|
#include "core/ui_integration.h"
|
||||||
#include "styles/style_chat.h"
|
#include "styles/style_chat.h"
|
||||||
|
#include "styles/style_chat_helpers.h"
|
||||||
|
#include "styles/style_menu_icons.h" // mediaMenuIconStealth.
|
||||||
|
|
||||||
namespace HistoryView {
|
namespace HistoryView {
|
||||||
namespace {
|
namespace {
|
||||||
|
@ -351,6 +354,156 @@ void Media::fillImageSpoiler(
|
||||||
spoiler->cornerCache);
|
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) {
|
void Media::createSpoilerLink(not_null<MediaSpoiler*> spoiler) {
|
||||||
const auto weak = base::make_weak(this);
|
const auto weak = base::make_weak(this);
|
||||||
spoiler->link = std::make_shared<LambdaClickHandler>([weak, spoiler](
|
spoiler->link = std::make_shared<LambdaClickHandler>([weak, spoiler](
|
||||||
|
|
|
@ -47,6 +47,7 @@ enum class InfoDisplayType : char;
|
||||||
struct TextState;
|
struct TextState;
|
||||||
struct StateRequest;
|
struct StateRequest;
|
||||||
struct MediaSpoiler;
|
struct MediaSpoiler;
|
||||||
|
struct MediaSpoilerTag;
|
||||||
class StickerPlayer;
|
class StickerPlayer;
|
||||||
class Element;
|
class Element;
|
||||||
struct SelectedQuote;
|
struct SelectedQuote;
|
||||||
|
@ -223,18 +224,18 @@ public:
|
||||||
QPoint point,
|
QPoint point,
|
||||||
StateRequest request) const;
|
StateRequest request) const;
|
||||||
|
|
||||||
virtual void drawPriceTag(
|
virtual void drawSpoilerTag(
|
||||||
Painter &p,
|
Painter &p,
|
||||||
QRect rthumb,
|
QRect rthumb,
|
||||||
const PaintContext &context,
|
const PaintContext &context,
|
||||||
Fn<QImage()> generateBackground) const {
|
Fn<QImage()> generateBackground) const {
|
||||||
Unexpected("Price tag method call.");
|
Unexpected("Spoiler tag method call.");
|
||||||
}
|
}
|
||||||
[[nodiscard]] virtual ClickHandlerPtr priceTagLink() const {
|
[[nodiscard]] virtual ClickHandlerPtr spoilerTagLink() const {
|
||||||
Unexpected("Price tag method call.");
|
Unexpected("Spoiler tag method call.");
|
||||||
}
|
}
|
||||||
[[nodiscard]] virtual QImage priceTagBackground() const {
|
[[nodiscard]] virtual QImage spoilerTagBackground() const {
|
||||||
Unexpected("Price tag method call.");
|
Unexpected("Spoiler tag method call.");
|
||||||
}
|
}
|
||||||
|
|
||||||
[[nodiscard]] virtual bool animating() const {
|
[[nodiscard]] virtual bool animating() const {
|
||||||
|
@ -390,6 +391,17 @@ protected:
|
||||||
not_null<MediaSpoiler*> spoiler,
|
not_null<MediaSpoiler*> spoiler,
|
||||||
QRect rect,
|
QRect rect,
|
||||||
const PaintContext &context) const;
|
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 createSpoilerLink(not_null<MediaSpoiler*> spoiler);
|
||||||
|
|
||||||
void repaint() const;
|
void repaint() const;
|
||||||
|
|
|
@ -7,10 +7,16 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
*/
|
*/
|
||||||
#include "history/view/media/history_view_media_common.h"
|
#include "history/view/media/history_view_media_common.h"
|
||||||
|
|
||||||
|
#include "api/api_sensitive_content.h"
|
||||||
#include "api/api_views.h"
|
#include "api/api_views.h"
|
||||||
#include "apiwrap.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/format_values.h"
|
||||||
#include "ui/text/text_utilities.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 "ui/painter.h"
|
||||||
#include "core/click_handler_types.h"
|
#include "core/click_handler_types.h"
|
||||||
#include "data/data_document.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
|
} // namespace HistoryView
|
||||||
|
|
|
@ -75,6 +75,10 @@ void PaintInterpolatedIcon(
|
||||||
int newWidth,
|
int newWidth,
|
||||||
int maxWidth);
|
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
|
} // namespace HistoryView
|
||||||
|
|
|
@ -297,16 +297,19 @@ QMargins GroupedMedia::groupedPadding() const {
|
||||||
(normal.bottom() - grouped.bottom()) + addToBottom);
|
(normal.bottom() - grouped.bottom()) + addToBottom);
|
||||||
}
|
}
|
||||||
|
|
||||||
Media *GroupedMedia::lookupUnpaidMedia() const {
|
Media *GroupedMedia::lookupSpoilerTagMedia() const {
|
||||||
if (_parts.empty()) {
|
if (_parts.empty()) {
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
const auto media = _parts.front().content.get();
|
const auto media = _parts.front().content.get();
|
||||||
|
if (media && _parts.front().item->isMediaSensitive()) {
|
||||||
|
return media;
|
||||||
|
}
|
||||||
const auto photo = media ? media->getPhoto() : nullptr;
|
const auto photo = media ? media->getPhoto() : nullptr;
|
||||||
return (photo && photo->extendedMediaPreview()) ? media : nullptr;
|
return (photo && photo->extendedMediaPreview()) ? media : nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
QImage GroupedMedia::generatePriceTagBackground(QRect full) const {
|
QImage GroupedMedia::generateSpoilerTagBackground(QRect full) const {
|
||||||
const auto ratio = style::DevicePixelRatio();
|
const auto ratio = style::DevicePixelRatio();
|
||||||
auto result = QImage(
|
auto result = QImage(
|
||||||
full.size() * ratio,
|
full.size() * ratio,
|
||||||
|
@ -317,7 +320,7 @@ QImage GroupedMedia::generatePriceTagBackground(QRect full) const {
|
||||||
const auto skip1 = st::historyGroupSkip / 2;
|
const auto skip1 = st::historyGroupSkip / 2;
|
||||||
const auto skip2 = st::historyGroupSkip - skip1;
|
const auto skip2 = st::historyGroupSkip - skip1;
|
||||||
for (const auto &part : _parts) {
|
for (const auto &part : _parts) {
|
||||||
auto background = part.content->priceTagBackground();
|
auto background = part.content->spoilerTagBackground();
|
||||||
const auto extended = part.geometry.translated(shift).marginsAdded(
|
const auto extended = part.geometry.translated(shift).marginsAdded(
|
||||||
{ skip1, skip1, skip2, skip2 });
|
{ skip1, skip1, skip2, skip2 });
|
||||||
if (background.isNull()) {
|
if (background.isNull()) {
|
||||||
|
@ -394,7 +397,7 @@ void GroupedMedia::draw(Painter &p, const PaintContext &context) const {
|
||||||
? Ui::BubbleRounding{ kSmall, kSmall, kSmall, kSmall }
|
? Ui::BubbleRounding{ kSmall, kSmall, kSmall, kSmall }
|
||||||
: adjustedBubbleRounding();
|
: adjustedBubbleRounding();
|
||||||
auto highlight = context.highlight.range;
|
auto highlight = context.highlight.range;
|
||||||
const auto unpaid = lookupUnpaidMedia();
|
const auto tagged = lookupSpoilerTagMedia();
|
||||||
auto fullRect = QRect();
|
auto fullRect = QRect();
|
||||||
const auto subpartHighlight = IsSubGroupSelection(highlight);
|
const auto subpartHighlight = IsSubGroupSelection(highlight);
|
||||||
for (auto i = 0, count = int(_parts.size()); i != count; ++i) {
|
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()) {
|
if (!part.cache.isNull()) {
|
||||||
nowCache = true;
|
nowCache = true;
|
||||||
}
|
}
|
||||||
if (unpaid || _purchasedPriceTag) {
|
if (tagged || _purchasedPriceTag) {
|
||||||
fullRect = fullRect.united(part.geometry);
|
fullRect = fullRect.united(part.geometry);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -443,9 +446,9 @@ void GroupedMedia::draw(Painter &p, const PaintContext &context) const {
|
||||||
history()->owner().registerHeavyViewPart(_parent);
|
history()->owner().registerHeavyViewPart(_parent);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (unpaid) {
|
if (tagged) {
|
||||||
unpaid->drawPriceTag(p, fullRect, context, [&] {
|
tagged->drawSpoilerTag(p, fullRect, context, [&] {
|
||||||
return generatePriceTagBackground(fullRect);
|
return generateSpoilerTagBackground(fullRect);
|
||||||
});
|
});
|
||||||
} else if (_purchasedPriceTag) {
|
} else if (_purchasedPriceTag) {
|
||||||
drawPurchasedTag(p, fullRect, context);
|
drawPurchasedTag(p, fullRect, context);
|
||||||
|
@ -511,9 +514,11 @@ PointState GroupedMedia::pointState(QPoint point) const {
|
||||||
TextState GroupedMedia::textState(QPoint point, StateRequest request) const {
|
TextState GroupedMedia::textState(QPoint point, StateRequest request) const {
|
||||||
const auto groupPadding = groupedPadding();
|
const auto groupPadding = groupedPadding();
|
||||||
auto result = getPartState(point - QPoint(0, groupPadding.top()), request);
|
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)) {
|
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())) {
|
if (_parent->media() == this && (!_parent->hasBubble() || isBubbleBottom())) {
|
||||||
|
|
|
@ -149,8 +149,8 @@ private:
|
||||||
RectParts sides) const;
|
RectParts sides) const;
|
||||||
[[nodiscard]] QMargins groupedPadding() const;
|
[[nodiscard]] QMargins groupedPadding() const;
|
||||||
|
|
||||||
[[nodiscard]] Media *lookupUnpaidMedia() const;
|
[[nodiscard]] Media *lookupSpoilerTagMedia() const;
|
||||||
[[nodiscard]] QImage generatePriceTagBackground(QRect full) const;
|
[[nodiscard]] QImage generateSpoilerTagBackground(QRect full) const;
|
||||||
|
|
||||||
mutable std::optional<HistoryItem*> _captionItem;
|
mutable std::optional<HistoryItem*> _captionItem;
|
||||||
std::vector<Part> _parts;
|
std::vector<Part> _parts;
|
||||||
|
|
|
@ -26,4 +26,14 @@ struct MediaSpoiler {
|
||||||
bool revealed = false;
|
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
|
} // namespace HistoryView
|
||||||
|
|
|
@ -63,15 +63,6 @@ struct Photo::Streamed {
|
||||||
QImage roundingMask;
|
QImage roundingMask;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct Photo::PriceTag {
|
|
||||||
uint64 price = 0;
|
|
||||||
QImage cache;
|
|
||||||
QColor darken;
|
|
||||||
QColor fg;
|
|
||||||
QColor star;
|
|
||||||
ClickHandlerPtr link;
|
|
||||||
};
|
|
||||||
|
|
||||||
Photo::Streamed::Streamed(
|
Photo::Streamed::Streamed(
|
||||||
std::shared_ptr<::Media::Streaming::Document> shared)
|
std::shared_ptr<::Media::Streaming::Document> shared)
|
||||||
: instance(std::move(shared), nullptr) {
|
: instance(std::move(shared), nullptr) {
|
||||||
|
@ -87,7 +78,10 @@ Photo::Photo(
|
||||||
, _storyId(realParent->media()
|
, _storyId(realParent->media()
|
||||||
? realParent->media()->storyId()
|
? realParent->media()->storyId()
|
||||||
: FullStoryId())
|
: 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());
|
create(realParent->fullId());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -342,7 +336,8 @@ void Photo::draw(Painter &p, const PaintContext &context) const {
|
||||||
}
|
}
|
||||||
|
|
||||||
const auto showEnlarge = loaded && _showEnlarge;
|
const auto showEnlarge = loaded && _showEnlarge;
|
||||||
const auto paintInCenter = (radial || (!loaded && !_data->loading()));
|
const auto paintInCenter = !_sensitiveSpoiler
|
||||||
|
&& (radial || (!loaded && !_data->loading()));
|
||||||
if (paintInCenter || showEnlarge) {
|
if (paintInCenter || showEnlarge) {
|
||||||
p.setPen(Qt::NoPen);
|
p.setPen(Qt::NoPen);
|
||||||
if (context.selected()) {
|
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)));
|
QRect rinner(inner.marginsRemoved(QMargins(st::msgFileRadialLine, st::msgFileRadialLine, st::msgFileRadialLine, st::msgFileRadialLine)));
|
||||||
_animation->radial.draw(p, rinner, st::msgFileRadialLine, sti->historyFileThumbRadialFg);
|
_animation->radial.draw(p, rinner, st::msgFileRadialLine, sti->historyFileThumbRadialFg);
|
||||||
}
|
}
|
||||||
} else if (preview) {
|
} else if (_sensitiveSpoiler || preview) {
|
||||||
drawPriceTag(p, rthumb, context, [&] {
|
drawSpoilerTag(p, rthumb, context, [&] {
|
||||||
return priceTagBackground();
|
return spoilerTagBackground();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (showEnlarge) {
|
if (showEnlarge) {
|
||||||
|
@ -426,105 +421,18 @@ void Photo::draw(Painter &p, const PaintContext &context) const {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Photo::setupPriceTag() const {
|
void Photo::drawSpoilerTag(
|
||||||
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(
|
|
||||||
Painter &p,
|
Painter &p,
|
||||||
QRect rthumb,
|
QRect rthumb,
|
||||||
const PaintContext &context,
|
const PaintContext &context,
|
||||||
Fn<QImage()> generateBackground) const {
|
Fn<QImage()> generateBackground) const {
|
||||||
if (!_priceTag) {
|
Media::drawSpoilerTag(
|
||||||
setupPriceTag();
|
p,
|
||||||
if (!_priceTag) {
|
_spoiler.get(),
|
||||||
return;
|
_spoilerTag,
|
||||||
}
|
rthumb,
|
||||||
}
|
context,
|
||||||
const auto st = context.st;
|
std::move(generateBackground));
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Photo::validateUserpicImageCache(QSize size, bool forum) const {
|
void Photo::validateUserpicImageCache(QSize size, bool forum) const {
|
||||||
|
@ -734,23 +642,11 @@ QRect Photo::enlargeRect() const {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
ClickHandlerPtr Photo::priceTagLink() const {
|
ClickHandlerPtr Photo::spoilerTagLink() const {
|
||||||
const auto item = parent()->data();
|
return Media::spoilerTagLink(_spoiler.get(), _spoilerTag);
|
||||||
if (!item->isRegular()) {
|
|
||||||
return nullptr;
|
|
||||||
} else if (!_priceTag) {
|
|
||||||
setupPriceTag();
|
|
||||||
if (!_priceTag) {
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!_priceTag->link) {
|
|
||||||
_priceTag->link = MakePaidMediaLink(item);
|
|
||||||
}
|
|
||||||
return _priceTag->link;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
QImage Photo::priceTagBackground() const {
|
QImage Photo::spoilerTagBackground() const {
|
||||||
return _spoiler ? _spoiler->background : QImage();
|
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)) {
|
if (QRect(paintx, painty, paintw, painth).contains(point)) {
|
||||||
ensureDataMediaCreated();
|
ensureDataMediaCreated();
|
||||||
result.link = _data->extendedMediaPreview()
|
result.link = (_spoiler && !_spoiler->revealed)
|
||||||
? priceTagLink()
|
? ((_data->extendedMediaPreview() || _sensitiveSpoiler)
|
||||||
: (_spoiler && !_spoiler->revealed)
|
? spoilerTagLink()
|
||||||
? _spoiler->link
|
: _spoiler->link)
|
||||||
: _data->uploading()
|
: _data->uploading()
|
||||||
? _cancell
|
? _cancell
|
||||||
: _dataMedia->loaded()
|
: _dataMedia->loaded()
|
||||||
|
@ -875,10 +771,11 @@ void Photo::drawGrouped(
|
||||||
p.setOpacity(1.);
|
p.setOpacity(1.);
|
||||||
}
|
}
|
||||||
|
|
||||||
const auto displayState = radial
|
const auto paintInCenter = !_sensitiveSpoiler
|
||||||
|| (!loaded && !_data->loading())
|
&& (radial
|
||||||
|| _data->waitingForAlbum();
|
|| (!loaded && !_data->loading())
|
||||||
if (displayState) {
|
|| _data->waitingForAlbum());
|
||||||
|
if (paintInCenter) {
|
||||||
const auto radialOpacity = radial
|
const auto radialOpacity = radial
|
||||||
? _animation->radial.opacity()
|
? _animation->radial.opacity()
|
||||||
: 1.;
|
: 1.;
|
||||||
|
@ -941,17 +838,18 @@ TextState Photo::getStateGrouped(
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
ensureDataMediaCreated();
|
ensureDataMediaCreated();
|
||||||
return TextState(_parent, _data->extendedMediaPreview()
|
auto link = (_spoiler && !_spoiler->revealed)
|
||||||
? priceTagLink()
|
? ((_data->extendedMediaPreview() || _sensitiveSpoiler)
|
||||||
: (_spoiler && !_spoiler->revealed)
|
? spoilerTagLink()
|
||||||
? _spoiler->link
|
: _spoiler->link)
|
||||||
: _data->uploading()
|
: _data->uploading()
|
||||||
? _cancell
|
? _cancell
|
||||||
: _dataMedia->loaded()
|
: _dataMedia->loaded()
|
||||||
? _openl
|
? _openl
|
||||||
: _data->loading()
|
: _data->loading()
|
||||||
? _cancell
|
? _cancell
|
||||||
: _savel);
|
: _savel;
|
||||||
|
return TextState(_parent, std::move(link));
|
||||||
}
|
}
|
||||||
|
|
||||||
float64 Photo::dataProgress() const {
|
float64 Photo::dataProgress() const {
|
||||||
|
|
|
@ -75,13 +75,13 @@ public:
|
||||||
QPoint point,
|
QPoint point,
|
||||||
StateRequest request) const override;
|
StateRequest request) const override;
|
||||||
|
|
||||||
void drawPriceTag(
|
void drawSpoilerTag(
|
||||||
Painter &p,
|
Painter &p,
|
||||||
QRect rthumb,
|
QRect rthumb,
|
||||||
const PaintContext &context,
|
const PaintContext &context,
|
||||||
Fn<QImage()> generateBackground) const override;
|
Fn<QImage()> generateBackground) const override;
|
||||||
ClickHandlerPtr priceTagLink() const override;
|
ClickHandlerPtr spoilerTagLink() const override;
|
||||||
QImage priceTagBackground() const override;
|
QImage spoilerTagBackground() const override;
|
||||||
|
|
||||||
void hideSpoilers() override;
|
void hideSpoilers() override;
|
||||||
bool needsBubble() const override;
|
bool needsBubble() const override;
|
||||||
|
@ -105,7 +105,6 @@ protected:
|
||||||
|
|
||||||
private:
|
private:
|
||||||
struct Streamed;
|
struct Streamed;
|
||||||
struct PriceTag;
|
|
||||||
|
|
||||||
void create(FullMsgId contextId, PeerData *chat = nullptr);
|
void create(FullMsgId contextId, PeerData *chat = nullptr);
|
||||||
|
|
||||||
|
@ -115,7 +114,7 @@ private:
|
||||||
|
|
||||||
void ensureDataMediaCreated() const;
|
void ensureDataMediaCreated() const;
|
||||||
void dataMediaCreated() const;
|
void dataMediaCreated() const;
|
||||||
void setupPriceTag() const;
|
void setupSpoilerTag() const;
|
||||||
|
|
||||||
QSize countOptimalSize() override;
|
QSize countOptimalSize() override;
|
||||||
QSize countCurrentSize(int newWidth) override;
|
QSize countCurrentSize(int newWidth) override;
|
||||||
|
@ -161,14 +160,15 @@ private:
|
||||||
|
|
||||||
const not_null<PhotoData*> _data;
|
const not_null<PhotoData*> _data;
|
||||||
const FullStoryId _storyId;
|
const FullStoryId _storyId;
|
||||||
mutable std::unique_ptr<PriceTag> _priceTag;
|
|
||||||
mutable std::shared_ptr<Data::PhotoMedia> _dataMedia;
|
mutable std::shared_ptr<Data::PhotoMedia> _dataMedia;
|
||||||
mutable std::unique_ptr<Streamed> _streamed;
|
mutable std::unique_ptr<Streamed> _streamed;
|
||||||
const std::unique_ptr<MediaSpoiler> _spoiler;
|
const std::unique_ptr<MediaSpoiler> _spoiler;
|
||||||
|
mutable std::unique_ptr<MediaSpoilerTag> _spoilerTag;
|
||||||
mutable QImage _imageCache;
|
mutable QImage _imageCache;
|
||||||
mutable std::optional<Ui::BubbleRounding> _imageCacheRounding;
|
mutable std::optional<Ui::BubbleRounding> _imageCacheRounding;
|
||||||
uint32 _serviceWidth : 27 = 0;
|
uint32 _serviceWidth : 26 = 0;
|
||||||
uint32 _purchasedPriceTag : 1 = 0;
|
uint32 _purchasedPriceTag : 1 = 0;
|
||||||
|
const uint32 _sensitiveSpoiler : 1 = 0;
|
||||||
mutable uint32 _imageCacheForum : 1 = 0;
|
mutable uint32 _imageCacheForum : 1 = 0;
|
||||||
mutable uint32 _imageCacheBlurred : 1 = 0;
|
mutable uint32 _imageCacheBlurred : 1 = 0;
|
||||||
mutable uint32 _pollingStory : 1 = 0;
|
mutable uint32 _pollingStory : 1 = 0;
|
||||||
|
|
|
@ -38,15 +38,18 @@ void AppConfig::start() {
|
||||||
}, _lifetime);
|
}, _lifetime);
|
||||||
}
|
}
|
||||||
|
|
||||||
void AppConfig::refresh() {
|
void AppConfig::refresh(bool force) {
|
||||||
if (_requestId || !_api) {
|
if (_requestId || !_api) {
|
||||||
|
if (force) {
|
||||||
|
_pendingRefresh = true;
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
_pendingRefresh = false;
|
||||||
_requestId = _api->request(MTPhelp_GetAppConfig(
|
_requestId = _api->request(MTPhelp_GetAppConfig(
|
||||||
MTP_int(_hash)
|
MTP_int(_hash)
|
||||||
)).done([=](const MTPhelp_AppConfig &result) {
|
)).done([=](const MTPhelp_AppConfig &result) {
|
||||||
_requestId = 0;
|
_requestId = 0;
|
||||||
refreshDelayed();
|
|
||||||
result.match([&](const MTPDhelp_appConfig &data) {
|
result.match([&](const MTPDhelp_appConfig &data) {
|
||||||
_hash = data.vhash().v;
|
_hash = data.vhash().v;
|
||||||
|
|
||||||
|
@ -55,15 +58,25 @@ void AppConfig::refresh() {
|
||||||
LOG(("API Error: Unexpected config type."));
|
LOG(("API Error: Unexpected config type."));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
auto was = ignoredRestrictionReasons();
|
||||||
|
|
||||||
_data.clear();
|
_data.clear();
|
||||||
for (const auto &element : config.c_jsonObject().vvalue().v) {
|
for (const auto &element : config.c_jsonObject().vvalue().v) {
|
||||||
element.match([&](const MTPDjsonObjectValue &data) {
|
element.match([&](const MTPDjsonObjectValue &data) {
|
||||||
_data.emplace_or_assign(qs(data.vkey()), data.vvalue());
|
_data.emplace_or_assign(qs(data.vkey()), data.vvalue());
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
updateIgnoredRestrictionReasons(std::move(was));
|
||||||
|
|
||||||
DEBUG_LOG(("getAppConfig result handled."));
|
DEBUG_LOG(("getAppConfig result handled."));
|
||||||
_refreshed.fire({});
|
_refreshed.fire({});
|
||||||
}, [](const MTPDhelp_appConfigNotModified &) {});
|
}, [](const MTPDhelp_appConfigNotModified &) {});
|
||||||
|
|
||||||
|
if (base::take(_pendingRefresh)) {
|
||||||
|
refresh();
|
||||||
|
} else {
|
||||||
|
refreshDelayed();
|
||||||
|
}
|
||||||
}).fail([=] {
|
}).fail([=] {
|
||||||
_requestId = 0;
|
_requestId = 0;
|
||||||
refreshDelayed();
|
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 {
|
rpl::producer<> AppConfig::refreshed() const {
|
||||||
return _refreshed.events();
|
return _refreshed.events();
|
||||||
}
|
}
|
||||||
|
|
|
@ -55,7 +55,15 @@ public:
|
||||||
|
|
||||||
[[nodiscard]] bool newRequirePremiumFree() const;
|
[[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:
|
private:
|
||||||
void refreshDelayed();
|
void refreshDelayed();
|
||||||
|
@ -84,14 +92,20 @@ private:
|
||||||
const QString &key,
|
const QString &key,
|
||||||
std::vector<int> &&fallback) const;
|
std::vector<int> &&fallback) const;
|
||||||
|
|
||||||
|
void updateIgnoredRestrictionReasons(std::vector<QString> was);
|
||||||
|
|
||||||
const not_null<Account*> _account;
|
const not_null<Account*> _account;
|
||||||
std::optional<MTP::Sender> _api;
|
std::optional<MTP::Sender> _api;
|
||||||
mtpRequestId _requestId = 0;
|
mtpRequestId _requestId = 0;
|
||||||
int32 _hash = 0;
|
int32 _hash = 0;
|
||||||
|
bool _pendingRefresh = false;
|
||||||
base::flat_map<QString, MTPJSONValue> _data;
|
base::flat_map<QString, MTPJSONValue> _data;
|
||||||
rpl::event_stream<> _refreshed;
|
rpl::event_stream<> _refreshed;
|
||||||
base::flat_set<QString> _dismissedSuggestions;
|
base::flat_set<QString> _dismissedSuggestions;
|
||||||
|
|
||||||
|
std::vector<QString> _ignoreRestrictionReasons;
|
||||||
|
rpl::event_stream<std::vector<QString>> _ignoreRestrictionChanges;
|
||||||
|
|
||||||
rpl::lifetime _lifetime;
|
rpl::lifetime _lifetime;
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
|
@ -7,7 +7,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
*/
|
*/
|
||||||
#include "settings/settings_chat.h"
|
#include "settings/settings_chat.h"
|
||||||
|
|
||||||
|
#include "base/timer_rpl.h"
|
||||||
#include "settings/settings_advanced.h"
|
#include "settings/settings_advanced.h"
|
||||||
|
#include "settings/settings_privacy_security.h"
|
||||||
#include "settings/settings_experimental.h"
|
#include "settings/settings_experimental.h"
|
||||||
#include "boxes/abstract_box.h"
|
#include "boxes/abstract_box.h"
|
||||||
#include "boxes/peers/edit_peer_color_box.h"
|
#include "boxes/peers/edit_peer_color_box.h"
|
||||||
|
@ -1022,7 +1024,6 @@ void SetupMessages(
|
||||||
void SetupArchive(
|
void SetupArchive(
|
||||||
not_null<Window::SessionController*> controller,
|
not_null<Window::SessionController*> controller,
|
||||||
not_null<Ui::VerticalLayout*> container) {
|
not_null<Ui::VerticalLayout*> container) {
|
||||||
Ui::AddDivider(container);
|
|
||||||
Ui::AddSkip(container);
|
Ui::AddSkip(container);
|
||||||
|
|
||||||
PreloadArchiveSettings(&controller->session());
|
PreloadArchiveSettings(&controller->session());
|
||||||
|
@ -1801,12 +1802,17 @@ void Chat::fillTopBarMenu(const Ui::Menu::MenuCallback &addAction) {
|
||||||
void Chat::setupContent(not_null<Window::SessionController*> controller) {
|
void Chat::setupContent(not_null<Window::SessionController*> controller) {
|
||||||
const auto content = Ui::CreateChild<Ui::VerticalLayout>(this);
|
const auto content = Ui::CreateChild<Ui::VerticalLayout>(this);
|
||||||
|
|
||||||
|
auto updateOnTick = rpl::single(
|
||||||
|
) | rpl::then(base::timer_each(60 * crl::time(1000)));
|
||||||
|
|
||||||
SetupThemeOptions(controller, content);
|
SetupThemeOptions(controller, content);
|
||||||
SetupThemeSettings(controller, content);
|
SetupThemeSettings(controller, content);
|
||||||
SetupCloudThemes(controller, content);
|
SetupCloudThemes(controller, content);
|
||||||
SetupChatBackground(controller, content);
|
SetupChatBackground(controller, content);
|
||||||
SetupStickersEmoji(controller, content);
|
SetupStickersEmoji(controller, content);
|
||||||
SetupMessages(controller, content);
|
SetupMessages(controller, content);
|
||||||
|
Ui::AddDivider(content);
|
||||||
|
SetupSensitiveContent(controller, content, std::move(updateOnTick));
|
||||||
SetupArchive(controller, content);
|
SetupArchive(controller, content);
|
||||||
|
|
||||||
Ui::ResizeFitChild(this, content);
|
Ui::ResizeFitChild(this, content);
|
||||||
|
|
|
@ -577,47 +577,6 @@ void SetupTopPeers(
|
||||||
Ui::AddDividerText(container, tr::lng_settings_top_peers_about());
|
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(
|
void SetupSelfDestruction(
|
||||||
not_null<Window::SessionController*> controller,
|
not_null<Window::SessionController*> controller,
|
||||||
not_null<Ui::VerticalLayout*> container,
|
not_null<Ui::VerticalLayout*> container,
|
||||||
|
@ -911,6 +870,47 @@ void SetupSecurity(
|
||||||
|
|
||||||
} // namespace
|
} // 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) {
|
int ExceptionUsersCount(const std::vector<not_null<PeerData*>> &exceptions) {
|
||||||
const auto add = [](int already, not_null<PeerData*> peer) {
|
const auto add = [](int already, not_null<PeerData*> peer) {
|
||||||
if (const auto chat = peer->asChat()) {
|
if (const auto chat = peer->asChat()) {
|
||||||
|
@ -1099,11 +1099,6 @@ void PrivacySecurity::setupContent(
|
||||||
SetupSecurity(controller, content, trigger(), showOtherMethod());
|
SetupSecurity(controller, content, trigger(), showOtherMethod());
|
||||||
SetupPrivacy(controller, content, trigger());
|
SetupPrivacy(controller, content, trigger());
|
||||||
SetupTopPeers(controller, content);
|
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);
|
SetupArchiveAndMute(controller, content);
|
||||||
SetupConfirmationExtensions(controller, content);
|
SetupConfirmationExtensions(controller, content);
|
||||||
SetupBotsAndWebsites(controller, content);
|
SetupBotsAndWebsites(controller, content);
|
||||||
|
|
|
@ -18,6 +18,11 @@ class BoxContent;
|
||||||
|
|
||||||
namespace Settings {
|
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);
|
int ExceptionUsersCount(const std::vector<not_null<PeerData*>> &exceptions);
|
||||||
|
|
||||||
bool CheckEditCloudPassword(not_null<::Main::Session*> session);
|
bool CheckEditCloudPassword(not_null<::Main::Session*> session);
|
||||||
|
|
Loading…
Add table
Reference in a new issue