Track birthdays, edit birthday privacy.

This commit is contained in:
John Preston 2024-03-22 12:41:28 +04:00
parent 5e68dace9f
commit 59663d6661
15 changed files with 293 additions and 24 deletions

View file

@ -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";

View file

@ -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;
}

View file

@ -30,6 +30,7 @@ public:
ProfilePhoto,
Voices,
About,
Birthday,
};
enum class Option {
Everyone,

View file

@ -374,6 +374,7 @@ void EditPrivacyBox::setupContent() {
};
auto above = _controller->setupAboveWidget(
_window,
content,
rpl::duplicate(optionValue),
getDelegate()->outerContainer());

View file

@ -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) {

View file

@ -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

View file

@ -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

View file

@ -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; }

View file

@ -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()));

View file

@ -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

View file

@ -152,6 +152,10 @@ settingsPrivacySecurityPadding: 12px;
settingsPrivacySkip: 14px;
settingsPrivacySkipTop: 4px;
settingsPrivacyAddBirthday: FlatLabel(defaultFlatLabel) {
minWidth: 256px;
}
settingsCloudPasswordIconSize: 100px;
settingLocalPasscodeInputField: InputField(defaultInputField) {

View file

@ -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

View file

@ -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

View file

@ -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,

View file

@ -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