diff --git a/Telegram/CMakeLists.txt b/Telegram/CMakeLists.txt
index 47b693e36..0050add99 100644
--- a/Telegram/CMakeLists.txt
+++ b/Telegram/CMakeLists.txt
@@ -1067,6 +1067,8 @@ PRIVATE
profile/profile_cover_drop_area.h
settings/settings_advanced.cpp
settings/settings_advanced.h
+ settings/settings_blocked_peers.cpp
+ settings/settings_blocked_peers.h
settings/settings_chat.cpp
settings/settings_chat.h
settings/settings_calls.cpp
diff --git a/Telegram/Resources/animations/blocked_peers_empty.tgs b/Telegram/Resources/animations/blocked_peers_empty.tgs
new file mode 100644
index 000000000..02d5a9de6
Binary files /dev/null and b/Telegram/Resources/animations/blocked_peers_empty.tgs differ
diff --git a/Telegram/Resources/icons/settings/blocked.png b/Telegram/Resources/icons/settings/blocked.png
new file mode 100644
index 000000000..394b9f289
Binary files /dev/null and b/Telegram/Resources/icons/settings/blocked.png differ
diff --git a/Telegram/Resources/icons/settings/blocked@2x.png b/Telegram/Resources/icons/settings/blocked@2x.png
new file mode 100644
index 000000000..ad15b0ed1
Binary files /dev/null and b/Telegram/Resources/icons/settings/blocked@2x.png differ
diff --git a/Telegram/Resources/icons/settings/blocked@3x.png b/Telegram/Resources/icons/settings/blocked@3x.png
new file mode 100644
index 000000000..64ff47658
Binary files /dev/null and b/Telegram/Resources/icons/settings/blocked@3x.png differ
diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings
index 0a07b55d1..19f288c59 100644
--- a/Telegram/Resources/langs/lang.strings
+++ b/Telegram/Resources/langs/lang.strings
@@ -799,6 +799,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_blocked_list_confirm_text" = "Do you want to block {name} from messaging and calling you on Telegram?";
"lng_blocked_list_confirm_clear" = "Delete this chat";
"lng_blocked_list_confirm_ok" = "Block";
+"lng_blocked_list_empty_title" = "No blocked users";
+"lng_blocked_list_empty_description" = "You haven't blocked anyone yet.";
+"lng_blocked_list_subtitle#one" = "{count} blocked user";
+"lng_blocked_list_subtitle#other" = "{count} blocked users";
"lng_edit_privacy_everyone" = "Everybody";
"lng_edit_privacy_contacts" = "My contacts";
diff --git a/Telegram/Resources/qrc/telegram/animations.qrc b/Telegram/Resources/qrc/telegram/animations.qrc
index eac41dc7f..a750dc771 100644
--- a/Telegram/Resources/qrc/telegram/animations.qrc
+++ b/Telegram/Resources/qrc/telegram/animations.qrc
@@ -2,4 +2,7 @@
../../animations/change_number.tgs
+
+ ../../animations/blocked_peers_empty.tgs
+
diff --git a/Telegram/SourceFiles/boxes/boxes.style b/Telegram/SourceFiles/boxes/boxes.style
index 98f306627..8c1456e84 100644
--- a/Telegram/SourceFiles/boxes/boxes.style
+++ b/Telegram/SourceFiles/boxes/boxes.style
@@ -602,6 +602,9 @@ changePhoneError: FlatLabel(changePhoneLabel) {
textFg: boxTextFgError;
}
+blockedUsersListSubtitleAddPadding: margins(0px, 1px, 0px, -14px);
+blockedUsersListIconPadding: margins(0px, 34px, 0px, 5px);
+
adminLogFilterUserpicLeft: 15px;
adminLogFilterLittleSkip: 16px;
adminLogFilterCheckbox: Checkbox(defaultBoxCheckbox) {
diff --git a/Telegram/SourceFiles/settings/settings.style b/Telegram/SourceFiles/settings/settings.style
index 2c08e93a2..0d2b88e93 100644
--- a/Telegram/SourceFiles/settings/settings.style
+++ b/Telegram/SourceFiles/settings/settings.style
@@ -90,6 +90,7 @@ settingsIconPosition: icon {{ "settings/position", settingsIconFg }};
settingsIconPin: icon {{ "settings/pin", settingsIconFg }};
settingsIconDownload: icon {{ "settings/download", settingsIconFg }};
settingsIconMention: icon {{ "settings/mention", settingsIconFg }};
+settingsIconBlocked: icon {{ "settings/blocked", settingsIconFg }};
settingsCheckbox: Checkbox(defaultBoxCheckbox) {
textPosition: point(15px, 1px);
diff --git a/Telegram/SourceFiles/settings/settings_blocked_peers.cpp b/Telegram/SourceFiles/settings/settings_blocked_peers.cpp
new file mode 100644
index 000000000..522b81fca
--- /dev/null
+++ b/Telegram/SourceFiles/settings/settings_blocked_peers.cpp
@@ -0,0 +1,211 @@
+/*
+This file is part of Telegram Desktop,
+the official desktop application for the Telegram messaging service.
+
+For license and copyright information please follow this link:
+https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
+*/
+#include "settings/settings_blocked_peers.h"
+
+#include "api/api_blocked_peers.h"
+#include "apiwrap.h"
+#include "data/data_changes.h"
+#include "data/data_peer.h"
+#include "lang/lang_keys.h"
+#include "lottie/lottie_icon.h"
+#include "main/main_session.h"
+#include "settings/settings_privacy_controllers.h"
+#include "ui/widgets/buttons.h"
+#include "ui/wrap/padding_wrap.h"
+#include "ui/wrap/slide_wrap.h"
+#include "ui/wrap/vertical_layout.h"
+#include "window/window_session_controller.h"
+#include "styles/style_settings.h"
+#include "styles/style_boxes.h"
+
+namespace Settings {
+
+Blocked::Blocked(
+ QWidget *parent,
+ not_null controller)
+: Section(parent)
+, _controller(controller) {
+
+ setupContent();
+
+ {
+ auto padding = st::changePhoneIconPadding;
+ padding.setBottom(padding.top());
+ _loading = base::make_unique_q>(
+ this,
+ object_ptr>(
+ this,
+ object_ptr(
+ this,
+ tr::lng_contacts_loading(),
+ st::changePhoneDescription),
+ std::move(padding)));
+ Ui::ResizeFitChild(this, _loading.get());
+ }
+
+ _controller->session().api().blockedPeers().slice(
+ ) | rpl::start_with_next([=](const Api::BlockedPeers::Slice &slice) {
+ checkTotal(slice.total);
+ }, lifetime());
+
+ _controller->session().changes().peerUpdates(
+ Data::PeerUpdate::Flag::IsBlocked
+ ) | rpl::start_with_next([=](const Data::PeerUpdate &update) {
+ if (update.peer->isBlocked()) {
+ checkTotal(1);
+ }
+ }, lifetime());
+}
+
+rpl::producer Blocked::title() {
+ return tr::lng_settings_blocked_users();
+}
+
+QPointer Blocked::createPinnedToTop(not_null parent) {
+ const auto content = Ui::CreateChild(parent.get());
+
+ AddSkip(content);
+
+ AddButton(
+ content,
+ tr::lng_blocked_list_add(),
+ st::settingsButton,
+ { &st::settingsIconBlocked, kIconLightBlue }
+ )->addClickHandler([=] {
+ BlockedBoxController::BlockNewPeer(_controller);
+ });
+
+ AddSkip(content);
+ AddDividerText(content, tr::lng_blocked_list_about());
+
+ {
+ const auto subtitle = content->add(
+ object_ptr>(
+ content,
+ object_ptr(content)))->setDuration(0);
+ AddSkip(subtitle->entity());
+ auto subtitleText = _countBlocked.value(
+ ) | rpl::map([=](int count) {
+ return tr::lng_blocked_list_subtitle(tr::now, lt_count, count);
+ });
+ AddSubsectionTitle(
+ subtitle->entity(),
+ rpl::duplicate(subtitleText),
+ st::blockedUsersListSubtitleAddPadding);
+ subtitle->toggleOn(
+ rpl::merge(
+ _emptinessChanges.events() | rpl::map(!rpl::mappers::_1),
+ _countBlocked.value() | rpl::map(rpl::mappers::_1 > 0)
+ ) | rpl::distinct_until_changed());
+
+ // Workaround.
+ std::move(
+ subtitleText
+ ) | rpl::start_with_next([=] {
+ subtitle->entity()->resizeToWidth(content->width());
+ }, subtitle->lifetime());
+ }
+
+ return Ui::MakeWeak(not_null{ content });
+}
+
+void Blocked::setupContent() {
+ using namespace rpl::mappers;
+ const auto container = Ui::CreateChild(this);
+
+ const auto listWrap = container->add(
+ object_ptr>(
+ container,
+ object_ptr(container)));
+ listWrap->toggleOn(
+ _emptinessChanges.events_starting_with(true) | rpl::map(!_1),
+ anim::type::instant);
+
+ {
+ struct State {
+ std::unique_ptr controller;
+ std::unique_ptr delegate;
+ };
+
+ auto controller = std::make_unique(_controller);
+ const auto content = listWrap->entity()->add(
+ object_ptr(this, controller.get()));
+
+ const auto state = content->lifetime().make_state();
+ state->controller = std::move(controller);
+ state->delegate = std::make_unique();
+
+ state->delegate->setContent(content);
+ state->controller->setDelegate(state->delegate.get());
+
+ state->controller->rowsCountChanges(
+ ) | rpl::start_with_next([=](int total) {
+ _countBlocked = total;
+ checkTotal(total);
+ }, content->lifetime());
+ _countBlocked = content->fullRowsCount();
+ }
+
+ const auto emptyWrap = container->add(
+ object_ptr>(
+ container,
+ object_ptr(container)));
+ emptyWrap->toggleOn(
+ _emptinessChanges.events_starting_with(false),
+ anim::type::instant);
+
+ {
+ const auto content = emptyWrap->entity();
+ auto icon = CreateLottieIcon(content, {
+ .name = u"blocked_peers_empty"_q,
+ .sizeOverride = {
+ st::changePhoneIconSize,
+ st::changePhoneIconSize,
+ },
+ }, st::blockedUsersListIconPadding);
+ content->add(std::move(icon.widget));
+
+ _showFinished.events(
+ ) | rpl::start_with_next([animate = std::move(icon.animate)] {
+ animate();
+ }, content->lifetime());
+
+ content->add(
+ object_ptr>(
+ content,
+ object_ptr(
+ content,
+ tr::lng_blocked_list_empty_title(),
+ st::changePhoneTitle)),
+ st::changePhoneTitlePadding);
+
+ content->add(
+ object_ptr>(
+ content,
+ object_ptr(
+ content,
+ tr::lng_blocked_list_empty_description(),
+ st::changePhoneDescription)),
+ st::changePhoneDescriptionPadding);
+
+ AddSkip(content, st::blockedUsersListIconPadding.top());
+ }
+
+ Ui::ResizeFitChild(this, container);
+}
+
+void Blocked::checkTotal(int total) {
+ _loading = nullptr;
+ _emptinessChanges.fire(total <= 0);
+}
+
+void Blocked::showFinished() {
+ _showFinished.fire({});
+}
+
+} // namespace Settings
diff --git a/Telegram/SourceFiles/settings/settings_blocked_peers.h b/Telegram/SourceFiles/settings/settings_blocked_peers.h
new file mode 100644
index 000000000..a5642faa9
--- /dev/null
+++ b/Telegram/SourceFiles/settings/settings_blocked_peers.h
@@ -0,0 +1,46 @@
+/*
+This file is part of Telegram Desktop,
+the official desktop application for the Telegram messaging service.
+
+For license and copyright information please follow this link:
+https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
+*/
+#pragma once
+
+#include "settings/settings_common.h"
+
+namespace Window {
+class Controller;
+} // namespace Window
+
+namespace Settings {
+
+class Blocked : public Section {
+public:
+ Blocked(
+ QWidget *parent,
+ not_null controller);
+
+ void showFinished() override;
+
+ [[nodiscard]] rpl::producer title() override;
+
+ [[nodiscard]] QPointer createPinnedToTop(
+ not_null parent) override;
+
+private:
+ void setupContent();
+ void checkTotal(int total);
+
+ const not_null _controller;
+
+ base::unique_qptr _loading;
+
+ rpl::variable _countBlocked;
+
+ rpl::event_stream<> _showFinished;
+ rpl::event_stream _emptinessChanges;
+
+};
+
+} // namespace Settings
diff --git a/Telegram/SourceFiles/settings/settings_common.cpp b/Telegram/SourceFiles/settings/settings_common.cpp
index 92cf74939..9c1967322 100644
--- a/Telegram/SourceFiles/settings/settings_common.cpp
+++ b/Telegram/SourceFiles/settings/settings_common.cpp
@@ -264,7 +264,7 @@ LottieIcon CreateLottieIcon(
raw->lifetime().add([kept = std::move(owned)]{});
const auto animate = [=] {
- icon->animate([=] { raw->update(); }, 0, icon->framesCount());
+ icon->animate([=] { raw->update(); }, 0, icon->framesCount() - 1);
};
raw->paintRequest(
) | rpl::start_with_next([=] {
diff --git a/Telegram/SourceFiles/settings/settings_privacy_controllers.cpp b/Telegram/SourceFiles/settings/settings_privacy_controllers.cpp
index 7b6c1a6a2..b09be4621 100644
--- a/Telegram/SourceFiles/settings/settings_privacy_controllers.cpp
+++ b/Telegram/SourceFiles/settings/settings_privacy_controllers.cpp
@@ -383,6 +383,7 @@ void BlockedBoxController::handleBlockedEvent(not_null user) {
} else if (auto row = delegate()->peerListFindRow(user->id.value)) {
delegate()->peerListRemoveRow(row);
delegate()->peerListRefreshRows();
+ _rowsCountChanges.fire(delegate()->peerListFullRowsCount());
}
}
@@ -408,6 +409,7 @@ bool BlockedBoxController::appendRow(not_null peer) {
return false;
}
delegate()->peerListAppendRow(createRow(peer));
+ _rowsCountChanges.fire(delegate()->peerListFullRowsCount());
return true;
}
@@ -416,6 +418,7 @@ bool BlockedBoxController::prependRow(not_null peer) {
return false;
}
delegate()->peerListPrependRow(createRow(peer));
+ _rowsCountChanges.fire(delegate()->peerListFullRowsCount());
return true;
}
@@ -440,6 +443,10 @@ std::unique_ptr BlockedBoxController::createRow(
return row;
}
+rpl::producer BlockedBoxController::rowsCountChanges() const {
+ return _rowsCountChanges.events();
+}
+
PhoneNumberPrivacyController::PhoneNumberPrivacyController(
not_null controller)
: _controller(controller) {
diff --git a/Telegram/SourceFiles/settings/settings_privacy_controllers.h b/Telegram/SourceFiles/settings/settings_privacy_controllers.h
index 240724607..60d26e2dc 100644
--- a/Telegram/SourceFiles/settings/settings_privacy_controllers.h
+++ b/Telegram/SourceFiles/settings/settings_privacy_controllers.h
@@ -33,6 +33,8 @@ public:
void rowRightActionClicked(not_null row) override;
void loadMoreRows() override;
+ [[nodiscard]] rpl::producer rowsCountChanges() const;
+
static void BlockNewPeer(not_null window);
private:
@@ -50,6 +52,8 @@ private:
base::has_weak_ptr _guard;
+ rpl::event_stream _rowsCountChanges;
+
};
class PhoneNumberPrivacyController : public EditPrivacyController {
diff --git a/Telegram/SourceFiles/settings/settings_privacy_security.cpp b/Telegram/SourceFiles/settings/settings_privacy_security.cpp
index 22af983bc..f0507396e 100644
--- a/Telegram/SourceFiles/settings/settings_privacy_security.cpp
+++ b/Telegram/SourceFiles/settings/settings_privacy_security.cpp
@@ -13,11 +13,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "api/api_self_destruct.h"
#include "api/api_sensitive_content.h"
#include "api/api_global_privacy.h"
+#include "settings/settings_blocked_peers.h"
#include "settings/settings_common.h"
#include "settings/settings_privacy_controllers.h"
#include "base/timer_rpl.h"
#include "base/unixtime.h"
-#include "boxes/peer_list_box.h"
#include "boxes/edit_privacy_box.h"
#include "boxes/passcode_box.h"
#include "boxes/auto_lock_box.h"
@@ -772,17 +772,7 @@ void SetupBlockedList(
st::settingsButton,
{ &st::settingsIconMinus, kIconRed });
blockedPeers->addClickHandler([=] {
- const auto initBox = [=](not_null box) {
- box->addButton(tr::lng_close(), [=] {
- box->closeBox();
- });
- box->addLeftButton(tr::lng_blocked_list_add(), [=] {
- BlockedBoxController::BlockNewPeer(controller);
- });
- };
- controller->show(Box(
- std::make_unique(controller),
- initBox));
+ showOther(Blocked::Id());
});
std::move(
updateTrigger