Implement birthday info / privacy edit boxes.

This commit is contained in:
John Preston 2024-03-22 14:52:10 +04:00
parent 59663d6661
commit c82d7bd909
12 changed files with 439 additions and 18 deletions

View file

@ -671,6 +671,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"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_birthday_saved" = "Your date of birth was updated.";
"lng_settings_birthday_reset" = "Reset";
"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";

View file

@ -21,17 +21,21 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "core/click_handler_types.h"
#include "boxes/background_preview_box.h"
#include "ui/boxes/confirm_box.h"
#include "ui/boxes/edit_birthday_box.h"
#include "boxes/share_box.h"
#include "boxes/connection_box.h"
#include "boxes/edit_privacy_box.h"
#include "boxes/premium_preview_box.h"
#include "boxes/sticker_set_box.h"
#include "boxes/sessions_box.h"
#include "boxes/language_box.h"
#include "passport/passport_form_controller.h"
#include "ui/toast/toast.h"
#include "data/data_session.h"
#include "data/data_document.h"
#include "data/data_birthday.h"
#include "data/data_channel.h"
#include "data/data_document.h"
#include "data/data_session.h"
#include "data/data_user.h"
#include "media/player/media_player_instance.h"
#include "media/view/media_view_open_common.h"
#include "window/window_session_controller.h"
@ -44,6 +48,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "settings/settings_global_ttl.h"
#include "settings/settings_folders.h"
#include "settings/settings_main.h"
#include "settings/settings_privacy_controllers.h"
#include "settings/settings_privacy_security.h"
#include "settings/settings_chat.h"
#include "settings/settings_premium.h"
@ -669,6 +674,59 @@ bool ShowSearchTagsPromo(
return true;
}
bool ShowEditBirthday(
Window::SessionController *controller,
const Match &match,
const QVariant &context) {
if (!controller) {
return false;
}
const auto user = controller->session().user();
const auto save = [=](Data::Birthday result) {
user->setBirthday(result);
using Flag = MTPaccount_UpdateBirthday::Flag;
using BFlag = MTPDbirthday::Flag;
user->session().api().request(MTPaccount_UpdateBirthday(
MTP_flags(result ? Flag::f_birthday : Flag()),
MTP_birthday(
MTP_flags(result.year() ? BFlag::f_year : BFlag()),
MTP_int(result.day()),
MTP_int(result.month()),
MTP_int(result.year()))
)).done(crl::guard(controller, [=] {
controller->showToast(tr::lng_settings_birthday_saved(tr::now));
})).fail(crl::guard(controller, [=](const MTP::Error &error) {
controller->showToast(u"Error: "_q + error.type());
})).send();
};
controller->show(Box(
Ui::EditBirthdayBox,
user->birthday(),
save));
return true;
}
bool ShowEditBirthdayPrivacy(
Window::SessionController *controller,
const Match &match,
const QVariant &context) {
if (!controller) {
return false;
}
auto syncLifetime = controller->session().api().userPrivacy().value(
Api::UserPrivacy::Key::Birthday
) | rpl::take(
1
) | rpl::start_with_next([=](const Api::UserPrivacy::Rule &value) {
controller->show(Box<EditPrivacyBox>(
controller,
std::make_unique<::Settings::BirthdayPrivacyController>(),
value));
});
return true;
}
void ExportTestChatTheme(
not_null<Window::SessionController*> controller,
not_null<const Data::CloudTheme*> theme) {
@ -1034,9 +1092,17 @@ const std::vector<LocalUrlHandler> &InternalUrlHandlers() {
CopyPeerId
},
{
u"about_tags"_q,
u"^about_tags$"_q,
ShowSearchTagsPromo
},
{
u"^edit_birthday$"_q,
ShowEditBirthday,
},
{
u"^edit_privacy_birthday$"_q,
ShowEditBirthdayPrivacy,
},
};
return Result;
}

View file

@ -11,7 +11,8 @@ namespace Data {
namespace {
[[nodiscard]] bool Validate(int day, int month, int year) {
if (year != 0 && (year < 1900 || year > 2100)) {
if (year != 0
&& (year < Birthday::kYearMin || year > Birthday::kYearMax)) {
return false;
} else if (day < 1) {
return false;

View file

@ -30,6 +30,9 @@ public:
friend inline constexpr auto operator<=>(Birthday, Birthday) = default;
friend inline constexpr bool operator==(Birthday, Birthday) = default;
static constexpr auto kYearMin = 1900;
static constexpr auto kYearMax = 2100;
private:
int _value = 0;

View file

@ -343,6 +343,15 @@ rpl::producer<bool> CanAddContactValue(not_null<UserData*> user) {
) | rpl::map(!_1);
}
rpl::producer<Data::Birthday> BirthdayValue(not_null<UserData*> user) {
return user->session().changes().peerFlagsValue(
user,
UpdateFlag::Birthday
) | rpl::map([=] {
return user->birthday();
});
}
rpl::producer<bool> AmInChannelValue(not_null<ChannelData*> channel) {
return channel->session().changes().peerFlagsValue(
channel,

View file

@ -17,6 +17,7 @@ struct ChannelLocation;
namespace Data {
class ForumTopic;
class Thread;
class Birthday;
} // namespace Data
namespace Main {
@ -83,6 +84,8 @@ rpl::producer<not_null<PeerData*>> MigratedOrMeValue(
not_null<UserData*> user);
[[nodiscard]] rpl::producer<bool> CanAddContactValue(
not_null<UserData*> user);
[[nodiscard]] rpl::producer<Data::Birthday> BirthdayValue(
not_null<UserData*> user);
[[nodiscard]] rpl::producer<bool> AmInChannelValue(
not_null<ChannelData*> channel);
[[nodiscard]] rpl::producer<int> MembersCountValue(not_null<PeerData*> peer);

View file

@ -25,6 +25,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/vertical_list.h"
#include "ui/unread_badge_paint.h"
#include "core/application.h"
#include "core/click_handler_types.h"
#include "core/core_settings.h"
#include "chat_helpers/emoji_suggestions_widget.h"
#include "boxes/add_contact_box.h"
@ -48,6 +49,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "apiwrap.h"
#include "api/api_peer_photo.h"
#include "api/api_user_names.h"
#include "api/api_user_privacy.h"
#include "base/call_delayed.h"
#include "base/options.h"
#include "base/unixtime.h"
@ -353,6 +355,84 @@ void AddRow(
}, wrap->lifetime());
}
void SetupBirthday(
not_null<Ui::VerticalLayout*> container,
not_null<Window::SessionController*> controller,
not_null<UserData*> self) {
const auto session = &self->session();
Ui::AddSkip(container);
auto value = rpl::combine(
Info::Profile::BirthdayValue(self),
tr::lng_settings_birthday_add()
) | rpl::map([](Data::Birthday birthday, const QString &add) {
const auto wrap = &Ui::Text::WithEntities;
if (const auto year = birthday.year()) {
return wrap(tr::lng_month_day_year(
tr::now,
lt_month,
Lang::MonthSmall(birthday.month())(tr::now),
lt_day,
QString::number(birthday.day()),
lt_year,
QString::number(year)));
} else if (birthday) {
return wrap(tr::lng_month_day(
tr::now,
lt_month,
Lang::MonthSmall(birthday.month())(tr::now),
lt_day,
QString::number(birthday.day())));
}
auto result = TextWithEntities{ add };
result.entities.push_back({
EntityType::CustomUrl,
0,
int(add.size()),
"internal:edit_username" });
return result;
});
const auto edit = [=] {
Core::App().openInternalUrl(
u"internal:edit_birthday"_q,
QVariant::fromValue(ClickHandlerContext{
.sessionWindow = base::make_weak(controller),
}));
};
AddRow(
container,
tr::lng_settings_birthday_label(),
std::move(value),
tr::lng_mediaview_copy(tr::now),
edit,
{ &st::menuIconGiftPremium });
const auto key = Api::UserPrivacy::Key::Birthday;
session->api().userPrivacy().reload(key);
auto isExactlyContacts = session->api().userPrivacy().value(
key
) | rpl::map([=](const Api::UserPrivacy::Rule &value) {
return (value.option == Api::UserPrivacy::Option::Contacts)
&& value.always.empty()
&& value.never.empty();
}) | rpl::distinct_until_changed();
Ui::AddSkip(container);
Ui::AddDividerText(container, rpl::conditional(
std::move(isExactlyContacts),
tr::lng_settings_birthday_contacts(
lt_link,
tr::lng_settings_birthday_contacts_link(
) | Ui::Text::ToLink(u"internal:edit_privacy_birthday"_q),
Ui::Text::WithEntities),
tr::lng_settings_birthday_about(
lt_link,
tr::lng_settings_birthday_about_link(
) | Ui::Text::ToLink(u"internal:edit_privacy_birthday"_q),
Ui::Text::WithEntities)));
}
void SetupRows(
not_null<Ui::VerticalLayout*> container,
not_null<Window::SessionController*> controller,
@ -954,6 +1034,7 @@ void Information::setupContent(
SetupPhoto(content, controller, self);
SetupBio(content, self);
SetupRows(content, controller, self);
SetupBirthday(content, controller, self);
SetupAccountsWrap(content, controller);
Ui::ResizeFitChild(this, content);

View file

@ -53,6 +53,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "styles/style_chat_helpers.h"
#include "styles/style_settings.h"
#include "styles/style_info.h"
#include "styles/style_layers.h"
#include "styles/style_menu_icons.h"
#include <QtGui/QGuiApplication>
@ -1522,21 +1523,19 @@ object_ptr<Ui::RpWidget> BirthdayPrivacyController::setupAboveWidget(
not_null<QWidget*> outerContainer) {
const auto session = &controller->session();
const auto user = session->user();
auto result = object_ptr<Ui::SlideWrap<Ui::FlatLabel>>(
auto result = object_ptr<Ui::SlideWrap<Ui::DividerLabel>>(
parent,
object_ptr<Ui::FlatLabel>(
object_ptr<Ui::DividerLabel>(
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));
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::boxDividerLabel),
st::defaultBoxDividerLabelPadding));
result->toggleOn(session->changes().peerFlagsValue(
user,
Data::PeerUpdate::Flag::Birthday

View file

@ -30,7 +30,7 @@ public:
explicit BlockedBoxController(
not_null<Window::SessionController*> window);
Main::Session &session() const override;
::Main::Session &session() const override;
void prepare() override;
void rowClicked(not_null<PeerListRow*> row) override;
void rowRightActionClicked(not_null<PeerListRow*> row) override;

View file

@ -0,0 +1,232 @@
/*
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 "ui/boxes/edit_birthday_box.h"
#include "base/event_filter.h"
#include "data/data_birthday.h"
#include "lang/lang_keys.h"
#include "ui/layers/generic_box.h"
#include "ui/widgets/vertical_drum_picker.h"
#include "styles/style_layers.h"
#include "styles/style_settings.h"
#include <QtCore/QDate>
namespace Ui {
class GenericBox;
void EditBirthdayBox(
not_null<Ui::GenericBox*> box,
Data::Birthday current,
Fn<void(Data::Birthday)> save) {
box->setWidth(st::boxWideWidth);
const auto content = box->addRow(object_ptr<Ui::FixedHeightWidget>(
box,
st::settingsWorkingHoursPicker));
const auto font = st::boxTextFont;
const auto itemHeight = st::settingsWorkingHoursPickerItemHeight;
const auto picker = [=](
int count,
int startIndex,
Fn<void(QPainter &p, QRectF rect, int index)> paint) {
auto paintCallback = [=](
QPainter &p,
int index,
float64 y,
float64 distanceFromCenter,
int outerWidth) {
const auto r = QRectF(0, y, outerWidth, itemHeight);
const auto progress = std::abs(distanceFromCenter);
const auto revProgress = 1. - progress;
p.save();
p.translate(r.center());
constexpr auto kMinYScale = 0.2;
const auto yScale = kMinYScale
+ (1. - kMinYScale) * anim::easeOutCubic(1., revProgress);
p.scale(1., yScale);
p.translate(-r.center());
p.setOpacity(revProgress);
p.setFont(font);
p.setPen(st::defaultFlatLabel.textFg);
paint(p, r, index);
p.restore();
};
return Ui::CreateChild<Ui::VerticalDrumPicker>(
content,
std::move(paintCallback),
count,
itemHeight,
startIndex);
};
const auto nowDate = QDate::currentDate();
const auto nowYear = nowDate.year();
const auto nowMonth = nowDate.month();
const auto nowDay = nowDate.day();
const auto now = Data::Birthday(nowDay, nowMonth, nowYear);
const auto max = current.year() ? std::max(now, current) : now;
const auto maxYear = max.year();
const auto minYear = Data::Birthday::kYearMin;
const auto yearsCount = (maxYear - minYear + 2); // Last - not set.
const auto yearsStartIndex = current.year()
? (current.year() - minYear)
: (yearsCount - 1);
const auto yearsPaint = [=](QPainter &p, QRectF rect, int index) {
p.drawText(
rect,
(index < yearsCount - 1
? QString::number(minYear + index)
: QString::fromUtf8("\xe2\x80\x94")),
style::al_center);
};
const auto years = picker(yearsCount, yearsStartIndex, yearsPaint);
struct State {
rpl::variable<Ui::VerticalDrumPicker*> months;
rpl::variable<Ui::VerticalDrumPicker*> days;
};
const auto state = content->lifetime().make_state<State>();
// years->value() is valid only after size is set.
rpl::combine(
content->sizeValue(),
state->months.value(),
state->days.value()
) | rpl::start_with_next([=](
QSize s,
Ui::VerticalDrumPicker *months,
Ui::VerticalDrumPicker *days) {
const auto half = s.width() / 2;
years->setGeometry(half * 3 / 2, 0, half / 2, s.height());
if (months) {
months->setGeometry(half / 2, 0, half, s.height());
}
if (days) {
days->setGeometry(0, 0, half / 2, s.height());
}
}, content->lifetime());
Ui::SendPendingMoveResizeEvents(years);
years->value() | rpl::start_with_next([=](int yearsIndex) {
const auto year = (yearsIndex == yearsCount - 1)
? 0
: minYear + yearsIndex;
const auto monthsCount = (year == maxYear)
? max.month()
: 12;
const auto monthsStartIndex = std::clamp(
(state->months.current()
? state->months.current()->index()
: current.month()
? (current.month() - 1)
: (now.month() - 1)),
0,
monthsCount - 1);
const auto monthsPaint = [=](QPainter &p, QRectF rect, int index) {
p.drawText(
rect,
Lang::Month(index + 1)(tr::now),
style::al_center);
};
const auto updated = picker(
monthsCount,
monthsStartIndex,
monthsPaint);
delete state->months.current();
state->months = updated;
state->months.current()->show();
}, years->lifetime());
Ui::SendPendingMoveResizeEvents(state->months.current());
state->months.value() | rpl::map([=](Ui::VerticalDrumPicker *picker) {
return picker ? picker->value() : rpl::single(current.month()
? (current.month() - 1)
: (now.month() - 1));
}) | rpl::flatten_latest() | rpl::start_with_next([=](int monthIndex) {
const auto month = monthIndex + 1;
const auto yearsIndex = years->index();
const auto year = (yearsIndex == yearsCount - 1)
? 0
: minYear + yearsIndex;
const auto daysCount = (year == maxYear && month == max.month())
? max.day()
: (month == 2)
? ((!year || ((year % 4) && (!(year % 100) || (year % 400))))
? 29
: 28)
: ((month == 4) || (month == 6) || (month == 9) || (month == 11))
? 30
: 31;
const auto daysStartIndex = std::clamp(
(state->days.current()
? state->days.current()->index()
: current.day()
? (current.day() - 1)
: (now.day() - 1)),
0,
daysCount - 1);
const auto daysPaint = [=](QPainter &p, QRectF rect, int index) {
p.drawText(rect, QString::number(index + 1), style::al_center);
};
const auto updated = picker(
daysCount,
daysStartIndex,
daysPaint);
delete state->days.current();
state->days = updated;
state->days.current()->show();
}, years->lifetime());
content->paintRequest(
) | rpl::start_with_next([=](const QRect &r) {
auto p = QPainter(content);
p.fillRect(r, Qt::transparent);
const auto lineRect = QRect(
0,
content->height() / 2,
content->width(),
st::defaultInputField.borderActive);
p.fillRect(lineRect.translated(0, itemHeight / 2), st::activeLineFg);
p.fillRect(lineRect.translated(0, -itemHeight / 2), st::activeLineFg);
}, content->lifetime());
base::install_event_filter(box, [=](not_null<QEvent*> e) {
if (e->type() == QEvent::KeyPress) {
years->handleKeyEvent(static_cast<QKeyEvent*>(e.get()));
}
return base::EventFilterResult::Continue;
});
box->addButton(tr::lng_settings_save(), [=] {
const auto result = Data::Birthday(
state->days.current()->index() + 1,
state->months.current()->index() + 1,
((years->index() == yearsCount - 1)
? 0
: minYear + years->index()));
box->closeBox();
save(result);
});
box->addButton(tr::lng_cancel(), [=] {
box->closeBox();
});
if (current) {
box->addLeftButton(tr::lng_settings_birthday_reset(), [=] {
box->closeBox();
save(Data::Birthday());
});
}
}
} // namespace Ui

View file

@ -0,0 +1,23 @@
/*
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;
} // namespace Data
namespace Ui {
class GenericBox;
void EditBirthdayBox(
not_null<Ui::GenericBox*> box,
Data::Birthday current,
Fn<void(Data::Birthday)> save);
} // namespace Ui

View file

@ -241,6 +241,8 @@ PRIVATE
ui/boxes/confirm_phone_box.h
ui/boxes/country_select_box.cpp
ui/boxes/country_select_box.h
ui/boxes/edit_birthday_box.cpp
ui/boxes/edit_birthday_box.h
ui/boxes/edit_invite_link.cpp
ui/boxes/edit_invite_link.h
ui/boxes/rate_call_box.cpp