651 lines
		
	
	
	
		
			17 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			651 lines
		
	
	
	
		
			17 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
/*
 | 
						|
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_context_menu.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);
 | 
						|
	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([=] {
 | 
						|
		const auto controller = show->resolveWindow(
 | 
						|
			ChatHelpers::WindowUsage::PremiumPromo);
 | 
						|
		if (controller) {
 | 
						|
			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 show = controller()->uiShow();
 | 
						|
	const auto fail = [=](QString error) {
 | 
						|
	};
 | 
						|
	controller()->session().data().businessInfo().saveChatIntro(
 | 
						|
		_intro.current(),
 | 
						|
		fail);
 | 
						|
}
 | 
						|
 | 
						|
} // namespace
 | 
						|
 | 
						|
Type ChatIntroId() {
 | 
						|
	return ChatIntro::Id();
 | 
						|
}
 | 
						|
 | 
						|
} // namespace Settings
 |