diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index df0fd736e..3d1ac3b3c 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -1329,6 +1329,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_info_mobile_hidden" = "Hidden"; "lng_info_username_label" = "Username"; "lng_info_usernames_label" = "also"; +"lng_info_birthday_label" = "Date of birth"; +"lng_info_birthday_years#one" = "{date} ({count} year old)"; +"lng_info_birthday_years#other" = "{date} ({count} years old)"; +"lng_info_birthday_today_label" = "Birthday today"; +"lng_info_birthday_today" = "{emoji} {date}"; "lng_info_bio_label" = "Bio"; "lng_info_link_label" = "Link"; "lng_info_location_label" = "Location"; diff --git a/Telegram/SourceFiles/boxes/peers/peer_short_info_box.cpp b/Telegram/SourceFiles/boxes/peers/peer_short_info_box.cpp index bc07210f6..47afccd2f 100644 --- a/Telegram/SourceFiles/boxes/peers/peer_short_info_box.cpp +++ b/Telegram/SourceFiles/boxes/peers/peer_short_info_box.cpp @@ -17,6 +17,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/text/text_utilities.h" #include "ui/painter.h" #include "info/profile/info_profile_text.h" +#include "info/profile/info_profile_values.h" #include "media/streaming/media_streaming_instance.h" #include "media/streaming/media_streaming_player.h" #include "base/event_filter.h" @@ -788,6 +789,10 @@ void PeerShortInfoBox::prepareRows() { tr::lng_info_username_label(), usernameValue() | Ui::Text::ToWithEntities(), tr::lng_context_copy_mention(tr::now)); + addInfoOneLine( + birthdayLabel(), + birthdayValue() | Ui::Text::ToWithEntities(), + tr::lng_mediaview_copy(tr::now)); } RectParts PeerShortInfoBox::customCornersFilling() { @@ -827,29 +832,47 @@ void PeerShortInfoBox::refreshRoundedTopImage(const QColor &color) { } rpl::producer PeerShortInfoBox::nameValue() const { - return _fields.value() | rpl::map([](const PeerShortInfoFields &fields) { + return _fields.value( + ) | rpl::map([](const PeerShortInfoFields &fields) { return fields.name; }) | rpl::distinct_until_changed(); } rpl::producer PeerShortInfoBox::linkValue() const { - return _fields.value() | rpl::map([](const PeerShortInfoFields &fields) { + return _fields.value( + ) | rpl::map([](const PeerShortInfoFields &fields) { return Ui::Text::Link(fields.link, fields.link); }) | rpl::distinct_until_changed(); } rpl::producer PeerShortInfoBox::phoneValue() const { - return _fields.value() | rpl::map([](const PeerShortInfoFields &fields) { + return _fields.value( + ) | rpl::map([](const PeerShortInfoFields &fields) { return fields.phone; }) | rpl::distinct_until_changed(); } rpl::producer PeerShortInfoBox::usernameValue() const { - return _fields.value() | rpl::map([](const PeerShortInfoFields &fields) { + return _fields.value( + ) | rpl::map([](const PeerShortInfoFields &fields) { return fields.username; }) | rpl::distinct_until_changed(); } +rpl::producer PeerShortInfoBox::birthdayLabel() const { + return Info::Profile::BirthdayLabelText(_fields.value( + ) | rpl::map([](const PeerShortInfoFields &fields) { + return fields.birthday; + }) | rpl::distinct_until_changed()); +} + +rpl::producer PeerShortInfoBox::birthdayValue() const { + return Info::Profile::BirthdayValueText(_fields.value( + ) | rpl::map([](const PeerShortInfoFields &fields) { + return fields.birthday; + }) | rpl::distinct_until_changed()); +} + rpl::producer PeerShortInfoBox::aboutValue() const { return _fields.value() | rpl::map([](const PeerShortInfoFields &fields) { return fields.about; diff --git a/Telegram/SourceFiles/boxes/peers/peer_short_info_box.h b/Telegram/SourceFiles/boxes/peers/peer_short_info_box.h index 13a0be40d..b5eec6c19 100644 --- a/Telegram/SourceFiles/boxes/peers/peer_short_info_box.h +++ b/Telegram/SourceFiles/boxes/peers/peer_short_info_box.h @@ -7,6 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once +#include "data/data_birthday.h" #include "ui/layers/box_content.h" namespace style { @@ -40,6 +41,7 @@ struct PeerShortInfoFields { QString link; TextWithEntities about; QString username; + Data::Birthday birthday; bool isBio = false; }; @@ -171,6 +173,8 @@ private: [[nodiscard]] rpl::producer linkValue() const; [[nodiscard]] rpl::producer phoneValue() const; [[nodiscard]] rpl::producer usernameValue() const; + [[nodiscard]] rpl::producer birthdayLabel() const; + [[nodiscard]] rpl::producer birthdayValue() const; [[nodiscard]] rpl::producer aboutValue() const; const style::ShortInfoBox &_st; diff --git a/Telegram/SourceFiles/boxes/peers/prepare_short_info_box.cpp b/Telegram/SourceFiles/boxes/peers/prepare_short_info_box.cpp index d71645316..a6b442aca 100644 --- a/Telegram/SourceFiles/boxes/peers/prepare_short_info_box.cpp +++ b/Telegram/SourceFiles/boxes/peers/prepare_short_info_box.cpp @@ -203,7 +203,8 @@ void ProcessFullPhoto( (UpdateFlag::Name | UpdateFlag::PhoneNumber | UpdateFlag::Username - | UpdateFlag::About) + | UpdateFlag::About + | UpdateFlag::Birthday) ) | rpl::map([=] { const auto user = peer->asUser(); const auto username = peer->userName(); @@ -217,6 +218,7 @@ void ProcessFullPhoto( .username = ((user && !username.isEmpty()) ? ('@' + username) : QString()), + .birthday = user ? user->birthday() : Data::Birthday(), .isBio = (user && !user->isBot()), }; }); diff --git a/Telegram/SourceFiles/data/data_birthday.cpp b/Telegram/SourceFiles/data/data_birthday.cpp index e29378cbe..679933fb1 100644 --- a/Telegram/SourceFiles/data/data_birthday.cpp +++ b/Telegram/SourceFiles/data/data_birthday.cpp @@ -7,6 +7,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "data/data_birthday.h" +#include "base/timer_rpl.h" +#include "lang/lang_keys.h" + +#include + namespace Data { namespace { @@ -66,5 +71,62 @@ int Birthday::year() const { return _value / 10000; } +QString BirthdayText(Birthday date) { + if (const auto year = date.year()) { + return tr::lng_month_day_year( + tr::now, + lt_month, + Lang::MonthSmall(date.month())(tr::now), + lt_day, + QString::number(date.day()), + lt_year, + QString::number(year)); + } else if (date) { + return tr::lng_month_day( + tr::now, + lt_month, + Lang::MonthSmall(date.month())(tr::now), + lt_day, + QString::number(date.day())); + } + return QString(); +} + +QString BirthdayCake() { + return QString::fromUtf8("\xf0\x9f\x8e\x82"); +} + +int BirthdayAge(Birthday date) { + if (!date.year()) { + return 0; + } + const auto now = QDate::currentDate(); + const auto day = QDate(date.year(), date.month(), date.day()); + if (!day.isValid() || day >= now) { + return 0; + } + auto age = now.year() - date.year(); + if (now < QDate(date.year() + age, date.month(), date.day())) { + --age; + } + return age; +} + +bool IsBirthdayToday(Birthday date) { + if (!date) { + return false; + } + const auto now = QDate::currentDate(); + return date.day() == now.day() && date.month() == now.month(); +} + +rpl::producer IsBirthdayTodayValue(Birthday date) { + return rpl::single() | rpl::then(base::timer_each( + 60 * crl::time(1000) + )) | rpl::map([=] { + return IsBirthdayToday(date); + }) | rpl::distinct_until_changed(); +} + } // namespace Data diff --git a/Telegram/SourceFiles/data/data_birthday.h b/Telegram/SourceFiles/data/data_birthday.h index fd36870cc..064ff6cf0 100644 --- a/Telegram/SourceFiles/data/data_birthday.h +++ b/Telegram/SourceFiles/data/data_birthday.h @@ -38,4 +38,10 @@ private: }; +[[nodiscard]] QString BirthdayText(Birthday date); +[[nodiscard]] QString BirthdayCake(); +[[nodiscard]] int BirthdayAge(Birthday date); +[[nodiscard]] bool IsBirthdayToday(Birthday date); +[[nodiscard]] rpl::producer IsBirthdayTodayValue(Birthday date); + } // namespace Data diff --git a/Telegram/SourceFiles/info/profile/info_profile_actions.cpp b/Telegram/SourceFiles/info/profile/info_profile_actions.cpp index ddcb93e45..25fc17c34 100644 --- a/Telegram/SourceFiles/info/profile/info_profile_actions.cpp +++ b/Telegram/SourceFiles/info/profile/info_profile_actions.cpp @@ -1003,6 +1003,14 @@ object_ptr DetailsFiller::setupInfo() { return false; }); } else { + addInfoOneLine( + BirthdayLabelText(BirthdayValue(user)), + BirthdayValueText( + BirthdayValue(user) + ) | Ui::Text::ToWithEntities(), + tr::lng_mediaview_copy(tr::now), + st::infoProfileLabeledUsernamePadding); + tracker.track(result->add(CreateWorkingHours(result, user))); auto locationText = user->session().changes().peerFlagsValue( diff --git a/Telegram/SourceFiles/info/profile/info_profile_values.cpp b/Telegram/SourceFiles/info/profile/info_profile_values.cpp index 76636ddbd..56b7eac84 100644 --- a/Telegram/SourceFiles/info/profile/info_profile_values.cpp +++ b/Telegram/SourceFiles/info/profile/info_profile_values.cpp @@ -635,5 +635,48 @@ rpl::producer EmojiStatusIdValue(not_null peer) { ) | rpl::map([=] { return peer->emojiStatusId(); }); } +rpl::producer BirthdayLabelText( + rpl::producer birthday) { + return std::move(birthday) | rpl::map([](Data::Birthday value) { + return rpl::conditional( + Data::IsBirthdayTodayValue(value), + tr::lng_info_birthday_today_label(), + tr::lng_info_birthday_label()); + }) | rpl::flatten_latest(); +} + +rpl::producer BirthdayValueText( + rpl::producer birthday) { + return std::move( + birthday + ) | rpl::map([](Data::Birthday value) -> rpl::producer { + if (!value) { + return rpl::single(QString()); + } + return Data::IsBirthdayTodayValue( + value + ) | rpl::map([=](bool today) { + auto text = Data::BirthdayText(value); + if (const auto age = Data::BirthdayAge(value)) { + text = tr::lng_info_birthday_years( + tr::now, + lt_count, + age, + lt_date, + text); + } + if (today) { + text = tr::lng_info_birthday_today( + tr::now, + lt_emoji, + Data::BirthdayCake(), + lt_date, + text); + } + return text; + }); + }) | rpl::flatten_latest(); +} + } // namespace Profile } // namespace Info diff --git a/Telegram/SourceFiles/info/profile/info_profile_values.h b/Telegram/SourceFiles/info/profile/info_profile_values.h index b335949c7..5dac2207d 100644 --- a/Telegram/SourceFiles/info/profile/info_profile_values.h +++ b/Telegram/SourceFiles/info/profile/info_profile_values.h @@ -121,4 +121,9 @@ enum class BadgeType; [[nodiscard]] rpl::producer EmojiStatusIdValue( not_null peer); +[[nodiscard]] rpl::producer BirthdayLabelText( + rpl::producer birthday); +[[nodiscard]] rpl::producer BirthdayValueText( + rpl::producer birthday); + } // namespace Info::Profile diff --git a/Telegram/SourceFiles/settings/settings_information.cpp b/Telegram/SourceFiles/settings/settings_information.cpp index eef68335e..c577b0a71 100644 --- a/Telegram/SourceFiles/settings/settings_information.cpp +++ b/Telegram/SourceFiles/settings/settings_information.cpp @@ -367,23 +367,9 @@ void SetupBirthday( 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()))); + const auto text = Data::BirthdayText(birthday); + if (!text.isEmpty()) { + return TextWithEntities{ text }; } auto result = TextWithEntities{ add }; result.entities.push_back({