From 59663d66615c4b0b049216a7d435bccfa3975d16 Mon Sep 17 00:00:00 2001
From: John Preston <johnprestonmail@gmail.com>
Date: Fri, 22 Mar 2024 12:41:28 +0400
Subject: [PATCH] Track birthdays, edit birthday privacy.

---
 Telegram/Resources/langs/lang.strings         | 18 +++++
 Telegram/SourceFiles/api/api_user_privacy.cpp |  3 +
 Telegram/SourceFiles/api/api_user_privacy.h   |  1 +
 .../SourceFiles/boxes/edit_privacy_box.cpp    |  1 +
 Telegram/SourceFiles/boxes/edit_privacy_box.h |  1 +
 Telegram/SourceFiles/data/data_birthday.cpp   | 69 ++++++++++++++++
 Telegram/SourceFiles/data/data_birthday.h     | 38 +++++++++
 Telegram/SourceFiles/data/data_changes.h      | 31 +++----
 Telegram/SourceFiles/data/data_user.cpp       | 25 ++++++
 Telegram/SourceFiles/data/data_user.h         | 12 ++-
 Telegram/SourceFiles/settings/settings.style  |  4 +
 .../settings/settings_privacy_controllers.cpp | 81 +++++++++++++++++--
 .../settings/settings_privacy_controllers.h   | 25 ++++++
 .../settings/settings_privacy_security.cpp    |  6 ++
 Telegram/cmake/td_ui.cmake                    |  2 +
 15 files changed, 293 insertions(+), 24 deletions(-)
 create mode 100644 Telegram/SourceFiles/data/data_birthday.cpp
 create mode 100644 Telegram/SourceFiles/data/data_birthday.h

diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings
index 02ba53c30..ca78a2d9d 100644
--- a/Telegram/Resources/langs/lang.strings
+++ b/Telegram/Resources/langs/lang.strings
@@ -652,6 +652,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 "lng_settings_messages_privacy" = "Messages";
 "lng_settings_voices_privacy" = "Voice messages";
 "lng_settings_bio_privacy" = "Bio";
+"lng_settings_birthday_privacy" = "Date of Birth";
 "lng_settings_privacy_premium" = "Only subscribers of {link} can restrict receiving voice messages.";
 "lng_settings_privacy_premium_link" = "Telegram Premium";
 "lng_settings_passcode_disable" = "Disable Passcode";
@@ -664,6 +665,12 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 "lng_settings_phone_label" = "Phone number";
 "lng_settings_username_add" = "Add username";
 "lng_settings_username_about" = "Username lets people contact you on Telegram without needing your phone number.";
+"lng_settings_birthday_label" = "Date of Birth";
+"lng_settings_birthday_add" = "Add";
+"lng_settings_birthday_about" = "Choose who can see your birthday in {link}.";
+"lng_settings_birthday_about_link" = "Settings";
+"lng_settings_birthday_contacts" = "Only your contacts can see your birthday. {link}";
+"lng_settings_birthday_contacts_link" = "Change >";
 "lng_settings_add_account_about" = "You can add up to four accounts with different phone numbers.";
 "lng_settings_peer_to_peer_about" = "Disabling peer-to-peer will relay all calls through Telegram servers to avoid revealing your IP address, but may slightly decrease audio quality.";
 "lng_settings_advanced" = "Advanced";
@@ -1087,6 +1094,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 "lng_edit_privacy_everyone" = "Everybody";
 "lng_edit_privacy_contacts" = "My contacts";
 "lng_edit_privacy_close_friends" = "Close friends";
+"lng_edit_privacy_contacts_and_premium" = "Contacts and Premium";
 "lng_edit_privacy_nobody" = "Nobody";
 "lng_edit_privacy_premium" = "Premium users";
 "lng_edit_privacy_exceptions" = "Add exceptions";
@@ -1136,6 +1144,16 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 "lng_edit_privacy_about_always_title" = "Always allow";
 "lng_edit_privacy_about_never_title" = "Never allow";
 
+"lng_edit_privacy_birthday_title" = "Date of birth privacy";
+"lng_edit_privacy_birthday_header" = "Who can see my date of birth";
+"lng_edit_privacy_birthday_always_empty" = "Always allow";
+"lng_edit_privacy_birthday_never_empty" = "Never allow";
+"lng_edit_privacy_birthday_exceptions" = "These users will or will not be able to see your date of birth regardless of the settings above.";
+"lng_edit_privacy_birthday_always_title" = "Always allow";
+"lng_edit_privacy_birthday_never_title" = "Never allow";
+"lng_edit_privacy_birthday_yet" = "You haven't entered your date of birth yet.\n{link}";
+"lng_edit_privacy_birthday_yet_link" = "Add my birthday >";
+
 "lng_edit_privacy_calls_title" = "Voice calls privacy";
 "lng_edit_privacy_calls_header" = "Who can call you";
 "lng_edit_privacy_calls_always_empty" = "Always allow";
diff --git a/Telegram/SourceFiles/api/api_user_privacy.cpp b/Telegram/SourceFiles/api/api_user_privacy.cpp
index eaf6b8a08..ce9dc91f9 100644
--- a/Telegram/SourceFiles/api/api_user_privacy.cpp
+++ b/Telegram/SourceFiles/api/api_user_privacy.cpp
@@ -194,6 +194,7 @@ MTPInputPrivacyKey KeyToTL(UserPrivacy::Key key) {
 	case Key::ProfilePhoto: return MTP_inputPrivacyKeyProfilePhoto();
 	case Key::Voices: return MTP_inputPrivacyKeyVoiceMessages();
 	case Key::About: return MTP_inputPrivacyKeyAbout();
+	case Key::Birthday: return MTP_inputPrivacyKeyBirthday();
 	}
 	Unexpected("Key in Api::UserPrivacy::KetToTL.");
 }
@@ -221,6 +222,8 @@ std::optional<UserPrivacy::Key> TLToKey(mtpTypeId type) {
 	case mtpc_inputPrivacyKeyVoiceMessages: return Key::Voices;
 	case mtpc_privacyKeyAbout:
 	case mtpc_inputPrivacyKeyAbout: return Key::About;
+	case mtpc_privacyKeyBirthday:
+	case mtpc_inputPrivacyKeyBirthday: return Key::Birthday;
 	}
 	return std::nullopt;
 }
diff --git a/Telegram/SourceFiles/api/api_user_privacy.h b/Telegram/SourceFiles/api/api_user_privacy.h
index 4dc951f6a..884000fb7 100644
--- a/Telegram/SourceFiles/api/api_user_privacy.h
+++ b/Telegram/SourceFiles/api/api_user_privacy.h
@@ -30,6 +30,7 @@ public:
 		ProfilePhoto,
 		Voices,
 		About,
+		Birthday,
 	};
 	enum class Option {
 		Everyone,
diff --git a/Telegram/SourceFiles/boxes/edit_privacy_box.cpp b/Telegram/SourceFiles/boxes/edit_privacy_box.cpp
index 367742971..cd472746e 100644
--- a/Telegram/SourceFiles/boxes/edit_privacy_box.cpp
+++ b/Telegram/SourceFiles/boxes/edit_privacy_box.cpp
@@ -374,6 +374,7 @@ void EditPrivacyBox::setupContent() {
 	};
 
 	auto above = _controller->setupAboveWidget(
+		_window,
 		content,
 		rpl::duplicate(optionValue),
 		getDelegate()->outerContainer());
diff --git a/Telegram/SourceFiles/boxes/edit_privacy_box.h b/Telegram/SourceFiles/boxes/edit_privacy_box.h
index cfdc14ad7..da8bc98cf 100644
--- a/Telegram/SourceFiles/boxes/edit_privacy_box.h
+++ b/Telegram/SourceFiles/boxes/edit_privacy_box.h
@@ -62,6 +62,7 @@ public:
 	}
 
 	[[nodiscard]] virtual object_ptr<Ui::RpWidget> setupAboveWidget(
+			not_null<Window::SessionController*> controller,
 			not_null<QWidget*> parent,
 			rpl::producer<Option> option,
 			not_null<QWidget*> outerContainer) {
diff --git a/Telegram/SourceFiles/data/data_birthday.cpp b/Telegram/SourceFiles/data/data_birthday.cpp
new file mode 100644
index 000000000..4b9df693f
--- /dev/null
+++ b/Telegram/SourceFiles/data/data_birthday.cpp
@@ -0,0 +1,69 @@
+/*
+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 "data/data_birthday.h"
+
+namespace Data {
+namespace {
+
+[[nodiscard]] bool Validate(int day, int month, int year) {
+	if (year != 0 && (year < 1900 || year > 2100)) {
+		return false;
+	} else if (day < 1) {
+		return false;
+	} else if (month == 2) {
+		if (day == 29) {
+			return !year
+				|| (year % 4 == 0 && (year % 100 != 0 || year % 400 == 0));
+		}
+		return day <= 28;
+	} else if (month == 4 || month == 6 || month == 9 || month == 11) {
+		return day <= 30;
+	} else if (month > 0 && month <= 12) {
+		return day <= 31;
+	}
+	return false;
+}
+
+[[nodiscard]] int Serialize(int day, int month, int year) {
+	return day + month * 100 + year * 10000;
+}
+
+} // namespace
+
+Birthday::Birthday() = default;
+
+Birthday::Birthday(int day, int month, int year)
+: _value(Validate(day, month, year) ? Serialize(day, month, year) : 0) {
+}
+
+Birthday Birthday::FromSerialized(int value) {
+	return Birthday(value % 100, (value / 100) % 100, value / 10000);
+}
+
+int Birthday::serialize() const {
+	return _value;
+}
+
+bool Birthday::valid() const {
+	return _value != 0;
+}
+
+int Birthday::day() const {
+	return _value % 100;
+}
+
+int Birthday::month() const {
+	return (_value / 100) % 100;
+}
+
+int Birthday::year() const {
+	return _value / 10000;
+}
+
+} // namespace Data
+
diff --git a/Telegram/SourceFiles/data/data_birthday.h b/Telegram/SourceFiles/data/data_birthday.h
new file mode 100644
index 000000000..13a5e4cfe
--- /dev/null
+++ b/Telegram/SourceFiles/data/data_birthday.h
@@ -0,0 +1,38 @@
+/*
+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
+
+namespace Data {
+
+class Birthday final {
+public:
+	Birthday();
+	Birthday(int day, int month, int year = 0);
+
+	[[nodiscard]] static Birthday FromSerialized(int value);
+	[[nodiscard]] int serialize() const;
+
+	[[nodiscard]] bool valid() const;
+
+	[[nodiscard]] int day() const;
+	[[nodiscard]] int month() const;
+	[[nodiscard]] int year() const;
+
+	explicit operator bool() const {
+		return valid();
+	}
+
+	friend inline constexpr auto operator<=>(Birthday, Birthday) = default;
+	friend inline constexpr bool operator==(Birthday, Birthday) = default;
+
+private:
+	int _value = 0;
+
+};
+
+} // namespace Data
diff --git a/Telegram/SourceFiles/data/data_changes.h b/Telegram/SourceFiles/data/data_changes.h
index bd85b02fa..eeb80ccb7 100644
--- a/Telegram/SourceFiles/data/data_changes.h
+++ b/Telegram/SourceFiles/data/data_changes.h
@@ -89,27 +89,28 @@ struct PeerUpdate {
 		IsBot               = (1ULL << 27),
 		EmojiStatus         = (1ULL << 28),
 		BusinessDetails     = (1ULL << 29),
+		Birthday            = (1ULL << 30),
 
 		// For chats and channels
-		InviteLinks         = (1ULL << 30),
-		Members             = (1ULL << 31),
-		Admins              = (1ULL << 32),
-		BannedUsers         = (1ULL << 33),
-		Rights              = (1ULL << 34),
-		PendingRequests     = (1ULL << 35),
-		Reactions           = (1ULL << 36),
+		InviteLinks         = (1ULL << 31),
+		Members             = (1ULL << 32),
+		Admins              = (1ULL << 33),
+		BannedUsers         = (1ULL << 34),
+		Rights              = (1ULL << 35),
+		PendingRequests     = (1ULL << 36),
+		Reactions           = (1ULL << 37),
 
 		// For channels
-		ChannelAmIn         = (1ULL << 37),
-		StickersSet         = (1ULL << 38),
-		EmojiSet            = (1ULL << 39),
-		ChannelLinkedChat   = (1ULL << 40),
-		ChannelLocation     = (1ULL << 41),
-		Slowmode            = (1ULL << 42),
-		GroupCall           = (1ULL << 43),
+		ChannelAmIn         = (1ULL << 38),
+		StickersSet         = (1ULL << 39),
+		EmojiSet            = (1ULL << 40),
+		ChannelLinkedChat   = (1ULL << 41),
+		ChannelLocation     = (1ULL << 42),
+		Slowmode            = (1ULL << 43),
+		GroupCall           = (1ULL << 44),
 
 		// For iteration
-		LastUsedBit         = (1ULL << 43),
+		LastUsedBit         = (1ULL << 44),
 	};
 	using Flags = base::flags<Flag>;
 	friend inline constexpr auto is_flag_type(Flag) { return true; }
diff --git a/Telegram/SourceFiles/data/data_user.cpp b/Telegram/SourceFiles/data/data_user.cpp
index ebe681ac3..ea5d0dad1 100644
--- a/Telegram/SourceFiles/data/data_user.cpp
+++ b/Telegram/SourceFiles/data/data_user.cpp
@@ -482,6 +482,30 @@ void UserData::setCallsStatus(CallsStatus callsStatus) {
 	}
 }
 
+
+Data::Birthday UserData::birthday() const {
+	return _birthday;
+}
+
+void UserData::setBirthday(Data::Birthday value) {
+	if (_birthday != value) {
+		_birthday = value;
+		session().changes().peerUpdated(this, UpdateFlag::Birthday);
+	}
+}
+
+void UserData::setBirthday(const tl::conditional<MTPBirthday> &value) {
+	if (!value) {
+		setBirthday(Data::Birthday());
+	} else {
+		const auto &data = value->data();
+		setBirthday(Data::Birthday(
+			data.vday().v,
+			data.vmonth().v,
+			data.vyear().value_or_empty()));
+	}
+}
+
 bool UserData::hasCalls() const {
 	return (callsStatus() != CallsStatus::Disabled)
 		&& (callsStatus() != CallsStatus::Unknown);
@@ -598,6 +622,7 @@ void ApplyUserUpdate(not_null<UserData*> user, const MTPDuserFull &update) {
 		update.vbusiness_work_hours(),
 		update.vbusiness_location(),
 		update.vbusiness_intro()));
+	user->setBirthday(update.vbirthday());
 	if (user->isSelf()) {
 		user->owner().businessInfo().applyAwaySettings(
 			FromMTP(&user->owner(), update.vbusiness_away_message()));
diff --git a/Telegram/SourceFiles/data/data_user.h b/Telegram/SourceFiles/data/data_user.h
index cf9eafef7..fd992a0ef 100644
--- a/Telegram/SourceFiles/data/data_user.h
+++ b/Telegram/SourceFiles/data/data_user.h
@@ -7,6 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 */
 #pragma once
 
+#include "data/data_birthday.h"
 #include "data/data_peer.h"
 #include "data/data_chat_participant_status.h"
 #include "data/data_lastseen_status.h"
@@ -179,6 +180,10 @@ public:
 	bool hasCalls() const;
 	void setCallsStatus(CallsStatus callsStatus);
 
+	[[nodiscard]] Data::Birthday birthday() const;
+	void setBirthday(Data::Birthday value);
+	void setBirthday(const tl::conditional<MTPBirthday> &value);
+
 	std::unique_ptr<BotInfo> botInfo;
 
 	void setUnavailableReasons(
@@ -204,6 +209,10 @@ private:
 
 	Flags _flags;
 	Data::LastseenStatus _lastseen;
+	Data::Birthday _birthday;
+	int _commonChatsCount = 0;
+	ContactStatus _contactStatus = ContactStatus::Unknown;
+	CallsStatus _callsStatus = CallsStatus::Unknown;
 
 	Data::UsernamesInfo _username;
 
@@ -211,9 +220,6 @@ private:
 	std::vector<Data::UnavailableReason> _unavailableReasons;
 	QString _phone;
 	QString _privateForwardName;
-	ContactStatus _contactStatus = ContactStatus::Unknown;
-	CallsStatus _callsStatus = CallsStatus::Unknown;
-	int _commonChatsCount = 0;
 
 	uint64 _accessHash = 0;
 	static constexpr auto kInaccessibleAccessHashOld
diff --git a/Telegram/SourceFiles/settings/settings.style b/Telegram/SourceFiles/settings/settings.style
index 2545afbf9..9ad3e3954 100644
--- a/Telegram/SourceFiles/settings/settings.style
+++ b/Telegram/SourceFiles/settings/settings.style
@@ -152,6 +152,10 @@ settingsPrivacySecurityPadding: 12px;
 settingsPrivacySkip: 14px;
 settingsPrivacySkipTop: 4px;
 
+settingsPrivacyAddBirthday: FlatLabel(defaultFlatLabel) {
+	minWidth: 256px;
+}
+
 settingsCloudPasswordIconSize: 100px;
 
 settingLocalPasscodeInputField: InputField(defaultInputField) {
diff --git a/Telegram/SourceFiles/settings/settings_privacy_controllers.cpp b/Telegram/SourceFiles/settings/settings_privacy_controllers.cpp
index 36a4ef35a..b0657d2b1 100644
--- a/Telegram/SourceFiles/settings/settings_privacy_controllers.cpp
+++ b/Telegram/SourceFiles/settings/settings_privacy_controllers.cpp
@@ -975,6 +975,7 @@ auto ForwardsPrivacyController::exceptionsDescription() const
 }
 
 object_ptr<Ui::RpWidget> ForwardsPrivacyController::setupAboveWidget(
+		not_null<Window::SessionController*> controller,
 		not_null<QWidget*> parent,
 		rpl::producer<Option> optionValue,
 		not_null<QWidget*> outerContainer) {
@@ -982,7 +983,7 @@ object_ptr<Ui::RpWidget> ForwardsPrivacyController::setupAboveWidget(
 
 	auto message = GenerateForwardedItem(
 		delegate(),
-		_controller->session().data().history(
+		controller->session().data().history(
 			PeerData::kServiceNotificationsId),
 		tr::lng_edit_privacy_forwards_sample_message(tr::now));
 	const auto view = message.get();
@@ -1072,18 +1073,18 @@ object_ptr<Ui::RpWidget> ForwardsPrivacyController::setupAboveWidget(
 	) | rpl::start_with_next([=](QRect rect) {
 		// #TODO themes
 		Window::SectionWidget::PaintBackground(
-			_controller,
-			_controller->defaultChatTheme().get(), // #TODO themes
+			controller,
+			controller->defaultChatTheme().get(), // #TODO themes
 			widget,
 			rect);
 
 		Painter p(widget);
-		const auto theme = _controller->defaultChatTheme().get();
+		const auto theme = controller->defaultChatTheme().get();
 		auto context = theme->preparePaintContext(
 			_chatStyle.get(),
 			widget->rect(),
 			widget->rect(),
-			_controller->isGifPausedAtLeastFor(
+			controller->isGifPausedAtLeastFor(
 				Window::GifPauseReason::Layer));
 		p.translate(padding / 2, padding + view->marginBottom());
 		context.outbg = view->hasOutLayout();
@@ -1115,6 +1116,7 @@ rpl::producer<QString> ProfilePhotoPrivacyController::optionsTitleKey() const {
 }
 
 object_ptr<Ui::RpWidget> ProfilePhotoPrivacyController::setupAboveWidget(
+		not_null<Window::SessionController*> controller,
 		not_null<QWidget*> parent,
 		rpl::producer<Option> optionValue,
 		not_null<QWidget*> outerContainer) {
@@ -1475,7 +1477,74 @@ rpl::producer<QString> AboutPrivacyController::exceptionBoxTitle(
 
 auto AboutPrivacyController::exceptionsDescription() const
 -> rpl::producer<QString> {
-	return tr::lng_edit_privacy_about_exceptions();
+	return tr::lng_edit_privacy_birthday_exceptions();
+}
+
+UserPrivacy::Key BirthdayPrivacyController::key() const {
+	return Key::Birthday;
+}
+
+rpl::producer<QString> BirthdayPrivacyController::title() const {
+	return tr::lng_edit_privacy_birthday_title();
+}
+
+rpl::producer<QString> BirthdayPrivacyController::optionsTitleKey() const {
+	return tr::lng_edit_privacy_birthday_header();
+}
+
+rpl::producer<QString> BirthdayPrivacyController::exceptionButtonTextKey(
+	Exception exception) const {
+	switch (exception) {
+	case Exception::Always: return tr::lng_edit_privacy_birthday_always_empty();
+	case Exception::Never: return tr::lng_edit_privacy_birthday_never_empty();
+	}
+	Unexpected("Invalid exception value.");
+}
+
+rpl::producer<QString> BirthdayPrivacyController::exceptionBoxTitle(
+	Exception exception) const {
+	switch (exception) {
+	case Exception::Always: return tr::lng_edit_privacy_birthday_always_title();
+	case Exception::Never: return tr::lng_edit_privacy_birthday_never_title();
+	}
+	Unexpected("Invalid exception value.");
+}
+
+auto BirthdayPrivacyController::exceptionsDescription() const
+-> rpl::producer<QString> {
+	return tr::lng_edit_privacy_birthday_exceptions();
+}
+
+object_ptr<Ui::RpWidget> BirthdayPrivacyController::setupAboveWidget(
+		not_null<Window::SessionController*> controller,
+		not_null<QWidget*> parent,
+		rpl::producer<Option> optionValue,
+		not_null<QWidget*> outerContainer) {
+	const auto session = &controller->session();
+	const auto user = session->user();
+	auto result = object_ptr<Ui::SlideWrap<Ui::FlatLabel>>(
+		parent,
+		object_ptr<Ui::FlatLabel>(
+			parent,
+			tr::lng_edit_privacy_birthday_yet(
+				lt_link,
+				tr::lng_edit_privacy_birthday_yet_link(
+				) | Ui::Text::ToLink("internal:edit_birthday"),
+				Ui::Text::WithEntities),
+			st::settingsPrivacyAddBirthday),
+		st::boxRowPadding + style::margins(
+			0,
+			st::defaultVerticalListSkip,
+			0,
+			st::settingsPrivacySkipTop));
+	result->toggleOn(session->changes().peerFlagsValue(
+		user,
+		Data::PeerUpdate::Flag::Birthday
+	) | rpl::map([=] {
+		return !user->birthday();
+	}));
+	result->finishAnimating();
+	return result;
 }
 
 } // namespace Settings
diff --git a/Telegram/SourceFiles/settings/settings_privacy_controllers.h b/Telegram/SourceFiles/settings/settings_privacy_controllers.h
index 25920a1f0..23a727808 100644
--- a/Telegram/SourceFiles/settings/settings_privacy_controllers.h
+++ b/Telegram/SourceFiles/settings/settings_privacy_controllers.h
@@ -215,6 +215,7 @@ public:
 	rpl::producer<QString> exceptionsDescription() const override;
 
 	object_ptr<Ui::RpWidget> setupAboveWidget(
+		not_null<Window::SessionController*> controller,
 		not_null<QWidget*> parent,
 		rpl::producer<Option> optionValue,
 		not_null<QWidget*> outerContainer) override;
@@ -249,6 +250,7 @@ public:
 		rpl::producer<int> value) override;
 
 	object_ptr<Ui::RpWidget> setupAboveWidget(
+		not_null<Window::SessionController*> controller,
 		not_null<QWidget*> parent,
 		rpl::producer<Option> optionValue,
 		not_null<QWidget*> outerContainer) override;
@@ -314,4 +316,27 @@ public:
 
 };
 
+class BirthdayPrivacyController final : public EditPrivacyController {
+public:
+	using Option = EditPrivacyBox::Option;
+	using Exception = EditPrivacyBox::Exception;
+
+	Key key() const override;
+
+	rpl::producer<QString> title() const override;
+	rpl::producer<QString> optionsTitleKey() const override;
+	rpl::producer<QString> exceptionButtonTextKey(
+		Exception exception) const override;
+	rpl::producer<QString> exceptionBoxTitle(
+		Exception exception) const override;
+	rpl::producer<QString> exceptionsDescription() const override;
+
+	object_ptr<Ui::RpWidget> setupAboveWidget(
+		not_null<Window::SessionController*> controller,
+		not_null<QWidget*> parent,
+		rpl::producer<Option> optionValue,
+		not_null<QWidget*> outerContainer) override;
+
+};
+
 } // namespace Settings
diff --git a/Telegram/SourceFiles/settings/settings_privacy_security.cpp b/Telegram/SourceFiles/settings/settings_privacy_security.cpp
index 6d16cac01..1b70efbb4 100644
--- a/Telegram/SourceFiles/settings/settings_privacy_security.cpp
+++ b/Telegram/SourceFiles/settings/settings_privacy_security.cpp
@@ -135,6 +135,8 @@ QString PrivacyBase(Privacy::Key key, Privacy::Option option) {
 		case Option::Contacts: return tr::lng_edit_privacy_contacts(tr::now);
 		case Option::CloseFriends:
 			return tr::lng_edit_privacy_close_friends(tr::now);
+		case Option::ContactsAndPremium:
+			return tr::lng_edit_privacy_contacts_and_premium(tr::now);
 		case Option::Nobody: return tr::lng_edit_privacy_nobody(tr::now);
 		}
 		Unexpected("Value in Privacy::Option.");
@@ -355,6 +357,10 @@ void SetupPrivacy(
 		tr::lng_settings_bio_privacy(),
 		Key::About,
 		[] { return std::make_unique<AboutPrivacyController>(); });
+	add(
+		tr::lng_settings_birthday_privacy(),
+		Key::Birthday,
+		[] { return std::make_unique<BirthdayPrivacyController>(); });
 	add(
 		tr::lng_settings_forwards_privacy(),
 		Key::Forwards,
diff --git a/Telegram/cmake/td_ui.cmake b/Telegram/cmake/td_ui.cmake
index 0e98ad7b8..5a4b2bd13 100644
--- a/Telegram/cmake/td_ui.cmake
+++ b/Telegram/cmake/td_ui.cmake
@@ -76,6 +76,8 @@ PRIVATE
     countries/countries_instance.cpp
     countries/countries_instance.h
 
+    data/data_birthday.cpp
+    data/data_birthday.h
     data/data_statistics_chart.cpp
     data/data_statistics_chart.h
     data/data_subscription_option.h