diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index 229a8f23cf..2ff0d3b109 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -3155,6 +3155,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_boost_group_ask" = "Ask your **Premium** members to boost your group with this link:"; //"lng_boost_group_gifting" = "Boost your group by gifting your members Telegram Premium. {link}"; +"lng_boost_channel_title_autotranslate" = "Autotranslation of Messages"; +"lng_boost_channel_needs_level_autotranslate#one" = "Your channel needs to reach **Level {count}** to enable autotranslation of messages."; +"lng_boost_channel_needs_level_autotranslate#other" = "Your channel needs to reach **Level {count}** to enable autotranslation of messages."; + "lng_feature_stories#one" = "**{count}** Story Per Day"; "lng_feature_stories#other" = "**{count}** Stories Per Day"; "lng_feature_reactions#one" = "**{count}** Custom Reaction"; @@ -3173,6 +3177,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_feature_custom_background_group" = "Custom Group Background"; "lng_feature_custom_emoji_pack" = "Custom Emoji Pack"; "lng_feature_transcribe" = "Voice-to-Text Conversion"; +"lng_feature_autotranslate" = "Autotranslation of Messages"; "lng_giveaway_new_title" = "Boosts via Gifts"; "lng_giveaway_new_about" = "Get more boosts for your channel by gifting Premium to your subscribers."; @@ -4392,6 +4397,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_edit_contact_title" = "Edit contact"; "lng_edit_channel_title" = "Edit channel"; "lng_edit_bot_title" = "Edit bot"; +"lng_edit_autotranslate" = "Auto-translate messages"; "lng_edit_sign_messages" = "Sign messages"; "lng_edit_sign_messages_about" = "Add names of admins to the messages they post."; "lng_edit_sign_profiles" = "Show authors' profiles"; @@ -5372,6 +5378,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_admin_log_participant_volume_channel" = "{from} changed live stream volume for {user} to {percent}"; "lng_admin_log_antispam_enabled" = "{from} enabled aggressive anti-spam"; "lng_admin_log_antispam_disabled" = "{from} disabled aggressive anti-spam"; +"lng_admin_log_autotranslate_enabled" = "{from} enabled automatic translation"; +"lng_admin_log_autotranslate_disabled" = "{from} disabled automatic translation"; "lng_admin_log_change_color" = "{from} changed channel color from {previous} to {color}"; "lng_admin_log_set_background_emoji" = "{from} set channel background emoji to {emoji}"; "lng_admin_log_change_background_emoji" = "{from} changed channel background emoji from {previous} to {emoji}"; diff --git a/Telegram/SourceFiles/boxes/peers/edit_peer_color_box.cpp b/Telegram/SourceFiles/boxes/peers/edit_peer_color_box.cpp index c457ce579a..c1c160ac08 100644 --- a/Telegram/SourceFiles/boxes/peers/edit_peer_color_box.cpp +++ b/Telegram/SourceFiles/boxes/peers/edit_peer_color_box.cpp @@ -1482,7 +1482,11 @@ void CheckBoostLevel( show->show(Box(Ui::AskBoostBox, Ui::AskBoostBoxData{ .link = qs(data.vboost_url()), .boost = counters, + .features = (peer->isChannel() + ? LookupBoostFeatures(peer->asChannel()) + : Ui::BoostFeatures()), .reason = *reason, + .group = !peer->isBroadcast(), }, openStatistics, nullptr)); cancel(); }).fail([=](const MTP::Error &error) { diff --git a/Telegram/SourceFiles/boxes/peers/edit_peer_info_box.cpp b/Telegram/SourceFiles/boxes/peers/edit_peer_info_box.cpp index a457f084ea..f9416ccdaf 100644 --- a/Telegram/SourceFiles/boxes/peers/edit_peer_info_box.cpp +++ b/Telegram/SourceFiles/boxes/peers/edit_peer_info_box.cpp @@ -358,6 +358,7 @@ private: std::optional description; std::optional hiddenPreHistory; std::optional forum; + std::optional autotranslate; std::optional signatures; std::optional signatureProfiles; std::optional noForwards; @@ -385,6 +386,7 @@ private: //void fillInviteLinkButton(); void fillForumButton(); void fillColorIndexButton(); + void fillAutoTranslateButton(); void fillSignaturesButton(); void fillHistoryVisibilityButton(); void fillManageSection(); @@ -413,6 +415,7 @@ private: [[nodiscard]] bool validateDescription(Saving &to) const; [[nodiscard]] bool validateHistoryVisibility(Saving &to) const; [[nodiscard]] bool validateForum(Saving &to) const; + [[nodiscard]] bool validateAutotranslate(Saving &to) const; [[nodiscard]] bool validateSignatures(Saving &to) const; [[nodiscard]] bool validateForwards(Saving &to) const; [[nodiscard]] bool validateJoinToWrite(Saving &to) const; @@ -426,6 +429,7 @@ private: void saveDescription(); void saveHistoryVisibility(); void saveForum(); + void saveAutotranslate(); void saveSignatures(); void saveForwards(); void saveJoinToWrite(); @@ -452,6 +456,7 @@ private: std::optional _historyVisibilitySavedValue; std::optional _typeDataSavedValue; std::optional _forumSavedValue; + std::optional _autotranslateSavedValue; std::optional _signaturesSavedValue; std::optional _signatureProfilesSavedValue; @@ -1093,6 +1098,61 @@ void Controller::fillColorIndexButton() { st::managePeerColorsButton); } +void Controller::fillAutoTranslateButton() { + Expects(_controls.buttonsLayout != nullptr); + + const auto channel = _peer->asBroadcast(); + if (!channel) { + return; + } + + const auto requiredLevel = Data::LevelLimits(&channel->session()) + .channelAutoTranslateLevelMin(); + const auto autotranslate = _controls.buttonsLayout->add( + EditPeerInfoBox::CreateButton( + _controls.buttonsLayout, + tr::lng_edit_autotranslate(), + rpl::single(QString()), + [] {}, + st::manageGroupTopicsButton, + { &st::menuIconTranslate })); + const auto toggled = autotranslate->lifetime().make_state< + rpl::event_stream + >(); + autotranslate->toggleOn(rpl::single( + channel->autoTranslation() + ) | rpl::then(toggled->events())); + const auto isLocked = channel->levelHint() < requiredLevel; + const auto reason = Ui::AskBoostReason{ + .data = Ui::AskBoostAutotranslate{ .requiredLevel = requiredLevel }, + }; + + autotranslate->setToggleLocked(isLocked); + + autotranslate->toggledChanges( + ) | rpl::start_with_next([=](bool value) { + if (!isLocked) { + _autotranslateSavedValue = toggled; + } else if (value) { + toggled->fire(false); + CheckBoostLevel( + _navigation->uiShow(), + _peer, + [=](int level) { + return (level < requiredLevel) + ? std::make_optional(reason) + : std::nullopt; + }, + [] {}); + } + }, autotranslate->lifetime()); + + autotranslate->toggledValue( + ) | rpl::start_with_next([=](bool toggled) { + _autotranslateSavedValue = toggled; + }, _controls.buttonsLayout->lifetime()); +} + void Controller::fillSignaturesButton() { Expects(_controls.buttonsLayout != nullptr); @@ -1255,6 +1315,8 @@ void Controller::fillManageSection() { const auto canEditSignatures = isChannel && channel->canEditSignatures() && !channel->isMegagroup(); + const auto canEditAutoTranslate = isChannel + && channel->canEditAutoTranslate(); const auto canEditPreHistoryHidden = isChannel ? channel->canEditPreHistoryHidden() : chat->canEditPreHistoryHidden(); @@ -1308,6 +1370,9 @@ void Controller::fillManageSection() { if (canEditColorIndex) { fillColorIndexButton(); } + if (canEditAutoTranslate) { + fillAutoTranslateButton(); + } if (canEditSignatures) { fillSignaturesButton(); } else if (canEditPreHistoryHidden @@ -1543,7 +1608,11 @@ void Controller::editReactions() { strong->show(Box(Ui::AskBoostBox, Ui::AskBoostBoxData{ .link = link, .boost = counters, + .features = (_peer->isChannel() + ? LookupBoostFeatures(_peer->asChannel()) + : Ui::BoostFeatures()), .reason = { Ui::AskBoostCustomReactions{ required } }, + .group = !_peer->isBroadcast(), }, openStatistics, nullptr)); } }; @@ -1897,6 +1966,7 @@ std::optional Controller::validate() const { && validateDescription(result) && validateHistoryVisibility(result) && validateForum(result) + && validateAutotranslate(result) && validateSignatures(result) && validateForwards(result) && validateJoinToWrite(result) @@ -1984,6 +2054,14 @@ bool Controller::validateForum(Saving &to) const { return true; } +bool Controller::validateAutotranslate(Saving &to) const { + if (!_autotranslateSavedValue.has_value()) { + return true; + } + to.autotranslate = _autotranslateSavedValue; + return true; +} + bool Controller::validateSignatures(Saving &to) const { Expects(_signaturesSavedValue.has_value() == _signatureProfilesSavedValue.has_value()); @@ -2035,6 +2113,7 @@ void Controller::save() { pushSaveStage([=] { saveDescription(); }); pushSaveStage([=] { saveHistoryVisibility(); }); pushSaveStage([=] { saveForum(); }); + pushSaveStage([=] { saveAutotranslate(); }); pushSaveStage([=] { saveSignatures(); }); pushSaveStage([=] { saveForwards(); }); pushSaveStage([=] { saveJoinToWrite(); }); @@ -2181,7 +2260,8 @@ void Controller::saveLinkedChat() { )).done([=] { channel->setLinkedChat(*_savingData.linkedChat); continueSave(); - }).fail([=] { + }).fail([=](const MTP::Error &error) { + _navigation->showToast(error.type()); cancelSave(); }).send(); } @@ -2421,6 +2501,30 @@ void Controller::saveForum() { if (error.type() == u"CHAT_NOT_MODIFIED"_q) { continueSave(); } else { + _navigation->showToast(error.type()); + cancelSave(); + } + }).send(); +} + +void Controller::saveAutotranslate() { + const auto channel = _peer->asBroadcast(); + if (!_savingData.autotranslate + || !channel + || (*_savingData.autotranslate == channel->autoTranslation())) { + return continueSave(); + } + _api.request(MTPchannels_ToggleAutotranslation( + channel->inputChannel, + MTP_bool(*_savingData.autotranslate) + )).done([=](const MTPUpdates &result) { + channel->session().api().applyUpdates(result); + continueSave(); + }).fail([=](const MTP::Error &error) { + if (error.type() == u"CHAT_NOT_MODIFIED"_q) { + continueSave(); + } else { + _navigation->showToast(error.type()); cancelSave(); } }).send(); @@ -2455,6 +2559,7 @@ void Controller::saveSignatures() { if (error.type() == u"CHAT_NOT_MODIFIED"_q) { continueSave(); } else { + _navigation->showToast(error.type()); cancelSave(); } }).send(); @@ -2475,6 +2580,7 @@ void Controller::saveForwards() { if (error.type() == u"CHAT_NOT_MODIFIED"_q) { continueSave(); } else { + _navigation->showToast(error.type()); cancelSave(); } }).send(); @@ -2497,6 +2603,7 @@ void Controller::saveJoinToWrite() { if (error.type() == u"CHAT_NOT_MODIFIED"_q) { continueSave(); } else { + _navigation->showToast(error.type()); cancelSave(); } }).send(); @@ -2519,6 +2626,7 @@ void Controller::saveRequestToJoin() { if (error.type() == u"CHAT_NOT_MODIFIED"_q) { continueSave(); } else { + _navigation->showToast(error.type()); cancelSave(); } }).send(); diff --git a/Telegram/SourceFiles/boxes/peers/replace_boost_box.cpp b/Telegram/SourceFiles/boxes/peers/replace_boost_box.cpp index 354b1bb5a5..85be6dd1ce 100644 --- a/Telegram/SourceFiles/boxes/peers/replace_boost_box.cpp +++ b/Telegram/SourceFiles/boxes/peers/replace_boost_box.cpp @@ -449,6 +449,7 @@ Ui::BoostFeatures LookupBoostFeatures(not_null channel) { .nameColorsByLevel = std::move(nameColorsByLevel), .linkStylesByLevel = std::move(linkStylesByLevel), .linkLogoLevel = group ? 0 : levelLimits.channelBgIconLevelMin(), + .autotranslateLevel = group ? 0 : levelLimits.channelAutoTranslateLevelMin(), .transcribeLevel = group ? levelLimits.groupTranscribeLevelMin() : 0, .emojiPackLevel = group ? levelLimits.groupEmojiStickersLevelMin() : 0, .emojiStatusLevel = group diff --git a/Telegram/SourceFiles/data/data_channel.cpp b/Telegram/SourceFiles/data/data_channel.cpp index fb6c980550..5ff510e6e8 100644 --- a/Telegram/SourceFiles/data/data_channel.cpp +++ b/Telegram/SourceFiles/data/data_channel.cpp @@ -649,7 +649,11 @@ bool ChannelData::canEditPermissions() const { } bool ChannelData::canEditSignatures() const { - return isChannel() && canEditInformation(); + return isBroadcast() && canEditInformation(); +} + +bool ChannelData::canEditAutoTranslate() const { + return isBroadcast() && canEditInformation(); } bool ChannelData::canEditPreHistoryHidden() const { diff --git a/Telegram/SourceFiles/data/data_channel.h b/Telegram/SourceFiles/data/data_channel.h index 115516073c..61591122ac 100644 --- a/Telegram/SourceFiles/data/data_channel.h +++ b/Telegram/SourceFiles/data/data_channel.h @@ -73,6 +73,7 @@ enum class ChannelDataFlag : uint64 { SignatureProfiles = (1ULL << 35), StargiftsAvailable = (1ULL << 36), PaidMessagesAvailable = (1ULL << 37), + AutoTranslation = (1ULL << 38), }; inline constexpr bool is_flag_type(ChannelDataFlag) { return true; }; using ChannelDataFlags = base::flags; @@ -321,6 +322,9 @@ public: [[nodiscard]] bool antiSpamMode() const { return flags() & Flag::AntiSpam; } + [[nodiscard]] bool autoTranslation() const { + return flags() & Flag::AutoTranslation; + } [[nodiscard]] auto adminRights() const { return _adminRights.current(); @@ -382,6 +386,7 @@ public: [[nodiscard]] bool canViewAdmins() const; [[nodiscard]] bool canViewBanned() const; [[nodiscard]] bool canEditSignatures() const; + [[nodiscard]] bool canEditAutoTranslate() const; [[nodiscard]] bool canEditStickers() const; [[nodiscard]] bool canEditEmoji() const; [[nodiscard]] bool canDelete() const; diff --git a/Telegram/SourceFiles/data/data_premium_limits.cpp b/Telegram/SourceFiles/data/data_premium_limits.cpp index 6421f722a7..f83d3924ba 100644 --- a/Telegram/SourceFiles/data/data_premium_limits.cpp +++ b/Telegram/SourceFiles/data/data_premium_limits.cpp @@ -262,6 +262,12 @@ int LevelLimits::channelRestrictSponsoredLevelMin() const { 20); } +int LevelLimits::channelAutoTranslateLevelMin() const { + return _session->appConfig().get( + u"channel_autotranslation_level_min"_q, + 3); +} + int LevelLimits::groupTranscribeLevelMin() const { return _session->appConfig().get( u"group_transcribe_level_min"_q, diff --git a/Telegram/SourceFiles/data/data_premium_limits.h b/Telegram/SourceFiles/data/data_premium_limits.h index f1cfe146d7..90c12b697c 100644 --- a/Telegram/SourceFiles/data/data_premium_limits.h +++ b/Telegram/SourceFiles/data/data_premium_limits.h @@ -102,6 +102,7 @@ public: [[nodiscard]] int channelWallpaperLevelMin() const; [[nodiscard]] int channelCustomWallpaperLevelMin() const; [[nodiscard]] int channelRestrictSponsoredLevelMin() const; + [[nodiscard]] int channelAutoTranslateLevelMin() const; [[nodiscard]] int groupTranscribeLevelMin() const; [[nodiscard]] int groupEmojiStickersLevelMin() const; [[nodiscard]] int groupProfileBgIconLevelMin() const; diff --git a/Telegram/SourceFiles/data/data_session.cpp b/Telegram/SourceFiles/data/data_session.cpp index 6189cbd069..039903f2a3 100644 --- a/Telegram/SourceFiles/data/data_session.cpp +++ b/Telegram/SourceFiles/data/data_session.cpp @@ -967,7 +967,8 @@ not_null Session::processChat(const MTPChat &data) { | Flag::Forum | ((!minimal && !data.is_stories_hidden_min()) ? Flag::StoriesHidden - : Flag()); + : Flag()) + | Flag::AutoTranslation; const auto storiesState = minimal ? std::optional() : data.is_stories_unavailable() @@ -1006,7 +1007,8 @@ not_null Session::processChat(const MTPChat &data) { && !data.is_stories_hidden_min() && data.is_stories_hidden()) ? Flag::StoriesHidden - : Flag()); + : Flag()) + | (data.is_autotranslation() ? Flag::AutoTranslation : Flag()); channel->setFlags((channel->flags() & ~flagsMask) | flagsSet); channel->setBotVerifyDetailsIcon( data.vbot_verification_icon().value_or_empty()); diff --git a/Telegram/SourceFiles/settings/settings.style b/Telegram/SourceFiles/settings/settings.style index 6ab1f55657..f28f04e777 100644 --- a/Telegram/SourceFiles/settings/settings.style +++ b/Telegram/SourceFiles/settings/settings.style @@ -31,7 +31,7 @@ settingsButtonLight: SettingsButton(settingsButton) { } settingsButtonLightNoIcon: SettingsButton(settingsButtonLight, settingsButtonNoIcon) { } -settingsButtonNoIconLocked : SettingsButton(settingsButtonNoIcon) { +settingsButtonNoIconLocked: SettingsButton(settingsButtonNoIcon) { toggle: Toggle(infoProfileToggle) { lockIcon: icon {{ "info/info_rights_lock", menuIconFg }}; } diff --git a/Telegram/SourceFiles/ui/boxes/boost_box.cpp b/Telegram/SourceFiles/ui/boxes/boost_box.cpp index c70860189a..d2cfeafca4 100644 --- a/Telegram/SourceFiles/ui/boxes/boost_box.cpp +++ b/Telegram/SourceFiles/ui/boxes/boost_box.cpp @@ -143,6 +143,7 @@ void AddFeaturesList( const auto proj = &Ui::Text::RichLangValue; const auto lowMax = std::max({ features.linkLogoLevel, + features.autotranslateLevel, features.transcribeLevel, features.emojiPackLevel, features.emojiStatusLevel, @@ -208,6 +209,11 @@ void AddFeaturesList( st::boostFeatureCustomEmoji); } if (!group) { + if (i >= features.autotranslateLevel) { + add( + tr::lng_feature_autotranslate(proj), + st::boostFeatureAutoTranslate); + } if (const auto j = features.linkStylesByLevel.find(i) ; j != end(features.linkStylesByLevel)) { linkStyles += j->second; @@ -665,6 +671,8 @@ void AskBoostBox( Fn startGiveaway) { box->setWidth(st::boxWideWidth); box->setStyle(st::boostBox); + box->setNoContentMargin(true); + box->addSkip(st::boxRowPadding.left()); FillBoostLimit( BoxShowFinishes(box), @@ -676,6 +684,8 @@ void AskBoostBox( auto title = v::match(data.reason.data, [](AskBoostChannelColor) { return tr::lng_boost_channel_title_color(); + }, [](AskBoostAutotranslate) { + return tr::lng_boost_channel_title_autotranslate(); }, [](AskBoostWallpaper) { return tr::lng_boost_channel_title_wallpaper(); }, [](AskBoostEmojiStatus) { @@ -696,6 +706,11 @@ void AskBoostBox( lt_count, rpl::single(float64(data.requiredLevel)), Ui::Text::RichLangValue); + }, [&](AskBoostAutotranslate data) { + return tr::lng_boost_channel_needs_level_autotranslate( + lt_count, + rpl::single(float64(data.requiredLevel)), + Ui::Text::RichLangValue); }, [&](AskBoostWallpaper data) { isGroup = data.group; return (data.group @@ -766,6 +781,12 @@ void AskBoostBox( box->uiShow(), std::move(stats))); + AddFeaturesList( + box->verticalLayout(), + data.features, + data.boost.level + (data.boost.nextLevelBoosts ? 1 : 0), + data.group); + auto submit = tr::lng_boost_channel_ask_button(); const auto button = box->addButton(rpl::duplicate(submit), [=] { QGuiApplication::clipboard()->setText(data.link); diff --git a/Telegram/SourceFiles/ui/boxes/boost_box.h b/Telegram/SourceFiles/ui/boxes/boost_box.h index e232f0524b..d53c30ab81 100644 --- a/Telegram/SourceFiles/ui/boxes/boost_box.h +++ b/Telegram/SourceFiles/ui/boxes/boost_box.h @@ -35,6 +35,7 @@ struct BoostFeatures { base::flat_map nameColorsByLevel; base::flat_map linkStylesByLevel; int linkLogoLevel = 0; + int autotranslateLevel = 0; int transcribeLevel = 0; int emojiPackLevel = 0; int emojiStatusLevel = 0; @@ -74,6 +75,10 @@ struct AskBoostChannelColor { int requiredLevel = 0; }; +struct AskBoostAutotranslate { + int requiredLevel = 0; +}; + struct AskBoostWallpaper { int requiredLevel = 0; bool group = false; @@ -103,6 +108,7 @@ struct AskBoostWearCollectible { struct AskBoostReason { std::variant< AskBoostChannelColor, + AskBoostAutotranslate, AskBoostWallpaper, AskBoostEmojiStatus, AskBoostEmojiPack, @@ -114,7 +120,9 @@ struct AskBoostReason { struct AskBoostBoxData { QString link; BoostCounters boost; + BoostFeatures features; AskBoostReason reason; + bool group = false; }; void AskBoostBox( diff --git a/Telegram/SourceFiles/ui/effects/premium.style b/Telegram/SourceFiles/ui/effects/premium.style index a97c5b16bc..55e83334b6 100644 --- a/Telegram/SourceFiles/ui/effects/premium.style +++ b/Telegram/SourceFiles/ui/effects/premium.style @@ -371,6 +371,7 @@ boostFeatureLink: icon{{ "settings/premium/features/feature_links", windowBgActi boostFeatureName: icon{{ "settings/premium/features/feature_color_names", windowBgActive }}; boostFeatureStories: icon{{ "settings/premium/features/feature_stories", windowBgActive }}; boostFeatureTranscribe: icon{{ "settings/premium/features/feature_voice", windowBgActive }}; +boostFeatureAutoTranslate: icon{{ "menu/translate", windowBgActive }}; boostFeatureOffSponsored: icon{{ "settings/premium/features/feature_off_sponsored", windowBgActive }}; paidReactBox: Box(boostBox) {