Initial frozen accounts support.

This commit is contained in:
John Preston 2025-03-10 17:52:13 +04:00
parent 7f53a19647
commit d9b270b477
29 changed files with 481 additions and 39 deletions

File diff suppressed because one or more lines are too long

Binary file not shown.

After

Width:  |  Height:  |  Size: 630 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

View file

@ -6268,6 +6268,19 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_qr_box_transparent_background" = "Transparent Background";
"lng_qr_box_font_size" = "Font size";
"lng_frozen_bar_title" = "Your account is frozen!";
"lng_frozen_bar_text" = "Click to view details {arrow}";
"lng_frozen_restrict_title" = "Your account is frozen";
"lng_frozen_restrict_text" = "Click to view details";
"lng_frozen_title" = "Your Account is Frozen";
"lng_frozen_subtitle1" = "Violation of Terms";
"lng_frozen_text1" = "Your account was frozen for breaking Telegram's Terms and Conditions.";
"lng_frozen_subtitle2" = "Read-Only Mode";
"lng_frozen_text2" = "You can access your account but can't send messages or take actions.";
"lng_frozen_subtitle3" = "Appeal Before Deactivation";
"lng_frozen_text3" = "Appeal via {link} before {date}, or your account will be deleted.";
"lng_frozen_appeal_button" = "Submit an Appeal";
// Wnd specific
"lng_wnd_choose_program_menu" = "Choose Default Program...";

View file

@ -30,6 +30,7 @@
<file alias="noresults.tgs">../../animations/noresults.tgs</file>
<file alias="hello_status.tgs">../../animations/hello_status.tgs</file>
<file alias="starref_link.tgs">../../animations/starref_link.tgs</file>
<file alias="media_forbidden.tgs">../../animations/media_forbidden.tgs</file>
<file alias="dice_idle.tgs">../../animations/dice/dice_idle.tgs</file>
<file alias="dart_idle.tgs">../../animations/dice/dart_idle.tgs</file>

View file

@ -1566,3 +1566,27 @@ processingVideoView: RoundButton(defaultActiveButton) {
textBgOver: transparent;
ripple: emptyRippleAnimation;
}
frozenBarTitle: FlatLabel(defaultFlatLabel) {
style: semiboldTextStyle;
textFg: attentionButtonFg;
}
frozenRestrictionTitle: FlatLabel(frozenBarTitle) {
align: align(top);
}
frozenBarSubtitle: FlatLabel(defaultFlatLabel) {
textFg: windowSubTextFg;
}
frozenRestrictionSubtitle: FlatLabel(frozenBarSubtitle) {
align: align(top);
}
frozenInfoBox: Box(defaultBox) {
buttonPadding: margins(16px, 11px, 16px, 16px);
buttonHeight: 42px;
button: RoundButton(defaultActiveButton) {
height: 42px;
textTop: 12px;
style: semiboldTextStyle;
}
shadowIgnoreTopSkip: true;
}

View file

@ -11,38 +11,48 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "history/history.h" // History::session
#include "history/history_item.h" // HistoryItem::originalText
#include "history/history_item_helpers.h" // DropDisallowedCustomEmoji
#include "base/unixtime.h"
#include "base/qthelp_regex.h"
#include "base/qthelp_url.h"
#include "base/event_filter.h"
#include "ui/chat/chat_style.h"
#include "ui/layers/generic_box.h"
#include "ui/basic_click_handlers.h"
#include "ui/rect.h"
#include "core/shortcuts.h"
#include "core/application.h"
#include "core/core_settings.h"
#include "core/ui_integration.h"
#include "lottie/lottie_icon.h"
#include "info/profile/info_profile_icon.h"
#include "ui/text/text_utilities.h"
#include "ui/toast/toast.h"
#include "ui/wrap/vertical_layout.h"
#include "ui/widgets/buttons.h"
#include "ui/widgets/popup_menu.h"
#include "ui/widgets/shadow.h"
#include "ui/power_saving.h"
#include "ui/vertical_list.h"
#include "ui/ui_utility.h"
#include "data/data_session.h"
#include "data/data_user.h"
#include "data/data_document.h"
#include "data/stickers/data_custom_emoji.h"
#include "chat_helpers/emoji_suggestions_widget.h"
#include "history/view/controls/compose_controls_common.h"
#include "window/window_session_controller.h"
#include "lang/lang_keys.h"
#include "mainwindow.h"
#include "main/main_session.h"
#include "settings/settings_common.h"
#include "settings/settings_premium.h"
#include "styles/style_layers.h"
#include "styles/style_boxes.h"
#include "styles/style_chat.h"
#include "styles/style_chat_helpers.h"
#include "styles/style_credits.h"
#include "styles/style_dialogs.h"
#include "styles/style_menu_icons.h"
#include "styles/style_settings.h"
#include "base/qt/qt_common_adapters.h"
@ -1187,10 +1197,10 @@ base::unique_qptr<Ui::RpWidget> CreateDisabledFieldView(
return result;
}
base::unique_qptr<Ui::RpWidget> TextErrorSendRestriction(
std::unique_ptr<Ui::RpWidget> TextErrorSendRestriction(
QWidget *parent,
const QString &text) {
auto result = base::make_unique_q<Ui::RpWidget>(parent);
auto result = std::make_unique<Ui::RpWidget>(parent);
const auto raw = result.get();
const auto label = CreateChild<Ui::FlatLabel>(
result.get(),
@ -1215,11 +1225,11 @@ base::unique_qptr<Ui::RpWidget> TextErrorSendRestriction(
return result;
}
base::unique_qptr<Ui::RpWidget> PremiumRequiredSendRestriction(
std::unique_ptr<Ui::RpWidget> PremiumRequiredSendRestriction(
QWidget *parent,
not_null<UserData*> user,
not_null<Window::SessionController*> controller) {
auto result = base::make_unique_q<Ui::RpWidget>(parent);
auto result = std::make_unique<Ui::RpWidget>(parent);
const auto raw = result.get();
const auto label = CreateChild<Ui::FlatLabel>(
result.get(),
@ -1254,6 +1264,196 @@ base::unique_qptr<Ui::RpWidget> PremiumRequiredSendRestriction(
return result;
}
std::unique_ptr<Ui::AbstractButton> BoostsToLiftWriteRestriction(
not_null<QWidget*> parent,
std::shared_ptr<ChatHelpers::Show> show,
not_null<PeerData*> peer,
int boosts) {
auto result = std::make_unique<Ui::FlatButton>(
parent,
tr::lng_restricted_boost_group(tr::now),
st::historyComposeButton);
result->setClickedCallback([=] {
const auto window = show->resolveWindow();
window->resolveBoostState(peer->asChannel(), boosts);
});
return result;
}
std::unique_ptr<Ui::AbstractButton> FrozenWriteRestriction(
not_null<QWidget*> parent,
std::shared_ptr<ChatHelpers::Show> show,
FrozenWriteRestrictionType type,
FreezeInfoStyleOverride st) {
using namespace Ui;
auto result = std::make_unique<FlatButton>(
parent,
QString(),
st::historyComposeButton);
const auto raw = result.get();
const auto bar = (type == FrozenWriteRestrictionType::DialogsList);
const auto title = CreateChild<FlatLabel>(
raw,
(bar ? tr::lng_frozen_bar_title : tr::lng_frozen_restrict_title)(
tr::now),
bar ? st::frozenBarTitle : st::frozenRestrictionTitle);
title->setAttribute(Qt::WA_TransparentForMouseEvents);
title->show();
const auto subtitle = CreateChild<FlatLabel>(
raw,
(bar
? tr::lng_frozen_bar_text(
lt_arrow,
rpl::single(Ui::Text::IconEmoji(&st::textMoreIconEmoji)),
Ui::Text::WithEntities)
: tr::lng_frozen_restrict_text(Ui::Text::WithEntities)),
bar ? st::frozenBarSubtitle : st::frozenRestrictionSubtitle);
subtitle->setAttribute(Qt::WA_TransparentForMouseEvents);
subtitle->show();
const auto shadow = bar ? CreateChild<PlainShadow>(raw) : nullptr;
const auto icon = bar ? CreateChild<RpWidget>(raw) : nullptr;
if (icon) {
icon->paintRequest() | rpl::start_with_next([=] {
auto p = QPainter(icon);
st::menuIconDisableAttention.paintInCenter(p, icon->rect());
}, icon->lifetime());
icon->show();
}
raw->sizeValue() | rpl::start_with_next([=](QSize size) {
if (bar) {
const auto toggle = [&](auto &&widget, bool shown) {
if (widget->isHidden() == shown) {
widget->setVisible(shown);
}
};
const auto small = 2 * st::defaultDialogRow.photoSize;
const auto shown = (size.width() > small);
toggle(icon, !shown);
toggle(title, shown);
toggle(subtitle, shown);
icon->setGeometry(0, 0, size.width(), size.height());
}
const auto skip = bar
? st::defaultDialogRow.padding.left()
: 2 * st::normalFont->spacew;
const auto available = size.width() - skip * 2;
title->resizeToWidth(available);
subtitle->resizeToWidth(available);
const auto height = title->height() + subtitle->height();
const auto top = (size.height() - height) / 2;
title->moveToLeft(skip, top, size.width());
subtitle->moveToLeft(skip, top + title->height(), size.width());
const auto line = st::lineWidth;
if (shadow) {
shadow->setGeometry(0, size.height() - line, size.width(), line);
}
}, title->lifetime());
const auto info = show->session().frozen();
const auto detailsBox = [=](not_null<GenericBox*> box) {
box->setWidth(st::boxWideWidth);
box->setStyle(st::frozenInfoBox);
box->setNoContentMargin(true);
box->addTopButton(st::boxTitleClose, [=] {
box->closeBox();
});
const auto content = box->verticalLayout();
auto icon = Settings::CreateLottieIcon(
content,
{
.name = u"media_forbidden"_q,
.sizeOverride = {
st::changePhoneIconSize,
st::changePhoneIconSize,
},
},
st::settingLocalPasscodeIconPadding);
content->add(std::move(icon.widget));
box->setShowFinishedCallback([animate = std::move(icon.animate)] {
animate(anim::repeat::once);
});
Ui::AddSkip(content);
const auto infoRow = [&](
rpl::producer<QString> title,
rpl::producer<TextWithEntities> text,
not_null<const style::icon*> icon) {
auto raw = content->add(
object_ptr<Ui::VerticalLayout>(content));
raw->add(
object_ptr<Ui::FlatLabel>(
raw,
std::move(title) | Ui::Text::ToBold(),
st.infoTitle ? *st.infoTitle : st::defaultFlatLabel),
st::settingsPremiumRowTitlePadding);
raw->add(
object_ptr<Ui::FlatLabel>(
raw,
std::move(text),
st.infoAbout ? *st.infoAbout : st::upgradeGiftSubtext),
st::settingsPremiumRowAboutPadding);
object_ptr<Info::Profile::FloatingIcon>(
raw,
*icon,
st::starrefInfoIconPosition);
};
content->add(
object_ptr<Ui::FlatLabel>(
content,
tr::lng_frozen_title(),
st.title ? *st.title : st::uniqueGiftTitle),
st::settingsPremiumRowTitlePadding);
Ui::AddSkip(content, st::defaultVerticalListSkip * 3);
infoRow(
tr::lng_frozen_subtitle1(),
tr::lng_frozen_text1(Text::WithEntities),
st.violationIcon ? st.violationIcon : &st::menuIconBlock);
infoRow(
tr::lng_frozen_subtitle2(),
tr::lng_frozen_text2(Text::WithEntities),
st.readOnlyIcon ? st.readOnlyIcon : &st::menuIconLock);
infoRow(
tr::lng_frozen_subtitle3(),
tr::lng_frozen_text3(
lt_link,
rpl::single(Text::Link(u"@SpamBot"_q, info.appealUrl)),
lt_date,
rpl::single(TextWithEntities{
langDayOfMonthFull(
base::unixtime::parse(info.until).date()),
}),
Text::WithEntities),
st.appealIcon ? st.appealIcon : &st::menuIconHourglass);
const auto button = box->addButton(
tr::lng_frozen_appeal_button(),
[url = info.appealUrl] { UrlClickHandler::Open(url); });
const auto buttonPadding = st::frozenInfoBox.buttonPadding;
const auto buttonWidth = st::boxWideWidth
- buttonPadding.left()
- buttonPadding.right();
button->widthValue() | rpl::filter([=] {
return (button->widthNoMargins() != buttonWidth);
}) | rpl::start_with_next([=] {
button->resizeToWidth(buttonWidth);
}, button->lifetime());
};
raw->setClickedCallback([=] {
show->show(Box(detailsBox));
});
return result;
}
void SelectTextInFieldWithMargins(
not_null<Ui::InputField*> field,
const TextSelection &selection) {

View file

@ -37,6 +37,10 @@ enum class PauseReason;
class Show;
} // namespace ChatHelpers
namespace HistoryView::Controls {
struct WriteRestriction;
} // namespace HistoryView::Controls
namespace Ui {
class PopupMenu;
class Show;
@ -162,13 +166,41 @@ private:
[[nodiscard]] base::unique_qptr<Ui::RpWidget> CreateDisabledFieldView(
QWidget *parent,
not_null<PeerData*> peer);
[[nodiscard]] base::unique_qptr<Ui::RpWidget> TextErrorSendRestriction(
[[nodiscard]] std::unique_ptr<Ui::RpWidget> TextErrorSendRestriction(
QWidget *parent,
const QString &text);
[[nodiscard]] base::unique_qptr<Ui::RpWidget> PremiumRequiredSendRestriction(
[[nodiscard]] std::unique_ptr<Ui::RpWidget> PremiumRequiredSendRestriction(
QWidget *parent,
not_null<UserData*> user,
not_null<Window::SessionController*> controller);
[[nodiscard]] auto BoostsToLiftWriteRestriction(
not_null<QWidget*> parent,
std::shared_ptr<ChatHelpers::Show> show,
not_null<PeerData*> peer,
int boosts)
-> std::unique_ptr<Ui::AbstractButton>;
struct FreezeInfoStyleOverride {
const style::Box *box = nullptr;
const style::FlatLabel *title = nullptr;
const style::FlatLabel *subtitle = nullptr;
const style::icon *violationIcon = nullptr;
const style::icon *readOnlyIcon = nullptr;
const style::icon *appealIcon = nullptr;
const style::FlatLabel *infoTitle = nullptr;
const style::FlatLabel *infoAbout = nullptr;
};
[[nodiscard]] FreezeInfoStyleOverride DarkFreezeInfoStyle();
enum class FrozenWriteRestrictionType {
MessageField,
DialogsList,
};
[[nodiscard]] std::unique_ptr<Ui::AbstractButton> FrozenWriteRestriction(
not_null<QWidget*> parent,
std::shared_ptr<ChatHelpers::Show> show,
FrozenWriteRestrictionType type,
FreezeInfoStyleOverride st = {});
void SelectTextInFieldWithMargins(
not_null<Ui::InputField*> field,

View file

@ -118,7 +118,10 @@ bool CanSendAnyOf(
not_null<const PeerData*> peer,
ChatRestrictions rights,
bool forbidInForums) {
if (const auto user = peer->asUser()) {
if (peer->session().frozen()
&& !peer->isFreezeAppealChat()) {
return false;
} else if (const auto user = peer->asUser()) {
if (user->isInaccessible()
|| user->isRepliesChat()
|| user->isVerifyCodes()) {
@ -178,7 +181,13 @@ SendError RestrictionError(
not_null<PeerData*> peer,
ChatRestriction restriction) {
using Flag = ChatRestriction;
if (const auto restricted = peer->amRestricted(restriction)) {
if (peer->session().frozen()
&& !peer->isFreezeAppealChat()) {
return SendError({
.text = tr::lng_frozen_restrict_title(tr::now),
.frozen = true,
});
} else if (const auto restricted = peer->amRestricted(restriction)) {
if (const auto user = peer->asUser()) {
if (user->requiresPremiumToWrite()
&& !user->session().premium()) {

View file

@ -191,16 +191,19 @@ struct SendError {
QString text;
int boostsToLift = 0;
bool premiumToLift = false;
bool frozen = false;
};
SendError(Args &&args)
: text(std::move(args.text))
, boostsToLift(args.boostsToLift)
, premiumToLift(args.premiumToLift) {
, premiumToLift(args.premiumToLift)
, frozen(args.frozen) {
}
QString text;
int boostsToLift = 0;
bool premiumToLift = false;
bool frozen = false;
[[nodiscard]] SendError value_or(SendError other) const {
return *this ? *this : other;

View file

@ -1349,6 +1349,10 @@ bool PeerData::isVerifyCodes() const {
return (id == kVerifyCodesId);
}
bool PeerData::isFreezeAppealChat() const {
return username().compare(u"spambot"_q, Qt::CaseInsensitive) == 0;
}
bool PeerData::sharedMediaInfo() const {
return isSelf() || isRepliesChat();
}

View file

@ -235,6 +235,7 @@ public:
[[nodiscard]] bool isGigagroup() const;
[[nodiscard]] bool isRepliesChat() const;
[[nodiscard]] bool isVerifyCodes() const;
[[nodiscard]] bool isFreezeAppealChat() const;
[[nodiscard]] bool sharedMediaInfo() const;
[[nodiscard]] bool savedSublistsInfo() const;
[[nodiscard]] bool hasStoriesHidden() const;

View file

@ -49,6 +49,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "main/main_session_settings.h"
#include "api/api_chat_filters.h"
#include "apiwrap.h"
#include "chat_helpers/message_field.h"
#include "core/application.h"
#include "core/ui_integration.h"
#include "core/update_checker.h"
@ -680,6 +681,8 @@ Widget::Widget(
|| !controller->enoughSpaceForFilters())) {
toggleFiltersMenu(true);
}
setupFrozenAccountBar();
}
void Widget::setupSwipeBack() {
@ -990,6 +993,29 @@ void Widget::setupTouchChatPreview() {
}, _inner->lifetime());
}
void Widget::setupFrozenAccountBar() {
session().frozenValue(
) | rpl::start_with_next([=] {
updateFrozenAccountBar();
updateControlsGeometry();
}, lifetime());
}
void Widget::updateFrozenAccountBar() {
if (_layout == Layout::Child
|| _openedForum
|| _openedFolder
|| !session().frozen()) {
_frozenAccountBar = nullptr;
} else if (!_frozenAccountBar) {
_frozenAccountBar = FrozenWriteRestriction(
this,
controller()->uiShow(),
FrozenWriteRestrictionType::DialogsList);
_frozenAccountBar->show();
}
}
void Widget::setupMoreChatsBar() {
if (_layout == Layout::Child) {
return;
@ -1418,6 +1444,9 @@ void Widget::updateControlsVisibility(bool fast) {
if (_moreChatsBar) {
_moreChatsBar->show();
}
if (_frozenAccountBar) {
_frozenAccountBar->show();
}
if (_chatFilters) {
_chatFilters->show();
}
@ -1736,6 +1765,7 @@ void Widget::changeOpenedFolder(Data::Folder *folder, anim::type animated) {
if (_stories) {
storiesExplicitCollapse();
}
updateFrozenAccountBar();
}, (folder != nullptr), animated);
}
@ -1792,6 +1822,7 @@ void Widget::changeOpenedForum(Data::Forum *forum, anim::type animated) {
_api.request(base::take(_topicSearchRequest)).cancel();
_inner->changeOpenedForum(forum);
storiesToggleExplicitExpand(false);
updateFrozenAccountBar();
updateStoriesVisibility();
}, (forum != nullptr), animated);
}
@ -2123,6 +2154,9 @@ void Widget::startWidthAnimation() {
}
_widthAnimationCache = grabNonNarrowScrollFrame();
_scroll->hide();
if (_frozenAccountBar) {
_frozenAccountBar->hide();
}
if (_chatFilters) {
_chatFilters->hide();
}
@ -2133,6 +2167,9 @@ void Widget::stopWidthAnimation() {
_widthAnimationCache = QPixmap();
if (!_showAnimation) {
_scroll->setVisible(!_suggestions);
if (_frozenAccountBar) {
_frozenAccountBar->setVisible(!_suggestions);
}
if (_chatFilters) {
_chatFilters->setVisible(!_suggestions);
}
@ -2230,6 +2267,9 @@ void Widget::startSlideAnimation(
if (_moreChatsBar) {
_moreChatsBar->hide();
}
if (_frozenAccountBar) {
_frozenAccountBar->hide();
}
if (_chatFilters) {
_chatFilters->hide();
}
@ -3813,9 +3853,17 @@ void Widget::updateControlsGeometry() {
if (_chatFilters) {
_chatFilters->resizeToWidth(barw);
}
if (_frozenAccountBar) {
_frozenAccountBar->resize(barw, _frozenAccountBar->height());
}
_updateScrollGeometryCached = [=] {
const auto moreChatsBarTop = expandedStoriesTop
const auto frozenBarTop = expandedStoriesTop
+ ((!_stories || _stories->isHidden()) ? 0 : _aboveScrollAdded);
if (_frozenAccountBar) {
_frozenAccountBar->move(0, frozenBarTop);
}
const auto moreChatsBarTop = frozenBarTop
+ (_frozenAccountBar ? _frozenAccountBar->height() : 0);
if (_moreChatsBar) {
_moreChatsBar->move(0, moreChatsBarTop);
}

View file

@ -201,6 +201,7 @@ private:
void setupSupportMode();
void setupTouchChatPreview();
void setupFrozenAccountBar();
void setupConnectingWidget();
void setupMainMenuToggle();
void setupMoreChatsBar();
@ -223,6 +224,7 @@ private:
void showMainMenu();
void clearSearchCache(bool clearPosts);
void setSearchQuery(const QString &query, int cursorPosition = -1);
void updateFrozenAccountBar();
void updateControlsVisibility(bool fast = false);
void updateLockUnlockVisibility(
anim::type animated = anim::type::instant);
@ -300,6 +302,9 @@ private:
const Layout _layout = Layout::Main;
int _narrowWidth = 0;
std::unique_ptr<Ui::AbstractButton> _frozenAccountBar;
object_ptr<Ui::RpWidget> _searchControls;
object_ptr<HistoryView::TopBarWidget> _subsectionTopBar = { nullptr };
struct {

View file

@ -6514,21 +6514,24 @@ void HistoryWidget::updateSendRestriction() {
_sendRestrictionKey = restriction.text;
if (!restriction) {
_sendRestriction = nullptr;
} else if (restriction.frozen) {
const auto show = controller()->uiShow();
_sendRestriction = FrozenWriteRestriction(
this,
show,
FrozenWriteRestrictionType::MessageField);
} else if (restriction.premiumToLift) {
_sendRestriction = PremiumRequiredSendRestriction(
this,
_peer->asUser(),
controller());
} else if (const auto lifting = restriction.boostsToLift) {
auto button = base::make_unique_q<Ui::FlatButton>(
const auto show = controller()->uiShow();
_sendRestriction = BoostsToLiftWriteRestriction(
this,
restriction.text,
st::historyComposeButton);
const auto channel = _peer->asChannel();
button->setClickedCallback([=] {
controller()->resolveBoostState(channel, lifting);
});
_sendRestriction = std::move(button);
show,
_peer,
lifting);
} else {
_sendRestriction = TextErrorSendRestriction(this, restriction.text);
}

View file

@ -820,7 +820,7 @@ private:
bool _cmdStartShown = false;
object_ptr<Ui::InputField> _field;
base::unique_qptr<Ui::RpWidget> _fieldDisabled;
base::unique_qptr<Ui::RpWidget> _sendRestriction;
std::unique_ptr<Ui::RpWidget> _sendRestriction;
using CharactersLimitLabel = HistoryView::Controls::CharactersLimitLabel;
base::unique_qptr<CharactersLimitLabel> _charsLimitation;
QString _sendRestrictionKey;

View file

@ -40,6 +40,7 @@ enum class WriteRestrictionType {
None,
Rights,
PremiumRequired,
Frozen,
};
struct WriteRestriction {

View file

@ -2324,15 +2324,17 @@ void SetupRestrictionView(
) | rpl::distinct_until_changed(
) | rpl::start_with_next([=](Controls::WriteRestriction value) {
using Type = Controls::WriteRestriction::Type;
if (const auto lifting = value.boostsToLift) {
state->button = std::make_unique<Ui::FlatButton>(
if (value.type == Type::Frozen) {
state->button = FrozenWriteRestriction(
widget,
tr::lng_restricted_boost_group(tr::now),
st::historyComposeButton);
state->button->setClickedCallback([=] {
const auto window = show->resolveWindow();
window->resolveBoostState(peer->asChannel(), lifting);
});
show,
FrozenWriteRestrictionType::MessageField);
} else if (const auto lifting = value.boostsToLift) {
state->button = BoostsToLiftWriteRestriction(
widget,
show,
peer,
lifting);
} else if (value.type == Type::Rights) {
state->icon = nullptr;
state->unlock = nullptr;

View file

@ -671,12 +671,22 @@ void RepliesWidget::setupComposeControls() {
: tr::lng_forum_topic_closed(tr::now);
});
auto writeRestriction = rpl::combine(
session().frozenValue(),
session().changes().peerFlagsValue(
_history->peer,
Data::PeerUpdate::Flag::Rights),
Data::CanSendAnythingValue(_history->peer),
std::move(topicWriteRestrictions)
) | rpl::map([=](auto, auto, Data::SendError topicRestriction) {
) | rpl::map([=](
const Main::FreezeInfo &info,
auto,
auto,
Data::SendError topicRestriction) {
if (info) {
return Controls::WriteRestriction{
.type = Controls::WriteRestrictionType::Frozen,
};
}
const auto allWithoutPolls = Data::AllSendRestrictions()
& ~ChatRestriction::SendPolls;
const auto canSendAnything = _topic

View file

@ -268,15 +268,22 @@ void ScheduledWidget::setupComposeControls() {
: tr::lng_forum_topic_closed(tr::now);
});
return rpl::combine(
session().frozenValue(),
session().changes().peerFlagsValue(
_history->peer,
Data::PeerUpdate::Flag::Rights),
Data::CanSendAnythingValue(_history->peer),
std::move(topicWriteRestrictions)
) | rpl::map([=](
const Main::FreezeInfo &info,
auto,
auto,
Data::SendError topicRestriction) {
if (info) {
return Controls::WriteRestriction{
.type = Controls::WriteRestrictionType::Frozen,
};
}
const auto allWithoutPolls = Data::AllSendRestrictions()
& ~ChatRestriction::SendPolls;
const auto canSendAnything = Data::CanSendAnyOf(
@ -303,11 +310,17 @@ void ScheduledWidget::setupComposeControls() {
}()
: [&] {
return rpl::combine(
session().frozenValue(),
session().changes().peerFlagsValue(
_history->peer,
Data::PeerUpdate::Flag::Rights),
Data::CanSendAnythingValue(_history->peer)
) | rpl::map([=] {
) | rpl::map([=](const Main::FreezeInfo &info, auto, auto) {
if (info) {
return Controls::WriteRestriction{
.type = Controls::WriteRestrictionType::Frozen,
};
}
const auto allWithoutPolls = Data::AllSendRestrictions()
& ~ChatRestriction::SendPolls;
const auto canSendAnything = Data::CanSendAnyOf(

View file

@ -24,6 +24,7 @@ AppConfig::AppConfig(not_null<Account*> account) : _account(account) {
) | rpl::filter([=](Session *session) {
return (session != nullptr);
}) | rpl::start_with_next([=] {
_lastFrozenRefresh = 0;
refresh();
}, _lifetime);
}
@ -35,6 +36,18 @@ void AppConfig::start() {
) | rpl::start_with_next([=](not_null<MTP::Instance*> instance) {
_api.emplace(instance);
refresh();
_frozenTrackLifetime = instance->frozenErrorReceived(
) | rpl::start_with_next([=] {
if (!get<int>(u"freeze_since_date"_q, 0)) {
const auto now = crl::now();
if (!_lastFrozenRefresh
|| now > _lastFrozenRefresh + kRefreshTimeout) {
_lastFrozenRefresh = now;
refresh();
}
}
});
}, _lifetime);
}

View file

@ -124,6 +124,9 @@ private:
std::vector<QString> _startRefPrefixes;
crl::time _lastFrozenRefresh = 0;
rpl::lifetime _frozenTrackLifetime;
rpl::lifetime _lifetime;
};

View file

@ -160,15 +160,6 @@ Session::Session(
}
}, _lifetime);
#ifndef OS_MAC_STORE
appConfig().value(
) | rpl::start_with_next([=] {
_premiumPossible = !appConfig().get<bool>(
u"premium_purchase_blocked"_q,
true);
}, _lifetime);
#endif // OS_MAC_STORE
if (_settings->hadLegacyCallsPeerToPeerNobody()) {
api().userPrivacy().save(
Api::UserPrivacy::Key::CallsPeer2Peer,
@ -204,6 +195,27 @@ Session::Session(
_api->requestNotifySettings(MTP_inputNotifyBroadcasts());
Core::App().downloadManager().trackSession(this);
appConfig().value(
) | rpl::start_with_next([=] {
appConfigRefreshed();
}, _lifetime);
}
void Session::appConfigRefreshed() {
const auto &config = appConfig();
_frozen = FreezeInfo{
.since = config.get<int>(u"freeze_since_date"_q, 0),
.until = config.get<int>(u"freeze_until_date"_q, 0),
.appealUrl = config.get<QString>(u"freeze_appeal_url"_q, QString()),
};
#ifndef OS_MAC_STORE
_premiumPossible = !config.get<bool>(
u"premium_purchase_blocked"_q,
true);
#endif // OS_MAC_STORE
}
void Session::setTmpPassword(const QByteArray &password, TimeId validUntil) {
@ -431,6 +443,14 @@ Support::FastButtonsBots &Session::fastButtonsBots() const {
return *_fastButtonsBots;
}
FreezeInfo Session::frozen() const {
return _frozen.current();
}
rpl::producer<FreezeInfo> Session::frozenValue() const {
return _frozen.value();
}
void Session::addWindow(not_null<Window::SessionController*> controller) {
_windows.emplace(controller);
controller->lifetime().add([=] {

View file

@ -80,6 +80,19 @@ class Domain;
class SessionSettings;
class SendAsPeers;
struct FreezeInfo {
TimeId since = 0;
TimeId until = 0;
QString appealUrl;
explicit operator bool() const {
return since != 0;
}
friend inline bool operator==(
const FreezeInfo &,
const FreezeInfo &) = default;
};
class Session final : public base::has_weak_ptr {
public:
Session(
@ -236,12 +249,17 @@ public:
[[nodiscard]] Support::Templates &supportTemplates() const;
[[nodiscard]] Support::FastButtonsBots &fastButtonsBots() const;
[[nodiscard]] FreezeInfo frozen() const;
[[nodiscard]] rpl::producer<FreezeInfo> frozenValue() const;
[[nodiscard]] auto colorIndicesValue()
-> rpl::producer<Ui::ColorIndicesCompressed>;
private:
static constexpr auto kDefaultSaveDelay = crl::time(1000);
void appConfigRefreshed();
const UserId _userId;
const not_null<Account*> _account;
@ -288,6 +306,8 @@ private:
base::flat_set<not_null<Window::SessionController*>> _windows;
base::Timer _saveSettingsTimer;
rpl::variable<FreezeInfo> _frozen;
QByteArray _tmpPassword;
TimeId _tmpPasswordValidUntil = 0;

View file

@ -841,7 +841,9 @@ void ReplyArea::show(
peer
) | rpl::map([=](bool can) {
using namespace HistoryView::Controls;
return (can
return user->session().frozen()
? WriteRestriction{ .type = WriteRestrictionType::Frozen }
: (can
|| !user
|| !user->requiresPremiumToWrite()
|| user->session().premium())

View file

@ -100,6 +100,7 @@ public:
[[nodiscard]] auto nonPremiumDelayedRequests() const
-> rpl::producer<mtpRequestId>;
[[nodiscard]] rpl::producer<> frozenErrorReceived() const;
void restart();
void restart(ShiftedDcId shiftedDcId);
@ -286,6 +287,7 @@ private:
Fn<void(ShiftedDcId shiftedDcId)> _sessionResetHandler;
rpl::event_stream<mtpRequestId> _nonPremiumDelayedRequests;
rpl::event_stream<> _frozenErrorReceived;
base::Timer _checkDelayedTimer;
@ -562,6 +564,10 @@ auto Instance::Private::nonPremiumDelayedRequests() const
return _nonPremiumDelayedRequests.events();
}
rpl::producer<> Instance::Private::frozenErrorReceived() const {
return _frozenErrorReceived.events();
}
void Instance::Private::requestConfigIfOld() {
const auto timeout = _config->values().blockedMode
? kConfigBecomesOldForBlockedIn
@ -1593,6 +1599,8 @@ bool Instance::Private::onErrorDefault(
return true;
} else if (type == u"CONNECTION_LANG_CODE_INVALID"_q) {
Lang::CurrentCloudManager().resetToDefault();
} else if (type == u"FROZEN_METHOD_INVALID"_q) {
_frozenErrorReceived.fire({});
}
if (badGuestDc) _badGuestDcRequests.erase(requestId);
return false;
@ -1920,6 +1928,10 @@ rpl::producer<mtpRequestId> Instance::nonPremiumDelayedRequests() const {
return _private->nonPremiumDelayedRequests();
}
rpl::producer<> Instance::frozenErrorReceived() const {
return _private->frozenErrorReceived();
}
void Instance::requestConfigIfOld() {
_private->requestConfigIfOld();
}

View file

@ -142,6 +142,7 @@ public:
[[nodiscard]] auto nonPremiumDelayedRequests() const
-> rpl::producer<mtpRequestId>;
[[nodiscard]] rpl::producer<> frozenErrorReceived() const;
void syncHttpUnixtime();

View file

@ -177,6 +177,7 @@ menuIconUnique: icon {{ "menu/unique", menuIconColor }};
menuIconNftWear: icon {{ "menu/nft_wear", menuIconColor }};
menuIconNftTakeOff: icon {{ "menu/nft_takeoff", menuIconColor }};
menuIconShortcut: icon {{ "menu/shortcut", menuIconColor }};
menuIconHourglass: icon {{ "menu/hourglass", menuIconColor }};
menuIconTTLAny: icon {{ "menu/auto_delete_plain", menuIconColor }};
menuIconTTLAnyTextPosition: point(11px, 22px);