Start editing chat intro in Telegram Business.

This commit is contained in:
John Preston 2024-03-19 10:09:00 +04:00
parent cf1d0677d1
commit 191c35914f
30 changed files with 1056 additions and 44 deletions

View File

@ -1293,6 +1293,8 @@ PRIVATE
settings/business/settings_away_message.h
settings/business/settings_shortcut_messages.cpp
settings/business/settings_shortcut_messages.h
settings/business/settings_chat_intro.cpp
settings/business/settings_chat_intro.h
settings/business/settings_chatbots.cpp
settings/business/settings_chatbots.h
settings/business/settings_greeting.cpp

Binary file not shown.

After

Width:  |  Height:  |  Size: 523 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 973 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

@ -2185,6 +2185,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_business_about_away_messages" = "Define messages that are automatically sent when you are off.";
"lng_business_subtitle_chatbots" = "Chatbots";
"lng_business_about_chatbots" = "Add any third party chatbots that will process customer interactions.";
"lng_business_subtitle_chat_intro" = "Intro";
"lng_business_about_chat_intro" = "Customize the message people see before they start a chat with you.";
"lng_location_title" = "Location";
"lng_location_about" = "Display the location of your business on your account.";
@ -2297,6 +2299,17 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_chatbot_menu_remove" = "Remove bot from this chat";
"lng_chatbot_menu_revoke" = "Revoke access to this chat";
"lng_chat_intro_title" = "Intro";
"lng_chat_intro_subtitle" = "Customize your intro";
"lng_chat_intro_default_title" = "No messages here yet...";
"lng_chat_intro_default_message" = "Send a message or tap on the greeting below";
"lng_chat_intro_enter_title" = "Enter Title";
"lng_chat_intro_enter_message" = "Enter Message";
"lng_chat_intro_choose_sticker" = "Choose Sticker";
"lng_chat_intro_random_sticker" = "Random";
"lng_chat_intro_about" = "You can customize the message people see before they start a chat with you.";
"lng_chat_intro_reset" = "Reset to Default";
"lng_boost_channel_button" = "Boost Channel";
"lng_boost_group_button" = "Boost Group";
"lng_boost_again_button" = "Boost Again";

View File

@ -109,6 +109,18 @@ rpl::producer<> Premium::cloudSetUpdated() const {
return _cloudSetUpdated.events();
}
auto Premium::helloStickers() const
-> const std::vector<not_null<DocumentData*>> & {
if (_helloStickers.empty()) {
const_cast<Premium*>(this)->reloadHelloStickers();
}
return _helloStickers;
}
rpl::producer<> Premium::helloStickersUpdated() const {
return _helloStickersUpdated.events();
}
int64 Premium::monthlyAmount() const {
return _monthlyAmount;
}
@ -225,6 +237,33 @@ void Premium::reloadCloudSet() {
}).send();
}
void Premium::reloadHelloStickers() {
if (_helloStickersRequestId) {
return;
}
_helloStickersRequestId = _api.request(MTPmessages_GetStickers(
MTP_string("\xf0\x9f\x91\x8b\xe2\xad\x90\xef\xb8\x8f"),
MTP_long(_helloStickersHash)
)).done([=](const MTPmessages_Stickers &result) {
_helloStickersRequestId = 0;
result.match([&](const MTPDmessages_stickersNotModified &) {
}, [&](const MTPDmessages_stickers &data) {
_helloStickersHash = data.vhash().v;
const auto owner = &_session->data();
_helloStickers.clear();
for (const auto &sticker : data.vstickers().v) {
const auto document = owner->processDocument(sticker);
if (document->sticker()) {
_helloStickers.push_back(document);
}
}
_helloStickersUpdated.fire({});
});
}).fail([=] {
_helloStickersRequestId = 0;
}).send();
}
void Premium::checkGiftCode(
const QString &slug,
Fn<void(GiftCode)> done) {
@ -609,4 +648,24 @@ RequirePremiumState ResolveRequiresPremiumToWrite(
return RequirePremiumState::Unknown;
}
rpl::producer<DocumentData*> RandomHelloStickerValue(
not_null<Main::Session*> session) {
const auto premium = &session->api().premium();
const auto random = [=] {
const auto &v = premium->helloStickers();
Assert(!v.empty());
return v[base::RandomIndex(v.size())].get();
};
const auto &v = premium->helloStickers();
if (!v.empty()) {
return rpl::single(random());
}
return rpl::single<DocumentData*>(
nullptr
) | rpl::then(premium->helloStickersUpdated(
) | rpl::filter([=] {
return !premium->helloStickers().empty();
}) | rpl::take(1) | rpl::map(random));
}
} // namespace Api

View File

@ -85,6 +85,10 @@ public:
-> const std::vector<not_null<DocumentData*>> &;
[[nodiscard]] rpl::producer<> cloudSetUpdated() const;
[[nodiscard]] auto helloStickers() const
-> const std::vector<not_null<DocumentData*>> &;
[[nodiscard]] rpl::producer<> helloStickersUpdated() const;
[[nodiscard]] int64 monthlyAmount() const;
[[nodiscard]] QString monthlyCurrency() const;
@ -111,6 +115,7 @@ private:
void reloadPromo();
void reloadStickers();
void reloadCloudSet();
void reloadHelloStickers();
void requestPremiumRequiredSlice();
const not_null<Main::Session*> _session;
@ -133,6 +138,11 @@ private:
std::vector<not_null<DocumentData*>> _cloudSet;
rpl::event_stream<> _cloudSetUpdated;
mtpRequestId _helloStickersRequestId = 0;
uint64 _helloStickersHash = 0;
std::vector<not_null<DocumentData*>> _helloStickers;
rpl::event_stream<> _helloStickersUpdated;
int64 _monthlyAmount = 0;
QString _monthlyCurrency;
@ -215,4 +225,7 @@ enum class RequirePremiumState {
not_null<PeerData*> peer,
History *maybeHistory);
[[nodiscard]] rpl::producer<DocumentData*> RandomHelloStickerValue(
not_null<Main::Session*> session);
} // namespace Api

View File

@ -808,34 +808,6 @@ int ColorSelector::resizeGetHeight(int newWidth) {
return st;
}
struct ButtonWithEmoji {
not_null<const style::SettingsButton*> st;
int emojiWidth = 0;
int noneWidth = 0;
int added = 0;
};
[[nodiscard]] ButtonWithEmoji ButtonStyleWithRightEmoji(
not_null<Ui::RpWidget*> parent) {
const auto ratio = style::DevicePixelRatio();
const auto emojiWidth = Data::FrameSizeFromTag({}) / ratio;
const auto noneWidth = st::normalFont->width(
tr::lng_settings_color_emoji_off(tr::now));
const auto added = st::normalFont->spacew;
const auto rightAdded = std::max(noneWidth, emojiWidth);
return {
.st = ButtonStyleWithAddedPadding(
parent,
st::peerAppearanceButton,
QMargins(0, 0, added + rightAdded, 0)),
.emojiWidth = emojiWidth,
.noneWidth = noneWidth,
.added = added,
};
}
[[nodiscard]] object_ptr<Ui::SettingsButton> CreateEmojiIconButton(
not_null<Ui::RpWidget*> parent,
std::shared_ptr<ChatHelpers::Show> show,
@ -844,7 +816,9 @@ struct ButtonWithEmoji {
rpl::producer<uint8> colorIndexValue,
rpl::producer<DocumentId> emojiIdValue,
Fn<void(DocumentId)> emojiIdChosen) {
const auto button = ButtonStyleWithRightEmoji(parent);
const auto button = ButtonStyleWithRightEmoji(
parent,
tr::lng_settings_color_emoji_off(tr::now));
auto result = Settings::CreateButtonWithIcon(
parent,
tr::lng_settings_color_emoji(),
@ -962,7 +936,9 @@ struct ButtonWithEmoji {
rpl::producer<DocumentId> statusIdValue,
Fn<void(DocumentId,TimeId)> statusIdChosen,
bool group) {
const auto button = ButtonStyleWithRightEmoji(parent);
const auto button = ButtonStyleWithRightEmoji(
parent,
tr::lng_settings_color_emoji_off(tr::now));
const auto &phrase = group
? tr::lng_edit_channel_status_group
: tr::lng_edit_channel_status;
@ -1073,7 +1049,9 @@ struct ButtonWithEmoji {
not_null<ChannelData*> channel) {
Expects(channel->mgInfo != nullptr);
const auto button = ButtonStyleWithRightEmoji(parent);
const auto button = ButtonStyleWithRightEmoji(
parent,
tr::lng_settings_color_emoji_off(tr::now));
auto result = Settings::CreateButtonWithIcon(
parent,
tr::lng_group_emoji(),
@ -1509,3 +1487,25 @@ void CheckBoostLevel(
cancel();
}).send();
}
ButtonWithEmoji ButtonStyleWithRightEmoji(
not_null<Ui::RpWidget*> parent,
const QString &noneString,
const style::SettingsButton &parentSt) {
const auto ratio = style::DevicePixelRatio();
const auto emojiWidth = Data::FrameSizeFromTag({}) / ratio;
const auto noneWidth = st::normalFont->width(noneString);
const auto added = st::normalFont->spacew;
const auto rightAdded = std::max(noneWidth, emojiWidth);
return {
.st = ButtonStyleWithAddedPadding(
parent,
parentSt,
QMargins(0, 0, added + rightAdded, 0)),
.emojiWidth = emojiWidth,
.noneWidth = noneWidth,
.added = added,
};
}

View File

@ -7,6 +7,14 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#pragma once
namespace style {
struct SettingsButton;
} // namespace style
namespace st {
extern const style::SettingsButton &peerAppearanceButton;
} // namespace st
namespace ChatHelpers {
class Show;
} // namespace ChatHelpers
@ -17,6 +25,7 @@ class ChatStyle;
class ChatTheme;
class VerticalLayout;
struct AskBoostReason;
class RpWidget;
} // namespace Ui
void EditPeerColorBox(
@ -36,3 +45,14 @@ void CheckBoostLevel(
not_null<PeerData*> peer,
Fn<std::optional<Ui::AskBoostReason>(int level)> askMore,
Fn<void()> cancel);
struct ButtonWithEmoji {
not_null<const style::SettingsButton*> st;
int emojiWidth = 0;
int noneWidth = 0;
int added = 0;
};
[[nodiscard]] ButtonWithEmoji ButtonStyleWithRightEmoji(
not_null<Ui::RpWidget*> parent,
const QString &noneString,
const style::SettingsButton &parentSt = st::peerAppearanceButton);

View File

@ -144,6 +144,8 @@ void PreloadSticker(const std::shared_ptr<Data::DocumentMedia> &media) {
return tr::lng_business_subtitle_away_messages();
case PremiumFeature::BusinessBots:
return tr::lng_business_subtitle_chatbots();
case PremiumFeature::ChatIntro:
return tr::lng_business_subtitle_chat_intro();
}
Unexpected("PremiumFeature in SectionTitle.");
}
@ -201,6 +203,8 @@ void PreloadSticker(const std::shared_ptr<Data::DocumentMedia> &media) {
return tr::lng_business_about_away_messages();
case PremiumFeature::BusinessBots:
return tr::lng_business_about_chatbots();
case PremiumFeature::ChatIntro:
return tr::lng_business_about_chat_intro();
}
Unexpected("PremiumFeature in SectionTitle.");
}
@ -528,6 +532,7 @@ struct VideoPreviewDocument {
case PremiumFeature::GreetingMessage: return "greeting_message";
case PremiumFeature::AwayMessage: return "away_message";
case PremiumFeature::BusinessBots: return "business_bots";
case PremiumFeature::ChatIntro: return "chat_intro";
}
return "";
}();

View File

@ -74,6 +74,7 @@ enum class PremiumFeature {
GreetingMessage,
AwayMessage,
BusinessBots,
ChatIntro,
kCount,
};

View File

@ -66,7 +66,7 @@ enum class StickerLottieSize : uint8 {
EmojiInteractionReserved5,
EmojiInteractionReserved6,
EmojiInteractionReserved7,
PremiumReactionPreview,
ChatIntroHelloSticker,
};
[[nodiscard]] uint8 LottieCacheKeyShift(
uint8 replacementsTag,

View File

@ -378,6 +378,9 @@ TabbedSelector::TabbedSelector(
tabs.reserve(2);
tabs.push_back(createTab(SelectorTab::Stickers, 0));
tabs.push_back(createTab(SelectorTab::Masks, 1));
} else if (_mode == Mode::StickersOnly) {
tabs.reserve(1);
tabs.push_back(createTab(SelectorTab::Stickers, 0));
} else {
tabs.reserve(1);
tabs.push_back(createTab(SelectorTab::Emoji, 0));
@ -385,10 +388,10 @@ TabbedSelector::TabbedSelector(
return tabs;
}())
, _currentTabType(full()
? session().settings().selectorTab()
: mediaEditor()
? SelectorTab::Stickers
: SelectorTab::Emoji)
? session().settings().selectorTab()
: (mediaEditor() || _mode == Mode::StickersOnly)
? SelectorTab::Stickers
: SelectorTab::Emoji)
, _hasEmojiTab(ranges::contains(_tabs, SelectorTab::Emoji, &Tab::type))
, _hasStickersTab(ranges::contains(_tabs, SelectorTab::Stickers, &Tab::type))
, _hasGifsTab(ranges::contains(_tabs, SelectorTab::Gifs, &Tab::type))
@ -661,7 +664,7 @@ void TabbedSelector::updateTabsSliderGeometry() {
if (!_tabsSlider) {
return;
}
const auto w = mediaEditor() && hasMasksTab() && masks()->mySetsEmpty()
const auto w = (mediaEditor() && hasMasksTab() && masks()->mySetsEmpty())
? width() / 2
: width();
_tabsSlider->resizeToWidth(w);

View File

@ -79,6 +79,7 @@ using InlineChosen = InlineBots::ResultSelected;
enum class TabbedSelectorMode {
Full,
EmojiOnly,
StickersOnly,
MediaEditor,
EmojiStatus,
ChannelStatus,

View File

@ -7,6 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#include "data/business/data_business_common.h"
#include "data/data_document.h"
#include "data/data_session.h"
#include "data/data_user.h"
@ -333,4 +334,22 @@ WorkingIntervals ReplaceDayIntervals(
return result.normalized();
}
ChatIntro FromMTP(
not_null<Session*> owner,
const tl::conditional<MTPBusinessIntro> &intro) {
auto result = ChatIntro();
if (intro) {
const auto &data = intro->data();
result.title = qs(data.vtitle());
result.description = qs(data.vdescription());
if (const auto document = data.vsticker()) {
result.sticker = owner->processDocument(*document);
if (!result.sticker->sticker()) {
result.sticker = nullptr;
}
}
}
return result;
}
} // namespace Data

View File

@ -252,4 +252,22 @@ struct GreetingSettings {
not_null<Session*> owner,
const tl::conditional<MTPBusinessGreetingMessage> &message);
struct ChatIntro {
QString title;
QString description;
DocumentData *sticker = nullptr;
explicit operator bool() const {
return !title.isEmpty() || !description.isEmpty();
}
friend inline bool operator==(
const ChatIntro &a,
const ChatIntro &b) = default;
};
[[nodiscard]] ChatIntro FromMTP(
not_null<Session*> owner,
const tl::conditional<MTPBusinessIntro> &intro);
} // namespace Data

View File

@ -10,6 +10,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "apiwrap.h"
#include "base/unixtime.h"
#include "data/business/data_business_common.h"
#include "data/data_document.h"
#include "data/data_session.h"
#include "data/data_user.h"
#include "main/main_session.h"
@ -183,6 +184,54 @@ rpl::producer<> BusinessInfo::greetingSettingsChanged() const {
return _greetingSettingsChanged.events();
}
void BusinessInfo::saveChatIntro(ChatIntro data, Fn<void(QString)> fail) {
const auto &was = _chatIntro;
if (was == data) {
return;
} else {
const auto session = &_owner->session();
using Flag = MTPaccount_UpdateBusinessIntro::Flag;
session->api().request(MTPaccount_UpdateBusinessIntro(
MTP_flags(data ? Flag::f_intro : Flag()),
MTP_inputBusinessIntro(
MTP_flags(data.sticker ? MTPDinputBusinessIntro::Flag::f_sticker : MTPDinputBusinessIntro::Flag()),
MTP_string(data.title),
MTP_string(data.description),
(data.sticker
? data.sticker->mtpInput()
: MTP_inputDocumentEmpty()))
)).fail([=](const MTP::Error &error) {
_chatIntro = was;
_chatIntroChanged.fire({});
if (fail) {
fail(error.type());
}
}).send();
}
_chatIntro = std::move(data);
_chatIntroChanged.fire({});
}
void BusinessInfo::applyChatIntro(ChatIntro data) {
if (_chatIntro == data) {
return;
}
_chatIntro = data;
_chatIntroChanged.fire({});
}
ChatIntro BusinessInfo::chatIntro() const {
return _chatIntro.value_or(ChatIntro());
}
bool BusinessInfo::chatIntroLoaded() const {
return _chatIntro.has_value();
}
rpl::producer<> BusinessInfo::chatIntroChanged() const {
return _chatIntroChanged.events();
}
void BusinessInfo::preload() {
preloadTimezones();
}

View File

@ -36,6 +36,12 @@ public:
[[nodiscard]] bool greetingSettingsLoaded() const;
[[nodiscard]] rpl::producer<> greetingSettingsChanged() const;
void saveChatIntro(ChatIntro data, Fn<void(QString)> fail);
void applyChatIntro(ChatIntro data);
[[nodiscard]] ChatIntro chatIntro() const;
[[nodiscard]] bool chatIntroLoaded() const;
[[nodiscard]] rpl::producer<> chatIntroChanged() const;
void preloadTimezones();
[[nodiscard]] bool timezonesLoaded() const;
[[nodiscard]] rpl::producer<Timezones> timezonesValue() const;
@ -51,6 +57,9 @@ private:
std::optional<GreetingSettings> _greetingSettings;
rpl::event_stream<> _greetingSettingsChanged;
std::optional<ChatIntro> _chatIntro;
rpl::event_stream<> _chatIntroChanged;
mtpRequestId _timezonesRequestId = 0;
int32 _timezonesHash = 0;

View File

@ -601,6 +601,8 @@ void ApplyUserUpdate(not_null<UserData*> user, const MTPDuserFull &update) {
FromMTP(&user->owner(), update.vbusiness_away_message()));
user->owner().businessInfo().applyGreetingSettings(
FromMTP(&user->owner(), update.vbusiness_greeting_message()));
user->owner().businessInfo().applyChatIntro(
FromMTP(&user->owner(), update.vbusiness_intro()));
}
user->owner().stories().apply(user, update.vstories());

View File

@ -7,10 +7,15 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#include "history/view/history_view_about_view.h"
#include "chat_helpers/stickers_lottie.h"
#include "core/click_handler_types.h"
#include "data/business/data_business_common.h"
#include "data/data_document.h"
#include "data/data_session.h"
#include "data/data_user.h"
#include "history/view/media/history_view_service_box.h"
#include "history/view/media/history_view_sticker_player_abstract.h"
#include "history/view/media/history_view_sticker.h"
#include "history/view/history_view_element.h"
#include "history/history.h"
#include "history/history_item.h"
@ -64,6 +69,43 @@ private:
};
class ChatIntroBox final : public ServiceBoxContent {
public:
ChatIntroBox(not_null<Element*> parent, Data::ChatIntro data);
~ChatIntroBox();
int width() override;
int top() override;
QSize size() override;
QString title() override;
TextWithEntities subtitle() override;
int buttonSkip() override;
rpl::producer<QString> button() override;
void draw(
Painter &p,
const PaintContext &context,
const QRect &geometry) override;
ClickHandlerPtr createViewLink() override;
bool hideServiceText() override {
return true;
}
void stickerClearLoopPlayed() override;
std::unique_ptr<StickerPlayer> stickerTakePlayer(
not_null<DocumentData*> data,
const Lottie::ColorReplacements *replacements) override;
bool hasHeavyPart() override;
void unloadHeavyPart() override;
private:
const not_null<Element*> _parent;
const Data::ChatIntro _data;
mutable std::optional<Sticker> _sticker;
};
PremiumRequiredBox::PremiumRequiredBox(not_null<Element*> parent)
: _parent(parent) {
}
@ -133,6 +175,99 @@ bool PremiumRequiredBox::hasHeavyPart() {
void PremiumRequiredBox::unloadHeavyPart() {
}
ChatIntroBox::ChatIntroBox(not_null<Element*> parent, Data::ChatIntro data)
: _parent(parent)
, _data(data) {
if (const auto document = data.sticker) {
if (const auto sticker = document->sticker()) {
const auto skipPremiumEffect = false;
_sticker.emplace(_parent, document, skipPremiumEffect, _parent);
_sticker->setDiceIndex(sticker->alt, 0);
_sticker->setGiftBoxSticker(true);
_sticker->initSize();
_sticker->setCustomEmojiPart(
st::chatIntroStickerSize,
ChatHelpers::StickerLottieSize::ChatIntroHelloSticker);
}
}
}
ChatIntroBox::~ChatIntroBox() = default;
int ChatIntroBox::width() {
return st::chatIntroWidth;
}
int ChatIntroBox::top() {
return st::msgServiceGiftBoxButtonMargins.top();
}
QSize ChatIntroBox::size() {
return { st::msgServicePhotoWidth, st::msgServicePhotoWidth };
}
QString ChatIntroBox::title() {
return _data ? _data.title : tr::lng_chat_intro_default_title(tr::now);
}
int ChatIntroBox::buttonSkip() {
return st::storyMentionButtonSkip;
}
rpl::producer<QString> ChatIntroBox::button() {
return nullptr;
}
TextWithEntities ChatIntroBox::subtitle() {
return {
(_data
? _data.description
: tr::lng_chat_intro_default_message(tr::now))
};
}
ClickHandlerPtr ChatIntroBox::createViewLink() {
return std::make_shared<LambdaClickHandler>([=](ClickContext context) {
const auto my = context.other.value<ClickHandlerContext>();
if (const auto controller = my.sessionWindow.get()) {
Settings::ShowPremium(controller, u"require_premium"_q);
}
});
}
void ChatIntroBox::draw(
Painter &p,
const PaintContext &context,
const QRect &geometry) {
if (_sticker) {
_sticker->draw(p, context, geometry);
}
}
void ChatIntroBox::stickerClearLoopPlayed() {
if (_sticker) {
_sticker->stickerClearLoopPlayed();
}
}
std::unique_ptr<StickerPlayer> ChatIntroBox::stickerTakePlayer(
not_null<DocumentData*> data,
const Lottie::ColorReplacements *replacements) {
return _sticker
? _sticker->stickerTakePlayer(data, replacements)
: nullptr;
}
bool ChatIntroBox::hasHeavyPart() {
return _sticker && _sticker->hasHeavyPart();
}
void ChatIntroBox::unloadHeavyPart() {
if (_sticker) {
_sticker->unloadHeavyPart();
}
}
} // namespace
AboutView::AboutView(
@ -142,6 +277,10 @@ AboutView::AboutView(
, _delegate(delegate) {
}
AboutView::~AboutView() {
setItem({}, nullptr);
}
not_null<History*> AboutView::history() const {
return _history;
}
@ -187,6 +326,37 @@ bool AboutView::refresh() {
return true;
}
void AboutView::make(Data::ChatIntro data) {
const auto item = _history->makeMessage({
.id = _history->nextNonHistoryEntryId(),
.flags = (MessageFlag::FakeAboutView
| MessageFlag::FakeHistoryItem
| MessageFlag::Local),
.from = _history->peer->id,
}, PreparedServiceText{ { data.description } });
setItem(AdminLog::OwnedItem(_delegate, item), data.sticker);
_item->overrideMedia(std::make_unique<ServiceBox>(
_item.get(),
std::make_unique<ChatIntroBox>(_item.get(), data)));
}
void AboutView::setItem(AdminLog::OwnedItem item, DocumentData *sticker) {
if (const auto was = _item ? _item->data().get() : nullptr) {
if (_sticker) {
was->history()->owner().unregisterDocumentItem(_sticker, was);
}
}
_item = std::move(item);
_sticker = sticker;
if (const auto now = _item ? _item->data().get() : nullptr) {
if (_sticker) {
now->history()->owner().registerDocumentItem(_sticker, now);
}
}
}
AdminLog::OwnedItem AboutView::makeAboutBot(not_null<BotInfo*> info) {
const auto textWithEntities = TextUtilities::ParseEntities(
info->description,

View File

@ -9,6 +9,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "history/admin_log/history_admin_log_item.h"
namespace Data {
struct ChatIntro;
} // namespace Data
namespace HistoryView {
class AboutView final : public ClickHandlerHost {
@ -16,6 +20,7 @@ public:
AboutView(
not_null<History*> history,
not_null<ElementDelegate*> delegate);
~AboutView();
[[nodiscard]] not_null<History*> history() const;
[[nodiscard]] Element *view() const;
@ -23,16 +28,20 @@ public:
bool refresh();
void make(Data::ChatIntro data);
int top = 0;
int height = 0;
private:
[[nodiscard]] AdminLog::OwnedItem makeAboutBot(not_null<BotInfo*> info);
[[nodiscard]] AdminLog::OwnedItem makePremiumRequired();
void setItem(AdminLog::OwnedItem item, DocumentData *sticker);
const not_null<History*> _history;
const not_null<ElementDelegate*> _delegate;
AdminLog::OwnedItem _item;
DocumentData *_sticker = nullptr;
int _version = 0;
};

View File

@ -884,6 +884,9 @@ BusinessBotStatus::Bar::Bar(QWidget *parent)
, _settings(this, st::historyBusinessBotSettings) {
_name->setAttribute(Qt::WA_TransparentForMouseEvents);
_status->setAttribute(Qt::WA_TransparentForMouseEvents);
_togglePaused->setFullRadius(true);
_togglePaused->setTextTransform(
Ui::RoundButton::TextTransform::NoTransform);
_settings->setClickedCallback([=] {
showMenu();
});
@ -984,7 +987,9 @@ int BusinessBotStatus::Bar::resizeGetHeight(int newWidth) {
}
auto available = newWidth - _settings->width() - st.namePosition.x();
if (!_togglePaused->isHidden()) {
_togglePaused->moveToRight(_settings->width(), 0);
_togglePaused->moveToRight(
_settings->width(),
(st.height - _togglePaused->height()) / 2);
available -= _togglePaused->width();
}
_name->resizeToWidth(available);

View File

@ -114,7 +114,7 @@ chatPhotoEmpty#37c1011c = ChatPhoto;
chatPhoto#1c6e1c11 flags:# has_video:flags.0?true photo_id:long stripped_thumb:flags.1?bytes dc_id:int = ChatPhoto;
messageEmpty#90a6ca84 flags:# id:int peer_id:flags.0?Peer = Message;
message#2357bf25 flags:# out:flags.1?true mentioned:flags.4?true media_unread:flags.5?true silent:flags.13?true post:flags.14?true from_scheduled:flags.18?true legacy:flags.19?true edit_hide:flags.21?true pinned:flags.24?true noforwards:flags.26?true invert_media:flags.27?true flags2:# id:int from_id:flags.8?Peer from_boosts_applied:flags.29?int peer_id:Peer saved_peer_id:flags.28?Peer fwd_from:flags.2?MessageFwdHeader via_bot_id:flags.11?long via_business_bot_id:flags2.0?long reply_to:flags.3?MessageReplyHeader date:int message:string media:flags.9?MessageMedia reply_markup:flags.6?ReplyMarkup entities:flags.7?Vector<MessageEntity> views:flags.10?int forwards:flags.10?int replies:flags.23?MessageReplies edit_date:flags.15?int post_author:flags.16?string grouped_id:flags.17?long reactions:flags.20?MessageReactions restriction_reason:flags.22?Vector<RestrictionReason> ttl_period:flags.25?int quick_reply_shortcut_id:flags.30?int = Message;
message#2357bf25 flags:# out:flags.1?true mentioned:flags.4?true media_unread:flags.5?true silent:flags.13?true post:flags.14?true from_scheduled:flags.18?true legacy:flags.19?true edit_hide:flags.21?true pinned:flags.24?true noforwards:flags.26?true invert_media:flags.27?true flags2:# offline:flags2.1?true id:int from_id:flags.8?Peer from_boosts_applied:flags.29?int peer_id:Peer saved_peer_id:flags.28?Peer fwd_from:flags.2?MessageFwdHeader via_bot_id:flags.11?long via_business_bot_id:flags2.0?long reply_to:flags.3?MessageReplyHeader date:int message:string media:flags.9?MessageMedia reply_markup:flags.6?ReplyMarkup entities:flags.7?Vector<MessageEntity> views:flags.10?int forwards:flags.10?int replies:flags.23?MessageReplies edit_date:flags.15?int post_author:flags.16?string grouped_id:flags.17?long reactions:flags.20?MessageReactions restriction_reason:flags.22?Vector<RestrictionReason> ttl_period:flags.25?int quick_reply_shortcut_id:flags.30?int = Message;
messageService#2b085862 flags:# out:flags.1?true mentioned:flags.4?true media_unread:flags.5?true silent:flags.13?true post:flags.14?true legacy:flags.19?true id:int from_id:flags.8?Peer peer_id:Peer reply_to:flags.3?MessageReplyHeader date:int action:MessageAction ttl_period:flags.25?int = Message;
messageMediaEmpty#3ded6320 = MessageMedia;
@ -227,7 +227,7 @@ inputReportReasonFake#f5ddd6e7 = ReportReason;
inputReportReasonIllegalDrugs#a8eb2be = ReportReason;
inputReportReasonPersonalDetails#9ec7863d = ReportReason;
userFull#670bbc9c flags:# blocked:flags.0?true phone_calls_available:flags.4?true phone_calls_private:flags.5?true can_pin_message:flags.7?true has_scheduled:flags.12?true video_calls_available:flags.13?true voice_messages_forbidden:flags.20?true translations_disabled:flags.23?true stories_pinned_available:flags.26?true blocked_my_stories_from:flags.27?true wallpaper_overridden:flags.28?true contact_require_premium:flags.29?true read_dates_private:flags.30?true flags2:# id:long about:flags.1?string settings:PeerSettings personal_photo:flags.21?Photo profile_photo:flags.2?Photo fallback_photo:flags.22?Photo notify_settings:PeerNotifySettings bot_info:flags.3?BotInfo pinned_msg_id:flags.6?int common_chats_count:int folder_id:flags.11?int ttl_period:flags.14?int theme_emoticon:flags.15?string private_forward_name:flags.16?string bot_group_admin_rights:flags.17?ChatAdminRights bot_broadcast_admin_rights:flags.18?ChatAdminRights premium_gifts:flags.19?Vector<PremiumGiftOption> wallpaper:flags.24?WallPaper stories:flags.25?PeerStories business_work_hours:flags2.0?BusinessWorkHours business_location:flags2.1?BusinessLocation business_greeting_message:flags2.2?BusinessGreetingMessage business_away_message:flags2.3?BusinessAwayMessage business_intro:flags2.4?BusinessIntro = UserFull;
userFull#ecdadceb flags:# blocked:flags.0?true phone_calls_available:flags.4?true phone_calls_private:flags.5?true can_pin_message:flags.7?true has_scheduled:flags.12?true video_calls_available:flags.13?true voice_messages_forbidden:flags.20?true translations_disabled:flags.23?true stories_pinned_available:flags.26?true blocked_my_stories_from:flags.27?true wallpaper_overridden:flags.28?true contact_require_premium:flags.29?true read_dates_private:flags.30?true flags2:# id:long about:flags.1?string settings:PeerSettings personal_photo:flags.21?Photo profile_photo:flags.2?Photo fallback_photo:flags.22?Photo notify_settings:PeerNotifySettings bot_info:flags.3?BotInfo pinned_msg_id:flags.6?int common_chats_count:int folder_id:flags.11?int ttl_period:flags.14?int theme_emoticon:flags.15?string private_forward_name:flags.16?string bot_group_admin_rights:flags.17?ChatAdminRights bot_broadcast_admin_rights:flags.18?ChatAdminRights premium_gifts:flags.19?Vector<PremiumGiftOption> wallpaper:flags.24?WallPaper stories:flags.25?PeerStories business_work_hours:flags2.0?BusinessWorkHours business_location:flags2.1?BusinessLocation business_greeting_message:flags2.2?BusinessGreetingMessage business_away_message:flags2.3?BusinessAwayMessage business_intro:flags2.4?BusinessIntro birthday:flags2.5?Birthday = UserFull;
contact#145ade0b user_id:long mutual:Bool = Contact;
@ -517,6 +517,7 @@ inputPrivacyKeyPhoneNumber#352dafa = InputPrivacyKey;
inputPrivacyKeyAddedByPhone#d1219bdd = InputPrivacyKey;
inputPrivacyKeyVoiceMessages#aee69d68 = InputPrivacyKey;
inputPrivacyKeyAbout#3823cc40 = InputPrivacyKey;
inputPrivacyKeyBirthday#d65a11cc = InputPrivacyKey;
privacyKeyStatusTimestamp#bc2eab30 = PrivacyKey;
privacyKeyChatInvite#500e6dfa = PrivacyKey;
@ -528,6 +529,7 @@ privacyKeyPhoneNumber#d19ae46d = PrivacyKey;
privacyKeyAddedByPhone#42ffd42b = PrivacyKey;
privacyKeyVoiceMessages#697f414 = PrivacyKey;
privacyKeyAbout#a486b761 = PrivacyKey;
privacyKeyBirthday#2000a518 = PrivacyKey;
inputPrivacyValueAllowContacts#d09e07b = InputPrivacyRule;
inputPrivacyValueAllowAll#184b35ce = InputPrivacyRule;
@ -1710,6 +1712,8 @@ account.connectedBots#17d7f87b connected_bots:Vector<ConnectedBot> users:Vector<
messages.dialogFilters#2ad93719 flags:# tags_enabled:flags.0?true filters:Vector<DialogFilter> = messages.DialogFilters;
birthday#6c8e1e06 flags:# day:int month:int year:flags.0?int = Birthday;
botBusinessConnection#896433b4 flags:# can_reply:flags.0?true disabled:flags.1?true connection_id:string user_id:long dc_id:int date:int = BotBusinessConnection;
inputBusinessIntro#9c469cd flags:# title:string description:string sticker:flags.0?InputDocument = InputBusinessIntro;
@ -1727,6 +1731,10 @@ inputBusinessBotRecipients#c4e5921e flags:# existing_chats:flags.0?true new_chat
businessBotRecipients#b88cf373 flags:# existing_chats:flags.0?true new_chats:flags.1?true contacts:flags.2?true non_contacts:flags.3?true exclude_selected:flags.5?true users:flags.4?Vector<long> exclude_users:flags.6?Vector<long> = BusinessBotRecipients;
contactBirthday#1d998733 contact_id:long birthday:Birthday = ContactBirthday;
contacts.contactBirthdays#114ff30d contacts:Vector<ContactBirthday> users:Vector<User> = contacts.ContactBirthdays;
---functions---
invokeAfterMsg#cb9f372d {X:Type} msg_id:long query:!X = X;
@ -1863,6 +1871,7 @@ account.getBotBusinessConnection#76a86270 connection_id:string = Updates;
account.updateBusinessIntro#a614d034 flags:# intro:flags.0?InputBusinessIntro = Bool;
account.toggleConnectedBotPaused#646e1097 peer:InputPeer paused:Bool = Bool;
account.disablePeerConnectedBot#5e437ed9 peer:InputPeer = Bool;
account.updateBirthday#cc6e0c11 flags:# birthday:flags.0?Birthday = Bool;
users.getUsers#d91a548 id:Vector<InputUser> = Vector<User>;
users.getFullUser#b60f5918 id:InputUser = users.UserFull;
@ -1894,6 +1903,7 @@ contacts.exportContactToken#f8654027 = ExportedContactToken;
contacts.importContactToken#13005788 token:string = User;
contacts.editCloseFriends#ba6705f0 id:Vector<long> = Bool;
contacts.setBlocked#94c65c76 flags:# my_stories_from:flags.0?true id:Vector<InputPeer> limit:int = Bool;
contacts.getBirthdays#daeda864 = contacts.ContactBirthdays;
messages.getMessages#63c66506 id:Vector<InputMessage> = messages.Messages;
messages.getDialogs#a0f4cb4f flags:# exclude_pinned:flags.0?true folder_id:flags.1?int offset_date:int offset_id:int offset_peer:InputPeer limit:int hash:long = messages.Dialogs;

View File

@ -31,7 +31,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
namespace Settings {
namespace {
class AwayMessage : public BusinessSection<AwayMessage> {
class AwayMessage final : public BusinessSection<AwayMessage> {
public:
AwayMessage(
QWidget *parent,

View File

@ -0,0 +1,564 @@
/*
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 "settings/business/settings_chat_intro.h"
#include "api/api_premium.h"
#include "boxes/peers/edit_peer_color_box.h" // ButtonStyleWithRightEmoji
#include "chat_helpers/tabbed_panel.h"
#include "chat_helpers/tabbed_selector.h"
#include "core/application.h"
#include "data/business/data_business_info.h"
#include "data/data_document.h"
#include "data/data_session.h"
#include "history/view/history_view_about_view.h"
#include "history/view/history_view_element.h"
#include "history/history.h"
#include "lang/lang_keys.h"
#include "main/main_session.h"
#include "settings/business/settings_recipients_helper.h"
#include "ui/chat/chat_style.h"
#include "ui/chat/chat_theme.h"
#include "ui/effects/path_shift_gradient.h"
#include "ui/text/text_utilities.h"
#include "ui/widgets/fields/input_field.h"
#include "ui/widgets/buttons.h"
#include "ui/wrap/vertical_layout.h"
#include "ui/wrap/slide_wrap.h"
#include "ui/painter.h"
#include "ui/vertical_list.h"
#include "window/themes/window_theme.h"
#include "window/section_widget.h"
#include "window/window_controller.h"
#include "window/window_session_controller.h"
#include "styles/style_chat.h"
#include "styles/style_chat_helpers.h"
#include "styles/style_layers.h"
#include "styles/style_settings.h"
namespace Settings {
namespace {
using namespace HistoryView;
class PreviewDelegate final : public DefaultElementDelegate {
public:
PreviewDelegate(
not_null<QWidget*> parent,
not_null<Ui::ChatStyle*> st,
Fn<void()> update);
bool elementAnimationsPaused() override;
not_null<Ui::PathShiftGradient*> elementPathShiftGradient() override;
Context elementContext() override;
private:
const not_null<QWidget*> _parent;
const std::unique_ptr<Ui::PathShiftGradient> _pathGradient;
};
class PreviewWrap final : public Ui::RpWidget {
public:
PreviewWrap(
not_null<QWidget*> parent,
not_null<Main::Session*> session,
rpl::producer<Data::ChatIntro> value);
~PreviewWrap();
private:
void paintEvent(QPaintEvent *e) override;
void resizeTo(int width);
void prepare(rpl::producer<Data::ChatIntro> value);
const not_null<History*> _history;
const std::unique_ptr<Ui::ChatTheme> _theme;
const std::unique_ptr<Ui::ChatStyle> _style;
const std::unique_ptr<PreviewDelegate> _delegate;
std::unique_ptr<AboutView> _view;
QPoint _position;
};
class StickerPanel final {
public:
StickerPanel();
~StickerPanel();
struct Descriptor {
not_null<Window::SessionController*> controller;
not_null<QWidget*> button;
DocumentId ensureAddedId = 0;
};
void show(Descriptor &&descriptor);
void repaint();
[[nodiscard]] bool hasFocus() const;
struct CustomChosen {
not_null<DocumentData*> sticker;
};
[[nodiscard]] rpl::producer<CustomChosen> someCustomChosen() const {
return _someCustomChosen.events();
}
private:
void create(const Descriptor &descriptor);
base::unique_qptr<ChatHelpers::TabbedPanel> _panel;
QPointer<QWidget> _panelButton;
rpl::event_stream<CustomChosen> _someCustomChosen;
};
class ChatIntro final : public BusinessSection<ChatIntro> {
public:
ChatIntro(
QWidget *parent,
not_null<Window::SessionController*> controller);
~ChatIntro();
[[nodiscard]] bool closeByOutsideClick() const override;
[[nodiscard]] rpl::producer<QString> title() override;
void setInnerFocus() override {
_setFocus();
}
private:
void setupContent(not_null<Window::SessionController*> controller);
void save();
Fn<void()> _setFocus;
rpl::variable<Data::ChatIntro> _intro;
};
[[nodiscard]] object_ptr<Ui::SettingsButton> CreateIntroStickerButton(
not_null<Ui::RpWidget*> parent,
std::shared_ptr<ChatHelpers::Show> show,
rpl::producer<DocumentData*> stickerValue,
Fn<void(DocumentData*)> stickerChosen) {
const auto button = ButtonStyleWithRightEmoji(
parent,
tr::lng_chat_intro_random_sticker(tr::now),
st::settingsButtonNoIcon);
auto result = Settings::CreateButtonWithIcon(
parent,
tr::lng_chat_intro_choose_sticker(),
*button.st);
const auto raw = result.data();
const auto right = Ui::CreateChild<Ui::RpWidget>(raw);
right->show();
struct State {
StickerPanel panel;
DocumentId stickerId = 0;
};
const auto state = right->lifetime().make_state<State>();
state->panel.someCustomChosen(
) | rpl::start_with_next([=](StickerPanel::CustomChosen chosen) {
stickerChosen(chosen.sticker);
}, raw->lifetime());
const auto session = &show->session();
std::move(
stickerValue
) | rpl::start_with_next([=](DocumentData *sticker) {
state->stickerId = sticker ? sticker->id : 0;
right->resize(
(sticker ? button.emojiWidth : button.noneWidth) + button.added,
right->height());
right->update();
}, right->lifetime());
rpl::combine(
raw->sizeValue(),
right->widthValue()
) | rpl::start_with_next([=](QSize outer, int width) {
right->resize(width, outer.height());
const auto skip = st::settingsButton.padding.right();
right->moveToRight(skip - button.added, 0, outer.width());
}, right->lifetime());
right->paintRequest(
) | rpl::start_with_next([=] {
auto p = QPainter(right);
const auto height = right->height();
if (false) {
// #TODO paint small sticker
} else {
const auto &font = st::normalFont;
p.setFont(font);
p.setPen(st::windowActiveTextFg);
p.drawText(
QPoint(
button.added,
(height - font->height) / 2 + font->ascent),
tr::lng_chat_intro_random_sticker(tr::now));
}
}, right->lifetime());
raw->setClickedCallback([=] {
const auto controller = show->resolveWindow(
ChatHelpers::WindowUsage::PremiumPromo);
if (controller) {
state->panel.show({
.controller = controller,
.button = right,
.ensureAddedId = state->stickerId,
});
}
});
return result;
}
PreviewDelegate::PreviewDelegate(
not_null<QWidget*> parent,
not_null<Ui::ChatStyle*> st,
Fn<void()> update)
: _parent(parent)
, _pathGradient(MakePathShiftGradient(st, update)) {
}
bool PreviewDelegate::elementAnimationsPaused() {
return _parent->window()->isActiveWindow();
}
auto PreviewDelegate::elementPathShiftGradient()
-> not_null<Ui::PathShiftGradient*> {
return _pathGradient.get();
}
Context PreviewDelegate::elementContext() {
return Context::History;
}
PreviewWrap::PreviewWrap(
not_null<QWidget*> parent,
not_null<Main::Session*> session,
rpl::producer<Data::ChatIntro> value)
: RpWidget(parent)
, _history(session->data().history(session->userPeerId()))
, _theme(Window::Theme::DefaultChatThemeOn(lifetime()))
, _style(std::make_unique<Ui::ChatStyle>(
_history->session().colorIndicesValue()))
, _delegate(std::make_unique<PreviewDelegate>(
parent,
_style.get(),
[=] { update(); }))
, _position(0, st::msgMargin.bottom()) {
_style->apply(_theme.get());
session->data().viewRepaintRequest(
) | rpl::start_with_next([=](not_null<const Element*> view) {
if (view == _view->view()) {
update();
}
}, lifetime());
prepare(std::move(value));
}
PreviewWrap::~PreviewWrap() {
_view = nullptr;
}
void PreviewWrap::prepare(rpl::producer<Data::ChatIntro> value) {
_view = std::make_unique<AboutView>(
_history.get(),
_delegate.get());
std::move(value) | rpl::start_with_next([=](Data::ChatIntro intro) {
_view->make(std::move(intro));
if (width() >= st::msgMinWidth) {
resizeTo(width());
}
update();
}, lifetime());
widthValue(
) | rpl::filter([=](int width) {
return width >= st::msgMinWidth;
}) | rpl::start_with_next([=](int width) {
resizeTo(width);
}, lifetime());
}
void PreviewWrap::resizeTo(int width) {
const auto height = _position.y()
+ _view->view()->resizeGetHeight(width)
+ _position.y()
+ st::msgServiceMargin.top()
+ st::msgServiceGiftBoxTopSkip
- st::msgServiceMargin.bottom();
resize(width, height);
}
void PreviewWrap::paintEvent(QPaintEvent *e) {
auto p = Painter(this);
const auto clip = e->rect();
if (!clip.isEmpty()) {
p.setClipRect(clip);
Window::SectionWidget::PaintBackground(
p,
_theme.get(),
QSize(width(), window()->height()),
clip);
}
auto context = _theme->preparePaintContext(
_style.get(),
rect(),
e->rect(),
!window()->isActiveWindow());
p.translate(_position);
_view->view()->draw(p, context);
}
StickerPanel::StickerPanel() = default;
StickerPanel::~StickerPanel() = default;
void StickerPanel::show(Descriptor &&descriptor) {
const auto controller = descriptor.controller;
if (!_panel) {
create(descriptor);
_panel->shownValue(
) | rpl::filter([=] {
return (_panelButton != nullptr);
}) | rpl::start_with_next([=](bool shown) {
if (shown) {
_panelButton->installEventFilter(_panel.get());
} else {
_panelButton->removeEventFilter(_panel.get());
}
}, _panel->lifetime());
}
const auto button = descriptor.button;
if (const auto previous = _panelButton.data()) {
if (previous != button) {
previous->removeEventFilter(_panel.get());
}
}
_panelButton = button;
const auto feed = [=, now = descriptor.ensureAddedId](
std::vector<DocumentId> list) {
list.insert(begin(list), 0);
if (now && !ranges::contains(list, now)) {
list.push_back(now);
}
_panel->selector()->provideRecentEmoji(list);
};
const auto parent = _panel->parentWidget();
const auto global = button->mapToGlobal(QPoint());
const auto local = parent->mapFromGlobal(global);
_panel->moveBottomRight(
local.y() + (st::normalFont->height / 2),
local.x() + button->width() * 3);
_panel->toggleAnimated();
}
bool StickerPanel::hasFocus() const {
return _panel && Ui::InFocusChain(_panel.get());
}
void StickerPanel::repaint() {
_panel->selector()->update();
}
void StickerPanel::create(const Descriptor &descriptor) {
using Selector = ChatHelpers::TabbedSelector;
using Descriptor = ChatHelpers::TabbedSelectorDescriptor;
using Mode = ChatHelpers::TabbedSelector::Mode;
const auto controller = descriptor.controller;
const auto body = controller->window().widget()->bodyWidget();
_panel = base::make_unique_q<ChatHelpers::TabbedPanel>(
body,
controller,
object_ptr<Selector>(
nullptr,
Descriptor{
.show = controller->uiShow(),
.st = st::backgroundEmojiPan,
.level = Window::GifPauseReason::Layer,
.mode = Mode::StickersOnly,
.features = {
.megagroupSet = false,
.stickersSettings = false,
.openStickerSets = false,
},
}));
_panel->setDropDown(false);
_panel->setDesiredHeightValues(
1.,
st::emojiPanMinHeight / 2,
st::emojiPanMinHeight);
_panel->hide();
_panel->selector()->fileChosen(
) | rpl::start_with_next([=](ChatHelpers::FileChosen data) {
_someCustomChosen.fire({ data.document });
_panel->hideAnimated();
}, _panel->lifetime());
}
ChatIntro::ChatIntro(
QWidget *parent,
not_null<Window::SessionController*> controller)
: BusinessSection(parent, controller) {
setupContent(controller);
}
ChatIntro::~ChatIntro() {
if (!Core::Quitting()) {
save();
}
}
bool ChatIntro::closeByOutsideClick() const {
return false;
}
rpl::producer<QString> ChatIntro::title() {
return tr::lng_chat_intro_title();
}
[[nodiscard]] rpl::producer<Data::ChatIntro> IntroWithRandomSticker(
not_null<Main::Session*> session,
rpl::producer<Data::ChatIntro> intro) {
return std::move(intro) | rpl::map([=](Data::ChatIntro intro)
-> rpl::producer<Data::ChatIntro> {
if (intro.sticker) {
return rpl::single(std::move(intro));
}
return Api::RandomHelloStickerValue(
session
) | rpl::map([=](DocumentData *sticker) {
auto copy = intro;
copy.sticker = sticker;
return copy;
});
}) | rpl::flatten_latest();
}
void ChatIntro::setupContent(
not_null<Window::SessionController*> controller) {
using namespace rpl::mappers;
const auto content = Ui::CreateChild<Ui::VerticalLayout>(this);
const auto info = &controller->session().data().businessInfo();
const auto current = info->chatIntro();
_intro = info->chatIntro();
const auto change = [=](Fn<void(Data::ChatIntro &)> modify) {
auto intro = _intro.current();
modify(intro);
_intro = intro;
};
const auto preview = content->add(
object_ptr<PreviewWrap>(
content,
&controller->session(),
IntroWithRandomSticker(&controller->session(), _intro.value())),
{});
const auto title = content->add(
object_ptr<Ui::InputField>(
content,
st::settingsChatIntroField,
tr::lng_chat_intro_enter_title(),
current.title),
st::settingsChatIntroFieldMargins);
const auto description = content->add(
object_ptr<Ui::InputField>(
content,
st::settingsChatIntroField,
tr::lng_chat_intro_enter_message(),
current.description),
st::settingsChatIntroFieldMargins);
content->add(CreateIntroStickerButton(
content,
controller->uiShow(),
_intro.value() | rpl::map([](const Data::ChatIntro &intro) {
return intro.sticker;
}) | rpl::distinct_until_changed(),
[=](DocumentData *sticker) {
change([&](Data::ChatIntro &intro) {
intro.sticker = sticker;
});
}));
Ui::AddSkip(content);
title->changes() | rpl::start_with_next([=] {
change([&](Data::ChatIntro &intro) {
intro.title = title->getLastText();
});
}, title->lifetime());
description->changes() | rpl::start_with_next([=] {
change([&](Data::ChatIntro &intro) {
intro.description = description->getLastText();
});
}, description->lifetime());
_setFocus = [=] {
title->setFocusFast();
};
Ui::AddDividerText(
content,
tr::lng_chat_intro_about(),
st::peerAppearanceDividerTextMargin);
Ui::AddSkip(content);
const auto resetWrap = content->add(
object_ptr<Ui::SlideWrap<Ui::SettingsButton>>(
content,
object_ptr<Ui::SettingsButton>(
content,
tr::lng_chat_intro_reset(),
st::settingsAttentionButton
)));
resetWrap->toggleOn(
_intro.value() | rpl::map([](const Data::ChatIntro &intro) {
return !!intro;
}));
resetWrap->entity()->setClickedCallback([=] {
_intro = Data::ChatIntro();
});
Ui::ResizeFitChild(this, content);
}
void ChatIntro::save() {
const auto show = controller()->uiShow();
const auto fail = [=](QString error) {
if (error == u"BUSINESS_RECIPIENTS_EMPTY"_q) {
show->showToast(tr::lng_greeting_recipients_empty(tr::now));
}
};
controller()->session().data().businessInfo().saveChatIntro(
_intro.current(),
fail);
}
} // namespace
Type ChatIntroId() {
return ChatIntro::Id();
}
} // namespace Settings

View File

@ -0,0 +1,16 @@
/*
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
#include "settings/settings_type.h"
namespace Settings {
[[nodiscard]] Type ChatIntroId();
} // namespace Settings

View File

@ -46,7 +46,7 @@ struct BotState {
LookupState state = LookupState::Empty;
};
class Chatbots : public BusinessSection<Chatbots> {
class Chatbots final : public BusinessSection<Chatbots> {
public:
Chatbots(
QWidget *parent,

View File

@ -110,6 +110,7 @@ settingsBusinessIconReplies: icon {{ "settings/premium/business/business_quick",
settingsBusinessIconGreeting: icon {{ "settings/premium/status", settingsIconFg }};
settingsBusinessIconAway: icon {{ "settings/premium/business/business_away", settingsIconFg }};
settingsBusinessIconChatbots: icon {{ "settings/premium/business/business_chatbots", settingsIconFg }};
settingsBusinessIconChatIntro: icon {{ "settings/premium/intro", settingsIconFg }};
settingsPremiumNewBadge: FlatLabel(defaultFlatLabel) {
style: TextStyle(semiboldTextStyle) {
@ -638,3 +639,7 @@ settingsChatbotsNotFound: FlatLabel(defaultFlatLabel) {
}
settingsChatbotsDeleteIcon: icon {{ "dialogs/dialogs_cancel_search", dialogsMenuIconFg }};
settingsChatbotsDeleteIconOver: icon {{ "dialogs/dialogs_cancel_search", dialogsMenuIconFgOver }};
settingsChatIntroField: InputField(defaultMultiSelectSearchField) {
}
settingsChatIntroFieldMargins: margins(20px, 8px, 20px, 8px);

View File

@ -23,6 +23,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "main/main_app_config.h"
#include "main/main_session.h"
#include "settings/business/settings_away_message.h"
#include "settings/business/settings_chat_intro.h"
#include "settings/business/settings_chatbots.h"
#include "settings/business/settings_greeting.h"
#include "settings/business/settings_location.h"
@ -69,6 +70,7 @@ using Order = std::vector<QString>;
u"business_hours"_q,
u"business_location"_q,
u"business_bots"_q,
u"intro"_q,
};
}
@ -128,6 +130,15 @@ using Order = std::vector<QString>;
PremiumFeature::BusinessBots,
},
},
{
u"intro"_q,
Entry{
&st::settingsBusinessIconChatIntro,
tr::lng_business_subtitle_chat_intro(),
tr::lng_business_about_chat_intro(),
PremiumFeature::ChatIntro,
},
},
};
}
@ -227,9 +238,9 @@ void AddBusinessSummary(
icons.reserve(int(entryMap.size()));
{
const auto &account = controller->session().account();
const auto mtpOrder = account.appConfig().get<Order>(
const auto mtpOrder = /*account.appConfig().get<Order>(
"business_promo_order",
FallbackOrder());
FallbackOrder())*/FallbackOrder(); AssertIsDebug();
const auto processEntry = [&](Entry &entry) {
icons.push_back(entry.icon);
addRow(entry);
@ -375,6 +386,7 @@ void Business::setupContent() {
case PremiumFeature::GreetingMessage: return GreetingId();
case PremiumFeature::QuickReplies: return QuickRepliesId();
case PremiumFeature::BusinessBots: return ChatbotsId();
case PremiumFeature::ChatIntro: return ChatIntroId();
}
Unexpected("Feature in showFeature.");
}());
@ -396,6 +408,8 @@ void Business::setupContent() {
return owner->shortcutMessages().shortcutsLoaded();
case PremiumFeature::BusinessBots:
return owner->chatbots().loaded();
case PremiumFeature::ChatIntro:
return owner->session().user()->isFullLoaded();
}
Unexpected("Feature in isReady.");
};
@ -670,6 +684,8 @@ std::vector<PremiumFeature> BusinessFeaturesOrder(
return PremiumFeature::BusinessLocation;
} else if (s == u"business_bots"_q) {
return PremiumFeature::BusinessBots;
} else if (s == u"chat_intro"_q) {
return PremiumFeature::ChatIntro;
}
return PremiumFeature::kCount;
}) | ranges::views::filter([](PremiumFeature feature) {

View File

@ -1061,3 +1061,6 @@ boostsMessageIconPadding: margins(0px, 2px, 0px, 0px);
historyIvIcon: icon{{ "boosts/boost_mini2", windowFg }};
historyIvIconPadding: margins(2px, 2px, 2px, 0px);
chatIntroStickerSize: 96px;
chatIntroWidth: 224px;