mirror of
https://github.com/kotatogram/kotatogram-desktop
synced 2025-09-05 00:55:12 +00:00
565 lines
14 KiB
C++
565 lines
14 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/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
|