Files
AyuGramDesktop/Telegram/SourceFiles/ayu/ui/boxes/message_shot_box.cpp
T

445 lines
12 KiB
C++

// This is the source code of AyuGram for Desktop.
//
// We do not and cannot prevent the use of our code,
// but be respectful and credit the original author.
//
// Copyright @Radolyn, 2026
#include "ayu/ui/boxes/message_shot_box.h"
#include "lang_auto.h"
#include "ayu/ayu_settings.h"
#include "ayu/ui/boxes/theme_selector_box.h"
#include "ayu/ui/components/image_view.h"
#include "ayu/utils/telegram_helpers.h"
#include "boxes/abstract_box.h"
#include "data/data_chat.h"
#include "data/data_channel.h"
#include "data/data_todo_list.h"
#include "data/data_user.h"
#include "history/history.h"
#include "history/history_item_components.h"
#include "history/history_item.h"
#include "main/main_session.h"
#include "settings/settings_common.h"
#include "styles/style_ayu_styles.h"
#include "styles/style_layers.h"
#include "styles/style_settings.h"
#include "ui/vertical_list.h"
#include "ui/widgets/buttons.h"
#include "ui/wrap/vertical_layout.h"
#include <memory>
#include <QFileDialog>
#include <QGuiApplication>
MessageShotBox::MessageShotBox(
QWidget *parent,
AyuFeatures::MessageShot::ShotConfig config)
: _config(std::move(config)) {
}
void MessageShotBox::prepare() {
setupContent();
}
void MessageShotBox::setupContent() {
_selectedPalette = AyuFeatures::MessageShot::getPersistedPalette();
if (!_selectedPalette) {
_selectedPalette = std::make_shared<style::palette>();
}
AyuFeatures::MessageShot::setPersistedPalette(_selectedPalette);
AyuFeatures::MessageShot::ensureChatThemesRefreshed();
auto &settings = AyuSettings::getInstance();
auto &shotSettings = settings.messageShotSettings();
const auto savedSimpleQuotesAndReplies = settings.simpleQuotesAndReplies();
settings.setSimpleQuotesAndReplies(!shotSettings.showColorfulReplies());
using namespace Settings;
auto savedThemeApplyResult = AyuFeatures::MessageShot::SavedThemeApplyResult::Failed;
const auto hasSavedTheme = shotSettings.embeddedThemeType() != -1
|| shotSettings.cloudThemeId() != 0;
if (hasSavedTheme) {
savedThemeApplyResult = AyuFeatures::MessageShot::applySavedThemePalette(
_selectedPalette,
nullptr);
if (savedThemeApplyResult != AyuFeatures::MessageShot::SavedThemeApplyResult::Failed) {
_config.st = std::make_shared<Ui::ChatStyle>(_selectedPalette.get());
} else {
shotSettings.clearTheme();
_config.st = std::make_shared<Ui::ChatStyle>(_config.controller->chatStyle());
}
}
AyuFeatures::MessageShot::setShotConfig(_config);
setTitle(rpl::single(tr::ayu_MessageShotTopBarText(tr::now)));
auto wrap = object_ptr<Ui::VerticalLayout>(this);
const auto content = wrap.data();
setInnerWidget(object_ptr<Ui::OverrideMargins>(this, std::move(wrap)));
AddSubsectionTitle(content, tr::ayu_MessageShotPreview());
const auto imageView = content->add(object_ptr<ImageView>(content), st::imageViewPadding);
AddSkip(content);
AddDivider(content);
AddSkip(content);
AddSubsectionTitle(content, tr::ayu_MessageShotPreferences());
auto hasReactions = false;
auto hasReplies = false;
auto hasHeaderDecorations = false;
auto hasSpoilers = false;
for (const auto &item : _config.messages) {
if (!hasReactions && !item->reactions().empty()) {
hasReactions = true;
}
if (!hasReplies) {
if (item->replyTo().replying()
|| (item->media() && item->media()->webpage())) {
hasReplies = true;
}
}
if (!hasSpoilers) {
for (const auto &entity : item->originalText().entities) {
if (entity.type() == EntityType::Spoiler) {
hasSpoilers = true;
break;
}
}
if (!hasSpoilers && item->media()) {
if (item->media()->hasSpoiler()) {
hasSpoilers = true;
} else if (const auto todoList = item->media()->todolist()) {
for (const auto &entity : todoList->title.entities) {
if (entity.type() == EntityType::Spoiler) {
hasSpoilers = true;
break;
}
}
if (!hasSpoilers) {
for (const auto &task : todoList->items) {
for (const auto &entity : task.text.entities) {
if (entity.type() == EntityType::Spoiler) {
hasSpoilers = true;
break;
}
}
if (hasSpoilers) break;
}
}
}
}
}
if (!hasHeaderDecorations) {
const auto drawChannelBadge = [&] {
if (item->isDiscussionPost()) {
return true;
} else if (item->author()->isMegagroup()) {
if (const auto signedInfo = item->Get<HistoryMessageSigned>()) {
if (!signedInfo->viaBusinessBot) {
return false;
}
}
}
return item->history()->peer->isMegagroup()
&& item->author()->isChannel()
&& !item->out();
}();
auto badgeText = QString();
if (item->isDiscussionPost()) {
badgeText = tr::lng_channel_badge(tr::now);
} else if (item->author()->isMegagroup()) {
if (const auto signedInfo = item->Get<HistoryMessageSigned>()) {
if (!signedInfo->viaBusinessBot) {
badgeText = signedInfo->author;
}
}
} else if (drawChannelBadge) {
badgeText = tr::lng_channel_badge(tr::now);
} else if (const auto chat = item->history()->peer->asChat()) {
if (const auto user = item->author()->asUser()) {
const auto rank = chat->memberRanks.find(peerToUser(user->id));
if (rank != chat->memberRanks.end()) {
badgeText = rank->second;
}
}
} else if (const auto channel = item->history()->peer->asMegagroup()) {
if (const auto user = item->author()->asUser()) {
const auto info = channel->mgInfo.get();
const auto userId = peerToUser(user->id);
const auto isCreator = info && (info->creator == user);
const auto isAdmin = info && info->admins.contains(userId);
if (isCreator || isAdmin) {
const auto rank = info->memberRanks.find(userId);
if (rank != info->memberRanks.end()
&& !rank->second.isEmpty()) {
badgeText = rank->second;
} else if (isCreator) {
badgeText = tr::lng_owner_badge(tr::now);
} else {
badgeText = tr::lng_admin_badge(tr::now);
}
} else {
badgeText = item->fromRank();
}
}
}
hasHeaderDecorations = drawChannelBadge
|| !badgeText.isEmpty()
|| (item->boostsApplied() > 0);
}
if (hasReactions
&& hasReplies
&& hasHeaderDecorations
&& hasSpoilers) {
break;
}
}
const auto firstPreviewLatch = std::make_shared<TimedCountDownLatch>(1);
const auto generation = content->lifetime().make_state<int>(0);
const auto weak = base::make_weak(this);
const auto updatePreview = [=]
{
const auto currentGeneration = ++(*generation);
AyuFeatures::MessageShot::Make(this, _config, [=](const QImage &image, bool final)
{
if (!weak || currentGeneration != *generation) {
return;
}
if (final || imageView->getImage().isNull()) {
imageView->setImage(image);
}
firstPreviewLatch->countDown();
});
};
if (savedThemeApplyResult == AyuFeatures::MessageShot::SavedThemeApplyResult::AwaitingAsync) {
const auto weakBox = base::make_weak(this);
AyuFeatures::MessageShot::subscribeToCloudThemeLoad(
_config.controller,
_selectedPalette,
[=] {
if (!weakBox) {
return;
}
_config.st = std::make_shared<Ui::ChatStyle>(_selectedPalette.get());
updatePreview();
});
}
auto selectedTheme =
content->lifetime().make_state<rpl::variable<QString>>(
AyuFeatures::MessageShot::resolveThemeName());
AddButtonWithLabel(
content,
tr::ayu_MessageShotTheme(),
selectedTheme->value(),
st::settingsButtonNoIcon
)->addClickHandler(
[=]
{
AyuFeatures::MessageShot::setChoosingTheme(true);
auto box = Box<ThemeSelectorBox>(_config.controller);
box->paletteSelected() | rpl::on_next(
[=](const style::palette &palette) mutable
{
_selectedPalette->reset();
_selectedPalette->load(palette.save());
_config.st = std::make_shared<Ui::ChatStyle>(_selectedPalette.get());
auto &shot = AyuSettings::getInstance().messageShotSettings();
const auto embedded = AyuFeatures::MessageShot::getSelectedFromDefault();
const auto cloud = AyuFeatures::MessageShot::getSelectedFromCustom();
if (cloud.has_value()) {
const auto accountId = _config.controller->session().userId().bare;
shot.setCloudTheme(accountId, cloud->id, cloud->accessHash, cloud->documentId, cloud->title);
} else if (embedded != Window::Theme::EmbeddedType(-1)) {
const auto color = AyuFeatures::MessageShot::getSelectedColorFromDefault();
shot.setEmbeddedTheme(static_cast<int>(embedded), color ? color->rgb() : 0);
} else {
shot.clearTheme();
}
updatePreview();
},
content->lifetime());
box->themeNameChanged() | rpl::on_next(
[=](const QString &name)
{
selectedTheme->force_assign(name);
},
content->lifetime());
box->boxClosing() | rpl::on_next(
[=]
{
AyuFeatures::MessageShot::setChoosingTheme(false);
},
content->lifetime());
Ui::show(std::move(box), Ui::LayerOption::KeepOther);
});
AddButtonWithIcon(
content,
tr::ayu_MessageShotShowBackground(),
st::settingsButtonNoIcon
)->toggleOn(rpl::single(shotSettings.showBackground())
)->toggledValue(
) | rpl::skip(1) | on_next(
[=](bool enabled)
{
AyuSettings::getInstance().messageShotSettings().setShowBackground(enabled);
updatePreview();
},
content->lifetime());
auto latestToggle = AddButtonWithIcon(
content,
tr::ayu_MessageShotShowDate(),
st::settingsButtonNoIcon
);
latestToggle->toggleOn(rpl::single(shotSettings.showDate())
)->toggledValue(
) | rpl::skip(1) | on_next(
[=](bool enabled)
{
AyuSettings::getInstance().messageShotSettings().setShowDate(enabled);
updatePreview();
},
content->lifetime());
if (hasReactions) {
latestToggle = AddButtonWithIcon(
content,
tr::ayu_MessageShotShowReactions(),
st::settingsButtonNoIcon
);
latestToggle->toggleOn(rpl::single(shotSettings.showReactions())
)->toggledValue(
) | rpl::skip(1) | on_next(
[=](bool enabled)
{
AyuSettings::getInstance().messageShotSettings().setShowReactions(enabled);
updatePreview();
},
content->lifetime());
}
if (hasHeaderDecorations) {
latestToggle = AddButtonWithIcon(
content,
tr::ayu_MessageShotShowHeaderDecorations(),
st::settingsButtonNoIcon
);
latestToggle->toggleOn(rpl::single(shotSettings.showHeaderDecorations())
)->toggledValue(
) | rpl::skip(1) | on_next(
[=](bool enabled)
{
AyuSettings::getInstance().messageShotSettings().setShowHeaderDecorations(enabled);
updatePreview();
},
content->lifetime());
}
if (hasReplies) {
latestToggle = AddButtonWithIcon(
content,
tr::ayu_MessageShotShowColorfulReplies(),
st::settingsButtonNoIcon
);
latestToggle->toggleOn(rpl::single(shotSettings.showColorfulReplies())
)->toggledValue(
) | rpl::skip(1) | on_next(
[=](bool enabled)
{
auto &currentSettings = AyuSettings::getInstance();
currentSettings.messageShotSettings().setShowColorfulReplies(enabled);
currentSettings.setSimpleQuotesAndReplies(!enabled);
_config.st = std::make_shared<Ui::ChatStyle>(_config.st.get());
updatePreview();
},
content->lifetime());
}
if (hasSpoilers) {
latestToggle = AddButtonWithIcon(
content,
tr::ayu_MessageShotRevealSpoilers(),
st::settingsButtonNoIcon
);
latestToggle->toggleOn(rpl::single(shotSettings.revealSpoilers())
)->toggledValue(
) | rpl::skip(1) | on_next(
[=](bool enabled)
{
AyuSettings::getInstance().messageShotSettings().setRevealSpoilers(enabled);
updatePreview();
},
content->lifetime());
}
AddSkip(content);
addButton(tr::ayu_MessageShotSave(),
[=]
{
const auto image = imageView->getImage();
const auto path = QFileDialog::getSaveFileName(
this,
tr::lng_save_file(tr::now),
QString(),
"*.png");
if (!path.isEmpty()) {
image.save(path);
}
_tookShot = true;
closeBox();
});
addButton(tr::ayu_MessageShotCopy(),
[=]
{
QGuiApplication::clipboard()->setImage(imageView->getImage());
_tookShot = true;
closeBox();
});
updatePreview();
firstPreviewLatch->await(std::chrono::seconds(1));
const auto boxWidth = imageView->getImage().width() / style::DevicePixelRatio() + (st::boxPadding.left() + st::boxPadding.right()) * 4;
boxClosing() | rpl::on_next(
[=]
{
AyuFeatures::MessageShot::resetCustomSelected();
AyuFeatures::MessageShot::resetDefaultSelected();
AyuFeatures::MessageShot::resetShotConfig();
AyuSettings::getInstance().setSimpleQuotesAndReplies(savedSimpleQuotesAndReplies);
},
content->lifetime());
setDimensionsToContent(boxWidth, content);
scrollToWidget(latestToggle);
}