| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646 |
- /*
- 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/stickers_lottie.h"
- #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_document_media.h"
- #include "data/data_session.h"
- #include "data/data_user.h"
- #include "history/view/media/history_view_media_common.h"
- #include "history/view/media/history_view_sticker_player.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_app_config.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;
- };
- void show(Descriptor &&descriptor);
- 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]] int PartLimit(
- not_null<Main::Session*> session,
- const QString &key,
- int defaultValue) {
- return session->appConfig().get<int>(key, defaultValue);
- }
- [[nodiscard]] not_null<Ui::InputField*> AddPartInput(
- not_null<Ui::VerticalLayout*> container,
- rpl::producer<QString> placeholder,
- QString current,
- int limit) {
- const auto field = container->add(
- object_ptr<Ui::InputField>(
- container,
- st::settingsChatIntroField,
- std::move(placeholder),
- current),
- st::settingsChatIntroFieldMargins);
- field->setMaxLength(limit);
- Ui::AddLengthLimitLabel(field, limit);
- return field;
- }
- rpl::producer<std::shared_ptr<StickerPlayer>> IconPlayerValue(
- not_null<DocumentData*> sticker,
- Fn<void()> update) {
- const auto media = sticker->createMediaView();
- media->checkStickerLarge();
- media->goodThumbnailWanted();
- return rpl::single() | rpl::then(
- sticker->owner().session().downloaderTaskFinished()
- ) | rpl::filter([=] {
- return media->loaded();
- }) | rpl::take(1) | rpl::map([=] {
- auto result = std::shared_ptr<StickerPlayer>();
- const auto info = sticker->sticker();
- const auto box = QSize(st::emojiSize, st::emojiSize);
- if (info->isLottie()) {
- result = std::make_shared<LottiePlayer>(
- ChatHelpers::LottiePlayerFromDocument(
- media.get(),
- ChatHelpers::StickerLottieSize::StickerEmojiSize,
- box,
- Lottie::Quality::High));
- } else if (info->isWebm()) {
- result = std::make_shared<WebmPlayer>(
- media->owner()->location(),
- media->bytes(),
- box);
- } else {
- result = std::make_shared<StaticStickerPlayer>(
- media->owner()->location(),
- media->bytes(),
- box);
- }
- result->setRepaintCallback(update);
- return result;
- });
- }
- [[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;
- DocumentData *sticker = nullptr;
- std::shared_ptr<StickerPlayer> player;
- rpl::lifetime playerLifetime;
- };
- const auto state = right->lifetime().make_state<State>();
- state->panel.someCustomChosen(
- ) | rpl::start_with_next([=](StickerPanel::CustomChosen chosen) {
- stickerChosen(chosen.sticker);
- }, raw->lifetime());
- std::move(
- stickerValue
- ) | rpl::start_with_next([=](DocumentData *sticker) {
- state->sticker = sticker;
- if (sticker) {
- right->resize(button.emojiWidth + button.added, right->height());
- IconPlayerValue(
- sticker,
- [=] { right->update(); }
- ) | rpl::start_with_next([=](
- std::shared_ptr<StickerPlayer> player) {
- state->player = std::move(player);
- right->update();
- }, state->playerLifetime);
- } else {
- state->playerLifetime.destroy();
- state->player = nullptr;
- right->resize(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 (state->player) {
- if (state->player->ready()) {
- const auto frame = state->player->frame(
- QSize(st::emojiSize, st::emojiSize),
- QColor(0, 0, 0, 0),
- false,
- crl::now(),
- !right->window()->isActiveWindow()).image;
- const auto target = DownscaledSize(
- frame.size(),
- QSize(st::emojiSize, st::emojiSize));
- p.drawImage(
- QRect(
- button.added + (st::emojiSize - target.width()) / 2,
- (height - target.height()) / 2,
- target.width(),
- target.height()),
- frame);
- state->player->markFrameShown();
- }
- } 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([=] {
- if (const auto controller = show->resolveWindow()) {
- state->panel.show({
- .controller = controller,
- .button = right,
- });
- }
- });
- 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());
- session->downloaderTaskFinished() | rpl::start_with_next([=] {
- 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), true);
- 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) {
- 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 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();
- }
- 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::ChatIntro,
- .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) {
- auto random = rpl::single(
- Api::RandomHelloStickerValue(session)
- ) | rpl::then(rpl::duplicate(
- intro
- ) | rpl::map([=](const Data::ChatIntro &intro) {
- return intro.sticker;
- }) | rpl::distinct_until_changed(
- ) | rpl::filter([](DocumentData *sticker) {
- return !sticker;
- }) | rpl::map([=] {
- return Api::RandomHelloStickerValue(session);
- })) | rpl::flatten_latest();
- return rpl::combine(
- std::move(intro),
- std::move(random)
- ) | rpl::map([=](Data::ChatIntro intro, DocumentData *hello) {
- if (!intro.sticker) {
- intro.sticker = hello;
- }
- return intro;
- });
- }
- void ChatIntro::setupContent(
- not_null<Window::SessionController*> controller) {
- using namespace rpl::mappers;
- const auto content = Ui::CreateChild<Ui::VerticalLayout>(this);
- const auto session = &controller->session();
- _intro = controller->session().user()->businessDetails().intro;
- const auto change = [=](Fn<void(Data::ChatIntro &)> modify) {
- auto intro = _intro.current();
- modify(intro);
- _intro = intro;
- };
- content->add(
- object_ptr<PreviewWrap>(
- content,
- session,
- IntroWithRandomSticker(session, _intro.value())),
- {});
- const auto title = AddPartInput(
- content,
- tr::lng_chat_intro_enter_title(),
- _intro.current().title,
- PartLimit(session, u"intro_title_length_limit"_q, 32));
- const auto description = AddPartInput(
- content,
- tr::lng_chat_intro_enter_message(),
- _intro.current().description,
- PartLimit(session, u"intro_description_length_limit"_q, 70));
- 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();
- title->clear();
- description->clear();
- title->setFocus();
- });
- Ui::ResizeFitChild(this, content);
- }
- void ChatIntro::save() {
- const auto fail = [=](QString error) {
- };
- controller()->session().data().businessInfo().saveChatIntro(
- _intro.current(),
- fail);
- }
- } // namespace
- Type ChatIntroId() {
- return ChatIntro::Id();
- }
- } // namespace Settings
|