719 linhas
20 KiB
C++
719 linhas
20 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 "boxes/create_ai_tone_box.h"
|
|
|
|
#include "chat_helpers/compose/compose_show.h"
|
|
#include "chat_helpers/emoji_list_widget.h"
|
|
#include "chat_helpers/stickers_lottie.h"
|
|
#include "data/data_ai_compose_tones.h"
|
|
#include "data/data_document.h"
|
|
#include "data/data_document_media.h"
|
|
#include "data/data_forum_icons.h"
|
|
#include "data/data_premium_limits.h"
|
|
#include "data/data_session.h"
|
|
#include "data/stickers/data_custom_emoji.h"
|
|
#include "history/view/media/history_view_sticker_player.h"
|
|
#include "lang/lang_keys.h"
|
|
#include "main/session/session_show.h"
|
|
#include "main/main_app_config.h"
|
|
#include "main/main_session.h"
|
|
#include "settings/sections/settings_premium.h"
|
|
#include "ui/abstract_button.h"
|
|
#include "ui/boxes/confirm_box.h"
|
|
#include "ui/controls/custom_emoji_toast_icon.h"
|
|
#include "ui/controls/warning_tooltip.h"
|
|
#include "ui/effects/animations.h"
|
|
#include "ui/layers/generic_box.h"
|
|
#include "ui/layers/show.h"
|
|
#include "ui/painter.h"
|
|
#include "ui/text/text_utilities.h"
|
|
#include "ui/toast/toast.h"
|
|
#include "ui/vertical_list.h"
|
|
#include "ui/widgets/buttons.h"
|
|
#include "ui/widgets/checkbox.h"
|
|
#include "ui/widgets/fields/input_field.h"
|
|
#include "ui/widgets/labels.h"
|
|
#include "ui/widgets/shadow.h"
|
|
#include "ui/wrap/padding_wrap.h"
|
|
#include "ui/wrap/vertical_layout.h"
|
|
#include "window/window_session_controller.h"
|
|
|
|
#include "styles/style_boxes.h"
|
|
#include "styles/style_chat_helpers.h"
|
|
#include "styles/style_dialogs.h"
|
|
#include "styles/style_layers.h"
|
|
|
|
namespace {
|
|
|
|
constexpr auto kAiComposeToneToastDuration = crl::time(4000);
|
|
|
|
void ShowToneToast(
|
|
std::shared_ptr<Ui::Show> show,
|
|
not_null<Main::Session*> session,
|
|
const Data::AiComposeTone &tone,
|
|
bool created) {
|
|
const auto size = QSize(
|
|
st::aiComposeToneToastIconSize.width(),
|
|
st::aiComposeToneToastIconSize.height());
|
|
show->showToast(Ui::Toast::Config{
|
|
.title = (created
|
|
? tr::lng_ai_compose_tone_created
|
|
: tr::lng_ai_compose_tone_updated)(
|
|
tr::now,
|
|
lt_title,
|
|
tone.title),
|
|
.text = tr::lng_ai_compose_tone_created_description(
|
|
tr::now,
|
|
Ui::Text::WithEntities),
|
|
.iconContent = Ui::MakeCustomEmojiToastIcon(
|
|
session,
|
|
tone.emojiId,
|
|
size),
|
|
.iconPadding = st::aiComposeToneToastIconPadding,
|
|
.duration = kAiComposeToneToastDuration,
|
|
});
|
|
}
|
|
|
|
void ChooseToneIconBox(
|
|
not_null<Ui::GenericBox*> box,
|
|
not_null<Window::SessionController*> controller,
|
|
Fn<void(DocumentId)> chosen) {
|
|
using namespace ChatHelpers;
|
|
|
|
box->setTitle(tr::lng_ai_compose_tone_icon_title());
|
|
box->setWidth(st::boxWideWidth);
|
|
box->setMaxHeight(st::editTopicMaxHeight);
|
|
box->setScrollStyle(st::reactPanelScroll);
|
|
|
|
const auto manager = &controller->session().data().customEmojiManager();
|
|
const auto icons = &controller->session().data().forumIcons();
|
|
|
|
auto factory = [=](DocumentId id, Fn<void()> repaint)
|
|
-> std::unique_ptr<Ui::Text::CustomEmoji> {
|
|
return manager->create(
|
|
id,
|
|
std::move(repaint),
|
|
Data::CustomEmojiManager::SizeTag::Large);
|
|
};
|
|
|
|
const auto top = box->setPinnedToTopContent(
|
|
object_ptr<Ui::VerticalLayout>(box));
|
|
|
|
const auto body = box->verticalLayout();
|
|
const auto selector = body->add(
|
|
object_ptr<EmojiListWidget>(body, EmojiListDescriptor{
|
|
.show = controller->uiShow(),
|
|
.mode = EmojiListWidget::Mode::TopicIcon,
|
|
.paused = Window::PausedIn(
|
|
controller,
|
|
Window::GifPauseReason::Layer),
|
|
.customRecentList = DocumentListToRecent(icons->list()),
|
|
.customRecentFactory = std::move(factory),
|
|
.st = &st::reactPanelEmojiPan,
|
|
}),
|
|
st::reactPanelEmojiPan.padding);
|
|
|
|
icons->requestDefaultIfUnknown();
|
|
icons->defaultUpdates(
|
|
) | rpl::on_next([=] {
|
|
selector->provideRecent(DocumentListToRecent(icons->list()));
|
|
}, selector->lifetime());
|
|
|
|
top->add(selector->createFooter());
|
|
|
|
const auto shadow = Ui::CreateChild<Ui::PlainShadow>(box.get());
|
|
shadow->show();
|
|
|
|
rpl::combine(
|
|
top->heightValue(),
|
|
selector->widthValue()
|
|
) | rpl::on_next([=](int topHeight, int width) {
|
|
shadow->setGeometry(0, topHeight, width, st::lineWidth);
|
|
}, shadow->lifetime());
|
|
|
|
selector->refreshEmoji();
|
|
|
|
selector->scrollToRequests(
|
|
) | rpl::on_next([=](int y) {
|
|
box->scrollToY(y);
|
|
shadow->update();
|
|
}, selector->lifetime());
|
|
|
|
rpl::combine(
|
|
box->heightValue(),
|
|
top->heightValue(),
|
|
rpl::mappers::_1 - rpl::mappers::_2
|
|
) | rpl::on_next([=](int height) {
|
|
selector->setMinimalHeight(selector->width(), height);
|
|
}, body->lifetime());
|
|
|
|
selector->customChosen(
|
|
) | rpl::on_next([=](ChatHelpers::FileChosen data) {
|
|
chosen(data.document->id);
|
|
box->closeBox();
|
|
}, selector->lifetime());
|
|
|
|
box->addButton(tr::lng_cancel(), [=] { box->closeBox(); });
|
|
}
|
|
|
|
} // namespace
|
|
|
|
not_null<Ui::AbstractButton*> AddAiToneIconPreview(
|
|
not_null<Ui::VerticalLayout*> container,
|
|
not_null<Main::Session*> session,
|
|
rpl::producer<DocumentId> emojiIdValue,
|
|
Fn<void(DocumentId)> emojiIdChosen) {
|
|
using StickerPlayer = HistoryView::StickerPlayer;
|
|
struct State {
|
|
DocumentId emojiId = 0;
|
|
std::shared_ptr<StickerPlayer> player;
|
|
bool playerUsesTextColor = false;
|
|
};
|
|
|
|
const auto outer = st::aiToneIconPreviewSize;
|
|
const auto inner = st::aiToneIconPreviewInnerSize;
|
|
const auto top = st::aiToneIconPreviewTopSkip;
|
|
const auto bottom = st::aiToneIconPreviewBottomSkip;
|
|
const auto holder = container->add(
|
|
object_ptr<Ui::FixedHeightWidget>(
|
|
container,
|
|
outer + top + bottom));
|
|
const auto button = Ui::CreateChild<Ui::AbstractButton>(holder);
|
|
button->resize(outer, outer);
|
|
button->show();
|
|
|
|
holder->widthValue(
|
|
) | rpl::on_next([=](int width) {
|
|
button->move((width - outer) / 2, top);
|
|
}, button->lifetime());
|
|
|
|
const auto state = button->lifetime().make_state<State>();
|
|
const auto emojiIdVar = button->lifetime().make_state<
|
|
rpl::variable<DocumentId>>(std::move(emojiIdValue));
|
|
|
|
emojiIdVar->value(
|
|
) | rpl::on_next([=](DocumentId id) {
|
|
state->emojiId = id;
|
|
}, button->lifetime());
|
|
|
|
emojiIdVar->value(
|
|
) | rpl::map([=](DocumentId id) -> rpl::producer<DocumentData*> {
|
|
if (!id) {
|
|
return rpl::single((DocumentData*)nullptr);
|
|
}
|
|
return session->data().customEmojiManager().resolve(
|
|
id
|
|
) | rpl::map([=](not_null<DocumentData*> document) {
|
|
return document.get();
|
|
}) | rpl::map_error_to_done();
|
|
}) | rpl::flatten_latest(
|
|
) | rpl::map([=](DocumentData *document)
|
|
-> rpl::producer<std::shared_ptr<StickerPlayer>> {
|
|
if (!document) {
|
|
return rpl::single(std::shared_ptr<StickerPlayer>());
|
|
}
|
|
const auto media = document->createMediaView();
|
|
media->checkStickerLarge();
|
|
media->goodThumbnailWanted();
|
|
|
|
return rpl::single() | rpl::then(
|
|
document->session().downloaderTaskFinished()
|
|
) | rpl::filter([=] {
|
|
return media->loaded();
|
|
}) | rpl::take(1) | rpl::map([=] {
|
|
auto result = std::shared_ptr<StickerPlayer>();
|
|
const auto sticker = document->sticker();
|
|
const auto size = QSize(inner, inner);
|
|
if (sticker && sticker->isLottie()) {
|
|
result = std::make_shared<HistoryView::LottiePlayer>(
|
|
ChatHelpers::LottiePlayerFromDocument(
|
|
media.get(),
|
|
ChatHelpers::StickerLottieSize::StickerSet,
|
|
size,
|
|
Lottie::Quality::High));
|
|
} else if (sticker && sticker->isWebm()) {
|
|
result = std::make_shared<HistoryView::WebmPlayer>(
|
|
media->owner()->location(),
|
|
media->bytes(),
|
|
size);
|
|
} else {
|
|
result = std::make_shared<HistoryView::StaticStickerPlayer>(
|
|
media->owner()->location(),
|
|
media->bytes(),
|
|
size);
|
|
}
|
|
result->setRepaintCallback([=] { button->update(); });
|
|
state->playerUsesTextColor
|
|
= media->owner()->emojiUsesTextColor();
|
|
return result;
|
|
});
|
|
}) | rpl::flatten_latest(
|
|
) | rpl::on_next([=](std::shared_ptr<StickerPlayer> player) {
|
|
state->player = std::move(player);
|
|
button->update();
|
|
}, button->lifetime());
|
|
|
|
button->paintRequest(
|
|
) | rpl::on_next([=] {
|
|
auto p = QPainter(button);
|
|
auto hq = PainterHighQualityEnabler(p);
|
|
p.setPen(Qt::NoPen);
|
|
p.setBrush(st::aiToneIconPreviewBg);
|
|
p.drawEllipse(button->rect());
|
|
if (state->player && state->player->ready()) {
|
|
const auto color = state->playerUsesTextColor
|
|
? st::windowFg->c
|
|
: QColor(0, 0, 0, 0);
|
|
const auto frame = state->player->frame(
|
|
QSize(inner, inner),
|
|
color,
|
|
false,
|
|
crl::now(),
|
|
false).image;
|
|
const auto sz = frame.size() / style::DevicePixelRatio();
|
|
p.drawImage(
|
|
QRect(
|
|
(outer - sz.width()) / 2,
|
|
(outer - sz.height()) / 2,
|
|
sz.width(),
|
|
sz.height()),
|
|
frame);
|
|
state->player->markFrameShown();
|
|
} else if (!state->emojiId) {
|
|
st::aiToneIconPreviewPlaceholder.paintInCenter(
|
|
p,
|
|
button->rect());
|
|
}
|
|
}, button->lifetime());
|
|
|
|
if (emojiIdChosen) {
|
|
button->setClickedCallback([=] {
|
|
const auto controller = ChatHelpers::ResolveWindowDefault()(
|
|
session);
|
|
if (!controller) {
|
|
return;
|
|
}
|
|
controller->uiShow()->showBox(Box(
|
|
ChooseToneIconBox,
|
|
controller,
|
|
crl::guard(button, [=](DocumentId id) {
|
|
emojiIdChosen(id);
|
|
})));
|
|
});
|
|
} else {
|
|
button->setAttribute(Qt::WA_TransparentForMouseEvents);
|
|
}
|
|
|
|
return button;
|
|
}
|
|
|
|
namespace {
|
|
|
|
void SetupToneBox(
|
|
not_null<Ui::GenericBox*> box,
|
|
not_null<Main::Session*> session,
|
|
DocumentId initialEmojiId,
|
|
const QString &initialName,
|
|
const QString &initialPrompt,
|
|
bool initialDisplayAuthor,
|
|
rpl::producer<QString> title,
|
|
rpl::producer<QString> submitLabel,
|
|
Fn<void(DocumentId, QString, QString, bool)> submit,
|
|
Fn<void()> requestDelete = nullptr) {
|
|
box->setStyle(st::aiComposeBox);
|
|
box->setNoContentMargin(true);
|
|
box->setWidth(st::boxWideWidth);
|
|
box->addTopButton(st::aiComposeBoxClose, [=] { box->closeBox(); });
|
|
box->setTitle(std::move(title));
|
|
|
|
const auto container = box->verticalLayout();
|
|
const auto emojiId = container->lifetime().make_state<
|
|
rpl::variable<DocumentId>>(initialEmojiId);
|
|
|
|
const auto iconButton = AddAiToneIconPreview(
|
|
container,
|
|
session,
|
|
emojiId->value(),
|
|
[=](DocumentId id) { *emojiId = id; });
|
|
|
|
const auto name = box->addRow(
|
|
object_ptr<Ui::InputField>(
|
|
box,
|
|
st::aiToneNameField,
|
|
Ui::InputField::Mode::SingleLine,
|
|
rpl::producer<QString>(),
|
|
initialName),
|
|
st::aiToneFieldsMargin);
|
|
name->setMaxLength(session->appConfig().get<int>(
|
|
u"aicompose_tone_title_length_max"_q,
|
|
12));
|
|
|
|
Ui::AddSkip(container, st::aiToneFieldsSkip);
|
|
|
|
const auto promptSt = box->lifetime().make_state<style::InputField>(
|
|
st::aiTonePromptField);
|
|
{
|
|
const auto &placeholderStyle = st::aiTonePlaceholderLabel.style;
|
|
const auto fieldsMargin = st::aiToneFieldsMargin;
|
|
const auto contentWidth = st::boxWideWidth
|
|
- fieldsMargin.left() - fieldsMargin.right()
|
|
- promptSt->textMargins.left() - promptSt->textMargins.right();
|
|
auto measure = Ui::Text::String{ contentWidth / 2 };
|
|
measure.setText(
|
|
placeholderStyle,
|
|
tr::lng_ai_compose_tone_prompt_placeholder(tr::now));
|
|
const auto desiredMin = measure.countHeight(contentWidth)
|
|
+ promptSt->textMargins.top()
|
|
+ promptSt->textMargins.bottom();
|
|
if (promptSt->heightMin < desiredMin) {
|
|
promptSt->heightMin = desiredMin;
|
|
}
|
|
if (promptSt->heightMax < promptSt->heightMin) {
|
|
promptSt->heightMax = promptSt->heightMin;
|
|
}
|
|
}
|
|
|
|
const auto prompt = box->addRow(
|
|
object_ptr<Ui::InputField>(
|
|
box,
|
|
*promptSt,
|
|
Ui::InputField::Mode::MultiLine,
|
|
rpl::producer<QString>(),
|
|
initialPrompt),
|
|
st::aiToneFieldsMargin);
|
|
prompt->setSubmitSettings(Ui::InputField::SubmitSettings::None);
|
|
prompt->setMaxLength(session->appConfig().get<int>(
|
|
u"aicompose_tone_prompt_length_max"_q,
|
|
1024));
|
|
|
|
struct FieldDecor {
|
|
not_null<Ui::RpWidget*> bg;
|
|
not_null<Ui::FlatLabel*> placeholder;
|
|
Ui::Animations::Simple anim;
|
|
bool hidden = false;
|
|
};
|
|
const auto makeDecor = [=](
|
|
not_null<Ui::InputField*> field,
|
|
rpl::producer<QString> placeholderText) {
|
|
const auto parent = field->parentWidget();
|
|
const auto decor = field->lifetime().make_state<FieldDecor>(FieldDecor{
|
|
.bg = Ui::CreateChild<Ui::RpWidget>(parent),
|
|
.placeholder = Ui::CreateChild<Ui::FlatLabel>(
|
|
parent,
|
|
std::move(placeholderText),
|
|
st::aiTonePlaceholderLabel),
|
|
});
|
|
decor->bg->setAttribute(Qt::WA_TransparentForMouseEvents);
|
|
decor->placeholder->setAttribute(Qt::WA_TransparentForMouseEvents);
|
|
decor->bg->paintRequest(
|
|
) | rpl::on_next([bg = decor->bg] {
|
|
auto p = QPainter(bg);
|
|
auto hq = PainterHighQualityEnabler(p);
|
|
p.setPen(Qt::NoPen);
|
|
p.setBrush(st::aiToneFieldBg);
|
|
const auto r = st::aiToneFieldRadius;
|
|
p.drawRoundedRect(bg->rect(), r, r);
|
|
}, decor->bg->lifetime());
|
|
decor->bg->lower();
|
|
decor->placeholder->raise();
|
|
|
|
const auto applyPosition = [=] {
|
|
const auto pad = st::aiToneFieldPadding;
|
|
const auto progress = decor->anim.value(decor->hidden ? 1. : 0.);
|
|
const auto shift = int(base::SafeRound(
|
|
progress * (-st::defaultInputField.placeholderShift)));
|
|
decor->placeholder->moveToLeft(
|
|
field->x() + pad.left() + shift,
|
|
field->y() + pad.top());
|
|
decor->placeholder->setOpacity(1. - progress);
|
|
};
|
|
field->geometryValue(
|
|
) | rpl::on_next([=](QRect g) {
|
|
if (g.isEmpty()) {
|
|
return;
|
|
}
|
|
const auto pad = st::aiToneFieldPadding;
|
|
decor->bg->setGeometry(g);
|
|
decor->placeholder->resizeToWidth(
|
|
g.width() - pad.left() - pad.right());
|
|
applyPosition();
|
|
}, field->lifetime());
|
|
|
|
const auto animate = [=](bool hidden) {
|
|
if (decor->hidden == hidden) {
|
|
return;
|
|
}
|
|
decor->hidden = hidden;
|
|
decor->anim.start(
|
|
applyPosition,
|
|
hidden ? 0. : 1.,
|
|
hidden ? 1. : 0.,
|
|
st::defaultInputField.duration);
|
|
};
|
|
field->changes(
|
|
) | rpl::on_next([=] {
|
|
animate(!field->getLastText().isEmpty());
|
|
}, field->lifetime());
|
|
decor->hidden = !field->getLastText().isEmpty();
|
|
applyPosition();
|
|
return decor;
|
|
};
|
|
makeDecor(name, tr::lng_ai_compose_tone_name_placeholder());
|
|
const auto promptDecor = makeDecor(
|
|
prompt,
|
|
tr::lng_ai_compose_tone_prompt_placeholder());
|
|
|
|
const auto authorCheckbox = box->addRow(
|
|
object_ptr<Ui::Checkbox>(
|
|
box,
|
|
tr::lng_ai_compose_tone_author(tr::now),
|
|
st::aiComposeEmojifyCheckbox,
|
|
std::make_unique<Ui::RoundCheckView>(
|
|
st::defaultCheck,
|
|
initialDisplayAuthor)),
|
|
st::aiToneAuthorCheckboxMargin,
|
|
style::al_top);
|
|
|
|
const auto deleteButton = requestDelete
|
|
? box->addRow(
|
|
object_ptr<Ui::RoundButton>(
|
|
box,
|
|
tr::lng_ai_compose_tone_delete(),
|
|
st::aiToneDeleteButton),
|
|
st::aiToneDeleteButtonMargin)
|
|
: nullptr;
|
|
if (deleteButton) {
|
|
deleteButton->setFullRadius(true);
|
|
deleteButton->setClickedCallback(std::move(requestDelete));
|
|
box->widthValue(
|
|
) | rpl::on_next([=](int width) {
|
|
const auto &margin = st::aiToneDeleteButtonMargin;
|
|
deleteButton->setFullWidth(
|
|
width - margin.left() - margin.right());
|
|
}, deleteButton->lifetime());
|
|
}
|
|
|
|
rpl::combine(
|
|
prompt->topValue(),
|
|
promptDecor->placeholder->heightValue(),
|
|
box->getDelegate()->contentHeightMaxValue()
|
|
) | rpl::on_next([=](int top, int phHeight, int contentHeight) {
|
|
const auto pad = st::aiToneFieldPadding;
|
|
const auto deleteBlock = deleteButton
|
|
? (deleteButton->heightNoMargins()
|
|
+ st::aiToneDeleteButtonMargin.top()
|
|
+ st::aiToneDeleteButtonMargin.bottom())
|
|
: 0;
|
|
prompt->setMaxHeight(contentHeight
|
|
- top
|
|
- st::aiToneFieldsMargin.bottom()
|
|
- authorCheckbox->heightNoMargins()
|
|
- st::aiToneAuthorCheckboxMargin.top()
|
|
- st::aiToneAuthorCheckboxMargin.bottom()
|
|
- deleteBlock);
|
|
prompt->setMinHeight(phHeight + pad.top() + pad.bottom());
|
|
}, prompt->lifetime());
|
|
|
|
box->setFocusCallback([=] {
|
|
name->setFocusFast();
|
|
});
|
|
|
|
const auto warning = box->lifetime().make_state<Ui::WarningTooltip>();
|
|
const auto save = [=] {
|
|
const auto nameText = name->getLastText().trimmed();
|
|
const auto promptText = prompt->getLastText().trimmed();
|
|
const auto showWarning = [=](
|
|
not_null<QWidget*> target,
|
|
rpl::producer<TextWithEntities> text) {
|
|
warning->show({
|
|
.parent = box,
|
|
.target = target,
|
|
.text = std::move(text),
|
|
});
|
|
};
|
|
if (!emojiId->current()) {
|
|
showWarning(
|
|
iconButton,
|
|
tr::lng_ai_compose_tone_warn_icon(tr::marked));
|
|
return;
|
|
} else if (nameText.isEmpty()) {
|
|
name->showError();
|
|
showWarning(
|
|
name,
|
|
tr::lng_ai_compose_tone_warn_name(tr::marked));
|
|
return;
|
|
} else if (promptText.isEmpty()) {
|
|
prompt->showError();
|
|
showWarning(
|
|
prompt,
|
|
tr::lng_ai_compose_tone_warn_prompt(tr::marked));
|
|
return;
|
|
}
|
|
warning->hide(anim::type::normal);
|
|
submit(
|
|
emojiId->current(),
|
|
nameText,
|
|
promptText,
|
|
authorCheckbox->checked());
|
|
};
|
|
|
|
const auto submitBtn = box->addButton(std::move(submitLabel), save);
|
|
submitBtn->setFullRadius(true);
|
|
}
|
|
|
|
} // namespace
|
|
|
|
void CreateAiToneBox(
|
|
not_null<Ui::GenericBox*> box,
|
|
not_null<Main::Session*> session,
|
|
Fn<void(Data::AiComposeTone)> saved) {
|
|
SetupToneBox(
|
|
box,
|
|
session,
|
|
DocumentId(0),
|
|
QString(),
|
|
QString(),
|
|
false,
|
|
tr::lng_ai_compose_create_tone_title(),
|
|
tr::lng_ai_compose_tone_create(),
|
|
[=](DocumentId emojiId,
|
|
const QString &name,
|
|
const QString &prompt,
|
|
bool displayAuthor) {
|
|
session->data().aiComposeTones().create(
|
|
name,
|
|
prompt,
|
|
emojiId,
|
|
displayAuthor,
|
|
crl::guard(box, [=](Data::AiComposeTone tone) {
|
|
const auto show = box->uiShow();
|
|
box->closeBox();
|
|
ShowToneToast(show, session, tone, true);
|
|
if (saved) {
|
|
saved(tone);
|
|
}
|
|
}),
|
|
crl::guard(box, [=](const MTP::Error &error) {
|
|
if (error.type() == u"TONES_SAVED_TOO_MANY"_q) {
|
|
ShowAiComposeToneLimitError(box->uiShow(), session);
|
|
} else if (!MTP::IgnoreError(error)) {
|
|
box->showToast(error.type());
|
|
}
|
|
}));
|
|
},
|
|
nullptr);
|
|
}
|
|
|
|
void EditAiToneBox(
|
|
not_null<Ui::GenericBox*> box,
|
|
not_null<Main::Session*> session,
|
|
const Data::AiComposeTone &tone,
|
|
Fn<void(Data::AiComposeTone)> saved) {
|
|
const auto toneCopy = tone;
|
|
SetupToneBox(
|
|
box,
|
|
session,
|
|
tone.emojiId,
|
|
tone.title,
|
|
tone.prompt,
|
|
tone.authorId != 0,
|
|
tr::lng_ai_compose_edit_tone_title(),
|
|
tr::lng_ai_compose_tone_save(),
|
|
[=](DocumentId emojiId,
|
|
const QString &name,
|
|
const QString &prompt,
|
|
bool displayAuthor) {
|
|
session->data().aiComposeTones().update(
|
|
toneCopy,
|
|
name,
|
|
prompt,
|
|
std::make_optional(emojiId),
|
|
std::make_optional(displayAuthor),
|
|
crl::guard(box, [=](Data::AiComposeTone updated) {
|
|
const auto show = box->uiShow();
|
|
box->closeBox();
|
|
ShowToneToast(show, session, updated, false);
|
|
if (saved) {
|
|
saved(updated);
|
|
}
|
|
}));
|
|
},
|
|
[=] {
|
|
ConfirmDeleteAiTone(
|
|
box->uiShow(),
|
|
session,
|
|
toneCopy,
|
|
[=] { box->closeBox(); });
|
|
});
|
|
}
|
|
|
|
void ConfirmDeleteAiTone(
|
|
std::shared_ptr<Ui::Show> show,
|
|
not_null<Main::Session*> session,
|
|
const Data::AiComposeTone &tone,
|
|
Fn<void()> done) {
|
|
if (!tone.creator) {
|
|
show->show(Ui::MakeConfirmBox({
|
|
.text = tr::lng_ai_compose_tone_remove_sure(),
|
|
.confirmed = [=](Fn<void()> &&close) {
|
|
close();
|
|
session->data().aiComposeTones().save(
|
|
tone,
|
|
true,
|
|
done);
|
|
},
|
|
.confirmText = tr::lng_box_remove(),
|
|
}));
|
|
return;
|
|
}
|
|
show->show(Ui::MakeConfirmBox({
|
|
.text = tr::lng_ai_compose_tone_delete_sure(),
|
|
.confirmed = [=](Fn<void()> &&close) {
|
|
close();
|
|
session->data().aiComposeTones().remove(tone, done);
|
|
},
|
|
.confirmText = tr::lng_box_delete(),
|
|
.confirmStyle = &st::attentionBoxButton,
|
|
.title = tr::lng_ai_compose_tone_delete(),
|
|
}));
|
|
}
|
|
|
|
void ShowAiComposeToneLimitError(
|
|
std::shared_ptr<Ui::Show> show,
|
|
not_null<Main::Session*> session) {
|
|
const auto limits = Data::PremiumLimits(session);
|
|
const auto premium = session->premium();
|
|
const auto premiumPossible = session->premiumPossible();
|
|
const auto defaultLimit = limits.aiComposeSavedTonesDefault();
|
|
const auto premiumLimit = limits.aiComposeSavedTonesPremium();
|
|
const auto current = premium ? premiumLimit : defaultLimit;
|
|
if (premium || !premiumPossible) {
|
|
show->showToast(tr::lng_ai_compose_tone_saved_limit_final(
|
|
tr::now,
|
|
lt_count,
|
|
current,
|
|
tr::rich));
|
|
} else {
|
|
Settings::ShowPremiumPromoToast(
|
|
Main::MakeSessionShow(show, session),
|
|
ChatHelpers::ResolveWindowDefault(),
|
|
tr::lng_ai_compose_tone_saved_limit(
|
|
tr::now,
|
|
lt_count,
|
|
defaultLimit,
|
|
lt_link,
|
|
tr::bold(tr::lng_ai_compose_tone_saved_limit_link(
|
|
tr::now,
|
|
tr::link)),
|
|
lt_premium_count,
|
|
tr::bold(QString::number(premiumLimit)),
|
|
tr::rich),
|
|
u"ai_compose_tones"_q);
|
|
}
|
|
}
|