diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index e149ccd29..0f8f971f1 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -672,7 +672,14 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "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_birthday_reset" = "Remove"; +"lng_settings_channel_label" = "Personal channel"; +"lng_settings_channel_add" = "Add"; +"lng_settings_channel_remove" = "Remove"; +"lng_settings_channel_no_yet" = "You don't have any channels yet."; +"lng_settings_channel_start" = "Start a Channel"; +"lng_settings_channel_saved" = "Your personal channel was updated."; +"lng_settings_channel_removed" = "Your personal channel was removed."; "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"; diff --git a/Telegram/SourceFiles/core/local_url_handlers.cpp b/Telegram/SourceFiles/core/local_url_handlers.cpp index 44474df65..1cd4c18fc 100644 --- a/Telegram/SourceFiles/core/local_url_handlers.cpp +++ b/Telegram/SourceFiles/core/local_url_handlers.cpp @@ -69,6 +69,102 @@ namespace { using Match = qthelp::RegularExpressionMatch; +class PersonalChannelController final : public PeerListController { +public: + explicit PersonalChannelController(not_null session); + ~PersonalChannelController(); + + Main::Session &session() const override; + void prepare() override; + void rowClicked(not_null row) override; + [[nodiscard]] rpl::producer> chosen() const; + +private: + const not_null _session; + rpl::event_stream> _chosen; + mtpRequestId _requestId = 0; + +}; + +PersonalChannelController::PersonalChannelController( + not_null session) +: _session(session) { +} + +PersonalChannelController::~PersonalChannelController() { + if (_requestId) { + _session->api().request(_requestId).cancel(); + } +} + +Main::Session &PersonalChannelController::session() const { + return *_session; +} + +void PersonalChannelController::prepare() { + using Flag = MTPchannels_GetAdminedPublicChannels::Flag; + _requestId = _session->api().request( + MTPchannels_GetAdminedPublicChannels( + MTP_flags(Flag::f_for_personal)) + ).done([=](const MTPmessages_Chats &result) { + _requestId = 0; + + const auto &chats = result.match([](const auto &data) { + return data.vchats().v; + }); + for (const auto &chat : chats) { + if (const auto peer = _session->data().processChat(chat)) { + if (!delegate()->peerListFindRow(peer->id.value)) { + delegate()->peerListAppendRow( + std::make_unique(peer)); + } + } + } + delegate()->peerListRefreshRows(); + }).send(); +} + +void PersonalChannelController::rowClicked(not_null row) { + if (const auto channel = row->peer()->asChannel()) { + _chosen.fire_copy(channel); + } +} + +auto PersonalChannelController::chosen() const +-> rpl::producer> { + return _chosen.events(); +} + +void SavePersonalChannel( + not_null window, + ChannelData *channel) { + const auto self = window->session().user(); + const auto history = channel + ? channel->owner().history(channel->id).get() + : nullptr; + const auto item = history + ? history->lastServerMessage() + : nullptr; + const auto channelId = channel + ? peerToChannel(channel->id) + : ChannelId(); + const auto messageId = item ? item->id : MsgId(); + if (self->personalChannelId() != channelId + || (messageId + && self->personalChannelMessageId() != messageId)) { + self->setPersonalChannel(channelId, messageId); + self->session().api().request(MTPaccount_UpdatePersonalChannel( + channel ? channel->inputChannel : MTP_inputChannelEmpty() + )).done(crl::guard(window, [=] { + window->showToast((channel + ? tr::lng_settings_channel_saved + : tr::lng_settings_channel_removed)(tr::now)); + })).fail(crl::guard(window, [=](const MTP::Error &error) { + window->showToast(u"Error: "_q + error.type()); + })).send(); + } +} + bool JoinGroupByHash( Window::SessionController *controller, const Match &match, @@ -730,6 +826,45 @@ bool ShowEditBirthdayPrivacy( return true; } +bool ShowEditPersonalChannel( + Window::SessionController *controller, + const Match &match, + const QVariant &context) { + if (!controller) { + return false; + } + + auto listController = std::make_unique( + &controller->session()); + const auto rawController = listController.get(); + auto initBox = [=](not_null box) { + box->setTitle(tr::lng_settings_channel_label()); + box->addButton(tr::lng_box_done(), [=] { + box->closeBox(); + }); + + const auto save = [=](ChannelData *channel) { + SavePersonalChannel(controller, channel); + box->closeBox(); + }; + + rawController->chosen( + ) | rpl::start_with_next([=](not_null channel) { + save(channel); + }, box->lifetime()); + + if (controller->session().user()->personalChannelId()) { + box->addLeftButton(tr::lng_settings_channel_remove(), [=] { + save(nullptr); + }); + } + }; + controller->show(Box( + std::move(listController), + std::move(initBox))); + return true; +} + void ExportTestChatTheme( not_null controller, not_null theme) { @@ -1128,6 +1263,10 @@ const std::vector &InternalUrlHandlers() { u"^edit_privacy_birthday$"_q, ShowEditBirthdayPrivacy, }, + { + u"^edit_personal_channel$"_q, + ShowEditPersonalChannel, + }, }; return Result; } diff --git a/Telegram/SourceFiles/data/data_changes.h b/Telegram/SourceFiles/data/data_changes.h index eeb80ccb7..d33a41617 100644 --- a/Telegram/SourceFiles/data/data_changes.h +++ b/Telegram/SourceFiles/data/data_changes.h @@ -90,27 +90,28 @@ struct PeerUpdate { EmojiStatus = (1ULL << 28), BusinessDetails = (1ULL << 29), Birthday = (1ULL << 30), + PersonalChannel = (1ULL << 31), // For chats and channels - InviteLinks = (1ULL << 31), - Members = (1ULL << 32), - Admins = (1ULL << 33), - BannedUsers = (1ULL << 34), - Rights = (1ULL << 35), - PendingRequests = (1ULL << 36), - Reactions = (1ULL << 37), + InviteLinks = (1ULL << 32), + Members = (1ULL << 33), + Admins = (1ULL << 34), + BannedUsers = (1ULL << 35), + Rights = (1ULL << 36), + PendingRequests = (1ULL << 37), + Reactions = (1ULL << 38), // For channels - ChannelAmIn = (1ULL << 38), - StickersSet = (1ULL << 39), - EmojiSet = (1ULL << 40), - ChannelLinkedChat = (1ULL << 41), - ChannelLocation = (1ULL << 42), - Slowmode = (1ULL << 43), - GroupCall = (1ULL << 44), + ChannelAmIn = (1ULL << 39), + StickersSet = (1ULL << 40), + EmojiSet = (1ULL << 41), + ChannelLinkedChat = (1ULL << 42), + ChannelLocation = (1ULL << 43), + Slowmode = (1ULL << 44), + GroupCall = (1ULL << 45), // For iteration - LastUsedBit = (1ULL << 44), + LastUsedBit = (1ULL << 45), }; using Flags = base::flags; 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 ea5d0dad1..cffe8ad67 100644 --- a/Telegram/SourceFiles/data/data_user.cpp +++ b/Telegram/SourceFiles/data/data_user.cpp @@ -195,6 +195,23 @@ void UserData::setBusinessDetails(Data::BusinessDetails details) { session().changes().peerUpdated(this, UpdateFlag::BusinessDetails); } +ChannelId UserData::personalChannelId() const { + return _personalChannelId; +} + +MsgId UserData::personalChannelMessageId() const { + return _personalChannelMessageId; +} + +void UserData::setPersonalChannel(ChannelId channelId, MsgId messageId) { + if (_personalChannelId != channelId + || _personalChannelMessageId != messageId) { + _personalChannelId = channelId; + _personalChannelMessageId = messageId; + session().changes().peerUpdated(this, UpdateFlag::PersonalChannel); + } +} + void UserData::setName(const QString &newFirstName, const QString &newLastName, const QString &newPhoneName, const QString &newUsername) { bool changeName = !newFirstName.isEmpty() || !newLastName.isEmpty(); @@ -623,6 +640,9 @@ void ApplyUserUpdate(not_null user, const MTPDuserFull &update) { update.vbusiness_location(), update.vbusiness_intro())); user->setBirthday(update.vbirthday()); + user->setPersonalChannel( + update.vpersonal_channel_id().value_or_empty(), + update.vpersonal_channel_message().value_or_empty()); 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 166988287..7a8a22582 100644 --- a/Telegram/SourceFiles/data/data_user.h +++ b/Telegram/SourceFiles/data/data_user.h @@ -205,6 +205,10 @@ public: [[nodiscard]] const Data::BusinessDetails &businessDetails() const; void setBusinessDetails(Data::BusinessDetails details); + [[nodiscard]] ChannelId personalChannelId() const; + [[nodiscard]] MsgId personalChannelMessageId() const; + void setPersonalChannel(ChannelId channelId, MsgId messageId); + private: auto unavailableReasons() const -> const std::vector & override; @@ -223,6 +227,9 @@ private: QString _phone; QString _privateForwardName; + ChannelId _personalChannelId = 0; + MsgId _personalChannelMessageId = 0; + uint64 _accessHash = 0; static constexpr auto kInaccessibleAccessHashOld = 0xFFFFFFFFFFFFFFFFULL; diff --git a/Telegram/SourceFiles/info/profile/info_profile_values.cpp b/Telegram/SourceFiles/info/profile/info_profile_values.cpp index 56b7eac84..aa10dff08 100644 --- a/Telegram/SourceFiles/info/profile/info_profile_values.cpp +++ b/Telegram/SourceFiles/info/profile/info_profile_values.cpp @@ -352,6 +352,16 @@ rpl::producer BirthdayValue(not_null user) { }); } +rpl::producer PersonalChannelValue(not_null user) { + return user->session().changes().peerFlagsValue( + user, + UpdateFlag::PersonalChannel + ) | rpl::map([=] { + const auto channelId = user->personalChannelId(); + return channelId ? user->owner().channel(channelId).get() : nullptr; + }); +} + rpl::producer AmInChannelValue(not_null channel) { return channel->session().changes().peerFlagsValue( channel, diff --git a/Telegram/SourceFiles/info/profile/info_profile_values.h b/Telegram/SourceFiles/info/profile/info_profile_values.h index 5dac2207d..6b7ccf475 100644 --- a/Telegram/SourceFiles/info/profile/info_profile_values.h +++ b/Telegram/SourceFiles/info/profile/info_profile_values.h @@ -86,6 +86,8 @@ rpl::producer> MigratedOrMeValue( not_null user); [[nodiscard]] rpl::producer BirthdayValue( not_null user); +[[nodiscard]] rpl::producer PersonalChannelValue( + not_null user); [[nodiscard]] rpl::producer AmInChannelValue( not_null channel); [[nodiscard]] rpl::producer MembersCountValue(not_null peer); diff --git a/Telegram/SourceFiles/settings/settings_information.cpp b/Telegram/SourceFiles/settings/settings_information.cpp index c577b0a71..bf6989647 100644 --- a/Telegram/SourceFiles/settings/settings_information.cpp +++ b/Telegram/SourceFiles/settings/settings_information.cpp @@ -35,6 +35,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_user.h" #include "data/data_peer_values.h" #include "data/data_changes.h" +#include "data/data_channel.h" #include "data/data_premium_limits.h" #include "info/profile/info_profile_values.h" #include "info/profile/info_profile_badge.h" @@ -368,16 +369,7 @@ void SetupBirthday( tr::lng_settings_birthday_add() ) | rpl::map([](Data::Birthday birthday, const QString &add) { const auto text = Data::BirthdayText(birthday); - if (!text.isEmpty()) { - return TextWithEntities{ text }; - } - auto result = TextWithEntities{ add }; - result.entities.push_back({ - EntityType::CustomUrl, - 0, - int(add.size()), - "internal:edit_username" }); - return result; + return TextWithEntities{ !text.isEmpty() ? text : add }; }); const auto edit = [=] { Core::App().openInternalUrl( @@ -419,6 +411,39 @@ void SetupBirthday( Ui::Text::WithEntities))); } +void SetupPersonalChannel( + not_null container, + not_null controller, + not_null self) { + const auto session = &self->session(); + + Ui::AddSkip(container); + + auto value = rpl::combine( + Info::Profile::PersonalChannelValue(self), + tr::lng_settings_channel_add() + ) | rpl::map([](ChannelData *channel, const QString &add) { + return TextWithEntities{ channel ? channel->name() : add }; + }); + const auto edit = [=] { + Core::App().openInternalUrl( + u"internal:edit_personal_channel"_q, + QVariant::fromValue(ClickHandlerContext{ + .sessionWindow = base::make_weak(controller), + })); + }; + AddRow( + container, + tr::lng_settings_channel_label(), + std::move(value), + tr::lng_mediaview_copy(tr::now), + edit, + { &st::menuIconChannel }); + + Ui::AddSkip(container); + Ui::AddDivider(container); +} + void SetupRows( not_null container, not_null controller, @@ -1020,6 +1045,7 @@ void Information::setupContent( SetupPhoto(content, controller, self); SetupBio(content, self); SetupRows(content, controller, self); + SetupPersonalChannel(content, controller, self); SetupBirthday(content, controller, self); SetupAccountsWrap(content, controller);